Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package io.github.jan.supabase.storage

import io.ktor.http.parameters

/**
* A filter builder for [Storage.listBuckets]
*/
class BucketFilter {

/**
* The maximum number of buckets to return. If null, no limit is applied.
*/
var limit: Int? = null

/**
* The number of buckets to skip before returning results. Useful for pagination.
*/
var offset: Int? = null

/**
* A search query to filter buckets by name. If null, no search filter is applied.
*/
var search: String? = null

/**
* The sort order for the results. Can be [SortOrder.ASC] (ascending) or [SortOrder.DESC] (descending).
* If null, the default sort order from the API is used.
*/
private var sortOrder: SortOrder? = null

/**
* The column to sort the results by. If null, the default sort column from the API is used.
*/
private var sortColumn: SortColumn? = null

/**
* Sets the sorting criteria for the bucket list results
* @param column The column to sort by
* @param order The sort order (ascending or descending)
*/
fun sortBy(column: SortColumn, order: SortOrder) {
sortColumn = column
sortOrder = order
}

fun build() = parameters {
limit?.let { set("limit", it.toString()) }
offset?.let { set("offset", it.toString()) }
search?.let { set("search", it) }
sortOrder?.let { set("sortOrder", it.name.lowercase()) }
sortColumn?.let { set("sortColumn", it.name.lowercase()) }
}

/**
* Represents the available columns for sorting bucket results.
*/
enum class SortColumn {
/** Sort by bucket ID */
ID,

/** Sort by bucket name */
NAME,

/** Sort by bucket creation timestamp */
CREATED_AT,

/** Sort by bucket last updated timestamp */
UPDATED_AT
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ class BucketListFilter {

/**
* Sorts the result by the given [column] in the given [order]
* @param column The column to sort by
* @param order The sort order (ascending or descending)
*/
fun sortBy(column: String, order: String) {
fun sortBy(column: String, order: SortOrder) {
this.column = column
this.order = order
this.order = order.name.lowercase()
}

@SupabaseInternal
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.github.jan.supabase.storage

/**
* Represents the sort order for query results.
*/
enum class SortOrder {
/**
* Ascending order
*/
ASC,

/**
* Descending order
*/
DESC
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,33 @@ interface Storage : MainPlugin<Storage.Config>, CustomSerializationPlugin {
* @throws HttpRequestTimeoutException if the request timed out
* @throws HttpRequestException on network related issues
*/
suspend fun retrieveBuckets(): List<Bucket>
suspend fun listBuckets(filter: BucketFilter.() -> Unit = {}): List<Bucket>

/**
* Returns all buckets in the storage
* @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
*/
@Deprecated("Use listBuckets instead", ReplaceWith("listBuckets()"))
suspend fun retrieveBuckets(): List<Bucket> = listBuckets()

/**
* Retrieves a bucket by its [bucketId]
* @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 getBucket(bucketId: String): Bucket?

/**
* Retrieves a bucket by its [bucketId]
* @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 retrieveBucketById(bucketId: String): Bucket?
@Deprecated("Use getBucket instead", ReplaceWith("getBucket(bucketId)"))
suspend fun retrieveBucketById(bucketId: String): Bucket? = getBucket(bucketId)

/**
* Empties a bucket by its [bucketId]
Expand Down Expand Up @@ -200,9 +218,14 @@ internal class StorageImpl(override val supabaseClient: SupabaseClient, override

private val resumableClients = AtomicMutableMap<String, BucketApi>()

override suspend fun retrieveBuckets(): List<Bucket> = api.get("bucket").safeBody()
override suspend fun listBuckets(filter: BucketFilter.() -> Unit): List<Bucket> {
val response = api.get("bucket") {
url.parameters.appendAll(BucketFilter().apply(filter).build())
}
return response.safeBody()
}

override suspend fun retrieveBucketById(bucketId: String): Bucket? = api.get("bucket/$bucketId").safeBody()
override suspend fun getBucket(bucketId: String): Bucket? = api.get("bucket/$bucketId").safeBody()

override suspend fun deleteBucket(bucketId: String) {
api.delete("bucket/$bucketId")
Expand Down
3 changes: 2 additions & 1 deletion Storage/src/commonTest/kotlin/BucketApiTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import io.github.jan.supabase.storage.BucketApi
import io.github.jan.supabase.storage.FileObjectV2
import io.github.jan.supabase.storage.FileUploadResponse
import io.github.jan.supabase.storage.ImageTransformation
import io.github.jan.supabase.storage.SortOrder
import io.github.jan.supabase.storage.Storage
import io.github.jan.supabase.storage.resumable.MemoryResumableCache
import io.github.jan.supabase.storage.storage
Expand Down Expand Up @@ -451,7 +452,7 @@ class BucketApiTest {
limit = expectedLimit
offset = expectedOffset
search = expectedSearch
sortBy(expectedColumn, expectedOrder)
sortBy(expectedColumn, SortOrder.ASC)
}
// assertContentEquals(expectedData, data, "Data should be $expectedData")
}
Expand Down
148 changes: 148 additions & 0 deletions Storage/src/commonTest/kotlin/BucketFilterTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import io.github.jan.supabase.storage.BucketFilter
import io.github.jan.supabase.storage.SortOrder
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull

class BucketFilterTest {

@Test
fun testBucketFilterWithAllParameters() {
val filter = BucketFilter().apply {
limit = 10
offset = 5
search = "test"
sortBy(BucketFilter.SortColumn.NAME, SortOrder.ASC)
}
val params = filter.build()
assertEquals("10", params["limit"])
assertEquals("5", params["offset"])
assertEquals("test", params["search"])
assertEquals("asc", params["sortOrder"])
assertEquals("name", params["sortColumn"])
}

@Test
fun testBucketFilterEmpty() {
val filter = BucketFilter()
val params = filter.build()
assertNull(params["limit"])
assertNull(params["offset"])
assertNull(params["search"])
assertNull(params["sortOrder"])
assertNull(params["sortColumn"])
}

@Test
fun testBucketFilterIndividualParameters() {
// Test limit only
var filter = BucketFilter().apply { limit = 20 }
var params = filter.build()
assertEquals("20", params["limit"])
assertNull(params["offset"])

// Test offset only
filter = BucketFilter().apply { offset = 15 }
params = filter.build()
assertEquals("15", params["offset"])
assertNull(params["limit"])

// Test search only
filter = BucketFilter().apply { search = "my-bucket" }
params = filter.build()
assertEquals("my-bucket", params["search"])
assertNull(params["limit"])
}

@Test
fun testBucketFilterSortColumns() {
// Test all sort columns with both orders
val columns = listOf(
BucketFilter.SortColumn.ID to "id",
BucketFilter.SortColumn.NAME to "name",
BucketFilter.SortColumn.CREATED_AT to "created_at",
BucketFilter.SortColumn.UPDATED_AT to "updated_at"
)

for ((column, expectedName) in columns) {
// Test ascending
var filter = BucketFilter().apply { sortBy(column, SortOrder.ASC) }
var params = filter.build()
assertEquals(expectedName, params["sortColumn"])
assertEquals("asc", params["sortOrder"])

// Test descending
filter = BucketFilter().apply { sortBy(column, SortOrder.DESC) }
params = filter.build()
assertEquals(expectedName, params["sortColumn"])
assertEquals("desc", params["sortOrder"])
}
}

@Test
fun testBucketFilterEdgeCases() {
// Zero values
var filter = BucketFilter().apply {
limit = 0
offset = 0
}
var params = filter.build()
assertEquals("0", params["limit"])
assertEquals("0", params["offset"])

// Empty search string
filter = BucketFilter().apply { search = "" }
params = filter.build()
assertEquals("", params["search"])

// Special characters in search
filter = BucketFilter().apply { search = "test-bucket_123" }
params = filter.build()
assertEquals("test-bucket_123", params["search"])

// Large numbers
filter = BucketFilter().apply {
limit = 1000
offset = 5000
}
params = filter.build()
assertEquals("1000", params["limit"])
assertEquals("5000", params["offset"])
}

@Test
fun testBucketFilterCombinations() {
// Limit and offset
var filter = BucketFilter().apply {
limit = 25
offset = 50
}
var params = filter.build()
assertEquals("25", params["limit"])
assertEquals("50", params["offset"])
assertNull(params["search"])

// Search and sort
filter = BucketFilter().apply {
search = "images"
sortBy(BucketFilter.SortColumn.UPDATED_AT, SortOrder.ASC)
}
params = filter.build()
assertEquals("images", params["search"])
assertEquals("updated_at", params["sortColumn"])
assertEquals("asc", params["sortOrder"])

// Pagination with sort
filter = BucketFilter().apply {
limit = 10
offset = 30
sortBy(BucketFilter.SortColumn.NAME, SortOrder.ASC)
}
params = filter.build()
assertEquals("10", params["limit"])
assertEquals("30", params["offset"])
assertEquals("name", params["sortColumn"])
assertEquals("asc", params["sortOrder"])
}

}
Loading
Loading