Skip to content

Commit

Permalink
support file size limit and mime types (#38)
Browse files Browse the repository at this point in the history
  • Loading branch information
MagnusHJensen authored May 30, 2023
1 parent 72a5bf8 commit 59e2667
Show file tree
Hide file tree
Showing 18 changed files with 239 additions and 111 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,6 @@ buildNumber.properties

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

infra/.env
infra/.env

TestApplication.java
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## v1.1.0 - [Supports v0.29.2](https://github.com/supabase/storage-api/releases/tag/v0.29.2)
- Added support for file size limit on bucket leve.
- Added utility class file size for parsing file sizes with `B, KB, MB, GB` in them
- Added support for allowed mime types on bucket leve, so you can now restrict uploads to buckets for a certain file type.

## v1.0.1 - [Supports v0.28.2](https://github.com/supabase/storage-api/releases/tag/v0.28.2)
- Added new `FileTransformOptions`.
- Updated [javadocs](https://supabase-community.github.io/storage-java)
Expand Down
24 changes: 13 additions & 11 deletions infra/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ services:
KONG_DECLARATIVE_CONFIG: /var/lib/kong/kong.yml
KONG_PLUGINS: request-transformer,cors,key-auth,http-log
ports:
- "8000:8000/tcp"
- "8443:8443/tcp"
- 8000:8000/tcp
- 8443:8443/tcp
rest:
image: postgrest/postgrest:latest
ports:
Expand All @@ -38,14 +38,17 @@ services:
ANON_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxMzUzMTk4NSwiZXhwIjoxOTI5MTA3OTg1fQ.ReNhHIoXIOa-8tL1DO3e26mJmOTnYuvdgobwIYGzrLQ
SERVICE_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaWF0IjoxNjEzNTMxOTg1LCJleHAiOjE5MjkxMDc5ODV9.FhK1kZdHmWdCIEZELt0QDCw6FIlCS8rVmp4RzaeI2LM
PROJECT_REF: bjwdssmqcnupljrqypxz # can be any random string
REGION: eu-west-1 # region where your bucket is located
POSTGREST_URL: http://rest:3000
GLOBAL_S3_BUCKET: java-supa-storage-testing # name of s3 bucket where you want to store objects
PGRST_JWT_SECRET: super-secret-jwt-token-with-at-least-32-characters-long
DATABASE_URL: postgres://postgres:postgres@db:5432/postgres
PGOPTIONS: "-c search_path=storage"

REGION: eu-west-1 # region where your bucket is located
GLOBAL_S3_BUCKET: java-supa-storage-testing # name of s3 bucket where you want to store objects
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}

DATABASE_URL: postgres://postgres:postgres@db:5432/postgres
PGOPTIONS: "-c search_path=storage"

FILE_SIZE_LIMIT: 52428800
STORAGE_BACKEND: file
FILE_STORAGE_BACKEND_PATH: /tmp/storage
Expand All @@ -54,13 +57,12 @@ services:
volumes:
- assets-volume:/tmp/storage
healthcheck:
test: [ 'CMD-SHELL', 'curl -f -LI http://localhost:5000/status' ]

test: ['CMD-SHELL', 'curl -f -LI http://localhost:5000/status']
db:
build:
context: ./postgres
ports:
- "5432:5432"
- 5432:5432
command:
- postgres
- -c
Expand All @@ -79,9 +81,9 @@ services:
imgproxy:
image: darthsim/imgproxy
ports:
- "50020:8080"
- 50020:8080
volumes:
- assets-volume:/tmp/storage
- assets-volume:/tmp/storage
environment:
- IMGPROXY_LOCAL_FILESYSTEM_ROOT=/
- IMGPROXY_USE_ETAG=true
Expand Down
4 changes: 3 additions & 1 deletion infra/postgres/dummy-data.sql
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,6 @@ CREATE POLICY authenticated_folder ON storage.objects for all USING (bucket_id='
-- allow CRUD access to a folder in bucket2 to its owners
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());
-- allow CRUD access to bucket4
CREATE POLICY open_all_update ON storage.objects for all WITH CHECK (bucket_id='bucket4');
CREATE POLICY open_all_update ON storage.objects for all WITH CHECK (bucket_id='bucket4');

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');
78 changes: 39 additions & 39 deletions infra/postgres/storage-schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -37,68 +37,68 @@ CREATE INDEX name_prefix_search ON storage.objects(name text_pattern_ops);
ALTER TABLE storage.objects ENABLE ROW LEVEL SECURITY;

CREATE OR REPLACE FUNCTION storage.foldername(name text)
RETURNS text[]
LANGUAGE plpgsql
RETURNS text[]
LANGUAGE plpgsql
AS $function$
DECLARE
_parts text[];
_parts text[];
BEGIN
select string_to_array(name, '/') into _parts;
return _parts[1:array_length(_parts,1)-1];
select string_to_array(name, '/') into _parts;
return _parts[1:array_length(_parts,1)-1];
END
$function$;

CREATE OR REPLACE FUNCTION storage.filename(name text)
RETURNS text
LANGUAGE plpgsql
RETURNS text
LANGUAGE plpgsql
AS $function$
DECLARE
_parts text[];
_parts text[];
BEGIN
select string_to_array(name, '/') into _parts;
return _parts[array_length(_parts,1)];
select string_to_array(name, '/') into _parts;
return _parts[array_length(_parts,1)];
END
$function$;

CREATE OR REPLACE FUNCTION storage.extension(name text)
RETURNS text
LANGUAGE plpgsql
RETURNS text
LANGUAGE plpgsql
AS $function$
DECLARE
_parts text[];
_filename text;
_parts text[];
_filename text;
BEGIN
select string_to_array(name, '/') into _parts;
select _parts[array_length(_parts,1)] into _filename;
return split_part(_filename, '.', 2);
select string_to_array(name, '/') into _parts;
select _parts[array_length(_parts,1)] into _filename;
return split_part(_filename, '.', 2);
END
$function$;

CREATE OR REPLACE FUNCTION storage.search(prefix text, bucketname text, limits int DEFAULT 100, levels int DEFAULT 1, offsets int DEFAULT 0)
RETURNS TABLE (
name text,
id uuid,
updated_at TIMESTAMPTZ,
created_at TIMESTAMPTZ,
last_accessed_at TIMESTAMPTZ,
metadata jsonb
)
LANGUAGE plpgsql
RETURNS TABLE (
name text,
id uuid,
updated_at TIMESTAMPTZ,
created_at TIMESTAMPTZ,
last_accessed_at TIMESTAMPTZ,
metadata jsonb
)
LANGUAGE plpgsql
AS $function$
BEGIN
return query
with files_folders as (
select ((string_to_array(objects.name, '/'))[levels]) as folder
from objects
where objects.name ilike prefix || '%'
and bucket_id = bucketname
GROUP by folder
limit limits
offset offsets
)
select files_folders.folder as name, objects.id, objects.updated_at, objects.created_at, objects.last_accessed_at, objects.metadata from files_folders
left join objects
on prefix || files_folders.folder = objects.name and objects.bucket_id=bucketname;
return query
with files_folders as (
select ((string_to_array(objects.name, '/'))[levels]) as folder
from objects
where objects.name ilike prefix || '%'
and bucket_id = bucketname
GROUP by folder
limit limits
offset offsets
)
select files_folders.folder as name, objects.id, objects.updated_at, objects.created_at, objects.last_accessed_at, objects.metadata from files_folders
left join objects
on prefix || files_folders.folder = objects.name and objects.bucket_id=bucketname;
END
$function$;

Expand Down
2 changes: 1 addition & 1 deletion infra/storage/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
FROM supabase/storage-api:v0.28.0
FROM supabase/storage-api:v0.29.0

RUN apk add curl --no-cache
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>io.supabase</groupId>
<artifactId>storage-java</artifactId>
<version>1.0.1</version>
<version>1.1.0</version>

<name>Storage Java</name>
<description>An async client library for the Supabase Storage API in Java</description>
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/io/supabase/StorageClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public StorageClient(String apiKey, String url) {
this(url, new HashMap<>() {{
put("Authorization", "Bearer " + apiKey);
}});
// Validate URL and throw if not a valid url.
//TODO: Validate URL and throw if not a valid url.
}

private StorageClient(String url, Map<String, String> headers) {
Expand Down
10 changes: 9 additions & 1 deletion src/main/java/io/supabase/api/StorageBucketAPI.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package io.supabase.api;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import io.supabase.data.bucket.Bucket;
import io.supabase.data.bucket.BucketCreateOptions;
import io.supabase.data.bucket.BucketUpdateOptions;
import io.supabase.data.bucket.CreateBucketResponse;
import io.supabase.utils.FileSize;
import io.supabase.utils.MessageResponse;
import io.supabase.utils.RestUtils;

Expand Down Expand Up @@ -37,7 +39,7 @@ public StorageBucketAPI(String url, Map<String, String> headers) {
*/
@Override
public CompletableFuture<CreateBucketResponse> createBucket(String bucketId) {
return createBucket(bucketId, new BucketCreateOptions(false));
return createBucket(bucketId, new BucketCreateOptions(false, new FileSize(0), null));
}

/**
Expand All @@ -50,9 +52,15 @@ public CompletableFuture<CreateBucketResponse> createBucket(String bucketId) {
@Override
public CompletableFuture<CreateBucketResponse> createBucket(String bucketId, BucketCreateOptions options) {
JsonObject body = new JsonObject();
JsonArray allowedMimeTypes = new JsonArray();
for (String mimeType : options.getAllowedMimeTypes()) {
allowedMimeTypes.add(mimeType);
}
body.addProperty("name", bucketId);
body.addProperty("id", bucketId);
body.addProperty("public", options.isPublic());
body.addProperty("file_size_limit", options.getFileSizeLimit().getFileSizeAsB());
body.add("allowed_mime_types", allowedMimeTypes);
return RestUtils.post(new TypeToken<CreateBucketResponse>() {
}, headers, url + "bucket", body);
}
Expand Down
65 changes: 22 additions & 43 deletions src/main/java/io/supabase/data/bucket/Bucket.java
Original file line number Diff line number Diff line change
@@ -1,49 +1,28 @@
package io.supabase.data.bucket;


import com.google.gson.annotations.JsonAdapter;
import com.google.gson.annotations.SerializedName;

public class Bucket {
private final String id;
private final String name;
private final String owner;
@SerializedName("public")
private final boolean isBucketPublic;
@SerializedName("created_at")
private final String createdAt;
@SerializedName("updated_at")
private final String updatedAt;

public Bucket(String id, String name, String owner, boolean isBucketPublic, String createdAt, String updatedAt) {
this.id = id;
this.name = name;
this.owner = owner;
this.isBucketPublic = isBucketPublic;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}

public String getId() {
return id;
}

public String getName() {
return name;
}

public String getOwner() {
return owner;
}

public boolean isBucketPublic() {
return isBucketPublic;
}

public String getCreatedAt() {
return createdAt;
}

public String getUpdatedAt() {
return updatedAt;
import io.supabase.utils.FileSize;

import java.util.List;

public record Bucket(String id, String name, String owner,
@SerializedName("public") boolean isBucketPublic,
@SerializedName("file_size_limit") @JsonAdapter(FileSize.class) FileSize fileSizeLimit,
@SerializedName("allowed_mime_types") List<String> allowedMimeTypes,
@SerializedName("created_at") String createdAt,
@SerializedName("updated_at") String updatedAt) {

@Override
public String toString() {
return "Bucket{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", owner='" + owner + '\'' +
", isBucketPublic=" + isBucketPublic +
", createdAt='" + createdAt + '\'' +
", updatedAt='" + updatedAt + '\'' +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package io.supabase.data.bucket;

public class BucketCreateOptions extends BucketOptions {
import io.supabase.utils.FileSize;

import java.util.List;

public BucketCreateOptions(boolean isPublic) {
super(isPublic);
public class BucketCreateOptions extends BucketOptions {
public BucketCreateOptions(boolean isPublic, FileSize fileSizeLimit, List<String> allowedMimeTypes) {
super(isPublic, fileSizeLimit, allowedMimeTypes == null ? List.of() : allowedMimeTypes);
}
}
18 changes: 17 additions & 1 deletion src/main/java/io/supabase/data/bucket/BucketOptions.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
package io.supabase.data.bucket;

import io.supabase.utils.FileSize;

import java.util.List;

public class BucketOptions {
private final boolean isPublic;
private final FileSize fileSizeLimit;
private final List<String> allowedMimeTypes;

public BucketOptions(boolean isPublic) {
public BucketOptions(boolean isPublic, FileSize fileSizeLimit, List<String> allowedMimeTypes) {
this.isPublic = isPublic;
this.fileSizeLimit = fileSizeLimit;
this.allowedMimeTypes = List.copyOf(allowedMimeTypes);
}

public boolean isPublic() {
return isPublic;
}

public FileSize getFileSizeLimit() {
return fileSizeLimit;
}

public List<String> getAllowedMimeTypes() {
return List.copyOf(allowedMimeTypes);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package io.supabase.data.bucket;

import io.supabase.utils.FileSize;

import java.util.List;

public class BucketUpdateOptions extends BucketOptions {
public BucketUpdateOptions(boolean isPublic) {
super(isPublic);
public BucketUpdateOptions(boolean isPublic, FileSize fileSizeLimit, List<String> allowedMimeTypes) {
super(isPublic, fileSizeLimit, allowedMimeTypes == null ? List.of() : allowedMimeTypes);
}
}
Loading

0 comments on commit 59e2667

Please sign in to comment.