Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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