mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 06:24:24 +01:00
Greenfield: Add file endpoints and upload (#6075)
* Greenfield: Add file endpoints and upload - Endpoints for server files - File upload using `multipart/form-data` Closes #6074. Can also be tested using cURL: - `curl --location 'https://localhost:14142/api/v1/files' --header 'Authorization: token MY_API_TOKEN' --form 'file=@"LOCAL_FILEPATH"'` - `curl --location 'https://localhost:14142/api/v1/users/me/picture' --header 'Authorization: token MY_API_TOKEN' --form 'file=@"LOCAL_FILEPATH"'` * Revert UnresolvedUri changes * Add upload for store logo
This commit is contained in:
29
BTCPayServer.Client/BTCPayServerClient.Files.cs
Normal file
29
BTCPayServer.Client/BTCPayServerClient.Files.cs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,4 +37,13 @@ public partial class BTCPayServerClient
|
|||||||
return await SendHttpRequest<StoreData>($"api/v1/stores/{storeId}", request, HttpMethod.Put, token);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,16 @@ public partial class BTCPayServerClient
|
|||||||
return await SendHttpRequest<ApplicationUserData>("api/v1/users/me", request, HttpMethod.Put, token);
|
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)
|
public virtual async Task<ApplicationUserData> CreateUser(CreateApplicationUserRequest request, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
return await SendHttpRequest<ApplicationUserData>("api/v1/users", request, HttpMethod.Post, token);
|
return await SendHttpRequest<ApplicationUserData>("api/v1/users", request, HttpMethod.Post, token);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
@@ -152,6 +153,19 @@ public partial class BTCPayServerClient
|
|||||||
return request;
|
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)
|
public static void AppendPayloadToQuery(UriBuilder uri, KeyValuePair<string, object> keyValuePair)
|
||||||
{
|
{
|
||||||
if (uri.Query.Length > 1)
|
if (uri.Query.Length > 1)
|
||||||
|
|||||||
16
BTCPayServer.Client/Models/FileData.cs
Normal file
16
BTCPayServer.Client/Models/FileData.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
@@ -239,6 +239,76 @@ namespace BTCPayServer.Tests
|
|||||||
await newUserClient.GetInvoices(store.Id);
|
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)]
|
[Fact(Timeout = TestTimeout)]
|
||||||
[Trait("Integration", "Integration")]
|
[Trait("Integration", "Integration")]
|
||||||
public async Task CanCreateReadUpdateAndDeletePointOfSaleApp()
|
public async Task CanCreateReadUpdateAndDeletePointOfSaleApp()
|
||||||
|
|||||||
BIN
BTCPayServer.Tests/TestData/logo.png
Normal file
BIN
BTCPayServer.Tests/TestData/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.3 KiB |
@@ -161,10 +161,14 @@ namespace BTCPayServer.Tests
|
|||||||
{
|
{
|
||||||
errors.Remove(validationError);
|
errors.Remove(validationError);
|
||||||
}
|
}
|
||||||
valid = !errors.Any();
|
if (errors.Any())
|
||||||
|
{
|
||||||
|
foreach (ValidationError error in errors)
|
||||||
|
{
|
||||||
|
TestLogs.LogInformation($"Error Type: {error.ErrorType} - {error.Path}: {error.Message} - Value: {error.Value}");
|
||||||
|
}
|
||||||
|
}
|
||||||
Assert.Empty(errors);
|
Assert.Empty(errors);
|
||||||
Assert.True(valid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Abstractions.Constants;
|
||||||
|
using BTCPayServer.Abstractions.Contracts;
|
||||||
|
using BTCPayServer.Abstractions.Extensions;
|
||||||
|
using BTCPayServer.Client;
|
||||||
|
using BTCPayServer.Client.Models;
|
||||||
|
using BTCPayServer.Data;
|
||||||
|
using BTCPayServer.Storage.Services;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Cors;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Controllers.Greenfield;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[EnableCors(CorsPolicies.All)]
|
||||||
|
[Authorize(Policy = Policies.CanModifyServerSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
|
public class GreenfieldFilesController(
|
||||||
|
UserManager<ApplicationUser> userManager,
|
||||||
|
IFileService fileService,
|
||||||
|
StoredFileRepository fileRepository)
|
||||||
|
: Controller
|
||||||
|
{
|
||||||
|
[HttpGet("~/api/v1/files")]
|
||||||
|
public async Task<IActionResult> GetFiles()
|
||||||
|
{
|
||||||
|
var storedFiles = await fileRepository.GetFiles();
|
||||||
|
var files = new List<FileData>();
|
||||||
|
foreach (var file in storedFiles)
|
||||||
|
files.Add(await ToFileData(file));
|
||||||
|
return Ok(files);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("~/api/v1/files/{fileId}")]
|
||||||
|
public async Task<IActionResult> GetFile(string fileId)
|
||||||
|
{
|
||||||
|
var file = await fileRepository.GetFile(fileId);
|
||||||
|
return file == null
|
||||||
|
? this.CreateAPIError(404, "file-not-found", "The file does not exist.")
|
||||||
|
: Ok(await ToFileData(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("~/api/v1/files")]
|
||||||
|
public async Task<IActionResult> UploadFile(IFormFile file)
|
||||||
|
{
|
||||||
|
if (file is null)
|
||||||
|
ModelState.AddModelError(nameof(file), "Invalid file");
|
||||||
|
else if (!file.FileName.IsValidFileName())
|
||||||
|
ModelState.AddModelError(nameof(file.FileName), "Invalid filename");
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
return this.CreateValidationError(ModelState);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var userId = userManager.GetUserId(User)!;
|
||||||
|
var newFile = await fileService.AddFile(file!, userId);
|
||||||
|
return Ok(await ToFileData(newFile));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return this.CreateAPIError(404, "file-upload-failed", e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("~/api/v1/files/{fileId}")]
|
||||||
|
public async Task<IActionResult> DeleteFile(string fileId)
|
||||||
|
{
|
||||||
|
var file = await fileRepository.GetFile(fileId);
|
||||||
|
if (file == null) return this.CreateAPIError(404, "file-not-found", "The file does not exist.");
|
||||||
|
await fileRepository.RemoveFile(file);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<FileData> ToFileData(IStoredFile file)
|
||||||
|
{
|
||||||
|
return new FileData
|
||||||
|
{
|
||||||
|
Id = file.Id,
|
||||||
|
UserId = file.ApplicationUserId,
|
||||||
|
Uri = new UnresolvedUri.FileIdUri(file.Id).ToString(),
|
||||||
|
Url = await fileService.GetFileUrl(Request.GetAbsoluteRootUri(), file.Id),
|
||||||
|
OriginalName = file.FileName,
|
||||||
|
StorageName = file.StorageFileName,
|
||||||
|
CreatedAt = file.Timestamp
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,12 +3,13 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Abstractions.Constants;
|
using BTCPayServer.Abstractions.Constants;
|
||||||
|
using BTCPayServer.Abstractions.Contracts;
|
||||||
using BTCPayServer.Abstractions.Extensions;
|
using BTCPayServer.Abstractions.Extensions;
|
||||||
using BTCPayServer.Client;
|
using BTCPayServer.Client;
|
||||||
using BTCPayServer.Client.Models;
|
using BTCPayServer.Client.Models;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
using BTCPayServer.Security;
|
using BTCPayServer.Services;
|
||||||
using BTCPayServer.Services.Rates;
|
using BTCPayServer.Services.Rates;
|
||||||
using BTCPayServer.Services.Stores;
|
using BTCPayServer.Services.Stores;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
@@ -27,31 +28,40 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
{
|
{
|
||||||
private readonly StoreRepository _storeRepository;
|
private readonly StoreRepository _storeRepository;
|
||||||
private readonly UserManager<ApplicationUser> _userManager;
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
|
private readonly IFileService _fileService;
|
||||||
|
private readonly UriResolver _uriResolver;
|
||||||
|
|
||||||
public GreenfieldStoresController(StoreRepository storeRepository, UserManager<ApplicationUser> userManager)
|
public GreenfieldStoresController(
|
||||||
|
StoreRepository storeRepository,
|
||||||
|
UserManager<ApplicationUser> userManager,
|
||||||
|
IFileService fileService,
|
||||||
|
UriResolver uriResolver)
|
||||||
{
|
{
|
||||||
_storeRepository = storeRepository;
|
_storeRepository = storeRepository;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
|
_fileService = fileService;
|
||||||
|
_uriResolver = uriResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
[HttpGet("~/api/v1/stores")]
|
[HttpGet("~/api/v1/stores")]
|
||||||
public Task<ActionResult<IEnumerable<Client.Models.StoreData>>> GetStores()
|
public async Task<ActionResult<IEnumerable<Client.Models.StoreData>>> GetStores()
|
||||||
{
|
{
|
||||||
var stores = HttpContext.GetStoresData();
|
var storesData = HttpContext.GetStoresData();
|
||||||
return Task.FromResult<ActionResult<IEnumerable<Client.Models.StoreData>>>(Ok(stores.Select(FromModel)));
|
var stores = new List<Client.Models.StoreData>();
|
||||||
|
foreach (var storeData in storesData)
|
||||||
|
{
|
||||||
|
stores.Add(await FromModel(storeData));
|
||||||
|
}
|
||||||
|
return Ok(stores);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
[HttpGet("~/api/v1/stores/{storeId}")]
|
[HttpGet("~/api/v1/stores/{storeId}")]
|
||||||
public IActionResult GetStore(string storeId)
|
public async Task<IActionResult> GetStore(string storeId)
|
||||||
{
|
{
|
||||||
var store = HttpContext.GetStoreData();
|
var store = HttpContext.GetStoreData();
|
||||||
if (store == null)
|
return store == null ? StoreNotFound() : Ok(await FromModel(store));
|
||||||
{
|
|
||||||
return StoreNotFound();
|
|
||||||
}
|
|
||||||
return Ok(FromModel(store));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
@@ -59,10 +69,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
public async Task<IActionResult> RemoveStore(string storeId)
|
public async Task<IActionResult> RemoveStore(string storeId)
|
||||||
{
|
{
|
||||||
var store = HttpContext.GetStoreData();
|
var store = HttpContext.GetStoreData();
|
||||||
if (store == null)
|
if (store == null) return StoreNotFound();
|
||||||
{
|
|
||||||
return StoreNotFound();
|
|
||||||
}
|
|
||||||
await _storeRepository.RemoveStore(storeId, _userManager.GetUserId(User));
|
await _storeRepository.RemoveStore(storeId, _userManager.GetUserId(User));
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
@@ -72,17 +80,13 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
public async Task<IActionResult> CreateStore(CreateStoreRequest request)
|
public async Task<IActionResult> CreateStore(CreateStoreRequest request)
|
||||||
{
|
{
|
||||||
var validationResult = Validate(request);
|
var validationResult = Validate(request);
|
||||||
if (validationResult != null)
|
if (validationResult != null) return validationResult;
|
||||||
{
|
|
||||||
return validationResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
var store = new Data.StoreData();
|
|
||||||
|
|
||||||
|
var store = new StoreData();
|
||||||
PaymentMethodId.TryParse(request.DefaultPaymentMethod, out var defaultPaymentMethodId);
|
PaymentMethodId.TryParse(request.DefaultPaymentMethod, out var defaultPaymentMethodId);
|
||||||
ToModel(request, store, defaultPaymentMethodId);
|
ToModel(request, store, defaultPaymentMethodId);
|
||||||
await _storeRepository.CreateStore(_userManager.GetUserId(User), store);
|
await _storeRepository.CreateStore(_userManager.GetUserId(User), store);
|
||||||
return Ok(FromModel(store));
|
return Ok(await FromModel(store));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
@@ -90,24 +94,78 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
public async Task<IActionResult> UpdateStore(string storeId, UpdateStoreRequest request)
|
public async Task<IActionResult> UpdateStore(string storeId, UpdateStoreRequest request)
|
||||||
{
|
{
|
||||||
var store = HttpContext.GetStoreData();
|
var store = HttpContext.GetStoreData();
|
||||||
if (store == null)
|
if (store == null) return StoreNotFound();
|
||||||
{
|
|
||||||
return StoreNotFound();
|
|
||||||
}
|
|
||||||
var validationResult = Validate(request);
|
var validationResult = Validate(request);
|
||||||
if (validationResult != null)
|
if (validationResult != null) return validationResult;
|
||||||
{
|
|
||||||
return validationResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
PaymentMethodId.TryParse(request.DefaultPaymentMethod, out var defaultPaymentMethodId);
|
PaymentMethodId.TryParse(request.DefaultPaymentMethod, out var defaultPaymentMethodId);
|
||||||
|
|
||||||
ToModel(request, store, defaultPaymentMethodId);
|
ToModel(request, store, defaultPaymentMethodId);
|
||||||
await _storeRepository.UpdateStore(store);
|
await _storeRepository.UpdateStore(store);
|
||||||
return Ok(FromModel(store));
|
return Ok(await FromModel(store));
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static Client.Models.StoreData FromModel(StoreData data)
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
|
[HttpPost("~/api/v1/stores/{storeId}/logo")]
|
||||||
|
public async Task<IActionResult> UploadStoreLogo(string storeId, IFormFile file)
|
||||||
|
{
|
||||||
|
var store = HttpContext.GetStoreData();
|
||||||
|
if (store == null) return StoreNotFound();
|
||||||
|
|
||||||
|
if (file is null)
|
||||||
|
ModelState.AddModelError(nameof(file), "Invalid file");
|
||||||
|
else if (file.Length > 1_000_000)
|
||||||
|
ModelState.AddModelError(nameof(file), "The uploaded image file should be less than 1MB");
|
||||||
|
else if (!file.ContentType.StartsWith("image/", StringComparison.InvariantCulture))
|
||||||
|
ModelState.AddModelError(nameof(file), "The uploaded file needs to be an image");
|
||||||
|
else if (!file.FileName.IsValidFileName())
|
||||||
|
ModelState.AddModelError(nameof(file.FileName), "Invalid filename");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var formFile = await file.Bufferize();
|
||||||
|
if (!FileTypeDetector.IsPicture(formFile.Buffer, formFile.FileName))
|
||||||
|
ModelState.AddModelError(nameof(file), "The uploaded file needs to be an image");
|
||||||
|
}
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
return this.CreateValidationError(ModelState);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var userId = _userManager.GetUserId(User)!;
|
||||||
|
var storedFile = await _fileService.AddFile(file!, userId);
|
||||||
|
var blob = store.GetStoreBlob();
|
||||||
|
blob.LogoUrl = new UnresolvedUri.FileIdUri(storedFile.Id);
|
||||||
|
store.SetStoreBlob(blob);
|
||||||
|
await _storeRepository.UpdateStore(store);
|
||||||
|
|
||||||
|
return Ok(await FromModel(store));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return this.CreateAPIError(404, "file-upload-failed", e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
|
[HttpDelete("~/api/v1/stores/{storeId}/logo")]
|
||||||
|
public async Task<IActionResult> DeleteStoreLogo(string storeId)
|
||||||
|
{
|
||||||
|
var store = HttpContext.GetStoreData();
|
||||||
|
if (store == null) return StoreNotFound();
|
||||||
|
|
||||||
|
var blob = store.GetStoreBlob();
|
||||||
|
var fileId = (blob.LogoUrl as UnresolvedUri.FileIdUri)?.FileId;
|
||||||
|
if (!string.IsNullOrEmpty(fileId))
|
||||||
|
{
|
||||||
|
var userId = _userManager.GetUserId(User)!;
|
||||||
|
await _fileService.RemoveFile(fileId, userId);
|
||||||
|
blob.LogoUrl = null;
|
||||||
|
store.SetStoreBlob(blob);
|
||||||
|
await _storeRepository.UpdateStore(store);
|
||||||
|
}
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task<Client.Models.StoreData> FromModel(StoreData data)
|
||||||
{
|
{
|
||||||
var storeBlob = data.GetStoreBlob();
|
var storeBlob = data.GetStoreBlob();
|
||||||
return new Client.Models.StoreData
|
return new Client.Models.StoreData
|
||||||
@@ -117,9 +175,9 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
Website = data.StoreWebsite,
|
Website = data.StoreWebsite,
|
||||||
Archived = data.Archived,
|
Archived = data.Archived,
|
||||||
BrandColor = storeBlob.BrandColor,
|
BrandColor = storeBlob.BrandColor,
|
||||||
CssUrl = storeBlob.CssUrl?.ToString(),
|
CssUrl = storeBlob.CssUrl == null ? null : await _uriResolver.Resolve(Request.GetAbsoluteRootUri(), storeBlob.CssUrl),
|
||||||
LogoUrl = storeBlob.LogoUrl?.ToString(),
|
LogoUrl = storeBlob.LogoUrl == null ? null : await _uriResolver.Resolve(Request.GetAbsoluteRootUri(), storeBlob.LogoUrl),
|
||||||
PaymentSoundUrl = storeBlob.PaymentSoundUrl?.ToString(),
|
PaymentSoundUrl = storeBlob.PaymentSoundUrl == null ? null : await _uriResolver.Resolve(Request.GetAbsoluteRootUri(), storeBlob.PaymentSoundUrl),
|
||||||
SupportUrl = storeBlob.StoreSupportUrl,
|
SupportUrl = storeBlob.StoreSupportUrl,
|
||||||
SpeedPolicy = data.SpeedPolicy,
|
SpeedPolicy = data.SpeedPolicy,
|
||||||
DefaultPaymentMethod = data.GetDefaultPaymentId()?.ToString(),
|
DefaultPaymentMethod = data.GetDefaultPaymentId()?.ToString(),
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
using System.Linq;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Abstractions.Constants;
|
using BTCPayServer.Abstractions.Constants;
|
||||||
using BTCPayServer.Client;
|
using BTCPayServer.Client;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Security;
|
|
||||||
using BTCPayServer.Services.Stores;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Cors;
|
using Microsoft.AspNetCore.Cors;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
@@ -22,14 +20,14 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
public class GreenfieldTestApiKeyController : ControllerBase
|
public class GreenfieldTestApiKeyController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly UserManager<ApplicationUser> _userManager;
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
private readonly StoreRepository _storeRepository;
|
private readonly GreenfieldStoresController _greenfieldStoresController;
|
||||||
private readonly BTCPayServerClient _localBTCPayServerClient;
|
|
||||||
|
|
||||||
public GreenfieldTestApiKeyController(UserManager<ApplicationUser> userManager, StoreRepository storeRepository, BTCPayServerClient localBTCPayServerClient)
|
public GreenfieldTestApiKeyController(
|
||||||
|
UserManager<ApplicationUser> userManager,
|
||||||
|
GreenfieldStoresController greenfieldStoresController)
|
||||||
{
|
{
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_storeRepository = storeRepository;
|
_greenfieldStoresController = greenfieldStoresController;
|
||||||
_localBTCPayServerClient = localBTCPayServerClient;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("me/id")]
|
[HttpGet("me/id")]
|
||||||
@@ -55,9 +53,15 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
|
|
||||||
[HttpGet("me/stores")]
|
[HttpGet("me/stores")]
|
||||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
public BTCPayServer.Client.Models.StoreData[] GetCurrentUserStores()
|
public async Task<BTCPayServer.Client.Models.StoreData[]> GetCurrentUserStores()
|
||||||
{
|
{
|
||||||
return this.HttpContext.GetStoresData().Select(Greenfield.GreenfieldStoresController.FromModel).ToArray();
|
var storesData = HttpContext.GetStoresData();
|
||||||
|
var stores = new List<Client.Models.StoreData>();
|
||||||
|
foreach (var storeData in storesData)
|
||||||
|
{
|
||||||
|
stores.Add(await _greenfieldStoresController.FromModel(storeData));
|
||||||
|
}
|
||||||
|
return stores.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("me/stores/{storeId}/can-view")]
|
[HttpGet("me/stores/{storeId}/can-view")]
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using BTCPayServer.Abstractions.Constants;
|
using BTCPayServer.Abstractions.Constants;
|
||||||
|
using BTCPayServer.Abstractions.Contracts;
|
||||||
using BTCPayServer.Abstractions.Extensions;
|
using BTCPayServer.Abstractions.Extensions;
|
||||||
using BTCPayServer.Client;
|
using BTCPayServer.Client;
|
||||||
using BTCPayServer.Client.Models;
|
using BTCPayServer.Client.Models;
|
||||||
@@ -17,6 +18,7 @@ using BTCPayServer.Security.Greenfield;
|
|||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Cors;
|
using Microsoft.AspNetCore.Cors;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using NicolasDorier.RateLimits;
|
using NicolasDorier.RateLimits;
|
||||||
@@ -41,6 +43,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
private readonly IAuthorizationService _authorizationService;
|
private readonly IAuthorizationService _authorizationService;
|
||||||
private readonly UserService _userService;
|
private readonly UserService _userService;
|
||||||
private readonly UriResolver _uriResolver;
|
private readonly UriResolver _uriResolver;
|
||||||
|
private readonly IFileService _fileService;
|
||||||
|
|
||||||
public GreenfieldUsersController(UserManager<ApplicationUser> userManager,
|
public GreenfieldUsersController(UserManager<ApplicationUser> userManager,
|
||||||
RoleManager<IdentityRole> roleManager,
|
RoleManager<IdentityRole> roleManager,
|
||||||
@@ -53,6 +56,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
IAuthorizationService authorizationService,
|
IAuthorizationService authorizationService,
|
||||||
UserService userService,
|
UserService userService,
|
||||||
UriResolver uriResolver,
|
UriResolver uriResolver,
|
||||||
|
IFileService fileService,
|
||||||
Logs logs)
|
Logs logs)
|
||||||
{
|
{
|
||||||
this.Logs = logs;
|
this.Logs = logs;
|
||||||
@@ -67,6 +71,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
_authorizationService = authorizationService;
|
_authorizationService = authorizationService;
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
_uriResolver = uriResolver;
|
_uriResolver = uriResolver;
|
||||||
|
_fileService = fileService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = Policies.CanViewUsers, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
[Authorize(Policy = Policies.CanViewUsers, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
@@ -224,6 +229,68 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
return Ok(model);
|
return Ok(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authorize(Policy = Policies.CanModifyProfile, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
|
[HttpPost("~/api/v1/users/me/picture")]
|
||||||
|
public async Task<IActionResult> UploadCurrentUserProfilePicture(IFormFile? file)
|
||||||
|
{
|
||||||
|
if (file is null)
|
||||||
|
ModelState.AddModelError(nameof(file), "Invalid file");
|
||||||
|
else if (file.Length > 1_000_000)
|
||||||
|
ModelState.AddModelError(nameof(file), "The uploaded image file should be less than 1MB");
|
||||||
|
else if (!file.ContentType.StartsWith("image/", StringComparison.InvariantCulture))
|
||||||
|
ModelState.AddModelError(nameof(file), "The uploaded file needs to be an image");
|
||||||
|
else if (!file.FileName.IsValidFileName())
|
||||||
|
ModelState.AddModelError(nameof(file.FileName), "Invalid filename");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var formFile = await file.Bufferize();
|
||||||
|
if (!FileTypeDetector.IsPicture(formFile.Buffer, formFile.FileName))
|
||||||
|
ModelState.AddModelError(nameof(file), "The uploaded file needs to be an image");
|
||||||
|
}
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
return this.CreateValidationError(ModelState);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var user = await _userManager.GetUserAsync(User);
|
||||||
|
var storedFile = await _fileService.AddFile(file!, user!.Id);
|
||||||
|
var blob = user.GetBlob() ?? new UserBlob();
|
||||||
|
var fileIdUri = new UnresolvedUri.FileIdUri(storedFile.Id);
|
||||||
|
blob.ImageUrl = fileIdUri.ToString();
|
||||||
|
user.SetBlob(blob);
|
||||||
|
await _userManager.UpdateAsync(user);
|
||||||
|
|
||||||
|
var model = await FromModel(user);
|
||||||
|
return Ok(model);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return this.CreateAPIError(404, "file-upload-failed", e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize(Policy = Policies.CanModifyProfile, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
|
[HttpDelete("~/api/v1/users/me/picture")]
|
||||||
|
public async Task<IActionResult> DeleteCurrentUserProfilePicture()
|
||||||
|
{
|
||||||
|
var user = await _userManager.GetUserAsync(User);
|
||||||
|
if (user is null)
|
||||||
|
{
|
||||||
|
return this.UserNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
var blob = user.GetBlob() ?? new UserBlob();
|
||||||
|
if (!string.IsNullOrEmpty(blob.ImageUrl))
|
||||||
|
{
|
||||||
|
var fileId = (UnresolvedUri.Create(blob.ImageUrl) as UnresolvedUri.FileIdUri)?.FileId;
|
||||||
|
if (!string.IsNullOrEmpty(fileId)) await _fileService.RemoveFile(fileId, user.Id);
|
||||||
|
blob.ImageUrl = null;
|
||||||
|
user.SetBlob(blob);
|
||||||
|
await _userManager.UpdateAsync(user);
|
||||||
|
}
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
[Authorize(Policy = Policies.CanDeleteUser, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
[Authorize(Policy = Policies.CanDeleteUser, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||||
[HttpDelete("~/api/v1/users/me")]
|
[HttpDelete("~/api/v1/users/me")]
|
||||||
public async Task<IActionResult> DeleteCurrentUser()
|
public async Task<IActionResult> DeleteCurrentUser()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
@@ -19,6 +20,7 @@ using Microsoft.AspNetCore.Authorization;
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.StaticFiles;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
@@ -772,9 +774,9 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
return GetFromActionResult(await GetController<GreenfieldStoresController>().GetStores());
|
return GetFromActionResult(await GetController<GreenfieldStoresController>().GetStores());
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task<StoreData> GetStore(string storeId, CancellationToken token = default)
|
public override async Task<StoreData> GetStore(string storeId, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
return Task.FromResult(GetFromActionResult<StoreData>(GetController<GreenfieldStoresController>().GetStore(storeId)));
|
return GetFromActionResult<StoreData>(await GetController<GreenfieldStoresController>().GetStore(storeId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task RemoveStore(string storeId, CancellationToken token = default)
|
public override async Task RemoveStore(string storeId, CancellationToken token = default)
|
||||||
@@ -793,6 +795,17 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
return GetFromActionResult<StoreData>(await GetController<GreenfieldStoresController>().UpdateStore(storeId, request));
|
return GetFromActionResult<StoreData>(await GetController<GreenfieldStoresController>().UpdateStore(storeId, request));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override async Task<StoreData> UploadStoreLogo(string storeId, string filePath, string mimeType, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
var file = GetFormFile(filePath, mimeType);
|
||||||
|
return GetFromActionResult<StoreData>(await GetController<GreenfieldStoresController>().UploadStoreLogo(storeId, file));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task DeleteStoreLogo(string storeId, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
HandleActionResult(await GetController<GreenfieldStoresController>().DeleteStoreLogo(storeId));
|
||||||
|
}
|
||||||
|
|
||||||
public override async Task<IEnumerable<InvoiceData>> GetInvoices(string storeId, string[] orderId = null,
|
public override async Task<IEnumerable<InvoiceData>> GetInvoices(string storeId, string[] orderId = null,
|
||||||
InvoiceStatus[] status = null,
|
InvoiceStatus[] status = null,
|
||||||
DateTimeOffset? startDate = null,
|
DateTimeOffset? startDate = null,
|
||||||
@@ -880,6 +893,17 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
return GetFromActionResult<ApplicationUserData>(await GetController<GreenfieldUsersController>().UpdateCurrentUser(request, token));
|
return GetFromActionResult<ApplicationUserData>(await GetController<GreenfieldUsersController>().UpdateCurrentUser(request, token));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override async Task<ApplicationUserData> UploadCurrentUserProfilePicture(string filePath, string mimeType, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
var file = GetFormFile(filePath, mimeType);
|
||||||
|
return GetFromActionResult<ApplicationUserData>(await GetController<GreenfieldUsersController>().UploadCurrentUserProfilePicture(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task DeleteCurrentUserProfilePicture(CancellationToken token = default)
|
||||||
|
{
|
||||||
|
HandleActionResult(await GetController<GreenfieldUsersController>().DeleteCurrentUserProfilePicture());
|
||||||
|
}
|
||||||
|
|
||||||
public override async Task DeleteCurrentUser(CancellationToken token = default)
|
public override async Task DeleteCurrentUser(CancellationToken token = default)
|
||||||
{
|
{
|
||||||
HandleActionResult(await GetController<GreenfieldUsersController>().DeleteCurrentUser());
|
HandleActionResult(await GetController<GreenfieldUsersController>().DeleteCurrentUser());
|
||||||
@@ -1251,5 +1275,37 @@ namespace BTCPayServer.Controllers.Greenfield
|
|||||||
{
|
{
|
||||||
return GetFromActionResult<List<RoleData>>(await GetController<GreenfieldStoreRolesController>().GetStoreRoles(storeId));
|
return GetFromActionResult<List<RoleData>>(await GetController<GreenfieldStoreRolesController>().GetStoreRoles(storeId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override async Task<FileData[]> GetFiles(CancellationToken token = default)
|
||||||
|
{
|
||||||
|
return GetFromActionResult<FileData[]>(await GetController<GreenfieldFilesController>().GetFiles());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<FileData> GetFile(string fileId, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
return GetFromActionResult<FileData>(await GetController<GreenfieldFilesController>().GetFile(fileId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<FileData> UploadFile(string filePath, string mimeType, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
var file = GetFormFile(filePath, mimeType);
|
||||||
|
return GetFromActionResult<FileData>(await GetController<GreenfieldFilesController>().UploadFile(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task DeleteFile(string fileId, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
HandleActionResult(await GetController<GreenfieldFilesController>().DeleteFile(fileId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private IFormFile GetFormFile(string filePath, string mimeType)
|
||||||
|
{
|
||||||
|
var fileName = Path.GetFileName(filePath);
|
||||||
|
var fs = File.OpenRead(filePath);
|
||||||
|
return new FormFile(fs, 0, fs.Length, fileName, fileName)
|
||||||
|
{
|
||||||
|
Headers = new HeaderDictionary(),
|
||||||
|
ContentType = mimeType
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#nullable enable
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace BTCPayServer
|
namespace BTCPayServer
|
||||||
|
|||||||
223
BTCPayServer/wwwroot/swagger/v1/swagger.template.files.json
Normal file
223
BTCPayServer/wwwroot/swagger/v1/swagger.template.files.json
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
{
|
||||||
|
"paths": {
|
||||||
|
"/api/v1/files": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "Files_GetFiles",
|
||||||
|
"tags": [
|
||||||
|
"Files"
|
||||||
|
],
|
||||||
|
"summary": "Get all files",
|
||||||
|
"description": "Load all files that exist.",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Files found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/FileData"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Missing authorization for loading the files"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"API_Key": [
|
||||||
|
"btcpay.server.canmodifyserversettings"
|
||||||
|
],
|
||||||
|
"Basic": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Files"
|
||||||
|
],
|
||||||
|
"summary": "Uploads a file",
|
||||||
|
"description": "Uploads a file",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"multipart/form-data": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"file": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The profile picture",
|
||||||
|
"format": "binary"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"operationId": "Files_UploadFile",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Uploads a file",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/FileData"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"415": {
|
||||||
|
"description": "The upload did not work"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"API_Key": [
|
||||||
|
"btcpay.server.canmodifyserversettings"
|
||||||
|
],
|
||||||
|
"Basic": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/files/{fileId}": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "Files_GetFile",
|
||||||
|
"tags": [
|
||||||
|
"Files"
|
||||||
|
],
|
||||||
|
"summary": "Get file",
|
||||||
|
"description": "View information about the specified file",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "fileId",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"description": "The file information to fetch",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "File found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/FileData"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Missing authorization for loading the file"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"API_Key": [
|
||||||
|
"btcpay.server.canmodifyserversettings"
|
||||||
|
],
|
||||||
|
"Basic": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"tags": [
|
||||||
|
"Files"
|
||||||
|
],
|
||||||
|
"summary": "Delete file",
|
||||||
|
"description": "Deletes the file",
|
||||||
|
"operationId": "Files_DeleteFile",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "fileId",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"description": "The file to delete",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "File deleted successfully"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "The file could not be found"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"API_Key": [
|
||||||
|
"btcpay.server.canmodifyserversettings"
|
||||||
|
],
|
||||||
|
"Basic": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"FileData": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The id of the file",
|
||||||
|
"nullable": false
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The id of the user that uploaded the file",
|
||||||
|
"nullable": false
|
||||||
|
},
|
||||||
|
"uri": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The internal URI of the file",
|
||||||
|
"nullable": false
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The full URL of the file",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"originalName": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The original name of the file",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"storageName": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The storage name of the file",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"created": {
|
||||||
|
"nullable": true,
|
||||||
|
"description": "The creation date of the file as a unix timestamp",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/UnixTimestamp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"name": "Files",
|
||||||
|
"description": "File operations"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -255,6 +255,80 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/stores/{storeId}/logo": {
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Stores"
|
||||||
|
],
|
||||||
|
"summary": "Uploads a logo for the store",
|
||||||
|
"description": "Uploads a logo for the store",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"multipart/form-data": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"file": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The logo",
|
||||||
|
"format": "binary"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"operationId": "Stores_UploadStoreLogo",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Uploads a logo for the store",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ApplicationUserData"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "The store could not be found"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"API_Key": [
|
||||||
|
"btcpay.store.canmodifystoresettings"
|
||||||
|
],
|
||||||
|
"Basic": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"tags": [
|
||||||
|
"Stores"
|
||||||
|
],
|
||||||
|
"summary": "Deletes the store logo",
|
||||||
|
"description": "Delete the store's logo",
|
||||||
|
"operationId": "Stores_DeleteStoreLogo",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Store logo deleted successfully"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "The store could not be found"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"API_Key": [
|
||||||
|
"btcpay.store.canmodifystoresettings"
|
||||||
|
],
|
||||||
|
"Basic": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/stores/{storeId}/roles": {
|
"/api/v1/stores/{storeId}/roles": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
|
|||||||
@@ -128,6 +128,80 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/users/me/picture": {
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
|
"summary": "Uploads a profile picture for the current user",
|
||||||
|
"description": "Uploads a profile picture for the current user",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"multipart/form-data": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"file": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The profile picture",
|
||||||
|
"format": "binary"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"operationId": "Users_UploadCurrentUserProfilePicture",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Uploads a profile picture for the current user",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ApplicationUserData"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "The user could not be found"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"API_Key": [
|
||||||
|
"btcpay.user.canmodifyprofile"
|
||||||
|
],
|
||||||
|
"Basic": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
|
"summary": "Deletes user profile picture",
|
||||||
|
"description": "Deletes the user profile picture",
|
||||||
|
"operationId": "Users_DeleteCurrentUserProfilePicture",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Profile picture deleted successfully"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "The user could not be found"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"API_Key": [
|
||||||
|
"btcpay.user.canmodifyprofile"
|
||||||
|
],
|
||||||
|
"Basic": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/users": {
|
"/api/v1/users": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "Users_GetUsers",
|
"operationId": "Users_GetUsers",
|
||||||
|
|||||||
Reference in New Issue
Block a user