Compare commits
10 Commits
b51b37a6e8
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 206b974ab3 | |||
| 97900dddbf | |||
| 3f23b957b0 | |||
| 10388d9a7f | |||
| 0c98c6d86a | |||
| fb5536d29d | |||
| c54a33c159 | |||
| eda0ad794d | |||
| 4a1d334a5c | |||
| a1b5d0371d |
@@ -4,6 +4,7 @@
|
||||
@inherits ModuleBase
|
||||
@inject IEngineerApplicationService ApplicationService
|
||||
@inject NavigationManager NavManager
|
||||
@inject Oqtane.Services.IUserService UserService
|
||||
|
||||
@if (Oqtane.Security.UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) || Oqtane.Security.UserSecurity.IsAuthorized(PageState.User, "Premium Member"))
|
||||
{
|
||||
@@ -16,7 +17,7 @@
|
||||
else if (_applications.Count == 0)
|
||||
{
|
||||
<div class="alert alert-warning">
|
||||
Keine genehmigten Anträge gefunden.
|
||||
Keine Anträge gefunden.
|
||||
</div>
|
||||
}
|
||||
else
|
||||
@@ -27,16 +28,42 @@
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Ingenieur-Antrag</h5>
|
||||
<h6 class="card-subtitle mb-2 text-muted">Benutzer ID: @app.UserId</h6>
|
||||
<p class="card-text">
|
||||
<strong>Datei:</strong> @app.FileId<br/>
|
||||
<strong>Status:</strong> <span class="badge bg-success">Veröffentlicht</span><br/>
|
||||
<strong>Datum:</strong> @(app.ApprovedOn?.ToShortDateString() ?? app.CreatedOn.ToShortDateString())
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<h5 class="card-title mb-0">@(string.IsNullOrEmpty(app.Title) ? "Ingenieur-Antrag" : app.Title)</h5>
|
||||
@if (Oqtane.Security.UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||
{
|
||||
@if (_confirmDeleteId == app.ApplicationId)
|
||||
{
|
||||
<div class="d-flex gap-1">
|
||||
<button class="btn btn-danger btn-sm" @onclick="@(() => DeleteApp(app))" title="Bestätigen">
|
||||
<span class="oi oi-check"></span>
|
||||
</button>
|
||||
<button class="btn btn-secondary btn-sm" @onclick="@(() => _confirmDeleteId = -1)" title="Abbrechen">
|
||||
<span class="oi oi-x"></span>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button class="btn btn-outline-danger btn-sm" @onclick="@(() => _confirmDeleteId = app.ApplicationId)" title="Antrag löschen">
|
||||
<span class="oi oi-trash"></span>
|
||||
</button>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<h6 class="card-subtitle mb-2 text-muted mt-1">von @GetUserName(app.UserId)</h6>
|
||||
@if (!string.IsNullOrEmpty(app.ShortDescription))
|
||||
{
|
||||
<p class="card-text">@app.ShortDescription</p>
|
||||
}
|
||||
<p class="card-text text-muted">
|
||||
<small>
|
||||
<strong>Datum:</strong> @(app.ApprovedOn?.ToShortDateString() ?? app.CreatedOn.ToShortDateString())
|
||||
</small>
|
||||
</p>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-primary btn-sm" @onclick="@(async () => ShowDetail(app))">PDF ansehen</button>
|
||||
<a href="@((NavManager.BaseUri + "api/engineerapplication") + "/download/" + app.ApplicationId + "?moduleid=" + ModuleState.ModuleId)" target="_blank" class="btn btn-outline-secondary btn-sm">Herunterladen</a>
|
||||
<a href="@(PageState.Alias.Path == "" ? "" : "/" + PageState.Alias.Path)/api/file/download/@(app.FileId)/attach" target="_blank" class="btn btn-outline-secondary btn-sm">Herunterladen</a>
|
||||
<button class="btn btn-outline-danger btn-sm" @onclick="@(() => InitReport(app))">Melden</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -59,12 +86,12 @@ else
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Antrags-PDF (@_selectedApp.FileId)</h5>
|
||||
<h5 class="modal-title">Antrags-PDF</h5>
|
||||
<button type="button" class="btn-close" @onclick="@(() => _selectedApp = null)"></button>
|
||||
</div>
|
||||
<div class="modal-body p-0">
|
||||
<div class="ratio ratio-16x9" style="min-height: 500px;">
|
||||
<iframe src="@((NavManager.BaseUri + "api/engineerapplication") + "/download/" + _selectedApp.ApplicationId + "?moduleid=" + ModuleState.ModuleId)" allowfullscreen></iframe>
|
||||
<div style="min-height: 600px; height: 75vh;">
|
||||
<iframe src="@(PageState.Alias.Path == "" ? "" : "/" + PageState.Alias.Path)/api/file/download/@(_selectedApp.FileId)" style="width: 100%; height: 100%; border: none;" allowfullscreen></iframe>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@@ -86,7 +113,7 @@ else
|
||||
<button type="button" class="btn-close" @onclick="@(() => _reportApp = null)"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Bitte geben Sie einen Grund an, warum Sie diesen Antrag melden (Benutzer ID: @_reportApp.UserId, Datei: @_reportApp.FileId).</p>
|
||||
<p>Bitte geben Sie einen Grund an, warum Sie diesen Antrag melden (von @GetUserName(_reportApp.UserId)).</p>
|
||||
<textarea class="form-control" rows="3" @bind="_reportReason" placeholder="Grund..."></textarea>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@@ -106,6 +133,8 @@ else
|
||||
private EngineerApplication _selectedApp;
|
||||
private EngineerApplication _reportApp;
|
||||
private string _reportReason;
|
||||
private Dictionary<int, string> _userNames = new();
|
||||
private int _confirmDeleteId = -1;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
@@ -119,6 +148,20 @@ else
|
||||
if (approved != null) _applications.AddRange(approved);
|
||||
|
||||
_applications = _applications.GroupBy(a => a.ApplicationId).Select(g => g.First()).ToList();
|
||||
|
||||
// Benutzernamen laden
|
||||
foreach (var userId in _applications.Select(a => a.UserId).Distinct())
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = await UserService.GetUserAsync(userId, ModuleState.SiteId);
|
||||
_userNames[userId] = user?.DisplayName ?? $"Benutzer {userId}";
|
||||
}
|
||||
catch
|
||||
{
|
||||
_userNames[userId] = $"Benutzer {userId}";
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -154,4 +197,25 @@ else
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteApp(EngineerApplication app)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ApplicationService.DeleteApplicationAsync(app.ApplicationId, ModuleState.ModuleId);
|
||||
_applications.Remove(app);
|
||||
_confirmDeleteId = -1;
|
||||
AddModuleMessage("Antrag erfolgreich gelöscht.", MessageType.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AddModuleMessage("Fehler beim Löschen: " + ex.Message, MessageType.Error);
|
||||
_confirmDeleteId = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetUserName(int userId)
|
||||
{
|
||||
return _userNames.TryGetValue(userId, out var name) ? name : $"Benutzer {userId}";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,6 +22,16 @@
|
||||
<div class="card p-3">
|
||||
<p>Bitte laden Sie Ihren Ingenieur-Antrag als PDF-Datei hoch.</p>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="title" class="form-label">Titel</label>
|
||||
<input id="title" type="text" class="form-control" @bind="_existingApp.Title" maxlength="256"/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="description" class="form-label">Kurzbeschreibung</label>
|
||||
<textarea id="description" class="form-control" rows="3" @bind="_existingApp.ShortDescription" placeholder="Kurze Beschreibung Ihres Ingenieur-Antrags..."></textarea>
|
||||
</div>
|
||||
|
||||
@* <div class="mb-3">
|
||||
<label for="pdfUpload" class="form-label">Antrags-PDF</label>
|
||||
<InputFile OnChange="@LoadFiles" class="form-control" accept=".pdf"/>
|
||||
@@ -49,10 +59,33 @@ else
|
||||
<p>
|
||||
<strong>Status:</strong> <span class="badge bg-success">Veröffentlicht</span>
|
||||
</p>
|
||||
@if (!string.IsNullOrEmpty(_existingApp.Title))
|
||||
{
|
||||
<p>
|
||||
<strong>Titel:</strong> @_existingApp.Title
|
||||
</p>
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(_existingApp.ShortDescription))
|
||||
{
|
||||
<p>
|
||||
<strong>Kurzbeschreibung:</strong> @_existingApp.ShortDescription
|
||||
</p>
|
||||
}
|
||||
<p>
|
||||
<strong>Datum:</strong> @_existingApp.CreatedOn.ToShortDateString()
|
||||
</p>
|
||||
<button class="btn btn-primary" @onclick="EditApp">Antrag aktualisieren</button>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-primary" @onclick="EditApp">Antrag aktualisieren</button>
|
||||
@if (!_confirmDelete)
|
||||
{
|
||||
<button class="btn btn-outline-danger" @onclick="() => _confirmDelete = true">Antrag löschen</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button class="btn btn-danger" @onclick="DeleteApp">Wirklich löschen?</button>
|
||||
<button class="btn btn-secondary" @onclick="() => _confirmDelete = false">Abbrechen</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -63,6 +96,7 @@ else
|
||||
|
||||
private EngineerApplication _existingApp = new EngineerApplication();
|
||||
private bool _showForm = true;
|
||||
private bool _confirmDelete = false;
|
||||
private string _message = "";
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
@@ -104,8 +138,10 @@ else
|
||||
{
|
||||
ApplicationId = _existingApp?.ApplicationId ?? 0,
|
||||
ModuleId = ModuleState.ModuleId,
|
||||
UserId = PageState.User.UserId, // Ensure UserID is set
|
||||
UserId = PageState.User.UserId,
|
||||
FileId = _existingApp.FileId,
|
||||
Title = _existingApp.Title,
|
||||
ShortDescription = _existingApp.ShortDescription,
|
||||
Status = "Published", // Auto-publish
|
||||
SubmittedOn = DateTime.UtcNow,
|
||||
ApprovedOn = DateTime.UtcNow, // Auto-approved
|
||||
@@ -136,6 +172,23 @@ else
|
||||
NavManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
|
||||
private async Task DeleteApp()
|
||||
{
|
||||
try
|
||||
{
|
||||
await ApplicationService.DeleteApplicationAsync(_existingApp.ApplicationId, ModuleState.ModuleId);
|
||||
_existingApp = new EngineerApplication { Status = "New" };
|
||||
_showForm = true;
|
||||
_confirmDelete = false;
|
||||
_message = "Antrag erfolgreich gelöscht.";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_message = "Fehler beim Löschen: " + ex.Message;
|
||||
_confirmDelete = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Task OnSelectFile(int fileId)
|
||||
{
|
||||
|
||||
@@ -9,10 +9,12 @@ namespace SZUAbsolventenverein.Module.PremiumArea
|
||||
public ModuleDefinition ModuleDefinition => new ModuleDefinition
|
||||
{
|
||||
Name = "PremiumArea",
|
||||
Description = "This module adds a premium member system to Octane. Users receive premium status after completing a payment. Premium members get access to exclusive features and content.",
|
||||
Version = "1.0.2",
|
||||
ServerManagerType = "SZUAbsolventenverein.Module.PremiumArea.Manager.PremiumAreaManager, SZUAbsolventenverein.Module.PremiumArea.Server.Oqtane",
|
||||
ReleaseVersions = "1.0.0,1.0.1,1.0.2",
|
||||
Description =
|
||||
"This module adds a premium member system to Octane. Users receive premium status after completing a payment. Premium members get access to exclusive features and content.",
|
||||
Version = "1.0.4",
|
||||
ServerManagerType =
|
||||
"SZUAbsolventenverein.Module.PremiumArea.Manager.PremiumAreaManager, SZUAbsolventenverein.Module.PremiumArea.Server.Oqtane",
|
||||
ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4",
|
||||
Dependencies = "SZUAbsolventenverein.Module.PremiumArea.Shared.Oqtane",
|
||||
PackageName = "SZUAbsolventenverein.Module.PremiumArea",
|
||||
// Hier definieren Sie, WELCHE Permissions verfügbar sind
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>$projectname$</id>
|
||||
<version>1.0.0</version>
|
||||
<id>$ProjectName$</id>
|
||||
<version>1.0.4</version>
|
||||
<authors>SZUAbsolventenverein</authors>
|
||||
<owners>SZUAbsolventenverein</owners>
|
||||
<title>PremiumArea</title>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
TargetFramework=$1
|
||||
ProjectName=$2
|
||||
|
||||
find . -name "*.nupkg" -delete
|
||||
"..\..\oqtane.framework\oqtane.package\FixProps.exe"
|
||||
"..\..\oqtane.framework\oqtane.package\nuget.exe" pack %ProjectName%.nuspec -Properties targetframework=%TargetFramework%;projectname=%ProjectName%
|
||||
cp -f "*.nupkg" "..\..\oqtane.framework\Oqtane.Server\Packages\"
|
||||
find . -name *.nupkg -delete
|
||||
dotnet run --project ../../fixProps/FixProps/FixProps.csproj
|
||||
dotnet pack $ProjectName.nuspec "/p:targetframework=${TargetFramework};ProjectName=${ProjectName}"
|
||||
cp -f *.nupkg ../../oqtane.framework/Oqtane.Server/Packages/
|
||||
|
||||
32
Server/Migrations/01000003_AddTitleAndDescription.cs
Normal file
32
Server/Migrations/01000003_AddTitleAndDescription.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Oqtane.Databases.Interfaces;
|
||||
using Oqtane.Migrations;
|
||||
using SZUAbsolventenverein.Module.PremiumArea.Migrations.EntityBuilders;
|
||||
using SZUAbsolventenverein.Module.PremiumArea.Repository;
|
||||
|
||||
namespace SZUAbsolventenverein.Module.PremiumArea.Migrations
|
||||
{
|
||||
[DbContext(typeof(PremiumAreaContext))]
|
||||
[Migration("SZUAbsolventenverein.Module.PremiumArea.01.00.00.03")]
|
||||
public class AddTitleAndDescription : MultiDatabaseMigration
|
||||
{
|
||||
public AddTitleAndDescription(IDatabase database) : base(database)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var table = new EngineerApplicationEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
table.AddStringColumn("Title", 256, true);
|
||||
table.AddMaxStringColumn("ShortDescription", true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var table = new EngineerApplicationEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
table.DropColumn("Title");
|
||||
table.DropColumn("ShortDescription");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,10 +11,16 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Migrations.EntityBuilders
|
||||
public class EngineerApplicationEntityBuilder : AuditableBaseEntityBuilder<EngineerApplicationEntityBuilder>
|
||||
{
|
||||
private const string _entityTableName = "SZUAbsolventenvereinEngineerApplications";
|
||||
private readonly PrimaryKey<EngineerApplicationEntityBuilder> _primaryKey = new("PK_SZUAbsolventenvereinEngineerApplications", x => x.ApplicationId);
|
||||
private readonly ForeignKey<EngineerApplicationEntityBuilder> _moduleForeignKey = new("FK_SZUAbsolventenvereinEngineerApplications_Module", x => x.ModuleId, "Module", "ModuleId", ReferentialAction.Cascade);
|
||||
|
||||
public EngineerApplicationEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
|
||||
private readonly PrimaryKey<EngineerApplicationEntityBuilder> _primaryKey =
|
||||
new("PK_SZUAbsolventenvereinEngineerApplications", x => x.ApplicationId);
|
||||
|
||||
private readonly ForeignKey<EngineerApplicationEntityBuilder> _moduleForeignKey =
|
||||
new("FK_SZUAbsolventenvereinEngineerApplications_Module", x => x.ModuleId, "Module", "ModuleId",
|
||||
ReferentialAction.Cascade);
|
||||
|
||||
public EngineerApplicationEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(
|
||||
migrationBuilder, database)
|
||||
{
|
||||
EntityTableName = _entityTableName;
|
||||
PrimaryKey = _primaryKey;
|
||||
@@ -27,6 +33,8 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Migrations.EntityBuilders
|
||||
UserId = AddIntegerColumn(table, "UserId");
|
||||
ModuleId = AddIntegerColumn(table, "ModuleId");
|
||||
FileId = AddIntegerColumn(table, "FileId", true);
|
||||
Title = AddStringColumn(table, "Title", 256, true);
|
||||
ShortDescription = AddMaxStringColumn(table, "ShortDescription", true);
|
||||
PdfFileName = AddStringColumn(table, "PdfFileName", 256);
|
||||
Status = AddStringColumn(table, "Status", 50);
|
||||
AdminReviewedBy = AddIntegerColumn(table, "AdminReviewedBy", true);
|
||||
@@ -42,11 +50,12 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Migrations.EntityBuilders
|
||||
}
|
||||
|
||||
|
||||
|
||||
public OperationBuilder<AddColumnOperation> ApplicationId { get; set; }
|
||||
public OperationBuilder<AddColumnOperation> UserId { get; set; }
|
||||
public OperationBuilder<AddColumnOperation> ModuleId { get; set; }
|
||||
public OperationBuilder<AddColumnOperation> FileId { get; set; }
|
||||
public OperationBuilder<AddColumnOperation> Title { get; set; }
|
||||
public OperationBuilder<AddColumnOperation> ShortDescription { get; set; }
|
||||
public OperationBuilder<AddColumnOperation> PdfFileName { get; set; }
|
||||
public OperationBuilder<AddColumnOperation> Status { get; set; }
|
||||
public OperationBuilder<AddColumnOperation> AdminReviewedBy { get; set; }
|
||||
|
||||
@@ -90,6 +90,8 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Repository
|
||||
if (existing != null)
|
||||
{
|
||||
existing.FileId = EngineerApplication.FileId;
|
||||
existing.Title = EngineerApplication.Title;
|
||||
existing.ShortDescription = EngineerApplication.ShortDescription;
|
||||
existing.Status = EngineerApplication.Status;
|
||||
existing.SubmittedOn = EngineerApplication.SubmittedOn;
|
||||
existing.ApprovedOn = EngineerApplication.ApprovedOn;
|
||||
|
||||
@@ -43,20 +43,28 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Services
|
||||
return Task.FromResult(_repository.GetEngineerApplications(ModuleId).ToList());
|
||||
}
|
||||
|
||||
// Check if Premium
|
||||
if (IsUserPremium(user))
|
||||
var userId = _accessor.HttpContext.GetUserId();
|
||||
var results = new List<EngineerApplication>();
|
||||
|
||||
// Always include the user's own applications (needed for Apply.razor)
|
||||
if (userId != -1)
|
||||
{
|
||||
// Return Approved AND Published
|
||||
// Repository GetEngineerApplications(ModuleId, status) usually filters by status.
|
||||
// If I want both, I might need 2 calls or update Repository.
|
||||
// Simple approach: Get All and filter here? No, repository likely filters.
|
||||
// Let's call twice and combine for now, or assume Repository supports "Published".
|
||||
var approved = _repository.GetEngineerApplications(ModuleId, "Approved");
|
||||
var published = _repository.GetEngineerApplications(ModuleId, "Published");
|
||||
return Task.FromResult(approved.Union(published).ToList());
|
||||
var ownApps = _repository.GetEngineerApplications(ModuleId)
|
||||
.Where(a => a.UserId == userId).ToList();
|
||||
results.AddRange(ownApps);
|
||||
}
|
||||
|
||||
return Task.FromResult(new List<EngineerApplication>());
|
||||
// Check if Premium - also show approved/published apps from others
|
||||
if (IsUserPremium(user))
|
||||
{
|
||||
var approved = _repository.GetEngineerApplications(ModuleId, "Approved");
|
||||
var published = _repository.GetEngineerApplications(ModuleId, "Published");
|
||||
results.AddRange(approved.Union(published));
|
||||
// Remove duplicates (own apps might already be in approved/published)
|
||||
results = results.GroupBy(a => a.ApplicationId).Select(g => g.First()).ToList();
|
||||
}
|
||||
|
||||
return Task.FromResult(results);
|
||||
}
|
||||
|
||||
public Task<List<EngineerApplication>> GetApplicationsAsync(int ModuleId, string status)
|
||||
@@ -142,19 +150,23 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Services
|
||||
var userId = _accessor.HttpContext.GetUserId();
|
||||
bool isAdmin = _userPermissions.IsAuthorized(user, _alias.SiteId, EntityNames.Module, Application.ModuleId,
|
||||
PermissionNames.Edit);
|
||||
bool isOwner = (userId != -1 && existing.UserId == userId);
|
||||
|
||||
if (isAdmin || (existing.UserId == userId && existing.Status == "Draft")) // Only owner can edit if Draft
|
||||
if (isAdmin || isOwner)
|
||||
{
|
||||
if (!isAdmin)
|
||||
{
|
||||
Application.Status = existing.Status == "Draft" && Application.Status == "Submitted"
|
||||
? "Published"
|
||||
: existing.Status; // Auto-publish on submit
|
||||
// If client sends "Published" (which Apply.razor does), apply it.
|
||||
if (Application.Status == "Published" && existing.Status == "Draft")
|
||||
// Owner can update their own application
|
||||
// Accept "Published" status from client (auto-publish without admin approval)
|
||||
if (Application.Status == "Published")
|
||||
{
|
||||
Application.SubmittedOn = DateTime.UtcNow;
|
||||
Application.ApprovedOn = DateTime.UtcNow;
|
||||
Application.SubmittedOn ??= DateTime.UtcNow;
|
||||
Application.ApprovedOn ??= DateTime.UtcNow;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Keep existing status if client didn't explicitly set to Published
|
||||
Application.Status = existing.Status;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +176,9 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Services
|
||||
return Task.FromResult(Application);
|
||||
}
|
||||
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security,
|
||||
"Unauthorized Update Attempt. UserId: {UserId}, AppOwner: {OwnerId}, IsAdmin: {IsAdmin}",
|
||||
userId, existing.UserId, isAdmin);
|
||||
return Task.FromResult<EngineerApplication>(null);
|
||||
}
|
||||
|
||||
@@ -265,40 +280,20 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Services
|
||||
|
||||
private bool IsUserPremium(System.Security.Claims.ClaimsPrincipal user)
|
||||
{
|
||||
// Oqtane's GetUserId() extension returns -1 if not found.
|
||||
// We need to parse User object myself or use "User.Identity.Name" to find user if needed.
|
||||
// But _premiumService.IsPremium(userId) asks for Int.
|
||||
// I'll assume I can get UserId.
|
||||
// Since Oqtane is weird with Claims sometimes, I'll use the Helper Extension `GetUserId()`.
|
||||
// But wait, "GetUserId" is an extension on HttpContext or ClaimsPrincipal?
|
||||
// In Oqtane.Shared.UserSecurity class? Or Oqtane.Extensions?
|
||||
// Usually `_accessor.HttpContext.GlobalUserId()` ??
|
||||
// Let's rely on standard DI/Context.
|
||||
if (!user.Identity.IsAuthenticated)
|
||||
return false;
|
||||
|
||||
// NOTE: IsUserPremium check requires querying the DB via PremiumService.
|
||||
// This could be expensive on every list call. Caching might be better but for now DB is fine.
|
||||
|
||||
// To get UserId from ClaimsPrincipal:
|
||||
// var userId = int.Parse(user.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value);
|
||||
// I will use `_accessor.HttpContext.GetUserId()` from `Oqtane.Infrastructure` or similar if available.
|
||||
// Since I am already using `_accessor`, I'll rely on Oqtane extensions being available.
|
||||
// Check 1: Oqtane role "Premium Member" (matches the UI-level check in ApplicationList.razor)
|
||||
if (user.IsInRole("Premium Member"))
|
||||
return true;
|
||||
|
||||
// Check 2: Custom UserPremium DB table (for premium granted via GrantPremium/payment)
|
||||
int userId = -1;
|
||||
// Trying standard Oqtane way which is usually:
|
||||
if (user.Identity.IsAuthenticated)
|
||||
var claim = user.Claims.FirstOrDefault(item =>
|
||||
item.Type == System.Security.Claims.ClaimTypes.NameIdentifier);
|
||||
if (claim != null)
|
||||
{
|
||||
// Using Oqtane.Shared.PrincipalExtensions?
|
||||
// Let's just try to parse NameIdentifier if GetUserId() is not available in my context.
|
||||
// But wait, `ModuleControllerBase` has `User` property.
|
||||
// Here I am in a Service.
|
||||
|
||||
// I will write a helper to safely get UserId.
|
||||
var claim = user.Claims.FirstOrDefault(item =>
|
||||
item.Type == System.Security.Claims.ClaimTypes.NameIdentifier);
|
||||
if (claim != null)
|
||||
{
|
||||
int.TryParse(claim.Value, out userId);
|
||||
}
|
||||
int.TryParse(claim.Value, out userId);
|
||||
}
|
||||
|
||||
if (userId != -1)
|
||||
|
||||
@@ -12,16 +12,8 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Models
|
||||
public int UserId { get; set; }
|
||||
public int ModuleId { get; set; } // Context context
|
||||
public int FileId { get; set; }
|
||||
public string PdfFileName { get; set; } = "antrag.pdf"; // Legacy-Spalte, DB ist NOT NULL
|
||||
|
||||
// Legacy-Spalten: existieren noch in der DB (Migration lief nicht)
|
||||
public bool IsReported { get; set; } = false;
|
||||
public string ReportReason { get; set; }
|
||||
public int ReportCount { get; set; } = 0;
|
||||
public int? AdminReviewedBy { get; set; }
|
||||
public DateTime? AdminReviewedAt { get; set; }
|
||||
|
||||
public string AdminNote { get; set; } = ""; // DB ist NOT NULL
|
||||
[StringLength(256)] public string Title { get; set; }
|
||||
public string ShortDescription { get; set; }
|
||||
|
||||
// Status: "Draft", "Submitted", "Approved", "Rejected"
|
||||
[StringLength(50)] public string Status { get; set; }
|
||||
|
||||
Reference in New Issue
Block a user