diff --git a/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/BucketApi.kt b/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/BucketApi.kt index aa164c97..5c76d495 100644 --- a/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/BucketApi.kt +++ b/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/BucketApi.kt @@ -34,36 +34,33 @@ sealed interface BucketApi { * Uploads a file in [bucketId] under [path] * @param path The path to upload the file to * @param data The data to upload - * @param upsert Whether to overwrite an existing file * @return the key to the uploaded file * @throws IllegalArgumentException if data to upload is empty * @throws RestException or one of its subclasses if receiving an error response * @throws HttpRequestTimeoutException if the request timed out * @throws HttpRequestException on network related issues */ - suspend fun upload(path: String, data: ByteArray, upsert: Boolean = false, options: FileOptionBuilder.() -> Unit = {}): FileUploadResponse { + suspend fun upload(path: String, data: ByteArray, options: FileOptionBuilder.() -> Unit = {}): FileUploadResponse { require(data.isNotEmpty()) { "The data to upload should not be empty" } - return upload(path, UploadData(ByteReadChannel(data), data.size.toLong()), upsert, options) + return upload(path, UploadData(ByteReadChannel(data), data.size.toLong()), options) } /** * Uploads a file in [bucketId] under [path] * @param path The path to upload the file to * @param data The data to upload - * @param upsert Whether to overwrite an existing file * @return the key to the uploaded file * @throws RestException or one of its subclasses if receiving an error response * @throws HttpRequestTimeoutException if the request timed out * @throws HttpRequestException on network related issues */ - suspend fun upload(path: String, data: UploadData, upsert: Boolean = false, options: FileOptionBuilder.() -> Unit = {}): FileUploadResponse + suspend fun upload(path: String, data: UploadData, options: FileOptionBuilder.() -> Unit = {}): FileUploadResponse /** * Uploads a file in [bucketId] under [path] using a presigned url * @param path The path to upload the file to - * @param token The presigned url token + * @param token The pre-signed url token * @param data The data to upload - * @param upsert Whether to overwrite an existing file * @return the key of the uploaded file * @throws IllegalArgumentException if data to upload is empty */ @@ -71,11 +68,10 @@ sealed interface BucketApi { path: String, token: String, data: ByteArray, - upsert: Boolean = false, options: FileOptionBuilder.() -> Unit = {} ): FileUploadResponse { require(data.isNotEmpty()) { "The data to upload should not be empty" } - return uploadToSignedUrl(path, token, UploadData(ByteReadChannel(data), data.size.toLong()), upsert, options) + return uploadToSignedUrl(path, token, UploadData(ByteReadChannel(data), data.size.toLong()), options) } /** @@ -83,42 +79,39 @@ sealed interface BucketApi { * @param path The path to upload the file to * @param token The presigned url token * @param data The data to upload - * @param upsert Whether to overwrite an existing file * @return the key of the uploaded file * @throws RestException or one of its subclasses if receiving an error response * @throws HttpRequestTimeoutException if the request timed out * @throws HttpRequestException on network related issues * @throws HttpRequestException on network related issues */ - suspend fun uploadToSignedUrl(path: String, token: String, data: UploadData, upsert: Boolean = false, options: FileOptionBuilder.() -> Unit = {}): FileUploadResponse + suspend fun uploadToSignedUrl(path: String, token: String, data: UploadData, options: FileOptionBuilder.() -> Unit = {}): FileUploadResponse /** * Updates a file in [bucketId] under [path] * @param path The path to update the file to * @param data The new data - * @param upsert Whether to overwrite an existing file * @return the key to the updated file * @throws IllegalArgumentException if data to upload is empty * @throws RestException or one of its subclasses if receiving an error response * @throws HttpRequestTimeoutException if the request timed out * @throws HttpRequestException on network related issues */ - suspend fun update(path: String, data: ByteArray, upsert: Boolean = false, options: FileOptionBuilder.() -> Unit = {}): FileUploadResponse { + suspend fun update(path: String, data: ByteArray, options: FileOptionBuilder.() -> Unit = {}): FileUploadResponse { require(data.isNotEmpty()) { "The data to upload should not be empty" } - return update(path, UploadData(ByteReadChannel(data), data.size.toLong()), upsert, options) + return update(path, UploadData(ByteReadChannel(data), data.size.toLong()), options) } /** * Updates a file in [bucketId] under [path] * @param path The path to update the file to * @param data The new data - * @param upsert Whether to overwrite an existing file * @return the key to the updated file * @throws RestException or one of its subclasses if receiving an error response * @throws HttpRequestTimeoutException if the request timed out * @throws HttpRequestException on network related issues */ - suspend fun update(path: String, data: UploadData, upsert: Boolean = false, options: FileOptionBuilder.() -> Unit = {}): FileUploadResponse + suspend fun update(path: String, data: UploadData, options: FileOptionBuilder.() -> Unit = {}): FileUploadResponse /** * Deletes all files in [bucketId] with in [paths] diff --git a/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/BucketApiImpl.kt b/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/BucketApiImpl.kt index 6051ccf3..76c83d16 100644 --- a/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/BucketApiImpl.kt +++ b/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/BucketApiImpl.kt @@ -44,21 +44,19 @@ internal class BucketApiImpl(override val bucketId: String, val storage: Storage override suspend fun update( path: String, data: UploadData, - upsert: Boolean, options: FileOptionBuilder.() -> Unit ): FileUploadResponse = uploadOrUpdate( - HttpMethod.Put, bucketId, path, data, upsert, options + HttpMethod.Put, bucketId, path, data, options ) override suspend fun uploadToSignedUrl( path: String, token: String, data: UploadData, - upsert: Boolean, options: FileOptionBuilder.() -> Unit ): FileUploadResponse { - return uploadToSignedUrl(path, token, data, upsert, options) {} + return uploadToSignedUrl(path, token, data, options) {} } override suspend fun createSignedUploadUrl(path: String): UploadSignedUrl { @@ -77,11 +75,10 @@ internal class BucketApiImpl(override val bucketId: String, val storage: Storage override suspend fun upload( path: String, data: UploadData, - upsert: Boolean, options: FileOptionBuilder.() -> Unit ): FileUploadResponse = uploadOrUpdate( - HttpMethod.Post, bucketId, path, data, upsert, options + HttpMethod.Post, bucketId, path, data, options ) override suspend fun delete(paths: Collection) { @@ -249,14 +246,13 @@ internal class BucketApiImpl(override val bucketId: String, val storage: Storage bucket: String, path: String, data: UploadData, - upsert: Boolean, options: FileOptionBuilder.() -> Unit, extra: HttpRequestBuilder.() -> Unit = {} ): FileUploadResponse { val optionBuilder = FileOptionBuilder(storage.serializer).apply(options) val response = storage.api.request("object/$bucket/$path") { this.method = method - defaultUploadRequest(path, data, upsert, optionBuilder, extra) + defaultUploadRequest(path, data, optionBuilder, extra) }.body() val key = response["Key"]?.jsonPrimitive?.content ?: error("Expected a key in a upload response") @@ -271,14 +267,13 @@ internal class BucketApiImpl(override val bucketId: String, val storage: Storage path: String, token: String, data: UploadData, - upsert: Boolean, options: FileOptionBuilder.() -> Unit, extra: HttpRequestBuilder.() -> Unit = {} ): FileUploadResponse { val optionBuilder = FileOptionBuilder(storage.serializer).apply(options) val response = storage.api.put("object/upload/sign/$bucketId/$path") { parameter("token", token) - defaultUploadRequest(path, data, upsert, optionBuilder, extra) + defaultUploadRequest(path, data, optionBuilder, extra) }.body() val key = response["Key"]?.jsonPrimitive?.content ?: error("Expected a key in a upload response") @@ -291,17 +286,16 @@ internal class BucketApiImpl(override val bucketId: String, val storage: Storage private fun HttpRequestBuilder.defaultUploadRequest( path: String, data: UploadData, - upsert: Boolean, optionBuilder: FileOptionBuilder, extra: HttpRequestBuilder.() -> Unit ) { setBody(object : OutgoingContent.ReadChannelContent() { - override val contentType: ContentType = ContentType.defaultForFilePath(path) + override val contentType: ContentType = optionBuilder.contentType ?: ContentType.defaultForFilePath(path) override val contentLength: Long = data.size override fun readFrom(): ByteReadChannel = data.stream }) - header(HttpHeaders.ContentType, ContentType.defaultForFilePath(path)) - header(UPSERT_HEADER, upsert.toString()) + header(HttpHeaders.ContentType, optionBuilder.contentType ?: ContentType.defaultForFilePath(path)) + header(UPSERT_HEADER, optionBuilder.upsert.toString()) optionBuilder.userMetadata?.let { header("x-metadata", Base64.Default.encode(it.toString().encodeToByteArray()).also((::println))) } diff --git a/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/FileOptionBuilder.kt b/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/FileOptionBuilder.kt index d59c30e5..67b998ca 100644 --- a/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/FileOptionBuilder.kt +++ b/Storage/src/commonMain/kotlin/io/github/jan/supabase/storage/FileOptionBuilder.kt @@ -2,6 +2,7 @@ package io.github.jan.supabase.storage import io.github.jan.supabase.SupabaseSerializer import io.github.jan.supabase.encodeToJsonElement +import io.ktor.http.ContentType import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObjectBuilder import kotlinx.serialization.json.buildJsonObject @@ -11,10 +12,14 @@ import kotlinx.serialization.json.jsonObject * Builder for uploading files with additional options * @param serializer The serializer to use for encoding the metadata * @param userMetadata The user metadata to upload with the file + * @param upsert Whether to update the file if it already exists + * @param contentType The content type of the file. If null, the content type will be inferred from the file extension */ class FileOptionBuilder( @PublishedApi internal val serializer: SupabaseSerializer, var userMetadata: JsonObject? = null, + var upsert: Boolean = false, + var contentType: ContentType? = null, ) { /** diff --git a/Storage/src/commonTest/kotlin/BucketApiTest.kt b/Storage/src/commonTest/kotlin/BucketApiTest.kt index 2dc3640c..ac09886a 100644 --- a/Storage/src/commonTest/kotlin/BucketApiTest.kt +++ b/Storage/src/commonTest/kotlin/BucketApiTest.kt @@ -89,8 +89,9 @@ class BucketApiTest { ) }, request = { client, expectedPath, data, meta -> - client.storage[bucketId].upload(expectedPath, data, upsert = true) { + client.storage[bucketId].upload(expectedPath, data) { userMetadata = meta + upsert = true } } ) @@ -115,8 +116,9 @@ class BucketApiTest { ) }, request = { client, expectedPath, data, meta -> - client.storage[bucketId].uploadToSignedUrl(path = expectedPath, token = expectedToken, data = data, upsert = false) { + client.storage[bucketId].uploadToSignedUrl(path = expectedPath, token = expectedToken, data = data) { userMetadata = meta + upsert = false } } ) @@ -141,8 +143,9 @@ class BucketApiTest { ) }, request = { client, expectedPath, data, meta -> - client.storage[bucketId].uploadToSignedUrl(path = expectedPath, token = expectedToken, data = data, upsert = true) { + client.storage[bucketId].uploadToSignedUrl(path = expectedPath, token = expectedToken, data = data) { userMetadata = meta + upsert = true } } ) @@ -181,8 +184,9 @@ class BucketApiTest { ) }, request = { client, expectedPath, data, meta -> - client.storage[bucketId].update(expectedPath, data, upsert = true) { + client.storage[bucketId].update(expectedPath, data) { userMetadata = meta + upsert = true } } )