Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
gruve-p committed Jul 11, 2024
2 parents 461fc0b + 25ae6df commit ea965a4
Show file tree
Hide file tree
Showing 58 changed files with 1,190 additions and 197 deletions.
4 changes: 2 additions & 2 deletions BTCPayServer.Abstractions/BTCPayServer.Abstractions.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="HtmlSanitizer" Version="8.0.838" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.5" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BTCPayServer.Client\BTCPayServer.Client.csproj" />
Expand Down
29 changes: 29 additions & 0 deletions BTCPayServer.Client/BTCPayServerClient.Files.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Client.Models;

namespace BTCPayServer.Client;

public partial class BTCPayServerClient
{
public virtual async Task<FileData[]> GetFiles(CancellationToken token = default)
{
return await SendHttpRequest<FileData[]>("api/v1/files", null, HttpMethod.Get, token);
}

public virtual async Task<FileData> GetFile(string fileId, CancellationToken token = default)
{
return await SendHttpRequest<FileData>($"api/v1/files/{fileId}", null, HttpMethod.Get, token);
}

public virtual async Task<FileData> UploadFile(string filePath, string mimeType, CancellationToken token = default)
{
return await UploadFileRequest<FileData>("api/v1/files", filePath, mimeType, "file", HttpMethod.Post, token);
}

public virtual async Task DeleteFile(string fileId, CancellationToken token = default)
{
await SendHttpRequest($"api/v1/files/{fileId}", null, HttpMethod.Delete, token);
}
}
4 changes: 3 additions & 1 deletion BTCPayServer.Client/BTCPayServerClient.Notifications.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace BTCPayServer.Client;
public partial class BTCPayServerClient
{
public virtual async Task<IEnumerable<NotificationData>> GetNotifications(bool? seen = null, int? skip = null,
int? take = null, CancellationToken token = default)
int? take = null, string[] storeId = null, CancellationToken token = default)
{
var queryPayload = new Dictionary<string, object>();
if (seen != null)
Expand All @@ -18,6 +18,8 @@ public virtual async Task<IEnumerable<NotificationData>> GetNotifications(bool?
queryPayload.Add(nameof(skip), skip);
if (take != null)
queryPayload.Add(nameof(take), take);
if (storeId != null)
queryPayload.Add(nameof(storeId), storeId);
return await SendHttpRequest<IEnumerable<NotificationData>>("api/v1/users/me/notifications", queryPayload, HttpMethod.Get, token);
}

Expand Down
9 changes: 9 additions & 0 deletions BTCPayServer.Client/BTCPayServerClient.Stores.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,13 @@ public virtual async Task<StoreData> UpdateStore(string storeId, UpdateStoreRequ
return await SendHttpRequest<StoreData>($"api/v1/stores/{storeId}", request, HttpMethod.Put, token);
}

public virtual async Task<StoreData> UploadStoreLogo(string storeId, string filePath, string mimeType, CancellationToken token = default)
{
return await UploadFileRequest<StoreData>($"api/v1/stores/{storeId}/logo", filePath, mimeType, "file", HttpMethod.Post, token);
}

public virtual async Task DeleteStoreLogo(string storeId, CancellationToken token = default)
{
await SendHttpRequest($"api/v1/stores/{storeId}/logo", null, HttpMethod.Delete, token);
}
}
10 changes: 10 additions & 0 deletions BTCPayServer.Client/BTCPayServerClient.Users.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ public virtual async Task<ApplicationUserData> UpdateCurrentUser(UpdateApplicati
return await SendHttpRequest<ApplicationUserData>("api/v1/users/me", request, HttpMethod.Put, token);
}

public virtual async Task<ApplicationUserData> UploadCurrentUserProfilePicture(string filePath, string mimeType, CancellationToken token = default)
{
return await UploadFileRequest<ApplicationUserData>("api/v1/users/me/picture", filePath, mimeType, "file", HttpMethod.Post, token);
}

public virtual async Task DeleteCurrentUserProfilePicture(CancellationToken token = default)
{
await SendHttpRequest("api/v1/users/me/picture", null, HttpMethod.Delete, token);
}

