1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-07-30 10:19:37 +02:00

Add user first and last name fields to signup form

This commit is contained in:
Sean Morley 2024-04-03 22:59:05 +00:00
parent 372db59211
commit ba6a5283fe
12 changed files with 273 additions and 10 deletions

View file

@ -0,0 +1,2 @@
ALTER TABLE "user" ADD COLUMN "first_name" text NOT NULL;--> statement-breakpoint
ALTER TABLE "user" ADD COLUMN "last_name" text NOT NULL;

View file

@ -0,0 +1,147 @@
{
"id": "2039600b-1f5f-4f37-84dd-bb40636855e7",
"prevId": "b0849b3e-02e1-42e1-b07c-6fa613c98e82",
"version": "5",
"dialect": "pg",
"tables": {
"featuredAdventures": {
"name": "featuredAdventures",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"location": {
"name": "location",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"session": {
"name": "session",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"session_user_id_user_id_fk": {
"name": "session_user_id_user_id_fk",
"tableFrom": "session",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"sharedAdventures": {
"name": "sharedAdventures",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"data": {
"name": "data",
"type": "json",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true
},
"first_name": {
"name": "first_name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"last_name": {
"name": "last_name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"hashed_password": {
"name": "hashed_password",
"type": "varchar",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View file

@ -50,6 +50,13 @@
"when": 1712105206127, "when": 1712105206127,
"tag": "0006_melted_leech", "tag": "0006_melted_leech",
"breakpoints": true "breakpoints": true
},
{
"idx": 7,
"version": "5",
"when": 1712167204757,
"tag": "0007_nervous_scalphunter",
"breakpoints": true
} }
] ]
} }

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { visitCount } from "$lib/utils/stores/visitCountStore"; import { visitCount } from "$lib/utils/stores/visitCountStore";
import { goto } from "$app/navigation"; import { goto } from "$app/navigation";
import type { DatabaseUser } from "$lib/server/auth";
async function goHome() { async function goHome() {
goto("/"); goto("/");
} }

View file

@ -21,6 +21,8 @@ export const sharedAdventures = pgTable("sharedAdventures", {
export const userTable = pgTable("user", { export const userTable = pgTable("user", {
id: text("id").primaryKey(), id: text("id").primaryKey(),
username: text("username").notNull(), username: text("username").notNull(),
first_name: text("first_name").notNull(),
last_name: text("last_name").notNull(),
hashed_password: varchar("hashed_password").notNull(), hashed_password: varchar("hashed_password").notNull(),
}); });

View file

@ -1,9 +1,8 @@
import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle"; import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle";
import { Lucia } from "lucia"; import { Lucia, TimeSpan } from "lucia";
import { dev } from "$app/environment"; import { dev } from "$app/environment";
import { userTable, sessionTable } from "$lib/db/schema"; import { userTable, sessionTable } from "$lib/db/schema";
import { db } from "$lib/db/db.server"; import { db } from "$lib/db/db.server";
import { Argon2id } from "oslo/password";
const adapter = new DrizzlePostgreSQLAdapter(db, sessionTable, userTable); const adapter = new DrizzlePostgreSQLAdapter(db, sessionTable, userTable);
@ -17,6 +16,9 @@ export const lucia = new Lucia(adapter, {
return { return {
// attributes has the type of DatabaseUserAttributes // attributes has the type of DatabaseUserAttributes
username: attributes.username, username: attributes.username,
id: attributes.id,
first_name: attributes.first_name,
last_name: attributes.last_name,
}; };
}, },
}); });
@ -24,15 +26,14 @@ export const lucia = new Lucia(adapter, {
declare module "lucia" { declare module "lucia" {
interface Register { interface Register {
Lucia: typeof lucia; Lucia: typeof lucia;
DatabaseUserAttributes: DatabaseUserAttributes; DatabaseUserAttributes: DatabaseUser;
} }
} }
interface DatabaseUserAttributes {
username: string;
}
export interface DatabaseUser { export interface DatabaseUser {
id: string; id: string;
username: string; username: string;
first_name: string;
last_name: string;
hashed_password: string; hashed_password: string;
} }

View file

@ -0,0 +1,12 @@
import type { LayoutServerLoad, PageServerLoad } from "./$types";
export const load: LayoutServerLoad = async (event) => {
if (event.locals.user) {
return {
user: event.locals.user,
};
}
return {
user: null,
};
};

View file

@ -1,4 +1,4 @@
<script> <script lang="ts">
import Footer from "$lib/components/Footer.svelte"; import Footer from "$lib/components/Footer.svelte";
import Navbar from "$lib/components/Navbar.svelte"; import Navbar from "$lib/components/Navbar.svelte";
import "../app.css"; import "../app.css";

View file

@ -0,0 +1,30 @@
import { lucia } from "$lib/server/auth";
import { fail, redirect } from "@sveltejs/kit";
import type { Actions, PageServerLoad } from "./$types";
export const load: PageServerLoad = async (event) => {
if (event.locals.user)
return {
user: event.locals.user,
};
return {
user: null,
};
};
export const actions: Actions = {
default: async (event) => {
if (!event.locals.session) {
return fail(401);
}
await lucia.invalidateSession(event.locals.session.id);
const sessionCookie = lucia.createBlankSessionCookie();
event.cookies.set(sessionCookie.name, sessionCookie.value, {
path: ".",
...sessionCookie.attributes,
});
return redirect(302, "/login");
},
};

View file

@ -1,4 +1,8 @@
<script lang="ts"> <script lang="ts">
import { enhance } from "$app/forms";
import type { PageData } from "./$types";
export let data: PageData;
import { goto } from "$app/navigation"; import { goto } from "$app/navigation";
import campingDrawing from "$lib/assets/camping.svg"; import campingDrawing from "$lib/assets/camping.svg";
import { visitCount } from "$lib/utils/stores/visitCountStore"; import { visitCount } from "$lib/utils/stores/visitCountStore";
@ -10,6 +14,9 @@
<div class="flex flex-col items-center justify-center"> <div class="flex flex-col items-center justify-center">
<article class="prose"> <article class="prose">
{#if data.user && data.user.username != ""}
<h1 class="mb-4">Welcome {data.user.first_name}. Let's get Exploring!</h1>
{/if}
<h1 class="mb-4">Welcome. Let's get Exploring!</h1> <h1 class="mb-4">Welcome. Let's get Exploring!</h1>
</article> </article>
<img src={campingDrawing} class="w-1/4 mb-4" alt="Logo" /> <img src={campingDrawing} class="w-1/4 mb-4" alt="Logo" />
@ -23,3 +30,9 @@
</div> </div>
</div> </div>
</div> </div>
{#if data.user}
<form method="post" use:enhance>
<button>Sign out</button>
</form>
{/if}

View file

@ -4,17 +4,29 @@ import { fail, redirect } from "@sveltejs/kit";
import { generateId } from "lucia"; import { generateId } from "lucia";
import { Argon2id } from "oslo/password"; import { Argon2id } from "oslo/password";
import { db } from "$lib/db/db.server"; import { db } from "$lib/db/db.server";
import type { DatabaseUser } from "$lib/server/auth";
import type { Actions } from "./$types"; import type { Actions } from "./$types";
import { userTable } from "$lib/db/schema"; import { userTable } from "$lib/db/schema";
import { eq } from "drizzle-orm";
export const actions: Actions = { export const actions: Actions = {
default: async (event) => { default: async (event) => {
const formData = await event.request.formData(); const formData = await event.request.formData();
const username = formData.get("username"); const username = formData.get("username");
const password = formData.get("password"); const password = formData.get("password");
const firstName = formData.get("first_name");
const lastName = formData.get("last_name");
// username must be between 4 ~ 31 characters, and only consists of lowercase letters, 0-9, -, and _ // username must be between 4 ~ 31 characters, and only consists of lowercase letters, 0-9, -, and _
// keep in mind some database (e.g. mysql) are case insensitive // keep in mind some database (e.g. mysql) are case insensitive
// check all to make sure all fields are provided
if (!username || !password || !firstName || !lastName) {
return fail(400, {
message: "All fields are required",
});
}
if ( if (
typeof username !== "string" || typeof username !== "string" ||
username.length < 3 || username.length < 3 ||
@ -35,17 +47,50 @@ export const actions: Actions = {
}); });
} }
if (
typeof firstName !== "string" ||
firstName.length < 1 ||
firstName.length > 255
) {
return fail(400, {
message: "Invalid first name",
});
}
if (
typeof lastName !== "string" ||
lastName.length < 1 ||
lastName.length > 255
) {
return fail(400, {
message: "Invalid last name",
});
}
const userId = generateId(15); const userId = generateId(15);
const hashedPassword = await new Argon2id().hash(password); const hashedPassword = await new Argon2id().hash(password);
// TODO: check if username is already used const usernameTaken = await db
.select()
.from(userTable)
.where(eq(userTable.username, username))
.limit(1)
.then((results) => results[0] as unknown as DatabaseUser | undefined);
if (usernameTaken) {
return fail(400, {
message: "Username already taken",
});
}
await db await db
.insert(userTable) .insert(userTable)
.values({ .values({
id: userId, id: userId,
username: username, username: username,
first_name: firstName,
last_name: lastName,
hashed_password: hashedPassword, hashed_password: hashedPassword,
}) } as DatabaseUser)
.execute(); .execute();
const session = await lucia.createSession(userId, {}); const session = await lucia.createSession(userId, {});

View file

@ -6,6 +6,10 @@
<h1>Sign up</h1> <h1>Sign up</h1>
<form method="post" use:enhance> <form method="post" use:enhance>
<label for="username">Username</label> <label for="username">Username</label>
<label for="first_name">First Name</label>
<input name="first_name" id="first_name" /><br />
<label for="last_name">Last Name</label>
<input name="last_name" id="last_name" /><br />
<input name="username" id="username" /><br /> <input name="username" id="username" /><br />
<label for="password">Password</label> <label for="password">Password</label>
<input type="password" name="password" id="password" /><br /> <input type="password" name="password" id="password" /><br />