Feature: Hall of Fame Module Implementation (1.0.1)
- Added Hall of Fame module logic (Models, Controller, Service). - Implemented 'One Entry Per User' and 'Publish/Draft' workflow. - Updated UI to Grid Layout (Index.razor) and Unified Form (Edit.razor). - Added Database Migration 01000001 for new columns. - Bumped version to 1.0.1.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Oqtane.Shared;
|
||||
using Oqtane.Enums;
|
||||
@@ -22,6 +23,7 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Controllers
|
||||
_HallOfFameService = HallOfFameService;
|
||||
}
|
||||
|
||||
// GET: api/<controller>?moduleid=x
|
||||
// GET: api/<controller>?moduleid=x
|
||||
[HttpGet]
|
||||
[Authorize(Policy = PolicyNames.ViewModule)]
|
||||
@@ -30,7 +32,11 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Controllers
|
||||
int ModuleId;
|
||||
if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId))
|
||||
{
|
||||
return await _HallOfFameService.GetHallOfFamesAsync(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.
|
||||
return list.Where(item => item.Status == "Published");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -58,6 +64,25 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
// GET api/<controller>/user/5?moduleid=x
|
||||
[HttpGet("user/{userid}")]
|
||||
[Authorize(Policy = PolicyNames.ViewModule)]
|
||||
public async Task<Models.HallOfFame> GetByUserId(int userid, string moduleid)
|
||||
{
|
||||
int ModuleId;
|
||||
if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId))
|
||||
{
|
||||
var list = await _HallOfFameService.GetHallOfFamesAsync(ModuleId);
|
||||
return list.FirstOrDefault(item => item.UserId == userid);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized HallOfFame GetByUserId Attempt {UserId} {ModuleId}", userid, moduleid);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// POST api/<controller>
|
||||
[HttpPost]
|
||||
[Authorize(Policy = PolicyNames.EditModule)]
|
||||
@@ -65,6 +90,15 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Controllers
|
||||
{
|
||||
if (ModelState.IsValid && IsAuthorizedEntityId(EntityNames.Module, HallOfFame.ModuleId))
|
||||
{
|
||||
// Enforce one entry per user
|
||||
var allEntries = await _HallOfFameService.GetHallOfFamesAsync(HallOfFame.ModuleId);
|
||||
if (allEntries.Any(e => e.UserId == HallOfFame.UserId))
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "User {UserId} already has a Hall of Fame entry.", HallOfFame.UserId);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
|
||||
return null;
|
||||
}
|
||||
|
||||
HallOfFame = await _HallOfFameService.AddHallOfFameAsync(HallOfFame);
|
||||
}
|
||||
else
|
||||
@@ -83,7 +117,17 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Controllers
|
||||
{
|
||||
if (ModelState.IsValid && HallOfFame.HallOfFameId == id && IsAuthorizedEntityId(EntityNames.Module, HallOfFame.ModuleId))
|
||||
{
|
||||
HallOfFame = await _HallOfFameService.UpdateHallOfFameAsync(HallOfFame);
|
||||
var existing = await _HallOfFameService.GetHallOfFameAsync(id, HallOfFame.ModuleId);
|
||||
if (existing != null && existing.UserId == HallOfFame.UserId)
|
||||
{
|
||||
HallOfFame = await _HallOfFameService.UpdateHallOfFameAsync(HallOfFame);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized HallOfFame Put Attempt by User {UserId} for Entry {HallOfFameId}", HallOfFame.UserId, id);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
HallOfFame = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
130
Server/Migrations/01000001_AddHallOfFameColumns.cs
Normal file
130
Server/Migrations/01000001_AddHallOfFameColumns.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
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);
|
||||
|
||||
// Add new columns manually since we are upgrading an existing table
|
||||
// Note: Integer columns are generated as default/nullable depending on definition, Oqtane Helpers handle generic types.
|
||||
// Using logic similar to Oqtane.Migrations.EntityBuilders
|
||||
|
||||
if (ActiveDatabase.Name == "Sqlite") // Sqlite specific or generic
|
||||
{
|
||||
// Generic AddColumn: Table, Name, Type, Nullable
|
||||
// However, Oqtane EntityBuilder usually builds tables.
|
||||
// We will use migrationBuilder directly via helper if possible or standard AddColumn.
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "Year",
|
||||
table: "SZUAbsolventenvereinHallOfFame",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Description",
|
||||
table: "SZUAbsolventenvereinHallOfFame",
|
||||
nullable: true); // Allow nulls initially or empty? MaxString usually nullable in Oqtane context? Let's check.
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Image",
|
||||
table: "SZUAbsolventenvereinHallOfFame",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Link",
|
||||
table: "SZUAbsolventenvereinHallOfFame",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Status",
|
||||
table: "SZUAbsolventenvereinHallOfFame",
|
||||
maxLength: 50,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "UserId",
|
||||
table: "SZUAbsolventenvereinHallOfFame",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// For SQL Server / others, simply use same AddColumn but allow EF Core to handle types
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "Year",
|
||||
table: "SZUAbsolventenvereinHallOfFame",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Description",
|
||||
table: "SZUAbsolventenvereinHallOfFame",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Image",
|
||||
table: "SZUAbsolventenvereinHallOfFame",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Link",
|
||||
table: "SZUAbsolventenvereinHallOfFame",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Status",
|
||||
table: "SZUAbsolventenvereinHallOfFame",
|
||||
maxLength: 50,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,12 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Migrations.EntityBuilders
|
||||
HallOfFameId = AddAutoIncrementColumn(table,"HallOfFameId");
|
||||
ModuleId = AddIntegerColumn(table,"ModuleId");
|
||||
Name = AddMaxStringColumn(table,"Name");
|
||||
Year = AddIntegerColumn(table,"Year");
|
||||
Description = AddMaxStringColumn(table,"Description");
|
||||
Image = AddMaxStringColumn(table,"Image");
|
||||
Link = AddMaxStringColumn(table,"Link");
|
||||
Status = AddStringColumn(table,"Status", 50);
|
||||
UserId = AddIntegerColumn(table,"UserId");
|
||||
AddAuditableColumns(table);
|
||||
return this;
|
||||
}
|
||||
@@ -32,5 +38,12 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Migrations.EntityBuilders
|
||||
public OperationBuilder<AddColumnOperation> HallOfFameId { get; set; }
|
||||
public OperationBuilder<AddColumnOperation> ModuleId { get; set; }
|
||||
public OperationBuilder<AddColumnOperation> Name { get; set; }
|
||||
public OperationBuilder<AddColumnOperation> Year { get; set; }
|
||||
public OperationBuilder<AddColumnOperation> Description { get; set; }
|
||||
public OperationBuilder<AddColumnOperation> Image { get; set; }
|
||||
public OperationBuilder<AddColumnOperation> Link { get; set; }
|
||||
public OperationBuilder<AddColumnOperation> Status { get; set; }
|
||||
public OperationBuilder<AddColumnOperation> UserId { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
|
||||
<Version>1.0.0</Version>
|
||||
<Version>1.0.1</Version>
|
||||
<Product>SZUAbsolventenverein.Module.HallOfFame</Product>
|
||||
<Authors>SZUAbsolventenverein</Authors>
|
||||
<Company>SZUAbsolventenverein</Company>
|
||||
|
||||
@@ -54,6 +54,20 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Services
|
||||
}
|
||||
}
|
||||
|
||||
public Task<Models.HallOfFame> GetHallOfFameByUserIdAsync(int UserId, int ModuleId)
|
||||
{
|
||||
if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.View))
|
||||
{
|
||||
// Assuming Repository doesn't have specific method yet, using LINQ on GetHallOfFames
|
||||
return Task.FromResult(_HallOfFameRepository.GetHallOfFames(ModuleId).FirstOrDefault(item => item.UserId == UserId));
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized HallOfFame GetByUserId Attempt {UserId} {ModuleId}", UserId, ModuleId);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<Models.HallOfFame> AddHallOfFameAsync(Models.HallOfFame HallOfFame)
|
||||
{
|
||||
if (_userPermissions.IsAuthorized(_accessor.HttpContext.User, _alias.SiteId, EntityNames.Module, HallOfFame.ModuleId, PermissionNames.Edit))
|
||||
|
||||
Reference in New Issue
Block a user