Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion database/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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 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;
37 changes: 27 additions & 10 deletions routes/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 });
Expand All @@ -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,
Expand All @@ -137,7 +128,33 @@ router.post("/complete-onboarding", async (req, res) => {

const organisation = mem.rows[0] || null;

// Regenerate auth cookie
if (organisation && organisation.role === "employee") {
const questionCheck = await pool.query(
`SELECT COUNT(*) as question_count FROM onboarding_questions WHERE organisation_id = $1`,
[organisation.id]
);

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",
});
}
}
}

await pool.query(
`UPDATE users SET has_completed_onboarding = true WHERE id = $1`,
[user.userId]
);

setAuthCookie(res, {
...user,
hasCompletedOnboarding: true,
Expand Down
36 changes: 30 additions & 6 deletions routes/courses.js
Original file line number Diff line number Diff line change
Expand Up @@ -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" });
Expand All @@ -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");
Expand All @@ -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) {
Expand All @@ -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" });
Expand All @@ -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) {
Expand Down
Loading