From 330cd938cab07a7967bc06f03ee4b6d4a6a5cd59 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Wed, 6 Nov 2024 17:44:07 +0000 Subject: [PATCH] Add UUID package and enhance user registration flow; update schemas and forms to support user creation with registration tokens --- package-lock.json | 16 +++++- package.json | 3 +- src/lib/server/db/index.ts | 8 +-- src/lib/server/db/schema.ts | 1 + src/routes/admin/+page.server.ts | 83 +++++++++++++++++++++++++++++--- src/routes/admin/+page.svelte | 50 ++++++++++++++----- src/routes/admin/schema.ts | 1 + 7 files changed, 139 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5d1a86d..7ca0976 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,8 @@ "@sveltejs/adapter-node": "^5.2.9", "better-sqlite3": "^11.1.2", "drizzle-orm": "^0.33.0", - "js-sha256": "^0.11.0" + "js-sha256": "^0.11.0", + "uuid": "^11.0.2" }, "devDependencies": { "@sveltejs/kit": "^2.0.0", @@ -8255,6 +8256,19 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/uuid": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.2.tgz", + "integrity": "sha512-14FfcOJmqdjbBPdDjFQyk/SdT4NySW4eM0zcG+HqbHP5jzuH56xO3J1DGhgs/cEMCfwYi3HQI1gnTO62iaG+tQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, "node_modules/valibot": { "version": "0.41.0", "resolved": "https://registry.npmjs.org/valibot/-/valibot-0.41.0.tgz", diff --git a/package.json b/package.json index b46b369..9784cef 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "@sveltejs/adapter-node": "^5.2.9", "better-sqlite3": "^11.1.2", "drizzle-orm": "^0.33.0", - "js-sha256": "^0.11.0" + "js-sha256": "^0.11.0", + "uuid": "^11.0.2" } } diff --git a/src/lib/server/db/index.ts b/src/lib/server/db/index.ts index 1f0d4e4..95c3cb4 100644 --- a/src/lib/server/db/index.ts +++ b/src/lib/server/db/index.ts @@ -25,8 +25,8 @@ export function sanitizeUser(user: User): User { return user; } -import { users, sessions } from './schema'; // Importing the users and sessions tables and their types -import type { User, Session } from './schema'; // Importing the User and Session types +import { users, sessions, registrations } from './schema'; // Importing the users and sessions tables and their types +import type { User, Session, Registration } from './schema'; // Importing the User and Session types import { count, eq } from 'drizzle-orm'; -export { users, sessions }; // Exporting the users and sessions tables and their types -export type { User, Session }; // Exporting the User and Session types \ No newline at end of file +export { users, sessions, registrations }; // Exporting the users and sessions tables and their types +export type { User, Session, Registration }; // Exporting the User and Session types \ No newline at end of file diff --git a/src/lib/server/db/schema.ts b/src/lib/server/db/schema.ts index d49dcba..6f0fb01 100644 --- a/src/lib/server/db/schema.ts +++ b/src/lib/server/db/schema.ts @@ -34,3 +34,4 @@ export const registrations = sqliteTable('registrations', { export type User = InferSelectModel; export type Session = InferSelectModel; +export type Registration = InferSelectModel; diff --git a/src/routes/admin/+page.server.ts b/src/routes/admin/+page.server.ts index d31669e..a9b464d 100644 --- a/src/routes/admin/+page.server.ts +++ b/src/routes/admin/+page.server.ts @@ -1,13 +1,20 @@ -import { db, users, type User } from "$lib/server/db"; -import { message, setError, superValidate } from "sveltekit-superforms"; +import { db, users, type User, registrations } from "$lib/server/db"; +import { type Infer, message, setError, superValidate } from "sveltekit-superforms"; import type { Actions, PageServerLoad } from "./$types"; import { zod } from "sveltekit-superforms/adapters"; -import { userFormSchema } from "./schema"; -import { redirect } from "@sveltejs/kit"; +import { userFormSchema, type UserFormSchema } from "./schema"; +import { fail, redirect, text } from "@sveltejs/kit"; +import { eq } from "drizzle-orm"; +import { v4 as uuid4 } from "uuid"; + +type Message = { + text: string | undefined; + token: string | undefined; +} export const load: PageServerLoad = async (event) => { // set up the form - const form = await superValidate(zod(userFormSchema)); + const form = await superValidate,Message>(zod(userFormSchema)); // server-side guard - check for user if (!event.locals.user) { return redirect(302, "/logout"); @@ -22,7 +29,7 @@ export const load: PageServerLoad = async (event) => { // return the public users return { users: publicUsers, - form: form + form: form, } } @@ -38,11 +45,75 @@ export const actions: Actions = { if (!event.locals.user) { return } + // validate the form + const form = await superValidate,Message>(event, zod(userFormSchema)); + if (!form.valid) { + return fail(400, { form }); + } + // ensure we do not have a user Id + if (form.data.id) { + return setError(form, '', 'User ID is not allowed'); + } + // create the user + const created = await db.insert(users).values({ + firstname: form.data.firstname, + lastname: form.data.lastname, + email: form.data.email, + }).returning(); + // ensure we created one and only one user + if (created.length === 0) { + return setError(form, '', 'User not created'); + } + if (created.length > 1) { + // this should never happen, but hey, we are being safe + return setError(form, '', 'Multiple users created'); + } + const createdUser = created[0]; + // now we need to create a registration token so the user can create a password + const regToken = uuid4(); + const registration = await db.insert(registrations).values({ + userId: createdUser.id, + token: regToken, + created_at: new Date(Date.now()), + }).returning({ id: registrations.id }); + // ensure we created one and only one registration + if (registration.length !== 1) { + return setError(form, '', 'Error creating registration token'); + } + // return the created user and the registration token + return message(form, { + text: 'User created', + token: regToken, + }); }, update: async (event) => { // server-side guard - check for user if (!event.locals.user) { return } + // validate the form + const form = await superValidate,Message>(event, zod(userFormSchema)); + if (!form.valid) { + return fail(400, { form }); + } + // ensure we have a user Id + if (!form.data.id) { + return setError(form, '', 'User ID is required'); + } + // update the user + const updated: { id: number }[] = await db.update(users).set({ + firstname: form.data.firstname, + lastname: form.data.lastname, + email: form.data.email, + }).where(eq(users.id, form.data.id)).returning({ id: users.id}); + // make sure the user was updated + if (updated.length === 0) { + return setError(form, '', 'User not found'); + } + // return the updated user + return message(form, { + text: 'User updated', + token: undefined, + }); } } \ No newline at end of file diff --git a/src/routes/admin/+page.svelte b/src/routes/admin/+page.svelte index 298e7d5..82b77f5 100644 --- a/src/routes/admin/+page.svelte +++ b/src/routes/admin/+page.svelte @@ -1,12 +1,13 @@ @@ -69,7 +95,7 @@ {m.adminTitle()} {m.adminDescription()} - @@ -185,7 +211,8 @@ formData.set({ firstname: "", lastname: "", - email: "" + email: "", + id: undefined, }); } }} @@ -197,6 +224,7 @@ use:enhance class="space-y-4" > + {dialogAction} User Form Description @@ -228,7 +256,7 @@ - + diff --git a/src/routes/admin/schema.ts b/src/routes/admin/schema.ts index 343222a..e23163a 100644 --- a/src/routes/admin/schema.ts +++ b/src/routes/admin/schema.ts @@ -2,6 +2,7 @@ import { z } from 'zod'; // dont tell them errors, so not to disclose anything export const userFormSchema = z.object({ + id: z.number().int().positive().optional(), firstname: z.string().min(3).max(50), lastname: z.string().min(3).max(50), email: z.string().email(),