diff --git a/JetHerald/Controllers/Ui/AdminUsersController.cs b/JetHerald/Controllers/Ui/AdminUsersController.cs new file mode 100644 index 0000000..a269dbb --- /dev/null +++ b/JetHerald/Controllers/Ui/AdminUsersController.cs @@ -0,0 +1,61 @@ +using JetHerald.Authorization; +using JetHerald.Contracts; +using JetHerald.Services; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace JetHerald.Controllers.Ui; + +[Permission("admin.users")] +[Route("ui/admin/users")] +public class AdminUsersController : Controller +{ + Db Db { get; } + public AdminUsersController(Db db) + { + Db = db; + } + + [HttpGet] + public async Task Index() + { + using var ctx = await Db.GetContext(); + + var users = await ctx.GetUsers(); + var plans = await ctx.GetPlans(); + var roles = await ctx.GetRoles(); + + return View(new AdminUsersModel + { + Users = users.ToArray(), + Plans = plans.ToDictionary(p => p.PlanId), + Roles = roles.ToDictionary(r => r.RoleId) + }); + } + + public class SetPermsRequest + { + [BindProperty(Name = "planId"), BindRequired] + public uint PlanId { get; set; } + [BindProperty(Name = "roleId"), BindRequired] + public uint RoleId { get; set; } + } + + [Permission("admin.users.setperms")] + [HttpPost, Route("setperms/{userId?}")] + public async Task SetPerms([FromRoute] uint userId, SetPermsRequest req) + { + using var ctx = await Db.GetContext(); + await ctx.UpdatePerms(userId, req.PlanId, req.RoleId); + ctx.Commit(); + + return RedirectToAction(nameof(Index)); + } +} + +public class AdminUsersModel +{ + public User[] Users { get; set; } + public Dictionary Plans { get; set; } + public Dictionary Roles { get; set; } +} \ No newline at end of file diff --git a/JetHerald/JetHerald.csproj b/JetHerald/JetHerald.csproj index e092bb3..e32830e 100644 --- a/JetHerald/JetHerald.csproj +++ b/JetHerald/JetHerald.csproj @@ -2,18 +2,18 @@ net6.0 - en - false - false + en + false + false - - + + - + \ No newline at end of file diff --git a/JetHerald/Services/DbContext.cs b/JetHerald/Services/DbContext.cs index 3ed3b93..03bdaed 100644 --- a/JetHerald/Services/DbContext.cs +++ b/JetHerald/Services/DbContext.cs @@ -47,26 +47,39 @@ public class DbContext : IDisposable Tran.Dispose(); Conn.Dispose(); } - public Task> GetTopicsForUser(uint userId) + public Task> GetTopicsForUser(uint userId) => Tran.QueryAsync( " SELECT * FROM topic WHERE CreatorId = @userId", new { userId }); - public Task> GetPlans() + + public Task UpdatePerms(uint userId, uint planId, uint roleId) + => Tran.ExecuteAsync(@" + UPDATE user + SET PlanId = @planId, + RoleId = @roleId + WHERE UserId = @userId", + new { userId, planId, roleId }); + + public Task> GetPlans() => Tran.QueryAsync("SELECT * FROM plan"); - public Task> GetRoles() + public Task> GetRoles() => Tran.QueryAsync("SELECT * FROM role"); - public Task> GetInvites() + public Task> GetInvites() => Tran.QueryAsync(@" SELECT ui.*, u.Login as RedeemedByLogin FROM userinvite ui LEFT JOIN user u ON ui.RedeemedBy = u.UserId"); + public Task> GetUsers() + => Tran.QueryAsync(@" + SELECT u.* + FROM user u;"); public Task> GetHeartsForUser(uint userId) => Tran.QueryAsync( " SELECT h.* FROM heart h JOIN topic USING (TopicId) WHERE CreatorId = @userId", new { userId }); - public Task CreateUserInvite(uint planId, uint roleId, string inviteCode) + public Task CreateUserInvite(uint planId, uint roleId, string inviteCode) => Tran.ExecuteAsync(@" INSERT INTO userinvite ( PlanId, RoleId, InviteCode) @@ -97,11 +110,11 @@ public class DbContext : IDisposable " WHERE ReadToken = @token", new { token, sub }); - public Task> GetHeartsForTopic(uint topicId) + public Task> GetHeartsForTopic(uint topicId) => Tran.QueryAsync( " SELECT * FROM heart WHERE TopicId = @topicId", new { topicId }); - public Task GetUser(string login) + public Task GetUser(string login) => Tran.QuerySingleOrDefaultAsync(@" SELECT u.*, up.*, ur.* FROM user u @@ -154,30 +167,30 @@ public class DbContext : IDisposable ( Login, Name, PasswordHash, PasswordSalt, HashType, PlanId, RoleId) VALUES (@Login, @Name, @PasswordHash, @PasswordSalt, @HashType, @PlanId, @RoleId);", - param:user); + param: user); return await GetUser(user.Login); } - public Task RedeemInvite(uint inviteId, uint userId) + public Task RedeemInvite(uint inviteId, uint userId) => Tran.ExecuteAsync( @"UPDATE userinvite SET RedeemedBy = @userId WHERE UserInviteId = @inviteId", new { inviteId, userId }); - public Task GetInviteByCode(string inviteCode) + public Task GetInviteByCode(string inviteCode) => Tran.QuerySingleOrDefaultAsync( " SELECT * FROM userinvite " + " WHERE InviteCode = @inviteCode " + " AND RedeemedBy IS NULL ", new { inviteCode }); - public Task> GetSubsForTopic(uint topicId) + public Task> GetSubsForTopic(uint topicId) => Tran.QueryAsync( " SELECT Sub " + " FROM topic_sub " + " WHERE TopicId = @topicid", new { topicId }); - public Task> GetTopicsForSub(NamespacedId sub) + public Task> GetTopicsForSub(NamespacedId sub) => Tran.QueryAsync( " SELECT t.*" + " FROM topic_sub ts" + @@ -185,7 +198,7 @@ public class DbContext : IDisposable " WHERE ts.Sub = @sub", new { sub }); - public Task CreateSubscription(uint topicId, NamespacedId sub) + public Task CreateSubscription(uint topicId, NamespacedId sub) => Tran.ExecuteAsync( " INSERT INTO topic_sub" + " (TopicId, Sub)" + @@ -193,7 +206,7 @@ public class DbContext : IDisposable " (@topicId, @sub)", new { topicId, sub }); - public Task RemoveSubscription(string topicName, NamespacedId sub) + public Task RemoveSubscription(string topicName, NamespacedId sub) => Tran.ExecuteAsync( " DELETE ts " + " FROM topic_sub ts" + @@ -202,7 +215,7 @@ public class DbContext : IDisposable new { topicName, sub }); - public Task ReportHeartbeat(uint topicId, string heart, int timeoutSeconds) + public Task ReportHeartbeat(uint topicId, string heart, int timeoutSeconds) => Tran.QueryFirstAsync( @"CALL report_heartbeat(@topicId, @heart, @timeoutSeconds);", new { topicId, heart, timeoutSeconds }); @@ -215,14 +228,14 @@ public class DbContext : IDisposable #region TicketStore - public Task RemoveSession(string sessionId) + public Task RemoveSession(string sessionId) => Tran.ExecuteAsync("DELETE FROM usersession WHERE SessionId = @sessionId", new { sessionId }); - public Task GetSession(string sessionId) + public Task GetSession(string sessionId) => Tran.QuerySingleOrDefaultAsync( "SELECT * FROM usersession WHERE SessionId = @sessionId", new { sessionId }); - public Task UpdateSession(string sessionId, byte[] data, DateTime expiryTs) + public Task UpdateSession(string sessionId, byte[] data, DateTime expiryTs) => Tran.ExecuteAsync(@" UPDATE usersession SET SessionData = @data, diff --git a/JetHerald/Views/AdminUsers/Index.cshtml b/JetHerald/Views/AdminUsers/Index.cshtml new file mode 100644 index 0000000..8f40410 --- /dev/null +++ b/JetHerald/Views/AdminUsers/Index.cshtml @@ -0,0 +1,50 @@ +@model AdminUsersModel + +

Invites

+
    + @foreach (var user in Model.Users) + { +
  • + @user.Name @@@user.Login ( + @if (Context.UserCan("admin.users.setperms")) + { +
    + r:, + p: + +
    + } + else + { + @: r:@Model.Roles[user.RoleId].Name, + @: p:@Model.Plans[user.PlanId].Name + } + ) +
  • + } +
\ No newline at end of file diff --git a/JetHerald/wwwroot/css/global.css b/JetHerald/wwwroot/css/global.css index 985f084..db44cad 100644 --- a/JetHerald/wwwroot/css/global.css +++ b/JetHerald/wwwroot/css/global.css @@ -395,4 +395,8 @@ td.numeric { a.show-button { display: inline; +} + +.username { + color: gray; } \ No newline at end of file