public virtual async Task<ApplicationUserData> CreateUser(CreateApplicationUserRequest request, CancellationToken token = default)
{
return await SendHttpRequest<ApplicationUserData>("api/v1/users", request, HttpMethod.Post, token);
Expand Down
14 changes: 14 additions & 0 deletions BTCPayServer.Client/BTCPayServerClient.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
Expand Down Expand Up @@ -152,6 +153,19 @@ protected virtual HttpRequestMessage CreateHttpRequest<T>(string path,
return request;
}

protected virtual async Task<T> UploadFileRequest<T>(string apiPath, string filePath, string mimeType, string formFieldName, HttpMethod method = null, CancellationToken token = default)
{
using MultipartFormDataContent multipartContent = new();
var fileContent = new StreamContent(File.OpenRead(filePath));
var fileName = Path.GetFileName(filePath);
fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse(mimeType);
multipartContent.Add(fileContent, formFieldName, fileName);
var req = CreateHttpRequest(apiPath, null, method ?? HttpMethod.Post);
req.Content = multipartContent;
using var resp = await _httpClient.SendAsync(req, token);
return await HandleResponse<T>(resp);
}

public static void AppendPayloadToQuery(UriBuilder uri, KeyValuePair<string, object> keyValuePair)
{
if (uri.Query.Length > 1)
Expand Down
16 changes: 16 additions & 0 deletions BTCPayServer.Client/Models/FileData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using Newtonsoft.Json;

namespace BTCPayServer.Client.Models;

public class FileData
{
public string Id { get; set; }
public string UserId { get; set; }
public string Uri { get; set; }
public string Url { get; set; }
public string OriginalName { get; set; }
public string StorageName { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTimeOffset? CreatedAt { get; set; }
}
1 change: 1 addition & 0 deletions BTCPayServer.Client/Models/NotificationData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public class NotificationData
public string Identifier { get; set; }
public string Type { get; set; }
public string Body { get; set; }
public string StoreId { get; set; }
public bool Seen { get; set; }
public Uri Link { get; set; }

Expand Down
4 changes: 2 additions & 2 deletions BTCPayServer.Data/BTCPayServer.Data.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
<Import Project="../Build/Common.csproj" />
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.5">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.6">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.7" />
<PackageReference Include="NBitcoin.Altcoins" Version="3.0.24" />
</ItemGroup>
<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion BTCPayServer.Rating/BTCPayServer.Rating.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
<PackageReference Include="NBitcoin" Version="7.0.37" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
Expand Down
117 changes: 112 additions & 5 deletions BTCPayServer.Tests/GreenfieldAPITests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -13,7 +13,6 @@
using BTCPayServer.Lightning;
using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.NTag424;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.PayoutProcessors;
using BTCPayServer.Services;
Expand All @@ -30,7 +29,6 @@
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
using static Org.BouncyCastle.Math.EC.ECCurve;
using CreateApplicationUserRequest = BTCPayServer.Client.Models.CreateApplicationUserRequest;

namespace BTCPayServer.Tests
Expand Down Expand Up @@ -241,6 +239,76 @@ await AssertHttpError(403,
await newUserClient.GetInvoices(store.Id);
}

[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanCreateReadAndDeleteFiles()
{
using var tester = CreateServerTester(newDb: true);
await tester.StartAsync();
var user = tester.NewAccount();
await user.GrantAccessAsync();
await user.MakeAdmin();
var client = await user.CreateClient();

// List
Assert.Empty(await client.GetFiles());

// Upload
var filePath = TestUtils.GetTestDataFullPath("OldInvoices.csv");
var upload = await client.UploadFile(filePath, "text/csv");
Assert.Equal("OldInvoices.csv", upload.OriginalName);
Assert.NotNull(upload.Uri);
Assert.NotNull(upload.Url);

// Re-check list
Assert.Single(await client.GetFiles());

// Single file endpoint
var singleFile = await client.GetFile(upload.Id);
Assert.Equal("OldInvoices.csv", singleFile.OriginalName);
Assert.NotNull(singleFile.Uri);
Assert.NotNull(singleFile.Url);

// Delete
await client.DeleteFile(upload.Id);
Assert.Empty(await client.GetFiles());

// Profile image
await AssertValidationError(["file"],
async () => await client.UploadCurrentUserProfilePicture(filePath, "text/csv")
);

var profilePath = TestUtils.GetTestDataFullPath("logo.png");
var currentUser = await client.UploadCurrentUserProfilePicture(profilePath, "image/png");
var files = await client.GetFiles();
Assert.Single(files);
Assert.Equal("logo.png", files[0].OriginalName);
Assert.Equal(files[0].Url, currentUser.ImageUrl);

await client.DeleteCurrentUserProfilePicture();
Assert.Empty(await client.GetFiles());
currentUser = await client.GetCurrentUser();
Assert.Null(currentUser.ImageUrl);

// Store logo
var store = await client.CreateStore(new CreateStoreRequest { Name = "mystore" });
await AssertValidationError(["file"],
async () => await client.UploadStoreLogo(store.Id, filePath, "text/csv")
);

var logoPath = TestUtils.GetTestDataFullPath("logo.png");
var storeData = await client.UploadStoreLogo(store.Id, logoPath, "image/png");
files = await client.GetFiles();
Assert.Single(files);
Assert.Equal("logo.png", files[0].OriginalName);
Assert.Equal(files[0].Url, storeData.LogoUrl);

await client.DeleteStoreLogo(store.Id);
Assert.Empty(await client.GetFiles());
storeData = await client.GetStore(store.Id);
Assert.Null(storeData.LogoUrl);
}

[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanCreateReadUpdateAndDeletePointOfSaleApp()
Expand Down Expand Up @@ -2915,14 +2983,18 @@ public async Task NotificationAPITests()
await tester.PayTester.GetService<NotificationSender>()
.SendNotification(new UserScope(user.UserId), new NewVersionNotification());

Assert.Single(await viewOnlyClient.GetNotifications());
var notifications = (await viewOnlyClient.GetNotifications()).ToList();
Assert.Single(notifications);
Assert.Single(await viewOnlyClient.GetNotifications(false));
Assert.Empty(await viewOnlyClient.GetNotifications(true));

var notification = notifications.First();
Assert.Null(notification.StoreId);

Assert.Single(await client.GetNotifications());
Assert.Single(await client.GetNotifications(false));
Assert.Empty(await client.GetNotifications(true));
var notification = (await client.GetNotifications()).First();
notification = (await client.GetNotifications()).First();
notification = await client.GetNotification(notification.Id);
Assert.False(notification.Seen);
await AssertHttpError(403, async () =>
Expand All @@ -2940,6 +3012,41 @@ await AssertHttpError(403, async () =>
Assert.Empty(await viewOnlyClient.GetNotifications(true));
Assert.Empty(await viewOnlyClient.GetNotifications(false));

// Store association
var unrestricted = await user.CreateClient(Policies.Unrestricted);
var store1 = await unrestricted.CreateStore(new CreateStoreRequest { Name = "Store A" });
await tester.PayTester.GetService<NotificationSender>()
.SendNotification(new UserScope(user.UserId), new InviteAcceptedNotification{
UserId = user.UserId,
UserEmail = user.Email,
StoreId = store1.Id,
StoreName = store1.Name
});
notifications = (await client.GetNotifications()).ToList();
Assert.Single(notifications);

notification = notifications.First();
Assert.Equal(store1.Id, notification.StoreId);
Assert.Equal($"User {user.Email} accepted the invite to {store1.Name}.", notification.Body);

var store2 = await unrestricted.CreateStore(new CreateStoreRequest { Name = "Store B" });
await tester.PayTester.GetService<NotificationSender>()
.SendNotification(new UserScope(user.UserId), new InviteAcceptedNotification{
UserId = user.UserId,
UserEmail = user.Email,
StoreId = store2.Id,
StoreName = store2.Name
});
notifications = (await client.GetNotifications(storeId: [store2.Id])).ToList();
Assert.Single(notifications);

notification = notifications.First();
Assert.Equal(store2.Id, notification.StoreId);
Assert.Equal($"User {user.Email} accepted the invite to {store2.Name}.", notification.Body);

Assert.Equal(2, (await client.GetNotifications(storeId: [store1.Id, store2.Id])).Count());
Assert.Equal(2, (await client.GetNotifications()).Count());

// Settings
var settings = await client.GetNotificationSettings();
Assert.True(settings.Notifications.Find(n => n.Identifier == "newversion").Enabled);
Expand Down
Loading

0 comments on commit ea965a4

Please sign in to comment.