From 3b12a82982fc1e071de27dd04f80a9f8e95ed6cb Mon Sep 17 00:00:00 2001 From: beo3000 Date: Thu, 1 Jan 2026 10:58:02 +0100 Subject: [PATCH] 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 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../Interfaces/IClubGifService.cs | 1 + .../Services/ClubGifService.cs | 34 +++++ .../Components/Pages/Gif/Submit.razor | 139 +++++++++++++----- 3 files changed, 138 insertions(+), 36 deletions(-) diff --git a/src/Koogle.Application/Interfaces/IClubGifService.cs b/src/Koogle.Application/Interfaces/IClubGifService.cs index 3e5c88a..fb094e6 100644 --- a/src/Koogle.Application/Interfaces/IClubGifService.cs +++ b/src/Koogle.Application/Interfaces/IClubGifService.cs @@ -36,6 +36,7 @@ public interface IClubGifService // Anonymous Submission Task SubmitAnonymousAsync(string token, IFormFile file, string name, CancellationToken ct = default); + Task SubmitAnonymousFromUrlAsync(string token, string url, string name, CancellationToken ct = default); // Template Seeding /// diff --git a/src/Koogle.Application/Services/ClubGifService.cs b/src/Koogle.Application/Services/ClubGifService.cs index 2a3ff0e..66e8e87 100644 --- a/src/Koogle.Application/Services/ClubGifService.cs +++ b/src/Koogle.Application/Services/ClubGifService.cs @@ -285,6 +285,40 @@ public class ClubGifService : IClubGifService return MapToDto(gif, club.LoginName); } + public async Task 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 SeedTemplateGifsAsync(Guid clubId, CancellationToken ct = default) { var club = await _clubRepository.GetByIdAsync(clubId, ct) diff --git a/src/Koogle.Web/Components/Pages/Gif/Submit.razor b/src/Koogle.Web/Components/Pages/Gif/Submit.razor index 83d9b7b..b1e24fe 100644 --- a/src/Koogle.Web/Components/Pages/Gif/Submit.razor +++ b/src/Koogle.Web/Components/Pages/Gif/Submit.razor @@ -78,34 +78,67 @@ Variant="Variant.Outlined" Class="mb-2" /> - - - - @if (_selectedFile != null) - { - - @_selectedFile.Name - - @FormatFileSize(_selectedFile.Size) - - } - else - { - - Datei auswaehlen - - GIF, MP4 oder WebM (max. 20MB) - - } - - - + + + + Datei + + + + URL + + + + @if (_useUrl) + { + + } + else + { + + + + @if (_selectedFile != null) + { + + @_selectedFile.Name + + @FormatFileSize(_selectedFile.Size) + + } + else + { + + Datei auswaehlen + + GIF, MP4 oder WebM (max. 20MB) + + } + + + + } @if (_previewUrl != null) { @@ -167,8 +200,33 @@ private bool _isSubmitting; private bool _isSubmitted; 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() { @@ -208,20 +266,27 @@ private async Task SubmitGif() { - if (!CanSubmit || _selectedFile == null) return; + if (!CanSubmit) return; _isSubmitting = true; _error = null; try { - using var stream = _selectedFile.OpenReadStream(maxAllowedSize: 20 * 1024 * 1024); - using var ms = new MemoryStream(); - await stream.CopyToAsync(ms); - ms.Position = 0; + if (_useUrl) + { + await GifService.SubmitAnonymousFromUrlAsync(Token, _url, _name); + } + 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); - await GifService.SubmitAnonymousAsync(Token, formFile, _name); + var formFile = new FormFileFromStream(ms, _selectedFile.Name, _selectedFile.ContentType); + await GifService.SubmitAnonymousAsync(Token, formFile, _name); + } _isSubmitted = true; Snackbar.Add("GIF erfolgreich eingereicht!", Severity.Success); @@ -244,6 +309,8 @@ _selectedFile = null; _previewUrl = null; _error = null; + _url = ""; + _useUrl = false; } private static string FormatFileSize(long bytes)