DB Migrtation geändert und PDF upload funktioniert
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
|
||||
@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)
|
||||
{
|
||||
@@ -22,27 +22,27 @@
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
@foreach (var app in _applications)
|
||||
{
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Ingenieur-Antrag</h5>
|
||||
<h6 class="card-subtitle mb-2 text-muted">Benutzer ID: @app.UserId</h6>
|
||||
<p class="card-text">
|
||||
<strong>Datei:</strong> @app.PdfFileName<br/>
|
||||
<strong>Status:</strong> <span class="badge bg-success">@app.Status</span><br/>
|
||||
<strong>Datum:</strong> @(app.ApprovedOn?.ToShortDateString() ?? app.CreatedOn.ToShortDateString())
|
||||
</p>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-primary btn-sm" @onclick="@(async () => ShowDetail(app))">PDF ansehen</button>
|
||||
<a href="@((NavManager.BaseUri + "api/engineerapplication") + "/download/" + app.ApplicationId + "?moduleid=" + ModuleState.ModuleId)" target="_blank" class="btn btn-outline-secondary btn-sm">Herunterladen</a>
|
||||
<button class="btn btn-outline-danger btn-sm" @onclick="@(() => InitReport(app))">Melden</button>
|
||||
@foreach (var app in _applications)
|
||||
{
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Ingenieur-Antrag</h5>
|
||||
<h6 class="card-subtitle mb-2 text-muted">Benutzer ID: @app.UserId</h6>
|
||||
<p class="card-text">
|
||||
<strong>Datei:</strong> @app.FileId<br/>
|
||||
<strong>Status:</strong> <span class="badge bg-success">Veröffentlicht</span><br/>
|
||||
<strong>Datum:</strong> @(app.ApprovedOn?.ToShortDateString() ?? app.CreatedOn.ToShortDateString())
|
||||
</p>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-primary btn-sm" @onclick="@(async () => ShowDetail(app))">PDF ansehen</button>
|
||||
<a href="@((NavManager.BaseUri + "api/engineerapplication") + "/download/" + app.ApplicationId + "?moduleid=" + ModuleState.ModuleId)" target="_blank" class="btn btn-outline-secondary btn-sm">Herunterladen</a>
|
||||
<button class="btn btn-outline-danger btn-sm" @onclick="@(() => InitReport(app))">Melden</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -56,22 +56,22 @@ else
|
||||
@if (_selectedApp != null)
|
||||
{
|
||||
<div class="modal d-block" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Antrags-PDF (@_selectedApp.PdfFileName)</h5>
|
||||
<button type="button" class="btn-close" @onclick="@(() => _selectedApp = null)"></button>
|
||||
</div>
|
||||
<div class="modal-body p-0">
|
||||
<div class="ratio ratio-16x9" style="min-height: 500px;">
|
||||
<iframe src="@((NavManager.BaseUri + "api/engineerapplication") + "/download/" + _selectedApp.ApplicationId + "?moduleid=" + ModuleState.ModuleId)" allowfullscreen></iframe>
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Antrags-PDF (@_selectedApp.FileId)</h5>
|
||||
<button type="button" class="btn-close" @onclick="@(() => _selectedApp = null)"></button>
|
||||
</div>
|
||||
<div class="modal-body p-0">
|
||||
<div class="ratio ratio-16x9" style="min-height: 500px;">
|
||||
<iframe src="@((NavManager.BaseUri + "api/engineerapplication") + "/download/" + _selectedApp.ApplicationId + "?moduleid=" + ModuleState.ModuleId)" allowfullscreen></iframe>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" @onclick="@(() => _selectedApp = null)">Schließen</button>
|
||||
</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 class="modal-backdrop fade show"></div>
|
||||
}
|
||||
@@ -79,22 +79,22 @@ else
|
||||
@if (_reportApp != null)
|
||||
{
|
||||
<div class="modal d-block" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Antrag melden</h5>
|
||||
<button type="button" class="btn-close" @onclick="@(() => _reportApp = null)"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Bitte geben Sie einen Grund an, warum Sie diesen Antrag melden (Benutzer ID: @_reportApp.UserId, Datei: @_reportApp.PdfFileName).</p>
|
||||
<textarea class="form-control" rows="3" @bind="_reportReason" placeholder="Grund..."></textarea>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" @onclick="@(() => _reportApp = null)">Abbrechen</button>
|
||||
<button type="button" class="btn btn-danger" @onclick="SubmitReport">Meldung absenden</button>
|
||||
</div>
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Antrag melden</h5>
|
||||
<button type="button" class="btn-close" @onclick="@(() => _reportApp = null)"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Bitte geben Sie einen Grund an, warum Sie diesen Antrag melden (Benutzer ID: @_reportApp.UserId, Datei: @_reportApp.FileId).</p>
|
||||
<textarea class="form-control" rows="3" @bind="_reportReason" placeholder="Grund..."></textarea>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" @onclick="@(() => _reportApp = null)">Abbrechen</button>
|
||||
<button type="button" class="btn btn-danger" @onclick="SubmitReport">Meldung absenden</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-backdrop fade show"></div>
|
||||
}
|
||||
@@ -113,11 +113,11 @@ else
|
||||
{
|
||||
var published = await ApplicationService.GetApplicationsAsync(ModuleState.ModuleId, "Published");
|
||||
var approved = await ApplicationService.GetApplicationsAsync(ModuleState.ModuleId, "Approved");
|
||||
|
||||
|
||||
_applications = new List<EngineerApplication>();
|
||||
if (published != null) _applications.AddRange(published);
|
||||
if (approved != null) _applications.AddRange(approved);
|
||||
|
||||
|
||||
_applications = _applications.GroupBy(a => a.ApplicationId).Select(g => g.First()).ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -153,4 +153,5 @@ else
|
||||
AddModuleMessage("Fehler beim Melden: " + ex.Message, MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,83 +1,59 @@
|
||||
@using SZUAbsolventenverein.Module.PremiumArea.Services
|
||||
@using SZUAbsolventenverein.Module.PremiumArea.Models
|
||||
@using System.IO
|
||||
@using System.Net.Http.Headers
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
|
||||
@namespace SZUAbsolventenverein.Module.PremiumArea
|
||||
@inherits ModuleBase
|
||||
@inject IEngineerApplicationService ApplicationService
|
||||
@inject NavigationManager NavManager
|
||||
@inject IStringLocalizer<Apply> Localizer
|
||||
@inject HttpClient Http
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<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>
|
||||
|
||||
@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="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>
|
||||
<div class="card p-3">
|
||||
<p>Bitte laden Sie Ihren Ingenieur-Antrag als PDF-Datei hoch.</p>
|
||||
|
||||
@if (_selectedFile != null)
|
||||
{
|
||||
<div class="alert alert-success">
|
||||
Ausgewählt: <strong>@_selectedFile.Name</strong> (@(_selectedFile.Size / 1024) KB)
|
||||
</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> *@
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-warning">
|
||||
Antrags-Status: <strong>@_existingApp.Status</strong>. Sie können ihn derzeit nicht bearbeiten.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (_existingApp != null)
|
||||
@if (_existingApp.FileId > 0)
|
||||
{
|
||||
<div class="card">
|
||||
<div class="card-header">Ihr Antrag</div>
|
||||
<div class="card-body">
|
||||
<p><strong>Status:</strong> <span class="badge bg-@GetStatusColor(_existingApp.Status)">@_existingApp.Status</span></p>
|
||||
<p><strong>Datei:</strong> @_existingApp.PdfFileName</p>
|
||||
<p><strong>Datum:</strong> @_existingApp.CreatedOn.ToShortDateString()</p>
|
||||
|
||||
@if (_existingApp.Status == "Rejected")
|
||||
{
|
||||
<div class="alert alert-danger">
|
||||
<strong>Ablehnungsgrund:</strong> @_existingApp.AdminNote
|
||||
</div>
|
||||
<button class="btn btn-primary" @onclick="EditApp">Neuen Antrag einreichen / Aktualisieren</button>
|
||||
}
|
||||
else if (_existingApp.Status == "Draft")
|
||||
{
|
||||
<button class="btn btn-primary" @onclick="EditApp">Weiter bearbeiten</button>
|
||||
}
|
||||
</div>
|
||||
<div class="card-header">Ihr Antrag</div>
|
||||
<div class="card-body">
|
||||
<p>
|
||||
<strong>Status:</strong> <span class="badge bg-success">Veröffentlicht</span>
|
||||
</p>
|
||||
<p>
|
||||
<strong>Datum:</strong> @_existingApp.CreatedOn.ToShortDateString()
|
||||
</p>
|
||||
<button class="btn btn-primary" @onclick="EditApp">Antrag aktualisieren</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -85,49 +61,22 @@ else
|
||||
@code {
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||
|
||||
private EngineerApplication _existingApp;
|
||||
private IBrowserFile _selectedFile;
|
||||
private bool ShowForm = true;
|
||||
private string Message = "";
|
||||
private EngineerApplication _existingApp = new EngineerApplication();
|
||||
private bool _showForm = true;
|
||||
private string _message = "";
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// Load existing application for current user
|
||||
// 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
|
||||
try
|
||||
{
|
||||
var apps = await ApplicationService.GetApplicationsAsync(ModuleState.ModuleId);
|
||||
var userId = PageState.User?.UserId ?? -1;
|
||||
_existingApp = apps.FirstOrDefault(a => a.UserId == userId);
|
||||
|
||||
if (_existingApp != null)
|
||||
_existingApp.Status = "New";
|
||||
_existingApp = apps.FirstOrDefault(a => a.UserId == userId, _existingApp);
|
||||
|
||||
if (_existingApp.FileId > 0)
|
||||
{
|
||||
ShowForm = false;
|
||||
_showForm = false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -138,82 +87,47 @@ else
|
||||
|
||||
private void EditApp()
|
||||
{
|
||||
ShowForm = true;
|
||||
}
|
||||
|
||||
private void LoadFiles(InputFileChangeEventArgs e)
|
||||
{
|
||||
_selectedFile = e.File;
|
||||
Message = "";
|
||||
_showForm = true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
ApplicationId = _existingApp?.ApplicationId ?? 0,
|
||||
ModuleId = ModuleState.ModuleId,
|
||||
UserId = PageState.User.UserId, // Ensure UserID is set
|
||||
FileId = fileId,
|
||||
PdfFileName = fileName,
|
||||
FileId = _existingApp.FileId,
|
||||
Status = "Published", // Auto-publish
|
||||
SubmittedOn = DateTime.UtcNow,
|
||||
ApprovedOn = DateTime.UtcNow, // Auto-approved
|
||||
IsReported = false,
|
||||
ReportCount = 0
|
||||
};
|
||||
|
||||
if (app.ApplicationId == 0)
|
||||
{
|
||||
var result = await ApplicationService.AddApplicationAsync(app);
|
||||
_existingApp = result;
|
||||
var result = await ApplicationService.AddApplicationAsync(app);
|
||||
_existingApp = result;
|
||||
}
|
||||
else
|
||||
{
|
||||
await ApplicationService.UpdateApplicationAsync(app);
|
||||
_existingApp = await ApplicationService.GetApplicationAsync(app.ApplicationId, ModuleState.ModuleId);
|
||||
await ApplicationService.UpdateApplicationAsync(app);
|
||||
_existingApp = await ApplicationService.GetApplicationAsync(app.ApplicationId, ModuleState.ModuleId);
|
||||
}
|
||||
|
||||
ShowForm = false;
|
||||
Message = "Antrag erfolgreich gesendet.";
|
||||
_showForm = false;
|
||||
_message = "Antrag erfolgreich veröffentlicht.";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Message = "Fehler: " + ex.Message;
|
||||
_message = "Fehler: " + ex.Message;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,21 +136,11 @@ else
|
||||
NavManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
|
||||
private string GetStatusColor(string status)
|
||||
|
||||
private Task OnSelectFile(int fileId)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
"Approved" => "success",
|
||||
"Published" => "success", // Green for Published too
|
||||
"Rejected" => "danger",
|
||||
"Submitted" => "info",
|
||||
_ => "secondary"
|
||||
};
|
||||
}
|
||||
|
||||
private class UploadResult
|
||||
{
|
||||
public int FileId { get; set; }
|
||||
public string FileName { get; set; }
|
||||
_existingApp.FileId = fileId;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,13 +10,9 @@
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
|
||||
<div class="mb-3">
|
||||
<ActionLink Action="Apply" Text="Ingenieur Antrag hochladen" />
|
||||
<ActionLink Action="UserSearch" Text="Mitglieder finden" />
|
||||
@if (Oqtane.Security.UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||
{
|
||||
<ActionLink Action="AdminReview" Text="Admin Bereich" Security="SecurityAccessLevel.Edit" />
|
||||
}
|
||||
<ActionLink Action="ApplicationList" Text="Liste alle Ingenieur Anträge" />
|
||||
<ActionLink Action="Apply" Text="Ingenieur Antrag hochladen"/>
|
||||
<ActionLink Action="UserSearch" Text="Mitglieder finden"/>
|
||||
<ActionLink Action="ApplicationList" Text="Alle Ingenieur-Anträge"/>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
@@ -27,4 +23,5 @@
|
||||
new Stylesheet("_content/SZUAbsolventenverein.Module.PremiumArea/Module.css"),
|
||||
new Script("_content/SZUAbsolventenverein.Module.PremiumArea/Module.js")
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Modules;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace SZUAbsolventenverein.Module.PremiumArea
|
||||
{
|
||||
@@ -13,7 +14,9 @@ namespace SZUAbsolventenverein.Module.PremiumArea
|
||||
ServerManagerType = "SZUAbsolventenverein.Module.PremiumArea.Manager.PremiumAreaManager, SZUAbsolventenverein.Module.PremiumArea.Server.Oqtane",
|
||||
ReleaseVersions = "1.0.0,1.0.1,1.0.2",
|
||||
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}"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user