diff --git a/JetHerald/Contracts/IDb.cs b/JetHerald/Contracts/IDb.cs index 7ab8072..181e899 100644 --- a/JetHerald/Contracts/IDb.cs +++ b/JetHerald/Contracts/IDb.cs @@ -61,6 +61,7 @@ public class UserInvite public uint PlanId { get; set; } public uint RoleId { get; set; } public uint RedeemedBy { get; set; } + public string? RedeemedByLogin { get; set; } } public class UserSession diff --git a/JetHerald/Controllers/Ui/AdminController.cs b/JetHerald/Controllers/Ui/AdminController.cs new file mode 100644 index 0000000..e5dae97 --- /dev/null +++ b/JetHerald/Controllers/Ui/AdminController.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Mvc; +using JetHerald.Authorization; + +namespace JetHerald.Controllers.Ui; +[Permission("admin")] +[Route("ui/admin")] +public class AdminController : Controller +{ + public AdminController() { } + + [HttpGet] + public IActionResult Index() => View(); +} diff --git a/JetHerald/Controllers/Ui/AdminToolsController.cs b/JetHerald/Controllers/Ui/AdminInvitesController.cs similarity index 59% rename from JetHerald/Controllers/Ui/AdminToolsController.cs rename to JetHerald/Controllers/Ui/AdminInvitesController.cs index c3937f8..a826d89 100644 --- a/JetHerald/Controllers/Ui/AdminToolsController.cs +++ b/JetHerald/Controllers/Ui/AdminInvitesController.cs @@ -1,41 +1,35 @@ -using JetHerald.Authorization; -using JetHerald.Contracts; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using JetHerald.Authorization; using JetHerald.Options; using JetHerald.Services; - -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.ModelBinding; +using JetHerald.Contracts; namespace JetHerald.Controllers.Ui; -[Permission("admintools")] -public class AdminToolsController : Controller + +[Permission("admin.invites")] +[Route("ui/admin/invites")] +public class AdminInvitesController : Controller { Db Db { get; } - ILogger Log { get; } AuthConfig AuthCfg { get; } - public AdminToolsController( - ILogger log, + public AdminInvitesController( Db db, IOptionsSnapshot authCfg ) { Db = db; - Log = log; AuthCfg = authCfg.Value; } - [HttpGet, Route("ui/admintools/")] - public IActionResult Index() => View(); - - - [HttpGet, Route("ui/admintools/invites")] - public async Task ViewInvites() + [HttpGet] + public async Task Index() { using var ctx = await Db.GetContext(); var invites = await ctx.GetInvites(); var plans = await ctx.GetPlans(); var roles = await ctx.GetRoles(); - return View(new ViewInvitesModel + return View(new AdminInvitesModel { Invites = invites.ToArray(), Plans = plans.ToDictionary(p => p.PlanId), @@ -50,17 +44,35 @@ public class AdminToolsController : Controller [BindProperty(Name = "roleId"), BindRequired] public uint RoleId { get; set; } } - [HttpPost, Route("ui/admintools/invites/create")] + + [Permission("admin.invites.create")] + [HttpPost, Route("create")] public async Task CreateInvite(CreateInviteRequest req) { using var ctx = await Db.GetContext(); await ctx.CreateUserInvite(req.PlanId, req.RoleId, TokenHelper.GetToken(AuthCfg.InviteCodeLength)); ctx.Commit(); - return RedirectToAction(nameof(ViewInvites)); + return RedirectToAction(nameof(Index)); + } + + public class DeleteInviteRequest + { + [BindProperty(Name = "inviteId"), BindRequired] + public uint UserInviteId { get; set; } + } + + [Permission("admin.invites.delete")] + [HttpPost, Route("delete")] + public async Task DeleteInvite(DeleteInviteRequest req) + { + using var ctx = await Db.GetContext(); + await ctx.DeleteUserInvite(req.UserInviteId); + ctx.Commit(); + return RedirectToAction(nameof(Index)); } } -public class ViewInvitesModel +public class AdminInvitesModel { public UserInvite[] Invites { get; set; } public Dictionary Plans { get; set; } diff --git a/JetHerald/Services/DbContext.cs b/JetHerald/Services/DbContext.cs index 47e836a..fc8ecb4 100644 --- a/JetHerald/Services/DbContext.cs +++ b/JetHerald/Services/DbContext.cs @@ -56,7 +56,10 @@ public class DbContext : IDisposable public Task> GetRoles() => Tran.QueryAsync("SELECT * FROM role"); public Task> GetInvites() - => Tran.QueryAsync("SELECT * FROM userinvite"); + => Tran.QueryAsync(@" + SELECT ui.*, u.Login as RedeemedByLogin + FROM userinvite ui + LEFT JOIN user u ON ui.RedeemedBy = u.UserId"); public Task> GetHeartsForUser(uint userId) => Tran.QueryAsync( @@ -71,6 +74,11 @@ public class DbContext : IDisposable (@planId, @roleId, @inviteCode)", new { planId, roleId, inviteCode }); + public Task DeleteUserInvite(uint inviteId) + => Tran.ExecuteAsync(@" DELETE FROM userinvite WHERE UserInviteId = @intiteId", + new { inviteId }); + + public Task GetTopic(string name) => Tran.QuerySingleOrDefaultAsync( "SELECT * FROM topic WHERE Name = @name", diff --git a/JetHerald/Views/Admin/Index.cshtml b/JetHerald/Views/Admin/Index.cshtml new file mode 100644 index 0000000..9830753 --- /dev/null +++ b/JetHerald/Views/Admin/Index.cshtml @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/JetHerald/Views/AdminTools/ViewInvites.cshtml b/JetHerald/Views/AdminInvites/Index.cshtml similarity index 59% rename from JetHerald/Views/AdminTools/ViewInvites.cshtml rename to JetHerald/Views/AdminInvites/Index.cshtml index 3bb6c96..d00c5a7 100644 --- a/JetHerald/Views/AdminTools/ViewInvites.cshtml +++ b/JetHerald/Views/AdminInvites/Index.cshtml @@ -1,6 +1,6 @@ -@model ViewInvitesModel +@model AdminInvitesModel -
+
- 📤 + @if (invite.RedeemedBy == default) + { + + 📤 + + } + else + { + + @invite.RedeemedBy:@(invite.RedeemedByLogin ?? "deleted user") + + } } \ No newline at end of file diff --git a/JetHerald/Views/AdminTools/Index.cshtml b/JetHerald/Views/AdminTools/Index.cshtml deleted file mode 100644 index 24e1808..0000000 --- a/JetHerald/Views/AdminTools/Index.cshtml +++ /dev/null @@ -1,6 +0,0 @@ - \ No newline at end of file diff --git a/JetHerald/Views/Shared/_Layout.cshtml b/JetHerald/Views/Shared/_Layout.cshtml index 13e8d2a..fc1896f 100644 --- a/JetHerald/Views/Shared/_Layout.cshtml +++ b/JetHerald/Views/Shared/_Layout.cshtml @@ -21,9 +21,9 @@ { Dashboard } - @if (Context.UserCan("admintools")) + @if (Context.UserCan("admin")) { - Admin tools + Admin tools } @if (User.IsAnonymous()) {