submit gifs by URL:
UI (Submit.razor) - Toggle zwischen "Datei" und "URL" Modus - URL-Eingabefeld mit Validierung (nur HTTP/HTTPS) - Beide Modi teilen Submit-Button und Erfolgsanzeige Service (IClubGifService, ClubGifService) - Neue Methode SubmitAnonymousFromUrlAsync(token, url, name) - Nutzt bestehende SaveGifFromUrlAsync von MediaStorageService Serverseitige Validierung (bereits in MediaStorageService vorhanden): - Content-Type Prüfung: nur image/gif, video/mp4, video/webm - Dateigröße max. 20MB - Datei wird nach Download nochmals auf Größe geprüft - Ungültige Content-Types werden mit Exception abgelehnt
This commit is contained in:
parent
968026729d
commit
3b12a82982
|
|
@ -36,6 +36,7 @@ public interface IClubGifService
|
||||||
|
|
||||||
// Anonymous Submission
|
// Anonymous Submission
|
||||||
Task<ClubGifDto> SubmitAnonymousAsync(string token, IFormFile file, string name, CancellationToken ct = default);
|
Task<ClubGifDto> SubmitAnonymousAsync(string token, IFormFile file, string name, CancellationToken ct = default);
|
||||||
|
Task<ClubGifDto> SubmitAnonymousFromUrlAsync(string token, string url, string name, CancellationToken ct = default);
|
||||||
|
|
||||||
// Template Seeding
|
// Template Seeding
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -285,6 +285,40 @@ public class ClubGifService : IClubGifService
|
||||||
return MapToDto(gif, club.LoginName);
|
return MapToDto(gif, club.LoginName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<ClubGifDto> SubmitAnonymousFromUrlAsync(string token, string url, string name, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var tokenEntity = await _repository.GetSubmissionTokenAsync(token, ct)
|
||||||
|
?? throw new ArgumentException("Invalid or expired token");
|
||||||
|
|
||||||
|
if (!tokenEntity.IsValid)
|
||||||
|
throw new ArgumentException("Token is expired or usage limit reached");
|
||||||
|
|
||||||
|
var club = await _clubRepository.GetByIdAsync(tokenEntity.ClubId, ct)
|
||||||
|
?? throw new ArgumentException("Club not found");
|
||||||
|
|
||||||
|
var (fileName, contentType) = await _mediaStorage.SaveGifFromUrlAsync(club.LoginName, url, ct);
|
||||||
|
var fileInfo = new FileInfo(Path.Combine("wwwroot", "club-media", club.LoginName, "gifs", fileName));
|
||||||
|
|
||||||
|
var gif = new ClubGif
|
||||||
|
{
|
||||||
|
ClubId = tokenEntity.ClubId,
|
||||||
|
Name = name,
|
||||||
|
FileName = fileName,
|
||||||
|
OriginalFileName = Path.GetFileName(new Uri(url).LocalPath),
|
||||||
|
ContentType = contentType,
|
||||||
|
FileSizeBytes = fileInfo.Exists ? fileInfo.Length : 0,
|
||||||
|
SourceUrl = url,
|
||||||
|
AssignedEvents = ThrowEventType.None,
|
||||||
|
IsEnabled = false,
|
||||||
|
IsPendingApproval = true
|
||||||
|
};
|
||||||
|
|
||||||
|
gif = await _repository.AddAsync(gif, ct);
|
||||||
|
await _repository.IncrementSubmissionCountAsync(tokenEntity.Id, ct);
|
||||||
|
|
||||||
|
return MapToDto(gif, club.LoginName);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<int> SeedTemplateGifsAsync(Guid clubId, CancellationToken ct = default)
|
public async Task<int> SeedTemplateGifsAsync(Guid clubId, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var club = await _clubRepository.GetByIdAsync(clubId, ct)
|
var club = await _clubRepository.GetByIdAsync(clubId, ct)
|
||||||
|
|
|
||||||
|
|
@ -78,34 +78,67 @@
|
||||||
Variant="Variant.Outlined"
|
Variant="Variant.Outlined"
|
||||||
Class="mb-2" />
|
Class="mb-2" />
|
||||||
|
|
||||||
<MudFileUpload T="IBrowserFile"
|
<MudButtonGroup OverrideStyles="false" Class="mb-4" Style="width: 100%;">
|
||||||
Accept=".gif,.mp4,.webm"
|
<MudButton Variant="@(_useUrl ? Variant.Outlined : Variant.Filled)"
|
||||||
FilesChanged="OnFileSelected"
|
Color="Color.Primary"
|
||||||
MaximumFileCount="1"
|
Style="flex: 1;"
|
||||||
Class="mb-4">
|
OnClick="() => SetUploadMode(false)">
|
||||||
<ActivatorContent>
|
<MudIcon Icon="@Icons.Material.Filled.UploadFile" Class="mr-1" Size="Size.Small" />
|
||||||
<MudPaper Outlined="true"
|
Datei
|
||||||
Class="pa-4 d-flex flex-column align-center justify-center"
|
</MudButton>
|
||||||
Style="border-style: dashed; min-height: 120px; cursor: pointer;">
|
<MudButton Variant="@(_useUrl ? Variant.Filled : Variant.Outlined)"
|
||||||
@if (_selectedFile != null)
|
Color="Color.Primary"
|
||||||
{
|
Style="flex: 1;"
|
||||||
<MudIcon Icon="@Icons.Material.Filled.InsertDriveFile" Size="Size.Large" Color="Color.Success" />
|
OnClick="() => SetUploadMode(true)">
|
||||||
<MudText Typo="Typo.body1">@_selectedFile.Name</MudText>
|
<MudIcon Icon="@Icons.Material.Filled.Link" Class="mr-1" Size="Size.Small" />
|
||||||
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
URL
|
||||||
@FormatFileSize(_selectedFile.Size)
|
</MudButton>
|
||||||
</MudText>
|
</MudButtonGroup>
|
||||||
}
|
|
||||||
else
|
@if (_useUrl)
|
||||||
{
|
{
|
||||||
<MudIcon Icon="@Icons.Material.Filled.CloudUpload" Size="Size.Large" Color="Color.Primary" />
|
<MudTextField @bind-Value="_url"
|
||||||
<MudText Typo="Typo.body1">Datei auswaehlen</MudText>
|
Label="URL zum GIF/Video"
|
||||||
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
Placeholder="https://example.com/animation.gif"
|
||||||
GIF, MP4 oder WebM (max. 20MB)
|
Required="true"
|
||||||
</MudText>
|
Variant="Variant.Outlined"
|
||||||
}
|
InputType="InputType.Url"
|
||||||
</MudPaper>
|
Adornment="Adornment.Start"
|
||||||
</ActivatorContent>
|
AdornmentIcon="@Icons.Material.Filled.Link"
|
||||||
</MudFileUpload>
|
Class="mb-2"
|
||||||
|
HelperText="Direkt-Link zu GIF, MP4 oder WebM" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudFileUpload T="IBrowserFile"
|
||||||
|
Accept=".gif,.mp4,.webm"
|
||||||
|
FilesChanged="OnFileSelected"
|
||||||
|
MaximumFileCount="1"
|
||||||
|
Class="mb-4">
|
||||||
|
<ActivatorContent>
|
||||||
|
<MudPaper Outlined="true"
|
||||||
|
Class="pa-4 d-flex flex-column align-center justify-center"
|
||||||
|
Style="border-style: dashed; min-height: 120px; cursor: pointer;">
|
||||||
|
@if (_selectedFile != null)
|
||||||
|
{
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.InsertDriveFile" Size="Size.Large" Color="Color.Success" />
|
||||||
|
<MudText Typo="Typo.body1">@_selectedFile.Name</MudText>
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
||||||
|
@FormatFileSize(_selectedFile.Size)
|
||||||
|
</MudText>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.CloudUpload" Size="Size.Large" Color="Color.Primary" />
|
||||||
|
<MudText Typo="Typo.body1">Datei auswaehlen</MudText>
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
||||||
|
GIF, MP4 oder WebM (max. 20MB)
|
||||||
|
</MudText>
|
||||||
|
}
|
||||||
|
</MudPaper>
|
||||||
|
</ActivatorContent>
|
||||||
|
</MudFileUpload>
|
||||||
|
}
|
||||||
|
|
||||||
@if (_previewUrl != null)
|
@if (_previewUrl != null)
|
||||||
{
|
{
|
||||||
|
|
@ -167,8 +200,33 @@
|
||||||
private bool _isSubmitting;
|
private bool _isSubmitting;
|
||||||
private bool _isSubmitted;
|
private bool _isSubmitted;
|
||||||
private string? _error;
|
private string? _error;
|
||||||
|
private bool _useUrl;
|
||||||
|
private string _url = "";
|
||||||
|
|
||||||
private bool CanSubmit => !string.IsNullOrWhiteSpace(_name) && _selectedFile != null;
|
private bool CanSubmit => !string.IsNullOrWhiteSpace(_name) &&
|
||||||
|
(_useUrl ? IsValidUrl(_url) : _selectedFile != null);
|
||||||
|
|
||||||
|
private static bool IsValidUrl(string url)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(url)) return false;
|
||||||
|
return Uri.TryCreate(url, UriKind.Absolute, out var uri) &&
|
||||||
|
(uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetUploadMode(bool useUrl)
|
||||||
|
{
|
||||||
|
_useUrl = useUrl;
|
||||||
|
_error = null;
|
||||||
|
if (useUrl)
|
||||||
|
{
|
||||||
|
_selectedFile = null;
|
||||||
|
_previewUrl = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_url = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
|
|
@ -208,20 +266,27 @@
|
||||||
|
|
||||||
private async Task SubmitGif()
|
private async Task SubmitGif()
|
||||||
{
|
{
|
||||||
if (!CanSubmit || _selectedFile == null) return;
|
if (!CanSubmit) return;
|
||||||
|
|
||||||
_isSubmitting = true;
|
_isSubmitting = true;
|
||||||
_error = null;
|
_error = null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var stream = _selectedFile.OpenReadStream(maxAllowedSize: 20 * 1024 * 1024);
|
if (_useUrl)
|
||||||
using var ms = new MemoryStream();
|
{
|
||||||
await stream.CopyToAsync(ms);
|
await GifService.SubmitAnonymousFromUrlAsync(Token, _url, _name);
|
||||||
ms.Position = 0;
|
}
|
||||||
|
else if (_selectedFile != null)
|
||||||
|
{
|
||||||
|
using var stream = _selectedFile.OpenReadStream(maxAllowedSize: 20 * 1024 * 1024);
|
||||||
|
using var ms = new MemoryStream();
|
||||||
|
await stream.CopyToAsync(ms);
|
||||||
|
ms.Position = 0;
|
||||||
|
|
||||||
var formFile = new FormFileFromStream(ms, _selectedFile.Name, _selectedFile.ContentType);
|
var formFile = new FormFileFromStream(ms, _selectedFile.Name, _selectedFile.ContentType);
|
||||||
await GifService.SubmitAnonymousAsync(Token, formFile, _name);
|
await GifService.SubmitAnonymousAsync(Token, formFile, _name);
|
||||||
|
}
|
||||||
|
|
||||||
_isSubmitted = true;
|
_isSubmitted = true;
|
||||||
Snackbar.Add("GIF erfolgreich eingereicht!", Severity.Success);
|
Snackbar.Add("GIF erfolgreich eingereicht!", Severity.Success);
|
||||||
|
|
@ -244,6 +309,8 @@
|
||||||
_selectedFile = null;
|
_selectedFile = null;
|
||||||
_previewUrl = null;
|
_previewUrl = null;
|
||||||
_error = null;
|
_error = null;
|
||||||
|
_url = "";
|
||||||
|
_useUrl = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string FormatFileSize(long bytes)
|
private static string FormatFileSize(long bytes)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue