diff --git a/Sources/Storage/StorageBucketApi.swift b/Sources/Storage/StorageBucketApi.swift index 4e8cfba8..003a4382 100644 --- a/Sources/Storage/StorageBucketApi.swift +++ b/Sources/Storage/StorageBucketApi.swift @@ -16,7 +16,7 @@ public class StorageBucketApi: StorageApi { /// Retrieves the details of an existing Storage bucket. /// - Parameters: /// - id: The unique identifier of the bucket you would like to retrieve. - public func getBucket(id: String) async throws -> Bucket { + public func getBucket(_ id: String) async throws -> Bucket { try await execute(Request(path: "/bucket/\(id)", method: .get)) .decoded(decoder: configuration.decoder) } @@ -32,7 +32,7 @@ public class StorageBucketApi: StorageApi { /// Creates a new Storage bucket. /// - Parameters: /// - id: A unique identifier for the bucket you are creating. - public func createBucket(id: String, options: BucketOptions = .init()) async throws { + public func createBucket(_ id: String, options: BucketOptions = .init()) async throws { try await execute( Request( path: "/bucket", @@ -53,7 +53,7 @@ public class StorageBucketApi: StorageApi { /// Updates a Storage bucket /// - Parameters: /// - id: A unique identifier for the bucket you are updating. - public func updateBucket(id: String, options: BucketOptions) async throws { + public func updateBucket(_ id: String, options: BucketOptions) async throws { try await execute( Request( path: "/bucket/\(id)", @@ -74,7 +74,7 @@ public class StorageBucketApi: StorageApi { /// Removes all objects inside a single bucket. /// - Parameters: /// - id: The unique identifier of the bucket you would like to empty. - public func emptyBucket(id: String) async throws { + public func emptyBucket(_ id: String) async throws { try await execute(Request(path: "/bucket/\(id)/empty", method: .post)) } @@ -82,7 +82,7 @@ public class StorageBucketApi: StorageApi { /// You must first `empty()` the bucket. /// - Parameters: /// - id: The unique identifier of the bucket you would like to delete. - public func deleteBucket(id: String) async throws { + public func deleteBucket(_ id: String) async throws { try await execute(Request(path: "/bucket/\(id)", method: .delete)) } } diff --git a/Sources/Storage/StorageFileApi.swift b/Sources/Storage/StorageFileApi.swift index cc125505..8814a62f 100644 --- a/Sources/Storage/StorageFileApi.swift +++ b/Sources/Storage/StorageFileApi.swift @@ -142,6 +142,7 @@ public class StorageFileApi: StorageApi { /// `folder/image.png`. /// - expiresIn: The number of seconds until the signed URL expires. For example, `60` for a URL /// which is valid for one minute. + /// - download: Trigger a download with the specified file name. /// - transform: Transform the asset before serving it to the client. public func createSignedURL( path: String, @@ -195,6 +196,29 @@ public class StorageFileApi: StorageApi { return signedURL } + /// Create signed url to download file without requiring permissions. This URL can be valid for a + /// set number of seconds. + /// - Parameters: + /// - path: The file path to be downloaded, including the current file name. For example + /// `folder/image.png`. + /// - expiresIn: The number of seconds until the signed URL expires. For example, `60` for a URL + /// which is valid for one minute. + /// - download: Trigger a download with the default file name. + /// - transform: Transform the asset before serving it to the client. + public func createSignedURL( + path: String, + expiresIn: Int, + download: Bool, + transform: TransformOptions? = nil + ) async throws -> URL { + try await self.createSignedURL( + path: path, + expiresIn: expiresIn, + download: download ? "" : nil, + transform: transform + ) + } + /// Deletes files within the same bucket /// - Parameters: /// - paths: An array of files to be deletes, including the path and file name. For example @@ -252,13 +276,11 @@ public class StorageFileApi: StorageApi { /// Returns a public url for an asset. /// - Parameters: /// - path: The file path to the asset. For example `folder/image.png`. - /// - download: Whether the asset should be downloaded. - /// - fileName: If specified, the file name for the asset that is downloaded. + /// - download: Trigger a download with the specified file name. /// - options: Transform the asset before retrieving it on the client. public func getPublicURL( path: String, - download: Bool = false, - fileName: String = "", + download: String? = nil, options: TransformOptions? = nil ) throws -> URL { var queryItems: [URLQueryItem] = [] @@ -268,8 +290,8 @@ public class StorageFileApi: StorageApi { throw URLError(.badURL) } - if download { - queryItems.append(URLQueryItem(name: "download", value: fileName)) + if let download { + queryItems.append(URLQueryItem(name: "download", value: download)) } if let optionsQueryItems = options?.queryItems { @@ -287,6 +309,19 @@ public class StorageFileApi: StorageApi { return generatedUrl } + + /// Returns a public url for an asset. + /// - Parameters: + /// - path: The file path to the asset. For example `folder/image.png`. + /// - download: Trigger a download with the default file name. + /// - options: Transform the asset before retrieving it on the client. + public func getPublicURL( + path: String, + download: Bool, + options: TransformOptions? = nil + ) throws -> URL { + try getPublicURL(path: path, download: download ? "" : nil, options: options) + } } private func fileName(fromPath path: String) -> String { diff --git a/Sources/Storage/SupabaseStorage.swift b/Sources/Storage/SupabaseStorage.swift index 3d67a419..1b7c283c 100644 --- a/Sources/Storage/SupabaseStorage.swift +++ b/Sources/Storage/SupabaseStorage.swift @@ -26,7 +26,7 @@ public class SupabaseStorageClient: StorageBucketApi { /// Perform file operation in a bucket. /// - Parameter id: The bucket id to operate on. /// - Returns: StorageFileApi object - public func from(id: String) -> StorageFileApi { + public func from(_ id: String) -> StorageFileApi { StorageFileApi(bucketId: id, configuration: configuration) } } diff --git a/Tests/StorageTests/SupabaseStorageTests.swift b/Tests/StorageTests/SupabaseStorageTests.swift index ec4e5995..47aaaa48 100644 --- a/Tests/StorageTests/SupabaseStorageTests.swift +++ b/Tests/StorageTests/SupabaseStorageTests.swift @@ -42,18 +42,18 @@ final class SupabaseStorageTests: XCTestCase { "INTEGRATION_TESTS not defined." ) - try? await storage.emptyBucket(id: bucketId) - try? await storage.deleteBucket(id: bucketId) + try? await storage.emptyBucket(bucketId) + try? await storage.deleteBucket(bucketId) - try await storage.createBucket(id: bucketId, options: BucketOptions(public: true)) + try await storage.createBucket(bucketId, options: BucketOptions(public: true)) } func testUpdateBucket() async throws { - var bucket = try await storage.getBucket(id: bucketId) + var bucket = try await storage.getBucket(bucketId) XCTAssertTrue(bucket.isPublic) - try await storage.updateBucket(id: bucketId, options: BucketOptions(public: false)) - bucket = try await storage.getBucket(id: bucketId) + try await storage.updateBucket(bucketId, options: BucketOptions(public: false)) + bucket = try await storage.getBucket(bucketId) XCTAssertFalse(bucket.isPublic) } @@ -63,28 +63,28 @@ final class SupabaseStorageTests: XCTestCase { } func testFileIntegration() async throws { - var files = try await storage.from(id: bucketId).list() + var files = try await storage.from(bucketId).list() XCTAssertTrue(files.isEmpty) try await uploadTestData() - files = try await storage.from(id: bucketId).list() + files = try await storage.from(bucketId).list() XCTAssertEqual(files.map(\.name), ["README.md"]) - let downloadedData = try await storage.from(id: bucketId).download(path: "README.md") + let downloadedData = try await storage.from(bucketId).download(path: "README.md") XCTAssertEqual(downloadedData, uploadData) - try await storage.from(id: bucketId).move(from: "README.md", to: "README_2.md") + try await storage.from(bucketId).move(from: "README.md", to: "README_2.md") - var searchedFiles = try await storage.from(id: bucketId) + var searchedFiles = try await storage.from(bucketId) .list(options: .init(search: "README.md")) XCTAssertTrue(searchedFiles.isEmpty) - try await storage.from(id: bucketId).copy(from: "README_2.md", to: "README.md") - searchedFiles = try await storage.from(id: bucketId).list(options: .init(search: "README.md")) + try await storage.from(bucketId).copy(from: "README_2.md", to: "README.md") + searchedFiles = try await storage.from(bucketId).list(options: .init(search: "README.md")) XCTAssertEqual(searchedFiles.map(\.name), ["README.md"]) - let removedFiles = try await storage.from(id: bucketId).remove(paths: ["README_2.md"]) + let removedFiles = try await storage.from(bucketId).remove(paths: ["README_2.md"]) XCTAssertEqual(removedFiles.map(\.name), ["README_2.md"]) } @@ -93,10 +93,10 @@ final class SupabaseStorageTests: XCTestCase { let path = "README.md" - let baseUrl = try storage.from(id: bucketId).getPublicURL(path: path) + let baseUrl = try storage.from(bucketId).getPublicURL(path: path) XCTAssertEqual(baseUrl.absoluteString, "\(Self.supabaseURL)/object/public/\(bucketId)/\(path)") - let baseUrlWithDownload = try storage.from(id: bucketId).getPublicURL( + let baseUrlWithDownload = try storage.from(bucketId).getPublicURL( path: path, download: true ) @@ -105,16 +105,16 @@ final class SupabaseStorageTests: XCTestCase { "\(Self.supabaseURL)/object/public/\(bucketId)/\(path)?download=" ) - let baseUrlWithDownloadAndFileName = try storage.from(id: bucketId).getPublicURL( - path: path, download: true, fileName: "test" + let baseUrlWithDownloadAndFileName = try storage.from(bucketId).getPublicURL( + path: path, download: "test" ) XCTAssertEqual( baseUrlWithDownloadAndFileName.absoluteString, "\(Self.supabaseURL)/object/public/\(bucketId)/\(path)?download=test" ) - let baseUrlWithAllOptions = try storage.from(id: bucketId).getPublicURL( - path: path, download: true, fileName: "test", + let baseUrlWithAllOptions = try storage.from(bucketId).getPublicURL( + path: path, download: "test", options: TransformOptions(width: 300, height: 300) ) XCTAssertEqual( @@ -128,7 +128,7 @@ final class SupabaseStorageTests: XCTestCase { let path = "README.md" - let url = try await storage.from(id: bucketId).createSignedURL( + let url = try await storage.from(bucketId).createSignedURL( path: path, expiresIn: 60, download: "README_local.md" @@ -149,7 +149,7 @@ final class SupabaseStorageTests: XCTestCase { )! ) - try await storage.from(id: bucketId).update( + try await storage.from(bucketId).update( path: "README.md", file: File(name: "README.md", data: dataToUpdate ?? Data(), fileName: nil, contentType: nil) ) @@ -159,7 +159,7 @@ final class SupabaseStorageTests: XCTestCase { let file = File( name: "README.md", data: uploadData ?? Data(), fileName: "README.md", contentType: "text/html" ) - _ = try await storage.from(id: bucketId).upload( + _ = try await storage.from(bucketId).upload( path: "README.md", file: file, fileOptions: FileOptions(cacheControl: "3600") ) }