Skip to content

Commit c39cf08

Browse files
committed
feat: implement partially method copy
1 parent a66944f commit c39cf08

File tree

9 files changed

+391
-11
lines changed

9 files changed

+391
-11
lines changed

Storage/DestinationOptions.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace Supabase.Storage;
2+
3+
/// <summary>
4+
/// Represents the options for a destination in the context of Supabase Storage.
5+
/// </summary>
6+
public class DestinationOptions
7+
{
8+
/// <summary>
9+
/// Gets or sets the name of the destination bucket in the context of Supabase Storage.
10+
/// </summary>
11+
public string? DestinationBucket { get; set; }
12+
}

Storage/Exceptions/FailureHint.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ 400 when storageException.Content.ToLower().Contains("malformed") => NotAuthoriz
2727
400 when storageException.Content.ToLower().Contains("invalid signature") => NotAuthorized,
2828
400 when storageException.Content.ToLower().Contains("invalid") => InvalidInput,
2929
401 => NotAuthorized,
30+
403 when storageException.Content.ToLower().Contains("invalid compact jws") => NotAuthorized,
31+
403 when storageException.Content.ToLower().Contains("signature verification failed") => NotAuthorized,
3032
404 when storageException.Content.ToLower().Contains("not found") => NotFound,
3133
409 when storageException.Content.ToLower().Contains("exists") => AlreadyExists,
3234
500 => Internal,

Storage/Interfaces/IStorageFileApi.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ public interface IStorageFileApi<TFileObject>
1818
Task<string> DownloadPublicFile(string supabasePath, string localPath, TransformOptions? transformOptions = null, EventHandler<float>? onProgress = null);
1919
string GetPublicUrl(string path, TransformOptions? transformOptions = null);
2020
Task<List<TFileObject>?> List(string path = "", SearchOptions? options = null);
21-
Task<bool> Move(string fromPath, string toPath);
21+
Task<bool> Move(string fromPath, string toPath, DestinationOptions? options = null);
22+
Task<bool> Copy(string fromPath, string toPath, DestinationOptions? options = null);
2223
Task<TFileObject?> Remove(string path);
2324
Task<List<TFileObject>?> Remove(List<string> paths);
2425
Task<string> Update(byte[] data, string supabasePath, FileOptions? options = null, EventHandler<float>? onProgress = null);

Storage/StorageFileApi.cs

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -281,23 +281,46 @@ public Task<string> Update(byte[] data, string supabasePath, FileOptions? option
281281
}
282282

283283
/// <summary>
284-
/// Moves an existing file, optionally renaming it at the same time.
284+
/// Moves an existing file to a new location, optionally allowing renaming.
285285
/// </summary>
286-
/// <param name="fromPath">The original file path, including the current file name. For example `folder/image.png`.</param>
287-
/// <param name="toPath">The new file path, including the new file name. For example `folder/image-copy.png`.</param>
288-
/// <returns></returns>
289-
public async Task<bool> Move(string fromPath, string toPath)
286+
/// <param name="fromPath">The original file path, including the current file name (e.g., `folder/image.png`).</param>
287+
/// <param name="toPath">The target file path, including the new file name (e.g., `folder/image-copy.png`).</param>
288+
/// <param name="options">Optional parameters for specifying the destination bucket and other settings.</param>
289+
/// <returns>Returns a boolean value indicating whether the operation was successful.</returns>
290+
public async Task<bool> Move(string fromPath, string toPath, DestinationOptions? options = null)
290291
{
291292
var body = new Dictionary<string, string?>
292293
{
293294
{ "bucketId", BucketId },
294295
{ "sourceKey", fromPath },
295-
{ "destinationKey", toPath }
296+
{ "destinationKey", toPath },
297+
{ "destinationBucket", options?.DestinationBucket }
296298
};
297299
await Helpers.MakeRequest<GenericResponse>(HttpMethod.Post, $"{Url}/object/move", body, Headers);
298300
return true;
299301
}
300302

