Skip to content

Commit 59e2667

Browse files
support file size limit and mime types (#38)
1 parent 72a5bf8 commit 59e2667

18 files changed

+239
-111
lines changed

.gitignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -136,4 +136,6 @@ buildNumber.properties
136136

137137
# End of https://www.toptal.com/developers/gitignore/api/intellij+all,java,maven
138138

139-
infra/.env
139+
infra/.env
140+
141+
TestApplication.java

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## v1.1.0 - [Supports v0.29.2](https://github.com/supabase/storage-api/releases/tag/v0.29.2)
4+
- Added support for file size limit on bucket leve.
5+
- Added utility class file size for parsing file sizes with `B, KB, MB, GB` in them
6+
- Added support for allowed mime types on bucket leve, so you can now restrict uploads to buckets for a certain file type.
7+
38
## v1.0.1 - [Supports v0.28.2](https://github.com/supabase/storage-api/releases/tag/v0.28.2)
49
- Added new `FileTransformOptions`.
510
- Updated [javadocs](https://supabase-community.github.io/storage-java)

infra/docker-compose.yml

+13-11
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ services:
1010
KONG_DECLARATIVE_CONFIG: /var/lib/kong/kong.yml
1111
KONG_PLUGINS: request-transformer,cors,key-auth,http-log
1212
ports:
13-
- "8000:8000/tcp"
14-
- "8443:8443/tcp"
13+
- 8000:8000/tcp
14+
- 8443:8443/tcp
1515
rest:
1616
image: postgrest/postgrest:latest
1717
ports:
@@ -38,14 +38,17 @@ services:
3838
ANON_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxMzUzMTk4NSwiZXhwIjoxOTI5MTA3OTg1fQ.ReNhHIoXIOa-8tL1DO3e26mJmOTnYuvdgobwIYGzrLQ
3939
SERVICE_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaWF0IjoxNjEzNTMxOTg1LCJleHAiOjE5MjkxMDc5ODV9.FhK1kZdHmWdCIEZELt0QDCw6FIlCS8rVmp4RzaeI2LM
4040
PROJECT_REF: bjwdssmqcnupljrqypxz # can be any random string
41-
REGION: eu-west-1 # region where your bucket is located
4241
POSTGREST_URL: http://rest:3000
43-
GLOBAL_S3_BUCKET: java-supa-storage-testing # name of s3 bucket where you want to store objects
4442
PGRST_JWT_SECRET: super-secret-jwt-token-with-at-least-32-characters-long
45-
DATABASE_URL: postgres://postgres:postgres@db:5432/postgres
46-
PGOPTIONS: "-c search_path=storage"
43+
44+
REGION: eu-west-1 # region where your bucket is located
45+
GLOBAL_S3_BUCKET: java-supa-storage-testing # name of s3 bucket where you want to store objects
4746
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
4847
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
48+
49+
DATABASE_URL: postgres://postgres:postgres@db:5432/postgres
50+
PGOPTIONS: "-c search_path=storage"
51+
4952
FILE_SIZE_LIMIT: 52428800
5053
STORAGE_BACKEND: file
5154
FILE_STORAGE_BACKEND_PATH: /tmp/storage
@@ -54,13 +57,12 @@ services:
5457
volumes:
5558
- assets-volume:/tmp/storage
5659
healthcheck:
57-
test: [ 'CMD-SHELL', 'curl -f -LI http://localhost:5000/status' ]
58-
60+
test: ['CMD-SHELL', 'curl -f -LI http://localhost:5000/status']
5961
db:
6062
build:
6163
context: ./postgres
6264
ports:
63-
- "5432:5432"
65+
- 5432:5432
6466
command:
6567
- postgres
6668
- -c
@@ -79,9 +81,9 @@ services:
7981
imgproxy:
8082
image: darthsim/imgproxy
8183
ports:
82-
- "50020:8080"
84+
- 50020:8080
8385
volumes:
84-
- assets-volume:/tmp/storage
86+
- assets-volume:/tmp/storage
8587
environment:
8688
- IMGPROXY_LOCAL_FILESYSTEM_ROOT=/
8789
- IMGPROXY_USE_ETAG=true

infra/postgres/dummy-data.sql

+3-1
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,6 @@ CREATE POLICY authenticated_folder ON storage.objects for all USING (bucket_id='
5151
-- allow CRUD access to a folder in bucket2 to its owners
5252
CREATE POLICY crud_owner_only ON storage.objects for all USING (bucket_id='bucket2' and (storage.foldername(name))[1] = 'only_owner' and owner = auth.uid());
5353
-- allow CRUD access to bucket4
54-
CREATE POLICY open_all_update ON storage.objects for all WITH CHECK (bucket_id='bucket4');
54+
CREATE POLICY open_all_update ON storage.objects for all WITH CHECK (bucket_id='bucket4');
55+
56+
CREATE POLICY crud_my_bucket ON storage.objects for all USING (bucket_id='my-private-bucket' and auth.uid()::text = '317eadce-631a-4429-a0bb-f19a7a517b4a');

infra/postgres/storage-schema.sql

+39-39
Original file line numberDiff line numberDiff line change
@@ -37,68 +37,68 @@ CREATE INDEX name_prefix_search ON storage.objects(name text_pattern_ops);
3737
ALTER TABLE storage.objects ENABLE ROW LEVEL SECURITY;
3838

3939
CREATE OR REPLACE FUNCTION storage.foldername(name text)
40-
RETURNS text[]
41-
LANGUAGE plpgsql
40+
RETURNS text[]
41+
LANGUAGE plpgsql
4242
AS $function$
4343
DECLARE
44-
_parts text[];
44+
_parts text[];
4545
BEGIN
46-
select string_to_array(name, '/') into _parts;
47-
return _parts[1:array_length(_parts,1)-1];
46+
select string_to_array(name, '/') into _parts;
47+
return _parts[1:array_length(_parts,1)-1];
4848
END
4949
$function$;
5050

5151
CREATE OR REPLACE FUNCTION storage.filename(name text)
52-
RETURNS text
53-
LANGUAGE plpgsql
52+
RETURNS text
53+
LANGUAGE plpgsql
5454
AS $function$
5555
DECLARE
56-
_parts text[];
56+
_parts text[];
5757
BEGIN
58-
select string_to_array(name, '/') into _parts;
59-
return _parts[array_length(_parts,1)];
58+
select string_to_array(name, '/') into _parts;
59+
return _parts[array_length(_parts,1)];
6060
END
6161
$function$;
6262

6363
CREATE OR REPLACE FUNCTION storage.extension(name text)
64-
RETURNS text
65-
LANGUAGE plpgsql
64+
RETURNS text
65+
LANGUAGE plpgsql
6666
AS $function$
6767
DECLARE
68-
_parts text[];
69-
_filename text;
68+
_parts text[];
69+
_filename text;
7070
BEGIN
71-
select string_to_array(name, '/') into _parts;
72-
select _parts[array_length(_parts,1)] into _filename;
73-
return split_part(_filename, '.', 2);
71+
select string_to_array(name, '/') into _parts;
72+
select _parts[array_length(_parts,1)] into _filename;
73+
return split_part(_filename, '.', 2);
7474
END
7575
$function$;
7676

7777
CREATE OR REPLACE FUNCTION storage.search(prefix text, bucketname text, limits int DEFAULT 100, levels int DEFAULT 1, offsets int DEFAULT 0)
78-
RETURNS TABLE (
79-
name text,
80-
id uuid,
81-
updated_at TIMESTAMPTZ,
82-
created_at TIMESTAMPTZ,
83-
last_accessed_at TIMESTAMPTZ,
84-
metadata jsonb
85-
)
86-
LANGUAGE plpgsql
78+
RETURNS TABLE (
79+
name text,
80+
id uuid,
81+
updated_at TIMESTAMPTZ,
82+
created_at TIMESTAMPTZ,
83+
last_accessed_at TIMESTAMPTZ,
84+
metadata jsonb
85+
)
86+
LANGUAGE plpgsql
8787
AS $function$
8888
BEGIN
89-
return query
90-
with files_folders as (
91-
select ((string_to_array(objects.name, '/'))[levels]) as folder
92-
from objects
93-
where objects.name ilike prefix || '%'
94-
and bucket_id = bucketname
95-
GROUP by folder
96-
limit limits
97-
offset offsets
98-
)
99-
select files_folders.folder as name, objects.id, objects.updated_at, objects.created_at, objects.last_accessed_at, objects.metadata from files_folders
100-
left join objects
101-
on prefix || files_folders.folder = objects.name and objects.bucket_id=bucketname;
89+
return query
90+
with files_folders as (
91+
select ((string_to_array(objects.name, '/'))[levels]) as folder
92+
from objects
93+
where objects.name ilike prefix || '%'
94+
and bucket_id = bucketname
95+
GROUP by folder
96+
limit limits
97+
offset offsets
98+
)
99+
select files_folders.folder as name, objects.id, objects.updated_at, objects.created_at, objects.last_accessed_at, objects.metadata from files_folders
100+
left join objects
101+
on prefix || files_folders.folder = objects.name and objects.bucket_id=bucketname;
102102
END
103103
$function$;
104104

infra/storage/Dockerfile

+1-1
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.0
22

33
RUN apk add curl --no-cache

pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>io.supabase</groupId>
88
<artifactId>storage-java</artifactId>
9-
<version>1.0.1</version>
9+
<version>1.1.0</version>
1010

1111
<name>Storage Java</name>
1212
<description>An async client library for the Supabase Storage API in Java</description>

src/main/java/io/supabase/StorageClient.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public StorageClient(String apiKey, String url) {
1515
this(url, new HashMap<>() {{
1616
put("Authorization", "Bearer " + apiKey);
1717
}});
18-
// Validate URL and throw if not a valid url.
18+
//TODO: Validate URL and throw if not a valid url.
1919
}
2020

2121
private StorageClient(String url, Map<String, String> headers) {

src/main/java/io/supabase/api/StorageBucketAPI.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package io.supabase.api;
22

3+
import com.google.gson.JsonArray;
34
import com.google.gson.JsonObject;
45
import com.google.gson.reflect.TypeToken;
56
import io.supabase.data.bucket.Bucket;
67
import io.supabase.data.bucket.BucketCreateOptions;
78
import io.supabase.data.bucket.BucketUpdateOptions;
89
import io.supabase.data.bucket.CreateBucketResponse;
10+
import io.supabase.utils.FileSize;
911
import io.supabase.utils.MessageResponse;
1012
import io.supabase.utils.RestUtils;
1113

@@ -37,7 +39,7 @@ public StorageBucketAPI(String url, Map<String, String> headers) {
3739
*/
3840
@Override
3941
public CompletableFuture<CreateBucketResponse> createBucket(String bucketId) {
40-
return createBucket(bucketId, new BucketCreateOptions(false));
42+
return createBucket(bucketId, new BucketCreateOptions(false, new FileSize(0), null));
4143
}
4244

4345
/**
@@ -50,9 +52,15 @@ public CompletableFuture<CreateBucketResponse> createBucket(String bucketId) {
5052
@Override
5153
public CompletableFuture<CreateBucketResponse> createBucket(String bucketId, BucketCreateOptions options) {
5254
JsonObject body = new JsonObject();
55+
JsonArray allowedMimeTypes = new JsonArray();
56+
for (String mimeType : options.getAllowedMimeTypes()) {
57+
allowedMimeTypes.add(mimeType);
58+
}
5359
body.addProperty("name", bucketId);
5460
body.addProperty("id", bucketId);
5561
body.addProperty("public", options.isPublic());
62+
body.addProperty("file_size_limit", options.getFileSizeLimit().getFileSizeAsB());
63+
body.add("allowed_mime_types", allowedMimeTypes);
5664
return RestUtils.post(new TypeToken<CreateBucketResponse>() {
5765
}, headers, url + "bucket", body);
5866
}
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,28 @@
11
package io.supabase.data.bucket;
22

33

4+
import com.google.gson.annotations.JsonAdapter;
45
import com.google.gson.annotations.SerializedName;
5-
6-
public class Bucket {
7-
private final String id;
8-
private final String name;
9-
private final String owner;
10-
@SerializedName("public")
11-
private final boolean isBucketPublic;
12-
@SerializedName("created_at")
13-
private final String createdAt;
14-
@SerializedName("updated_at")
15-
private final String updatedAt;
16-
17-
public Bucket(String id, String name, String owner, boolean isBucketPublic, String createdAt, String updatedAt) {
18-
this.id = id;
19-
this.name = name;
20-
this.owner = owner;
21-
this.isBucketPublic = isBucketPublic;
22-
this.createdAt = createdAt;
23-
this.updatedAt = updatedAt;
24-
}
25-
26-
public String getId() {
27-
return id;
28-
}
29-
30-
public String getName() {
31-
return name;
32-
}
33-
34-
public String getOwner() {
35-
return owner;
36-
}
37-
38-
public boolean isBucketPublic() {
39-
return isBucketPublic;
40-
}
41-
42-
public String getCreatedAt() {
43-
return createdAt;
44-
}
45-
46-
public String getUpdatedAt() {
47-
return updatedAt;
6+
import io.supabase.utils.FileSize;
7+
8+
import java.util.List;
9+
10+
public record Bucket(String id, String name, String owner,
11+
@SerializedName("public") boolean isBucketPublic,
12+
@SerializedName("file_size_limit") @JsonAdapter(FileSize.class) FileSize fileSizeLimit,
13+
@SerializedName("allowed_mime_types") List<String> allowedMimeTypes,
14+
@SerializedName("created_at") String createdAt,
15+
@SerializedName("updated_at") String updatedAt) {
16+
17+
@Override
18+
public String toString() {
19+
return "Bucket{" +
20+
"id='" + id + '\'' +
21+
", name='" + name + '\'' +
22+
", owner='" + owner + '\'' +
23+
", isBucketPublic=" + isBucketPublic +
24+
", createdAt='" + createdAt + '\'' +
25+
", updatedAt='" + updatedAt + '\'' +
26+
'}';
4827
}
4928
}
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package io.supabase.data.bucket;
22

3-
public class BucketCreateOptions extends BucketOptions {
3+
import io.supabase.utils.FileSize;
4+
5+
import java.util.List;
46

5-
public BucketCreateOptions(boolean isPublic) {
6-
super(isPublic);
7+
public class BucketCreateOptions extends BucketOptions {
8+
public BucketCreateOptions(boolean isPublic, FileSize fileSizeLimit, List<String> allowedMimeTypes) {
9+
super(isPublic, fileSizeLimit, allowedMimeTypes == null ? List.of() : allowedMimeTypes);
710
}
811
}
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,29 @@
11
package io.supabase.data.bucket;
22

3+
import io.supabase.utils.FileSize;
4+
5+
import java.util.List;
6+
37
public class BucketOptions {
48
private final boolean isPublic;
9+
private final FileSize fileSizeLimit;
10+
private final List<String> allowedMimeTypes;
511

6-
public BucketOptions(boolean isPublic) {
12+
public BucketOptions(boolean isPublic, FileSize fileSizeLimit, List<String> allowedMimeTypes) {
713
this.isPublic = isPublic;
14+
this.fileSizeLimit = fileSizeLimit;
15+
this.allowedMimeTypes = List.copyOf(allowedMimeTypes);
816
}
917

1018
public boolean isPublic() {
1119
return isPublic;
1220
}
21+
22+
public FileSize getFileSizeLimit() {
23+
return fileSizeLimit;
24+
}
25+
26+
public List<String> getAllowedMimeTypes() {
27+
return List.copyOf(allowedMimeTypes);
28+
}
1329
}
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package io.supabase.data.bucket;
22

3+
import io.supabase.utils.FileSize;
4+
5+
import java.util.List;
6+
37
public class BucketUpdateOptions extends BucketOptions {
4-
public BucketUpdateOptions(boolean isPublic) {
5-
super(isPublic);
8+
public BucketUpdateOptions(boolean isPublic, FileSize fileSizeLimit, List<String> allowedMimeTypes) {
9+
super(isPublic, fileSizeLimit, allowedMimeTypes == null ? List.of() : allowedMimeTypes);
610
}
711
}

0 commit comments

Comments
 (0)