From 23a206ce9987053cbbce8a08db5d4d00eeb9d598 Mon Sep 17 00:00:00 2001 From: Holly Date: Sat, 28 Sep 2024 14:31:02 +0100 Subject: [PATCH] finish up the admin side for now --- server/controller/AdminController.ts | 98 ++++++++++++++++++- server/index.ts | 2 + server/models/admin/AdminDeletePartyModel.ts | 3 + .../models/admin/AdminExpireSessionModel.ts | 3 + server/models/admin/AdminIndexViewModel.ts | 3 +- server/models/admin/AdminPartyViewModel.ts | 5 + server/models/admin/AdminUserViewModel.ts | 7 ++ .../models/admin/AdminWebSessionsViewModel.ts | 6 ++ .../models/admin/AdminWsSessionsViewModel.ts | 6 ++ server/objects/Session.ts | 2 +- server/objects/SessionUser.ts | 4 +- server/objects/WsData.ts | 6 ++ server/repos/BadgeRepo.ts | 6 ++ server/repos/PartyRepo.ts | 8 +- server/repos/UserPartyRepo.ts | 6 +- server/repos/UserRepo.ts | 8 +- server/services/BadgeService.ts | 9 ++ server/services/PartyService.ts | 54 ++++++++-- server/services/UserService.ts | 28 ++++++ server/views/admin/index.ejs | 4 +- server/views/admin/parties.ejs | 7 +- server/views/admin/party.ejs | 43 ++++++++ server/views/admin/user.ejs | 65 ++++++++++++ server/views/admin/users.ejs | 3 + server/views/admin/websessions.ejs | 45 +++++++++ server/views/admin/wssessions.ejs | 43 ++++++++ server/views/base/header.ejs | 11 +-- 27 files changed, 455 insertions(+), 30 deletions(-) create mode 100644 server/models/admin/AdminDeletePartyModel.ts create mode 100644 server/models/admin/AdminExpireSessionModel.ts create mode 100644 server/models/admin/AdminPartyViewModel.ts create mode 100644 server/models/admin/AdminUserViewModel.ts create mode 100644 server/models/admin/AdminWebSessionsViewModel.ts create mode 100644 server/models/admin/AdminWsSessionsViewModel.ts create mode 100644 server/objects/WsData.ts create mode 100644 server/views/admin/party.ejs create mode 100644 server/views/admin/user.ejs create mode 100644 server/views/admin/websessions.ejs create mode 100644 server/views/admin/wssessions.ejs diff --git a/server/controller/AdminController.ts b/server/controller/AdminController.ts index 66f33d8..3ed33ae 100644 --- a/server/controller/AdminController.ts +++ b/server/controller/AdminController.ts @@ -1,9 +1,17 @@ import AdminBadgesViewModel from "../models/admin/AdminBadgesViewModel"; import AdminBadgeViewModel from "../models/admin/AdminBadgeViewModel"; import AdminDeleteBadgeModel from "../models/admin/AdminDeleteBadgeModel"; +import AdminDeletePartyModel from "../models/admin/AdminDeletePartyModel"; +import AdminExpireSessionModel from "../models/admin/AdminExpireSessionModel"; import AdminIndexViewModel from "../models/admin/AdminIndexViewModel"; import AdminPartiesViewModel from "../models/admin/AdminPartiesViewModel"; +import AdminPartyViewModel from "../models/admin/AdminPartyViewModel"; import AdminUsersViewModel from "../models/admin/AdminUsersViewModel"; +import AdminUserViewModel from "../models/admin/AdminUserViewModel"; +import AdminWebSessionsViewModel from "../models/admin/AdminWebSessionsViewModel"; +import AdminWsSessionsViewModel from "../models/admin/AdminWsSessionsViewModel"; +import Session from "../objects/Session"; +import WsData from "../objects/WsData"; import BadgeService from "../services/BadgeService"; import PartyService from "../services/PartyService"; import UserService from "../services/UserService"; @@ -13,7 +21,8 @@ export default class AdminController_Auth$Admin extends Controller { public async Index_Get() { const adminIndexViewModel: AdminIndexViewModel = { userCount: await UserService.GetUserCount(), - partyCount: await PartyService.GetPartyCount() + partyCount: await PartyService.GetPartyCount(), + badgeCount: await BadgeService.GetBadgeCount() }; return this.view(adminIndexViewModel); @@ -27,6 +36,33 @@ export default class AdminController_Auth$Admin extends Controller { return this.view(adminUsersViewModel); } + public async User_Get(adminUserViewModel: AdminUserViewModel) { + const user = adminUserViewModel.id ? await UserService.GetUser(parseInt(adminUserViewModel.id)) : null; + if (typeof(adminUserViewModel.id) !== "undefined" && user) { + adminUserViewModel.username = user.Username; + adminUserViewModel.userLevel = String(user.UserLevel); + } else { + adminUserViewModel.username = ""; + adminUserViewModel.userLevel = "0"; + } + + return this.view(adminUserViewModel); + } + + public async User_Post(adminUserViewModel: AdminUserViewModel) { + if (typeof(adminUserViewModel.id) === "undefined") { + return this.badRequest(); + } + + const user = await UserService.SaveUser(this.session.userId, parseInt(adminUserViewModel.id), adminUserViewModel.username ?? "", parseInt(adminUserViewModel.userLevel ?? "")); + if (!user) { + adminUserViewModel.message = "A user with that username already exists."; + return this.view(adminUserViewModel); + } + + return this.redirectToAction("users"); + } + public async Parties_Get() { const adminPartiesViewModel: AdminPartiesViewModel = { parties: await PartyService.GetAll() @@ -35,6 +71,40 @@ export default class AdminController_Auth$Admin extends Controller { return this.view(adminPartiesViewModel); } + public async Party_Get(adminPartyViewModel: AdminPartyViewModel) { + const party = adminPartyViewModel.id ? await PartyService.GetParty(parseInt(adminPartyViewModel.id)) : null; + if (typeof(adminPartyViewModel.id) !== "undefined" && party) { + adminPartyViewModel.partyRef = party.PartyRef; + adminPartyViewModel.name = party.Name; + } else { + adminPartyViewModel.partyRef = ""; + adminPartyViewModel.name = ""; + } + + return this.view(adminPartyViewModel); + } + + public async Party_Post(adminPartyViewModel: AdminPartyViewModel) { + if (typeof(adminPartyViewModel.id) === "undefined") { + return this.badRequest(); + } + + await PartyService.SaveParty(this.session.userId, parseInt(adminPartyViewModel.id), adminPartyViewModel.name ?? "", adminPartyViewModel.partyRef ?? ""); + + return this.redirectToAction("parties"); + } + + public async DeleteParty_Get(adminDeletePartyModel: AdminDeletePartyModel) { + if (typeof(adminDeletePartyModel.id) === "undefined" || typeof(adminDeletePartyModel.id) !== "string") { + return this.badRequest(); + } + const partyId = parseInt(adminDeletePartyModel.id); + + await PartyService.DeletePartyAdmin(this.session.userId, partyId); + + return this.redirectToAction("parties"); + } + public async Badges_Get() { const adminBadgesViewModel: AdminBadgesViewModel = { badges: await BadgeService.LoadAll() @@ -80,4 +150,30 @@ export default class AdminController_Auth$Admin extends Controller { return this.redirectToAction("badges"); } + + public async WebSessions_Get() { + const adminWebSessionsViewModel: AdminWebSessionsViewModel = { + sessions: Session.Sessions + }; + + return this.view(adminWebSessionsViewModel); + } + + public async ExpireSession_Get(adminExpireSessionModel: AdminExpireSessionModel) { + if (typeof(adminExpireSessionModel.key) === "undefined" || typeof(adminExpireSessionModel.key) !== "string") { + return this.badRequest(); + } + + Session.Sessions.remove(adminExpireSessionModel.key); + + return this.redirectToAction("websessions"); + } + + public async WsSessions_Get() { + const adminWsSessionsViewModel: AdminWsSessionsViewModel = { + users: WsData.users + }; + + return this.view(adminWsSessionsViewModel); + } } \ No newline at end of file diff --git a/server/index.ts b/server/index.ts index 3067366..9115258 100644 --- a/server/index.ts +++ b/server/index.ts @@ -49,10 +49,12 @@ import ApiController from "./controller/ApiController"; import PartyService from "./services/PartyService"; import AdminController_Auth$Admin from "./controller/AdminController"; import { join } from "path"; +import WsData from "./objects/WsData"; Console.customHeader(`MultiProbe server started at ${new Date()}`); const users = new FunkyArray(); +WsData.users = users; new Database(Config.database.address, Config.database.port, Config.database.username, Config.database.password, Config.database.name); diff --git a/server/models/admin/AdminDeletePartyModel.ts b/server/models/admin/AdminDeletePartyModel.ts new file mode 100644 index 0000000..fbd3a8c --- /dev/null +++ b/server/models/admin/AdminDeletePartyModel.ts @@ -0,0 +1,3 @@ +export default interface AdminDeletePartyModel { + id: string +} \ No newline at end of file diff --git a/server/models/admin/AdminExpireSessionModel.ts b/server/models/admin/AdminExpireSessionModel.ts new file mode 100644 index 0000000..91190b0 --- /dev/null +++ b/server/models/admin/AdminExpireSessionModel.ts @@ -0,0 +1,3 @@ +export default interface AdminExpireSessionModel { + key?:string +} \ No newline at end of file diff --git a/server/models/admin/AdminIndexViewModel.ts b/server/models/admin/AdminIndexViewModel.ts index 22165a0..e263537 100644 --- a/server/models/admin/AdminIndexViewModel.ts +++ b/server/models/admin/AdminIndexViewModel.ts @@ -1,4 +1,5 @@ export default interface AdminIndexViewModel { userCount: number, - partyCount: number + partyCount: number, + badgeCount: number } \ No newline at end of file diff --git a/server/models/admin/AdminPartyViewModel.ts b/server/models/admin/AdminPartyViewModel.ts new file mode 100644 index 0000000..50d9fe4 --- /dev/null +++ b/server/models/admin/AdminPartyViewModel.ts @@ -0,0 +1,5 @@ +export default interface AdminPartyViewModel { + id?: string, + partyRef?: string, + name?: string +} \ No newline at end of file diff --git a/server/models/admin/AdminUserViewModel.ts b/server/models/admin/AdminUserViewModel.ts new file mode 100644 index 0000000..3c1c869 --- /dev/null +++ b/server/models/admin/AdminUserViewModel.ts @@ -0,0 +1,7 @@ +export default interface AdminUserViewModel { + message?:string, + + id?: string, + username?: string, + userLevel?: string +} \ No newline at end of file diff --git a/server/models/admin/AdminWebSessionsViewModel.ts b/server/models/admin/AdminWebSessionsViewModel.ts new file mode 100644 index 0000000..01558a8 --- /dev/null +++ b/server/models/admin/AdminWebSessionsViewModel.ts @@ -0,0 +1,6 @@ +import FunkyArray from "funky-array"; +import SessionUser from "../../objects/SessionUser"; + +export default interface AdminWebSessionsViewModel { + sessions: FunkyArray +} \ No newline at end of file diff --git a/server/models/admin/AdminWsSessionsViewModel.ts b/server/models/admin/AdminWsSessionsViewModel.ts new file mode 100644 index 0000000..1b94dad --- /dev/null +++ b/server/models/admin/AdminWsSessionsViewModel.ts @@ -0,0 +1,6 @@ +import FunkyArray from "funky-array"; +import RemoteUser from "../../objects/RemoteUser"; + +export default interface AdminWsSessionsViewModel { + users: FunkyArray +} \ No newline at end of file diff --git a/server/objects/Session.ts b/server/objects/Session.ts index 1b052d8..78fc482 100644 --- a/server/objects/Session.ts +++ b/server/objects/Session.ts @@ -37,7 +37,7 @@ export default abstract class Session { validPeriod.setTime(validPeriod.getTime() + Config.session.validity); const key = randomBytes(Config.session.length).toString("hex"); - Session.Sessions.set(key, new SessionUser(user.Id, user.UserLevel, validPeriod)); + Session.Sessions.set(key, new SessionUser(user.Id, user.Username, user.UserLevel, validPeriod)); res.setCookie("MP_SESSION", key, { path: "/", diff --git a/server/objects/SessionUser.ts b/server/objects/SessionUser.ts index d5a750f..1d39c43 100644 --- a/server/objects/SessionUser.ts +++ b/server/objects/SessionUser.ts @@ -2,11 +2,13 @@ import { UserLevel } from "../enums/UserLevel"; export default class SessionUser { public readonly userId:number; + public readonly username:string; public readonly userLevel:UserLevel; public readonly validityPeriod:Date; - constructor(userId:number, userLevel:UserLevel, validityPeriod:Date) { + constructor(userId:number, username:string, userLevel:UserLevel, validityPeriod:Date) { this.userId = userId; + this.username = username; this.userLevel = userLevel; this.validityPeriod = validityPeriod; } diff --git a/server/objects/WsData.ts b/server/objects/WsData.ts new file mode 100644 index 0000000..616e4a7 --- /dev/null +++ b/server/objects/WsData.ts @@ -0,0 +1,6 @@ +import FunkyArray from "funky-array"; +import RemoteUser from "./RemoteUser"; + +export default abstract class WsData { + public static users: FunkyArray; +} \ No newline at end of file diff --git a/server/repos/BadgeRepo.ts b/server/repos/BadgeRepo.ts index 5124a42..8f4569d 100644 --- a/server/repos/BadgeRepo.ts +++ b/server/repos/BadgeRepo.ts @@ -27,6 +27,12 @@ export default abstract class BadgeRepo { } } + public static async selectBadgeCount() { + const countResult = await Database.Instance.query("SELECT COUNT(Id) FROM `Badge` WHERE IsDeleted = 0 LIMIT 1"); + + return countResult[0]["COUNT(Id)"]; + } + public static async insertUpdate(badge:Badge) { if (badge.Id === Number.MIN_VALUE) { badge.Id = (await Database.Instance.query("INSERT Badge (Name, Description, ImageUrl, ForUrl, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING Id;", [ diff --git a/server/repos/PartyRepo.ts b/server/repos/PartyRepo.ts index 2d85cfd..a9a6742 100644 --- a/server/repos/PartyRepo.ts +++ b/server/repos/PartyRepo.ts @@ -56,21 +56,23 @@ export default class PartyRepo { } public static async selectPartyCount() { - const countResult = await Database.Instance.query("SELECT COUNT(Id) FROM `Party` LIMIT 1"); + const countResult = await Database.Instance.query("SELECT COUNT(Id) FROM `Party` WHERE IsDeleted = 0 LIMIT 1"); return countResult[0]["COUNT(Id)"]; } public static async insertUpdate(party:Party) { if (party.Id === Number.MIN_VALUE) { - await Database.Instance.query("INSERT Party (Name, PartyRef, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", [ + party.Id = (await Database.Instance.query("INSERT Party (Name, PartyRef, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING Id;", [ party.Name, party.PartyRef, party.CreatedByUserId, party.CreatedDatetime.getTime(), party.LastModifiedByUserId ?? null, party.LastModifiedDatetime?.getTime() ?? null, party.DeletedByUserId ?? null, party.DeletedDatetime?.getTime() ?? null, Number(party.IsDeleted) - ]); + ]))[0]["Id"]; } else { await Database.Instance.query("UPDATE Party SET Name = ?, PartyRef = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ? WHERE Id = ?", [ party.Name, party.PartyRef, party.CreatedByUserId, party.CreatedDatetime.getTime(), party.LastModifiedByUserId ?? null, party.LastModifiedDatetime?.getTime() ?? null, party.DeletedByUserId ?? null, party.DeletedDatetime?.getTime() ?? null, Number(party.IsDeleted), party.Id ]); } + + return party; } } diff --git a/server/repos/UserPartyRepo.ts b/server/repos/UserPartyRepo.ts index f489763..032ce2e 100644 --- a/server/repos/UserPartyRepo.ts +++ b/server/repos/UserPartyRepo.ts @@ -60,14 +60,16 @@ export default class UserPartyRepo { public static async insertUpdate(userParty:UserParty) { if (userParty.Id === Number.MIN_VALUE) { - await Database.Instance.query("INSERT UserParty (UserId, PartyId, IsActive, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ + userParty.Id = (await Database.Instance.query("INSERT UserParty (UserId, PartyId, IsActive, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING Id;", [ userParty.UserId, userParty.PartyId, Number(userParty.IsActive), userParty.CreatedByUserId, userParty.CreatedDatetime.getTime(), userParty.LastModifiedByUserId ?? null, userParty.LastModifiedDatetime?.getTime() ?? null, userParty.DeletedByUserId ?? null, userParty.DeletedDatetime?.getTime() ?? null, Number(userParty.IsDeleted) - ]); + ]))[0]["Id"]; } else { await Database.Instance.query(`UPDATE UserParty SET UserId = ?, PartyId = ?, IsActive = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ? WHERE Id = ?`, [ userParty.UserId, userParty.PartyId, Number(userParty.IsActive), userParty.CreatedByUserId, userParty.CreatedDatetime.getTime(), userParty.LastModifiedByUserId ?? null, userParty.LastModifiedDatetime?.getTime() ?? null, userParty.DeletedByUserId ?? null, userParty.DeletedDatetime?.getTime() ?? null, Number(userParty.IsDeleted), userParty.Id ]); } + + return userParty; } } diff --git a/server/repos/UserRepo.ts b/server/repos/UserRepo.ts index 001f016..3009ac0 100644 --- a/server/repos/UserRepo.ts +++ b/server/repos/UserRepo.ts @@ -50,21 +50,23 @@ export default class UserRepo { } public static async selectUserCount() { - const countResult = await Database.Instance.query("SELECT COUNT(Id) FROM `User` LIMIT 1"); + const countResult = await Database.Instance.query("SELECT COUNT(Id) FROM `User` WHERE IsDeleted = 0 LIMIT 1"); return countResult[0]["COUNT(Id)"]; } public static async insertUpdate(user:User) { if (user.Id === Number.MIN_VALUE) { - await Database.Instance.query("INSERT User (UserLevelId, Username, PasswordHash, PasswordSalt, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ + user.Id = (await Database.Instance.query("INSERT User (UserLevelId, Username, PasswordHash, PasswordSalt, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING Id;", [ user.UserLevel, user.Username, user.PasswordHash, user.PasswordSalt, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId ?? null, user.LastModifiedDatetime?.getTime() ?? null, user.DeletedByUserId ?? null, user.DeletedDatetime?.getTime() ?? null, Number(user.IsDeleted) - ]); + ]))[0]["Id"]; } else { await Database.Instance.query(`UPDATE User SET UserLevelId = ?, Username = ?, PasswordHash = ?, PasswordSalt = ?, APIKey = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ? WHERE Id = ?`, [ user.UserLevel, user.Username, user.PasswordHash, user.PasswordSalt, user.APIKey, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId ?? null, user.LastModifiedDatetime?.getTime() ?? null, user.DeletedByUserId ?? null, user.DeletedDatetime?.getTime() ?? null, Number(user.IsDeleted), user.Id ]); } + + return user; } } diff --git a/server/services/BadgeService.ts b/server/services/BadgeService.ts index b2cc5cc..7541d02 100644 --- a/server/services/BadgeService.ts +++ b/server/services/BadgeService.ts @@ -57,4 +57,13 @@ export default abstract class BadgeService { return await BadgeRepo.insertUpdate(badge); } + + public static async GetBadgeCount() { + try { + return await BadgeRepo.selectBadgeCount(); + } catch (e) { + Console.printError(`MultiProbe server service error:\n${e}`); + throw e; + } + } } \ No newline at end of file diff --git a/server/services/PartyService.ts b/server/services/PartyService.ts index ebd57e1..1fae687 100644 --- a/server/services/PartyService.ts +++ b/server/services/PartyService.ts @@ -7,26 +7,44 @@ import UserPartyRepo from "../repos/UserPartyRepo"; export default abstract class PartyService { public static async CreateParty(currentUserId:number, name:string, partyRef:string) { try { - const party = new Party(); + let party = new Party(); party.Name = name; party.PartyRef = partyRef; party.CreatedByUserId = currentUserId; party.CreatedDatetime = new Date(); - await PartyRepo.insertUpdate(party); - - const newParty = await PartyRepo.selectByPartyRef(partyRef); - if (!newParty) { - throw "This shouldn't happen"; - } + party = await PartyRepo.insertUpdate(party); const userParty = new UserParty(); userParty.UserId = currentUserId; - userParty.PartyId = newParty.Id; + userParty.PartyId = party.Id; userParty.CreatedByUserId = currentUserId; userParty.CreatedDatetime = new Date(); await UserPartyRepo.insertUpdate(userParty); + + return party; + } catch (e) { + Console.printError(`MultiProbe server service error:\n${e}`); + throw e; + } + } + + public static async SaveParty(currentUserId:number, id:number | undefined, name:string, partyRef:string) { + try { + let party = id ? await PartyRepo.selectById(id) : null; + if (!party) { + party = new Party(); + party.CreatedByUserId = currentUserId; + party.CreatedDatetime = new Date(); + } else { + party.LastModifiedByUserId = currentUserId; + party.LastModifiedDatetime = new Date(); + } + party.Name = name; + party.PartyRef = partyRef; + + return await PartyRepo.insertUpdate(party); } catch (e) { Console.printError(`MultiProbe server service error:\n${e}`); throw e; @@ -76,7 +94,25 @@ export default abstract class PartyService { return null; } - console.log(party); + party.DeletedByUserId = currentUserId; + party.DeletedDatetime = new Date(); + party.IsDeleted = true; + + await PartyRepo.insertUpdate(party); + + return party; + } catch (e) { + Console.printError(`MultiProbe server service error:\n${e}`); + throw e; + } + } + + public static async DeletePartyAdmin(currentUserId:number, partyId:number) { + try { + const party = await PartyRepo.selectById(partyId); + if (!party) { + return null; + } party.DeletedByUserId = currentUserId; party.DeletedDatetime = new Date(); diff --git a/server/services/UserService.ts b/server/services/UserService.ts index a489d34..f73dcd8 100644 --- a/server/services/UserService.ts +++ b/server/services/UserService.ts @@ -4,6 +4,7 @@ import UserRepo from "../repos/UserRepo"; import PasswordUtility from "../utilities/PasswordUtility"; import UserParty from "../entities/UserParty"; import UserPartyRepo from "../repos/UserPartyRepo"; +import { UserLevel } from "../enums/UserLevel"; export default class UserService { public static async AuthenticateUser(username:string, password:string) { @@ -201,4 +202,31 @@ export default class UserService { throw e; } } + + public static async SaveUser(currentUserId:number, id:number | undefined, username:string, userLevel:UserLevel) { + try { + const existingCheck = id ? null : await UserRepo.selectByUsername(username); + if (existingCheck) { + return null; + } + + let user = id ? await UserRepo.selectById(id) : null; + if (!user) { + user = new User(); + user.CreatedByUserId = currentUserId; + user.CreatedDatetime = new Date(); + } else { + user.LastModifiedByUserId = currentUserId; + user.LastModifiedDatetime = new Date(); + } + + user.Username = username; + user.UserLevel = userLevel; + + return await UserRepo.insertUpdate(user); + } catch (e) { + Console.printError(`MultiProbe server service error:\n${e}`); + throw e; + } + } } \ No newline at end of file diff --git a/server/views/admin/index.ejs b/server/views/admin/index.ejs index b32b257..f8a18c7 100644 --- a/server/views/admin/index.ejs +++ b/server/views/admin/index.ejs @@ -37,7 +37,7 @@

Badges

-

<%= 0 %>

+

<%= badgeCount %>

@@ -48,6 +48,8 @@ Manage Users Manage Parties Manage Badges + Web Sessions + Websocket Sessions <%- include("../base/footer") %> \ No newline at end of file diff --git a/server/views/admin/parties.ejs b/server/views/admin/parties.ejs index 0f0ac07..6fa4336 100644 --- a/server/views/admin/parties.ejs +++ b/server/views/admin/parties.ejs @@ -15,6 +15,9 @@

Party Management

+
+ Add Party +
@@ -33,8 +36,8 @@ <%= party.PartyRef %> <%= party.Name %> - Edit - Delete + Edit + Delete <% } %> diff --git a/server/views/admin/party.ejs b/server/views/admin/party.ejs new file mode 100644 index 0000000..33c4236 --- /dev/null +++ b/server/views/admin/party.ejs @@ -0,0 +1,43 @@ +<%- include("../base/header", { title: typeof(id) === "undefined" || id.trim().length === 0 ? "Add Party" : `Edit ${name}`, userId: session.userId, isAdmin: true }) %> + + + +
+
+

<%= typeof(id) === "undefined" || id.trim().length === 0 ? "Add Party" : `Edit ${name}` %>

+
+
+ +
+ " /> + +
+
+ + " required maxlength="5" /> +
+
+ + " required maxlength="255" /> +
+
+ +
+
+ + Cancel +
+
+
+ +<%- include("../base/footer") %> \ No newline at end of file diff --git a/server/views/admin/user.ejs b/server/views/admin/user.ejs new file mode 100644 index 0000000..2adb02b --- /dev/null +++ b/server/views/admin/user.ejs @@ -0,0 +1,65 @@ +<%- include("../base/header", { title: typeof(id) === "undefined" || id.trim().length === 0 ? "Add User" : `Edit ${username}`, userId: session.userId, isAdmin: true }) %> + + + +
+
+

<%= typeof(id) === "undefined" || id.trim().length === 0 ? "Add User" : `Edit ${username}` %>

+
+
+ +<% if (typeof(message) === "string") { %> + +<% } %> + +
+ " /> + +
+
+ + " required maxlength="255" autocomplete="one-time-code" /> +
+
+
+
+ + +
+
+ +
+
+ + Cancel +
+
+
+ + + +<%- include("../base/footer") %> \ No newline at end of file diff --git a/server/views/admin/users.ejs b/server/views/admin/users.ejs index baaf1e5..ebd0857 100644 --- a/server/views/admin/users.ejs +++ b/server/views/admin/users.ejs @@ -15,6 +15,9 @@

User Management

+
+ Add User +
diff --git a/server/views/admin/websessions.ejs b/server/views/admin/websessions.ejs new file mode 100644 index 0000000..fa4560d --- /dev/null +++ b/server/views/admin/websessions.ejs @@ -0,0 +1,45 @@ +<%- include("../base/header", { title: "Web Sessions", userId: session.userId, isAdmin: true }) %> + +
+
+ +
+
+ +
+
+

Web Sessions

+
+
+ +
+
+ + + + + + + + <% + const keys = sessions._getKeys(); + for (const key of keys) { + const session = sessions.get(key); %> + + + + + + + <% } %> +
User IdUsernameExpires At 
<%= session.userId %><%= session.username %><%= session.validityPeriod.toString().split("GMT")[0] %> + Expire +
+
+
+<%- include("../base/footer") %> \ No newline at end of file diff --git a/server/views/admin/wssessions.ejs b/server/views/admin/wssessions.ejs new file mode 100644 index 0000000..e82f185 --- /dev/null +++ b/server/views/admin/wssessions.ejs @@ -0,0 +1,43 @@ +<%- include("../base/header", { title: "Websocket Sessions", userId: session.userId, isAdmin: true }) %> + +
+
+ +
+
+ +
+
+

Websocket Sessions

+
+
+ +
+
+ + + + + + + + <% + const keys = users._getKeys(); + for (const key of keys) { + const session = users.get(key); %> + + + + + + + <% } %> +
#UsernameCurrent LocationIs AFK?
<%= session.id %><%= session.username %>https://<%= session.rawURL %><%= session.isAfk ? "Yes" : "No" %>
+
+
+<%- include("../base/footer") %> \ No newline at end of file diff --git a/server/views/base/header.ejs b/server/views/base/header.ejs index 3d7d6ce..a2fc5bc 100644 --- a/server/views/base/header.ejs +++ b/server/views/base/header.ejs @@ -20,17 +20,16 @@ -