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:
parent
372db59211
commit
ba6a5283fe
12 changed files with 273 additions and 10 deletions
2
migrations/0007_nervous_scalphunter.sql
Normal file
2
migrations/0007_nervous_scalphunter.sql
Normal 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;
|
147
migrations/meta/0007_snapshot.json
Normal file
147
migrations/meta/0007_snapshot.json
Normal 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": {}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -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("/");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
12
src/routes/+layout.server.ts
Normal file
12
src/routes/+layout.server.ts
Normal 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,
|
||||||
|
};
|
||||||
|
};
|
|
@ -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";
|
||||||
|
|
30
src/routes/+page.server.ts
Normal file
30
src/routes/+page.server.ts
Normal 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");
|
||||||
|
},
|
||||||
|
};
|
|
@ -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}
|
||||||
|
|
|
@ -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, {});
|
||||||
|
|
|
@ -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 />
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue