From 2050c715dc276e338a83a5fe0d082fc2c6d246cd Mon Sep 17 00:00:00 2001 From: Markus Brueckner Date: Sun, 15 Dec 2024 09:11:40 +0100 Subject: [PATCH] - fix graph rendering issues & add server-side logging --- package-lock.json | 27 +++++++-- package.json | 2 + src/hooks.server.ts | 6 +- src/lib/components/Diagram.svelte | 55 ++++++++++++------- src/lib/queries.ts | 2 +- .../(app)/survey/[surveyId]/+page.server.ts | 7 +++ src/routes/[accessToken]/+page.server.ts | 26 +++++++-- src/routes/login/+page.server.ts | 7 ++- 8 files changed, 100 insertions(+), 32 deletions(-) diff --git a/package-lock.json b/package-lock.json index 499c5ab..839a8c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "d3-array": "^3.2.4", "d3-scale": "^4.0.2", "d3-shape": "^3.2.0", + "debug": "^4.4.0", "drizzle-orm": "^0.36.4", "layerchart": "^0.59.1", "mysql2": "^3.11.4" @@ -29,6 +30,7 @@ "@types/d3-array": "^3.2.1", "@types/d3-scale": "^4.0.8", "@types/d3-shape": "^3.1.6", + "@types/debug": "^4.1.12", "@types/eslint": "^9.6.0", "autoprefixer": "^10.4.20", "drizzle-kit": "^0.28.1", @@ -2364,6 +2366,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/eslint": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", @@ -2388,6 +2400,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.9.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", @@ -3425,10 +3444,9 @@ } }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -5347,7 +5365,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/mysql2": { diff --git a/package.json b/package.json index b66096d..3d34c03 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@types/d3-array": "^3.2.1", "@types/d3-scale": "^4.0.8", "@types/d3-shape": "^3.1.6", + "@types/debug": "^4.1.12", "@types/eslint": "^9.6.0", "autoprefixer": "^10.4.20", "drizzle-kit": "^0.28.1", @@ -46,6 +47,7 @@ "d3-array": "^3.2.4", "d3-scale": "^4.0.2", "d3-shape": "^3.2.0", + "debug": "^4.4.0", "drizzle-orm": "^0.36.4", "layerchart": "^0.59.1", "mysql2": "^3.11.4" diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 67bad01..2bc8bc7 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -2,10 +2,14 @@ import type { Handle } from "@sveltejs/kit"; import { deleteSessionTokenCookie, setSessionTokenCookie, validateSession } from "./lib/session/session"; +import debug from 'debug'; + +const log = debug('hooks'); + export const handle: Handle = async ({ event, resolve }) => { const token = event.cookies.get("session") ?? null; if (token === null) { - console.log("No session available") + log("No session available") event.locals.userId = null; event.locals.session = null; return resolve(event); diff --git a/src/lib/components/Diagram.svelte b/src/lib/components/Diagram.svelte index f0ca448..bcc5de3 100644 --- a/src/lib/components/Diagram.svelte +++ b/src/lib/components/Diagram.svelte @@ -1,6 +1,6 @@ @@ -51,13 +57,22 @@ {#each flatGroup(data, (d) => d.participant) as [participant, ratings], idx} + {/each} - - + + diff --git a/src/lib/queries.ts b/src/lib/queries.ts index c2d18f7..6835a43 100644 --- a/src/lib/queries.ts +++ b/src/lib/queries.ts @@ -22,7 +22,7 @@ export async function loadSurvey(id: number, ownerId: number) { accessToken: participant.accessToken, answers: answers.filter(answer => answer.participantId === participant.id).map(answer => ({ skillId: answer.skillId, - rating: answer.rating + rating: answer.rating / 10 // convert back to original vallue. The DB stores integer values only to prevent rounding and matching errors })) })), skills: skills.map(skill => ({ diff --git a/src/routes/(app)/survey/[surveyId]/+page.server.ts b/src/routes/(app)/survey/[surveyId]/+page.server.ts index b5c781c..fe619d3 100644 --- a/src/routes/(app)/survey/[surveyId]/+page.server.ts +++ b/src/routes/(app)/survey/[surveyId]/+page.server.ts @@ -2,18 +2,25 @@ import { error } from '@sveltejs/kit'; import type { PageServerLoad } from './$types'; import { loadSurvey } from '$lib/queries'; +import debug from 'debug'; + +const log = debug('survey:admin'); + export const load: PageServerLoad = async ({ params, locals }) => { const surveyId = parseInt(params.surveyId); if (isNaN(surveyId)) { + log('Invalid survey ID %s', params.surveyId); error(400, 'Invalid survey ID'); } if (!locals.userId) { + log('User is not logged in'); error(403, 'User is not logged in'); } const surveyData = await loadSurvey(surveyId, locals.userId); if (!surveyData) { + log('Survey not found or user (%s) does not have access: %s', locals.userId, params.surveyId); error(404, 'Survey not found'); } return surveyData; diff --git a/src/routes/[accessToken]/+page.server.ts b/src/routes/[accessToken]/+page.server.ts index eaf10ef..3c9de58 100644 --- a/src/routes/[accessToken]/+page.server.ts +++ b/src/routes/[accessToken]/+page.server.ts @@ -5,6 +5,11 @@ import type { PageServerLoad } from './$types'; import { eq } from 'drizzle-orm'; +import debug from 'debug'; + +const log_load = debug('survey:load'); +const log_store = debug('survey:store'); + export const load: PageServerLoad = async ({ params, url }) => { const results = await db.select() .from(surveyAccessTable) @@ -13,10 +18,12 @@ export const load: PageServerLoad = async ({ params, url }) => { .limit(1); if (results.length === 0) { + log_load('Survey not found: %s', params.accessToken); error(404, 'Survey not found'); } if (await db.$count(surveyAnswersTable, eq(surveyAnswersTable.participantId, results[0].survey_access_table.id)) > 0) { + log_load('Answers already submitted: %s', params.accessToken); error(400, 'Answers already submitted'); } @@ -44,10 +51,12 @@ export const actions = { .limit(1); if (results.length === 0) { + log_store('Survey not found: %s', params.accessToken); error(404, 'Survey not found'); } if (await db.$count(surveyAnswersTable, eq(surveyAnswersTable.participantId, results[0].survey_access_table.id)) > 0) { + log_store('Answers already submitted: %s', params.accessToken); error(400, 'Answers already submitted'); } @@ -56,20 +65,29 @@ export const actions = { const formData = await request.formData(); // validate that the form doesn't contain invalid skill IDs - const skillEntries = [...formData.entries()].filter(([key, _]) => key.startsWith('disable-')); + const skillEntries = [...formData.entries()].filter(([key, _]) => !key.startsWith('disable-')); const allIdsValid = skillEntries.every(([key, _]) => skills.some(skill => skill.id.toString() === key)); - if (!allIdsValid || skillEntries.length !== skills.length) { + const deselectedIds = [...formData.entries().filter(([key, _]) => key.startsWith('disable-')).map(([key, _]) => parseInt(key.replace('disable-', '')))]; + if (!allIdsValid || skillEntries.length + deselectedIds.length !== skills.length) { + let missing_ids = skills.filter(skill => !skillEntries.some(([key, _]) => key === skill.id.toString()) && !deselectedIds.includes(skill.id)); + let invalid_ids = skillEntries.filter(([key, _]) => !skills.some(skill => skill.id.toString() === key)).map(([key, _]) => key); + log_store.log("Invalid (%o) or missing (%o) skill IDs", invalid_ids, missing_ids); error(400, 'Invalid skill ID'); } const answers = skillEntries.map(([key, value]) => ({ participantId: results[0].survey_access_table.id, skillId: parseInt(key), - rating: parseFloat(value.toString()) + rating: parseFloat(value.toString()) * 10 // convert to int to store in the database without rounding issues etc. })); - if (answers.some(answer => isNaN(answer.rating) || answer.rating < 0 || answer.rating > 3)) { + if (answers.some(answer => isNaN(answer.rating) || answer.rating < 0 || answer.rating > 30)) { + log_store( + "Invalid answer: %o", + answers.filter(answer => isNaN(answer.rating) || answer.rating < 0 || answer.rating > 30) + ); error(400, 'Invalid answer'); } + await db.insert(surveyAnswersTable).values([...answers]); redirect(303, `/${params.accessToken}/thanks`); diff --git a/src/routes/login/+page.server.ts b/src/routes/login/+page.server.ts index d3c19b4..1210710 100644 --- a/src/routes/login/+page.server.ts +++ b/src/routes/login/+page.server.ts @@ -8,17 +8,21 @@ import { error } from '@sveltejs/kit'; import { verify } from '@node-rs/argon2'; import { generateRandomToken } from '$lib/randomToken'; +import debug from 'debug'; + +let log = debug('login'); + export const actions = { default: async (event) => { const formData = await event.request.formData(); const email = formData.get('email')?.toString(); const password = formData.get('password')?.toString(); if (!email || !password) { + log('Email and password are required'); error(400, 'Email and password are required'); } const userCredentials = await db.select().from(usersTable).where(eq(usersTable.email, email)); - console.log(userCredentials, formData.get('email')); if (userCredentials.length === 0) { error(403, 'Invalid credentials'); } @@ -38,6 +42,7 @@ export const actions = { } } else { + log('Invalid credentials for %s', email); error(403, 'Invalid credentials'); } }