mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-20 13:34:37 +01:00
Apps: Add direct file upload in item editor (#5140)
This commit is contained in:
parent
e998340387
commit
966e598f10
4 changed files with 85 additions and 6 deletions
|
@ -1,6 +1,9 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Data;
|
||||
|
@ -8,6 +11,7 @@ using BTCPayServer.Models.AppViewModels;
|
|||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
|
@ -21,17 +25,20 @@ namespace BTCPayServer.Controllers
|
|||
public UIAppsController(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
StoreRepository storeRepository,
|
||||
IFileService fileService,
|
||||
AppService appService,
|
||||
IHtmlHelper html)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_storeRepository = storeRepository;
|
||||
_fileService = fileService;
|
||||
_appService = appService;
|
||||
Html = html;
|
||||
}
|
||||
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly IFileService _fileService;
|
||||
private readonly AppService _appService;
|
||||
|
||||
public string CreatedAppId { get; set; }
|
||||
|
@ -184,13 +191,50 @@ namespace BTCPayServer.Controllers
|
|||
return RedirectToAction(nameof(UIStoresController.Dashboard), "UIStores", new { storeId = app.StoreDataId });
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[HttpPost("{appId}/upload-file")]
|
||||
[IgnoreAntiforgeryToken]
|
||||
public async Task<IActionResult> FileUpload(IFormFile file)
|
||||
{
|
||||
var app = GetCurrentApp();
|
||||
var userId = GetUserId();
|
||||
if (app is null || userId is null)
|
||||
return NotFound();
|
||||
|
||||
if (!file.ContentType.StartsWith("image/", StringComparison.InvariantCulture))
|
||||
{
|
||||
return Json(new { error = "The file needs to be an image" });
|
||||
}
|
||||
if (file.Length > 500_000)
|
||||
{
|
||||
return Json(new { error = "The image file size should be less than 0.5MB" });
|
||||
}
|
||||
var formFile = await file.Bufferize();
|
||||
if (!FileTypeDetector.IsPicture(formFile.Buffer, formFile.FileName))
|
||||
{
|
||||
return Json(new { error = "The file needs to be an image" });
|
||||
}
|
||||
try
|
||||
{
|
||||
var storedFile = await _fileService.AddFile(file, userId);
|
||||
var fileId = storedFile.Id;
|
||||
var fileUrl = await _fileService.GetFileUrl(Request.GetAbsoluteRootUri(), fileId);
|
||||
return Json(new { fileId, fileUrl });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return Json(new { error = $"Could not save file: {e.Message}" });
|
||||
}
|
||||
}
|
||||
|
||||
async Task<string> GetStoreDefaultCurrentIfEmpty(string storeId, string currency)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(currency))
|
||||
{
|
||||
currency = (await _storeRepository.FindStore(storeId)).GetStoreBlob().DefaultCurrency;
|
||||
var store = await _storeRepository.FindStore(storeId);
|
||||
currency = store?.GetStoreBlob().DefaultCurrency;
|
||||
}
|
||||
return currency.Trim().ToUpperInvariant();
|
||||
return currency?.Trim().ToUpperInvariant();
|
||||
}
|
||||
|
||||
private string GetUserId() => _userManager.GetUserId(User);
|
||||
|
|
|
@ -17,7 +17,6 @@ using BTCPayServer.Services.Rates;
|
|||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitpayClient;
|
||||
|
@ -267,6 +266,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
|
|||
if (app == null)
|
||||
return NotFound();
|
||||
|
||||
vm.AppId = app.Id;
|
||||
vm.TargetCurrency = await GetStoreDefaultCurrentIfEmpty(app.StoreDataId, vm.TargetCurrency);
|
||||
if (_currencies.GetCurrencyData(vm.TargetCurrency, false) == null)
|
||||
ModelState.AddModelError(nameof(vm.TargetCurrency), "Invalid currency");
|
||||
|
|
|
@ -566,6 +566,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
|||
if (app == null)
|
||||
return NotFound();
|
||||
|
||||
vm.Id = app.Id;
|
||||
if (!ModelState.IsValid)
|
||||
return View("PointOfSale/UpdatePointOfSale", vm);
|
||||
|
||||
|
|
|
@ -43,8 +43,13 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="EditorImageUrl" class="form-label">Image Url</label>
|
||||
<label for="EditorImage" class="form-label">Image Url</label>
|
||||
<input id="EditorImageUrl" class="form-control mb-2" pattern="[^\*#]+" v-model="editingItem && editingItem.image" ref="txtImage" />
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<input id="EditorImage" type="file" class="form-control" ref="editorImage" v-on:change="uploadFileChanged">
|
||||
<button class="btn btn-primary" type="button" id="EditorUploadButton" v-on:click="uploadFile" :disabled="uploadDisabled">Upload</button>
|
||||
</div>
|
||||
<span v-if="uploadError" v-text="uploadError" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="EditorDescription" class="form-label">Description</label>
|
||||
|
@ -142,6 +147,7 @@
|
|||
<script src="~/vendor/tom-select/tom-select.complete.min.js" asp-append-version="true"></script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const fileUploadUrl = @Safe.Json(Url.Action("FileUpload", "UIApps", new { appId = Context.GetRouteValue("appId") }));
|
||||
const parseConfig = str => {
|
||||
try {
|
||||
return JSON.parse(str)
|
||||
|
@ -166,7 +172,9 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||
{ text: 'Custom', value: 'Topup' },
|
||||
],
|
||||
categoriesSelect: null,
|
||||
productModal: null
|
||||
productModal: null,
|
||||
uploadDisabled: true,
|
||||
uploadError: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
@ -259,7 +267,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||
if (this.editingItem.description.startsWith("- ")){
|
||||
this.errors.push("Description cannot start with \"- \"");
|
||||
}
|
||||
if (!this.$refs.txtImage.checkValidity()) {
|
||||
if (!this.$refs.editorImage.checkValidity()) {
|
||||
this.errors.push("Image cannot have * or #");
|
||||
}
|
||||
if (this.editingItem.image.startsWith("- ")){
|
||||
|
@ -288,6 +296,32 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||
this.categoriesSelect.setValue(this.editingItem.categories);
|
||||
this.productModal.show();
|
||||
}
|
||||
},
|
||||
uploadFileChanged () {
|
||||
this.uploadDisabled = !this.$refs.editorImage || this.$refs.editorImage.files.length === 0;
|
||||
},
|
||||
async uploadFile() {
|
||||
const file = this.$refs.editorImage.files[0];
|
||||
if (!file) return this.uploadError = 'No file selected';
|
||||
|
||||
this.uploadError = null;
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
try {
|
||||
const response = await fetch(fileUploadUrl, { method: 'POST', body: formData });
|
||||
if (response.ok) {
|
||||
const { error, fileUrl } = await response.json();
|
||||
if (error) return this.uploadError = error;
|
||||
|
||||
this.editingItem.image = fileUrl;
|
||||
this.$refs.editorImage.value = null;
|
||||
this.uploadDisabled = true;
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
this.uploadError = 'Upload failed';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue