lots of changes

This commit is contained in:
Omer Sabic 2024-03-12 20:14:29 +01:00
parent 40d2342211
commit 3b1e627356
10 changed files with 197 additions and 58 deletions

17
src/app.d.ts vendored
View File

@ -3,11 +3,26 @@
declare global { declare global {
namespace App { namespace App {
// interface Error {} // interface Error {}
// interface Locals {} interface Locals {
user: SessionUserInfo,
project: SessionProjectInfo
}
// interface PageData {} // interface PageData {}
// interface PageState {} // interface PageState {}
// interface Platform {} // interface Platform {}
} }
interface SessionUserInfo {
id: string,
username: string,
email: string,
account_type: string
}
interface SessionProjectInfo {
id: string,
project_name: string
}
} }
export {}; export {};

View File

@ -2,8 +2,9 @@ import { validateSession } from '$lib/services/auth.server';
/** @type {import('@sveltejs/kit').Handle} */ /** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) { export async function handle({ event, resolve }) {
if(event.url.pathname === "/auth") return resolve(event);
const session_id = event.cookies.get("token"); const session_id = event.cookies.get("token");
if (session_id === undefined && event.url.pathname !== "/auth") return Response.redirect(event.url.host+"/auth", 303); if (session_id === undefined) return Response.redirect(event.url.host+"/auth", 303);
// @ts-ignore // @ts-ignore
const user = await validateSession(session_id); const user = await validateSession(session_id);

View File

@ -0,0 +1,69 @@
<script lang="ts">
import * as Form from "$lib/components/ui/form";
import { Input } from "$lib/components/ui/input";
import { Loader2 } from "lucide-svelte";
import { loginSchema, type LoginSchema } from "./schema";
import {
type SuperValidated,
type Infer,
superForm,
} from "sveltekit-superforms";
import { zodClient } from "sveltekit-superforms/adapters";
let isLoading = false;
export let data: SuperValidated<Infer<LoginSchema>>;
const form = superForm(data, {
validators: zodClient(loginSchema),
onSubmit: () => {
isLoading = true;
},
onUpdated: ({form: f}) => {
isLoading = false;
}
});
const { form: formData, enhance, message } = form;
</script>
<form method="POST" action="?/login" use:enhance>
{#if $message}
<span class="block text-center text-red-600">{$message}</span>
{/if}
<Form.Field {form} name="email">
<Form.Control let:attrs>
<Form.Label>Email</Form.Label>
<Input
{...attrs}
type="email"
bind:value={$formData.email}
disabled={isLoading}
/>
</Form.Control>
<Form.FieldErrors />
</Form.Field>
<Form.Field {form} name="password">
<Form.Control let:attrs>
<Form.Label>Password</Form.Label>
<Input
{...attrs}
type="password"
bind:value={$formData.password}
disabled={isLoading}
/>
</Form.Control>
<Form.FieldErrors />
<Form.Description>A strong password with 6-20 characters.</Form.Description>
</Form.Field>
<Form.Button disabled={isLoading} class="w-full">
{#if isLoading}
<Loader2 class="mr-2 h-4 w-4 animate-spin" />
{:else}
Submit
{/if}
</Form.Button>
</form>

View File

@ -1,9 +1,16 @@
import { z } from "zod"; import { z } from "zod";
export const formSchema = z.object({ export const signupSchema = z.object({
name: z.string().min(2).max(16), name: z.string().min(2).max(16),
email: z.string().min(2).max(50).email("Invalid email format"), email: z.string().min(2).max(50).email("Invalid email format"),
password: z.string().min(6).max(20), password: z.string().min(6).max(20),
}); });
export type FormSchema = typeof formSchema; export type SignupSchema = typeof signupSchema;
export const loginSchema = z.object({
email: z.string().min(2).max(50).email("Invalid email format"),
password: z.string().min(6).max(20),
});
export type LoginSchema = typeof loginSchema;

View File

@ -2,7 +2,7 @@
import * as Form from "$lib/components/ui/form"; import * as Form from "$lib/components/ui/form";
import { Input } from "$lib/components/ui/input"; import { Input } from "$lib/components/ui/input";
import { Loader2 } from "lucide-svelte"; import { Loader2 } from "lucide-svelte";
import { formSchema, type FormSchema } from "./schema"; import { signupSchema, type SignupSchema } from "./schema";
import { import {
type SuperValidated, type SuperValidated,
type Infer, type Infer,
@ -12,10 +12,10 @@
let isLoading = false; let isLoading = false;
export let data: SuperValidated<Infer<FormSchema>>; export let data: SuperValidated<Infer<SignupSchema>>;
const form = superForm(data, { const form = superForm(data, {
validators: zodClient(formSchema), validators: zodClient(signupSchema),
onSubmit: () => { onSubmit: () => {
isLoading = true; isLoading = true;
}, },
@ -66,8 +66,8 @@
disabled={isLoading} disabled={isLoading}
/> />
</Form.Control> </Form.Control>
<Form.Description>A strong password with 6-20 characters.</Form.Description>
<Form.FieldErrors /> <Form.FieldErrors />
<Form.Description>A strong password with 6-20 characters.</Form.Description>
</Form.Field> </Form.Field>
<Form.Button disabled={isLoading} class="w-full"> <Form.Button disabled={isLoading} class="w-full">
{#if isLoading} {#if isLoading}

View File

@ -1,7 +1,17 @@
import bcrypt from "bcrypt";
export function generateId(length: number): string { export function generateId(length: number): string {
var text = ""; var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (var i = 0; i < length; i++) for (var i = 0; i < length; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length)); text += possible.charAt(Math.floor(Math.random() * possible.length));
return text; return text;
}
export async function hashPassword(password: string): Promise<string> {
return await bcrypt.hash(password, 10);
}
export async function validatePassword(hashed_password: string, plain_password: string) {
return await bcrypt.compare(plain_password, hashed_password);
} }

View File

@ -4,7 +4,7 @@
import { Button } from '$lib/components/ui/button'; import { Button } from '$lib/components/ui/button';
import Player from '$lib/components/organisms/player.svelte'; import Player from '$lib/components/organisms/player.svelte';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { PlayIcon, Loader } from 'lucide-svelte'; import { PlayIcon, Loader, Plus, MailIcon } from 'lucide-svelte';
let script = ""; let script = "";
@ -27,23 +27,26 @@
<Player script={script} on:close={() => opened=false} /> <Player script={script} on:close={() => opened=false} />
{/if} {/if}
<div> <div class="flex gap-4 flex-col">
<Label>Your Latest Podcast</Label> <Label>Your Latest Podcasts</Label>
<Button class="flex w-full flex-row justify-start rounded text-left" variant="secondary" on:click={() => opened = true}> {#each pods as pod}
<div class="items-center justify-center rounded-lg p-2 pl-0"> <Button class="flex w-full flex-row justify-start rounded text-left" variant="secondary" on:click={() => opened = true}>
<PlayIcon class="h-4 w-4" /> <div class="items-center justify-center rounded-lg p-2 pl-0">
</div> <PlayIcon class="h-4 w-4" />
<div> </div>
<h3 class="text-md">Your Daily Pod</h3> <div>
<p class="text-sm text-muted-foreground">January 18th, 2024</p> <h3 class="text-md">Your Daily Pod</h3>
</div> <p class="text-sm text-muted-foreground">{new Date(pod.date_created).toLocaleDateString('de')}</p>
</Button> </div>
</Button>
{/each}
<Label>Your Newsletters</Label> <Label>Your Newsletters</Label>
<div class="overflow-x-scroll scrollbars-hidden"> <div class="overflow-x-scroll scrollbars-hidden">
{#each pods as pod, i} {#each pods as pod}
<Button class="mt-4 flex w-full flex-row justify-start rounded text-left" variant="secondary"> <Button class="mt-4 flex w-full flex-row justify-start rounded text-left" variant="secondary">
<div class="items-center justify-center rounded-lg p-2 pl-0"> <div class="items-center justify-center rounded-lg p-2 pl-0">
<PlayIcon class="h-4 w-4" /> <MailIcon class="h-4 w-4" />
</div> </div>
<div> <div>
<h3 class="text-md">{pod.title}</h3> <h3 class="text-md">{pod.title}</h3>

View File

@ -1,31 +1,15 @@
export async function GET() { import { db, podsTable } from "$lib/db"
import { eq } from "drizzle-orm";
/**
* @type {import("@sveltejs/kit").RequestHandler}
*/
export async function GET(event) {
const pods = await db.select().from(podsTable).where(eq(podsTable.user_id, event.locals.user.id)).orderBy(podsTable.date_created).limit(3);
return new Response(JSON.stringify({ return new Response(JSON.stringify({
success: true, success: true,
pods: [ pods
{ }));
title: "Your daily pod",
date: new Date("2024-03-11")
},
{
title: "Your rewind pod",
date: new Date("2024-03-10")
},
{
title: "Your pod today",
date: new Date("2024-03-09")
},
{
title: "My daily pod",
date: new Date("2024-03-08")
},
{
title: "I'm just making up stuff at this point",
date: new Date("2024-03-07")
},
{
title: "Tried that last one to see how long it can be",
date: new Date("2024-03-06")
}
]
}))
} }

View File

@ -1,16 +1,19 @@
import { setError, superValidate } from "sveltekit-superforms"; import { setError, setMessage, superValidate } from "sveltekit-superforms";
import { fail } from "@sveltejs/kit"; import { fail } from "@sveltejs/kit";
import { formSchema } from "$lib/components/organisms/auth/schema"; import { loginSchema, signupSchema } from "$lib/components/organisms/auth/schema";
import { zod } from "sveltekit-superforms/adapters"; import { zod } from "sveltekit-superforms/adapters";
import { db, usersTable } from "$lib/db"; import { db, usersTable } from "$lib/db";
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import * as authService from "$lib/services/auth.server"; import * as authService from "$lib/services/auth.server";
import { eq } from "drizzle-orm";
import { hashPassword, validatePassword } from "$lib/utils/auth.utils";
/** /**
* @type {import("./$types").PageServerLoad} * @type {import("./$types").PageServerLoad}
*/ */
export const load = async () => { export const load = async () => {
return { return {
form: await superValidate(zod(formSchema)), signupForm: await superValidate(zod(signupSchema)),
loginForm: await superValidate(zod(loginSchema)),
}; };
}; };
@ -19,7 +22,7 @@ export const load = async () => {
*/ */
export const actions = { export const actions = {
signup: async (event) => { signup: async (event) => {
const form = await superValidate(event, zod(formSchema)); const form = await superValidate(event, zod(signupSchema));
if (!form.valid) { if (!form.valid) {
return fail(400, { return fail(400, {
form, form,
@ -35,7 +38,7 @@ export const actions = {
const newUser = await db.insert(usersTable).values({ const newUser = await db.insert(usersTable).values({
name: form.data.name, name: form.data.name,
email: form.data.email, email: form.data.email,
hashed_password: (await bcrypt.hash(form.data.password, 10)), hashed_password: (await hashPassword(form.data.password)),
}).returning({ id: usersTable.id }).onConflictDoNothing({ target: usersTable.email }); }).returning({ id: usersTable.id }).onConflictDoNothing({ target: usersTable.email });
if (newUser.length === 0) return setError(form, "email", "Email already taken.", { if (newUser.length === 0) return setError(form, "email", "Email already taken.", {
@ -50,6 +53,42 @@ export const actions = {
secure: false secure: false
}); });
return {
form,
};
},
login: async (event) => {
const form = await superValidate(event, zod(loginSchema));
if (!form.valid) {
return fail(400, {
form,
});
}
// await (async () => {
// return new Promise((res, rej) => {
// setTimeout(res, 5000)
// })
// })()
const user = await db.select().from(usersTable).where(eq(usersTable.email, form.data.email));
if (user.length === 0) return setMessage(form, "Invalid login credentials", {
status: 409
});
if(!validatePassword(user[0].hashed_password, form.data.password)) return setMessage(form, "Invalid login credentials", {
status: 409
});
const sessionId = await authService.createSession(user[0].id);
event.cookies.set("token", sessionId, {
path: "/",
expires: new Date("01-01-2025"),
secure: false
});
return { return {
form, form,
}; };

View File

@ -1,8 +1,10 @@
<script> <script>
import AuthForm from '$lib/components/organisms/auth/auth-form.svelte'; import LoginForm from '$lib/components/organisms/auth/login-form.svelte';
import AuthForm from '$lib/components/organisms/auth/signup-form.svelte';
import { Button } from '$lib/components/ui/button'; import { Button } from '$lib/components/ui/button';
let authIsOpen = false; let authIsOpen = false;
let isLogin = false;
/** @type {import('./$types').PageData} */ /** @type {import('./$types').PageData} */
export let data; export let data;
@ -16,12 +18,21 @@
{#if authIsOpen} {#if authIsOpen}
<div class="pt-8 text-left"> <div class="pt-8 text-left">
<AuthForm data={data.form} /> {#if data.loginForm.message || data.signupForm.message}
<p class="color-red">{data.loginForm.message || data.signupForm.message}</p>
{/if}
{#if isLogin}
<LoginForm data={data.loginForm}/>
<p class="text-center">Don't have an account? <a class="text-primary font-bold" on:click={() => isLogin = false} href="#">Sign up</a></p>
{:else}
<AuthForm data={data.signupForm} />
<p class="text-center">Already have an account? <a class="text-primary font-bold" on:click={() => isLogin = true} href="#">Log in</a></p>
{/if}
</div> </div>
{:else} {:else}
<div class="mt-[100%]"> <div class="mt-[100%]">
<Button class="w-full" on:click={() => (authIsOpen = true)}>Sign up</Button> <Button class="w-full" on:click={() => (authIsOpen = true)}>Sign up</Button>
<Button class="mt-4 w-full" variant="ghost">Log in</Button> <Button class="mt-4 w-full" variant="ghost" on:click={() => (authIsOpen = isLogin = true)}>Log in</Button>
</div> </div>
{/if} {/if}
</div> </div>