mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Abstracted cloud storage - Amazon/Google/Azure/Local (#708)
* wip * add in storage system * ui fixes * fix settings ui * Add Files Crud UI * add titles * link files to users * add migration * set blob to public * remove base 64 read code * fix file query model init * move view model to own file * fix local root path * use datadir for local storage * move to services * add direct file url * try fix tests * remove magic string * remove other magic strings * show error message on unsupported provider * fix asp net version * redirect to storage settings if provider is not supported * start writing tests * fix tests * fix test again * add some more to the tests * more tests * try making local provider work on tests * fix formfile * fix small issue with returning deleted file * check if returned data is null for deleted file * validate azure Container name * more state fixes * change azure test trait * add tmp file url generator * fix tests * small clean * disable amazon and google comment out unused code for now comment out google/amazon
This commit is contained in:
committed by
Nicolas Dorier
parent
02d79de17c
commit
b184360eb7
@@ -33,4 +33,5 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BTCPayServer\BTCPayServer.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
247
BTCPayServer.Tests/StorageTests.cs
Normal file
247
BTCPayServer.Tests/StorageTests.cs
Normal file
@@ -0,0 +1,247 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.ServerViewModels;
|
||||
using BTCPayServer.Storage.Models;
|
||||
using BTCPayServer.Storage.Services.Providers.AzureBlobStorage.Configuration;
|
||||
using BTCPayServer.Storage.Services.Providers.FileSystemStorage.Configuration;
|
||||
using BTCPayServer.Storage.ViewModels;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Resources;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
public class StorageTests
|
||||
{
|
||||
public StorageTests(ITestOutputHelper helper)
|
||||
{
|
||||
Logs.Tester = new XUnitLog(helper) {Name = "Tests"};
|
||||
Logs.LogProvider = new XUnitLogProvider(helper);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async void CanConfigureStorage()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
var controller = tester.PayTester.GetController<ServerController>(user.UserId, user.StoreId);
|
||||
|
||||
// //For some reason, the tests cache something on circleci and this is set by default
|
||||
// //Initially, there is no configuration, make sure we display the choices available to configure
|
||||
// Assert.IsType<StorageSettings>(Assert.IsType<ViewResult>(await controller.Storage()).Model);
|
||||
//
|
||||
// //the file list should tell us it's not configured:
|
||||
// var viewFilesViewModelInitial =
|
||||
// Assert.IsType<ViewFilesViewModel>(Assert.IsType<ViewResult>(await controller.Files()).Model);
|
||||
// Assert.False(viewFilesViewModelInitial.StorageConfigured);
|
||||
|
||||
|
||||
//Once we select a provider, redirect to its view
|
||||
var localResult = Assert
|
||||
.IsType<RedirectToActionResult>(controller.Storage(new StorageSettings()
|
||||
{
|
||||
Provider = StorageProvider.FileSystem
|
||||
}));
|
||||
Assert.Equal(nameof(ServerController.StorageProvider), localResult.ActionName);
|
||||
Assert.Equal(StorageProvider.FileSystem.ToString(), localResult.RouteValues["provider"]);
|
||||
|
||||
//
|
||||
// var AmazonS3result = Assert
|
||||
// .IsType<RedirectToActionResult>(controller.Storage(new StorageSettings()
|
||||
// {
|
||||
// Provider = StorageProvider.AmazonS3
|
||||
// }));
|
||||
// Assert.Equal(nameof(ServerController.StorageProvider), AmazonS3result.ActionName);
|
||||
// Assert.Equal(StorageProvider.AmazonS3.ToString(), AmazonS3result.RouteValues["provider"]);
|
||||
//
|
||||
// var GoogleResult = Assert
|
||||
// .IsType<RedirectToActionResult>(controller.Storage(new StorageSettings()
|
||||
// {
|
||||
// Provider = StorageProvider.GoogleCloudStorage
|
||||
// }));
|
||||
// Assert.Equal(nameof(ServerController.StorageProvider), GoogleResult.ActionName);
|
||||
// Assert.Equal(StorageProvider.GoogleCloudStorage.ToString(), GoogleResult.RouteValues["provider"]);
|
||||
//
|
||||
|
||||
var AzureResult = Assert
|
||||
.IsType<RedirectToActionResult>(controller.Storage(new StorageSettings()
|
||||
{
|
||||
Provider = StorageProvider.AzureBlobStorage
|
||||
}));
|
||||
Assert.Equal(nameof(ServerController.StorageProvider), AzureResult.ActionName);
|
||||
Assert.Equal(StorageProvider.AzureBlobStorage.ToString(), AzureResult.RouteValues["provider"]);
|
||||
|
||||
//Cool, we get redirected to the config pages
|
||||
//Let's configure this stuff
|
||||
|
||||
//Let's try and cheat and go to an invalid storage provider config
|
||||
Assert.Equal(nameof(Storage), (Assert
|
||||
.IsType<RedirectToActionResult>(await controller.StorageProvider("I am not a real provider"))
|
||||
.ActionName));
|
||||
|
||||
//ok no more messing around, let's configure this shit.
|
||||
var fileSystemStorageConfiguration = Assert.IsType<FileSystemStorageConfiguration>(Assert
|
||||
.IsType<ViewResult>(await controller.StorageProvider(StorageProvider.FileSystem.ToString()))
|
||||
.Model);
|
||||
|
||||
//local file system does not need config, easy days!
|
||||
Assert.IsType<ViewResult>(
|
||||
await controller.EditFileSystemStorageProvider(fileSystemStorageConfiguration));
|
||||
|
||||
//ok cool, let's see if this got set right
|
||||
var shouldBeRedirectingToLocalStorageConfigPage =
|
||||
Assert.IsType<RedirectToActionResult>(await controller.Storage());
|
||||
Assert.Equal(nameof(StorageProvider), shouldBeRedirectingToLocalStorageConfigPage.ActionName);
|
||||
Assert.Equal(StorageProvider.FileSystem,
|
||||
shouldBeRedirectingToLocalStorageConfigPage.RouteValues["provider"]);
|
||||
|
||||
|
||||
//if we tell the settings page to force, it should allow us to select a new provider
|
||||
Assert.IsType<ChooseStorageViewModel>(Assert.IsType<ViewResult>(await controller.Storage(true)).Model);
|
||||
|
||||
//awesome, now let's see if the files result says we're all set up
|
||||
var viewFilesViewModel =
|
||||
Assert.IsType<ViewFilesViewModel>(Assert.IsType<ViewResult>(await controller.Files()).Model);
|
||||
Assert.True(viewFilesViewModel.StorageConfigured);
|
||||
Assert.Empty(viewFilesViewModel.Files);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async void CanUseLocalProviderFiles()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
var controller = tester.PayTester.GetController<ServerController>(user.UserId, user.StoreId);
|
||||
|
||||
var fileSystemStorageConfiguration = Assert.IsType<FileSystemStorageConfiguration>(Assert
|
||||
.IsType<ViewResult>(await controller.StorageProvider(StorageProvider.FileSystem.ToString()))
|
||||
.Model);
|
||||
Assert.IsType<ViewResult>(
|
||||
await controller.EditFileSystemStorageProvider(fileSystemStorageConfiguration));
|
||||
|
||||
var shouldBeRedirectingToLocalStorageConfigPage =
|
||||
Assert.IsType<RedirectToActionResult>(await controller.Storage());
|
||||
Assert.Equal(nameof(StorageProvider), shouldBeRedirectingToLocalStorageConfigPage.ActionName);
|
||||
Assert.Equal(StorageProvider.FileSystem,
|
||||
shouldBeRedirectingToLocalStorageConfigPage.RouteValues["provider"]);
|
||||
|
||||
|
||||
await CanUploadRemoveFiles(controller);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("ExternalIntegration", "ExternalIntegration")]
|
||||
public async Task CanUseAzureBlobStorage()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
var controller = tester.PayTester.GetController<ServerController>(user.UserId, user.StoreId);
|
||||
var azureBlobStorageConfiguration = Assert.IsType<AzureBlobStorageConfiguration>(Assert
|
||||
.IsType<ViewResult>(await controller.StorageProvider(StorageProvider.AzureBlobStorage.ToString()))
|
||||
.Model);
|
||||
|
||||
azureBlobStorageConfiguration.ConnectionString = GetFromSecrets("AzureBlobStorageConnectionString");
|
||||
azureBlobStorageConfiguration.ContainerName = "testscontainer";
|
||||
Assert.IsType<ViewResult>(
|
||||
await controller.EditAzureBlobStorageStorageProvider(azureBlobStorageConfiguration));
|
||||
|
||||
|
||||
var shouldBeRedirectingToAzureStorageConfigPage =
|
||||
Assert.IsType<RedirectToActionResult>(await controller.Storage());
|
||||
Assert.Equal(nameof(StorageProvider), shouldBeRedirectingToAzureStorageConfigPage.ActionName);
|
||||
Assert.Equal(StorageProvider.AzureBlobStorage,
|
||||
shouldBeRedirectingToAzureStorageConfigPage.RouteValues["provider"]);
|
||||
|
||||
//seems like azure config worked, let's see if the conn string was actually saved
|
||||
|
||||
Assert.Equal(azureBlobStorageConfiguration.ConnectionString, Assert
|
||||
.IsType<AzureBlobStorageConfiguration>(Assert
|
||||
.IsType<ViewResult>(
|
||||
await controller.StorageProvider(StorageProvider.AzureBlobStorage.ToString()))
|
||||
.Model).ConnectionString);
|
||||
|
||||
|
||||
|
||||
await CanUploadRemoveFiles(controller);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task CanUploadRemoveFiles(ServerController controller)
|
||||
{
|
||||
var filename = "uploadtestfile.txt";
|
||||
var fileContent = "content";
|
||||
File.WriteAllText(filename, fileContent);
|
||||
|
||||
var fileInfo = new FileInfo(filename);
|
||||
var formFile = new FormFile(
|
||||
new FileStream(filename, FileMode.OpenOrCreate),
|
||||
0,
|
||||
fileInfo.Length, fileInfo.Name, fileInfo.Name)
|
||||
{
|
||||
Headers = new HeaderDictionary()
|
||||
};
|
||||
formFile.ContentType = "text/plain";
|
||||
formFile.ContentDisposition = $"form-data; name=\"file\"; filename=\"{fileInfo.Name}\"";
|
||||
var uploadFormFileResult = Assert.IsType<RedirectToActionResult>(await controller.CreateFile(formFile));
|
||||
Assert.True(uploadFormFileResult.RouteValues.ContainsKey("fileId"));
|
||||
var fileId = uploadFormFileResult.RouteValues["fileId"].ToString();
|
||||
Assert.Equal("Files", uploadFormFileResult.ActionName);
|
||||
|
||||
var viewFilesViewModel =
|
||||
Assert.IsType<ViewFilesViewModel>(Assert.IsType<ViewResult>(await controller.Files(fileId)).Model);
|
||||
|
||||
Assert.NotEmpty(viewFilesViewModel.Files);
|
||||
Assert.Equal(fileId, viewFilesViewModel.SelectedFileId);
|
||||
Assert.NotEmpty(viewFilesViewModel.DirectFileUrl);
|
||||
|
||||
|
||||
var net = new System.Net.WebClient();
|
||||
var data = await net.DownloadStringTaskAsync(new Uri(viewFilesViewModel.DirectFileUrl));
|
||||
Assert.Equal(fileContent, data);
|
||||
|
||||
Assert.Equal(StatusMessageModel.StatusSeverity.Success, new StatusMessageModel(Assert
|
||||
.IsType<RedirectToActionResult>(await controller.DeleteFile(fileId))
|
||||
.RouteValues["statusMessage"].ToString()).Severity);
|
||||
|
||||
viewFilesViewModel =
|
||||
Assert.IsType<ViewFilesViewModel>(Assert.IsType<ViewResult>(await controller.Files(fileId)).Model);
|
||||
|
||||
Assert.Null(viewFilesViewModel.DirectFileUrl);
|
||||
Assert.Null(viewFilesViewModel.SelectedFileId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static string GetFromSecrets(string key)
|
||||
{
|
||||
var builder = new ConfigurationBuilder();
|
||||
builder.AddUserSecrets("AB0AC1DD-9D26-485B-9416-56A33F268117");
|
||||
var config = builder.Build();
|
||||
var token = config[key];
|
||||
Assert.False(token == null, $"{key} is not set.\n Run \"dotnet user-secrets set {key} <value>\"");
|
||||
return token;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,11 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.9" />
|
||||
<PackageReference Include="TwentyTwenty.Storage" Version="2.10.1" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Amazon" Version="2.10.1" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Azure" Version="2.10.1" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Google" Version="2.10.1" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Local" Version="2.10.1" />
|
||||
<PackageReference Include="YamlDotNet" Version="5.2.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
289
BTCPayServer/Controllers/ServerController.Storage.cs
Normal file
289
BTCPayServer/Controllers/ServerController.Storage.cs
Normal file
@@ -0,0 +1,289 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.ServerViewModels;
|
||||
using BTCPayServer.Storage.Models;
|
||||
using BTCPayServer.Storage.Services.Providers.AmazonS3Storage;
|
||||
using BTCPayServer.Storage.Services.Providers.AmazonS3Storage.Configuration;
|
||||
using BTCPayServer.Storage.Services.Providers.AzureBlobStorage;
|
||||
using BTCPayServer.Storage.Services.Providers.AzureBlobStorage.Configuration;
|
||||
using BTCPayServer.Storage.Services.Providers.FileSystemStorage;
|
||||
using BTCPayServer.Storage.Services.Providers.FileSystemStorage.Configuration;
|
||||
using BTCPayServer.Storage.Services.Providers.GoogleCloudStorage;
|
||||
using BTCPayServer.Storage.Services.Providers.GoogleCloudStorage.Configuration;
|
||||
using BTCPayServer.Storage.Services.Providers.Models;
|
||||
using BTCPayServer.Storage.ViewModels;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
public partial class ServerController
|
||||
{
|
||||
[HttpGet("server/files/{fileId?}")]
|
||||
public async Task<IActionResult> Files(string fileId = null, string statusMessage = null)
|
||||
{
|
||||
TempData["StatusMessage"] = statusMessage;
|
||||
var fileUrl = string.IsNullOrEmpty(fileId) ? null : await _FileService.GetFileUrl(fileId);
|
||||
|
||||
return View(new ViewFilesViewModel()
|
||||
{
|
||||
Files = await _StoredFileRepository.GetFiles(),
|
||||
SelectedFileId = string.IsNullOrEmpty(fileUrl) ? null : fileId,
|
||||
DirectFileUrl = fileUrl,
|
||||
StorageConfigured = (await _SettingsRepository.GetSettingAsync<StorageSettings>()) != null
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("server/files/{fileId}/delete")]
|
||||
public async Task<IActionResult> DeleteFile(string fileId)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _FileService.RemoveFile(fileId, null);
|
||||
return RedirectToAction(nameof(Files), new
|
||||
{
|
||||
fileId = "",
|
||||
statusMessage = "File removed"
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return RedirectToAction(nameof(Files), new
|
||||
{
|
||||
statusMessage = new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = e.Message
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("server/files/{fileId}/tmp")]
|
||||
public async Task<IActionResult> CreateTemporaryFileUrl(string fileId)
|
||||
{
|
||||
var file = await _StoredFileRepository.GetFile(fileId);
|
||||
|
||||
if (file == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return View(new CreateTemporaryFileUrlViewModel());
|
||||
}
|
||||
|
||||
[HttpPost("server/files/{fileId}/tmp")]
|
||||
public async Task<IActionResult> CreateTemporaryFileUrl(string fileId,
|
||||
CreateTemporaryFileUrlViewModel viewModel)
|
||||
{
|
||||
if (viewModel.TimeAmount <= 0)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.TimeAmount), "Time must be at least 1");
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(viewModel);
|
||||
}
|
||||
|
||||
var file = await _StoredFileRepository.GetFile(fileId);
|
||||
|
||||
if (file == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var expiry = DateTimeOffset.Now;
|
||||
switch (viewModel.TimeType)
|
||||
{
|
||||
case CreateTemporaryFileUrlViewModel.TmpFileTimeType.Seconds:
|
||||
expiry =expiry.AddSeconds(viewModel.TimeAmount);
|
||||
break;
|
||||
case CreateTemporaryFileUrlViewModel.TmpFileTimeType.Minutes:
|
||||
expiry = expiry.AddMinutes(viewModel.TimeAmount);
|
||||
break;
|
||||
case CreateTemporaryFileUrlViewModel.TmpFileTimeType.Hours:
|
||||
expiry = expiry.AddHours(viewModel.TimeAmount);
|
||||
break;
|
||||
case CreateTemporaryFileUrlViewModel.TmpFileTimeType.Days:
|
||||
expiry = expiry.AddDays(viewModel.TimeAmount);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
var url = await _FileService.GetTemporaryFileUrl(fileId, expiry, viewModel.IsDownload);
|
||||
|
||||
return RedirectToAction(nameof(Files), new
|
||||
{
|
||||
StatusMessage = new StatusMessageModel()
|
||||
{
|
||||
Html =
|
||||
$"Generated Temporary Url for file {file.FileName} which expires at {expiry:G}. <a href='{url}' target='_blank'>{url}</a>"
|
||||
}.ToString(),
|
||||
fileId,
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public class CreateTemporaryFileUrlViewModel
|
||||
{
|
||||
public enum TmpFileTimeType
|
||||
{
|
||||
Seconds,
|
||||
Minutes,
|
||||
Hours,
|
||||
Days
|
||||
}
|
||||
public int TimeAmount { get; set; }
|
||||
public TmpFileTimeType TimeType { get; set; }
|
||||
public bool IsDownload { get; set; }
|
||||
}
|
||||
|
||||
|
||||
[HttpPost("server/files/upload")]
|
||||
public async Task<IActionResult> CreateFile(IFormFile file)
|
||||
{
|
||||
var newFile = await _FileService.AddFile(file, GetUserId());
|
||||
return RedirectToAction(nameof(Files), new
|
||||
{
|
||||
statusMessage = "File added!",
|
||||
fileId = newFile.Id
|
||||
});
|
||||
}
|
||||
|
||||
private string GetUserId()
|
||||
{
|
||||
return _UserManager.GetUserId(ControllerContext.HttpContext.User);
|
||||
}
|
||||
|
||||
[HttpGet("server/storage")]
|
||||
public async Task<IActionResult> Storage(bool forceChoice = false, string statusMessage = null)
|
||||
{
|
||||
TempData["StatusMessage"] = statusMessage;
|
||||
var savedSettings = await _SettingsRepository.GetSettingAsync<StorageSettings>();
|
||||
if (forceChoice || savedSettings == null)
|
||||
{
|
||||
return View(new ChooseStorageViewModel()
|
||||
{
|
||||
ShowChangeWarning = savedSettings != null,
|
||||
Provider = savedSettings?.Provider ?? BTCPayServer.Storage.Models.StorageProvider.FileSystem
|
||||
});
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(StorageProvider), new
|
||||
{
|
||||
provider = savedSettings.Provider
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("server/storage")]
|
||||
public IActionResult Storage(StorageSettings viewModel)
|
||||
{
|
||||
return RedirectToAction("StorageProvider", "Server", new
|
||||
{
|
||||
provider = viewModel.Provider.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("server/storage/{provider}")]
|
||||
public async Task<IActionResult> StorageProvider(string provider)
|
||||
{
|
||||
if (!Enum.TryParse(typeof(StorageProvider), provider, out var storageProvider))
|
||||
{
|
||||
return RedirectToAction(nameof(Storage), new
|
||||
{
|
||||
StatusMessage = new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = $"{provider} provider is not supported"
|
||||
}.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
var data = (await _SettingsRepository.GetSettingAsync<StorageSettings>()) ?? new StorageSettings();
|
||||
|
||||
var storageProviderService =
|
||||
_StorageProviderServices.SingleOrDefault(service => service.StorageProvider().Equals(storageProvider));
|
||||
|
||||
switch (storageProviderService)
|
||||
{
|
||||
case null:
|
||||
return RedirectToAction(nameof(Storage), new
|
||||
{
|
||||
StatusMessage = new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = $"{storageProvider} is not supported"
|
||||
}.ToString()
|
||||
});
|
||||
case AzureBlobStorageFileProviderService fileProviderService:
|
||||
return View(nameof(EditAzureBlobStorageStorageProvider),
|
||||
fileProviderService.GetProviderConfiguration(data));
|
||||
|
||||
// case AmazonS3FileProviderService fileProviderService:
|
||||
// return View(nameof(EditAmazonS3StorageProvider),
|
||||
// fileProviderService.GetProviderConfiguration(data));
|
||||
//
|
||||
// case GoogleCloudStorageFileProviderService fileProviderService:
|
||||
// return View(nameof(EditGoogleCloudStorageStorageProvider),
|
||||
// fileProviderService.GetProviderConfiguration(data));
|
||||
|
||||
case FileSystemFileProviderService fileProviderService:
|
||||
return View(nameof(EditFileSystemStorageProvider),
|
||||
fileProviderService.GetProviderConfiguration(data));
|
||||
}
|
||||
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
|
||||
[HttpPost("server/storage/AzureBlobStorage")]
|
||||
public async Task<IActionResult> EditAzureBlobStorageStorageProvider(AzureBlobStorageConfiguration viewModel)
|
||||
{
|
||||
return await SaveStorageProvider(viewModel, BTCPayServer.Storage.Models.StorageProvider.AzureBlobStorage);
|
||||
}
|
||||
|
||||
// [HttpPost("server/storage/AmazonS3")]
|
||||
// public async Task<IActionResult> EditAmazonS3StorageProvider(AmazonS3StorageConfiguration viewModel)
|
||||
// {
|
||||
// return await SaveStorageProvider(viewModel, BTCPayServer.Storage.Models.StorageProvider.AmazonS3);
|
||||
// }
|
||||
//
|
||||
// [HttpPost("server/storage/GoogleCloudStorage")]
|
||||
// public async Task<IActionResult> EditGoogleCloudStorageStorageProvider(
|
||||
// GoogleCloudStorageConfiguration viewModel)
|
||||
// {
|
||||
// return await SaveStorageProvider(viewModel, BTCPayServer.Storage.Models.StorageProvider.GoogleCloudStorage);
|
||||
// }
|
||||
|
||||
[HttpPost("server/storage/FileSystem")]
|
||||
public async Task<IActionResult> EditFileSystemStorageProvider(FileSystemStorageConfiguration viewModel)
|
||||
{
|
||||
return await SaveStorageProvider(viewModel, BTCPayServer.Storage.Models.StorageProvider.FileSystem);
|
||||
}
|
||||
|
||||
private async Task<IActionResult> SaveStorageProvider(IBaseStorageConfiguration viewModel,
|
||||
StorageProvider storageProvider)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(viewModel);
|
||||
}
|
||||
|
||||
var data = (await _SettingsRepository.GetSettingAsync<StorageSettings>()) ?? new StorageSettings();
|
||||
data.Provider = storageProvider;
|
||||
data.Configuration = JObject.FromObject(viewModel);
|
||||
await _SettingsRepository.UpdateSetting(data);
|
||||
TempData["StatusMessage"] = new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Success,
|
||||
Message = "Storage settings updated successfully"
|
||||
}.ToString();
|
||||
return View(viewModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,9 @@ using Renci.SshNet;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Lightning;
|
||||
using System.Runtime.CompilerServices;
|
||||
using BTCPayServer.Storage.Models;
|
||||
using BTCPayServer.Storage.Services;
|
||||
using BTCPayServer.Storage.Services.Providers;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using BTCPayServer.Data;
|
||||
@@ -34,7 +37,7 @@ using BTCPayServer.Data;
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
[Authorize(Policy = BTCPayServer.Security.Policies.CanModifyServerSettings.Key)]
|
||||
public class ServerController : Controller
|
||||
public partial class ServerController : Controller
|
||||
{
|
||||
private UserManager<ApplicationUser> _UserManager;
|
||||
SettingsRepository _SettingsRepository;
|
||||
@@ -45,8 +48,14 @@ namespace BTCPayServer.Controllers
|
||||
private readonly TorServices _torServices;
|
||||
BTCPayServerOptions _Options;
|
||||
ApplicationDbContextFactory _ContextFactory;
|
||||
private readonly StoredFileRepository _StoredFileRepository;
|
||||
private readonly FileService _FileService;
|
||||
private readonly IEnumerable<IStorageProviderService> _StorageProviderServices;
|
||||
|
||||
public ServerController(UserManager<ApplicationUser> userManager,
|
||||
StoredFileRepository storedFileRepository,
|
||||
FileService fileService,
|
||||
IEnumerable<IStorageProviderService> storageProviderServices,
|
||||
BTCPayServerOptions options,
|
||||
RateFetcher rateProviderFactory,
|
||||
SettingsRepository settingsRepository,
|
||||
@@ -58,6 +67,9 @@ namespace BTCPayServer.Controllers
|
||||
ApplicationDbContextFactory contextFactory)
|
||||
{
|
||||
_Options = options;
|
||||
_StoredFileRepository = storedFileRepository;
|
||||
_FileService = fileService;
|
||||
_StorageProviderServices = storageProviderServices;
|
||||
_UserManager = userManager;
|
||||
_SettingsRepository = settingsRepository;
|
||||
_dashBoard = dashBoard;
|
||||
@@ -490,7 +502,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
[Route("server/services")]
|
||||
public IActionResult Services()
|
||||
public async Task<IActionResult> Services()
|
||||
{
|
||||
var result = new ServicesViewModel();
|
||||
result.ExternalServices = _Options.ExternalServices;
|
||||
@@ -529,6 +541,13 @@ namespace BTCPayServer.Controllers
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var storageSettings = await _SettingsRepository.GetSettingAsync<StorageSettings>();
|
||||
result.ExternalStorageServices.Add(new ServicesViewModel.OtherExternalService()
|
||||
{
|
||||
Name = storageSettings == null? "Not set": storageSettings.Provider.ToString(),
|
||||
Link = Url.Action("Storage")
|
||||
});
|
||||
return View(result);
|
||||
}
|
||||
|
||||
|
||||
24
BTCPayServer/Controllers/StorageController.cs
Normal file
24
BTCPayServer/Controllers/StorageController.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Storage.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Storage
|
||||
{
|
||||
[Route("Storage")]
|
||||
public class StorageController
|
||||
{
|
||||
private readonly FileService _FileService;
|
||||
|
||||
public StorageController(FileService fileService)
|
||||
{
|
||||
_FileService = fileService;
|
||||
}
|
||||
|
||||
[HttpGet("{fileId}")]
|
||||
public async Task<IActionResult> GetFile(string fileId)
|
||||
{
|
||||
var url = await _FileService.GetFileUrl(fileId);
|
||||
return new RedirectResult(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Services.PaymentRequests;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure.Internal;
|
||||
using BTCPayServer.Storage.Models;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
@@ -97,6 +94,11 @@ namespace BTCPayServer.Data
|
||||
get; set;
|
||||
}
|
||||
|
||||
public DbSet<StoredFile> Files
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
var isConfigured = optionsBuilder.Options.Extensions.OfType<RelationalOptionsExtension>().Any();
|
||||
|
||||
@@ -36,6 +36,10 @@ using System.Net;
|
||||
using BTCPayServer.PaymentRequest;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Storage;
|
||||
using BTCPayServer.Storage.Services.Providers.FileSystemStorage;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace BTCPayServer.Hosting
|
||||
{
|
||||
@@ -64,6 +68,7 @@ namespace BTCPayServer.Hosting
|
||||
.AddDefaultTokenProviders();
|
||||
services.AddSignalR();
|
||||
services.AddBTCPayServer();
|
||||
services.AddProviderStorage();
|
||||
services.AddSession();
|
||||
services.AddMvc(o =>
|
||||
{
|
||||
@@ -169,6 +174,7 @@ namespace BTCPayServer.Hosting
|
||||
app.UseCors();
|
||||
app.UsePayServer();
|
||||
app.UseStaticFiles();
|
||||
app.UseProviderStorage(options);
|
||||
app.UseAuthentication();
|
||||
app.UseSession();
|
||||
app.UseSignalR(route =>
|
||||
|
||||
635
BTCPayServer/Migrations/20190324141717_AddFiles.Designer.cs
generated
Normal file
635
BTCPayServer/Migrations/20190324141717_AddFiles.Designer.cs
generated
Normal file
@@ -0,0 +1,635 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20190324141717_AddFiles")]
|
||||
partial class AddFiles
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.1.8-servicing-32085");
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("Address")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTimeOffset?>("CreatedTime");
|
||||
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.HasKey("Address");
|
||||
|
||||
b.HasIndex("InvoiceDataId");
|
||||
|
||||
b.ToTable("AddressInvoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.APIKeyData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasMaxLength(50);
|
||||
|
||||
b.Property<string>("StoreId")
|
||||
.HasMaxLength(50);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StoreId");
|
||||
|
||||
b.ToTable("ApiKeys");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AppData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("AppType");
|
||||
|
||||
b.Property<DateTimeOffset>("Created");
|
||||
|
||||
b.Property<string>("Name");
|
||||
|
||||
b.Property<string>("Settings");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.Property<bool>("TagAllInvoices");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("Apps");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.Property<string>("Address");
|
||||
|
||||
b.Property<DateTimeOffset>("Assigned");
|
||||
|
||||
b.Property<string>("CryptoCode");
|
||||
|
||||
b.Property<DateTimeOffset?>("UnAssigned");
|
||||
|
||||
b.HasKey("InvoiceDataId", "Address");
|
||||
|
||||
b.ToTable("HistoricalAddressInvoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.Property<DateTimeOffset>("Created");
|
||||
|
||||
b.Property<string>("CustomerEmail");
|
||||
|
||||
b.Property<string>("ExceptionStatus");
|
||||
|
||||
b.Property<string>("ItemCode");
|
||||
|
||||
b.Property<string>("OrderId");
|
||||
|
||||
b.Property<string>("Status");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("Invoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceEventData", b =>
|
||||
{
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.Property<string>("UniqueId");
|
||||
|
||||
b.Property<string>("Message");
|
||||
|
||||
b.Property<DateTimeOffset>("Timestamp");
|
||||
|
||||
b.HasKey("InvoiceDataId", "UniqueId");
|
||||
|
||||
b.ToTable("InvoiceEvents");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PairedSINData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Facade");
|
||||
|
||||
b.Property<string>("Label");
|
||||
|
||||
b.Property<DateTimeOffset>("PairingTime");
|
||||
|
||||
b.Property<string>("SIN");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SIN");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("PairedSINData");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PairingCodeData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTime>("DateCreated");
|
||||
|
||||
b.Property<DateTimeOffset>("Expiration");
|
||||
|
||||
b.Property<string>("Facade");
|
||||
|
||||
b.Property<string>("Label");
|
||||
|
||||
b.Property<string>("SIN");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.Property<string>("TokenValue");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PairingCodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PaymentData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<bool>("Accounted");
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InvoiceDataId");
|
||||
|
||||
b.ToTable("Payments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PendingInvoiceData", b =>
|
||||
{
|
||||
b.Property<string>("Id");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PendingInvoices");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.Property<string>("InvoiceDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InvoiceDataId");
|
||||
|
||||
b.ToTable("RefundAddresses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.SettingData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Value");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.StoreData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("DefaultCrypto");
|
||||
|
||||
b.Property<string>("DerivationStrategies");
|
||||
|
||||
b.Property<string>("DerivationStrategy");
|
||||
|
||||
b.Property<int>("SpeedPolicy");
|
||||
|
||||
b.Property<byte[]>("StoreBlob");
|
||||
|
||||
b.Property<byte[]>("StoreCertificate");
|
||||
|
||||
b.Property<string>("StoreName");
|
||||
|
||||
b.Property<string>("StoreWebsite");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Stores");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.UserStore", b =>
|
||||
{
|
||||
b.Property<string>("ApplicationUserId");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.Property<string>("Role");
|
||||
|
||||
b.HasKey("ApplicationUserId", "StoreDataId");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("UserStore");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Models.ApplicationUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("AccessFailedCount");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken();
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<bool>("EmailConfirmed");
|
||||
|
||||
b.Property<bool>("LockoutEnabled");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("PasswordHash");
|
||||
|
||||
b.Property<string>("PhoneNumber");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed");
|
||||
|
||||
b.Property<bool>("RequiresEmailConfirmation");
|
||||
|
||||
b.Property<string>("SecurityStamp");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasName("UserNameIndex");
|
||||
|
||||
b.ToTable("AspNetUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Services.PaymentRequests.PaymentRequestData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<byte[]>("Blob");
|
||||
|
||||
b.Property<int>("Status");
|
||||
|
||||
b.Property<string>("StoreDataId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Status");
|
||||
|
||||
b.HasIndex("StoreDataId");
|
||||
|
||||
b.ToTable("PaymentRequests");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Storage.Models.StoredFile", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ApplicationUserId");
|
||||
|
||||
b.Property<string>("FileName");
|
||||
|
||||
b.Property<string>("StorageFileName");
|
||||
|
||||
b.Property<DateTime>("Timestamp");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationUserId");
|
||||
|
||||
b.ToTable("Files");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken();
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasName("RoleNameIndex");
|
||||
|
||||
b.ToTable("AspNetRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ClaimType");
|
||||
|
||||
b.Property<string>("ClaimValue");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ClaimType");
|
||||
|
||||
b.Property<string>("ClaimValue");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider");
|
||||
|
||||
b.Property<string>("ProviderKey");
|
||||
|
||||
b.Property<string>("ProviderDisplayName");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.Property<string>("RoleId");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.Property<string>("LoginProvider");
|
||||
|
||||
b.Property<string>("Name");
|
||||
|
||||
b.Property<string>("Value");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("AddressInvoices")
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.APIKeyData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
.WithMany("APIKeys")
|
||||
.HasForeignKey("StoreId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.AppData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
.WithMany("Apps")
|
||||
.HasForeignKey("StoreDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("HistoricalAddressInvoices")
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
.WithMany("Invoices")
|
||||
.HasForeignKey("StoreDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.InvoiceEventData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("Events")
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PairedSINData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
.WithMany("PairedSINs")
|
||||
.HasForeignKey("StoreDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PaymentData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("Payments")
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.PendingInvoiceData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("PendingInvoices")
|
||||
.HasForeignKey("Id")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
|
||||
.WithMany("RefundAddresses")
|
||||
.HasForeignKey("InvoiceDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Data.UserStore", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser", "ApplicationUser")
|
||||
.WithMany("UserStores")
|
||||
.HasForeignKey("ApplicationUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
.WithMany("UserStores")
|
||||
.HasForeignKey("StoreDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Services.PaymentRequests.PaymentRequestData", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
|
||||
.WithMany("PaymentRequests")
|
||||
.HasForeignKey("StoreDataId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Storage.Models.StoredFile", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser", "ApplicationUser")
|
||||
.WithMany("StoredFiles")
|
||||
.HasForeignKey("ApplicationUserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
43
BTCPayServer/Migrations/20190324141717_AddFiles.cs
Normal file
43
BTCPayServer/Migrations/20190324141717_AddFiles.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
public partial class AddFiles : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Files",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(nullable: false),
|
||||
FileName = table.Column<string>(nullable: true),
|
||||
StorageFileName = table.Column<string>(nullable: true),
|
||||
Timestamp = table.Column<DateTime>(nullable: false),
|
||||
ApplicationUserId = table.Column<string>(nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Files", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Files_AspNetUsers_ApplicationUserId",
|
||||
column: x => x.ApplicationUserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Files_ApplicationUserId",
|
||||
table: "Files",
|
||||
column: "ApplicationUserId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Files");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -348,6 +348,26 @@ namespace BTCPayServer.Migrations
|
||||
b.ToTable("PaymentRequests");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Storage.Models.StoredFile", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ApplicationUserId");
|
||||
|
||||
b.Property<string>("FileName");
|
||||
|
||||
b.Property<string>("StorageFileName");
|
||||
|
||||
b.Property<DateTime>("Timestamp");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApplicationUserId");
|
||||
|
||||
b.ToTable("Files");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@@ -556,6 +576,13 @@ namespace BTCPayServer.Migrations
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("BTCPayServer.Storage.Models.StoredFile", b =>
|
||||
{
|
||||
b.HasOne("BTCPayServer.Models.ApplicationUser", "ApplicationUser")
|
||||
.WithMany("StoredFiles")
|
||||
.HasForeignKey("ApplicationUserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Storage.Models;
|
||||
|
||||
namespace BTCPayServer.Models
|
||||
{
|
||||
@@ -20,5 +21,11 @@ namespace BTCPayServer.Models
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public List<StoredFile> StoredFiles
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,5 +20,6 @@ namespace BTCPayServer.Models.ServerViewModels
|
||||
public List<OtherExternalService> OtherExternalServices { get; set; } = new List<OtherExternalService>();
|
||||
public List<OtherExternalService> TorHttpServices { get; set; } = new List<OtherExternalService>();
|
||||
public List<OtherExternalService> TorOtherServices { get; set; } = new List<OtherExternalService>();
|
||||
public List<OtherExternalService> ExternalStorageServices { get; set; } = new List<OtherExternalService>();
|
||||
}
|
||||
}
|
||||
|
||||
13
BTCPayServer/Models/ServerViewModels/ViewFilesViewModel.cs
Normal file
13
BTCPayServer/Models/ServerViewModels/ViewFilesViewModel.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.Storage.Models;
|
||||
|
||||
namespace BTCPayServer.Models.ServerViewModels
|
||||
{
|
||||
public class ViewFilesViewModel
|
||||
{
|
||||
public List<StoredFile> Files { get; set; }
|
||||
public string DirectFileUrl { get; set; }
|
||||
public string SelectedFileId { get; set; }
|
||||
public bool StorageConfigured { get; set; }
|
||||
}
|
||||
}
|
||||
10
BTCPayServer/Storage/Models/StorageProvider.cs
Normal file
10
BTCPayServer/Storage/Models/StorageProvider.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace BTCPayServer.Storage.Models
|
||||
{
|
||||
public enum StorageProvider
|
||||
{
|
||||
AzureBlobStorage=0,
|
||||
// AmazonS3 =1,
|
||||
// GoogleCloudStorage =2,
|
||||
FileSystem =3
|
||||
}
|
||||
}
|
||||
19
BTCPayServer/Storage/Models/StorageSettings.cs
Normal file
19
BTCPayServer/Storage/Models/StorageSettings.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Storage.Models
|
||||
{
|
||||
public class StorageSettings
|
||||
{
|
||||
public StorageProvider Provider { get; set; }
|
||||
public string ConfigurationStr { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public JObject Configuration
|
||||
{
|
||||
get => JsonConvert.DeserializeObject<JObject>(string.IsNullOrEmpty(ConfigurationStr) ? "{}" : ConfigurationStr);
|
||||
set => ConfigurationStr = value.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
21
BTCPayServer/Storage/Models/StoredFile.cs
Normal file
21
BTCPayServer/Storage/Models/StoredFile.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using BTCPayServer.Models;
|
||||
|
||||
namespace BTCPayServer.Storage.Models
|
||||
{
|
||||
public class StoredFile
|
||||
{
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public string Id { get; set; }
|
||||
|
||||
public string FileName { get; set; }
|
||||
public string StorageFileName { get; set; }
|
||||
public DateTime Timestamp { get; set; }
|
||||
public string ApplicationUserId { get; set; }
|
||||
public ApplicationUser ApplicationUser
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
||||
71
BTCPayServer/Storage/Services/FileService.cs
Normal file
71
BTCPayServer/Storage/Services/FileService.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Storage.Models;
|
||||
using BTCPayServer.Storage.Services.Providers;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace BTCPayServer.Storage.Services
|
||||
{
|
||||
public class FileService
|
||||
{
|
||||
private readonly StoredFileRepository _FileRepository;
|
||||
private readonly IEnumerable<IStorageProviderService> _providers;
|
||||
private readonly SettingsRepository _SettingsRepository;
|
||||
|
||||
public FileService(StoredFileRepository fileRepository, IEnumerable<IStorageProviderService> providers,
|
||||
SettingsRepository settingsRepository)
|
||||
{
|
||||
_FileRepository = fileRepository;
|
||||
_providers = providers;
|
||||
_SettingsRepository = settingsRepository;
|
||||
}
|
||||
|
||||
public async Task<StoredFile> AddFile(IFormFile file, string userId)
|
||||
{
|
||||
var settings = await _SettingsRepository.GetSettingAsync<StorageSettings>();
|
||||
var provider = GetProvider(settings);
|
||||
|
||||
var storedFile = await provider.AddFile(file, settings);
|
||||
storedFile.ApplicationUserId = userId;
|
||||
await _FileRepository.AddFile(storedFile);
|
||||
return storedFile;
|
||||
}
|
||||
|
||||
public async Task<string> GetFileUrl(string fileId)
|
||||
{
|
||||
var settings = await _SettingsRepository.GetSettingAsync<StorageSettings>();
|
||||
var provider = GetProvider(settings);
|
||||
var storedFile = await _FileRepository.GetFile(fileId);
|
||||
return storedFile == null ? null: await provider.GetFileUrl(storedFile, settings);
|
||||
}
|
||||
|
||||
public async Task<string> GetTemporaryFileUrl(string fileId, DateTimeOffset expiry, bool isDownload)
|
||||
{
|
||||
var settings = await _SettingsRepository.GetSettingAsync<StorageSettings>();
|
||||
var provider = GetProvider(settings);
|
||||
var storedFile = await _FileRepository.GetFile(fileId);
|
||||
return storedFile == null ? null: await provider.GetTemporaryFileUrl(storedFile, settings,expiry,isDownload);
|
||||
}
|
||||
|
||||
public async Task RemoveFile(string fileId, string userId)
|
||||
{
|
||||
var settings = await _SettingsRepository.GetSettingAsync<StorageSettings>();
|
||||
var provider = GetProvider(settings);
|
||||
var storedFile = await _FileRepository.GetFile(fileId);
|
||||
if (string.IsNullOrEmpty(userId) ||
|
||||
storedFile.ApplicationUserId.Equals(userId, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
await provider.RemoveFile(storedFile, settings);
|
||||
await _FileRepository.RemoveFile(storedFile);
|
||||
}
|
||||
}
|
||||
|
||||
private IStorageProviderService GetProvider(StorageSettings storageSettings)
|
||||
{
|
||||
return _providers.FirstOrDefault((service) => service.StorageProvider().Equals(storageSettings.Provider));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
//using System.Threading.Tasks;
|
||||
//using BTCPayServer.Storage.Models;
|
||||
//using BTCPayServer.Storage.Services.Providers.AmazonS3Storage.Configuration;
|
||||
//using TwentyTwenty.Storage;
|
||||
//using TwentyTwenty.Storage.Amazon;
|
||||
//
|
||||
//namespace BTCPayServer.Storage.Services.Providers.AmazonS3Storage
|
||||
//{
|
||||
// public class
|
||||
// AmazonS3FileProviderService : BaseTwentyTwentyStorageFileProviderServiceBase<AmazonS3StorageConfiguration>
|
||||
// {
|
||||
// public override StorageProvider StorageProvider()
|
||||
// {
|
||||
// return Storage.Models.StorageProvider.AmazonS3;
|
||||
// }
|
||||
//
|
||||
// public override AmazonS3StorageConfiguration GetProviderConfiguration(StorageSettings configuration)
|
||||
// {
|
||||
// return configuration.Configuration.ParseAmazonS3StorageConfiguration();
|
||||
// }
|
||||
//
|
||||
// protected override Task<IStorageProvider> GetStorageProvider(AmazonS3StorageConfiguration configuration)
|
||||
// {
|
||||
// return Task.FromResult<IStorageProvider>(new AmazonStorageProvider(configuration));
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Storage.Services.Providers.AmazonS3Storage.Configuration
|
||||
{
|
||||
public static class AmazonS3FileProviderServiceExtensions
|
||||
{
|
||||
public static AmazonS3StorageConfiguration ParseAmazonS3StorageConfiguration(this JObject jObject)
|
||||
{
|
||||
return jObject.ToObject<AmazonS3StorageConfiguration>();
|
||||
}
|
||||
|
||||
public static JObject ConvertConfiguration(this AmazonS3StorageConfiguration configuration)
|
||||
{
|
||||
return JObject.FromObject(configuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using BTCPayServer.Storage.Services.Providers.Models;
|
||||
using TwentyTwenty.Storage.Amazon;
|
||||
|
||||
namespace BTCPayServer.Storage.Services.Providers.AmazonS3Storage.Configuration
|
||||
{
|
||||
public class AmazonS3StorageConfiguration : AmazonProviderOptions, IBaseStorageConfiguration
|
||||
{
|
||||
public string ContainerName { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Storage.Models;
|
||||
using BTCPayServer.Storage.Services.Providers.AzureBlobStorage.Configuration;
|
||||
using TwentyTwenty.Storage;
|
||||
using TwentyTwenty.Storage.Azure;
|
||||
|
||||
namespace BTCPayServer.Storage.Services.Providers.AzureBlobStorage
|
||||
{
|
||||
public class
|
||||
AzureBlobStorageFileProviderService : BaseTwentyTwentyStorageFileProviderServiceBase<
|
||||
AzureBlobStorageConfiguration>
|
||||
{
|
||||
public override StorageProvider StorageProvider()
|
||||
{
|
||||
return Storage.Models.StorageProvider.AzureBlobStorage;
|
||||
}
|
||||
|
||||
public override AzureBlobStorageConfiguration GetProviderConfiguration(StorageSettings configuration)
|
||||
{
|
||||
return configuration.Configuration.ParseAzureBlobStorageConfiguration();
|
||||
}
|
||||
|
||||
protected override Task<IStorageProvider> GetStorageProvider(AzureBlobStorageConfiguration configuration)
|
||||
{
|
||||
return Task.FromResult<IStorageProvider>(new AzureStorageProvider(configuration));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using BTCPayServer.Storage.Services.Providers.Models;
|
||||
using TwentyTwenty.Storage.Azure;
|
||||
|
||||
namespace BTCPayServer.Storage.Services.Providers.AzureBlobStorage.Configuration
|
||||
{
|
||||
public class AzureBlobStorageConfiguration : AzureProviderOptions, IBaseStorageConfiguration
|
||||
{
|
||||
[Required]
|
||||
[MinLength(3)]
|
||||
[MaxLength(63)]
|
||||
[RegularExpression(@"[a-z0-9-]+",
|
||||
ErrorMessage = "Characters must be lowercase or digits or -")]
|
||||
public string ContainerName { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Storage.Services.Providers.AzureBlobStorage.Configuration
|
||||
{
|
||||
public static class AzureBlobStorageFileProviderServiceExtensions
|
||||
{
|
||||
public static AzureBlobStorageConfiguration ParseAzureBlobStorageConfiguration(this JObject jObject)
|
||||
{
|
||||
return jObject.ToObject<AzureBlobStorageConfiguration>();
|
||||
}
|
||||
|
||||
public static JObject ConvertConfiguration(this AzureBlobStorageConfiguration configuration)
|
||||
{
|
||||
return JObject.FromObject(configuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Storage.Models;
|
||||
using BTCPayServer.Storage.Services.Providers.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using TwentyTwenty.Storage;
|
||||
|
||||
namespace BTCPayServer.Storage.Services.Providers
|
||||
{
|
||||
public abstract class
|
||||
BaseTwentyTwentyStorageFileProviderServiceBase<TStorageConfiguration> : IStorageProviderService
|
||||
where TStorageConfiguration : IBaseStorageConfiguration
|
||||
{
|
||||
public abstract StorageProvider StorageProvider();
|
||||
|
||||
public virtual async Task<StoredFile> AddFile(IFormFile file, StorageSettings configuration)
|
||||
{
|
||||
//respect https://www.microsoftpressstore.com/articles/article.aspx?p=2224058&seqNum=8 in naming
|
||||
var storageFileName = $"{Guid.NewGuid()}-{file.FileName.ToLowerInvariant()}";
|
||||
var providerConfiguration = GetProviderConfiguration(configuration);
|
||||
var provider = await GetStorageProvider(providerConfiguration);
|
||||
using (var fileStream = file.OpenReadStream())
|
||||
{
|
||||
await provider.SaveBlobStreamAsync(providerConfiguration.ContainerName, storageFileName, fileStream,
|
||||
new BlobProperties()
|
||||
{
|
||||
ContentType = file.ContentType,
|
||||
ContentDisposition = file.ContentDisposition,
|
||||
Security = BlobSecurity.Public,
|
||||
});
|
||||
}
|
||||
|
||||
return new StoredFile()
|
||||
{
|
||||
Timestamp = DateTime.Now,
|
||||
FileName = file.FileName,
|
||||
StorageFileName = storageFileName
|
||||
};
|
||||
}
|
||||
|
||||
public virtual async Task<string> GetFileUrl(StoredFile storedFile, StorageSettings configuration)
|
||||
{
|
||||
var providerConfiguration = GetProviderConfiguration(configuration);
|
||||
var provider = await GetStorageProvider(providerConfiguration);
|
||||
return provider.GetBlobUrl(providerConfiguration.ContainerName, storedFile.StorageFileName);
|
||||
}
|
||||
|
||||
public virtual async Task<string> GetTemporaryFileUrl(StoredFile storedFile, StorageSettings configuration,
|
||||
DateTimeOffset expiry, bool isDownload, BlobUrlAccess access = BlobUrlAccess.Read)
|
||||
{
|
||||
var providerConfiguration = GetProviderConfiguration(configuration);
|
||||
var provider = await GetStorageProvider(providerConfiguration);
|
||||
if (isDownload)
|
||||
{
|
||||
var descriptor =
|
||||
await provider.GetBlobDescriptorAsync(providerConfiguration.ContainerName,
|
||||
storedFile.StorageFileName);
|
||||
return provider.GetBlobSasUrl(providerConfiguration.ContainerName, storedFile.StorageFileName, expiry,
|
||||
true, storedFile.FileName, descriptor.ContentType, access);
|
||||
}
|
||||
|
||||
return provider.GetBlobSasUrl(providerConfiguration.ContainerName, storedFile.StorageFileName, expiry,
|
||||
false, null, null, access);
|
||||
}
|
||||
|
||||
public async Task RemoveFile(StoredFile storedFile, StorageSettings configuration)
|
||||
{
|
||||
var providerConfiguration = GetProviderConfiguration(configuration);
|
||||
var provider = await GetStorageProvider(providerConfiguration);
|
||||
await provider.DeleteBlobAsync(providerConfiguration.ContainerName, storedFile.StorageFileName);
|
||||
}
|
||||
|
||||
public abstract TStorageConfiguration GetProviderConfiguration(StorageSettings configuration);
|
||||
|
||||
protected abstract Task<IStorageProvider> GetStorageProvider(TStorageConfiguration configuration);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Storage.Services.Providers.FileSystemStorage.Configuration
|
||||
{
|
||||
public static class FileSystemFileProviderServiceExtensions
|
||||
{
|
||||
public static FileSystemStorageConfiguration ParseFileSystemStorageConfiguration(this JObject jObject)
|
||||
{
|
||||
return jObject.ToObject<FileSystemStorageConfiguration>();
|
||||
}
|
||||
|
||||
public static JObject ConvertConfiguration(this FileSystemStorageConfiguration configuration)
|
||||
{
|
||||
return JObject.FromObject(configuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using BTCPayServer.Storage.Services.Providers.Models;
|
||||
|
||||
namespace BTCPayServer.Storage.Services.Providers.FileSystemStorage.Configuration
|
||||
{
|
||||
public class FileSystemStorageConfiguration : IBaseStorageConfiguration
|
||||
{
|
||||
public string ContainerName { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Storage.Models;
|
||||
using BTCPayServer.Storage.Services.Providers.FileSystemStorage.Configuration;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using TwentyTwenty.Storage;
|
||||
using TwentyTwenty.Storage.Local;
|
||||
|
||||
namespace BTCPayServer.Storage.Services.Providers.FileSystemStorage
|
||||
{
|
||||
public class
|
||||
FileSystemFileProviderService : BaseTwentyTwentyStorageFileProviderServiceBase<FileSystemStorageConfiguration>
|
||||
{
|
||||
private readonly BTCPayServerEnvironment _BtcPayServerEnvironment;
|
||||
private readonly BTCPayServerOptions _Options;
|
||||
private readonly IHttpContextAccessor _HttpContextAccessor;
|
||||
|
||||
public FileSystemFileProviderService(BTCPayServerEnvironment btcPayServerEnvironment,
|
||||
BTCPayServerOptions options, IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_BtcPayServerEnvironment = btcPayServerEnvironment;
|
||||
_Options = options;
|
||||
_HttpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
public const string LocalStorageDirectoryName = "LocalStorage";
|
||||
|
||||
public static string GetStorageDir(BTCPayServerOptions options)
|
||||
{
|
||||
return Path.Combine(options.DataDir, LocalStorageDirectoryName);
|
||||
}
|
||||
|
||||
public override StorageProvider StorageProvider()
|
||||
{
|
||||
return Storage.Models.StorageProvider.FileSystem;
|
||||
}
|
||||
|
||||
public override FileSystemStorageConfiguration GetProviderConfiguration(StorageSettings configuration)
|
||||
{
|
||||
return configuration.Configuration.ParseFileSystemStorageConfiguration();
|
||||
}
|
||||
|
||||
protected override Task<IStorageProvider> GetStorageProvider(FileSystemStorageConfiguration configuration)
|
||||
{
|
||||
return Task.FromResult<IStorageProvider>(
|
||||
new LocalStorageProvider(new DirectoryInfo(GetStorageDir(_Options)).FullName));
|
||||
}
|
||||
|
||||
public override async Task<string> GetFileUrl(StoredFile storedFile, StorageSettings configuration)
|
||||
{
|
||||
var baseResult = await base.GetFileUrl(storedFile, configuration);
|
||||
var url =
|
||||
_HttpContextAccessor.HttpContext.Request.IsOnion()
|
||||
? _BtcPayServerEnvironment.OnionUrl
|
||||
: $"{_BtcPayServerEnvironment.ExpectedProtocol}://" +
|
||||
$"{_BtcPayServerEnvironment.ExpectedHost}" +
|
||||
$"{_Options.RootPath}{LocalStorageDirectoryName}";
|
||||
return baseResult.Replace(new DirectoryInfo(GetStorageDir(_Options)).FullName, url,
|
||||
StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
public override Task<string> GetTemporaryFileUrl(StoredFile storedFile, StorageSettings configuration, DateTimeOffset expiry, bool isDownload,
|
||||
BlobUrlAccess access = BlobUrlAccess.Read)
|
||||
{
|
||||
return GetFileUrl(storedFile, configuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using BTCPayServer.Storage.Services.Providers.Models;
|
||||
using TwentyTwenty.Storage.Google;
|
||||
|
||||
namespace BTCPayServer.Storage.Services.Providers.GoogleCloudStorage.Configuration
|
||||
{
|
||||
public class GoogleCloudStorageConfiguration : GoogleProviderOptions, IBaseStorageConfiguration
|
||||
{
|
||||
public string JsonCredentials{ get; set; }
|
||||
public string ContainerName { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Storage.Services.Providers.GoogleCloudStorage.Configuration
|
||||
{
|
||||
public static class GoogleCloudStorageFileProviderServiceExtensions
|
||||
{
|
||||
public static GoogleCloudStorageConfiguration ParseGoogleCloudStorageConfiguration(this JObject jObject)
|
||||
{
|
||||
return jObject.ToObject<GoogleCloudStorageConfiguration>();
|
||||
}
|
||||
|
||||
public static JObject ConvertConfiguration(this GoogleCloudStorageConfiguration configuration)
|
||||
{
|
||||
return JObject.FromObject(configuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
//using System.Threading.Tasks;
|
||||
//using BTCPayServer.Storage.Models;
|
||||
//using BTCPayServer.Storage.Services.Providers.GoogleCloudStorage.Configuration;
|
||||
//using Google.Apis.Auth.OAuth2;
|
||||
//using TwentyTwenty.Storage;
|
||||
//using TwentyTwenty.Storage.Google;
|
||||
//
|
||||
//namespace BTCPayServer.Storage.Services.Providers.GoogleCloudStorage
|
||||
//{
|
||||
// public class
|
||||
// GoogleCloudStorageFileProviderService : BaseTwentyTwentyStorageFileProviderServiceBase<
|
||||
// GoogleCloudStorageConfiguration>
|
||||
// {
|
||||
// public override StorageProvider StorageProvider()
|
||||
// {
|
||||
// return Storage.Models.StorageProvider.GoogleCloudStorage;
|
||||
// }
|
||||
//
|
||||
// public override GoogleCloudStorageConfiguration GetProviderConfiguration(StorageSettings configuration)
|
||||
// {
|
||||
// return configuration.Configuration.ParseGoogleCloudStorageConfiguration();
|
||||
// }
|
||||
//
|
||||
// protected override Task<IStorageProvider> GetStorageProvider(
|
||||
// GoogleCloudStorageConfiguration configuration)
|
||||
// {
|
||||
// return Task.FromResult<IStorageProvider>(new GoogleStorageProvider(GoogleCredential.FromJson(configuration.JsonCredentials), configuration));
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Storage.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using TwentyTwenty.Storage;
|
||||
|
||||
namespace BTCPayServer.Storage.Services.Providers
|
||||
{
|
||||
public interface IStorageProviderService
|
||||
{
|
||||
Task<StoredFile> AddFile(IFormFile formFile, StorageSettings configuration);
|
||||
Task RemoveFile(StoredFile storedFile, StorageSettings configuration);
|
||||
Task<string> GetFileUrl(StoredFile storedFile, StorageSettings configuration);
|
||||
Task<string> GetTemporaryFileUrl(StoredFile storedFile, StorageSettings configuration,
|
||||
DateTimeOffset expiry, bool isDownload, BlobUrlAccess access = BlobUrlAccess.Read);
|
||||
StorageProvider StorageProvider();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace BTCPayServer.Storage.Services.Providers.Models
|
||||
{
|
||||
public interface IBaseStorageConfiguration
|
||||
{
|
||||
string ContainerName { get; set; }
|
||||
}
|
||||
}
|
||||
69
BTCPayServer/Storage/Services/StoredFileRepository.cs
Normal file
69
BTCPayServer/Storage/Services/StoredFileRepository.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Storage.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace BTCPayServer.Storage.Services
|
||||
{
|
||||
public class StoredFileRepository
|
||||
{
|
||||
private readonly ApplicationDbContextFactory _ApplicationDbContextFactory;
|
||||
|
||||
public StoredFileRepository(ApplicationDbContextFactory applicationDbContextFactory)
|
||||
{
|
||||
_ApplicationDbContextFactory = applicationDbContextFactory;
|
||||
}
|
||||
|
||||
public async Task<StoredFile> GetFile(string fileId)
|
||||
{
|
||||
var filesResult = await GetFiles(new FilesQuery() {Id = new string[] {fileId}});
|
||||
return filesResult.FirstOrDefault();
|
||||
}
|
||||
|
||||
public async Task<List<StoredFile>> GetFiles(FilesQuery filesQuery = null)
|
||||
{
|
||||
if (filesQuery == null)
|
||||
{
|
||||
filesQuery = new FilesQuery();
|
||||
}
|
||||
|
||||
using (var context = _ApplicationDbContextFactory.CreateContext())
|
||||
{
|
||||
return await context.Files
|
||||
.Include(file => file.ApplicationUser)
|
||||
.Where(file =>
|
||||
(!filesQuery.Id.Any() || filesQuery.Id.Contains(file.Id)) &&
|
||||
(!filesQuery.UserIds.Any() || filesQuery.UserIds.Contains(file.ApplicationUserId)))
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RemoveFile(StoredFile file)
|
||||
{
|
||||
using (var context = _ApplicationDbContextFactory.CreateContext())
|
||||
{
|
||||
context.Attach(file);
|
||||
context.Files.Remove(file);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task AddFile(StoredFile storedFile)
|
||||
{
|
||||
using (var context = _ApplicationDbContextFactory.CreateContext())
|
||||
{
|
||||
await context.AddAsync(storedFile);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public class FilesQuery
|
||||
{
|
||||
public string[] Id { get; set; } = Array.Empty<string>();
|
||||
public string[] UserIds { get; set; } = Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
50
BTCPayServer/Storage/StorageExtensions.cs
Normal file
50
BTCPayServer/Storage/StorageExtensions.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System.IO;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Storage.Services;
|
||||
using BTCPayServer.Storage.Services.Providers;
|
||||
using BTCPayServer.Storage.Services.Providers.AmazonS3Storage;
|
||||
using BTCPayServer.Storage.Services.Providers.AzureBlobStorage;
|
||||
using BTCPayServer.Storage.Services.Providers.FileSystemStorage;
|
||||
using BTCPayServer.Storage.Services.Providers.GoogleCloudStorage;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace BTCPayServer.Storage
|
||||
{
|
||||
public static class StorageExtensions
|
||||
{
|
||||
public static void AddProviderStorage(this IServiceCollection serviceCollection)
|
||||
{
|
||||
serviceCollection.AddSingleton<StoredFileRepository>();
|
||||
serviceCollection.AddSingleton<FileService>();
|
||||
// serviceCollection.AddSingleton<IStorageProviderService, AmazonS3FileProviderService>();
|
||||
serviceCollection.AddSingleton<IStorageProviderService, AzureBlobStorageFileProviderService>();
|
||||
serviceCollection.AddSingleton<IStorageProviderService, FileSystemFileProviderService>();
|
||||
// serviceCollection.AddSingleton<IStorageProviderService, GoogleCloudStorageFileProviderService>();
|
||||
}
|
||||
|
||||
public static void UseProviderStorage(this IApplicationBuilder builder, BTCPayServerOptions options)
|
||||
{
|
||||
var dir = FileSystemFileProviderService.GetStorageDir(options);
|
||||
|
||||
DirectoryInfo dirInfo;
|
||||
if (!Directory.Exists(dir))
|
||||
{
|
||||
dirInfo = Directory.CreateDirectory(dir);
|
||||
}
|
||||
else
|
||||
{
|
||||
dirInfo = new DirectoryInfo(dir);
|
||||
}
|
||||
|
||||
builder.UseStaticFiles(new StaticFileOptions()
|
||||
{
|
||||
ServeUnknownFileTypes = true,
|
||||
RequestPath = new PathString($"/{FileSystemFileProviderService.LocalStorageDirectoryName}"),
|
||||
FileProvider = new PhysicalFileProvider(dirInfo.FullName)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
10
BTCPayServer/Storage/ViewModels/ChooseStorageViewModel.cs
Normal file
10
BTCPayServer/Storage/ViewModels/ChooseStorageViewModel.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using BTCPayServer.Storage.Models;
|
||||
|
||||
namespace BTCPayServer.Storage.ViewModels
|
||||
{
|
||||
public class ChooseStorageViewModel
|
||||
{
|
||||
public StorageProvider Provider { get; set; }
|
||||
public bool ShowChangeWarning { get; set; }
|
||||
}
|
||||
}
|
||||
44
BTCPayServer/Views/Server/CreateTemporaryFileUrl.cshtml
Normal file
44
BTCPayServer/Views/Server/CreateTemporaryFileUrl.cshtml
Normal file
@@ -0,0 +1,44 @@
|
||||
@using BTCPayServer.Controllers
|
||||
@model BTCPayServer.Controllers.ServerController.CreateTemporaryFileUrlViewModel
|
||||
@{
|
||||
ViewData.SetActivePageAndTitle(ServerNavPages.Services, $"Create temporary file link");
|
||||
|
||||
}
|
||||
|
||||
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
<partial name="_StatusMessage" for="@TempData["StatusMessage"]"/>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<form method="post">
|
||||
<div class="form-group">
|
||||
<label asp-for="IsDownload"></label>
|
||||
<input type="checkbox" class="form-check" asp-for="IsDownload"/>
|
||||
<span asp-validation-for="IsDownload" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="TimeAmount" class="control-label"></label>
|
||||
<div class="input-group">
|
||||
|
||||
<input type="number" asp-for="TimeAmount" class="form-control">
|
||||
<div class="input-group-append">
|
||||
<select asp-for="TimeType" asp-items="@Html.GetEnumSelectList< ServerController.CreateTemporaryFileUrlViewModel.TmpFileTimeType>()" class="custom-select"></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span asp-validation-for="TimeAmount" class="text-danger"></span>
|
||||
<span asp-validation-for="TimeType" class="text-danger"></span>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary" name="command" value="Generate">Generate</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
}
|
||||
61
BTCPayServer/Views/Server/EditAmazonS3StorageProvider.cshtml
Normal file
61
BTCPayServer/Views/Server/EditAmazonS3StorageProvider.cshtml
Normal file
@@ -0,0 +1,61 @@
|
||||
@model BTCPayServer.Storage.Services.Providers.AmazonS3Storage.Configuration.AmazonS3StorageConfiguration
|
||||
@{
|
||||
ViewData.SetActivePageAndTitle(ServerNavPages.Services, $"Storage - Amazon S3");
|
||||
}
|
||||
|
||||
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
<partial name="_StatusMessage" for="@TempData["StatusMessage"]" />
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<form method="post">
|
||||
<div class="form-group">
|
||||
<label asp-for="ContainerName"></label>
|
||||
<input class="form-control" asp-for="ContainerName" />
|
||||
<span asp-validation-for="ContainerName" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="SecretKey"></label>
|
||||
<input class="form-control" asp-for="SecretKey"/>
|
||||
<span asp-validation-for="SecretKey" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Bucket"></label>
|
||||
<input class="form-control" asp-for="Bucket"/>
|
||||
<span asp-validation-for="Bucket" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="ServiceUrl"></label>
|
||||
<input class="form-control" asp-for="ServiceUrl"/>
|
||||
<span asp-validation-for="ServiceUrl" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="ServerSideEncryptionMethod"></label>
|
||||
<input class="form-control" asp-for="ServerSideEncryptionMethod"/>
|
||||
<span asp-validation-for="ServerSideEncryptionMethod" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="ProfileName"></label>
|
||||
<input class="form-control" asp-for="ProfileName"/>
|
||||
<span asp-validation-for="ProfileName" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="ChunkedUploadThreshold"></label>
|
||||
<input class="form-control" type="number" asp-for="ChunkedUploadThreshold"/>
|
||||
<span asp-validation-for="ChunkedUploadThreshold" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary" name="command" value="Save">Save</button>
|
||||
<a asp-action="Storage" asp-route-forceChoice="true" >Change Storage provider</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
@model BTCPayServer.Storage.Services.Providers.AzureBlobStorage.Configuration.AzureBlobStorageConfiguration
|
||||
@{
|
||||
ViewData.SetActivePageAndTitle(ServerNavPages.Services, $"Storage - Azure Blob Storage");
|
||||
}
|
||||
|
||||
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
<partial name="_StatusMessage" for="@TempData["StatusMessage"]" />
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<form method="post">
|
||||
<div class="form-group">
|
||||
<label asp-for="ContainerName"></label>
|
||||
<input class="form-control" asp-for="ContainerName" />
|
||||
<span asp-validation-for="ContainerName" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="ConnectionString"></label>
|
||||
<input class="form-control" type="text" asp-for="ConnectionString"/>
|
||||
<span asp-validation-for="ConnectionString" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary" name="command" value="Save">Save</button>
|
||||
<a asp-action="Storage" asp-route-forceChoice="true" >Change Storage provider</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
@model BTCPayServer.Storage.Services.Providers.FileSystemStorage.Configuration.FileSystemStorageConfiguration
|
||||
@{
|
||||
ViewData.SetActivePageAndTitle(ServerNavPages.Services, $"Storage - Local Filesystem");
|
||||
}
|
||||
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
<partial name="_StatusMessage" for="@TempData["StatusMessage"]" />
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>Nothing to configure here. Data will be saved in the btcpay data directory.</p>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<form method="post">
|
||||
|
||||
<button type="submit" class="btn btn-primary" name="command" value="Save">Choose</button>
|
||||
<a asp-action="Storage" asp-route-forceChoice="true" >Change Storage provider</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
@model BTCPayServer.Storage.Services.Providers.GoogleCloudStorage.Configuration.GoogleCloudStorageConfiguration
|
||||
@{
|
||||
ViewData.SetActivePageAndTitle(ServerNavPages.Services, $"Storage - Google Cloud Storage");
|
||||
}
|
||||
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
<partial name="_StatusMessage" for="@TempData["StatusMessage"]" />
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<form method="post">
|
||||
<div class="form-group">
|
||||
<label asp-for="ContainerName"></label>
|
||||
<input class="form-control" asp-for="ContainerName" />
|
||||
<span asp-validation-for="ContainerName" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="JsonCredentials"></label>
|
||||
<input class="form-control" asp-for="JsonCredentials"/>
|
||||
<span asp-validation-for="JsonCredentials" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Bucket"></label>
|
||||
<input class="form-control" asp-for="Bucket"/>
|
||||
<span asp-validation-for="Bucket" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Email"></label>
|
||||
<input class="form-control" asp-for="Email"/>
|
||||
<span asp-validation-for="Email" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary" name="command" value="Save">Save</button>
|
||||
<a asp-action="Storage" asp-route-forceChoice="true" >Change Storage provider</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
}
|
||||
112
BTCPayServer/Views/Server/Files.cshtml
Normal file
112
BTCPayServer/Views/Server/Files.cshtml
Normal file
@@ -0,0 +1,112 @@
|
||||
@model ViewFilesViewModel
|
||||
@{
|
||||
ViewData.SetActivePageAndTitle(ServerNavPages.Files);
|
||||
}
|
||||
|
||||
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
<partial name="_StatusMessage" for="@TempData["StatusMessage"]"/>
|
||||
|
||||
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Timestamp</th>
|
||||
<th>User</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var file in Model.Files)
|
||||
{
|
||||
<tr>
|
||||
<td>@file.FileName</td>
|
||||
<td>@file.Timestamp.ToString("g")</td>
|
||||
<td>@file.ApplicationUser.UserName</td>
|
||||
<td>
|
||||
<a asp-action="Files" asp-route-fileId="@file.Id">Get Link</a>
|
||||
- <a asp-action="CreateTemporaryFileUrl" asp-route-fileId="@file.Id">Get Temp Link</a>
|
||||
- <a asp-action="DeleteFile" asp-route-fileId="@file.Id">Remove</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@if (!Model.Files.Any())
|
||||
{
|
||||
<tr>
|
||||
<td colspan="4" class="text-center">No files</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@if (!string.IsNullOrEmpty(Model.SelectedFileId))
|
||||
{
|
||||
var file = Model.Files.Single(storedFile => storedFile.Id.Equals(Model.SelectedFileId, StringComparison.InvariantCultureIgnoreCase));
|
||||
<div class="card mb-2">
|
||||
<div class="card-text">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
@file.FileName
|
||||
</li>
|
||||
|
||||
<li class="list-group-item">
|
||||
<div class="row px-0 mx-0">
|
||||
<span class="col-sm-12 col-md-2">Url: </span>
|
||||
<a class="col-sm-12 col-md-10 text-right" asp-action="GetFile" asp-controller="Storage" asp-route-fileId="@Model.SelectedFileId" target="_blank">
|
||||
@Url.Action("GetFile", "Storage", new
|
||||
{
|
||||
fileId = Model.SelectedFileId
|
||||
}, Context.Request.Scheme, Context.Request.Host.ToString(), Context.Request.PathBase.ToString())
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item ">
|
||||
<div class="row px-0 mx-0">
|
||||
<span class="col-sm-12 col-md-2">Direct Url </span>
|
||||
<a class="col-sm-12 col-md-10 text-right" href="@Model.DirectFileUrl" target="_blank">@Model.DirectFileUrl</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Model.StorageConfigured)
|
||||
{
|
||||
<div class="card">
|
||||
<form asp-action="CreateFile" method="post" enctype="multipart/form-data">
|
||||
|
||||
<div class="card-body">
|
||||
<h3 class="header">Upload File</h3>
|
||||
|
||||
<div class="custom-file">
|
||||
<input type="file" class="custom-file-input" name="file" id="file" required>
|
||||
<label class="custom-file-label" for="customFile">Choose file</label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<button class="btn btn-primary" role="button"><span class="fa fa-plus"></span> Upload file</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
|
||||
$('.custom-file-input').on('change',
|
||||
function() {
|
||||
var label = $(this).next('label');
|
||||
if (document.getElementById("file").files.length > 0) {
|
||||
var fileName = document.getElementById("file").files[0].name;
|
||||
label.addClass("selected").html(fileName);
|
||||
} else {
|
||||
label.removeClass("selected").html("Choose file");
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,6 @@ namespace BTCPayServer.Views.Server
|
||||
{
|
||||
public enum ServerNavPages
|
||||
{
|
||||
Index, Users, Rates, Emails, Policies, Theme, Services, Maintenance, Logs
|
||||
Index, Users, Rates, Emails, Policies, Theme, Services, Maintenance, Logs, Files
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
<partial name="_StatusMessage" for="@TempData["TempDataProperty-StatusMessage"]" />
|
||||
<partial name="_StatusMessage" for="@TempData["TempDataProperty-StatusMessage"]"/>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
@@ -26,7 +26,7 @@
|
||||
<tr>
|
||||
<th>Crypto</th>
|
||||
<th>Access Type</th>
|
||||
<th style="text-align:right">Actions</th>
|
||||
<th style="text-align: right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -35,7 +35,7 @@
|
||||
<tr>
|
||||
<td>@s.CryptoCode</td>
|
||||
<td>@s.DisplayName</td>
|
||||
<td style="text-align:right">
|
||||
<td style="text-align: right">
|
||||
<a asp-action="Service" asp-route-serviceName="@s.ServiceName" asp-route-cryptoCode="@s.CryptoCode">See information</a>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -59,7 +59,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th style="text-align:right">Actions</th>
|
||||
<th style="text-align: right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -67,7 +67,7 @@
|
||||
{
|
||||
<tr>
|
||||
<td>@s.Name</td>
|
||||
<td style="text-align:right">
|
||||
<td style="text-align: right">
|
||||
<a href="@s.Link" target="_blank">See information</a>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -92,7 +92,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th style="text-align:right">Actions</th>
|
||||
<th style="text-align: right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -100,7 +100,7 @@
|
||||
{
|
||||
<tr>
|
||||
<td>@s.Name</td>
|
||||
<td style="text-align:right">
|
||||
<td style="text-align: right">
|
||||
<a href="@s.Link" target="_blank">See information</a>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -125,7 +125,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th style="text-align:right">Actions</th>
|
||||
<th style="text-align: right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -133,7 +133,7 @@
|
||||
{
|
||||
<tr>
|
||||
<td>@s.Name</td>
|
||||
<td style="text-align:right">@s.Link</td>
|
||||
<td style="text-align: right">@s.Link</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
@@ -142,6 +142,35 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<h4>External storage services</h4>
|
||||
<div class="form-group">
|
||||
<span>Integrated Storage providers to store file uploads from btcpay</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th style="text-align: right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var s in Model.ExternalStorageServices)
|
||||
{
|
||||
<tr>
|
||||
<td>@s.Name</td>
|
||||
<td style="text-align: right">
|
||||
<a href="@s.Link">Edit</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
|
||||
36
BTCPayServer/Views/Server/Storage.cshtml
Normal file
36
BTCPayServer/Views/Server/Storage.cshtml
Normal file
@@ -0,0 +1,36 @@
|
||||
@using BTCPayServer.Storage.Models
|
||||
@model BTCPayServer.Storage.ViewModels.ChooseStorageViewModel
|
||||
@{
|
||||
ViewData.SetActivePageAndTitle(ServerNavPages.Services);
|
||||
}
|
||||
|
||||
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
<partial name="_StatusMessage" for="@TempData["StatusMessage"]"/>
|
||||
@if (Model.ShowChangeWarning)
|
||||
{
|
||||
<div class="alert alert-danger">
|
||||
If you change your configured storage provider, your current files will become inaccessible.
|
||||
</div>
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<form method="post">
|
||||
<div class="form-group">
|
||||
<label asp-for="Provider"></label>
|
||||
<select asp-for="Provider" asp-items="@Html.GetEnumSelectList<StorageProvider>()" class="form-control"></select>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary" name="command" value="Save">Next</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
}
|
||||
@@ -7,5 +7,6 @@
|
||||
<a class="nav-link @ViewData.IsActivePage(ServerNavPages.Theme)" asp-action="Theme">Theme</a>
|
||||
<a class="nav-link @ViewData.IsActivePage(ServerNavPages.Maintenance)" asp-action="Maintenance">Maintenance</a>
|
||||
<a class="nav-link @ViewData.IsActivePage(ServerNavPages.Logs)" asp-action="Logs">Logs</a>
|
||||
<a class="nav-link @ViewData.IsActivePage(ServerNavPages.Files)" asp-action="Files">Files</a>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user