Compare commits
12 Commits
f80e9d00ee
...
v1.0.4
| Author | SHA1 | Date | |
|---|---|---|---|
| 206b974ab3 | |||
| 97900dddbf | |||
| 3f23b957b0 | |||
| 10388d9a7f | |||
| 0c98c6d86a | |||
| fb5536d29d | |||
| c54a33c159 | |||
| eda0ad794d | |||
| b51b37a6e8 | |||
| 1e88a86be1 | |||
| 4a1d334a5c | |||
| a1b5d0371d |
@@ -1,179 +0,0 @@
|
|||||||
@using SZUAbsolventenverein.Module.PremiumArea.Services
|
|
||||||
@using SZUAbsolventenverein.Module.PremiumArea.Models
|
|
||||||
@namespace SZUAbsolventenverein.Module.PremiumArea
|
|
||||||
@inherits ModuleBase
|
|
||||||
@inject IEngineerApplicationService ApplicationService
|
|
||||||
@inject NavigationManager NavManager
|
|
||||||
|
|
||||||
<h3>Ingenieur-Anträge Prüfen</h3>
|
|
||||||
|
|
||||||
@if (_applications == null)
|
|
||||||
{
|
|
||||||
<p>Laden...</p>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-7">
|
|
||||||
<div class="mb-3">
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<input type="radio" class="btn-check" name="statusfilter" id="filterValidation" autocomplete="off" checked="@(_filterStatus == "Validation")" @onchange="@(() => _filterStatus = "Validation")">
|
|
||||||
<label class="btn btn-outline-primary" for="filterValidation">Validierung (Entwurf/Eingereicht)</label>
|
|
||||||
|
|
||||||
<input type="radio" class="btn-check" name="statusfilter" id="filterApproved" autocomplete="off" checked="@(_filterStatus == "Approved")" @onchange="@(() => _filterStatus = "Approved")">
|
|
||||||
<label class="btn btn-outline-success" for="filterApproved">Genehmigt</label>
|
|
||||||
|
|
||||||
<input type="radio" class="btn-check" name="statusfilter" id="filterRejected" autocomplete="off" checked="@(_filterStatus == "Rejected")" @onchange="@(() => _filterStatus = "Rejected")">
|
|
||||||
<label class="btn btn-outline-danger" for="filterRejected">Abgelehnt</label>
|
|
||||||
|
|
||||||
<input type="radio" class="btn-check" name="statusfilter" id="filterReported" autocomplete="off" checked="@(_filterStatus == "Reported")" @onchange="@(() => _filterStatus = "Reported")">
|
|
||||||
<label class="btn btn-outline-warning" for="filterReported">Gemeldet</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (FilteredApplications.Count == 0)
|
|
||||||
{
|
|
||||||
<p>Keine Anträge gefunden.</p>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<table class="table table-striped table-hover">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Benutzer ID</th>
|
|
||||||
<th>Dateiname</th>
|
|
||||||
<th>Datum</th>
|
|
||||||
<th>Status</th>
|
|
||||||
@if (_filterStatus == "Reported")
|
|
||||||
{
|
|
||||||
<th>Meldegrund</th>
|
|
||||||
}
|
|
||||||
<th>Aktionen</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@foreach (var app in FilteredApplications)
|
|
||||||
{
|
|
||||||
<tr @onclick="@(() => SelectApp(app))" style="cursor: pointer;" class="@(_selectedApp == app ? "table-active" : "")">
|
|
||||||
<td>@app.UserId</td>
|
|
||||||
<td>@app.PdfFileName</td>
|
|
||||||
<td>@(app.SubmittedOn?.ToShortDateString() ?? app.CreatedOn.ToShortDateString())</td>
|
|
||||||
<td>
|
|
||||||
@app.Status
|
|
||||||
@if(app.IsReported) { <span class="badge bg-warning text-dark">Gemeldet</span> }
|
|
||||||
</td>
|
|
||||||
@if (_filterStatus == "Reported")
|
|
||||||
{
|
|
||||||
<td class="text-danger">@app.ReportReason</td>
|
|
||||||
}
|
|
||||||
<td>
|
|
||||||
<button class="btn btn-sm btn-primary" @onclick="@((e) => SelectApp(app))">Prüfen</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="col-md-5">
|
|
||||||
@if (_selectedApp != null)
|
|
||||||
{
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
Antragsdetails
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<dl class="row">
|
|
||||||
<dt class="col-sm-4">Benutzer ID</dt>
|
|
||||||
<dd class="col-sm-8">@_selectedApp.UserId</dd>
|
|
||||||
<dt class="col-sm-4">Datei</dt>
|
|
||||||
<dd class="col-sm-8">@_selectedApp.PdfFileName</dd>
|
|
||||||
<dt class="col-sm-4">Status</dt>
|
|
||||||
<dd class="col-sm-8">@_selectedApp.Status</dd>
|
|
||||||
@if (_selectedApp.IsReported)
|
|
||||||
{
|
|
||||||
<dt class="col-sm-4 text-danger">Meldegrund</dt>
|
|
||||||
<dd class="col-sm-8 text-danger">@_selectedApp.ReportReason (Anzahl: @_selectedApp.ReportCount)</dd>
|
|
||||||
}
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<div class="ratio ratio-16x9">
|
|
||||||
<iframe src="@((NavManager.BaseUri + "api/engineerapplication") + "/download/" + _selectedApp.ApplicationId + "?moduleid=" + ModuleState.ModuleId)" allowfullscreen></iframe>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-grid gap-2">
|
|
||||||
@if (_selectedApp.Status != "Approved" || _selectedApp.IsReported)
|
|
||||||
{
|
|
||||||
<button class="btn @(_selectedApp.IsReported ? "btn-warning" : "btn-success")" @onclick="ApproveApp">
|
|
||||||
@(_selectedApp.IsReported ? "Meldung verwerfen / Behalten" : "Genehmigen & Premium gewähren")
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
@if (_selectedApp.Status != "Rejected")
|
|
||||||
{
|
|
||||||
<button class="btn btn-danger" @onclick="RejectApp">Ablehnen / Löschen</button>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@code {
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; // Admin Only
|
|
||||||
|
|
||||||
private List<EngineerApplication> _applications;
|
|
||||||
private EngineerApplication _selectedApp;
|
|
||||||
private string _filterStatus = "Validation";
|
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
|
||||||
{
|
|
||||||
await LoadApps();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task LoadApps()
|
|
||||||
{
|
|
||||||
// Load All applications
|
|
||||||
_applications = await ApplicationService.GetApplicationsAsync(ModuleState.ModuleId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<EngineerApplication> FilteredApplications
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_applications == null) return new List<EngineerApplication>();
|
|
||||||
if (_filterStatus == "Validation")
|
|
||||||
return _applications.Where(a => a.Status == "Draft" || a.Status == "Submitted" || a.Status == "Published").ToList();
|
|
||||||
|
|
||||||
if (_filterStatus == "Reported")
|
|
||||||
return _applications.Where(a => a.IsReported).ToList();
|
|
||||||
|
|
||||||
return _applications.Where(a => a.Status == _filterStatus).ToList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SelectApp(EngineerApplication app)
|
|
||||||
{
|
|
||||||
_selectedApp = app;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ApproveApp()
|
|
||||||
{
|
|
||||||
if (_selectedApp == null) return;
|
|
||||||
await ApplicationService.ApproveApplicationAsync(_selectedApp.ApplicationId, ModuleState.ModuleId);
|
|
||||||
await LoadApps();
|
|
||||||
_selectedApp = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task RejectApp()
|
|
||||||
{
|
|
||||||
if (_selectedApp == null) return;
|
|
||||||
// Basic rejection without custom reason for now since UI input was removed
|
|
||||||
await ApplicationService.RejectApplicationAsync(_selectedApp.ApplicationId, ModuleState.ModuleId, "Abgelehnt durch Admin");
|
|
||||||
await LoadApps();
|
|
||||||
_selectedApp = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,10 +4,11 @@
|
|||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject IEngineerApplicationService ApplicationService
|
@inject IEngineerApplicationService ApplicationService
|
||||||
@inject NavigationManager NavManager
|
@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"))
|
@if (Oqtane.Security.UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) || Oqtane.Security.UserSecurity.IsAuthorized(PageState.User, "Premium Member"))
|
||||||
{
|
{
|
||||||
<h3>Genehmigte Ingenieur-Anträge</h3>
|
<h3>Ingenieur-Anträge</h3>
|
||||||
|
|
||||||
@if (_applications == null)
|
@if (_applications == null)
|
||||||
{
|
{
|
||||||
@@ -16,33 +17,59 @@
|
|||||||
else if (_applications.Count == 0)
|
else if (_applications.Count == 0)
|
||||||
{
|
{
|
||||||
<div class="alert alert-warning">
|
<div class="alert alert-warning">
|
||||||
Keine genehmigten Anträge gefunden.
|
Keine Anträge gefunden.
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@foreach (var app in _applications)
|
@foreach (var app in _applications)
|
||||||
{
|
{
|
||||||
<div class="col-md-4 mb-3">
|
<div class="col-md-4 mb-3">
|
||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title">Ingenieur-Antrag</h5>
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
<h6 class="card-subtitle mb-2 text-muted">Benutzer ID: @app.UserId</h6>
|
<h5 class="card-title mb-0">@(string.IsNullOrEmpty(app.Title) ? "Ingenieur-Antrag" : app.Title)</h5>
|
||||||
<p class="card-text">
|
@if (Oqtane.Security.UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
<strong>Datei:</strong> @app.PdfFileName<br/>
|
{
|
||||||
<strong>Status:</strong> <span class="badge bg-success">@app.Status</span><br/>
|
@if (_confirmDeleteId == app.ApplicationId)
|
||||||
<strong>Datum:</strong> @(app.ApprovedOn?.ToShortDateString() ?? app.CreatedOn.ToShortDateString())
|
{
|
||||||
</p>
|
<div class="d-flex gap-1">
|
||||||
<div class="d-flex gap-2">
|
<button class="btn btn-danger btn-sm" @onclick="@(() => DeleteApp(app))" title="Bestätigen">
|
||||||
<button class="btn btn-primary btn-sm" @onclick="@(async () => ShowDetail(app))">PDF ansehen</button>
|
<span class="oi oi-check"></span>
|
||||||
<a href="@((NavManager.BaseUri + "api/engineerapplication") + "/download/" + app.ApplicationId + "?moduleid=" + ModuleState.ModuleId)" target="_blank" class="btn btn-outline-secondary btn-sm">Herunterladen</a>
|
</button>
|
||||||
<button class="btn btn-outline-danger btn-sm" @onclick="@(() => InitReport(app))">Melden</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="@(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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,22 +83,22 @@ else
|
|||||||
@if (_selectedApp != null)
|
@if (_selectedApp != null)
|
||||||
{
|
{
|
||||||
<div class="modal d-block" tabindex="-1">
|
<div class="modal d-block" tabindex="-1">
|
||||||
<div class="modal-dialog modal-lg">
|
<div class="modal-dialog modal-lg">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title">Antrags-PDF (@_selectedApp.PdfFileName)</h5>
|
<h5 class="modal-title">Antrags-PDF</h5>
|
||||||
<button type="button" class="btn-close" @onclick="@(() => _selectedApp = null)"></button>
|
<button type="button" class="btn-close" @onclick="@(() => _selectedApp = null)"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body p-0">
|
<div class="modal-body p-0">
|
||||||
<div class="ratio ratio-16x9" style="min-height: 500px;">
|
<div style="min-height: 600px; height: 75vh;">
|
||||||
<iframe src="@((NavManager.BaseUri + "api/engineerapplication") + "/download/" + _selectedApp.ApplicationId + "?moduleid=" + ModuleState.ModuleId)" allowfullscreen></iframe>
|
<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">
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@(() => _selectedApp = null)">Schließen</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@(() => _selectedApp = null)">Schließen</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-backdrop fade show"></div>
|
<div class="modal-backdrop fade show"></div>
|
||||||
}
|
}
|
||||||
@@ -79,22 +106,22 @@ else
|
|||||||
@if (_reportApp != null)
|
@if (_reportApp != null)
|
||||||
{
|
{
|
||||||
<div class="modal d-block" tabindex="-1">
|
<div class="modal d-block" tabindex="-1">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title">Antrag melden</h5>
|
<h5 class="modal-title">Antrag melden</h5>
|
||||||
<button type="button" class="btn-close" @onclick="@(() => _reportApp = null)"></button>
|
<button type="button" class="btn-close" @onclick="@(() => _reportApp = null)"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<p>Bitte geben Sie einen Grund an, warum Sie diesen Antrag melden (Benutzer ID: @_reportApp.UserId, Datei: @_reportApp.PdfFileName).</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>
|
<textarea class="form-control" rows="3" @bind="_reportReason" placeholder="Grund..."></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@(() => _reportApp = null)">Abbrechen</button>
|
<button type="button" class="btn btn-secondary" @onclick="@(() => _reportApp = null)">Abbrechen</button>
|
||||||
<button type="button" class="btn btn-danger" @onclick="SubmitReport">Meldung absenden</button>
|
<button type="button" class="btn btn-danger" @onclick="SubmitReport">Meldung absenden</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-backdrop fade show"></div>
|
<div class="modal-backdrop fade show"></div>
|
||||||
}
|
}
|
||||||
@@ -106,6 +133,8 @@ else
|
|||||||
private EngineerApplication _selectedApp;
|
private EngineerApplication _selectedApp;
|
||||||
private EngineerApplication _reportApp;
|
private EngineerApplication _reportApp;
|
||||||
private string _reportReason;
|
private string _reportReason;
|
||||||
|
private Dictionary<int, string> _userNames = new();
|
||||||
|
private int _confirmDeleteId = -1;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
@@ -113,12 +142,26 @@ else
|
|||||||
{
|
{
|
||||||
var published = await ApplicationService.GetApplicationsAsync(ModuleState.ModuleId, "Published");
|
var published = await ApplicationService.GetApplicationsAsync(ModuleState.ModuleId, "Published");
|
||||||
var approved = await ApplicationService.GetApplicationsAsync(ModuleState.ModuleId, "Approved");
|
var approved = await ApplicationService.GetApplicationsAsync(ModuleState.ModuleId, "Approved");
|
||||||
|
|
||||||
_applications = new List<EngineerApplication>();
|
_applications = new List<EngineerApplication>();
|
||||||
if (published != null) _applications.AddRange(published);
|
if (published != null) _applications.AddRange(published);
|
||||||
if (approved != null) _applications.AddRange(approved);
|
if (approved != null) _applications.AddRange(approved);
|
||||||
|
|
||||||
_applications = _applications.GroupBy(a => a.ApplicationId).Select(g => g.First()).ToList();
|
_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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -153,4 +196,26 @@ else
|
|||||||
AddModuleMessage("Fehler beim Melden: " + ex.Message, MessageType.Error);
|
AddModuleMessage("Fehler beim Melden: " + ex.Message, MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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}";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,83 +1,92 @@
|
|||||||
@using SZUAbsolventenverein.Module.PremiumArea.Services
|
@using SZUAbsolventenverein.Module.PremiumArea.Services
|
||||||
@using SZUAbsolventenverein.Module.PremiumArea.Models
|
@using SZUAbsolventenverein.Module.PremiumArea.Models
|
||||||
@using System.IO
|
|
||||||
@using System.Net.Http.Headers
|
|
||||||
@using Microsoft.AspNetCore.Components.Forms
|
|
||||||
@namespace SZUAbsolventenverein.Module.PremiumArea
|
@namespace SZUAbsolventenverein.Module.PremiumArea
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject IEngineerApplicationService ApplicationService
|
@inject IEngineerApplicationService ApplicationService
|
||||||
@inject NavigationManager NavManager
|
@inject NavigationManager NavManager
|
||||||
@inject IStringLocalizer<Apply> Localizer
|
@inject IStringLocalizer<Apply> Localizer
|
||||||
@inject HttpClient Http
|
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<h3>@Localizer["Ingenieur Antrag"]</h3>
|
<h3>@Localizer["Ingenieur Antrag"]</h3>
|
||||||
@if (!string.IsNullOrEmpty(Message))
|
@if (!string.IsNullOrEmpty(_message))
|
||||||
{
|
{
|
||||||
<div class="alert alert-info">@Message</div>
|
<div class="alert alert-info">@_message</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (ShowForm)
|
@if (_showForm)
|
||||||
{
|
{
|
||||||
@if (_existingApp == null || _existingApp.Status == "Draft" || _existingApp.Status == "New" || _existingApp.Status == "Rejected")
|
<div class="card p-3">
|
||||||
{
|
<p>Bitte laden Sie Ihren Ingenieur-Antrag als PDF-Datei hoch.</p>
|
||||||
<div class="card p-3">
|
|
||||||
<p>Bitte laden Sie Ihren Ingenieur-Antrag als PDF-Datei hoch.</p>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="pdfUpload" class="form-label">Antrags-PDF</label>
|
|
||||||
<InputFile OnChange="@LoadFiles" class="form-control" accept=".pdf" />
|
|
||||||
<div class="form-text">Max Größe: 20MB. Format: Nur PDF.</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (_selectedFile != null)
|
<div class="mb-3">
|
||||||
{
|
<label for="title" class="form-label">Titel</label>
|
||||||
<div class="alert alert-success">
|
<input id="title" type="text" class="form-control" @bind="_existingApp.Title" maxlength="256"/>
|
||||||
Ausgewählt: <strong>@_selectedFile.Name</strong> (@(_selectedFile.Size / 1024) KB)
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="mt-2">
|
|
||||||
<button class="btn btn-primary" @onclick="SubmitApplication" disabled="@(_selectedFile == null && _existingApp?.FileId == null)">
|
|
||||||
@(_existingApp != null ? "Antrag aktualisieren" : "Antrag absenden")
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-secondary" @onclick="Cancel">Abbrechen</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
else
|
<div class="mb-3">
|
||||||
{
|
<label for="description" class="form-label">Kurzbeschreibung</label>
|
||||||
<div class="alert alert-warning">
|
<textarea id="description" class="form-control" rows="3" @bind="_existingApp.ShortDescription" placeholder="Kurze Beschreibung Ihres Ingenieur-Antrags..."></textarea>
|
||||||
Antrags-Status: <strong>@_existingApp.Status</strong>. Sie können ihn derzeit nicht bearbeiten.
|
</div>
|
||||||
</div>
|
|
||||||
}
|
@* <div class="mb-3">
|
||||||
|
<label for="pdfUpload" class="form-label">Antrags-PDF</label>
|
||||||
|
<InputFile OnChange="@LoadFiles" class="form-control" accept=".pdf"/>
|
||||||
|
<div class="form-text">Max Größe: 20MB. Format: Nur PDF.</div>
|
||||||
|
</div> *@
|
||||||
|
|
||||||
|
|
||||||
|
<FileManager OnSelectFile="@OnSelectFile" ShowProgress="true" ShowSuccess="true"/>
|
||||||
|
|
||||||
|
<div class="mt-2">
|
||||||
|
<button class="btn btn-primary" @onclick="SubmitApplication" disabled="@(_existingApp?.FileId == 0)">
|
||||||
|
@(_existingApp.ApplicationId > 0 ? "Antrag aktualisieren" : "Antrag hochladen")
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary" @onclick="Cancel">Abbrechen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@if (_existingApp != null)
|
@if (_existingApp.FileId > 0)
|
||||||
{
|
{
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">Ihr Antrag</div>
|
<div class="card-header">Ihr Antrag</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p><strong>Status:</strong> <span class="badge bg-@GetStatusColor(_existingApp.Status)">@_existingApp.Status</span></p>
|
<p>
|
||||||
<p><strong>Datei:</strong> @_existingApp.PdfFileName</p>
|
<strong>Status:</strong> <span class="badge bg-success">Veröffentlicht</span>
|
||||||
<p><strong>Datum:</strong> @_existingApp.CreatedOn.ToShortDateString()</p>
|
</p>
|
||||||
|
@if (!string.IsNullOrEmpty(_existingApp.Title))
|
||||||
@if (_existingApp.Status == "Rejected")
|
{
|
||||||
{
|
<p>
|
||||||
<div class="alert alert-danger">
|
<strong>Titel:</strong> @_existingApp.Title
|
||||||
<strong>Ablehnungsgrund:</strong> @_existingApp.AdminNote
|
</p>
|
||||||
</div>
|
}
|
||||||
<button class="btn btn-primary" @onclick="EditApp">Neuen Antrag einreichen / Aktualisieren</button>
|
@if (!string.IsNullOrEmpty(_existingApp.ShortDescription))
|
||||||
}
|
{
|
||||||
else if (_existingApp.Status == "Draft")
|
<p>
|
||||||
{
|
<strong>Kurzbeschreibung:</strong> @_existingApp.ShortDescription
|
||||||
<button class="btn btn-primary" @onclick="EditApp">Weiter bearbeiten</button>
|
</p>
|
||||||
}
|
}
|
||||||
</div>
|
<p>
|
||||||
|
<strong>Datum:</strong> @_existingApp.CreatedOn.ToShortDateString()
|
||||||
|
</p>
|
||||||
|
<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>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,49 +94,23 @@ else
|
|||||||
@code {
|
@code {
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||||
|
|
||||||
private EngineerApplication _existingApp;
|
private EngineerApplication _existingApp = new EngineerApplication();
|
||||||
private IBrowserFile _selectedFile;
|
private bool _showForm = true;
|
||||||
private bool ShowForm = true;
|
private bool _confirmDelete = false;
|
||||||
private string Message = "";
|
private string _message = "";
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
// Load existing application for current user
|
try
|
||||||
// We can use a service method to "GetMyApplication" or filter by User.
|
|
||||||
// The service has GetApplicationsAsync(ModuleId).
|
|
||||||
// Since we are user, we should only get ours?
|
|
||||||
// Controller filters by permissions? No, GetApplicationsAsync gets ALL usually?
|
|
||||||
// Wait, the requirement: 'EngineerApplicationController.Get(moduleid)' returns all?
|
|
||||||
// Let's check Controller... 'Get(moduleid)' returns '_service.GetApplicationsAsync(ModuleId)'.
|
|
||||||
// If current user is standard user, does it return ALL?
|
|
||||||
// Security check: 'PolicyNames.ViewModule'.
|
|
||||||
// This is dangerous if standard user calls it.
|
|
||||||
// However, we are in 'Apply.razor'.
|
|
||||||
// Let's assume we need to filter client side or add 'GetMyApplication' to controller.
|
|
||||||
// Given constraints, I will fetch all and filter client side (not secure but quick fix if time constrained)
|
|
||||||
// OR better: I will fail if I can't filter.
|
|
||||||
// The Controller 'Get(moduleid)' calls `_service.GetApplicationsAsync`.
|
|
||||||
// Let's look at `EngineerApplicationController.Get(id, moduleid)`.
|
|
||||||
|
|
||||||
// I'll try to get "My Application" by checking if I passed an ID or if I can find one.
|
|
||||||
// Since I don't have "GetMyApplication", I might have to rely on the user knowing their ID or the list view passing it.
|
|
||||||
// But `Apply.razor` usually implies "Start new or View mine".
|
|
||||||
|
|
||||||
// For now, let's assume `Apply` is entered via button that might pass ID, or we fetch list and find ours.
|
|
||||||
// Fetching list of all apps is bad if there are many.
|
|
||||||
// Let's assume for this task I will try to fetch list and find mine (UserId match).
|
|
||||||
// Note: Controller `Get` probably should have filtered for non-admins.
|
|
||||||
// But ignoring that optimization for now.
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var apps = await ApplicationService.GetApplicationsAsync(ModuleState.ModuleId);
|
var apps = await ApplicationService.GetApplicationsAsync(ModuleState.ModuleId);
|
||||||
var userId = PageState.User?.UserId ?? -1;
|
var userId = PageState.User?.UserId ?? -1;
|
||||||
_existingApp = apps.FirstOrDefault(a => a.UserId == userId);
|
_existingApp.Status = "New";
|
||||||
|
_existingApp = apps.FirstOrDefault(a => a.UserId == userId, _existingApp);
|
||||||
if (_existingApp != null)
|
|
||||||
|
if (_existingApp.FileId > 0)
|
||||||
{
|
{
|
||||||
ShowForm = false;
|
_showForm = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -138,82 +121,49 @@ else
|
|||||||
|
|
||||||
private void EditApp()
|
private void EditApp()
|
||||||
{
|
{
|
||||||
ShowForm = true;
|
_showForm = true;
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadFiles(InputFileChangeEventArgs e)
|
|
||||||
{
|
|
||||||
_selectedFile = e.File;
|
|
||||||
Message = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SubmitApplication()
|
private async Task SubmitApplication()
|
||||||
{
|
{
|
||||||
if (_selectedFile == null && _existingApp?.FileId == null)
|
if (_existingApp == null || _existingApp.FileId == 0)
|
||||||
{
|
{
|
||||||
Message = "Bitte wählen Sie eine Datei aus.";
|
_message = "Bitte wählen Sie eine Datei aus.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
int? fileId = _existingApp?.FileId;
|
|
||||||
string fileName = _existingApp?.PdfFileName;
|
|
||||||
|
|
||||||
if (_selectedFile != null)
|
|
||||||
{
|
|
||||||
// Upload File
|
|
||||||
using var content = new MultipartFormDataContent();
|
|
||||||
var fileContent = new StreamContent(_selectedFile.OpenReadStream(20 * 1024 * 1024)); // 20MB
|
|
||||||
fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
|
|
||||||
content.Add(fileContent, "file", _selectedFile.Name);
|
|
||||||
|
|
||||||
var response = await Http.PostAsync((NavManager.BaseUri + "api/engineerapplication") + "/upload?moduleid=" + ModuleState.ModuleId, content);
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
Message = "Upload fehlgeschlagen: " + response.ReasonPhrase;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var uploadResult = await response.Content.ReadFromJsonAsync<UploadResult>();
|
|
||||||
if (uploadResult != null)
|
|
||||||
{
|
|
||||||
fileId = uploadResult.FileId;
|
|
||||||
fileName = uploadResult.FileName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var app = new EngineerApplication
|
var app = new EngineerApplication
|
||||||
{
|
{
|
||||||
ApplicationId = _existingApp?.ApplicationId ?? 0,
|
ApplicationId = _existingApp?.ApplicationId ?? 0,
|
||||||
ModuleId = ModuleState.ModuleId,
|
ModuleId = ModuleState.ModuleId,
|
||||||
UserId = PageState.User.UserId, // Ensure UserID is set
|
UserId = PageState.User.UserId,
|
||||||
FileId = fileId,
|
FileId = _existingApp.FileId,
|
||||||
PdfFileName = fileName,
|
Title = _existingApp.Title,
|
||||||
|
ShortDescription = _existingApp.ShortDescription,
|
||||||
Status = "Published", // Auto-publish
|
Status = "Published", // Auto-publish
|
||||||
SubmittedOn = DateTime.UtcNow,
|
SubmittedOn = DateTime.UtcNow,
|
||||||
ApprovedOn = DateTime.UtcNow, // Auto-approved
|
ApprovedOn = DateTime.UtcNow, // Auto-approved
|
||||||
IsReported = false,
|
|
||||||
ReportCount = 0
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (app.ApplicationId == 0)
|
if (app.ApplicationId == 0)
|
||||||
{
|
{
|
||||||
var result = await ApplicationService.AddApplicationAsync(app);
|
var result = await ApplicationService.AddApplicationAsync(app);
|
||||||
_existingApp = result;
|
_existingApp = result;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await ApplicationService.UpdateApplicationAsync(app);
|
await ApplicationService.UpdateApplicationAsync(app);
|
||||||
_existingApp = await ApplicationService.GetApplicationAsync(app.ApplicationId, ModuleState.ModuleId);
|
_existingApp = await ApplicationService.GetApplicationAsync(app.ApplicationId, ModuleState.ModuleId);
|
||||||
}
|
}
|
||||||
|
|
||||||
ShowForm = false;
|
_showForm = false;
|
||||||
Message = "Antrag erfolgreich gesendet.";
|
_message = "Antrag erfolgreich veröffentlicht.";
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Message = "Fehler: " + ex.Message;
|
_message = "Fehler: " + ex.Message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,21 +172,28 @@ else
|
|||||||
NavManager.NavigateTo(NavigateUrl());
|
NavManager.NavigateTo(NavigateUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetStatusColor(string status)
|
private async Task DeleteApp()
|
||||||
{
|
{
|
||||||
return status switch
|
try
|
||||||
{
|
{
|
||||||
"Approved" => "success",
|
await ApplicationService.DeleteApplicationAsync(_existingApp.ApplicationId, ModuleState.ModuleId);
|
||||||
"Published" => "success", // Green for Published too
|
_existingApp = new EngineerApplication { Status = "New" };
|
||||||
"Rejected" => "danger",
|
_showForm = true;
|
||||||
"Submitted" => "info",
|
_confirmDelete = false;
|
||||||
_ => "secondary"
|
_message = "Antrag erfolgreich gelöscht.";
|
||||||
};
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_message = "Fehler beim Löschen: " + ex.Message;
|
||||||
|
_confirmDelete = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class UploadResult
|
|
||||||
|
private Task OnSelectFile(int fileId)
|
||||||
{
|
{
|
||||||
public int FileId { get; set; }
|
_existingApp.FileId = fileId;
|
||||||
public string FileName { get; set; }
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,13 +10,9 @@
|
|||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<ActionLink Action="Apply" Text="Ingenieur Antrag hochladen" />
|
<ActionLink Action="Apply" Text="Ingenieur Antrag hochladen"/>
|
||||||
<ActionLink Action="UserSearch" Text="Mitglieder finden" />
|
<ActionLink Action="UserSearch" Text="Mitglieder finden"/>
|
||||||
@if (Oqtane.Security.UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
<ActionLink Action="ApplicationList" Text="Alle Ingenieur-Anträge"/>
|
||||||
{
|
|
||||||
<ActionLink Action="AdminReview" Text="Admin Bereich" Security="SecurityAccessLevel.Edit" />
|
|
||||||
}
|
|
||||||
<ActionLink Action="ApplicationList" Text="Liste alle Ingenieur Anträge" />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@@ -27,4 +23,5 @@
|
|||||||
new Stylesheet("_content/SZUAbsolventenverein.Module.PremiumArea/Module.css"),
|
new Stylesheet("_content/SZUAbsolventenverein.Module.PremiumArea/Module.css"),
|
||||||
new Script("_content/SZUAbsolventenverein.Module.PremiumArea/Module.js")
|
new Script("_content/SZUAbsolventenverein.Module.PremiumArea/Module.js")
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using Oqtane.Models;
|
using Oqtane.Models;
|
||||||
using Oqtane.Modules;
|
using Oqtane.Modules;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
|
||||||
namespace SZUAbsolventenverein.Module.PremiumArea
|
namespace SZUAbsolventenverein.Module.PremiumArea
|
||||||
{
|
{
|
||||||
@@ -8,12 +9,16 @@ namespace SZUAbsolventenverein.Module.PremiumArea
|
|||||||
public ModuleDefinition ModuleDefinition => new ModuleDefinition
|
public ModuleDefinition ModuleDefinition => new ModuleDefinition
|
||||||
{
|
{
|
||||||
Name = "PremiumArea",
|
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.",
|
Description =
|
||||||
Version = "1.0.2",
|
"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.",
|
||||||
ServerManagerType = "SZUAbsolventenverein.Module.PremiumArea.Manager.PremiumAreaManager, SZUAbsolventenverein.Module.PremiumArea.Server.Oqtane",
|
Version = "1.0.4",
|
||||||
ReleaseVersions = "1.0.0,1.0.1,1.0.2",
|
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",
|
Dependencies = "SZUAbsolventenverein.Module.PremiumArea.Shared.Oqtane",
|
||||||
PackageName = "SZUAbsolventenverein.Module.PremiumArea"
|
PackageName = "SZUAbsolventenverein.Module.PremiumArea",
|
||||||
|
// Hier definieren Sie, WELCHE Permissions verfügbar sind
|
||||||
|
PermissionNames = $"{PermissionNames.View},{PermissionNames.Edit},{PermissionNames.Browse}"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>$projectname$</id>
|
<id>$ProjectName$</id>
|
||||||
<version>1.0.0</version>
|
<version>1.0.4</version>
|
||||||
<authors>SZUAbsolventenverein</authors>
|
<authors>SZUAbsolventenverein</authors>
|
||||||
<owners>SZUAbsolventenverein</owners>
|
<owners>SZUAbsolventenverein</owners>
|
||||||
<title>PremiumArea</title>
|
<title>PremiumArea</title>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
TargetFramework=$1
|
TargetFramework=$1
|
||||||
ProjectName=$2
|
ProjectName=$2
|
||||||
|
|
||||||
find . -name "*.nupkg" -delete
|
find . -name *.nupkg -delete
|
||||||
"..\..\oqtane.framework\oqtane.package\FixProps.exe"
|
dotnet run --project ../../fixProps/FixProps/FixProps.csproj
|
||||||
"..\..\oqtane.framework\oqtane.package\nuget.exe" pack %ProjectName%.nuspec -Properties targetframework=%TargetFramework%;projectname=%ProjectName%
|
dotnet pack $ProjectName.nuspec "/p:targetframework=${TargetFramework};ProjectName=${ProjectName}"
|
||||||
cp -f "*.nupkg" "..\..\oqtane.framework\Oqtane.Server\Packages\"
|
cp -f *.nupkg ../../oqtane.framework/Oqtane.Server/Packages/
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
@@ -33,7 +34,9 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Controllers
|
|||||||
private readonly IHttpContextAccessor _accessor;
|
private readonly IHttpContextAccessor _accessor;
|
||||||
private readonly IWebHostEnvironment _environment;
|
private readonly IWebHostEnvironment _environment;
|
||||||
|
|
||||||
public EngineerApplicationController(IEngineerApplicationService service, IFileRepository files, IFolderRepository folders, IUserManager users, IUserPremiumRepository premiums, ILogManager logger, IHttpContextAccessor accessor, IWebHostEnvironment environment) : base(logger, accessor)
|
public EngineerApplicationController(IEngineerApplicationService service, IFileRepository files,
|
||||||
|
IFolderRepository folders, IUserManager users, IUserPremiumRepository premiums, ILogManager logger,
|
||||||
|
IHttpContextAccessor accessor, IWebHostEnvironment environment) : base(logger, accessor)
|
||||||
{
|
{
|
||||||
_service = service;
|
_service = service;
|
||||||
_files = files;
|
_files = files;
|
||||||
@@ -56,7 +59,8 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Controllers
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized EngineerApplication Get Attempt {ModuleId}", moduleid);
|
_logger.Log(LogLevel.Error, this, LogFunction.Security,
|
||||||
|
"Unauthorized EngineerApplication Get Attempt {ModuleId}", moduleid);
|
||||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -72,9 +76,10 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Controllers
|
|||||||
{
|
{
|
||||||
return await _service.GetApplicationsAsync(ModuleId, status);
|
return await _service.GetApplicationsAsync(ModuleId, status);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized EngineerApplication GetByStatus Attempt {ModuleId}", moduleid);
|
_logger.Log(LogLevel.Error, this, LogFunction.Security,
|
||||||
|
"Unauthorized EngineerApplication GetByStatus Attempt {ModuleId}", moduleid);
|
||||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -85,17 +90,18 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Controllers
|
|||||||
[Authorize(Policy = PolicyNames.ViewModule)]
|
[Authorize(Policy = PolicyNames.ViewModule)]
|
||||||
public async Task<EngineerApplication> Get(int id, string moduleid)
|
public async Task<EngineerApplication> Get(int id, string moduleid)
|
||||||
{
|
{
|
||||||
int ModuleId;
|
int ModuleId;
|
||||||
if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId))
|
if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId))
|
||||||
{
|
{
|
||||||
return await _service.GetApplicationAsync(id, ModuleId);
|
return await _service.GetApplicationAsync(id, ModuleId);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized EngineerApplication Get Attempt {Id} {ModuleId}", id, moduleid);
|
_logger.Log(LogLevel.Error, this, LogFunction.Security,
|
||||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
"Unauthorized EngineerApplication Get Attempt {Id} {ModuleId}", id, moduleid);
|
||||||
return null;
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
}
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST api/<controller>
|
// POST api/<controller>
|
||||||
@@ -105,11 +111,44 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Controllers
|
|||||||
{
|
{
|
||||||
if (ModelState.IsValid && IsAuthorizedEntityId(EntityNames.Module, Application.ModuleId))
|
if (ModelState.IsValid && IsAuthorizedEntityId(EntityNames.Module, Application.ModuleId))
|
||||||
{
|
{
|
||||||
return await _service.AddApplicationAsync(Application);
|
try
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Information, this, LogFunction.Create,
|
||||||
|
"DEBUG: Attempting to save application. UserId: {UserId}, ModuleId: {ModuleId}, FileId: {FileId}",
|
||||||
|
Application.UserId, Application.ModuleId, Application.FileId);
|
||||||
|
|
||||||
|
// Manual validation before EF Core sees it
|
||||||
|
if (Application.UserId == 0)
|
||||||
|
_logger.Log(LogLevel.Warning, this, LogFunction.Create, "DEBUG: UserId is 0!");
|
||||||
|
if (Application.FileId == null || Application.FileId == 0)
|
||||||
|
_logger.Log(LogLevel.Warning, this, LogFunction.Create, "DEBUG: FileId is null or 0!");
|
||||||
|
|
||||||
|
var result = await _service.AddApplicationAsync(Application);
|
||||||
|
_logger.Log(LogLevel.Information, this, LogFunction.Create, "DEBUG: Save successful!");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var innerMessage = ex.InnerException?.Message ?? "No inner exception";
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Create, ex,
|
||||||
|
"CRITICAL DB ERROR: {Message}. Inner: {Inner}", ex.Message, innerMessage);
|
||||||
|
|
||||||
|
// Force output to console so the user sees it immediately
|
||||||
|
Console.WriteLine("========================================");
|
||||||
|
Console.WriteLine($"!!! DATABASE INSERT FAILED !!!");
|
||||||
|
Console.WriteLine($"Error: {ex.Message}");
|
||||||
|
Console.WriteLine($"Inner: {innerMessage}");
|
||||||
|
Console.WriteLine($"Stack: {ex.StackTrace}");
|
||||||
|
Console.WriteLine("========================================");
|
||||||
|
|
||||||
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized EngineerApplication Post Attempt {Application}", Application);
|
_logger.Log(LogLevel.Error, this, LogFunction.Security,
|
||||||
|
"Unauthorized EngineerApplication Post Attempt {Application}", Application);
|
||||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -120,13 +159,26 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Controllers
|
|||||||
[Authorize(Policy = PolicyNames.ViewModule)] // Users can Edit own (Service checks ownership)
|
[Authorize(Policy = PolicyNames.ViewModule)] // Users can Edit own (Service checks ownership)
|
||||||
public async Task<EngineerApplication> Put(int id, [FromBody] EngineerApplication Application)
|
public async Task<EngineerApplication> Put(int id, [FromBody] EngineerApplication Application)
|
||||||
{
|
{
|
||||||
if (ModelState.IsValid && Application.ApplicationId == id && IsAuthorizedEntityId(EntityNames.Module, Application.ModuleId))
|
if (ModelState.IsValid && Application.ApplicationId == id &&
|
||||||
|
IsAuthorizedEntityId(EntityNames.Module, Application.ModuleId))
|
||||||
{
|
{
|
||||||
return await _service.UpdateApplicationAsync(Application);
|
try
|
||||||
|
{
|
||||||
|
return await _service.UpdateApplicationAsync(Application);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Update, ex,
|
||||||
|
"Error updating application: {Message}. Inner: {Inner}", ex.Message,
|
||||||
|
ex.InnerException?.Message);
|
||||||
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized EngineerApplication Put Attempt {Application}", Application);
|
_logger.Log(LogLevel.Error, this, LogFunction.Security,
|
||||||
|
"Unauthorized EngineerApplication Put Attempt {Application}", Application);
|
||||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -144,12 +196,12 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Controllers
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized EngineerApplication Delete Attempt {Id} {ModuleId}", id, moduleid);
|
_logger.Log(LogLevel.Error, this, LogFunction.Security,
|
||||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
"Unauthorized EngineerApplication Delete Attempt {Id} {ModuleId}", id, moduleid);
|
||||||
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST api/<controller>/approve/5
|
|
||||||
[HttpPost("approve/{id}")]
|
[HttpPost("approve/{id}")]
|
||||||
[Authorize(Policy = PolicyNames.EditModule)]
|
[Authorize(Policy = PolicyNames.EditModule)]
|
||||||
public async Task Approve(int id, string moduleid)
|
public async Task Approve(int id, string moduleid)
|
||||||
@@ -157,16 +209,57 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Controllers
|
|||||||
int ModuleId;
|
int ModuleId;
|
||||||
if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId))
|
if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId))
|
||||||
{
|
{
|
||||||
// We need to clear IsReported flag as well.
|
|
||||||
// Since the Service handles the logic, we should probably update it there.
|
|
||||||
// But if I can't find it easily, I can do it here if I get the app first.
|
|
||||||
// _service.ApproveApplicationAsync might just set Status="Approved".
|
|
||||||
// I should verify where the service logic is.
|
|
||||||
await _service.ApproveApplicationAsync(id, ModuleId);
|
await _service.ApproveApplicationAsync(id, ModuleId);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("log")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public IActionResult GetLog()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var repo = (EngineerApplicationRepository)_service.GetType().GetField("_repository",
|
||||||
|
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
|
||||||
|
.GetValue(_service);
|
||||||
|
var factory = (IDbContextFactory<PremiumAreaContext>)repo.GetType().GetField("_factory",
|
||||||
|
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(repo);
|
||||||
|
using var db = factory.CreateDbContext();
|
||||||
|
|
||||||
|
var connection = db.Database.GetDbConnection();
|
||||||
|
connection.Open();
|
||||||
|
using var command = connection.CreateCommand();
|
||||||
|
command.CommandText = "PRAGMA table_info(SZUAbsolventenvereinEngineerApplications);";
|
||||||
|
var columns = new List<string>();
|
||||||
|
using var reader = command.ExecuteReader();
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
columns.Add($"{reader["name"]} ({reader["type"]})");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (columns.Count == 0)
|
||||||
|
{
|
||||||
|
// Maybe it's rewritten?
|
||||||
|
command.CommandText =
|
||||||
|
"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '%Engineer%';";
|
||||||
|
using var reader2 = command.ExecuteReader();
|
||||||
|
while (reader2.Read())
|
||||||
|
{
|
||||||
|
columns.Add($"Found table: {reader2["name"]}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(columns);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Create, ex,
|
||||||
|
"Error getting columns: {Message}. Inner: {Inner}", ex.Message, ex.InnerException?.Message);
|
||||||
|
return Ok($"Error: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,63 +270,95 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Controllers
|
|||||||
int ModuleId;
|
int ModuleId;
|
||||||
if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId))
|
if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId))
|
||||||
{
|
{
|
||||||
if (Request.Form.Files.Count == 0) return BadRequest("No file uploaded");
|
try
|
||||||
|
|
||||||
var file = Request.Form.Files[0];
|
|
||||||
if (file.ContentType != "application/pdf") return BadRequest("Only PDF files are allowed");
|
|
||||||
|
|
||||||
var alias = _accessor.HttpContext.Items["Alias"] as Alias;
|
|
||||||
var siteId = alias.SiteId;
|
|
||||||
var folderPath = "EngineerApplications";
|
|
||||||
var folder = _folders.GetFolder(siteId, folderPath);
|
|
||||||
|
|
||||||
if (folder == null)
|
|
||||||
{
|
{
|
||||||
// Create folder
|
if (Request.Form.Files.Count == 0) return BadRequest("No file uploaded");
|
||||||
folder = new Folder
|
|
||||||
|
var file = Request.Form.Files[0];
|
||||||
|
if (file.ContentType != "application/pdf") return BadRequest("Only PDF files are allowed");
|
||||||
|
|
||||||
|
var alias = _accessor.HttpContext.Items["Alias"] as Alias; // Retained original alias retrieval
|
||||||
|
var siteId = alias.SiteId;
|
||||||
|
var folderPath = "EngineerApplications";
|
||||||
|
|
||||||
|
var folder = _folders.GetFolder(siteId, folderPath);
|
||||||
|
|
||||||
|
if (folder == null)
|
||||||
{
|
{
|
||||||
SiteId = siteId,
|
try
|
||||||
ParentId = null,
|
|
||||||
Name = "EngineerApplications",
|
|
||||||
Path = folderPath,
|
|
||||||
PermissionList = new List<Permission>
|
|
||||||
{
|
{
|
||||||
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
// Create folder
|
||||||
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
folder = new Folder
|
||||||
|
{
|
||||||
|
SiteId = siteId,
|
||||||
|
ParentId = null,
|
||||||
|
Name = "EngineerApplications",
|
||||||
|
Path = folderPath,
|
||||||
|
PermissionList = new List<Permission>
|
||||||
|
{
|
||||||
|
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||||
|
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
folder = _folders.AddFolder(folder);
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Create, ex,
|
||||||
|
"Error creating folder: {Message}", ex.Message);
|
||||||
|
return BadRequest($"Folder creation failed: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ext = Path.GetExtension(file.FileName).ToLower();
|
||||||
|
if (ext != ".pdf") return BadRequest("Invalid file extension");
|
||||||
|
|
||||||
|
var tenantId = alias.TenantId;
|
||||||
|
var uploadPath = Path.Combine(_environment.ContentRootPath, "Content", "Tenants",
|
||||||
|
tenantId.ToString(), "Sites", siteId.ToString(), folderPath);
|
||||||
|
if (!Directory.Exists(uploadPath)) Directory.CreateDirectory(uploadPath);
|
||||||
|
|
||||||
|
var uniqueName = $"{Guid.NewGuid()}{ext}";
|
||||||
|
var filePath = Path.Combine(uploadPath, uniqueName);
|
||||||
|
|
||||||
|
using (var stream = new FileStream(filePath, FileMode.Create))
|
||||||
|
{
|
||||||
|
await file.CopyToAsync(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileObj = new Oqtane.Models.File
|
||||||
|
{
|
||||||
|
FolderId = folder.FolderId,
|
||||||
|
Name = uniqueName,
|
||||||
|
Extension = ext.Substring(1),
|
||||||
|
Size = (int)file.Length,
|
||||||
|
ImageHeight = 0,
|
||||||
|
ImageWidth = 0
|
||||||
};
|
};
|
||||||
folder = _folders.AddFolder(folder);
|
|
||||||
}
|
|
||||||
|
|
||||||
var ext = Path.GetExtension(file.FileName).ToLower();
|
try
|
||||||
if (ext != ".pdf") return BadRequest("Invalid file extension");
|
{
|
||||||
|
var addedFile = _files.AddFile(fileObj);
|
||||||
var tenantId = alias.TenantId;
|
return Ok(new { FileId = addedFile.FileId, FileName = file.FileName });
|
||||||
var uploadPath = Path.Combine(_environment.ContentRootPath, "Content", "Tenants", tenantId.ToString(), "Sites", siteId.ToString(), folderPath);
|
}
|
||||||
if (!Directory.Exists(uploadPath)) Directory.CreateDirectory(uploadPath);
|
catch (Exception ex)
|
||||||
|
{
|
||||||
var uniqueName = $"{Guid.NewGuid()}{ext}";
|
_logger.Log(LogLevel.Error, this, LogFunction.Create, ex,
|
||||||
var filePath = Path.Combine(uploadPath, uniqueName);
|
"Error saving file record to DB: {Message}. Inner: {Inner}", ex.Message,
|
||||||
|
ex.InnerException?.Message);
|
||||||
using (var stream = new FileStream(filePath, FileMode.Create))
|
// Critical: This is where we suspect the DbUpdateException
|
||||||
{
|
Console.WriteLine($"UPLOAD DB ERROR: {ex.Message} | {ex.InnerException?.Message}");
|
||||||
await file.CopyToAsync(stream);
|
return BadRequest($"Database error during file registration: {ex.Message}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
var fileObj = new Oqtane.Models.File
|
|
||||||
{
|
{
|
||||||
FolderId = folder.FolderId,
|
_logger.Log(LogLevel.Error, this, LogFunction.Create, ex, "General Upload Error: {Message}",
|
||||||
Name = uniqueName,
|
ex.Message);
|
||||||
Extension = ext.Substring(1),
|
return BadRequest(ex.Message);
|
||||||
Size = (int)file.Length,
|
}
|
||||||
ImageHeight = 0,
|
|
||||||
ImageWidth = 0
|
|
||||||
};
|
|
||||||
|
|
||||||
var addedFile = _files.AddFile(fileObj);
|
|
||||||
|
|
||||||
return Ok(new { FileId = addedFile.FileId, FileName = file.FileName });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return StatusCode((int)HttpStatusCode.Forbidden);
|
return StatusCode((int)HttpStatusCode.Forbidden);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,71 +371,25 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Controllers
|
|||||||
{
|
{
|
||||||
await _service.ReportApplicationAsync(id, ModuleId, reason);
|
await _service.ReportApplicationAsync(id, ModuleId, reason);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("download/{id}")]
|
|
||||||
[Authorize]
|
|
||||||
public async Task<IActionResult> Download(int id, string moduleid)
|
|
||||||
{
|
|
||||||
int ModuleId;
|
|
||||||
if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId))
|
|
||||||
{
|
|
||||||
var app = await _service.GetApplicationAsync(id, ModuleId);
|
|
||||||
if (app == null) return NotFound();
|
|
||||||
|
|
||||||
var alias = _accessor.HttpContext.Items["Alias"] as Alias;
|
|
||||||
// Access Rules:
|
|
||||||
// 1. Admin
|
|
||||||
if (_accessor.HttpContext.User.IsInRole(RoleNames.Admin))
|
|
||||||
{
|
|
||||||
return await ServeFile(app.FileId.Value, app.PdfFileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Owner
|
|
||||||
var username = _accessor.HttpContext.User.Identity.Name;
|
|
||||||
var currentUserId = -1;
|
|
||||||
if (username != null)
|
|
||||||
{
|
|
||||||
var u = _users.GetUser(username, alias.SiteId);
|
|
||||||
if (u != null) currentUserId = u.UserId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentUserId == app.UserId)
|
|
||||||
{
|
|
||||||
return await ServeFile(app.FileId.Value, app.PdfFileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Premium User AND Published/Approved
|
|
||||||
if (app.Status == "Approved" || app.Status == "Published")
|
|
||||||
{
|
|
||||||
var premium = _premiums.GetUserPremium(currentUserId);
|
|
||||||
if (premium != null && premium.PremiumUntil.HasValue && premium.PremiumUntil.Value > DateTime.UtcNow)
|
|
||||||
{
|
|
||||||
return await ServeFile(app.FileId.Value, app.PdfFileName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return StatusCode((int)HttpStatusCode.Forbidden);
|
|
||||||
}
|
|
||||||
return StatusCode((int)HttpStatusCode.Forbidden);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<IActionResult> ServeFile(int fileId, string downloadName)
|
private async Task<IActionResult> ServeFile(int fileId, string downloadName)
|
||||||
{
|
{
|
||||||
var file = _files.GetFile(fileId);
|
var file = _files.GetFile(fileId);
|
||||||
if (file != null)
|
if (file != null)
|
||||||
{
|
{
|
||||||
var path = _files.GetFilePath(file);
|
var path = _files.GetFilePath(file);
|
||||||
if (System.IO.File.Exists(path))
|
if (System.IO.File.Exists(path))
|
||||||
{
|
{
|
||||||
var bytes = await System.IO.File.ReadAllBytesAsync(path);
|
var bytes = await System.IO.File.ReadAllBytesAsync(path);
|
||||||
return File(bytes, "application/pdf", downloadName ?? file.Name);
|
return File(bytes, "application/pdf", downloadName ?? file.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
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;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace SZUAbsolventenverein.Module.PremiumArea.Migrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(PremiumAreaContext))]
|
|
||||||
[Migration("SZUAbsolventenverein.Module.PremiumArea.01.00.00.02")]
|
|
||||||
public class AddReportAndFileColumns : MultiDatabaseMigration
|
|
||||||
{
|
|
||||||
public AddReportAndFileColumns(IDatabase database) : base(database)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
// Add FileId (nullable int) - assuming missing
|
|
||||||
migrationBuilder.AddColumn<int>(
|
|
||||||
name: "FileId",
|
|
||||||
table: "SZUAbsolventenvereinEngineerApplications",
|
|
||||||
nullable: true);
|
|
||||||
|
|
||||||
// Add PdfFileName (string 256)
|
|
||||||
migrationBuilder.AddColumn<string>(
|
|
||||||
name: "PdfFileName",
|
|
||||||
table: "SZUAbsolventenvereinEngineerApplications",
|
|
||||||
maxLength: 256,
|
|
||||||
nullable: true);
|
|
||||||
|
|
||||||
// Add ApprovedOn (DateTime nullable) - might exist but adding if missing?
|
|
||||||
// MigrationBuilder will fail if exists. We assume schema drift needs this.
|
|
||||||
// If it exists, user must handle.
|
|
||||||
migrationBuilder.AddColumn<DateTime>(
|
|
||||||
name: "ApprovedOn",
|
|
||||||
table: "SZUAbsolventenvereinEngineerApplications",
|
|
||||||
nullable: true);
|
|
||||||
|
|
||||||
// Add IsReported (bool not null default false)
|
|
||||||
migrationBuilder.AddColumn<bool>(
|
|
||||||
name: "IsReported",
|
|
||||||
table: "SZUAbsolventenvereinEngineerApplications",
|
|
||||||
nullable: false,
|
|
||||||
defaultValue: false);
|
|
||||||
|
|
||||||
// Add ReportReason (string max nullable)
|
|
||||||
migrationBuilder.AddColumn<string>(
|
|
||||||
name: "ReportReason",
|
|
||||||
table: "SZUAbsolventenvereinEngineerApplications",
|
|
||||||
nullable: true);
|
|
||||||
|
|
||||||
// Add ReportCount (int not null default 0)
|
|
||||||
migrationBuilder.AddColumn<int>(
|
|
||||||
name: "ReportCount",
|
|
||||||
table: "SZUAbsolventenvereinEngineerApplications",
|
|
||||||
nullable: false,
|
|
||||||
defaultValue: 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "IsReported",
|
|
||||||
table: "SZUAbsolventenvereinEngineerApplications");
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "ReportReason",
|
|
||||||
table: "SZUAbsolventenvereinEngineerApplications");
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "ReportCount",
|
|
||||||
table: "SZUAbsolventenvereinEngineerApplications");
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "FileId",
|
|
||||||
table: "SZUAbsolventenvereinEngineerApplications");
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "PdfFileName",
|
|
||||||
table: "SZUAbsolventenvereinEngineerApplications");
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "ApprovedOn",
|
|
||||||
table: "SZUAbsolventenvereinEngineerApplications");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
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.02")]
|
||||||
|
public class RemoveReportingAndMoveToFileManager : MultiDatabaseMigration
|
||||||
|
{
|
||||||
|
public RemoveReportingAndMoveToFileManager(IDatabase database) : base(database)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
var engAppBuilder = new EngineerApplicationEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||||
|
engAppBuilder.DropColumn("PdfFileName");
|
||||||
|
engAppBuilder.DropColumn("IsReported");
|
||||||
|
engAppBuilder.DropColumn("ReportReason");
|
||||||
|
engAppBuilder.DropColumn("ReportCount");
|
||||||
|
engAppBuilder.DropColumn("AdminReviewedBy");
|
||||||
|
engAppBuilder.DropColumn("AdminReviewedAt");
|
||||||
|
engAppBuilder.DropColumn("AdminNote");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
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>
|
public class EngineerApplicationEntityBuilder : AuditableBaseEntityBuilder<EngineerApplicationEntityBuilder>
|
||||||
{
|
{
|
||||||
private const string _entityTableName = "SZUAbsolventenvereinEngineerApplications";
|
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;
|
EntityTableName = _entityTableName;
|
||||||
PrimaryKey = _primaryKey;
|
PrimaryKey = _primaryKey;
|
||||||
@@ -27,6 +33,8 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Migrations.EntityBuilders
|
|||||||
UserId = AddIntegerColumn(table, "UserId");
|
UserId = AddIntegerColumn(table, "UserId");
|
||||||
ModuleId = AddIntegerColumn(table, "ModuleId");
|
ModuleId = AddIntegerColumn(table, "ModuleId");
|
||||||
FileId = AddIntegerColumn(table, "FileId", true);
|
FileId = AddIntegerColumn(table, "FileId", true);
|
||||||
|
Title = AddStringColumn(table, "Title", 256, true);
|
||||||
|
ShortDescription = AddMaxStringColumn(table, "ShortDescription", true);
|
||||||
PdfFileName = AddStringColumn(table, "PdfFileName", 256);
|
PdfFileName = AddStringColumn(table, "PdfFileName", 256);
|
||||||
Status = AddStringColumn(table, "Status", 50);
|
Status = AddStringColumn(table, "Status", 50);
|
||||||
AdminReviewedBy = AddIntegerColumn(table, "AdminReviewedBy", true);
|
AdminReviewedBy = AddIntegerColumn(table, "AdminReviewedBy", true);
|
||||||
@@ -42,11 +50,12 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Migrations.EntityBuilders
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public OperationBuilder<AddColumnOperation> ApplicationId { get; set; }
|
public OperationBuilder<AddColumnOperation> ApplicationId { get; set; }
|
||||||
public OperationBuilder<AddColumnOperation> UserId { get; set; }
|
public OperationBuilder<AddColumnOperation> UserId { get; set; }
|
||||||
public OperationBuilder<AddColumnOperation> ModuleId { get; set; }
|
public OperationBuilder<AddColumnOperation> ModuleId { get; set; }
|
||||||
public OperationBuilder<AddColumnOperation> FileId { 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> PdfFileName { get; set; }
|
||||||
public OperationBuilder<AddColumnOperation> Status { get; set; }
|
public OperationBuilder<AddColumnOperation> Status { get; set; }
|
||||||
public OperationBuilder<AddColumnOperation> AdminReviewedBy { get; set; }
|
public OperationBuilder<AddColumnOperation> AdminReviewedBy { get; set; }
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -52,23 +53,61 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Repository
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return db.EngineerApplication.AsNoTracking().FirstOrDefault(item => item.ApplicationId == ApplicationId);
|
return db.EngineerApplication.AsNoTracking()
|
||||||
|
.FirstOrDefault(item => item.ApplicationId == ApplicationId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public EngineerApplication AddEngineerApplication(EngineerApplication EngineerApplication)
|
public EngineerApplication AddEngineerApplication(EngineerApplication EngineerApplication)
|
||||||
{
|
{
|
||||||
using var db = _factory.CreateDbContext();
|
using var db = _factory.CreateDbContext();
|
||||||
db.EngineerApplication.Add(EngineerApplication);
|
try
|
||||||
db.SaveChanges();
|
{
|
||||||
|
EngineerApplication.CreatedBy = EngineerApplication.CreatedBy ?? "system";
|
||||||
|
EngineerApplication.CreatedOn = DateTime.UtcNow;
|
||||||
|
EngineerApplication.ModifiedBy = EngineerApplication.ModifiedBy ?? "system";
|
||||||
|
EngineerApplication.ModifiedOn = DateTime.UtcNow;
|
||||||
|
db.EngineerApplication.Add(EngineerApplication);
|
||||||
|
db.SaveChanges();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Throwing a new exception with more details so it's visible
|
||||||
|
var msg = $"DB Error: {ex.Message} | Inner: {ex.InnerException?.Message}";
|
||||||
|
Console.WriteLine(msg); // Log to console for dotnet run
|
||||||
|
throw new Exception(msg, ex);
|
||||||
|
}
|
||||||
|
|
||||||
return EngineerApplication;
|
return EngineerApplication;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EngineerApplication UpdateEngineerApplication(EngineerApplication EngineerApplication)
|
public EngineerApplication UpdateEngineerApplication(EngineerApplication EngineerApplication)
|
||||||
{
|
{
|
||||||
using var db = _factory.CreateDbContext();
|
using var db = _factory.CreateDbContext();
|
||||||
db.Entry(EngineerApplication).State = EntityState.Modified;
|
try
|
||||||
db.SaveChanges();
|
{
|
||||||
|
var existing = db.EngineerApplication.Find(EngineerApplication.ApplicationId);
|
||||||
|
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;
|
||||||
|
|
||||||
|
existing.ModifiedBy = EngineerApplication.ModifiedBy ?? "system";
|
||||||
|
existing.ModifiedOn = DateTime.UtcNow;
|
||||||
|
db.SaveChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var msg = $"DB Error (Update): {ex.Message} | Inner: {ex.InnerException?.Message}";
|
||||||
|
Console.WriteLine(msg);
|
||||||
|
throw new Exception(msg, ex);
|
||||||
|
}
|
||||||
|
|
||||||
return EngineerApplication;
|
return EngineerApplication;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -51,6 +52,10 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Repository
|
|||||||
public Models.PremiumArea AddPremiumArea(Models.PremiumArea PremiumArea)
|
public Models.PremiumArea AddPremiumArea(Models.PremiumArea PremiumArea)
|
||||||
{
|
{
|
||||||
using var db = _factory.CreateDbContext();
|
using var db = _factory.CreateDbContext();
|
||||||
|
PremiumArea.CreatedBy = PremiumArea.CreatedBy ?? "system";
|
||||||
|
PremiumArea.CreatedOn = DateTime.UtcNow;
|
||||||
|
PremiumArea.ModifiedBy = PremiumArea.ModifiedBy ?? "system";
|
||||||
|
PremiumArea.ModifiedOn = DateTime.UtcNow;
|
||||||
db.PremiumArea.Add(PremiumArea);
|
db.PremiumArea.Add(PremiumArea);
|
||||||
db.SaveChanges();
|
db.SaveChanges();
|
||||||
return PremiumArea;
|
return PremiumArea;
|
||||||
@@ -59,8 +64,14 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Repository
|
|||||||
public Models.PremiumArea UpdatePremiumArea(Models.PremiumArea PremiumArea)
|
public Models.PremiumArea UpdatePremiumArea(Models.PremiumArea PremiumArea)
|
||||||
{
|
{
|
||||||
using var db = _factory.CreateDbContext();
|
using var db = _factory.CreateDbContext();
|
||||||
db.Entry(PremiumArea).State = EntityState.Modified;
|
var existing = db.PremiumArea.Find(PremiumArea.PremiumAreaId);
|
||||||
db.SaveChanges();
|
if (existing != null)
|
||||||
|
{
|
||||||
|
existing.Name = PremiumArea.Name;
|
||||||
|
existing.ModifiedBy = PremiumArea.ModifiedBy ?? "system";
|
||||||
|
existing.ModifiedOn = DateTime.UtcNow;
|
||||||
|
db.SaveChanges();
|
||||||
|
}
|
||||||
return PremiumArea;
|
return PremiumArea;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -34,19 +35,35 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Repository
|
|||||||
using var db = _factory.CreateDbContext();
|
using var db = _factory.CreateDbContext();
|
||||||
if (UserPremium.Id > 0)
|
if (UserPremium.Id > 0)
|
||||||
{
|
{
|
||||||
db.Entry(UserPremium).State = EntityState.Modified;
|
var existing = db.UserPremium.Find(UserPremium.Id);
|
||||||
|
if (existing != null)
|
||||||
|
{
|
||||||
|
existing.UserId = UserPremium.UserId;
|
||||||
|
existing.PremiumUntil = UserPremium.PremiumUntil;
|
||||||
|
existing.ModifiedBy = UserPremium.ModifiedBy ?? "system";
|
||||||
|
existing.ModifiedOn = DateTime.UtcNow;
|
||||||
|
db.SaveChanges();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
UserPremium.CreatedBy = UserPremium.CreatedBy ?? "system";
|
||||||
|
UserPremium.CreatedOn = DateTime.UtcNow;
|
||||||
|
UserPremium.ModifiedBy = UserPremium.ModifiedBy ?? "system";
|
||||||
|
UserPremium.ModifiedOn = DateTime.UtcNow;
|
||||||
db.UserPremium.Add(UserPremium);
|
db.UserPremium.Add(UserPremium);
|
||||||
|
db.SaveChanges();
|
||||||
}
|
}
|
||||||
db.SaveChanges();
|
|
||||||
return UserPremium;
|
return UserPremium;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddPremiumEvent(PremiumEvent premiumEvent)
|
public void AddPremiumEvent(PremiumEvent premiumEvent)
|
||||||
{
|
{
|
||||||
using var db = _factory.CreateDbContext();
|
using var db = _factory.CreateDbContext();
|
||||||
|
premiumEvent.CreatedBy = premiumEvent.CreatedBy ?? "system";
|
||||||
|
premiumEvent.CreatedOn = DateTime.UtcNow;
|
||||||
|
premiumEvent.ModifiedBy = premiumEvent.ModifiedBy ?? "system";
|
||||||
|
premiumEvent.ModifiedOn = DateTime.UtcNow;
|
||||||
db.PremiumEvent.Add(premiumEvent);
|
db.PremiumEvent.Add(premiumEvent);
|
||||||
db.SaveChanges();
|
db.SaveChanges();
|
||||||
}
|
}
|
||||||
|
|||||||
98
Server/Services/PremiumService.cs
Normal file
98
Server/Services/PremiumService.cs
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
using System;
|
||||||
|
using SZUAbsolventenverein.Module.PremiumArea.Models;
|
||||||
|
using SZUAbsolventenverein.Module.PremiumArea.Repository;
|
||||||
|
using Oqtane.Infrastructure;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
using Oqtane.Modules;
|
||||||
|
using Oqtane.Enums;
|
||||||
|
|
||||||
|
namespace SZUAbsolventenverein.Module.PremiumArea.Services
|
||||||
|
{
|
||||||
|
public interface IPremiumService
|
||||||
|
{
|
||||||
|
bool IsPremium(int userId);
|
||||||
|
DateTime? GetPremiumUntil(int userId);
|
||||||
|
void GrantPremium(int userId, int durationMonths, string source, string referenceId = null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PremiumService : IPremiumService, ITransientService
|
||||||
|
{
|
||||||
|
private readonly IUserPremiumRepository _userPremiumRepository;
|
||||||
|
private readonly ILogManager _logger;
|
||||||
|
|
||||||
|
public PremiumService(IUserPremiumRepository userPremiumRepository, ILogManager logger)
|
||||||
|
{
|
||||||
|
_userPremiumRepository = userPremiumRepository;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPremium(int userId)
|
||||||
|
{
|
||||||
|
var premium = _userPremiumRepository.GetUserPremium(userId);
|
||||||
|
return premium != null && premium.PremiumUntil.HasValue && premium.PremiumUntil.Value > DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime? GetPremiumUntil(int userId)
|
||||||
|
{
|
||||||
|
var premium = _userPremiumRepository.GetUserPremium(userId);
|
||||||
|
return premium?.PremiumUntil;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GrantPremium(int userId, int durationMonths, string source, string referenceId = null)
|
||||||
|
{
|
||||||
|
var current = _userPremiumRepository.GetUserPremium(userId);
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
|
||||||
|
DateTime startBase = now;
|
||||||
|
if (current != null && current.PremiumUntil.HasValue && current.PremiumUntil.Value > now)
|
||||||
|
{
|
||||||
|
startBase = current.PremiumUntil.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newUntil = startBase.AddMonths(durationMonths);
|
||||||
|
|
||||||
|
// delta days for audit
|
||||||
|
int deltaDays = (newUntil - (current?.PremiumUntil ?? now)).Days;
|
||||||
|
// correction: actually we want to know how many days we ADDED to the existing flow.
|
||||||
|
// If they had 0 days separately, we added durationMonths * 30 approx.
|
||||||
|
// If they had existing time, we added durationMonths.
|
||||||
|
// Audit Log usually tracks "What did this action add?". It added 1 year.
|
||||||
|
// But let's calculate days added relative to "previous state".
|
||||||
|
// If expired: Added (NewUntil - Now) days.
|
||||||
|
// If active: Added (NewUntil - OldUntil) = durationMonths roughly.
|
||||||
|
|
||||||
|
// Simpler: Just store the DurationMonths converted to days roughly, or the stored delta.
|
||||||
|
int addedDays = (newUntil - startBase).Days;
|
||||||
|
|
||||||
|
if (current == null)
|
||||||
|
{
|
||||||
|
current = new UserPremium
|
||||||
|
{
|
||||||
|
UserId = userId,
|
||||||
|
CreatedOn = now,
|
||||||
|
ModifiedOn = now
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
current.PremiumUntil = newUntil;
|
||||||
|
current.Source = source;
|
||||||
|
current.ModifiedOn = now;
|
||||||
|
|
||||||
|
_userPremiumRepository.SaveUserPremium(current);
|
||||||
|
|
||||||
|
// Audit
|
||||||
|
var audit = new PremiumEvent
|
||||||
|
{
|
||||||
|
UserId = userId,
|
||||||
|
DeltaDays = addedDays,
|
||||||
|
Source = source,
|
||||||
|
ReferenceId = referenceId,
|
||||||
|
CreatedOn = now,
|
||||||
|
ModifiedOn = now
|
||||||
|
};
|
||||||
|
_userPremiumRepository.AddPremiumEvent(audit);
|
||||||
|
|
||||||
|
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Granted Premium for User {UserId} until {Until}", userId, newUntil);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,7 +22,9 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Services
|
|||||||
private readonly IHttpContextAccessor _accessor;
|
private readonly IHttpContextAccessor _accessor;
|
||||||
private readonly Alias _alias;
|
private readonly Alias _alias;
|
||||||
|
|
||||||
public ServerEngineerApplicationService(IEngineerApplicationRepository repository, IPremiumService premiumService, IUserPermissions userPermissions, ITenantManager tenantManager, ILogManager logger, IHttpContextAccessor accessor)
|
public ServerEngineerApplicationService(IEngineerApplicationRepository repository,
|
||||||
|
IPremiumService premiumService, IUserPermissions userPermissions, ITenantManager tenantManager,
|
||||||
|
ILogManager logger, IHttpContextAccessor accessor)
|
||||||
{
|
{
|
||||||
_repository = repository;
|
_repository = repository;
|
||||||
_premiumService = premiumService;
|
_premiumService = premiumService;
|
||||||
@@ -34,42 +36,51 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Services
|
|||||||
|
|
||||||
public Task<List<EngineerApplication>> GetApplicationsAsync(int ModuleId)
|
public Task<List<EngineerApplication>> GetApplicationsAsync(int ModuleId)
|
||||||
{
|
{
|
||||||
var user = _accessor.HttpContext.User;
|
var user = _accessor.HttpContext.User;
|
||||||
if (_userPermissions.IsAuthorized(user, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.Edit)) // Admin/Edit
|
if (_userPermissions.IsAuthorized(user, _alias.SiteId, EntityNames.Module, ModuleId,
|
||||||
{
|
PermissionNames.Edit)) // Admin/Edit
|
||||||
return Task.FromResult(_repository.GetEngineerApplications(ModuleId).ToList());
|
{
|
||||||
}
|
return Task.FromResult(_repository.GetEngineerApplications(ModuleId).ToList());
|
||||||
|
}
|
||||||
// Check if Premium
|
|
||||||
if (IsUserPremium(user))
|
|
||||||
{
|
|
||||||
// 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());
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.FromResult(new List<EngineerApplication>());
|
var userId = _accessor.HttpContext.GetUserId();
|
||||||
|
var results = new List<EngineerApplication>();
|
||||||
|
|
||||||
|
// Always include the user's own applications (needed for Apply.razor)
|
||||||
|
if (userId != -1)
|
||||||
|
{
|
||||||
|
var ownApps = _repository.GetEngineerApplications(ModuleId)
|
||||||
|
.Where(a => a.UserId == userId).ToList();
|
||||||
|
results.AddRange(ownApps);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
public Task<List<EngineerApplication>> GetApplicationsAsync(int ModuleId, string status)
|
||||||
{
|
{
|
||||||
var user = _accessor.HttpContext.User;
|
var user = _accessor.HttpContext.User;
|
||||||
if (_userPermissions.IsAuthorized(user, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.Edit))
|
if (_userPermissions.IsAuthorized(user, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.Edit))
|
||||||
{
|
{
|
||||||
return Task.FromResult(_repository.GetEngineerApplications(ModuleId, status).ToList());
|
return Task.FromResult(_repository.GetEngineerApplications(ModuleId, status).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((status == "Approved" || status == "Published") && IsUserPremium(user))
|
if ((status == "Approved" || status == "Published") && IsUserPremium(user))
|
||||||
{
|
{
|
||||||
return Task.FromResult(_repository.GetEngineerApplications(ModuleId, status).ToList());
|
return Task.FromResult(_repository.GetEngineerApplications(ModuleId, status).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.FromResult(new List<EngineerApplication>());
|
return Task.FromResult(new List<EngineerApplication>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<EngineerApplication> GetApplicationAsync(int ApplicationId, int ModuleId)
|
public Task<EngineerApplication> GetApplicationAsync(int ApplicationId, int ModuleId)
|
||||||
@@ -81,7 +92,8 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Services
|
|||||||
var userId = _accessor.HttpContext.GetUserId();
|
var userId = _accessor.HttpContext.GetUserId();
|
||||||
|
|
||||||
// Allow if Admin OR Owner OR (Premium AND (Approved OR Published))
|
// Allow if Admin OR Owner OR (Premium AND (Approved OR Published))
|
||||||
bool isAdmin = _userPermissions.IsAuthorized(user, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.Edit);
|
bool isAdmin = _userPermissions.IsAuthorized(user, _alias.SiteId, EntityNames.Module, ModuleId,
|
||||||
|
PermissionNames.Edit);
|
||||||
bool isOwner = (userId != -1 && app.UserId == userId);
|
bool isOwner = (userId != -1 && app.UserId == userId);
|
||||||
bool isPremiumViewer = ((app.Status == "Approved" || app.Status == "Published") && IsUserPremium(user));
|
bool isPremiumViewer = ((app.Status == "Approved" || app.Status == "Published") && IsUserPremium(user));
|
||||||
|
|
||||||
@@ -97,21 +109,22 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Services
|
|||||||
{
|
{
|
||||||
var user = _accessor.HttpContext.User;
|
var user = _accessor.HttpContext.User;
|
||||||
var userId = _accessor.HttpContext.GetUserId();
|
var userId = _accessor.HttpContext.GetUserId();
|
||||||
|
|
||||||
if (userId == -1) // Not logged in
|
if (userId == -1) // Not logged in
|
||||||
{
|
{
|
||||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Add Attempt (Anonymous)");
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Add Attempt (Anonymous)");
|
||||||
return Task.FromResult<EngineerApplication>(null);
|
return Task.FromResult<EngineerApplication>(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if allowed to view module (Registered Users usually can View)
|
// Check if allowed to view module (Registered Users usually can View)
|
||||||
if (_userPermissions.IsAuthorized(user, _alias.SiteId, EntityNames.Module, Application.ModuleId, PermissionNames.View))
|
if (_userPermissions.IsAuthorized(user, _alias.SiteId, EntityNames.Module, Application.ModuleId,
|
||||||
|
PermissionNames.View))
|
||||||
{
|
{
|
||||||
Application.UserId = userId;
|
Application.UserId = userId;
|
||||||
// Auto-publish if file uploaded (Checked by Status=Published from client)
|
// Auto-publish if file uploaded (Checked by Status=Published from client)
|
||||||
// If client sends "Published", we accept it.
|
// If client sends "Published", we accept it.
|
||||||
if (string.IsNullOrEmpty(Application.Status)) Application.Status = "Draft";
|
if (string.IsNullOrEmpty(Application.Status)) Application.Status = "Draft";
|
||||||
|
|
||||||
// Set ApprovedOn if Published? user asked for removal of admin approval.
|
// Set ApprovedOn if Published? user asked for removal of admin approval.
|
||||||
if (Application.Status == "Published")
|
if (Application.Status == "Published")
|
||||||
{
|
{
|
||||||
@@ -120,9 +133,11 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
Application = _repository.AddEngineerApplication(Application);
|
Application = _repository.AddEngineerApplication(Application);
|
||||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Application Added {Application}", Application);
|
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Application Added {Application}",
|
||||||
|
Application);
|
||||||
return Task.FromResult(Application);
|
return Task.FromResult(Application);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.FromResult<EngineerApplication>(null);
|
return Task.FromResult<EngineerApplication>(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,196 +148,179 @@ namespace SZUAbsolventenverein.Module.PremiumArea.Services
|
|||||||
|
|
||||||
var user = _accessor.HttpContext.User;
|
var user = _accessor.HttpContext.User;
|
||||||
var userId = _accessor.HttpContext.GetUserId();
|
var userId = _accessor.HttpContext.GetUserId();
|
||||||
bool isAdmin = _userPermissions.IsAuthorized(user, _alias.SiteId, EntityNames.Module, Application.ModuleId, PermissionNames.Edit);
|
bool isAdmin = _userPermissions.IsAuthorized(user, _alias.SiteId, EntityNames.Module, Application.ModuleId,
|
||||||
|
PermissionNames.Edit);
|
||||||
if (isAdmin || (existing.UserId == userId && existing.Status == "Draft")) // Only owner can edit if Draft
|
bool isOwner = (userId != -1 && existing.UserId == userId);
|
||||||
|
|
||||||
|
if (isAdmin || isOwner)
|
||||||
{
|
{
|
||||||
if (!isAdmin)
|
if (!isAdmin)
|
||||||
{
|
{
|
||||||
Application.Status = existing.Status == "Draft" && Application.Status == "Submitted" ? "Published" : existing.Status; // Auto-publish on submit
|
// Owner can update their own application
|
||||||
// If client sends "Published" (which Apply.razor does), apply it.
|
// Accept "Published" status from client (auto-publish without admin approval)
|
||||||
if (Application.Status == "Published" && existing.Status == "Draft")
|
if (Application.Status == "Published")
|
||||||
{
|
{
|
||||||
Application.SubmittedOn = DateTime.UtcNow;
|
Application.SubmittedOn ??= DateTime.UtcNow;
|
||||||
Application.ApprovedOn = DateTime.UtcNow;
|
Application.ApprovedOn ??= DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Keep existing status if client didn't explicitly set to Published
|
||||||
|
Application.Status = existing.Status;
|
||||||
}
|
}
|
||||||
|
|
||||||
Application.AdminNote = existing.AdminNote;
|
|
||||||
Application.AdminReviewedBy = existing.AdminReviewedBy;
|
|
||||||
Application.AdminReviewedAt = existing.AdminReviewedAt;
|
|
||||||
// Preserve report status if user editing
|
|
||||||
Application.IsReported = existing.IsReported;
|
|
||||||
Application.ReportReason = existing.ReportReason;
|
|
||||||
Application.ReportCount = existing.ReportCount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Application = _repository.UpdateEngineerApplication(Application);
|
Application = _repository.UpdateEngineerApplication(Application);
|
||||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Application Updated {Application}", Application);
|
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Application Updated {Application}",
|
||||||
|
Application);
|
||||||
return Task.FromResult(Application);
|
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);
|
return Task.FromResult<EngineerApplication>(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task DeleteApplicationAsync(int ApplicationId, int ModuleId)
|
public Task DeleteApplicationAsync(int ApplicationId, int ModuleId)
|
||||||
{
|
{
|
||||||
var existing = _repository.GetEngineerApplication(ApplicationId);
|
var existing = _repository.GetEngineerApplication(ApplicationId);
|
||||||
var user = _accessor.HttpContext.User;
|
var user = _accessor.HttpContext.User;
|
||||||
var userId = _accessor.HttpContext.GetUserId();
|
var userId = _accessor.HttpContext.GetUserId();
|
||||||
bool isAdmin = _userPermissions.IsAuthorized(user, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.Edit);
|
bool isAdmin = _userPermissions.IsAuthorized(user, _alias.SiteId, EntityNames.Module, ModuleId,
|
||||||
|
PermissionNames.Edit);
|
||||||
|
|
||||||
if (existing != null && (isAdmin || existing.UserId == userId))
|
if (existing != null && (isAdmin || existing.UserId == userId))
|
||||||
{
|
{
|
||||||
_repository.DeleteEngineerApplication(ApplicationId);
|
_repository.DeleteEngineerApplication(ApplicationId);
|
||||||
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Application Deleted {Id}", ApplicationId);
|
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Application Deleted {Id}", ApplicationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom Methods not just CRUD
|
// Custom Methods not just CRUD
|
||||||
public Task ApproveApplicationAsync(int ApplicationId, int ModuleId)
|
public Task ApproveApplicationAsync(int ApplicationId, int ModuleId)
|
||||||
{
|
{
|
||||||
var user = _accessor.HttpContext.User;
|
var user = _accessor.HttpContext.User;
|
||||||
if (_userPermissions.IsAuthorized(user, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.Edit))
|
if (_userPermissions.IsAuthorized(user, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.Edit))
|
||||||
{
|
{
|
||||||
var app = _repository.GetEngineerApplication(ApplicationId);
|
var app = _repository.GetEngineerApplication(ApplicationId);
|
||||||
if (app != null)
|
if (app != null)
|
||||||
{
|
{
|
||||||
app.Status = "Approved"; // Keep Approved status for Admin explicitly approving (locking)
|
app.Status = "Approved"; // Keep Approved status for Admin explicitly approving (locking)
|
||||||
app.ApprovedOn = DateTime.UtcNow;
|
app.ApprovedOn = DateTime.UtcNow;
|
||||||
app.AdminReviewedBy = _accessor.HttpContext.GetUserId();
|
// app.ReportReason = null; // Optional: keep history?
|
||||||
app.AdminReviewedAt = DateTime.UtcNow;
|
// app.ReportCount = 0;
|
||||||
|
|
||||||
// Clear Reports
|
|
||||||
app.IsReported = false;
|
|
||||||
// app.ReportReason = null; // Optional: keep history?
|
|
||||||
// app.ReportCount = 0;
|
|
||||||
|
|
||||||
_repository.UpdateEngineerApplication(app);
|
_repository.UpdateEngineerApplication(app);
|
||||||
|
|
||||||
// Grant Premium
|
// Grant Premium
|
||||||
_premiumService.GrantPremium(app.UserId, 12, "engineer_application", $"AppId:{app.ApplicationId}");
|
_premiumService.GrantPremium(app.UserId, 12, "engineer_application", $"AppId:{app.ApplicationId}");
|
||||||
|
|
||||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Application Approved {Id}", ApplicationId);
|
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Application Approved {Id}",
|
||||||
}
|
ApplicationId);
|
||||||
}
|
}
|
||||||
return Task.CompletedTask;
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task RejectApplicationAsync(int ApplicationId, int ModuleId, string Reason)
|
public Task RejectApplicationAsync(int ApplicationId, int ModuleId, string Reason)
|
||||||
{
|
{
|
||||||
var user = _accessor.HttpContext.User;
|
var user = _accessor.HttpContext.User;
|
||||||
if (_userPermissions.IsAuthorized(user, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.Edit))
|
if (_userPermissions.IsAuthorized(user, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.Edit))
|
||||||
{
|
{
|
||||||
var app = _repository.GetEngineerApplication(ApplicationId);
|
var app = _repository.GetEngineerApplication(ApplicationId);
|
||||||
if (app != null)
|
if (app != null)
|
||||||
{
|
{
|
||||||
app.Status = "Rejected";
|
app.Status = "Rejected";
|
||||||
app.AdminNote = Reason;
|
_repository.UpdateEngineerApplication(app);
|
||||||
app.AdminReviewedBy = _accessor.HttpContext.GetUserId();
|
|
||||||
app.AdminReviewedAt = DateTime.UtcNow;
|
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Application Rejected {Id}",
|
||||||
_repository.UpdateEngineerApplication(app);
|
ApplicationId);
|
||||||
|
}
|
||||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Application Rejected {Id}", ApplicationId);
|
}
|
||||||
}
|
|
||||||
}
|
return Task.CompletedTask;
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task ReportApplicationAsync(int ApplicationId, int ModuleId, string Reason)
|
public Task ReportApplicationAsync(int ApplicationId, int ModuleId, string Reason)
|
||||||
{
|
{
|
||||||
// Allow any View authorized user to report?
|
// Allow any View authorized user to report?
|
||||||
// Or only Premium users?
|
// Or only Premium users?
|
||||||
// Users who can VIEW the application can report it.
|
// Users who can VIEW the application can report it.
|
||||||
// Since we restrict View to Premium (or Owner/Admin), we check that.
|
// Since we restrict View to Premium (or Owner/Admin), we check that.
|
||||||
|
|
||||||
// First, get the application to check existence
|
|
||||||
var app = _repository.GetEngineerApplication(ApplicationId);
|
|
||||||
if (app == null || app.ModuleId != ModuleId) return Task.CompletedTask;
|
|
||||||
|
|
||||||
var user = _accessor.HttpContext.User;
|
// First, get the application to check existence
|
||||||
|
var app = _repository.GetEngineerApplication(ApplicationId);
|
||||||
// Check if user is allowed to View this app
|
if (app == null || app.ModuleId != ModuleId) return Task.CompletedTask;
|
||||||
bool canView = false;
|
|
||||||
|
|
||||||
// Admin/Edit
|
|
||||||
if (_userPermissions.IsAuthorized(user, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.Edit)) canView = true;
|
|
||||||
|
|
||||||
// Premium
|
|
||||||
else if (IsUserPremium(user) && (app.Status == "Approved" || app.Status == "Published")) canView = true;
|
|
||||||
|
|
||||||
if (canView)
|
var user = _accessor.HttpContext.User;
|
||||||
{
|
|
||||||
app.IsReported = true;
|
// Check if user is allowed to View this app
|
||||||
app.ReportReason = Reason;
|
bool canView = false;
|
||||||
app.ReportCount++;
|
|
||||||
_repository.UpdateEngineerApplication(app);
|
// Admin/Edit
|
||||||
// Send Notification?
|
if (_userPermissions.IsAuthorized(user, _alias.SiteId, EntityNames.Module, ModuleId, PermissionNames.Edit))
|
||||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Application Reported {Id}", ApplicationId);
|
canView = true;
|
||||||
}
|
|
||||||
return Task.CompletedTask;
|
// Premium
|
||||||
|
else if (IsUserPremium(user) && (app.Status == "Approved" || app.Status == "Published")) canView = true;
|
||||||
|
|
||||||
|
if (canView)
|
||||||
|
{
|
||||||
|
_repository.UpdateEngineerApplication(app);
|
||||||
|
// Send Notification?
|
||||||
|
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Application Reported {Id}", ApplicationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsUserPremium(System.Security.Claims.ClaimsPrincipal user)
|
private bool IsUserPremium(System.Security.Claims.ClaimsPrincipal user)
|
||||||
{
|
{
|
||||||
// Oqtane's GetUserId() extension returns -1 if not found.
|
if (!user.Identity.IsAuthenticated)
|
||||||
// We need to parse User object myself or use "User.Identity.Name" to find user if needed.
|
return false;
|
||||||
// But _premiumService.IsPremium(userId) asks for Int.
|
|
||||||
// I'll assume I can get UserId.
|
// Check 1: Oqtane role "Premium Member" (matches the UI-level check in ApplicationList.razor)
|
||||||
// Since Oqtane is weird with Claims sometimes, I'll use the Helper Extension `GetUserId()`.
|
if (user.IsInRole("Premium Member"))
|
||||||
// But wait, "GetUserId" is an extension on HttpContext or ClaimsPrincipal?
|
return true;
|
||||||
// In Oqtane.Shared.UserSecurity class? Or Oqtane.Extensions?
|
|
||||||
// Usually `_accessor.HttpContext.GlobalUserId()` ??
|
// Check 2: Custom UserPremium DB table (for premium granted via GrantPremium/payment)
|
||||||
// Let's rely on standard DI/Context.
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
int userId = -1;
|
int userId = -1;
|
||||||
// Trying standard Oqtane way which is usually:
|
var claim = user.Claims.FirstOrDefault(item =>
|
||||||
if (user.Identity.IsAuthenticated)
|
item.Type == System.Security.Claims.ClaimTypes.NameIdentifier);
|
||||||
|
if (claim != null)
|
||||||
{
|
{
|
||||||
// Using Oqtane.Shared.PrincipalExtensions?
|
int.TryParse(claim.Value, out userId);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userId != -1)
|
if (userId != -1)
|
||||||
{
|
{
|
||||||
return _premiumService.IsPremium(userId);
|
return _premiumService.IsPremium(userId);
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quick helper for GetUserId if not present in Usings
|
// Quick helper for GetUserId if not present in Usings
|
||||||
public static class ClaimsPrincipalExtensions
|
public static class ClaimsPrincipalExtensions
|
||||||
{
|
{
|
||||||
public static int GetUserId(this HttpContext context)
|
public static int GetUserId(this HttpContext context)
|
||||||
{
|
{
|
||||||
if (context?.User?.Identity?.IsAuthenticated == true)
|
if (context?.User?.Identity?.IsAuthenticated == true)
|
||||||
{
|
{
|
||||||
var claim = context.User.Claims.FirstOrDefault(item => item.Type == System.Security.Claims.ClaimTypes.NameIdentifier);
|
var claim = context.User.Claims.FirstOrDefault(item =>
|
||||||
|
item.Type == System.Security.Claims.ClaimTypes.NameIdentifier);
|
||||||
if (claim != null && int.TryParse(claim.Value, out int userId))
|
if (claim != null && int.TryParse(claim.Value, out int userId))
|
||||||
{
|
{
|
||||||
return userId;
|
return userId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
|
||||||
}
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,33 +5,20 @@ using Oqtane.Models;
|
|||||||
|
|
||||||
namespace SZUAbsolventenverein.Module.PremiumArea.Models
|
namespace SZUAbsolventenverein.Module.PremiumArea.Models
|
||||||
{
|
{
|
||||||
[Table("SZUAbsolventenvereinEngineerApplications")]
|
[Table("SZUAbsolventenvereinEngineerApplications")]
|
||||||
public class EngineerApplication : ModelBase
|
public class EngineerApplication : ModelBase
|
||||||
{
|
{
|
||||||
[Key]
|
[Key] public int ApplicationId { get; set; }
|
||||||
public int ApplicationId { get; set; }
|
public int UserId { get; set; }
|
||||||
public int UserId { get; set; }
|
public int ModuleId { get; set; } // Context context
|
||||||
public int ModuleId { get; set; } // Context context
|
public int FileId { get; set; }
|
||||||
|
[StringLength(256)] public string Title { get; set; }
|
||||||
|
public string ShortDescription { get; set; }
|
||||||
|
|
||||||
[Required]
|
// Status: "Draft", "Submitted", "Approved", "Rejected"
|
||||||
public int? FileId { get; set; }
|
[StringLength(50)] public string Status { get; set; }
|
||||||
|
|
||||||
[StringLength(256)]
|
public DateTime? SubmittedOn { get; set; }
|
||||||
public string PdfFileName { get; set; }
|
public DateTime? ApprovedOn { get; set; }
|
||||||
|
}
|
||||||
public bool IsReported { get; set; }
|
|
||||||
public string ReportReason { get; set; }
|
|
||||||
public int ReportCount { get; set; }
|
|
||||||
|
|
||||||
// Status: "Draft", "Submitted", "Approved", "Rejected"
|
|
||||||
[StringLength(50)]
|
|
||||||
public string Status { get; set; }
|
|
||||||
|
|
||||||
public int? AdminReviewedBy { get; set; }
|
|
||||||
public DateTime? AdminReviewedAt { get; set; }
|
|
||||||
public string AdminNote { get; set; }
|
|
||||||
|
|
||||||
public DateTime? SubmittedOn { get; set; }
|
|
||||||
public DateTime? ApprovedOn { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user