diff --git a/server/index.ts b/server/index.ts index 2bd6a4b..348e4b1 100644 --- a/server/index.ts +++ b/server/index.ts @@ -16,6 +16,7 @@ import UsernameData from "./interfaces/UsernameData"; import { randomBytes } from "crypto"; import SessionUser from "./objects/SessionUser"; import PasswordUtility from "./utilities/PasswordUtility"; +import CreateEditPartyData from "./interfaces/CreateEditPartyData"; Console.customHeader(`MultiProbe server started at ${new Date()}`); @@ -56,7 +57,9 @@ fastify.register(FastifyCookie, { } }); -// Get Methods +fastify.setNotFoundHandler(async (req, res) => { + return res.status(404).view("templates/404.ejs", { }); +}); function validateSession(cookies:{ [cookieName: string]: string | undefined }) { if ("MP_SESSION" in cookies && typeof(cookies["MP_SESSION"]) === "string") { @@ -69,13 +72,15 @@ function validateSession(cookies:{ [cookieName: string]: string | undefined }) { return undefined; } +// Get Methods + fastify.get("/", async (req, res) => { let session:SessionUser | undefined; if (session = validateSession(req.cookies)) { const user = await UserService.GetUser(session.userId); - //const groups = await UserService.GetUserParties(session.userId); + const parties = await UserService.GetUserParties(session.userId); if (user) { - return res.view("templates/home.ejs", { user, parties: [] }); + return res.view("templates/home.ejs", { user, parties }); } return res.view("templates/index.ejs", { }); @@ -96,8 +101,22 @@ fastify.get("/account/register", async (req, res) => { return res.view("templates/account/register.ejs", { }); }); -fastify.setNotFoundHandler(async (req, res) => { - return res.status(404).view("templates/404.ejs", { }); +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", { }); +}); + +fastify.get("/party/join", async (req, res) => { + let session:SessionUser | undefined; + if (!(session = validateSession(req.cookies))) { + return res.redirect(302, "/"); + } + + return res.view("templates/party/join.ejs", { }); }); // Post Methods @@ -159,6 +178,31 @@ fastify.post("/account/login", async (req, res) => { return res.view("templates/account/login.ejs", { }); }); +fastify.post("/party/create", async (req, res) => { + try { + let session:SessionUser | undefined; + if (!(session = validateSession(req.cookies))) { + return res.redirect(302, "/"); + } + + 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 ?? "" }); + } + + 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" }); + } + + await UserService.CreateParty(session.userId, data.partyName, data.partyRef); + + return res.redirect(302, "/"); + } catch (e) { + console.error(e); + } +}); + // Websocket stuff const websocketServer = new WebSocketServer({ @@ -295,4 +339,4 @@ function shutdown() { process.on("SIGQUIT", shutdown); process.on("SIGINT", shutdown); -process.on("SIGUSR2", shutdown); \ No newline at end of file +//process.on("SIGUSR2", shutdown); \ No newline at end of file diff --git a/server/interfaces/CreateEditPartyData.ts b/server/interfaces/CreateEditPartyData.ts new file mode 100644 index 0000000..8068c24 --- /dev/null +++ b/server/interfaces/CreateEditPartyData.ts @@ -0,0 +1,4 @@ +export default interface CreateEditPartyData { + partyName?:string; + partyRef?:string; +} \ No newline at end of file diff --git a/server/objects/UserParty.ts b/server/objects/UserParty.ts new file mode 100644 index 0000000..ec6297e --- /dev/null +++ b/server/objects/UserParty.ts @@ -0,0 +1,34 @@ +export default class UserParty { + public Id:number; + public UserId:number; + public PartyId:number; + public CreatedByUserId:number; + public CreatedDatetime:Date; + public LastModifiedByUserId?:number; + public LastModifiedDatetime?:Date; + public DeletedByUserId?:number; + 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") { + this.Id = id; + this.UserId = userId; + this.PartyId = partyId; + this.CreatedByUserId = createdByUserId; + this.CreatedDatetime = createdDateTime; + this.LastModifiedByUserId = lastModifiedByUserId; + this.LastModifiedDatetime = lastModifiedDatetime; + this.DeletedByUserId = deletedByUserId; + this.DeletedDatetime = deletedDatetime; + this.IsDeleted = isDeleted; + } else { + this.Id = Number.MIN_VALUE; + this.UserId = Number.MIN_VALUE; + this.PartyId = Number.MIN_VALUE; + this.CreatedByUserId = Number.MIN_VALUE; + this.CreatedDatetime = new Date(0); + this.IsDeleted = false; + } + } +} \ No newline at end of file diff --git a/server/repos/PartyRepo.ts b/server/repos/PartyRepo.ts index 25f5f29..5a9f5a5 100644 --- a/server/repos/PartyRepo.ts +++ b/server/repos/PartyRepo.ts @@ -5,7 +5,7 @@ import RepoBase from "./RepoBase"; export default class PartyRepo { public static async selectById(id:number) { - const dbParty = await Database.Instance.query("SELECT * FROM Party WHERE Id = ? LIMIT 1", [id]); + const dbParty = await Database.Instance.query("SELECT * FROM Party WHERE Id = ? AND IsDeleted = 0 LIMIT 1", [id]); if (dbParty == null || dbParty.length === 0) { return null; } else { @@ -15,8 +15,24 @@ export default class PartyRepo { } } + public static async selectByUserId(userId:number) { + const dbParties = await Database.Instance.query("SELECT Party.* FROM Party JOIN UserParty ON Party.Id = UserParty.PartyId WHERE UserParty.UserId = ? AND UserParty.IsDeleted = 0 AND Party.IsDeleted = 0", [userId]); + const parties = new Array(); + if (dbParties == null || dbParties.length === 0) { + return parties; + } else { + for (const dbParty of dbParties) { + const party = new Party(); + populatePartyFromDB(party, dbParty); + parties.push(party); + } + + return parties; + } + } + public static async selectByPartyRef(partyRef:string) { - const dbParty = await Database.Instance.query("SELECT * FROM Party WHERE PartyRef = ? LIMIT 1", [partyRef]); + const dbParty = await Database.Instance.query("SELECT * FROM Party WHERE PartyRef = ? AND IsDeleted = 0 LIMIT 1", [partyRef]); if (dbParty == null || dbParty.length === 0) { return null; } else { @@ -26,14 +42,14 @@ export default class PartyRepo { } } - public static async insertUpdate(user:User) { - if (user.Id === Number.MIN_VALUE) { - await Database.Instance.query("INSERT Party (Username, PasswordHash, PasswordSalt, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ - 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) + 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.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) ]); } else { - await Database.Instance.query(`UPDATE Party SET Username = ?, PasswordHash = ?, PasswordSalt = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ?, WHERE Id = ?`, [ - 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), user.Id + 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 ]); } } diff --git a/server/repos/UserPartyRepo.ts b/server/repos/UserPartyRepo.ts new file mode 100644 index 0000000..6a6bcfb --- /dev/null +++ b/server/repos/UserPartyRepo.ts @@ -0,0 +1,57 @@ +import Database from "../objects/Database"; +import UserParty from "../objects/UserParty"; +import RepoBase from "./RepoBase"; + +export default class UserPartyRepo { + public static async selectById(id:number) { + const dbUserParty = await Database.Instance.query("SELECT * FROM UserParty WHERE Id = ? AND IsDeleted = 0 LIMIT 1", [id]); + if (dbUserParty == null || dbUserParty.length === 0) { + return null; + } else { + const userParty = new UserParty(); + populateUserPartyFromDB(userParty, dbUserParty[0]); + return userParty; + } + } + + public static async selectByUserId(userId:number) { + const dbUserParties = await Database.Instance.query("SELECT * FROM UserParty WHERE UserId = ? AND IsDeleted = 0", [userId]); + if (dbUserParties == null || dbUserParties.length === 0) { + return null; + } else { + const userParties = new Array(); + for (const dbUserParty of dbUserParties) { + const userParty = new UserParty(); + populateUserPartyFromDB(userParty, dbUserParty[0]); + userParties.push(userParty); + } + + return userParties; + } + } + + 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) + ]); + } 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 + ]); + } + } +} + +function populateUserPartyFromDB(userParty:UserParty, dbUserParty:any) { + userParty.Id = dbUserParty.Id; + userParty.UserId = dbUserParty.UserId; + userParty.PartyId = dbUserParty.PartyId; + userParty.CreatedByUserId = dbUserParty.CreatedByUserId; + userParty.CreatedDatetime = new Date(dbUserParty.CreatedDatetime); + userParty.LastModifiedByUserId = dbUserParty.LastModifiedByUserId; + userParty.LastModifiedDatetime = RepoBase.convertNullableDatetimeIntToDate(dbUserParty.LastModifiedDatetime); + userParty.DeletedByUserId = dbUserParty.DeletedByUserId; + userParty.DeletedDatetime = RepoBase.convertNullableDatetimeIntToDate(dbUserParty.DeletedDatetime); + userParty.IsDeleted = dbUserParty.IsDeleted; +} \ No newline at end of file diff --git a/server/services/UserService.ts b/server/services/UserService.ts index e77833b..83d271f 100644 --- a/server/services/UserService.ts +++ b/server/services/UserService.ts @@ -3,6 +3,9 @@ import User from "../objects/User"; import PartyRepo from "../repos/PartyRepo"; import UserRepo from "../repos/UserRepo"; import PasswordUtility from "../utilities/PasswordUtility"; +import Party from "../objects/Party"; +import UserParty from "../objects/UserParty"; +import UserPartyRepo from "../repos/UserPartyRepo"; export default class UserService { public static async GetUser(id:number) { @@ -10,6 +13,7 @@ export default class UserService { return await UserRepo.selectById(id); } catch (e) { Console.printError(`MultiProbe server service error:\n${e}`); + throw e; } } @@ -18,6 +22,16 @@ export default class UserService { return await UserRepo.selectByUsername(username); } catch (e) { Console.printError(`MultiProbe server service error:\n${e}`); + throw e; + } + } + + public static async GetUserParties(userId:number) { + try { + return await PartyRepo.selectByUserId(userId); + } catch (e) { + Console.printError(`MultiProbe server service error:\n${e}`); + throw e; } } @@ -26,6 +40,7 @@ export default class UserService { return await PartyRepo.selectById(id); } catch (e) { Console.printError(`MultiProbe server service error:\n${e}`); + throw e; } } @@ -34,6 +49,7 @@ export default class UserService { return await PartyRepo.selectByPartyRef(partyRef); } catch (e) { Console.printError(`MultiProbe server service error:\n${e}`); + throw e; } } @@ -49,6 +65,35 @@ export default class UserService { await UserRepo.insertUpdate(user); } catch (e) { Console.printError(`MultiProbe server service error:\n${e}`); + throw e; + } + } + + public static async CreateParty(currentUserId:number, name:string, partyRef:string) { + try { + const 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"; + } + + const userParty = new UserParty(); + userParty.UserId = currentUserId; + userParty.PartyId = newParty.Id; + userParty.CreatedByUserId = currentUserId; + userParty.CreatedDatetime = new Date(); + + await UserPartyRepo.insertUpdate(userParty); + } 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 32c8adf..1747107 100644 --- a/server/templates/home.ejs +++ b/server/templates/home.ejs @@ -9,8 +9,8 @@ Change Password @@ -21,9 +21,26 @@ Name + Code - + + + <% for (const party of parties) { %> + + <%= party.Name %> + <%= party.PartyRef %> + + Set Active + <% if (party.CreatedByUserId === user.Id) { %> + Leave + <% } else { %> + Leave + <% } %> + + + <% } %> + <% } else { %> diff --git a/server/templates/party/createedit.ejs b/server/templates/party/createedit.ejs new file mode 100644 index 0000000..58d4463 --- /dev/null +++ b/server/templates/party/createedit.ejs @@ -0,0 +1,28 @@ +<%- include("../base/header", { title: typeof(party) === "undefined" ? "Create Party" : `Editing ${party.Name}` }) %> +<% if (typeof(party) === "undefined") { %> +

Create Party

+<% } else { %> +

Editing <%= party.Name %>

+<% } %> +
+
+
+
+
+ + +
+
+ + +
+ +
+ + Cancel +
+
+
+
+
+<%- include("../base/footer") %> \ No newline at end of file diff --git a/server/templates/party/join.ejs b/server/templates/party/join.ejs new file mode 100644 index 0000000..fe71569 --- /dev/null +++ b/server/templates/party/join.ejs @@ -0,0 +1,20 @@ +<%- include("../base/header", { title: "Join Party" }) %> +

Join Party

+
+
+
+
+
+ + +
+ +
+ + Cancel +
+
+
+
+
+<%- include("../base/footer") %> \ No newline at end of file