303+
/// <summary>
304+
/// Copies a file/object from one path to another within a bucket or across buckets.
305+
/// </summary>
306+
/// <param name="fromPath">The source path of the file/object to copy.</param>
307+
/// <param name="toPath">The destination path for the copied file/object.</param>
308+
/// <param name="options">Optional parameters such as the destination bucket.</param>
309+
/// <returns>True if the copy operation was successful.</returns>
310+
public async Task<bool> Copy(string fromPath, string toPath, DestinationOptions? options = null)
311+
{
312+
var body = new Dictionary<string, string?>
313+
{
314+
{ "bucketId", BucketId },
315+
{ "sourceKey", fromPath },
316+
{ "destinationKey", toPath },
317+
{ "destinationBucket", options?.DestinationBucket }
318+
};
319+
320+
await Helpers.MakeRequest<GenericResponse>(HttpMethod.Post, $"{Url}/object/copy", body, Headers);
321+
return true;
322+
}
323+
301324
/// <summary>
302325
/// Downloads a file from a private bucket. For public buckets, use <see cref="DownloadPublicFile(string, string, TransformOptions?, EventHandler{float}?)"/>
303326
/// </summary>

StorageTests/Helpers.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ namespace StorageTests
55
{
66
public static class Helpers
77
{
8-
public static string SupabaseUrl => "http://localhost:5000";
9-
public static string ServiceKey => "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIiwiaWF0IjoxNjEzNTMxOTg1LCJleHAiOjE5MjkxMDc5ODV9.th84OKK0Iz8QchDyXZRrojmKSEZ-OuitQm_5DvLiSIc";
10-
public static string PublicKey => "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwic3ViIjoiMzE3ZWFkY2UtNjMxYS00NDI5LWEwYmItZjE5YTdhNTE3YjRhIiwiZW1haWwiOiJpbmlhbit0ZXN0MUBzdXBhYmFzZS5pbyIsImV4cCI6MTkzOTEwNzk4NSwiYXBwX21ldGFkYXRhIjp7InByb3ZpZGVyIjoiZW1haWwifSwidXNlcl9tZXRhZGF0YSI6e30sInJvbGUiOiJhdXRoZW50aWNhdGVkIn0.E-x3oYcHIjFCdUO1M3wKDl1Ln32mik0xdHT2PjrvN70";
8+
public static string SupabaseUrl => "http://127.0.0.1:54321/storage/v1";
9+
public static string PublicKey => "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0";
10+
public static string ServiceKey => "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU";
1111

1212
public static string StorageUrl => $"{SupabaseUrl}";
1313

StorageTests/StorageFileAnonTests.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public async Task InitializeTest()
2828

2929
if (_bucket == null && await Storage.GetBucket(_bucketId) == null)
3030
{
31-
await AdminStorage.CreateBucket(_bucketId, new BucketUpsertOptions { Public = true });
31+
await AdminStorage.CreateBucket(_bucketId, new BucketUpsertOptions { Public = false });
3232
}
3333

3434
_adminBucket = AdminStorage.From(_bucketId);
@@ -131,6 +131,18 @@ await Assert.ThrowsExceptionAsync<SupabaseStorageException>(async () =>
131131
});
132132
}
133133

134+
[TestMethod("File: Throws attempting to Copy")]
135+
public async Task Copy()
136+
{
137+
var name = $"{Guid.NewGuid()}.bin";
138+
await _adminBucket.Upload(new Byte[] { 0x0, 0x1 }, name);
139+
140+
await Assert.ThrowsExceptionAsync<SupabaseStorageException>(async () =>
141+
{
142+
await _bucket.Copy(name, "new-file.bin");
143+
});
144+
}
145+
134146
[TestMethod("File: Get Public Link")]
135147
public async Task GetPublicLink()
136148
{

StorageTests/StorageFileTests.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,20 @@ public async Task Move()
155155
Assert.IsNotNull(items.Find((f) => f.Name == "new-file.bin"));
156156
Assert.IsNull(items.Find((f) => f.Name == name));
157157
}
158+
159+
[TestMethod("File: Copy")]
160+
public async Task Copy()
161+
{
162+
var name = $"{Guid.NewGuid()}.bin";
163+
await _bucket.Upload([0x0, 0x1], name);
164+
await _bucket.Copy(name, "new-file.bin");
165+
var items = await _bucket.List();
166+
167+
Assert.IsNotNull(items);
168+
169+
Assert.IsNotNull(items.Find((f) => f.Name == "new-file.bin"));
170+
Assert.IsNotNull(items.Find((f) => f.Name == name));
171+
}
158172

159173
[TestMethod("File: Get Public Link")]
160174
public async Task GetPublicLink()

supabase/.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Supabase
2+
.branches
3+
.temp
4+
5+
# dotenvx
6+
.env.keys
7+
.env.local
8+
.env.*.local

0 commit comments

Comments
 (0)