From 3aec94e4f0a5a76261a99ec09e1c3f758ab16ed0 Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Wed, 9 Jul 2025 00:22:05 +0800 Subject: [PATCH 1/4] Add relevant sql tables for onboarding assessment --- database/schema.sql | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/database/schema.sql b/database/schema.sql index 943c800..827abf7 100644 --- a/database/schema.sql +++ b/database/schema.sql @@ -269,4 +269,29 @@ CREATE TABLE roadmap_items ( PRIMARY KEY(roadmap_id, material_id) ); -COMMIT; +--- + + -- Table to store onboarding questions + CREATE TABLE onboarding_questions ( + id SERIAL PRIMARY KEY, + question_text TEXT NOT NULL, + position INTEGER NOT NULL DEFAULT 0 + ); + + -- Table to store answer options for each question + CREATE TABLE onboarding_question_options ( + id SERIAL PRIMARY KEY, + question_id INTEGER NOT NULL REFERENCES onboarding_questions(id) ON DELETE CASCADE, + option_text TEXT NOT NULL, + tag_id INTEGER NOT NULL REFERENCES tags(id) ON DELETE CASCADE + ); + + -- Table to store user responses + CREATE TABLE onboarding_responses ( + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + option_id INTEGER NOT NULL REFERENCES onboarding_question_options(id) ON DELETE CASCADE, + PRIMARY KEY(user_id, option_id) + ); + + +COMMIT; \ No newline at end of file From d7145c773f37123d650a37c7df997c968e8f6df3 Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Wed, 9 Jul 2025 01:05:16 +0800 Subject: [PATCH 2/4] Add endpoints for onboarding form --- routes/onboarding.js | 293 +++++++++++++++++++++++++++++++++++++++++++ server.js | 5 +- 2 files changed, 295 insertions(+), 3 deletions(-) create mode 100644 routes/onboarding.js diff --git a/routes/onboarding.js b/routes/onboarding.js new file mode 100644 index 0000000..5a5a41b --- /dev/null +++ b/routes/onboarding.js @@ -0,0 +1,293 @@ +const express = require("express"); +const pool = require("../database/db"); +const router = express.Router(); + +function getAuthUser(req) { + const { auth } = req.cookies; + if (!auth) return null; + try { + return JSON.parse(auth); + } catch { + return null; + } +} + +function isAdmin(user) { + return user && user.organisation && user.organisation.role === "admin"; +} + +function isEmployee(user) { + return user && user.organisation && user.organisation.role === "employee"; +} + +router.get("/questions", async (req, res) => { + const user = getAuthUser(req); + if (!user || !user.isLoggedIn) { + return res.status(401).json({ message: "Not logged in" }); + } + + // if (!isEmployee(user)) { + // return res.status(403).json({ message: "Employee access required" }); + // } + + try { + const questionsResult = await pool.query(` + SELECT id, question_text, position + FROM onboarding_questions + ORDER BY position ASC + `); + + const questions = []; + for (const question of questionsResult.rows) { + const optionsResult = await pool.query( + ` + SELECT oqo.id, oqo.option_text, oqo.tag_id, t.name as tag_name + FROM onboarding_question_options oqo + JOIN tags t ON t.id = oqo.tag_id + WHERE oqo.question_id = $1 + ORDER BY oqo.id ASC + `, + [question.id] + ); + + questions.push({ + id: question.id, + question_text: question.question_text, + position: question.position, + options: optionsResult.rows, + }); + } + + res.json({ questions }); + } catch (err) { + console.error(err); + res.status(500).json({ message: "Server error" }); + } +}); + +router.post("/questions", async (req, res) => { + const user = getAuthUser(req); + if (!user || !user.isLoggedIn) { + return res.status(401).json({ message: "Not logged in" }); + } + + if (!isAdmin(user)) { + return res.status(403).json({ message: "Admin access required" }); + } + + const { question_text, position = 0 } = req.body; + if (!question_text) { + return res.status(400).json({ message: "question_text is required" }); + } + + try { + const result = await pool.query( + ` + INSERT INTO onboarding_questions (question_text, position) + VALUES ($1, $2) + RETURNING id, question_text, position + `, + [question_text, position] + ); + + res.status(201).json({ question: result.rows[0] }); + } catch (err) { + console.error(err); + res.status(500).json({ message: "Server error" }); + } +}); + +router.post("/questions/:id/options", async (req, res) => { + const user = getAuthUser(req); + if (!user || !user.isLoggedIn) { + return res.status(401).json({ message: "Not logged in" }); + } + + if (!isAdmin(user)) { + return res.status(403).json({ message: "Admin access required" }); + } + + const { id } = req.params; + const { option_text, tag_id } = req.body; + + if (!option_text || !tag_id) { + return res + .status(400) + .json({ message: "option_text and tag_id are required" }); + } + + const client = await pool.connect(); + try { + await client.query("BEGIN"); + + const questionCheck = await client.query( + "SELECT id FROM onboarding_questions WHERE id = $1", + [id] + ); + if (questionCheck.rows.length === 0) { + await client.query("ROLLBACK"); + return res.status(404).json({ message: "Question not found" }); + } + + const tagCheck = await client.query( + "SELECT id, name FROM tags WHERE id = $1", + [tag_id] + ); + if (tagCheck.rows.length === 0) { + await client.query("ROLLBACK"); + return res.status(404).json({ message: "Tag not found" }); + } + + const result = await client.query( + ` + INSERT INTO onboarding_question_options (question_id, option_text, tag_id) + VALUES ($1, $2, $3) + RETURNING id, option_text, tag_id + `, + [id, option_text, tag_id] + ); + + await client.query("COMMIT"); + + const option = { + ...result.rows[0], + tag_name: tagCheck.rows[0].name, + }; + + res.status(201).json({ option }); + } catch (err) { + await client.query("ROLLBACK"); + console.error(err); + res.status(500).json({ message: "Server error" }); + } finally { + client.release(); + } +}); + +router.delete("/questions/:id", async (req, res) => { + const user = getAuthUser(req); + if (!user || !user.isLoggedIn) { + return res.status(401).json({ message: "Not logged in" }); + } + + if (!isAdmin(user)) { + return res.status(403).json({ message: "Admin access required" }); + } + + const { id } = req.params; + + try { + const result = await pool.query( + "DELETE FROM onboarding_questions WHERE id = $1 RETURNING id", + [id] + ); + + if (result.rows.length === 0) { + return res.status(404).json({ message: "Question not found" }); + } + + res.json({ message: "Question deleted successfully" }); + } catch (err) { + console.error(err); + res.status(500).json({ message: "Server error" }); + } +}); + +router.post("/responses", async (req, res) => { + const user = getAuthUser(req); + if (!user || !user.isLoggedIn) { + return res.status(401).json({ message: "Not logged in" }); + } + + if (!isEmployee(user)) { + return res.status(403).json({ message: "Employee access required" }); + } + + const { option_ids } = req.body; + if (!option_ids || !Array.isArray(option_ids) || option_ids.length === 0) { + return res.status(400).json({ message: "option_ids array is required" }); + } + + const client = await pool.connect(); + try { + await client.query("BEGIN"); + + await client.query("DELETE FROM onboarding_responses WHERE user_id = $1", [ + user.userId, + ]); + + for (const optionId of option_ids) { + await client.query( + ` + INSERT INTO onboarding_responses (user_id, option_id) + VALUES ($1, $2) + ON CONFLICT (user_id, option_id) DO NOTHING + `, + [user.userId, optionId] + ); + } + + await client.query( + "UPDATE users SET has_completed_onboarding = true WHERE id = $1", + [user.userId] + ); + + await client.query("COMMIT"); + + res.json({ message: "Responses submitted successfully" }); + } catch (err) { + await client.query("ROLLBACK"); + console.error(err); + res.status(500).json({ message: "Server error" }); + } finally { + client.release(); + } +}); + +router.get("/responses", async (req, res) => { + const user = getAuthUser(req); + if (!user || !user.isLoggedIn) { + return res.status(401).json({ message: "Not logged in" }); + } + + if (!isEmployee(user)) { + return res.status(403).json({ message: "Employee access required" }); + } + + try { + const result = await pool.query( + ` + SELECT + or.option_id, + oqo.option_text, + oqo.tag_id, + t.name as tag_name, + oq.question_text, + oq.id as question_id + FROM onboarding_responses or + JOIN onboarding_question_options oqo ON oqo.id = or.option_id + JOIN onboarding_questions oq ON oq.id = oqo.question_id + JOIN tags t ON t.id = oqo.tag_id + WHERE or.user_id = $1 + ORDER BY oq.position ASC + `, + [user.userId] + ); + + const responses = result.rows.map((row) => ({ + option_id: row.option_id, + option_text: row.option_text, + tag_id: row.tag_id, + tag_name: row.tag_name, + question_text: row.question_text, + question_id: row.question_id, + })); + + res.json({ responses }); + } catch (err) { + console.error(err); + res.status(500).json({ message: "Server error" }); + } +}); + +module.exports = router; diff --git a/server.js b/server.js index 0a736ba..83b5518 100644 --- a/server.js +++ b/server.js @@ -5,6 +5,7 @@ const orgRoutes = require("./routes/orgs"); const courseRoutes = require("./routes/courses"); const userRoutes = require("./routes/users"); const reportsRoutes = require("./routes/reports"); +const onboardingRoutes = require("./routes/onboarding"); const pool = require("./database/db"); const app = express(); const PORT = process.env.PORT || 4000; @@ -19,7 +20,6 @@ app.use("/uploads", express.static(uploadsDir)); app.use(express.json()); app.use(cookieParser()); -// optional CORS settings if Next.js runs on a different origin app.use((req, res, next) => { res.header("Access-Control-Allow-Origin", "http://localhost:3000"); res.header("Access-Control-Allow-Credentials", "true"); @@ -27,7 +27,6 @@ app.use((req, res, next) => { next(); }); -// your existing endpoints app.get("/", (req, res) => res.send("Welcome to PostgreSQL with Node.js and Express!") ); @@ -41,12 +40,12 @@ app.get("/checkconnection", async (req, res) => { } }); -// mount all auth routes under /api app.use("/api", authRoutes); app.use("/api/orgs", orgRoutes); app.use("/api/courses", courseRoutes); app.use("/api/users", userRoutes); app.use("/api/reports", reportsRoutes); +app.use("/api/onboarding", onboardingRoutes); app.listen(PORT, () => { console.log(`Server running on http://localhost:${PORT}`); From 0eac90943251fd2c0cdbc65a5983dc176d164553 Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Wed, 9 Jul 2025 09:15:35 +0800 Subject: [PATCH 3/4] Add onboarding form as questionaire for onboarding --- database/schema.sql | 2 +- routes/auth.js | 28 ++++++++++++++++++---------- routes/onboarding.js | 35 +++++++++++++++-------------------- 3 files changed, 34 insertions(+), 31 deletions(-) diff --git a/database/schema.sql b/database/schema.sql index 827abf7..aeccf93 100644 --- a/database/schema.sql +++ b/database/schema.sql @@ -283,7 +283,7 @@ CREATE TABLE roadmap_items ( id SERIAL PRIMARY KEY, question_id INTEGER NOT NULL REFERENCES onboarding_questions(id) ON DELETE CASCADE, option_text TEXT NOT NULL, - tag_id INTEGER NOT NULL REFERENCES tags(id) ON DELETE CASCADE + tag_id INTEGER REFERENCES tags(id) ON DELETE CASCADE ); -- Table to store user responses diff --git a/routes/auth.js b/routes/auth.js index 3615166..c7b63ec 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -13,7 +13,6 @@ function setAuthCookie(res, payload) { }); } -// SIGN UP → POST /api/signup router.post("/signup", async (req, res) => { const { email, password, firstname, lastname } = req.body; try { @@ -45,7 +44,6 @@ router.post("/signup", async (req, res) => { } }); -// LOG IN → POST /api/login router.post("/login", async (req, res) => { const { email, password } = req.body; try { @@ -90,12 +88,10 @@ router.post("/login", async (req, res) => { } }); -// LOGOUT → POST /api/logout router.post("/logout", (req, res) => { res.clearCookie("auth", { path: "/" }).json({ success: true }); }); -// WHOAMI → GET /api/me router.get("/me", (req, res) => { const { auth } = req.cookies; if (!auth) return res.json({ isLoggedIn: false }); @@ -118,11 +114,6 @@ router.post("/complete-onboarding", async (req, res) => { } try { - await pool.query( - `UPDATE users SET has_completed_onboarding = true WHERE id = $1`, - [user.userId] - ); - const mem = await pool.query( `SELECT o.id AS id, @@ -137,7 +128,24 @@ router.post("/complete-onboarding", async (req, res) => { const organisation = mem.rows[0] || null; - // Regenerate auth cookie + if (organisation && organisation.role === "employee") { + const responseCheck = await pool.query( + `SELECT COUNT(*) as response_count FROM onboarding_responses WHERE user_id = $1`, + [user.userId] + ); + + if (parseInt(responseCheck.rows[0].response_count) === 0) { + return res.status(400).json({ + message: "Onboarding questionnaire must be completed first", + }); + } + } + + await pool.query( + `UPDATE users SET has_completed_onboarding = true WHERE id = $1`, + [user.userId] + ); + setAuthCookie(res, { ...user, hasCompletedOnboarding: true, diff --git a/routes/onboarding.js b/routes/onboarding.js index 5a5a41b..87a458f 100644 --- a/routes/onboarding.js +++ b/routes/onboarding.js @@ -43,7 +43,7 @@ router.get("/questions", async (req, res) => { ` SELECT oqo.id, oqo.option_text, oqo.tag_id, t.name as tag_name FROM onboarding_question_options oqo - JOIN tags t ON t.id = oqo.tag_id + LEFT JOIN tags t ON t.id = oqo.tag_id WHERE oqo.question_id = $1 ORDER BY oqo.id ASC `, @@ -110,10 +110,8 @@ router.post("/questions/:id/options", async (req, res) => { const { id } = req.params; const { option_text, tag_id } = req.body; - if (!option_text || !tag_id) { - return res - .status(400) - .json({ message: "option_text and tag_id are required" }); + if (!option_text) { + return res.status(400).json({ message: "option_text is required" }); } const client = await pool.connect(); @@ -129,13 +127,15 @@ router.post("/questions/:id/options", async (req, res) => { return res.status(404).json({ message: "Question not found" }); } - const tagCheck = await client.query( - "SELECT id, name FROM tags WHERE id = $1", - [tag_id] - ); - if (tagCheck.rows.length === 0) { - await client.query("ROLLBACK"); - return res.status(404).json({ message: "Tag not found" }); + let tagCheck = { rows: [] }; + if (tag_id) { + tagCheck = await client.query("SELECT id, name FROM tags WHERE id = $1", [ + tag_id, + ]); + if (tagCheck.rows.length === 0) { + await client.query("ROLLBACK"); + return res.status(404).json({ message: "Tag not found" }); + } } const result = await client.query( @@ -144,14 +144,14 @@ router.post("/questions/:id/options", async (req, res) => { VALUES ($1, $2, $3) RETURNING id, option_text, tag_id `, - [id, option_text, tag_id] + [id, option_text, tag_id || null] ); await client.query("COMMIT"); const option = { ...result.rows[0], - tag_name: tagCheck.rows[0].name, + tag_name: tag_id ? tagCheck.rows[0].name : null, }; res.status(201).json({ option }); @@ -198,11 +198,6 @@ router.post("/responses", async (req, res) => { if (!user || !user.isLoggedIn) { return res.status(401).json({ message: "Not logged in" }); } - - if (!isEmployee(user)) { - return res.status(403).json({ message: "Employee access required" }); - } - const { option_ids } = req.body; if (!option_ids || !Array.isArray(option_ids) || option_ids.length === 0) { return res.status(400).json({ message: "option_ids array is required" }); @@ -267,7 +262,7 @@ router.get("/responses", async (req, res) => { FROM onboarding_responses or JOIN onboarding_question_options oqo ON oqo.id = or.option_id JOIN onboarding_questions oq ON oq.id = oqo.question_id - JOIN tags t ON t.id = oqo.tag_id + LEFT JOIN tags t ON t.id = oqo.tag_id WHERE or.user_id = $1 ORDER BY oq.position ASC `, From ca2223fdd10bc7317dd78ceac8c3c55ff12f8615 Mon Sep 17 00:00:00 2001 From: lavanyagarg112 Date: Wed, 9 Jul 2025 09:48:53 +0800 Subject: [PATCH 4/4] Make tags and form organisation specific --- routes/auth.js | 23 +++++--- routes/courses.js | 36 ++++++++++-- routes/onboarding.js | 127 ++++++++++++++++++++++++++++++++++++++----- routes/orgs.js | 11 +++- 4 files changed, 168 insertions(+), 29 deletions(-) diff --git a/routes/auth.js b/routes/auth.js index c7b63ec..0d601f9 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -129,15 +129,24 @@ router.post("/complete-onboarding", async (req, res) => { const organisation = mem.rows[0] || null; if (organisation && organisation.role === "employee") { - const responseCheck = await pool.query( - `SELECT COUNT(*) as response_count FROM onboarding_responses WHERE user_id = $1`, - [user.userId] + const questionCheck = await pool.query( + `SELECT COUNT(*) as question_count FROM onboarding_questions WHERE organisation_id = $1`, + [organisation.id] ); - if (parseInt(responseCheck.rows[0].response_count) === 0) { - return res.status(400).json({ - message: "Onboarding questionnaire must be completed first", - }); + const hasQuestions = parseInt(questionCheck.rows[0].question_count) > 0; + + if (hasQuestions) { + const responseCheck = await pool.query( + `SELECT COUNT(*) as response_count FROM onboarding_responses WHERE user_id = $1`, + [user.userId] + ); + + if (parseInt(responseCheck.rows[0].response_count) === 0) { + return res.status(400).json({ + message: "Onboarding questionnaire must be completed first", + }); + } } } diff --git a/routes/courses.js b/routes/courses.js index 6ef818a..2065ff2 100644 --- a/routes/courses.js +++ b/routes/courses.js @@ -1810,10 +1810,14 @@ router.post("/add-tags", async (req, res) => { } const userId = session.userId; + const organisationId = session.organisation?.id; const isAdmin = session.organisation?.role === "admin"; if (!isAdmin) { return res.status(403).json({ message: "Forbidden" }); } + if (!organisationId) { + return res.status(400).json({ message: "Organization required" }); + } const { tags } = req.body; if (!Array.isArray(tags)) { return res.status(400).json({ message: "tags[] are required" }); @@ -1827,10 +1831,10 @@ router.post("/add-tags", async (req, res) => { continue; // skip if tag has no name } await client.query( - `INSERT INTO tags (name) - VALUES ($1) - ON CONFLICT (name) DO NOTHING`, - [tag.name] + `INSERT INTO tags (name, organisation_id) + VALUES ($1, $2) + ON CONFLICT (name, organisation_id) DO NOTHING`, + [tag.name, organisationId] ); } await client.query("COMMIT"); @@ -1845,10 +1849,26 @@ router.post("/add-tags", async (req, res) => { }); router.get("/tags", async (req, res) => { + const { auth } = req.cookies; + if (!auth) return res.status(401).json({ message: "Not authenticated" }); + + let session; + try { + session = JSON.parse(auth); + } catch { + return res.status(400).json({ message: "Invalid session data" }); + } + + const organisationId = session.organisation?.id; + if (!organisationId) { + return res.status(400).json({ message: "Organization required" }); + } + const client = await pool.connect(); try { const { rows } = await client.query( - `SELECT id, name FROM tags ORDER BY name` + `SELECT id, name FROM tags WHERE organisation_id = $1 ORDER BY name`, + [organisationId] ); res.json(rows); } catch (err) { @@ -1870,10 +1890,14 @@ router.delete("/delete-tag", async (req, res) => { return res.status(400).json({ message: "Invalid session data" }); } + const organisationId = session.organisation?.id; const isAdmin = session.organisation?.role === "admin"; if (!isAdmin) { return res.status(403).json({ message: "Forbidden" }); } + if (!organisationId) { + return res.status(400).json({ message: "Organization required" }); + } const { tagId } = req.body; if (!tagId) { return res.status(400).json({ message: "tagId is required" }); @@ -1882,7 +1906,7 @@ router.delete("/delete-tag", async (req, res) => { const client = await pool.connect(); try { await client.query("BEGIN"); - await client.query(`DELETE FROM tags WHERE id = $1`, [tagId]); + await client.query(`DELETE FROM tags WHERE id = $1 AND organisation_id = $2`, [tagId, organisationId]); await client.query("COMMIT"); return res.status(200).json({ success: true }); } catch (err) { diff --git a/routes/onboarding.js b/routes/onboarding.js index 87a458f..021b8d5 100644 --- a/routes/onboarding.js +++ b/routes/onboarding.js @@ -26,16 +26,21 @@ router.get("/questions", async (req, res) => { return res.status(401).json({ message: "Not logged in" }); } - // if (!isEmployee(user)) { - // return res.status(403).json({ message: "Employee access required" }); - // } + const organisationId = user.organisation?.id; + if (!organisationId) { + return res.status(400).json({ message: "Organization required" }); + } try { - const questionsResult = await pool.query(` + const questionsResult = await pool.query( + ` SELECT id, question_text, position FROM onboarding_questions + WHERE organisation_id = $1 ORDER BY position ASC - `); + `, + [organisationId] + ); const questions = []; for (const question of questionsResult.rows) { @@ -80,14 +85,19 @@ router.post("/questions", async (req, res) => { return res.status(400).json({ message: "question_text is required" }); } + const organisationId = user.organisation?.id; + if (!organisationId) { + return res.status(400).json({ message: "Organization required" }); + } + try { const result = await pool.query( ` - INSERT INTO onboarding_questions (question_text, position) - VALUES ($1, $2) + INSERT INTO onboarding_questions (question_text, position, organisation_id) + VALUES ($1, $2, $3) RETURNING id, question_text, position `, - [question_text, position] + [question_text, position, organisationId] ); res.status(201).json({ question: result.rows[0] }); @@ -118,9 +128,15 @@ router.post("/questions/:id/options", async (req, res) => { try { await client.query("BEGIN"); + const organisationId = user.organisation?.id; + if (!organisationId) { + await client.query("ROLLBACK"); + return res.status(400).json({ message: "Organization required" }); + } + const questionCheck = await client.query( - "SELECT id FROM onboarding_questions WHERE id = $1", - [id] + "SELECT id FROM onboarding_questions WHERE id = $1 AND organisation_id = $2", + [id, organisationId] ); if (questionCheck.rows.length === 0) { await client.query("ROLLBACK"); @@ -129,9 +145,10 @@ router.post("/questions/:id/options", async (req, res) => { let tagCheck = { rows: [] }; if (tag_id) { - tagCheck = await client.query("SELECT id, name FROM tags WHERE id = $1", [ - tag_id, - ]); + tagCheck = await client.query( + "SELECT id, name FROM tags WHERE id = $1 AND organisation_id = $2", + [tag_id, organisationId] + ); if (tagCheck.rows.length === 0) { await client.query("ROLLBACK"); return res.status(404).json({ message: "Tag not found" }); @@ -176,12 +193,31 @@ router.delete("/questions/:id", async (req, res) => { const { id } = req.params; + const organisationId = user.organisation?.id; + if (!organisationId) { + return res.status(400).json({ message: "Organization required" }); + } + try { - const result = await pool.query( - "DELETE FROM onboarding_questions WHERE id = $1 RETURNING id", + // Check if question has any options before deletion + const optionCheck = await pool.query( + "SELECT COUNT(*) as option_count FROM onboarding_question_options WHERE question_id = $1", [id] ); + const hasOptions = parseInt(optionCheck.rows[0].option_count) > 0; + if (hasOptions) { + return res.status(400).json({ + message: + "Cannot delete question that has options. Please delete all options first.", + }); + } + + const result = await pool.query( + "DELETE FROM onboarding_questions WHERE id = $1 AND organisation_id = $2 RETURNING id", + [id, organisationId] + ); + if (result.rows.length === 0) { return res.status(404).json({ message: "Question not found" }); } @@ -193,6 +229,67 @@ router.delete("/questions/:id", async (req, res) => { } }); +router.delete("/options/:optionId", async (req, res) => { + const user = getAuthUser(req); + if (!user || !user.isLoggedIn) { + return res.status(401).json({ message: "Not logged in" }); + } + + if (!isAdmin(user)) { + return res.status(403).json({ message: "Admin access required" }); + } + + const { optionId } = req.params; + const organisationId = user.organisation?.id; + if (!organisationId) { + return res.status(400).json({ message: "Organization required" }); + } + + try { + const optionCheck = await pool.query( + ` + SELECT oqo.question_id, oq.organisation_id + FROM onboarding_question_options oqo + JOIN onboarding_questions oq ON oq.id = oqo.question_id + WHERE oqo.id = $1 + `, + [optionId] + ); + + if (optionCheck.rows.length === 0) { + return res.status(404).json({ message: "Option not found" }); + } + + const option = optionCheck.rows[0]; + if (option.organisation_id !== organisationId) { + return res.status(403).json({ message: "Access denied" }); + } + + const optionCountCheck = await pool.query( + "SELECT COUNT(*) as option_count FROM onboarding_question_options WHERE question_id = $1", + [option.question_id] + ); + + const optionCount = parseInt(optionCountCheck.rows[0].option_count); + if (optionCount <= 1) { + return res.status(400).json({ + message: + "Cannot delete the last option. Questions must have at least one option.", + }); + } + + const result = await pool.query( + "DELETE FROM onboarding_question_options WHERE id = $1 RETURNING id", + [optionId] + ); + + res.json({ message: "Option deleted successfully" }); + } catch (err) { + console.error(err); + res.status(500).json({ message: "Server error" }); + } +}); + router.post("/responses", async (req, res) => { const user = getAuthUser(req); if (!user || !user.isLoggedIn) { diff --git a/routes/orgs.js b/routes/orgs.js index fe383d5..1ec4bbb 100644 --- a/routes/orgs.js +++ b/routes/orgs.js @@ -14,7 +14,6 @@ function setAuthCookie(res, payload) { }); } -// Create a new organization AND make the current user its admin router.post("/", async (req, res) => { const { auth } = req.cookies; if (!auth) return res.status(401).json({ message: "Not authenticated" }); @@ -115,6 +114,16 @@ router.post("/addemployee", async (req, res) => { ); await client.query("COMMIT"); + + setAuthCookie(res, { + ...session, + organisation: { + id: org.id, + organisationname: org.organisation_name, + role: "employee", + }, + }); + return res.status(201).json({ organisation: { ...org, role: "employee" } }); } catch (err) { await client.query("ROLLBACK");