Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for file metadata, info and exists #694

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,25 @@ sealed interface BucketApi {
filter: BucketListFilter.() -> Unit = {}
): List<FileObject>

/**
* Returns information about the file under [path]
* @param path The path to get information about
* @return The file object
* @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 info(path: String): FileObjectV2

/**
* Checks if a file exists under [path]
* @return true if the file exists, false otherwise
* @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 exists(path: String): Boolean

/**
* Changes the bucket's public status to [public]
* @throws RestException or one of its subclasses if receiving an error response
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.jan.supabase.storage

import io.github.jan.supabase.exceptions.RestException
import io.github.jan.supabase.putJsonObject
import io.github.jan.supabase.safeBody
import io.github.jan.supabase.storage.BucketApi.Companion.UPSERT_HEADER
Expand All @@ -14,6 +15,7 @@ import io.ktor.client.statement.bodyAsChannel
import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpMethod
import io.ktor.http.HttpStatusCode
import io.ktor.http.Url
import io.ktor.http.content.OutgoingContent
import io.ktor.http.defaultForFilePath
Expand Down Expand Up @@ -220,6 +222,23 @@ internal class BucketApiImpl(override val bucketId: String, val storage: Storage
}).safeBody()
}

override suspend fun info(path: String): FileObjectV2 {
val response = storage.api.get("object/info/$bucketId/$path")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, lets wait a bit for adding the info method as there is a bug in the JS lib, this should be object/info/public, but there is also a object/info/authenticated.

We're figuring out internally on how we're naming methods.

So lets just wait a bit before merging this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I commented on the JS PR regarding this. I couldn't make this work on the hosted Supabase instance, but the self-hosted Docker one works.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@grdsdev Any news regarding this?

return response.safeBody<FileObjectV2>().copy(serializer = storage.serializer)
}

override suspend fun exists(path: String): Boolean {
try {
storage.api.request("object/$bucketId/$path") {
method = HttpMethod.Head
}
return true
} catch (e: RestException) {
if (e.statusCode in listOf(HttpStatusCode.NotFound.value, HttpStatusCode.BadRequest.value)) return false
throw e
}
}

private fun defaultUploadUrl(path: String) = "object/$bucketId/$path"

private fun uploadToSignedUrlUrl(path: String, token: String) = "object/upload/sign/$bucketId/$path?token=$token"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package io.github.jan.supabase.storage

import io.github.jan.supabase.SupabaseSerializer
import io.github.jan.supabase.decode
import io.github.jan.supabase.serializer.KotlinXSerializer
import io.ktor.http.ContentType
import kotlinx.datetime.Instant
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.json.JsonObject

/**
Expand All @@ -25,4 +30,60 @@ data class FileObject(
@SerialName("last_accessed_at")
val lastAccessedAt: Instant?,
val metadata: JsonObject?
)
)

/**
* Represents a file or a folder in a bucket. If the item is a folder, everything except [name] is null.
* @param name The name of the item
* @param id The id of the item
* @param version The version of the item
* @param bucketId The bucket id of the item
* @param updatedAt The last update date of the item
* @param createdAt The creation date of the item
* @param lastAccessedAt The last access date of the item
* @param metadata The metadata of the item
* @param size The size of the item
* @param rawContentType The content type of the item
* @param etag The etag of the item
* @param lastModified The last modified date of the item
* @param cacheControl The cache control of the item
* @param serializer The serializer to use for decoding the metadata
*/
@Serializable
data class FileObjectV2(
val name: String,
val id: String?,
Fixed Show fixed Hide fixed
val version: String,
@SerialName("bucket_id")
Fixed Show fixed Hide fixed
val bucketId: String? = null,
@SerialName("updated_at")
val updatedAt: Instant? = null,
@SerialName("created_at")
val createdAt: Instant?,
@SerialName("last_accessed_at")
val lastAccessedAt: Instant? = null,
val metadata: JsonObject?,
val size: Long,
@SerialName("content_type")
Fixed Show fixed Hide fixed
val rawContentType: String,
val etag: String?,
@SerialName("last_modified")
val lastModified: Instant?,
@SerialName("cache_control")
val cacheControl: String?,
@Transient @PublishedApi internal val serializer: SupabaseSerializer = KotlinXSerializer()
) {

/**
* The content type of the file
*/
val contentType by lazy {
ContentType.parse(rawContentType)
}

/**
* Decodes the metadata using the [serializer]
*/
Fixed Show fixed Hide fixed
inline fun <reified T> decodeMetadata(): T? = metadata?.let { serializer.decode(it.toString()) }

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package io.github.jan.supabase.storage

import io.github.jan.supabase.SupabaseSerializer
import io.github.jan.supabase.encodeToJsonElement
import io.github.jan.supabase.network.HttpRequestOverride
import io.ktor.http.ContentType
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonObjectBuilder
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.jsonObject

/**
* Builder for uploading files with additional options
Expand All @@ -13,6 +18,7 @@
class UploadOptionBuilder(
@PublishedApi internal val serializer: SupabaseSerializer,
var upsert: Boolean = false,
var userMetadata: JsonObject? = null,

Check warning

Code scanning / detekt

Public properties require documentation. Warning

The property userMetadata is missing documentation.
var contentType: ContentType? = null,
internal val httpRequestOverrides: MutableList<HttpRequestOverride> = mutableListOf()
) {
Expand All @@ -24,4 +30,20 @@
httpRequestOverrides.add(override)
}

/**
* Sets the user metadata to upload with the file
* @param data The data to upload. Must be serializable by the [serializer]
*/
inline fun <reified T : Any> userMetadata(data: T) {
userMetadata = serializer.encodeToJsonElement(data).jsonObject
}

/**
* Sets the user metadata to upload with the file
* @param builder The builder for the metadata
*/
inline fun userMetadata(builder: JsonObjectBuilder.() -> Unit) {
userMetadata = buildJsonObject(builder)
}

}
Loading
Loading