Skip to content

Commit 77e5c3d

Browse files
authored
feat(storage): add createSignedURLs method (#273)
* feat: add createSignedURLs method * Add storage example and storage test
1 parent fca6721 commit 77e5c3d

23 files changed

+543
-102
lines changed

Examples/Examples.xcodeproj/project.pbxproj

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
7962989D2AEBC6F9000AA957 /* SVGView in Frameworks */ = {isa = PBXBuildFile; productRef = 7962989C2AEBC6F9000AA957 /* SVGView */; };
3030
79719ECE2ADF26C400737804 /* Supabase in Frameworks */ = {isa = PBXBuildFile; productRef = 79719ECD2ADF26C400737804 /* Supabase */; };
3131
797D664A2B46A1D8007592ED /* Dependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 797D66492B46A1D8007592ED /* Dependencies.swift */; };
32+
797EFB662BABD82A00098D6B /* BucketList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 797EFB652BABD82A00098D6B /* BucketList.swift */; };
33+
797EFB682BABD90500098D6B /* Stringfy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 797EFB672BABD90500098D6B /* Stringfy.swift */; };
34+
797EFB6A2BABDF3800098D6B /* BucketDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 797EFB692BABDF3800098D6B /* BucketDetailView.swift */; };
35+
797EFB6C2BABE1B800098D6B /* FileObjectDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 797EFB6B2BABE1B800098D6B /* FileObjectDetailView.swift */; };
3236
7993B8A92B3C673A009B610B /* AuthView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993B8A82B3C673A009B610B /* AuthView.swift */; };
3337
7993B8AB2B3C67E0009B610B /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7993B8AA2B3C67E0009B610B /* Toast.swift */; };
3438
79AF047F2B2CE207008761AD /* AuthWithEmailAndPassword.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79AF047E2B2CE207008761AD /* AuthWithEmailAndPassword.swift */; };
@@ -87,6 +91,10 @@
8791
796298982AEBBA77000AA957 /* MFAFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFAFlow.swift; sourceTree = "<group>"; };
8892
7962989A2AEBBD9F000AA957 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
8993
797D66492B46A1D8007592ED /* Dependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dependencies.swift; sourceTree = "<group>"; };
94+
797EFB652BABD82A00098D6B /* BucketList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BucketList.swift; sourceTree = "<group>"; };
95+
797EFB672BABD90500098D6B /* Stringfy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stringfy.swift; sourceTree = "<group>"; };
96+
797EFB692BABDF3800098D6B /* BucketDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BucketDetailView.swift; sourceTree = "<group>"; };
97+
797EFB6B2BABE1B800098D6B /* FileObjectDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileObjectDetailView.swift; sourceTree = "<group>"; };
9098
7993B8A82B3C673A009B610B /* AuthView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthView.swift; sourceTree = "<group>"; };
9199
7993B8AA2B3C67E0009B610B /* Toast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toast.swift; sourceTree = "<group>"; };
92100
7993B8AC2B3C97B6009B610B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
@@ -182,6 +190,7 @@
182190
793895C82954ABFF0044F2B8 /* Examples */ = {
183191
isa = PBXGroup;
184192
children = (
193+
797EFB642BABD7FF00098D6B /* Storage */,
185194
79AF04822B2CE3BD008761AD /* Auth */,
186195
7962989A2AEBBD9F000AA957 /* Info.plist */,
187196
793895CD2954AC000044F2B8 /* Assets.xcassets */,
@@ -201,6 +210,7 @@
201210
793E03082B2CED5D00AC7DED /* Contants.swift */,
202211
793E030A2B2CEDDA00AC7DED /* ActionState.swift */,
203212
79E2B55B2B97A2310042CD21 /* UIApplicationExtensions.swift */,
213+
797EFB672BABD90500098D6B /* Stringfy.swift */,
204214
);
205215
path = Examples;
206216
sourceTree = "<group>";
@@ -220,6 +230,16 @@
220230
name = Frameworks;
221231
sourceTree = "<group>";
222232
};
233+
797EFB642BABD7FF00098D6B /* Storage */ = {
234+
isa = PBXGroup;
235+
children = (
236+
797EFB652BABD82A00098D6B /* BucketList.swift */,
237+
797EFB692BABDF3800098D6B /* BucketDetailView.swift */,
238+
797EFB6B2BABE1B800098D6B /* FileObjectDetailView.swift */,
239+
);
240+
path = Storage;
241+
sourceTree = "<group>";
242+
};
223243
79AF04822B2CE3BD008761AD /* Auth */ = {
224244
isa = PBXGroup;
225245
children = (
@@ -452,6 +472,7 @@
452472
7956406A2955AFBD0088A06F /* ErrorText.swift in Sources */,
453473
79AF04812B2CE261008761AD /* AuthView.swift in Sources */,
454474
794EF1242955F3DE008C9526 /* TodoListRow.swift in Sources */,
475+
797EFB662BABD82A00098D6B /* BucketList.swift in Sources */,
455476
79E2B55C2B97A2310042CD21 /* UIApplicationExtensions.swift in Sources */,
456477
794EF1222955F26A008C9526 /* AddTodoListView.swift in Sources */,
457478
7956405E2954ADE00088A06F /* Secrets.swift in Sources */,
@@ -462,6 +483,9 @@
462483
795640622955AD2B0088A06F /* HomeView.swift in Sources */,
463484
7940E3152B36187A0089BEE1 /* GoogleSignInWithWebFlow.swift in Sources */,
464485
793895CA2954ABFF0044F2B8 /* ExamplesApp.swift in Sources */,
486+
797EFB682BABD90500098D6B /* Stringfy.swift in Sources */,
487+
797EFB6C2BABE1B800098D6B /* FileObjectDetailView.swift in Sources */,
488+
797EFB6A2BABDF3800098D6B /* BucketDetailView.swift in Sources */,
465489
793E030D2B2DAB5700AC7DED /* SignInWithApple.swift in Sources */,
466490
793E030B2B2CEDDA00AC7DED /* ActionState.swift in Sources */,
467491
);

Examples/Examples/Auth/AuthView.swift

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,18 @@ struct AuthView: View {
2727
}
2828

2929
var body: some View {
30-
List {
31-
ForEach(Option.allCases, id: \.self) { option in
32-
NavigationLink(option.title, value: option)
30+
NavigationStack {
31+
List {
32+
ForEach(Option.allCases, id: \.self) { option in
33+
NavigationLink(option.title, value: option)
34+
}
3335
}
36+
.navigationDestination(for: Option.self) { options in
37+
options
38+
.navigationTitle(options.title)
39+
}
40+
.navigationBarTitleDisplayMode(.inline)
3441
}
35-
.navigationDestination(for: Option.self) { options in
36-
options
37-
.navigationTitle(options.title)
38-
}
39-
.navigationBarTitleDisplayMode(.inline)
4042
}
4143
}
4244

Examples/Examples/Auth/AuthWithEmailAndPassword.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,6 @@ struct AuthWithEmailAndPassword: View {
4646
}
4747
}
4848

49-
if case let .result(.failure(error)) = actionState {
50-
ErrorText(error)
51-
}
52-
5349
switch actionState {
5450
case .idle:
5551
EmptyView()
@@ -86,6 +82,7 @@ struct AuthWithEmailAndPassword: View {
8682
.animation(.default, value: mode)
8783
}
8884

85+
@MainActor
8986
func primaryActionButtonTapped() async {
9087
do {
9188
actionState = .inFlight
@@ -110,6 +107,7 @@ struct AuthWithEmailAndPassword: View {
110107
}
111108
}
112109

110+
@MainActor
113111
private func onOpenURL(_ url: URL) async {
114112
do {
115113
try await supabase.auth.session(from: url)
@@ -118,6 +116,7 @@ struct AuthWithEmailAndPassword: View {
118116
}
119117
}
120118

119+
@MainActor
121120
private func resendConfirmationButtonTapped() async {
122121
do {
123122
try await supabase.auth.resend(email: email, type: .signup)

Examples/Examples/ExamplesApp.swift

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@ struct ExamplesApp: App {
2222
let supabase = SupabaseClient(
2323
supabaseURL: Secrets.supabaseURL,
2424
supabaseKey: Secrets.supabaseAnonKey,
25-
options: .init(auth: .init(storage: KeychainLocalStorage(
26-
service: "supabase.gotrue.swift",
27-
accessGroup: nil
28-
)))
25+
options: .init(global: .init(logger: ConsoleLogger()))
2926
)
27+
28+
struct ConsoleLogger: SupabaseLogger {
29+
func log(message: SupabaseLogMessage) {
30+
print(message)
31+
}
32+
}

Examples/Examples/HomeView.swift

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// Created by Guilherme Souza on 23/12/22.
66
//
77

8+
import Supabase
89
import SwiftUI
910

1011
struct HomeView: View {
@@ -13,28 +14,31 @@ struct HomeView: View {
1314
@State private var mfaStatus: MFAStatus?
1415

1516
var body: some View {
16-
TodoListView()
17-
.toolbar {
18-
ToolbarItemGroup(placement: .cancellationAction) {
19-
Button("Sign out") {
20-
Task {
21-
try! await supabase.auth.signOut()
22-
}
17+
NavigationStack {
18+
BucketList()
19+
.navigationDestination(for: Bucket.self, destination: BucketDetailView.init)
20+
}
21+
.toolbar {
22+
ToolbarItemGroup(placement: .cancellationAction) {
23+
Button("Sign out") {
24+
Task {
25+
try! await supabase.auth.signOut()
2326
}
27+
}
2428

25-
Button("Reauthenticate") {
26-
Task {
27-
try! await supabase.auth.reauthenticate()
28-
}
29+
Button("Reauthenticate") {
30+
Task {
31+
try! await supabase.auth.reauthenticate()
2932
}
3033
}
3134
}
32-
.task {
35+
}
36+
.task {
3337
// mfaStatus = await verifyMFAStatus()
34-
}
35-
.sheet(unwrapping: $mfaStatus) { $mfaStatus in
36-
MFAFlow(status: mfaStatus)
37-
}
38+
}
39+
.sheet(unwrapping: $mfaStatus) { $mfaStatus in
40+
MFAFlow(status: mfaStatus)
41+
}
3842
}
3943

4044
private func verifyMFAStatus() async -> MFAStatus? {

Examples/Examples/RootView.swift

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,10 @@ struct RootView: View {
1212
@Environment(AuthController.self) var auth
1313

1414
var body: some View {
15-
NavigationStack {
16-
if auth.session == nil {
17-
AuthView()
18-
} else {
19-
HomeView()
20-
}
15+
if auth.session == nil {
16+
AuthView()
17+
} else {
18+
HomeView()
2119
}
2220
}
2321
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
//
2+
// BucketDetailView.swift
3+
// Examples
4+
//
5+
// Created by Guilherme Souza on 21/03/24.
6+
//
7+
8+
import Supabase
9+
import SwiftUI
10+
11+
struct BucketDetailView: View {
12+
let bucket: Bucket
13+
14+
@State private var fileObjects = ActionState<[FileObject], Error>.idle
15+
@State private var presentBucketDetails = false
16+
17+
var body: some View {
18+
Group {
19+
switch fileObjects {
20+
case .idle:
21+
Color.clear
22+
case .inFlight:
23+
ProgressView()
24+
case let .result(.success(files)):
25+
List {
26+
ForEach(files) { file in
27+
NavigationLink(file.name, value: file)
28+
}
29+
}
30+
case let .result(.failure(error)):
31+
VStack {
32+
ErrorText(error)
33+
Button("Retry") {
34+
Task { await load() }
35+
}
36+
}
37+
}
38+
}
39+
.task { await load() }
40+
.navigationTitle("Objects")
41+
.toolbar {
42+
ToolbarItem(placement: .primaryAction) {
43+
Button {
44+
presentBucketDetails = true
45+
} label: {
46+
Label("Detail", systemImage: "info.circle")
47+
}
48+
}
49+
}
50+
.popover(isPresented: $presentBucketDetails) {
51+
ScrollView {
52+
Text(stringfy(bucket))
53+
.monospaced()
54+
.frame(maxWidth: .infinity, alignment: .leading)
55+
.padding()
56+
}
57+
}
58+
.navigationDestination(for: FileObject.self) {
59+
FileObjectDetailView(api: supabase.storage.from(bucket.id), fileObject: $0)
60+
}
61+
}
62+
63+
@MainActor
64+
private func load() async {
65+
fileObjects = .inFlight
66+
fileObjects = await .result(
67+
Result {
68+
try await supabase.storage.from(bucket.id).list()
69+
}
70+
)
71+
}
72+
}
73+
74+
#Preview {
75+
BucketDetailView(
76+
bucket: Bucket(
77+
id: UUID().uuidString,
78+
name: "name",
79+
owner: "owner",
80+
isPublic: false,
81+
createdAt: Date(),
82+
updatedAt: Date(),
83+
allowedMimeTypes: nil,
84+
fileSizeLimit: nil
85+
)
86+
)
87+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//
2+
// BucketList.swift
3+
// Examples
4+
//
5+
// Created by Guilherme Souza on 21/03/24.
6+
//
7+
8+
import Supabase
9+
import SwiftUI
10+
11+
struct BucketList: View {
12+
@State var buckets = ActionState<[Bucket], Error>.idle
13+
14+
var body: some View {
15+
Group {
16+
switch buckets {
17+
case .idle:
18+
Color.clear
19+
case .inFlight:
20+
ProgressView()
21+
case let .result(.success(buckets)):
22+
List {
23+
ForEach(buckets, id: \.self) { bucket in
24+
NavigationLink(bucket.name, value: bucket)
25+
}
26+
}
27+
.overlay {
28+
if buckets.isEmpty {
29+
Text("No buckets found.")
30+
}
31+
}
32+
case let .result(.failure(error)):
33+
VStack {
34+
ErrorText(error)
35+
Button("Retry") {
36+
Task {
37+
await load()
38+
}
39+
}
40+
}
41+
}
42+
}
43+
.task {
44+
await load()
45+
}
46+
.navigationTitle("Bucket list")
47+
.toolbar {
48+
ToolbarItem(placement: .primaryAction) {
49+
Button("Add") {
50+
Task {
51+
do {
52+
try await supabase.storage.createBucket("bucket-\(UUID().uuidString)")
53+
await load()
54+
} catch {}
55+
}
56+
}
57+
}
58+
}
59+
}
60+
61+
@MainActor
62+
private func load() async {
63+
do {
64+
self.buckets = .inFlight
65+
let buckets = try await supabase.storage.listBuckets()
66+
self.buckets = .result(.success(buckets))
67+
} catch {
68+
buckets = .result(.failure(error))
69+
}
70+
}
71+
}
72+
73+
#Preview {
74+
BucketList()
75+
}

0 commit comments

Comments
 (0)