From 771a5ca97aed0a41d85866f2c424bbc57a94202e Mon Sep 17 00:00:00 2001 From: Holly Date: Wed, 24 Apr 2024 00:05:57 +0100 Subject: [PATCH] allow joining party, creating party, setting and unsetting active party, add Party ui to client --- server/index.ts | 78 +++++++++++++++++++++++++-- server/interfaces/IdData.ts | 3 ++ server/interfaces/JoinPartyData.ts | 3 ++ server/objects/UserParty.ts | 7 ++- server/repos/UserPartyRepo.ts | 37 +++++++++++-- server/services/UserService.ts | 64 ++++++++++++++++++++++ server/templates/home.ejs | 10 ++-- server/templates/party/createedit.ejs | 12 +++-- server/templates/party/join.ejs | 10 +++- 9 files changed, 205 insertions(+), 19 deletions(-) create mode 100644 server/interfaces/IdData.ts create mode 100644 server/interfaces/JoinPartyData.ts diff --git a/server/index.ts b/server/index.ts index 348e4b1..f0a0a19 100644 --- a/server/index.ts +++ b/server/index.ts @@ -17,6 +17,9 @@ import { randomBytes } from "crypto"; import SessionUser from "./objects/SessionUser"; import PasswordUtility from "./utilities/PasswordUtility"; import CreateEditPartyData from "./interfaces/CreateEditPartyData"; +import JoinPartyData from "./interfaces/JoinPartyData"; +import UserParty from "./objects/UserParty"; +import IdData from "./interfaces/IdData"; Console.customHeader(`MultiProbe server started at ${new Date()}`); @@ -79,8 +82,9 @@ fastify.get("/", async (req, res) => { if (session = validateSession(req.cookies)) { const user = await UserService.GetUser(session.userId); const parties = await UserService.GetUserParties(session.userId); + const activeUserParty = await UserService.GetActiveParty(session.userId); if (user) { - return res.view("templates/home.ejs", { user, parties }); + return res.view("templates/home.ejs", { user, parties, activeUserParty }); } return res.view("templates/index.ejs", { }); @@ -101,13 +105,19 @@ fastify.get("/account/register", async (req, res) => { return res.view("templates/account/register.ejs", { }); }); +fastify.get("/account/logout", async (req, res) => { + res.clearCookie("MP_SESSION"); + + return res.redirect(302, "/"); +}); + fastify.get("/party/create", async (req, res) => { let session:SessionUser | undefined; if (!(session = validateSession(req.cookies))) { return res.redirect(302, "/"); } - return res.view("templates/party/createedit.ejs", { }); + return res.view("templates/party/createedit.ejs", { session }); }); fastify.get("/party/join", async (req, res) => { @@ -116,7 +126,35 @@ fastify.get("/party/join", async (req, res) => { return res.redirect(302, "/"); } - return res.view("templates/party/join.ejs", { }); + return res.view("templates/party/join.ejs", { session }); +}); + +fastify.get("/party/setactive", async (req, res) => { + let session:SessionUser | undefined; + if (!(session = validateSession(req.cookies))) { + return res.redirect(302, "/"); + } + + const data = req.query as IdData; + const numericId = parseInt(data.id ?? "-1"); + if (typeof(data.id) !== "string" || isNaN(numericId)) { + return res.redirect(302, "/"); + } + + await UserService.SetActiveParty(session.userId, numericId); + + return res.redirect(302, "/"); +}); + +fastify.get("/party/deactivate", async (req, res) => { + let session:SessionUser | undefined; + if (!(session = validateSession(req.cookies))) { + return res.redirect(302, "/"); + } + + await UserService.DeactivateCurrentParty(session.userId); + + return res.redirect(302, "/"); }); // Post Methods @@ -187,12 +225,12 @@ fastify.post("/party/create", async (req, res) => { const data = req.body as CreateEditPartyData; if (typeof(data.partyName) !== "string" || typeof(data.partyRef) !== "string" || data.partyName.length === 0 || data.partyRef.length === 0) { - return res.view("templates/party/createedit.ejs", { partyName: data.partyName ?? "", partyRef: data.partyRef ?? "" }); + return res.view("templates/party/createedit.ejs", { session, partyName: data.partyName ?? "", partyRef: data.partyRef ?? "" }); } const party = await UserService.GetPartyByPartyRef(data.partyRef) if (party != null) { - return res.view("templates/party/createedit.ejs", { partyName: data.partyName ?? "", partyRef: data.partyRef ?? "", error: "A group with that Party ID already exists" }); + return res.view("templates/party/createedit.ejs", { session, partyName: data.partyName ?? "", partyRef: data.partyRef ?? "", error: "A group with that Party ID already exists" }); } await UserService.CreateParty(session.userId, data.partyName, data.partyRef); @@ -203,6 +241,36 @@ fastify.post("/party/create", async (req, res) => { } }); +fastify.post("/party/join", async (req, res) => { + try { + let session:SessionUser | undefined; + if (!(session = validateSession(req.cookies))) { + return res.redirect(302, "/"); + } + + const data = req.body as JoinPartyData; + if (typeof(data.partyRef) !== "string" || data.partyRef.length === 0) { + return res.view("templates/party/join.ejs", { partyRef: data.partyRef ?? "" }); + } + + const party = await UserService.GetPartyByPartyRef(data.partyRef); + if (party == null) { + return res.view("templates/party/join.ejs", { session, partyRef: data.partyRef ?? "", error: "That Join Code / Party ID is invalid." }); + } + + const userPartyExisting = await UserService.GetUserPartyForUser(session.userId, party.Id); + if (userPartyExisting != null) { + return res.view("templates/party/join.ejs", { session, partyRef: data.partyRef ?? "", error: "You are already in this group." }); + } + + await UserService.AddUserToParty(session.userId, party.Id); + + return res.redirect(302, "/"); + } catch (e) { + console.error(e); + } +}); + // Websocket stuff const websocketServer = new WebSocketServer({ diff --git a/server/interfaces/IdData.ts b/server/interfaces/IdData.ts new file mode 100644 index 0000000..0252b10 --- /dev/null +++ b/server/interfaces/IdData.ts @@ -0,0 +1,3 @@ +export default interface IdData { + id?: string +} \ No newline at end of file diff --git a/server/interfaces/JoinPartyData.ts b/server/interfaces/JoinPartyData.ts new file mode 100644 index 0000000..8ac6757 --- /dev/null +++ b/server/interfaces/JoinPartyData.ts @@ -0,0 +1,3 @@ +export default interface JoinPartyData { + partyRef: string +} \ No newline at end of file diff --git a/server/objects/UserParty.ts b/server/objects/UserParty.ts index ec6297e..085013d 100644 --- a/server/objects/UserParty.ts +++ b/server/objects/UserParty.ts @@ -2,6 +2,7 @@ export default class UserParty { public Id:number; public UserId:number; public PartyId:number; + public IsActive:boolean; public CreatedByUserId:number; public CreatedDatetime:Date; public LastModifiedByUserId?:number; @@ -10,11 +11,12 @@ export default class UserParty { public DeletedDatetime?:Date; public IsDeleted:boolean; - public constructor(id?:number, userId?:number, partyId?:number, createdByUserId?:number, createdDateTime?:Date, lastModifiedByUserId?:number, lastModifiedDatetime?:Date, deletedByUserId?:number, deletedDatetime?:Date, isDeleted?:boolean) { - if (typeof(id) == "number" && typeof(userId) == "number" && typeof(partyId) == "number" && typeof(createdByUserId) == "number" && createdDateTime instanceof Date && typeof(lastModifiedByUserId) == "number" && lastModifiedDatetime instanceof Date && typeof(deletedByUserId) == "number" && deletedDatetime instanceof Date && typeof(isDeleted) == "boolean") { + public constructor(id?:number, userId?:number, partyId?:number, isActive?:boolean, createdByUserId?:number, createdDateTime?:Date, lastModifiedByUserId?:number, lastModifiedDatetime?:Date, deletedByUserId?:number, deletedDatetime?:Date, isDeleted?:boolean) { + if (typeof(id) == "number" && typeof(userId) == "number" && typeof(partyId) == "number" && typeof(isActive) === "boolean" && typeof(createdByUserId) == "number" && createdDateTime instanceof Date && typeof(lastModifiedByUserId) == "number" && lastModifiedDatetime instanceof Date && typeof(deletedByUserId) == "number" && deletedDatetime instanceof Date && typeof(isDeleted) == "boolean") { this.Id = id; this.UserId = userId; this.PartyId = partyId; + this.IsActive = isActive; this.CreatedByUserId = createdByUserId; this.CreatedDatetime = createdDateTime; this.LastModifiedByUserId = lastModifiedByUserId; @@ -26,6 +28,7 @@ export default class UserParty { this.Id = Number.MIN_VALUE; this.UserId = Number.MIN_VALUE; this.PartyId = Number.MIN_VALUE; + this.IsActive = false; this.CreatedByUserId = Number.MIN_VALUE; this.CreatedDatetime = new Date(0); this.IsDeleted = false; diff --git a/server/repos/UserPartyRepo.ts b/server/repos/UserPartyRepo.ts index 6a6bcfb..5ca1645 100644 --- a/server/repos/UserPartyRepo.ts +++ b/server/repos/UserPartyRepo.ts @@ -30,14 +30,42 @@ export default class UserPartyRepo { } } + public static async selectByUserIdPartyId(userId:number, partyId:number) { + const dbUserParty = await Database.Instance.query("SELECT * FROM UserParty WHERE UserId = ? AND PartyId = ? AND IsDeleted = 0", [userId, partyId]); + if (dbUserParty == null || dbUserParty.length === 0) { + return null; + } else { + const userParty = new UserParty(); + populateUserPartyFromDB(userParty, dbUserParty[0]); + return userParty; + } + } + + public static async deactivateAll(userId:number) { + await Database.Instance.query("UPDATE UserParty SET IsActive = 0, LastModifiedByUserId = ?, LastModifiedDatetime = ? WHERE UserId = ? AND IsActive = 1", [ + userId, Date.now(), userId + ]); + } + + public static async selectActive(userId:number) { + const dbUserParty = await Database.Instance.query("SELECT * FROM UserParty WHERE UserId = ? AND IsActive = 1 AND IsDeleted = 0 LIMIT 1", [userId]); + if (dbUserParty == null || dbUserParty.length === 0) { + return null; + } else { + const userParty = new UserParty(); + populateUserPartyFromDB(userParty, dbUserParty[0]); + return userParty; + } + } + public static async insertUpdate(userParty:UserParty) { if (userParty.Id === Number.MIN_VALUE) { - await Database.Instance.query("INSERT UserParty (UserId, PartyId, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", [ - userParty.UserId, userParty.PartyId, userParty.CreatedByUserId, userParty.CreatedDatetime.getTime(), userParty.LastModifiedByUserId ?? null, userParty.LastModifiedDatetime?.getTime() ?? null, userParty.DeletedByUserId ?? null, userParty.DeletedDatetime?.getTime() ?? null, Number(userParty.IsDeleted) + await Database.Instance.query("INSERT UserParty (UserId, PartyId, IsActive, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ + 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) ]); } else { - await Database.Instance.query(`UPDATE UserParty SET UserId = ?, PartyId = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ?, WHERE Id = ?`, [ - userParty.UserId, userParty.PartyId, userParty.CreatedByUserId, userParty.CreatedDatetime.getTime(), userParty.LastModifiedByUserId ?? null, userParty.LastModifiedDatetime?.getTime() ?? null, userParty.DeletedByUserId ?? null, userParty.DeletedDatetime?.getTime() ?? null, Number(userParty.IsDeleted), userParty.Id + 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 ]); } } @@ -47,6 +75,7 @@ function populateUserPartyFromDB(userParty:UserParty, dbUserParty:any) { userParty.Id = dbUserParty.Id; userParty.UserId = dbUserParty.UserId; userParty.PartyId = dbUserParty.PartyId; + userParty.IsActive = dbUserParty.IsActive; userParty.CreatedByUserId = dbUserParty.CreatedByUserId; userParty.CreatedDatetime = new Date(dbUserParty.CreatedDatetime); userParty.LastModifiedByUserId = dbUserParty.LastModifiedByUserId; diff --git a/server/services/UserService.ts b/server/services/UserService.ts index 83d271f..d9f6fbf 100644 --- a/server/services/UserService.ts +++ b/server/services/UserService.ts @@ -35,6 +35,15 @@ export default class UserService { } } + public static async GetUserPartyForUser(userId:number, partyId:number) { + try { + return await UserPartyRepo.selectByUserIdPartyId(userId, partyId); + } catch (e) { + Console.printError(`MultiProbe server service error:\n${e}`); + throw e; + } + } + public static async GetParty(id:number) { try { return await PartyRepo.selectById(id); @@ -96,4 +105,59 @@ export default class UserService { throw e; } } + + public static async AddUserToParty(userId:number, partyId:number) { + try { + const userParty = new UserParty(); + userParty.UserId = userId; + userParty.PartyId = partyId; + userParty.CreatedByUserId = userId; + userParty.CreatedDatetime = new Date(); + + await UserPartyRepo.insertUpdate(userParty); + } catch (e) { + Console.printError(`MultiProbe server service error:\n${e}`); + throw e; + } + } + + public static async SetActiveParty(currentUserId:number, partyId:number) { + try { + await UserPartyRepo.deactivateAll(currentUserId); + + const userParty = await UserPartyRepo.selectByUserIdPartyId(currentUserId, partyId); + if (!userParty) { + return; + } + + userParty.IsActive = true; + userParty.LastModifiedByUserId = currentUserId; + userParty.LastModifiedDatetime = new Date(); + + await UserPartyRepo.insertUpdate(userParty); + } catch (e) { + Console.printError(`MultiProbe server service error:\n${e}`); + console.log(e); + throw e; + } + } + + public static async DeactivateCurrentParty(currentUserId:number) { + try { + await UserPartyRepo.deactivateAll(currentUserId); + } catch (e) { + Console.printError(`MultiProbe server service error:\n${e}`); + console.log(e); + throw e; + } + } + + public static async GetActiveParty(currentUserId:number) { + try { + return await UserPartyRepo.selectActive(currentUserId); + } catch (e) { + Console.printError(`MultiProbe server service error:\n${e}`); + throw e; + } + } } \ No newline at end of file diff --git a/server/templates/home.ejs b/server/templates/home.ejs index 1747107..c9efd4d 100644 --- a/server/templates/home.ejs +++ b/server/templates/home.ejs @@ -5,8 +5,8 @@

What would you like to do?

- Change Username - Change Password + Change Username + Change Password
Create Party @@ -31,7 +31,11 @@ <%= party.Name %> <%= party.PartyRef %> - Set Active + <% if (activeUserParty && activeUserParty.PartyId === party.Id) { %> + Deactivate + <% } else { %> + Set Current + <% } %> <% if (party.CreatedByUserId === user.Id) { %> Leave <% } else { %> diff --git a/server/templates/party/createedit.ejs b/server/templates/party/createedit.ejs index 58d4463..5aa078a 100644 --- a/server/templates/party/createedit.ejs +++ b/server/templates/party/createedit.ejs @@ -1,4 +1,4 @@ -<%- include("../base/header", { title: typeof(party) === "undefined" ? "Create Party" : `Editing ${party.Name}` }) %> +<%- include("../base/header", { title: typeof(party) === "undefined" ? "Create Party" : `Editing ${party.Name}`, userId: session.userId }) %> <% if (typeof(party) === "undefined") { %>

Create Party

<% } else { %> @@ -8,13 +8,19 @@
+ <% if (typeof(error) === "string") { %> +
+ <%= error %> +
+ <% } %> +
- + " required />
- + " required />
diff --git a/server/templates/party/join.ejs b/server/templates/party/join.ejs index fe71569..9a47905 100644 --- a/server/templates/party/join.ejs +++ b/server/templates/party/join.ejs @@ -1,12 +1,18 @@ -<%- include("../base/header", { title: "Join Party" }) %> +<%- include("../base/header", { title: "Join Party", userId: session.userId }) %>

Join Party

+ <% if (typeof(error) === "string") { %> +
+ <%= error %> +
+ <% } %> +
- + " required />