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 @@