Admin suite: monitoring dashboard, user management/ban, broadcast, reports, SMS test
CI/CD / CI · dotnet build (push) Failing after 1m40s
CI/CD / Deploy · hamkadr (push) Has been skipped

- /Admin/Overview: platform monitoring stats (users by role, facilities, listings, applies, push subs, queue, reports, bans)
- /Admin/Users: search/filter + ban/unban (User.IsBanned + reason); banned users blocked at login
- /Admin/Broadcast: send announcement (in-app + web push) to all / staff / employers via NotificationService
- Reports: report button on shift/job detail → /report endpoint → /Admin/Reports (resolve/dismiss)
- Settings: 'send test SMS' button; admin cross-nav links; SMS API config already in place
- migration AdminBanReports; verified overview/users/broadcast/report persist

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-04 13:19:20 +03:30
parent b46bd49c32
commit eae38373b9
26 changed files with 1689 additions and 4 deletions
+21
View File
@@ -5,6 +5,7 @@ using JobsMedical.Web.Models;
using JobsMedical.Web.Services;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
@@ -157,6 +158,26 @@ app.MapPost("/push/subscribe", async (PushSubscriptionDto dto, AppDbContext db,
return Results.Ok();
});
// User-submitted report against a listing (abuse/fake/wrong info).
app.MapPost("/report", async (HttpContext ctx, AppDbContext db, VisitorContext vc,
[FromForm] string targetType, [FromForm] int targetId, [FromForm] string reason,
[FromForm] string? label, [FromForm] string? returnUrl) =>
{
if (!string.IsNullOrWhiteSpace(reason) && Enum.TryParse<ReportTargetType>(targetType, true, out var tt))
{
int? uid = ctx.User?.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier) is { } c
&& int.TryParse(c.Value, out var n) ? n : null;
db.Reports.Add(new Report
{
TargetType = tt, TargetId = targetId, TargetLabel = label,
Reason = reason.Trim()[..Math.Min(reason.Trim().Length, 500)],
ReporterUserId = uid, ReporterVisitorId = vc.VisitorId,
});
await db.SaveChangesAsync();
}
return Results.Redirect((string.IsNullOrWhiteSpace(returnUrl) ? "/" : returnUrl) + "?reported=1");
}).DisableAntiforgery();
app.MapGet("/sw.js", () => Results.Content("""
const CACHE = 'hamkadr-v1';
self.addEventListener('install', e => { self.skipWaiting(); e.waitUntil(caches.open(CACHE).then(c => c.addAll(['/']))); });