add gif-Seeder and gif-Templates:
Zusammenfassung der Änderungen
Template-GIF Seeding implementiert:
1. DemoSeeder.cs aktualisiert:
- SeedTemplateGifsAsync() - Öffentliche Methode für externes Seeding
- SeedTemplateGifsInternalAsync() - Kopiert GIFs vom Template-Ordner und erstellt DB-Einträge
- DeleteClubGifsAsync() - Löscht GIF-Dateien und DB-Einträge beim Reset
- ResetDemoClubAsync() - Löscht nun auch GIFs und seedet sie neu
- SeedAsync() - Seedet Template-GIFs beim Demo-Club-Setup
2. IClubGifService.cs erweitert:
- SeedTemplateGifsAsync(Guid clubId) - Neue Interface-Methode
3. ClubGifService.cs erweitert:
- SeedTemplateGifsAsync() implementiert - Liest giftemplates.json, kopiert Dateien und erstellt ClubGif-Einträge
4. ClubService.cs aktualisiert:
- Injiziert IClubGifService
- CreateAsync() seedet automatisch Template-GIFs für neue Clubs
Ablauf:
1. Neuer Club: Template-GIFs werden automatisch kopiert nach wwwroot/club-media/{LoginName}/gifs/
2. Demo-Reset: Alte GIFs werden gelöscht, Templates neu kopiert
3. Demo-Seed: Template-GIFs werden geseedet falls noch keine vorhanden
Template-Struktur:
wwwroot/club-template/gifs/
├── giftemplates.json # Definition der GIFs
├── 44f5bc11-...-d1215.gif # Strike GIF
└── 297970f3-...-a2a93.gif # NoWood GIF
This commit is contained in:
parent
a3a5c288a1
commit
9aee6b50c0
|
|
@ -36,4 +36,11 @@ public interface IClubGifService
|
|||
|
||||
// Anonymous Submission
|
||||
Task<ClubGifDto> SubmitAnonymousAsync(string token, IFormFile file, string name, CancellationToken ct = default);
|
||||
|
||||
// Template Seeding
|
||||
/// <summary>
|
||||
/// Seeds template GIFs from club-template folder to a club.
|
||||
/// Should be called after creating a new club.
|
||||
/// </summary>
|
||||
Task<int> SeedTemplateGifsAsync(Guid clubId, CancellationToken ct = default);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Text.Json;
|
||||
using Koogle.Application.DTOs;
|
||||
using Koogle.Application.Interfaces;
|
||||
using Koogle.Domain.Entities;
|
||||
|
|
@ -17,17 +18,25 @@ public class ClubGifService : IClubGifService
|
|||
private readonly IClubRepository _clubRepository;
|
||||
private readonly IMediaStorageService _mediaStorage;
|
||||
private readonly ICurrentClubContext _clubContext;
|
||||
private readonly IWebHostEnvironment _env;
|
||||
|
||||
/// <summary>
|
||||
/// DTO for deserializing GIF template entries from giftemplates.json.
|
||||
/// </summary>
|
||||
private record GifTemplateEntry(string Name, string Filename, string ThrowEventType, string? Description);
|
||||
|
||||
public ClubGifService(
|
||||
IClubGifRepository repository,
|
||||
IClubRepository clubRepository,
|
||||
IMediaStorageService mediaStorage,
|
||||
ICurrentClubContext clubContext)
|
||||
ICurrentClubContext clubContext,
|
||||
IWebHostEnvironment env)
|
||||
{
|
||||
_repository = repository;
|
||||
_clubRepository = clubRepository;
|
||||
_mediaStorage = mediaStorage;
|
||||
_clubContext = clubContext;
|
||||
_env = env;
|
||||
}
|
||||
|
||||
public async Task<ClubGifDto?> GetByIdAsync(Guid id, CancellationToken ct = default)
|
||||
|
|
@ -276,6 +285,92 @@ public class ClubGifService : IClubGifService
|
|||
return MapToDto(gif, club.LoginName);
|
||||
}
|
||||
|
||||
public async Task<int> SeedTemplateGifsAsync(Guid clubId, CancellationToken ct = default)
|
||||
{
|
||||
var club = await _clubRepository.GetByIdAsync(clubId, ct)
|
||||
?? throw new ArgumentException("Club not found");
|
||||
|
||||
var templatePath = Path.Combine(_env.WebRootPath, "club-template", "gifs");
|
||||
var templateJsonPath = Path.Combine(templatePath, "giftemplates.json");
|
||||
|
||||
if (!File.Exists(templateJsonPath))
|
||||
{
|
||||
return 0; // No template file, skip seeding
|
||||
}
|
||||
|
||||
// Read template definitions
|
||||
var jsonContent = await File.ReadAllTextAsync(templateJsonPath, ct);
|
||||
var templates = JsonSerializer.Deserialize<List<GifTemplateEntry>>(jsonContent, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
});
|
||||
|
||||
if (templates == null || templates.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Ensure target directory exists
|
||||
_mediaStorage.EnsureClubDirectoryExists(club.LoginName);
|
||||
|
||||
var seededCount = 0;
|
||||
|
||||
foreach (var template in templates)
|
||||
{
|
||||
var sourceFile = Path.Combine(templatePath, template.Filename);
|
||||
if (!File.Exists(sourceFile))
|
||||
{
|
||||
continue; // Skip if source file doesn't exist
|
||||
}
|
||||
|
||||
// Parse event type
|
||||
if (!Enum.TryParse<ThrowEventType>(template.ThrowEventType, out var eventType))
|
||||
{
|
||||
eventType = ThrowEventType.None;
|
||||
}
|
||||
|
||||
// Determine content type
|
||||
var extension = Path.GetExtension(template.Filename).ToLowerInvariant();
|
||||
var contentType = extension switch
|
||||
{
|
||||
".gif" => "image/gif",
|
||||
".mp4" => "video/mp4",
|
||||
".webm" => "video/webm",
|
||||
_ => "application/octet-stream"
|
||||
};
|
||||
|
||||
// Copy file via media storage service
|
||||
await using var sourceStream = new FileStream(sourceFile, FileMode.Open, FileAccess.Read);
|
||||
var newFileName = await _mediaStorage.SaveGifAsync(club.LoginName, sourceStream, template.Filename, contentType, ct);
|
||||
|
||||
// Get file size
|
||||
var targetFile = Path.Combine(_env.WebRootPath, "club-media", club.LoginName, "gifs", newFileName);
|
||||
var fileInfo = new FileInfo(targetFile);
|
||||
|
||||
// Create database entry
|
||||
var gif = new ClubGif
|
||||
{
|
||||
ClubId = clubId,
|
||||
Name = template.Name,
|
||||
FileName = newFileName,
|
||||
OriginalFileName = template.Filename,
|
||||
ContentType = contentType,
|
||||
FileSizeBytes = fileInfo.Length,
|
||||
AssignedEvents = eventType,
|
||||
Description = template.Description,
|
||||
IsEnabled = true,
|
||||
IsPendingApproval = false,
|
||||
RatingScore = 0,
|
||||
RatingCount = 0
|
||||
};
|
||||
|
||||
await _repository.AddAsync(gif, ct);
|
||||
seededCount++;
|
||||
}
|
||||
|
||||
return seededCount;
|
||||
}
|
||||
|
||||
private static ClubGif? SelectWeightedRandom(List<ClubGif> gifs)
|
||||
{
|
||||
if (gifs.Count == 0) return null;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ public class ClubService : IClubService
|
|||
private readonly IClubRepository _clubRepository;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly AppDbContext _appDb;
|
||||
private readonly IClubGifService _gifService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ClubService"/> class.
|
||||
|
|
@ -25,11 +26,13 @@ public class ClubService : IClubService
|
|||
/// <param name="clubRepository">The club repository.</param>
|
||||
/// <param name="mapper">The AutoMapper instance.</param>
|
||||
/// <param name="appDb">The application database context.</param>
|
||||
public ClubService(IClubRepository clubRepository, IMapper mapper, AppDbContext appDb)
|
||||
/// <param name="gifService">The GIF service for seeding templates.</param>
|
||||
public ClubService(IClubRepository clubRepository, IMapper mapper, AppDbContext appDb, IClubGifService gifService)
|
||||
{
|
||||
_clubRepository = clubRepository;
|
||||
_mapper = mapper;
|
||||
_appDb = appDb;
|
||||
_gifService = gifService;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -72,6 +75,17 @@ public class ClubService : IClubService
|
|||
};
|
||||
|
||||
var created = await _clubRepository.AddAsync(entity, ct);
|
||||
|
||||
// Seed template GIFs for new club
|
||||
try
|
||||
{
|
||||
await _gifService.SeedTemplateGifsAsync(created.Id, ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Don't fail club creation if GIF seeding fails
|
||||
}
|
||||
|
||||
return _mapper.Map<ClubDto>(created);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
using System.Text.Json;
|
||||
using Koogle.Domain.Entities;
|
||||
using Koogle.Domain.Enums;
|
||||
using Koogle.Domain.Interfaces;
|
||||
using Koogle.Infrastructure.Identity;
|
||||
using KoogleApp.Data;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
|
@ -24,6 +26,11 @@ public static class DemoSeeder
|
|||
|
||||
private static readonly string[] GuestNames = { "Stefan Gast", "Thomas Besucher" };
|
||||
|
||||
/// <summary>
|
||||
/// DTO for deserializing GIF template entries from giftemplates.json.
|
||||
/// </summary>
|
||||
private record GifTemplateEntry(string Name, string Filename, string ThrowEventType, string? Description);
|
||||
|
||||
/// <summary>
|
||||
/// Seeds demo user, club membership, persons, expenses, triggers and sample days.
|
||||
/// </summary>
|
||||
|
|
@ -130,6 +137,14 @@ public static class DemoSeeder
|
|||
{
|
||||
await SeedDemoDataAsync(appDb, triggerRepo, club.Id, demoUser.Id);
|
||||
}
|
||||
|
||||
// 7) Seed template GIFs if not already present
|
||||
var webEnv = scope.ServiceProvider.GetRequiredService<IWebHostEnvironment>();
|
||||
var hasGifs = await appDb.ClubGifs.AnyAsync(g => g.ClubId == club.Id && !g.IsDeleted);
|
||||
if (!hasGifs)
|
||||
{
|
||||
await SeedTemplateGifsInternalAsync(appDb, webEnv, club.Id, club.LoginName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -140,19 +155,24 @@ public static class DemoSeeder
|
|||
using var scope = services.CreateScope();
|
||||
var appDb = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
var triggerRepo = scope.ServiceProvider.GetRequiredService<ITriggerRepository>();
|
||||
var env = scope.ServiceProvider.GetRequiredService<IWebHostEnvironment>();
|
||||
|
||||
// Find creator ID for seeding
|
||||
// Find creator ID and club for seeding
|
||||
var club = await appDb.Clubs.FindAsync(clubId);
|
||||
if (club == null) return;
|
||||
|
||||
var membership = await appDb.UserProfileClubs.FirstOrDefaultAsync(m => m.ClubId == clubId);
|
||||
var creatorId = membership?.AssignedById ?? Guid.Empty;
|
||||
|
||||
// Hard delete all club data
|
||||
// Hard delete all club data including GIFs
|
||||
await DeleteClubGifsAsync(appDb, env, clubId, club.LoginName);
|
||||
await HardDeleteClubDataAsync(appDb, clubId);
|
||||
|
||||
// Re-seed demo data
|
||||
await SeedDemoDataAsync(appDb, triggerRepo, clubId, creatorId);
|
||||
|
||||
// Re-seed template GIFs
|
||||
await SeedTemplateGifsInternalAsync(appDb, env, clubId, club.LoginName);
|
||||
}
|
||||
|
||||
private static async Task HardDeleteClubDataAsync(AppDbContext db, Guid clubId)
|
||||
|
|
@ -404,4 +424,128 @@ public static class DemoSeeder
|
|||
}
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Seeds template GIFs for a club by copying files and creating database entries.
|
||||
/// Called during demo reset and when creating new clubs.
|
||||
/// </summary>
|
||||
/// <param name="services">Service provider.</param>
|
||||
/// <param name="clubId">Club ID to seed GIFs for.</param>
|
||||
/// <param name="clubLoginName">Club's login name for folder path.</param>
|
||||
public static async Task SeedTemplateGifsAsync(IServiceProvider services, Guid clubId, string clubLoginName)
|
||||
{
|
||||
using var scope = services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
var env = scope.ServiceProvider.GetRequiredService<IWebHostEnvironment>();
|
||||
|
||||
await SeedTemplateGifsInternalAsync(db, env, clubId, clubLoginName);
|
||||
}
|
||||
|
||||
private static async Task SeedTemplateGifsInternalAsync(
|
||||
AppDbContext db,
|
||||
IWebHostEnvironment env,
|
||||
Guid clubId,
|
||||
string clubLoginName)
|
||||
{
|
||||
var templatePath = Path.Combine(env.WebRootPath, "club-template", "gifs");
|
||||
var templateJsonPath = Path.Combine(templatePath, "giftemplates.json");
|
||||
|
||||
if (!File.Exists(templateJsonPath))
|
||||
{
|
||||
return; // No template file, skip seeding
|
||||
}
|
||||
|
||||
// Read template definitions
|
||||
var jsonContent = await File.ReadAllTextAsync(templateJsonPath);
|
||||
var templates = JsonSerializer.Deserialize<List<GifTemplateEntry>>(jsonContent, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
});
|
||||
|
||||
if (templates == null || templates.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Create target directory
|
||||
var targetPath = Path.Combine(env.WebRootPath, "club-media", clubLoginName, "gifs");
|
||||
Directory.CreateDirectory(targetPath);
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
foreach (var template in templates)
|
||||
{
|
||||
var sourceFile = Path.Combine(templatePath, template.Filename);
|
||||
if (!File.Exists(sourceFile))
|
||||
{
|
||||
continue; // Skip if source file doesn't exist
|
||||
}
|
||||
|
||||
// Generate new filename for club
|
||||
var extension = Path.GetExtension(template.Filename);
|
||||
var newFileName = $"{Guid.NewGuid()}{extension}";
|
||||
var targetFile = Path.Combine(targetPath, newFileName);
|
||||
|
||||
// Copy file
|
||||
File.Copy(sourceFile, targetFile, overwrite: true);
|
||||
|
||||
// Parse event type
|
||||
if (!Enum.TryParse<ThrowEventType>(template.ThrowEventType, out var eventType))
|
||||
{
|
||||
eventType = ThrowEventType.None;
|
||||
}
|
||||
|
||||
// Get file info
|
||||
var fileInfo = new FileInfo(targetFile);
|
||||
|
||||
// Determine content type
|
||||
var contentType = extension.ToLowerInvariant() switch
|
||||
{
|
||||
".gif" => "image/gif",
|
||||
".mp4" => "video/mp4",
|
||||
".webm" => "video/webm",
|
||||
_ => "application/octet-stream"
|
||||
};
|
||||
|
||||
// Create database entry
|
||||
var gif = new ClubGif
|
||||
{
|
||||
ClubId = clubId,
|
||||
Name = template.Name,
|
||||
FileName = newFileName,
|
||||
OriginalFileName = template.Filename,
|
||||
ContentType = contentType,
|
||||
FileSizeBytes = fileInfo.Length,
|
||||
AssignedEvents = eventType,
|
||||
Description = template.Description,
|
||||
IsEnabled = true,
|
||||
IsPendingApproval = false,
|
||||
RatingScore = 0,
|
||||
RatingCount = 0,
|
||||
CreatedAt = now
|
||||
};
|
||||
|
||||
db.ClubGifs.Add(gif);
|
||||
}
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all GIF files and database entries for a club.
|
||||
/// </summary>
|
||||
private static async Task DeleteClubGifsAsync(AppDbContext db, IWebHostEnvironment env, Guid clubId, string clubLoginName)
|
||||
{
|
||||
// Delete database entries
|
||||
await db.Database.ExecuteSqlAsync($"DELETE FROM app.ClubGifRatings WHERE ClubGifId IN (SELECT Id FROM app.ClubGifs WHERE ClubId = {clubId})");
|
||||
await db.Database.ExecuteSqlAsync($"DELETE FROM app.ClubGifs WHERE ClubId = {clubId}");
|
||||
await db.Database.ExecuteSqlAsync($"DELETE FROM app.GifSubmissionTokens WHERE ClubId = {clubId}");
|
||||
|
||||
// Delete files
|
||||
var gifPath = Path.Combine(env.WebRootPath, "club-media", clubLoginName, "gifs");
|
||||
if (Directory.Exists(gifPath))
|
||||
{
|
||||
Directory.Delete(gifPath, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 1016 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.3 MiB |
|
|
@ -0,0 +1,14 @@
|
|||
[
|
||||
{
|
||||
"name": "alle Neune 1",
|
||||
"filename": "44f5bc11-8471-4ca9-b764-58e3929d1215.gif",
|
||||
"ThrowEventType": "Strike",
|
||||
"description": "Animation, alle Kegel werden getroffen"
|
||||
},
|
||||
{
|
||||
"name": "kein Holz 1",
|
||||
"filename": "297970f3-1bf1-4090-aa5e-d0f5cea42a93.gif",
|
||||
"ThrowEventType": "NoWood",
|
||||
"description": "Animation, Kegel springen zur Seite"
|
||||
}
|
||||
]
|
||||
Loading…
Reference in New Issue