-
-
-
Abbrechen
+
+
+
+
+ Abbrechen
+
+ @if (PageState.Action == "Edit")
+ {
+
+ }
@@ -79,6 +114,7 @@
private ElementReference form;
private bool validated = false;
+ private bool _uploading = false;
private int _id;
private string _name;
@@ -102,7 +138,6 @@
_id = Int32.Parse(PageState.QueryString["id"]);
HallOfFame HallOfFame = await HallOfFameService.GetHallOfFameAsync(_id, ModuleState.ModuleId);
- // Security check: only allow editing own entry
if (HallOfFame != null)
{
if (HallOfFame.UserId != PageState.User.UserId)
@@ -126,11 +161,9 @@
}
else // Add Mode
{
- // Check if user already has an entry to prevent duplicates
var existing = await HallOfFameService.GetHallOfFameByUserIdAsync(PageState.User.UserId, ModuleState.ModuleId);
if (existing != null)
{
- // Use NavigateUrl with parameters properly (simplified here)
NavigationManager.NavigateTo(EditUrl(existing.HallOfFameId.ToString()));
}
}
@@ -142,6 +175,50 @@
}
}
+ private async Task HandleFileSelected(InputFileChangeEventArgs e)
+ {
+ var file = e.File;
+ if (file == null) return;
+
+ if (file.Size > 5 * 1024 * 1024)
+ {
+ AddModuleMessage("Die Datei ist zu groß (max. 5 MB allowed).", MessageType.Warning);
+ return;
+ }
+
+ try
+ {
+ _uploading = true;
+ using var stream = file.OpenReadStream(5 * 1024 * 1024);
+ var url = await HallOfFameService.UploadFileAsync(stream, file.Name, ModuleState.ModuleId);
+ if (!string.IsNullOrEmpty(url))
+ {
+ _image = url;
+ AddModuleMessage("Foto erfolgreich hochgeladen.", MessageType.Success);
+ }
+ else
+ {
+ AddModuleMessage("Fehler beim Hochladen des Fotos.", MessageType.Error);
+ }
+ }
+ catch (Exception ex)
+ {
+ await logger.LogError(ex, "Error Uploading File {Error}", ex.Message);
+ AddModuleMessage("Ein technischer Fehler ist beim Upload aufgetreten.", MessageType.Error);
+ }
+ finally
+ {
+ _uploading = false;
+ StateHasChanged();
+ }
+ }
+
+ private void RemoveImage()
+ {
+ _image = string.Empty;
+ StateHasChanged();
+ }
+
private async Task Save(string status)
{
try
@@ -156,7 +233,7 @@
{
HallOfFame HallOfFame = new HallOfFame();
HallOfFame.ModuleId = ModuleState.ModuleId;
- HallOfFame.UserId = PageState.User.UserId; // Set Owner
+ HallOfFame.UserId = PageState.User.UserId;
HallOfFame.Name = _name;
HallOfFame.Year = _year;
HallOfFame.Description = _description;
@@ -170,7 +247,6 @@
else
{
HallOfFame HallOfFame = await HallOfFameService.GetHallOfFameAsync(_id, ModuleState.ModuleId);
- // Ensure we don't overwrite with invalid user logic, though server checks too
if (HallOfFame.UserId == PageState.User.UserId)
{
HallOfFame.Name = _name;
@@ -197,4 +273,18 @@
AddModuleMessage(Localizer["Message.SaveError"], MessageType.Error);
}
}
+
+ private async Task DeleteEntry()
+ {
+ try
+ {
+ await HallOfFameService.DeleteHallOfFameAsync(_id, ModuleState.ModuleId);
+ NavigationManager.NavigateTo(NavigateUrl());
+ }
+ catch (Exception ex)
+ {
+ await logger.LogError(ex, "Error Deleting HallOfFame {Error}", ex.Message);
+ AddModuleMessage("Fehler beim Löschen des Eintrags.", MessageType.Error);
+ }
+ }
}
diff --git a/Client/Modules/SZUAbsolventenverein.Module.HallOfFame/Index.razor b/Client/Modules/SZUAbsolventenverein.Module.HallOfFame/Index.razor
index c10e17b..3f79303 100644
--- a/Client/Modules/SZUAbsolventenverein.Module.HallOfFame/Index.razor
+++ b/Client/Modules/SZUAbsolventenverein.Module.HallOfFame/Index.razor
@@ -1,5 +1,7 @@
@using SZUAbsolventenverein.Module.HallOfFame.Services
@using SZUAbsolventenverein.Module.HallOfFame.Models
+@using Oqtane.Security
+@using Oqtane.Shared
@namespace SZUAbsolventenverein.Module.HallOfFame
@inherits ModuleBase
@@ -13,30 +15,44 @@
}
else
{
-
-
+
+
+
+
+
+
+
+
+
@if (PageState.User != null)
{
if (_myEntry != null)
{
-
+
}
else
{
-
+
}
}
- else
- {
-
Einloggen, um einen Eintrag zu erstellen.
- }
@if (@_HallOfFames.Count != 0)
{
- @foreach (var item in _HallOfFames)
+ @foreach (var item in FilteredHallOfFames)
{
@@ -44,13 +60,35 @@ else
{

}
-
+
@item.Name (@item.Year)
-
@item.Description
- @if (!string.IsNullOrEmpty(item.Link))
+ @if (item.IsReported && UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin + ";" + RoleNames.Host))
{
-
Mehr Infos
+
+ Dieser Eintrag wurde gemeldet!
+
}
+
+ @foreach (var line in (item.Description?.Replace("\t", " ").Split('\n') ?? Array.Empty
()))
+ {
+ @line
+ }
+
+
+
+
+ @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin + ";" + RoleNames.Host))
+ {
+
+ }
+
+
@@ -66,7 +104,6 @@ else
}
@code {
- public override string RenderMode => RenderModes.Static;
public override List
Resources => new List()
{
@@ -76,12 +113,58 @@ else
List _HallOfFames;
HallOfFame _myEntry;
+ string _searchText = "";
+ string _sortOption = "CreatedOn";
+ bool _sortAscending = false;
+
+ IEnumerable FilteredHallOfFames
+ {
+ get
+ {
+ var items = _HallOfFames.AsEnumerable();
+ if (!string.IsNullOrEmpty(_searchText))
+ {
+ items = items.Where(i =>
+ (i.Name?.Contains(_searchText, StringComparison.OrdinalIgnoreCase) ?? false) ||
+ (i.Description?.Contains(_searchText, StringComparison.OrdinalIgnoreCase) ?? false)
+ );
+ }
+
+ items = _sortOption switch
+ {
+ "Name" => _sortAscending ? items.OrderBy(i => i.Name) : items.OrderByDescending(i => i.Name),
+ "Year" => _sortAscending ? items.OrderBy(i => i.Year) : items.OrderByDescending(i => i.Year),
+ "CreatedOn" or _ => _sortAscending ? items.OrderBy(i => i.CreatedOn) : items.OrderByDescending(i => i.CreatedOn)
+ };
+
+ return items;
+ }
+ }
+
+ private string TruncateDescription(string description)
+ {
+ if (string.IsNullOrEmpty(description)) return "";
+ const int maxLength = 150;
+ if (description.Length <= maxLength) return description;
+ return description.Substring(0, maxLength).TrimEnd() + "...";
+ }
+
+ private void ToggleSortDirection()
+ {
+ _sortAscending = !_sortAscending;
+ }
protected override async Task OnInitializedAsync()
+ {
+ await LoadData();
+ }
+
+ private async Task LoadData()
{
try
{
- _HallOfFames = await HallOfFameService.GetHallOfFamesAsync(ModuleState.ModuleId);
+ var allEntries = await HallOfFameService.GetHallOfFamesAsync(ModuleState.ModuleId);
+ _HallOfFames = allEntries.Where(i => i.Status == "Published").ToList();
if (PageState.User != null)
{
@@ -94,4 +177,19 @@ else
AddModuleMessage(Localizer["Message.LoadError"], MessageType.Error);
}
}
+
+ private async Task Delete(int hallOfFameId)
+ {
+ try
+ {
+ await HallOfFameService.DeleteHallOfFameAsync(hallOfFameId, ModuleState.ModuleId);
+ AddModuleMessage("Eintrag wurde gelöscht.", MessageType.Success);
+ await LoadData();
+ }
+ catch (Exception ex)
+ {
+ await logger.LogError(ex, "Error Deleting HallOfFame {Error}", ex.Message);
+ AddModuleMessage("Fehler beim Löschen des Eintrags.", MessageType.Error);
+ }
+ }
}
\ No newline at end of file
diff --git a/Client/Modules/SZUAbsolventenverein.Module.HallOfFame/ModuleInfo.cs b/Client/Modules/SZUAbsolventenverein.Module.HallOfFame/ModuleInfo.cs
index 5898e34..ffaaeda 100644
--- a/Client/Modules/SZUAbsolventenverein.Module.HallOfFame/ModuleInfo.cs
+++ b/Client/Modules/SZUAbsolventenverein.Module.HallOfFame/ModuleInfo.cs
@@ -9,9 +9,9 @@ namespace SZUAbsolventenverein.Module.HallOfFame
{
Name = "HallOfFame",
Description = "The Hall of Fame module displays selected individuals or achievements within the CMS. Entries are shown online, can be exported as a PDF, and are only published with user consen.",
- Version = "1.0.0",
+ Version = "1.0.3",
ServerManagerType = "SZUAbsolventenverein.Module.HallOfFame.Manager.HallOfFameManager, SZUAbsolventenverein.Module.HallOfFame.Server.Oqtane",
- ReleaseVersions = "1.0.0",
+ ReleaseVersions = "1.0.0,1.0.2,1.0.3",
Dependencies = "SZUAbsolventenverein.Module.HallOfFame.Shared.Oqtane",
PackageName = "SZUAbsolventenverein.Module.HallOfFame"
};
diff --git a/Client/Services/HallOfFameService.cs b/Client/Services/HallOfFameService.cs
index 8451938..6b0d31b 100644
--- a/Client/Services/HallOfFameService.cs
+++ b/Client/Services/HallOfFameService.cs
@@ -1,9 +1,11 @@
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
+using System.Net;
using System.Threading.Tasks;
using Oqtane.Services;
using Oqtane.Shared;
+using System.Text.Json;
namespace SZUAbsolventenverein.Module.HallOfFame.Services
{
@@ -18,8 +20,11 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Services
Task AddHallOfFameAsync(Models.HallOfFame HallOfFame);
Task UpdateHallOfFameAsync(Models.HallOfFame HallOfFame);
-
Task DeleteHallOfFameAsync(int HallOfFameId, int ModuleId);
+ Task ReportAsync(int HallOfFameId, int ModuleId, string reason);
+ Task> GetHallOfFameReportsAsync(int HallOfFameId, int ModuleId);
+ Task DeleteHallOfFameReportAsync(int HallOfFameReportId, int ModuleId);
+ Task UploadFileAsync(System.IO.Stream stream, string fileName, int ModuleId);
}
public class HallOfFameService : ServiceBase, IHallOfFameService
@@ -30,8 +35,7 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Services
public async Task> GetHallOfFamesAsync(int ModuleId)
{
- List HallOfFames = await GetJsonAsync>(CreateAuthorizationPolicyUrl($"{Apiurl}?moduleid={ModuleId}", EntityNames.Module, ModuleId), Enumerable.Empty().ToList());
- return HallOfFames.OrderBy(item => item.Name).ToList();
+ return await GetJsonAsync>(CreateAuthorizationPolicyUrl($"{Apiurl}?moduleid={ModuleId}", EntityNames.Module, ModuleId), Enumerable.Empty().ToList());
}
public async Task GetHallOfFameAsync(int HallOfFameId, int ModuleId)
@@ -58,5 +62,38 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Services
{
await DeleteAsync(CreateAuthorizationPolicyUrl($"{Apiurl}/{HallOfFameId}/{ModuleId}", EntityNames.Module, ModuleId));
}
+
+ public async Task ReportAsync(int HallOfFameId, int ModuleId, string reason)
+ {
+ await PutAsync(CreateAuthorizationPolicyUrl($"{Apiurl}/report/{HallOfFameId}?reason={WebUtility.UrlEncode(reason)}", EntityNames.Module, ModuleId));
+ }
+
+ public async Task> GetHallOfFameReportsAsync(int HallOfFameId, int ModuleId)
+ {
+ return await GetJsonAsync>(CreateAuthorizationPolicyUrl($"{Apiurl}/reports/{HallOfFameId}?moduleid={ModuleId}", EntityNames.Module, ModuleId), Enumerable.Empty().ToList());
+ }
+
+ public async Task DeleteHallOfFameReportAsync(int HallOfFameReportId, int ModuleId)
+ {
+ await DeleteAsync(CreateAuthorizationPolicyUrl($"{Apiurl}/report/{HallOfFameReportId}/{ModuleId}", EntityNames.Module, ModuleId));
+ }
+
+ public async Task UploadFileAsync(System.IO.Stream stream, string fileName, int ModuleId)
+ {
+ var uri = CreateAuthorizationPolicyUrl($"{Apiurl}/upload", EntityNames.Module, ModuleId);
+ using var content = new MultipartFormDataContent();
+ var fileContent = new StreamContent(stream);
+ fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream");
+ content.Add(fileContent, "file", fileName);
+
+ var response = await GetHttpClient().PostAsync(uri, content);
+ if (response.IsSuccessStatusCode)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+ var result = JsonSerializer.Deserialize>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
+ return result["url"];
+ }
+ return null;
+ }
}
}
diff --git a/Package/debug.sh b/Package/debug.sh
index f90961d..5b29862 100755
--- a/Package/debug.sh
+++ b/Package/debug.sh
@@ -9,5 +9,5 @@ cp -f "../Server/bin/Debug/$TargetFramework/$ProjectName.Server.Oqtane.dll" "../
cp -f "../Server/bin/Debug/$TargetFramework/$ProjectName.Server.Oqtane.pdb" "../../oqtane.framework/Oqtane.Server/bin/Debug/$TargetFramework/"
cp -f "../Shared/bin/Debug/$TargetFramework/$ProjectName.Shared.Oqtane.dll" "../../oqtane.framework/Oqtane.Server/bin/Debug/$TargetFramework/"
cp -f "../Shared/bin/Debug/$TargetFramework/$ProjectName.Shared.Oqtane.pdb" "../../oqtane.framework/Oqtane.Server/bin/Debug/$TargetFramework/"
-mkdir -p "../../oqtane.framework/Oqtane.Server/wwwroot/_content/$ProjectName/"
+
cp -rf "../Server/wwwroot/"* "../../oqtane.framework/Oqtane.Server/wwwroot/_content/$ProjectName/"
diff --git a/QuestPDF_Integration.md b/QuestPDF_Integration.md
new file mode 100644
index 0000000..3a1d7f1
--- /dev/null
+++ b/QuestPDF_Integration.md
@@ -0,0 +1,85 @@
+# PDF-Design: Modern Glassmorphism Layout
+
+## Design-Konzept
+
+Das PDF-Layout verwendet ein **modernes Glassmorphism-Design** — inspiriert von Apple Keynote-Slides und Behance Case Studies. Jede Seite besteht aus einem vollflächigen Hintergrundbild mit zwei **schwebenden Glass-Cards** (Titel oben, Beschreibung unten).
+
+## Aufbau (Layer-System)
+
+```
+┌─────────────────────────────────┐
+│ LAYER 1: Vollbild-Hintergrund │ ← Edge-to-Edge Bild
+│ │
+│ LAYER 2: Oberes Dark-Overlay │ ← Kontrast für Titelkarte
+│ ┌───────────────────────────┐ │
+│ │ ███ TITELKARTE (Glass) ███ │ ← Name + Jahr
+│ └───────────────────────────┘ │
+│ │
+│ (Bild sichtbar) │
+│ │
+│ LAYER 3: Unteres Dark-Overlay │ ← Kontrast für Beschreibung
+│ ┌───────────────────────────┐ │
+│ │ ███ BESCHREIBUNG (Glass) ██ │ ← Bio-Text
+│ └───────────────────────────┘ │
+└─────────────────────────────────┘
+```
+
+## Glassmorphism-Technik (Build-Safe)
+
+Jede Karte nutzt die `Decoration` API mit mehrfachen `.Before()` Layern:
+
+```csharp
+.Decoration(card =>
+{
+ // 1. Weicher Schatten (sehr dezent, außen)
+ card.Before()
+ .Border(4f).BorderColor("#18000000")
+ .CornerRadius(20);
+
+ // 2. Lichtkante (innerer Glas-Rim)
+ card.Before()
+ .Border(1f).BorderColor("#44FFFFFF")
+ .CornerRadius(20);
+
+ // 3. Halbtransparenter dunkler Hintergrund
+ card.Before()
+ .Background("#C8181828")
+ .CornerRadius(20);
+
+ // 4. Inhalt (Text)
+ card.Content()
+ .PaddingVertical(28)
+ .PaddingHorizontal(36)
+ .Column(inner => { /* ... */ });
+});
+```
+
+## Design-Details
+
+| Element | Stil |
+|---------|------|
+| **Name** | 36pt, ExtraBold, Uppercase, Weiß, Letter-Spacing 0.5 |
+| **Trennlinie** | 1.5pt, halbtransparent Weiß (#55FFFFFF) |
+| **Jahrgang** | 15pt, gedämpft (#CCFFFFFF), Letter-Spacing 1.5 |
+| **Beschreibungs-Header** | 14pt, SemiBold, gedämpft, Letter-Spacing 2 |
+| **Beschreibungstext** | 11pt, 1.5 Zeilenhöhe, leicht gedämpft (#E8FFFFFF) |
+| **Titelkarte Radius** | 20px |
+| **Beschreibungskarte Radius** | 16px |
+| **Overlay oben** | 220pt Höhe, #99000000 |
+| **Overlay unten** | 280pt Höhe, #AA000000 |
+| **Seiten-Padding** | 40pt rundum |
+
+## Vorteile dieses Ansatzes
+
+- ✅ **100% Build-Safe**: Keine SkiaSharp-Abhängigkeit, rein QuestPDF Fluent API
+- ✅ **Stabil auf jedem Bild**: Kontrast-Overlays garantieren Lesbarkeit
+- ✅ **Visuell hochwertig**: Glasskarte + Tiefe + Typografie-Hierarchie
+- ✅ **Strukturell unverändert**: Name oben, Beschreibung unten, Bild vollflächig
+
+## Build-Befehl
+
+```bash
+dotnet build Server/SZUAbsolventenverein.Module.HallOfFame.Server.csproj
+```
+
+Letzte erfolgreiche Kompilierung: 19. Februar 2026 — 0 Fehler, 0 Warnungen.
diff --git a/Server/Controllers/HallOfFameController.cs b/Server/Controllers/HallOfFameController.cs
index fb95661..c6aa6dc 100644
--- a/Server/Controllers/HallOfFameController.cs
+++ b/Server/Controllers/HallOfFameController.cs
@@ -1,3 +1,4 @@
+using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using System.Collections.Generic;
@@ -10,6 +11,8 @@ using SZUAbsolventenverein.Module.HallOfFame.Services;
using Oqtane.Controllers;
using System.Net;
using System.Threading.Tasks;
+using System.IO;
+using Microsoft.AspNetCore.Hosting;
namespace SZUAbsolventenverein.Module.HallOfFame.Controllers
{
@@ -17,10 +20,12 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Controllers
public class HallOfFameController : ModuleControllerBase
{
private readonly IHallOfFameService _HallOfFameService;
+ private readonly IWebHostEnvironment _environment;
- public HallOfFameController(IHallOfFameService HallOfFameService, ILogManager logger, IHttpContextAccessor accessor) : base(logger, accessor)
+ public HallOfFameController(IHallOfFameService HallOfFameService, ILogManager logger, IHttpContextAccessor accessor, IWebHostEnvironment environment) : base(logger, accessor)
{
_HallOfFameService = HallOfFameService;
+ _environment = environment;
}
// GET: api/?moduleid=x
@@ -33,9 +38,10 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Controllers
if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId))
{
var list = await _HallOfFameService.GetHallOfFamesAsync(ModuleId);
- // Filter: Show only Published unless user has Edit permissions (simplified check for now, can be expanded)
- // For now, let's filter in memory or service. The requirement says: "Hauptseite zeigt nur Published".
- // We will filter here.
+ if (User.IsInRole(RoleNames.Admin) || User.IsInRole(RoleNames.Host))
+ {
+ return list;
+ }
return list.Where(item => item.Status == "Published");
}
else
@@ -138,7 +144,47 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Controllers
return HallOfFame;
}
- // DELETE api//5
+ // PUT api//report/5
+ [HttpPut("report/{id}")]
+ [Authorize(Policy = PolicyNames.ViewModule)]
+ public async Task Report(int id, [FromQuery] string reason)
+ {
+ Models.HallOfFame HallOfFame = await _HallOfFameService.GetHallOfFameAsync(id, -1);
+ if (HallOfFame != null && IsAuthorizedEntityId(EntityNames.Module, HallOfFame.ModuleId))
+ {
+ await _HallOfFameService.ReportAsync(id, HallOfFame.ModuleId, reason);
+ }
+ }
+
+ // GET api//reports/5?moduleid=x
+ [HttpGet("reports/{id}")]
+ [Authorize(Policy = PolicyNames.EditModule)]
+ public async Task> GetReports(int id, string moduleid)
+ {
+ int ModuleId;
+ if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId))
+ {
+ return await _HallOfFameService.GetHallOfFameReportsAsync(id, ModuleId);
+ }
+ else
+ {
+ _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized HallOfFame GetReports Attempt {HallOfFameId} {ModuleId}", id, moduleid);
+ HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
+ return null;
+ }
+ }
+
+ // DELETE api//report/5/x
+ [HttpDelete("report/{id}/{moduleid}")]
+ [Authorize(Policy = PolicyNames.EditModule)]
+ public async Task DeleteReport(int id, int moduleid)
+ {
+ if (IsAuthorizedEntityId(EntityNames.Module, moduleid))
+ {
+ await _HallOfFameService.DeleteHallOfFameReportAsync(id, moduleid);
+ }
+ }
+
[HttpDelete("{id}/{moduleid}")]
[Authorize(Policy = PolicyNames.EditModule)]
public async Task Delete(int id, int moduleid)
@@ -154,5 +200,33 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Controllers
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
+ [HttpPost("upload")]
+ [Authorize(Policy = PolicyNames.EditModule)]
+ public async Task Upload(IFormFile file)
+ {
+ if (file == null || file.Length == 0) return BadRequest("Keine Datei ausgewählt.");
+
+ var extension = Path.GetExtension(file.FileName).ToLower();
+ if (extension != ".jpg" && extension != ".jpeg" && extension != ".png")
+ {
+ return BadRequest("Nur JPG und PNG Dateien sind erlaubt.");
+ }
+
+ var folder = Path.Combine(_environment.WebRootPath, "Content", "HallOfFame");
+ if (!Directory.Exists(folder))
+ {
+ Directory.CreateDirectory(folder);
+ }
+
+ var fileName = Guid.NewGuid().ToString() + extension;
+ var path = Path.Combine(folder, fileName);
+
+ using (var stream = new FileStream(path, FileMode.Create))
+ {
+ await file.CopyToAsync(stream);
+ }
+
+ return Ok(new { url = "/Content/HallOfFame/" + fileName });
+ }
}
}
diff --git a/Server/Controllers/HallOfFamePDFController.cs b/Server/Controllers/HallOfFamePDFController.cs
new file mode 100644
index 0000000..217152d
--- /dev/null
+++ b/Server/Controllers/HallOfFamePDFController.cs
@@ -0,0 +1,188 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Hosting;
+using Oqtane.Shared;
+using Oqtane.Enums;
+using Oqtane.Infrastructure;
+using Oqtane.Controllers;
+using SZUAbsolventenverein.Module.HallOfFame.Services;
+using System.Linq;
+using System.Threading.Tasks;
+using QuestPDF.Fluent;
+using QuestPDF.Helpers;
+using QuestPDF.Infrastructure;
+
+namespace SZUAbsolventenverein.Module.HallOfFame.Controllers
+{
+ [Route(ControllerRoutes.ApiRoute)]
+ public class HallOfFamePdfController : ModuleControllerBase
+ {
+ private readonly IHallOfFameService _hallOfFameService;
+ private readonly IWebHostEnvironment _environment;
+
+ public HallOfFamePdfController(IHallOfFameService hallOfFameService, ILogManager logger, IHttpContextAccessor accessor, IWebHostEnvironment environment) : base(logger, accessor)
+ {
+ _hallOfFameService = hallOfFameService;
+ _environment = environment;
+ }
+
+ // GET: api/?moduleid=x&download=true/false
+ [HttpGet]
+ [Authorize(Policy = PolicyNames.ViewModule)]
+ public async Task Get(string moduleid, bool download = false)
+ {
+ int ModuleId;
+ if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId))
+ {
+ var entries = await _hallOfFameService.GetHallOfFamesAsync(ModuleId);
+ var publishedEntries = entries.Where(e => e.Status == "Published").ToList();
+
+ var document = Document.Create(container =>
+ {
+ foreach (var entry in publishedEntries)
+ {
+ // Bild laden falls vorhanden
+ byte[] imageBytes = null;
+ if (!string.IsNullOrEmpty(entry.Image))
+ {
+ try
+ {
+ var fullImagePath = System.IO.Path.Combine(
+ _environment.WebRootPath, entry.Image.TrimStart('/'));
+ if (System.IO.File.Exists(fullImagePath))
+ imageBytes = System.IO.File.ReadAllBytes(fullImagePath);
+ }
+ catch { }
+ }
+
+ container.Page(page =>
+ {
+ page.Size(PageSizes.A4);
+ page.Margin(0);
+
+ page.Content().Layers(layers =>
+ {
+ // ── Hintergrundbild (Edge-to-Edge) ──
+ if (imageBytes != null)
+ {
+ layers.Layer().Image(imageBytes).FitUnproportionally();
+ }
+ else
+ {
+ layers.Layer().Background("#1A1A2E");
+ }
+
+ // ── Inhalt (PrimaryLayer) ──
+ layers.PrimaryLayer()
+ .Padding(40)
+ .Column(column =>
+ {
+ // ═══ TITELKARTE (oben) ═══
+ column.Item()
+ .Border(5f).BorderColor("#20000000")
+ .CornerRadius(24)
+ .Border(3f).BorderColor("#33000000")
+ .CornerRadius(22)
+ .Border(1f).BorderColor("#44FFFFFF")
+ .Background("#CC1A1A2E")
+ .CornerRadius(20)
+ .PaddingVertical(28)
+ .PaddingHorizontal(36)
+ .Column(inner =>
+ {
+ // Name: groß, dominant, Uppercase
+ inner.Item()
+ .PaddingBottom(6)
+ .Text(entry.Name.ToUpper())
+ .FontSize(36)
+ .ExtraBold()
+ .FontColor(Colors.White)
+ .LetterSpacing(0.5f);
+
+ // Trennlinie
+ inner.Item()
+ .PaddingVertical(8)
+ .Height(1.5f)
+ .Background("#55FFFFFF");
+
+ // Jahr: sekundär, elegant
+ inner.Item()
+ .PaddingTop(4)
+ .Text($"Jahrgang {entry.Year}")
+ .FontSize(15)
+ .FontColor("#CCFFFFFF")
+ .LetterSpacing(1.5f);
+ });
+
+ // ═══ BESCHREIBUNGSKARTE (unten) ═══
+ var description = entry.Description ?? "";
+ var sections = description.Split('\n')
+ .Select(s => s.Trim())
+ .Where(s => !string.IsNullOrEmpty(s))
+ .ToArray();
+
+ column.Item().ExtendVertical().AlignBottom()
+ .Border(5f).BorderColor("#20000000")
+ .CornerRadius(20)
+ .Border(3f).BorderColor("#33000000")
+ .CornerRadius(18)
+ .Border(1f).BorderColor("#33FFFFFF")
+ .Background("#CC1A1A2E")
+ .CornerRadius(16)
+ .PaddingVertical(24)
+ .PaddingHorizontal(32)
+ .Column(descColumn =>
+ {
+ if (sections.Length > 0)
+ {
+ // Überschrift
+ descColumn.Item()
+ .PaddingBottom(12)
+ .Text("Beschreibung")
+ .FontSize(14)
+ .SemiBold()
+ .FontColor("#B0FFFFFF")
+ .LetterSpacing(2f);
+
+ // Trennlinie
+ descColumn.Item()
+ .PaddingBottom(14)
+ .Height(1f)
+ .Background("#33FFFFFF");
+
+ // Text
+ foreach (var line in sections)
+ {
+ descColumn.Item()
+ .PaddingBottom(8)
+ .Text(line)
+ .FontSize(11)
+ .FontColor("#E8FFFFFF")
+ .LineHeight(1.5f);
+ }
+ }
+ });
+ });
+ });
+ });
+ }
+ });
+
+ byte[] pdfBytes = document.GeneratePdf();
+
+ if (download)
+ {
+ return File(pdfBytes, "application/pdf", "HallOfFame.pdf");
+ }
+ // Inline: PDF wird im Browser angezeigt (Vorschau)
+ return File(pdfBytes, "application/pdf");
+ }
+ else
+ {
+ _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized HallOfFame PDF Get Attempt {ModuleId}", moduleid);
+ return Forbid();
+ }
+ }
+ }
+}
diff --git a/Server/Migrations/01000001_AddHallOfFameColumns.cs b/Server/Migrations/01000001_AddHallOfFameColumns.cs
deleted file mode 100644
index 3956fc5..0000000
--- a/Server/Migrations/01000001_AddHallOfFameColumns.cs
+++ /dev/null
@@ -1,83 +0,0 @@
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Migrations;
-using Oqtane.Databases.Interfaces;
-using Oqtane.Migrations;
-using SZUAbsolventenverein.Module.HallOfFame.Migrations.EntityBuilders;
-using SZUAbsolventenverein.Module.HallOfFame.Repository;
-
-namespace SZUAbsolventenverein.Module.HallOfFame.Migrations
-{
- [DbContext(typeof(HallOfFameContext))]
- [Migration("SZUAbsolventenverein.Module.HallOfFame.01.00.00.01")]
- public class AddHallOfFameColumns : MultiDatabaseMigration
- {
- public AddHallOfFameColumns(IDatabase database) : base(database)
- {
- }
-
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- var entityBuilder = new HallOfFameEntityBuilder(migrationBuilder, ActiveDatabase);
-
- migrationBuilder.AddColumn(
- name: "Year",
- table: "SZUAbsolventenvereinHallOfFame",
- nullable: false,
- defaultValue: 0);
-
- migrationBuilder.AddColumn(
- name: "Description",
- table: "SZUAbsolventenvereinHallOfFame",
- nullable: true);
-
- migrationBuilder.AddColumn(
- name: "Image",
- table: "SZUAbsolventenvereinHallOfFame",
- nullable: true);
-
- migrationBuilder.AddColumn(
- name: "Link",
- table: "SZUAbsolventenvereinHallOfFame",
- nullable: true);
-
- migrationBuilder.AddColumn(
- name: "Status",
- table: "SZUAbsolventenvereinHallOfFame",
- maxLength: 50,
- nullable: true);
-
- migrationBuilder.AddColumn(
- name: "UserId",
- table: "SZUAbsolventenvereinHallOfFame",
- nullable: false,
- defaultValue: 0);
- }
-
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.DropColumn(
- name: "Year",
- table: "SZUAbsolventenvereinHallOfFame");
-
- migrationBuilder.DropColumn(
- name: "Description",
- table: "SZUAbsolventenvereinHallOfFame");
-
- migrationBuilder.DropColumn(
- name: "Image",
- table: "SZUAbsolventenvereinHallOfFame");
-
- migrationBuilder.DropColumn(
- name: "Link",
- table: "SZUAbsolventenvereinHallOfFame");
-
- migrationBuilder.DropColumn(
- name: "Status",
- table: "SZUAbsolventenvereinHallOfFame");
-
- migrationBuilder.DropColumn(
- name: "UserId",
- table: "SZUAbsolventenvereinHallOfFame");
- }
- }
-}
diff --git a/Server/Migrations/01000002_AddReportingColumns.cs b/Server/Migrations/01000002_AddReportingColumns.cs
new file mode 100644
index 0000000..65e918e
--- /dev/null
+++ b/Server/Migrations/01000002_AddReportingColumns.cs
@@ -0,0 +1,43 @@
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Oqtane.Databases.Interfaces;
+using Oqtane.Migrations;
+using SZUAbsolventenverein.Module.HallOfFame.Migrations.EntityBuilders;
+using SZUAbsolventenverein.Module.HallOfFame.Repository;
+
+namespace SZUAbsolventenverein.Module.HallOfFame.Migrations
+{
+ [DbContext(typeof(HallOfFameContext))]
+ [Migration("SZUAbsolventenverein.Module.HallOfFame.01.00.00.02")]
+ public class AddReportingColumns : MultiDatabaseMigration
+ {
+ public AddReportingColumns(IDatabase database) : base(database)
+ {
+ }
+
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "IsReported",
+ table: "SZUAbsolventenvereinHallOfFame",
+ nullable: false,
+ defaultValue: false);
+
+ migrationBuilder.AddColumn(
+ name: "ReportReason",
+ table: "SZUAbsolventenvereinHallOfFame",
+ nullable: true);
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "IsReported",
+ table: "SZUAbsolventenvereinHallOfFame");
+
+ migrationBuilder.DropColumn(
+ name: "ReportReason",
+ table: "SZUAbsolventenvereinHallOfFame");
+ }
+ }
+}
diff --git a/Server/Migrations/01000003_AddReportTable.cs b/Server/Migrations/01000003_AddReportTable.cs
new file mode 100644
index 0000000..def5ba8
--- /dev/null
+++ b/Server/Migrations/01000003_AddReportTable.cs
@@ -0,0 +1,30 @@
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Oqtane.Databases.Interfaces;
+using Oqtane.Migrations;
+using SZUAbsolventenverein.Module.HallOfFame.Migrations.EntityBuilders;
+using SZUAbsolventenverein.Module.HallOfFame.Repository;
+
+namespace SZUAbsolventenverein.Module.HallOfFame.Migrations
+{
+ [DbContext(typeof(HallOfFameContext))]
+ [Migration("SZUAbsolventenverein.Module.HallOfFame.01.00.00.03")]
+ public class AddReportTable : MultiDatabaseMigration
+ {
+ public AddReportTable(IDatabase database) : base(database)
+ {
+ }
+
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ var entityBuilder = new HallOfFameReportEntityBuilder(migrationBuilder, ActiveDatabase);
+ entityBuilder.Create();
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ var entityBuilder = new HallOfFameReportEntityBuilder(migrationBuilder, ActiveDatabase);
+ entityBuilder.Drop();
+ }
+ }
+}
diff --git a/Server/Migrations/EntityBuilders/HallOfFameEntityBuilder.cs b/Server/Migrations/EntityBuilders/HallOfFameEntityBuilder.cs
index b3d6d56..9226896 100644
--- a/Server/Migrations/EntityBuilders/HallOfFameEntityBuilder.cs
+++ b/Server/Migrations/EntityBuilders/HallOfFameEntityBuilder.cs
@@ -44,6 +44,8 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Migrations.EntityBuilders
public OperationBuilder Link { get; set; }
public OperationBuilder Status { get; set; }
public OperationBuilder UserId { get; set; }
+ public OperationBuilder IsReported { get; set; }
+ public OperationBuilder ReportReason { get; set; }
}
}
diff --git a/Server/Migrations/EntityBuilders/HallOfFameReportEntityBuilder.cs b/Server/Migrations/EntityBuilders/HallOfFameReportEntityBuilder.cs
new file mode 100644
index 0000000..3489c31
--- /dev/null
+++ b/Server/Migrations/EntityBuilders/HallOfFameReportEntityBuilder.cs
@@ -0,0 +1,36 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Migrations.Operations;
+using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders;
+using Oqtane.Databases.Interfaces;
+using Oqtane.Migrations;
+using Oqtane.Migrations.EntityBuilders;
+
+namespace SZUAbsolventenverein.Module.HallOfFame.Migrations.EntityBuilders
+{
+ public class HallOfFameReportEntityBuilder : AuditableBaseEntityBuilder
+ {
+ private const string _entityTableName = "SZUAbsolventenvereinHallOfFameReport";
+ private readonly PrimaryKey _primaryKey = new("PK_SZUAbsolventenvereinHallOfFameReport", x => x.HallOfFameReportId);
+ private readonly ForeignKey _hallOfFameForeignKey = new("FK_SZUAbsolventenvereinHallOfFameReport_HallOfFame", x => x.HallOfFameId, "SZUAbsolventenvereinHallOfFame", "HallOfFameId", ReferentialAction.Cascade);
+
+ public HallOfFameReportEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
+ {
+ EntityTableName = _entityTableName;
+ PrimaryKey = _primaryKey;
+ ForeignKeys.Add(_hallOfFameForeignKey);
+ }
+
+ protected override HallOfFameReportEntityBuilder BuildTable(ColumnsBuilder table)
+ {
+ HallOfFameReportId = AddAutoIncrementColumn(table, "HallOfFameReportId");
+ HallOfFameId = AddIntegerColumn(table, "HallOfFameId");
+ Reason = AddMaxStringColumn(table, "Reason");
+ AddAuditableColumns(table);
+ return this;
+ }
+
+ public OperationBuilder HallOfFameReportId { get; set; }
+ public OperationBuilder HallOfFameId { get; set; }
+ public OperationBuilder Reason { get; set; }
+ }
+}
diff --git a/Server/Repository/HallOfFameContext.cs b/Server/Repository/HallOfFameContext.cs
index 88d3d62..e0a6612 100644
--- a/Server/Repository/HallOfFameContext.cs
+++ b/Server/Repository/HallOfFameContext.cs
@@ -10,6 +10,7 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Repository
public class HallOfFameContext : DBContextBase, ITransientService, IMultiDatabase
{
public virtual DbSet HallOfFame { get; set; }
+ public virtual DbSet HallOfFameReport { get; set; }
public HallOfFameContext(IDBContextDependencies DBContextDependencies) : base(DBContextDependencies)
{
@@ -21,6 +22,7 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Repository
base.OnModelCreating(builder);
builder.Entity().ToTable(ActiveDatabase.RewriteName("SZUAbsolventenvereinHallOfFame"));
+ builder.Entity().ToTable(ActiveDatabase.RewriteName("SZUAbsolventenvereinHallOfFameReport"));
}
}
}
diff --git a/Server/Repository/HallOfFameRepository.cs b/Server/Repository/HallOfFameRepository.cs
index 5f50af0..32dd537 100644
--- a/Server/Repository/HallOfFameRepository.cs
+++ b/Server/Repository/HallOfFameRepository.cs
@@ -13,6 +13,11 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Repository
Models.HallOfFame AddHallOfFame(Models.HallOfFame HallOfFame);
Models.HallOfFame UpdateHallOfFame(Models.HallOfFame HallOfFame);
void DeleteHallOfFame(int HallOfFameId);
+
+ IEnumerable GetHallOfFameReports(int HallOfFameId);
+ Models.HallOfFameReport GetHallOfFameReport(int HallOfFameReportId);
+ Models.HallOfFameReport AddHallOfFameReport(Models.HallOfFameReport HallOfFameReport);
+ void DeleteHallOfFameReport(int HallOfFameReportId);
}
public class HallOfFameRepository : IHallOfFameRepository, ITransientService
@@ -27,7 +32,14 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Repository
public IEnumerable GetHallOfFames(int ModuleId)
{
using var db = _factory.CreateDbContext();
- return db.HallOfFame.Where(item => item.ModuleId == ModuleId).ToList();
+ var items = db.HallOfFame.Where(item => item.ModuleId == ModuleId)
+ .OrderByDescending(item => item.CreatedOn)
+ .ToList();
+ foreach (var item in items)
+ {
+ item.Description = item.Description?.Replace("\t", " ");
+ }
+ return items;
}
public Models.HallOfFame GetHallOfFame(int HallOfFameId)
@@ -38,19 +50,26 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Repository
public Models.HallOfFame GetHallOfFame(int HallOfFameId, bool tracking)
{
using var db = _factory.CreateDbContext();
+ Models.HallOfFame item;
if (tracking)
{
- return db.HallOfFame.Find(HallOfFameId);
+ item = db.HallOfFame.Find(HallOfFameId);
}
else
{
- return db.HallOfFame.AsNoTracking().FirstOrDefault(item => item.HallOfFameId == HallOfFameId);
+ item = db.HallOfFame.AsNoTracking().FirstOrDefault(i => i.HallOfFameId == HallOfFameId);
}
+ if (item != null)
+ {
+ item.Description = item.Description?.Replace("\t", " ");
+ }
+ return item;
}
public Models.HallOfFame AddHallOfFame(Models.HallOfFame HallOfFame)
{
using var db = _factory.CreateDbContext();
+ HallOfFame.Description = HallOfFame.Description?.Replace("\t", " ");
db.HallOfFame.Add(HallOfFame);
db.SaveChanges();
return HallOfFame;
@@ -59,17 +78,72 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Repository
public Models.HallOfFame UpdateHallOfFame(Models.HallOfFame HallOfFame)
{
using var db = _factory.CreateDbContext();
+ HallOfFame.Description = HallOfFame.Description?.Replace("\t", " ");
db.Entry(HallOfFame).State = EntityState.Modified;
db.SaveChanges();
return HallOfFame;
}
public void DeleteHallOfFame(int HallOfFameId)
+ {
+ // First transaction: Delete all associated reports
+ using (var db = _factory.CreateDbContext())
+ {
+ var reports = db.HallOfFameReport.Where(item => item.HallOfFameId == HallOfFameId).ToList();
+ if (reports.Any())
+ {
+ db.HallOfFameReport.RemoveRange(reports);
+ db.SaveChanges();
+ }
+ }
+
+ // Second transaction: Delete the HallOfFame entry itself
+ using (var db = _factory.CreateDbContext())
+ {
+ var hallOfFame = db.HallOfFame.Find(HallOfFameId);
+ if (hallOfFame != null)
+ {
+ db.HallOfFame.Remove(hallOfFame);
+ db.SaveChanges();
+ }
+ }
+ }
+
+ public IEnumerable GetHallOfFameReports(int HallOfFameId)
{
using var db = _factory.CreateDbContext();
- Models.HallOfFame HallOfFame = db.HallOfFame.Find(HallOfFameId);
- db.HallOfFame.Remove(HallOfFame);
+ return db.HallOfFameReport.Where(item => item.HallOfFameId == HallOfFameId)
+ .OrderByDescending(item => item.CreatedOn)
+ .ToList();
+ }
+
+ public Models.HallOfFameReport GetHallOfFameReport(int HallOfFameReportId)
+ {
+ using var db = _factory.CreateDbContext();
+ return db.HallOfFameReport.Find(HallOfFameReportId);
+ }
+
+ public Models.HallOfFameReport AddHallOfFameReport(Models.HallOfFameReport HallOfFameReport)
+ {
+ using var db = _factory.CreateDbContext();
+ db.HallOfFameReport.Add(HallOfFameReport);
db.SaveChanges();
+ return HallOfFameReport;
+ }
+
+ public void DeleteHallOfFameReport(int HallOfFameReportId)
+ {
+ using var db = _factory.CreateDbContext();
+
+ // Clear any tracked entities to avoid conflicts
+ db.ChangeTracker.Clear();
+
+ Models.HallOfFameReport HallOfFameReport = db.HallOfFameReport.Find(HallOfFameReportId);
+ if (HallOfFameReport != null)
+ {
+ db.HallOfFameReport.Remove(HallOfFameReport);
+ db.SaveChanges();
+ }
}
}
}
diff --git a/Server/SZUAbsolventenverein.Module.HallOfFame.Server.csproj b/Server/SZUAbsolventenverein.Module.HallOfFame.Server.csproj
index 330dbdd..8568994 100644
--- a/Server/SZUAbsolventenverein.Module.HallOfFame.Server.csproj
+++ b/Server/SZUAbsolventenverein.Module.HallOfFame.Server.csproj
@@ -23,6 +23,7 @@
+
@@ -34,4 +35,16 @@
..\..\oqtane.framework\Oqtane.Server\bin\Debug\net10.0\Oqtane.Server.dll
..\..\oqtane.framework\Oqtane.Server\bin\Debug\net10.0\Oqtane.Shared.dll
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Server/Services/HallOfFameService.cs b/Server/Services/HallOfFameService.cs
index 6d8845a..db8d1b6 100644
--- a/Server/Services/HallOfFameService.cs
+++ b/Server/Services/HallOfFameService.cs
@@ -2,12 +2,16 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
+using System.Net.Http;
using Oqtane.Enums;
using Oqtane.Infrastructure;
using Oqtane.Models;
using Oqtane.Security;
using Oqtane.Shared;
using SZUAbsolventenverein.Module.HallOfFame.Repository;
+using Microsoft.AspNetCore.Hosting;
+using System.IO;
+using System;
namespace SZUAbsolventenverein.Module.HallOfFame.Services
{
@@ -18,14 +22,16 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Services
private readonly ILogManager _logger;
private readonly IHttpContextAccessor _accessor;
private readonly Alias _alias;
+ private readonly IWebHostEnvironment _environment;
- public ServerHallOfFameService(IHallOfFameRepository HallOfFameRepository, IUserPermissions userPermissions, ITenantManager tenantManager, ILogManager logger, IHttpContextAccessor accessor)
+ public ServerHallOfFameService(IHallOfFameRepository HallOfFameRepository, IUserPermissions userPermissions, ITenantManager tenantManager, ILogManager logger, IHttpContextAccessor accessor, IWebHostEnvironment environment)
{
_HallOfFameRepository = HallOfFameRepository;
_userPermissions = userPermissions;
_logger = logger;
_accessor = accessor;
_alias = tenantManager.GetAlias();
+ _environment = environment;
}
public Task> GetHallOfFamesAsync(int ModuleId)
@@ -111,5 +117,104 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Services
}
return Task.CompletedTask;
}
+
+ public Task ReportAsync(int HallOfFameId, int ModuleId, string reason)
+ {
+ if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.View))
+ {
+ var report = new Models.HallOfFameReport
+ {
+ HallOfFameId = HallOfFameId,
+ Reason = reason
+ };
+ _HallOfFameRepository.AddHallOfFameReport(report);
+
+ var hallOfFame = _HallOfFameRepository.GetHallOfFame(HallOfFameId);
+ if (hallOfFame != null && !hallOfFame.IsReported)
+ {
+ hallOfFame.IsReported = true;
+ _HallOfFameRepository.UpdateHallOfFame(hallOfFame);
+ }
+ _logger.Log(LogLevel.Information, this, LogFunction.Update, "HallOfFame Reported {HallOfFameId}", HallOfFameId);
+ }
+ else
+ {
+ _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized HallOfFame Report Attempt {HallOfFameId} {ModuleId}", HallOfFameId, ModuleId);
+ }
+ return Task.CompletedTask;
+ }
+
+ public Task> GetHallOfFameReportsAsync(int HallOfFameId, int ModuleId)
+ {
+ if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.Edit))
+ {
+ return Task.FromResult(_HallOfFameRepository.GetHallOfFameReports(HallOfFameId).ToList());
+ }
+ else
+ {
+ _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized HallOfFame Get Reports Attempt {HallOfFameId} {ModuleId}", HallOfFameId, ModuleId);
+ return null;
+ }
+ }
+
+ public Task DeleteHallOfFameReportAsync(int HallOfFameReportId, int ModuleId)
+ {
+ if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.Edit))
+ {
+ var report = _HallOfFameRepository.GetHallOfFameReport(HallOfFameReportId);
+ if (report != null)
+ {
+ int hallOfFameId = report.HallOfFameId;
+ _HallOfFameRepository.DeleteHallOfFameReport(HallOfFameReportId);
+
+ // Check if there are any reports left for this entry
+ var remainingReports = _HallOfFameRepository.GetHallOfFameReports(hallOfFameId);
+ if (!remainingReports.Any())
+ {
+ var hallOfFame = _HallOfFameRepository.GetHallOfFame(hallOfFameId);
+ if (hallOfFame != null)
+ {
+ hallOfFame.IsReported = false;
+ _HallOfFameRepository.UpdateHallOfFame(hallOfFame);
+ }
+ }
+ }
+ _logger.Log(LogLevel.Information, this, LogFunction.Delete, "HallOfFame Report Deleted {HallOfFameReportId}", HallOfFameReportId);
+ }
+ else
+ {
+ _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized HallOfFame Delete Report Attempt {HallOfFameReportId} {ModuleId}", HallOfFameReportId, ModuleId);
+ }
+ return Task.CompletedTask;
+ }
+ public async Task UploadFileAsync(Stream stream, string fileName, int ModuleId)
+ {
+ if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.Edit))
+ {
+ var extension = Path.GetExtension(fileName).ToLower();
+
+ if (extension != ".jpg" && extension != ".jpeg" && extension != ".png")
+ {
+ return null;
+ }
+
+ var folder = Path.Combine(_environment.WebRootPath, "Content", "HallOfFame");
+ if (!Directory.Exists(folder))
+ {
+ Directory.CreateDirectory(folder);
+ }
+
+ var newFileName = Guid.NewGuid().ToString() + extension;
+ var path = Path.Combine(folder, newFileName);
+
+ using (var fileStream = new FileStream(path, FileMode.Create))
+ {
+ await stream.CopyToAsync(fileStream);
+ }
+
+ return "/Content/HallOfFame/" + newFileName;
+ }
+ return null;
+ }
}
}
diff --git a/Server/Startup/ServerStartup.cs b/Server/Startup/ServerStartup.cs
index 73882f4..9a0d2c0 100644
--- a/Server/Startup/ServerStartup.cs
+++ b/Server/Startup/ServerStartup.cs
@@ -1,4 +1,4 @@
-using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Oqtane.Infrastructure;
@@ -21,6 +21,9 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Startup
public void ConfigureServices(IServiceCollection services)
{
+ // QuestPDF Lizenz konfigurieren
+ QuestPDF.Settings.License = QuestPDF.Infrastructure.LicenseType.Community;
+
services.AddTransient();
services.AddDbContextFactory(opt => { }, ServiceLifetime.Transient);
}
diff --git a/Server/wwwroot/Module.css b/Server/wwwroot/Module.css
index 0856a26..2f6c06b 100644
--- a/Server/wwwroot/Module.css
+++ b/Server/wwwroot/Module.css
@@ -1 +1,17 @@
-/* Module Custom Styles */
\ No newline at end of file
+/* Module Custom Styles */
+
+.hof-description-container {
+ min-height: 120px;
+ /* Adjust this value based on the desired card size */
+ margin-bottom: 1rem;
+}
+
+.hof-description-line {
+ display: block;
+ padding-left: 1.1em;
+ text-indent: -1.1em;
+ margin-bottom: 0.2rem;
+ line-height: 1.5;
+ word-break: break-word;
+ text-align: left;
+}
\ No newline at end of file
diff --git a/Shared/Models/HallOfFame.cs b/Shared/Models/HallOfFame.cs
index 4219d5b..7678226 100644
--- a/Shared/Models/HallOfFame.cs
+++ b/Shared/Models/HallOfFame.cs
@@ -19,6 +19,8 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Models
public string Link { get; set; }
public string Status { get; set; } // "Draft" or "Published"
public int UserId { get; set; } // Owner
+ public bool IsReported { get; set; }
+ public string ReportReason { get; set; }
public string CreatedBy { get; set; }
diff --git a/Shared/Models/HallOfFameReport.cs b/Shared/Models/HallOfFameReport.cs
new file mode 100644
index 0000000..48514e4
--- /dev/null
+++ b/Shared/Models/HallOfFameReport.cs
@@ -0,0 +1,21 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using Oqtane.Models;
+
+namespace SZUAbsolventenverein.Module.HallOfFame.Models
+{
+ [Table("SZUAbsolventenvereinHallOfFameReport")]
+ public class HallOfFameReport : IAuditable
+ {
+ [Key]
+ public int HallOfFameReportId { get; set; }
+ public int HallOfFameId { get; set; }
+ public string Reason { get; set; }
+
+ public string CreatedBy { get; set; }
+ public DateTime CreatedOn { get; set; }
+ public string ModifiedBy { get; set; }
+ public DateTime ModifiedOn { get; set; }
+ }
+}