init
This commit is contained in:
12
src/routes/+layout.svelte
Normal file
12
src/routes/+layout.svelte
Normal file
@@ -0,0 +1,12 @@
|
||||
<script lang="ts">
|
||||
import '../app.css'
|
||||
import favicon from '$lib/assets/favicon.svg'
|
||||
|
||||
let { children } = $props()
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<link rel="icon" href={favicon} />
|
||||
</svelte:head>
|
||||
|
||||
{@render children?.()}
|
||||
15
src/routes/+page.svelte
Normal file
15
src/routes/+page.svelte
Normal file
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
async function crawlClevertronik() {
|
||||
const response = await fetch('/crawl', { method: 'POST' })
|
||||
const productName = await response.json()
|
||||
console.log(productName)
|
||||
}
|
||||
</script>
|
||||
|
||||
<h1>Welcome to SvelteKit</h1>
|
||||
<p>
|
||||
Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the
|
||||
documentation
|
||||
</p>
|
||||
|
||||
<button onclick={crawlClevertronik}>lets do it</button>
|
||||
22
src/routes/crawl/+server.ts
Normal file
22
src/routes/crawl/+server.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { crawlClevertronik } from '$lib/server/crawler/clevertronik'
|
||||
import { json, error } from '@sveltejs/kit'
|
||||
import { Effect } from 'effect'
|
||||
|
||||
export async function POST() {
|
||||
try {
|
||||
const pageContent = await Effect.runPromise(
|
||||
crawlClevertronik.pipe(Effect.annotateLogs({ route: 'crawl' })),
|
||||
)
|
||||
return json(pageContent)
|
||||
} catch (e) {
|
||||
if (
|
||||
e &&
|
||||
typeof e === 'object' &&
|
||||
'message' in e &&
|
||||
typeof e.message === 'string'
|
||||
) {
|
||||
return error(500, { message: e.message })
|
||||
}
|
||||
return error(500, { message: 'Internal Error' })
|
||||
}
|
||||
}
|
||||
2
src/routes/demo/+page.svelte
Normal file
2
src/routes/demo/+page.svelte
Normal file
@@ -0,0 +1,2 @@
|
||||
<a href="/demo/paraglide">paraglide</a>
|
||||
<a href="/demo/lucia">lucia</a>
|
||||
31
src/routes/demo/lucia/+page.server.ts
Normal file
31
src/routes/demo/lucia/+page.server.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import * as auth from '$lib/server/auth'
|
||||
import { fail, redirect } from '@sveltejs/kit'
|
||||
import { getRequestEvent } from '$app/server'
|
||||
import type { Actions, PageServerLoad } from './$types'
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
const user = requireLogin()
|
||||
return { user }
|
||||
}
|
||||
|
||||
export const actions: Actions = {
|
||||
logout: async (event) => {
|
||||
if (!event.locals.session) {
|
||||
return fail(401)
|
||||
}
|
||||
await auth.invalidateSession(event.locals.session.id)
|
||||
auth.deleteSessionTokenCookie(event)
|
||||
|
||||
return redirect(302, '/demo/lucia/login')
|
||||
}
|
||||
}
|
||||
|
||||
function requireLogin() {
|
||||
const { locals } = getRequestEvent()
|
||||
|
||||
if (!locals.user) {
|
||||
return redirect(302, '/demo/lucia/login')
|
||||
}
|
||||
|
||||
return locals.user
|
||||
}
|
||||
12
src/routes/demo/lucia/+page.svelte
Normal file
12
src/routes/demo/lucia/+page.svelte
Normal file
@@ -0,0 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { enhance } from '$app/forms'
|
||||
import type { PageServerData } from './$types'
|
||||
|
||||
let { data }: { data: PageServerData } = $props()
|
||||
</script>
|
||||
|
||||
<h1>Hi, {data.user.username}!</h1>
|
||||
<p>Your user ID is {data.user.id}.</p>
|
||||
<form method="post" action="?/logout" use:enhance>
|
||||
<button>Sign out</button>
|
||||
</form>
|
||||
107
src/routes/demo/lucia/login/+page.server.ts
Normal file
107
src/routes/demo/lucia/login/+page.server.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { hash, verify } from '@node-rs/argon2'
|
||||
import { encodeBase32LowerCase } from '@oslojs/encoding'
|
||||
import { fail, redirect } from '@sveltejs/kit'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import * as auth from '$lib/server/auth'
|
||||
import { db } from '$lib/server/db'
|
||||
import { user } from '$lib/server/db/user'
|
||||
import type { Actions, PageServerLoad } from './$types'
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
if (event.locals.user) {
|
||||
return redirect(302, '/demo/lucia')
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
export const actions: Actions = {
|
||||
login: async (event) => {
|
||||
const formData = await event.request.formData()
|
||||
const username = formData.get('username')
|
||||
const password = formData.get('password')
|
||||
|
||||
if (!validateUsername(username)) {
|
||||
return fail(400, {
|
||||
message: 'Invalid username (min 3, max 31 characters, alphanumeric only)'
|
||||
})
|
||||
}
|
||||
if (!validatePassword(password)) {
|
||||
return fail(400, { message: 'Invalid password (min 6, max 255 characters)' })
|
||||
}
|
||||
|
||||
const results = await db.select().from(user).where(eq(user.username, username))
|
||||
|
||||
const existingUser = results.at(0)
|
||||
if (!existingUser) {
|
||||
return fail(400, { message: 'Incorrect username or password' })
|
||||
}
|
||||
|
||||
const validPassword = await verify(existingUser.passwordHash, password, {
|
||||
memoryCost: 19456,
|
||||
timeCost: 2,
|
||||
outputLen: 32,
|
||||
parallelism: 1
|
||||
})
|
||||
if (!validPassword) {
|
||||
return fail(400, { message: 'Incorrect username or password' })
|
||||
}
|
||||
|
||||
const sessionToken = auth.generateSessionToken()
|
||||
const session = await auth.createSession(sessionToken, existingUser.id)
|
||||
auth.setSessionTokenCookie(event, sessionToken, session.expiresAt)
|
||||
|
||||
return redirect(302, '/demo/lucia')
|
||||
},
|
||||
register: async (event) => {
|
||||
const formData = await event.request.formData()
|
||||
const username = formData.get('username')
|
||||
const password = formData.get('password')
|
||||
|
||||
if (!validateUsername(username)) {
|
||||
return fail(400, { message: 'Invalid username' })
|
||||
}
|
||||
if (!validatePassword(password)) {
|
||||
return fail(400, { message: 'Invalid password' })
|
||||
}
|
||||
|
||||
const userId = generateUserId()
|
||||
const passwordHash = await hash(password, {
|
||||
// recommended minimum parameters
|
||||
memoryCost: 19456,
|
||||
timeCost: 2,
|
||||
outputLen: 32,
|
||||
parallelism: 1
|
||||
})
|
||||
|
||||
try {
|
||||
await db.insert(user).values({ id: userId, username, passwordHash })
|
||||
|
||||
const sessionToken = auth.generateSessionToken()
|
||||
const session = await auth.createSession(sessionToken, userId)
|
||||
auth.setSessionTokenCookie(event, sessionToken, session.expiresAt)
|
||||
} catch {
|
||||
return fail(500, { message: 'An error has occurred' })
|
||||
}
|
||||
return redirect(302, '/demo/lucia')
|
||||
}
|
||||
}
|
||||
|
||||
function generateUserId() {
|
||||
// ID with 120 bits of entropy, or about the same as UUID v4.
|
||||
const bytes = crypto.getRandomValues(new Uint8Array(15))
|
||||
const id = encodeBase32LowerCase(bytes)
|
||||
return id
|
||||
}
|
||||
|
||||
function validateUsername(username: unknown): username is string {
|
||||
return (
|
||||
typeof username === 'string' &&
|
||||
username.length >= 3 &&
|
||||
username.length <= 31 &&
|
||||
/^[a-z0-9_-]+$/.test(username)
|
||||
)
|
||||
}
|
||||
|
||||
function validatePassword(password: unknown): password is string {
|
||||
return typeof password === 'string' && password.length >= 6 && password.length <= 255
|
||||
}
|
||||
34
src/routes/demo/lucia/login/+page.svelte
Normal file
34
src/routes/demo/lucia/login/+page.svelte
Normal file
@@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import { enhance } from '$app/forms'
|
||||
import type { ActionData } from './$types'
|
||||
|
||||
let { form }: { form: ActionData } = $props()
|
||||
</script>
|
||||
|
||||
<h1>Login/Register</h1>
|
||||
<form method="post" action="?/login" use:enhance>
|
||||
<label>
|
||||
Username
|
||||
<input
|
||||
name="username"
|
||||
class="mt-1 rounded-md border border-gray-300 bg-white px-3 py-2 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:outline-none"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Password
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
class="mt-1 rounded-md border border-gray-300 bg-white px-3 py-2 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-500 focus:outline-none"
|
||||
/>
|
||||
</label>
|
||||
<button class="rounded-md bg-blue-600 px-4 py-2 text-white transition hover:bg-blue-700"
|
||||
>Login</button
|
||||
>
|
||||
<button
|
||||
formaction="?/register"
|
||||
class="rounded-md bg-blue-600 px-4 py-2 text-white transition hover:bg-blue-700"
|
||||
>Register</button
|
||||
>
|
||||
</form>
|
||||
<p style="color: red">{form?.message ?? ''}</p>
|
||||
19
src/routes/demo/paraglide/+page.svelte
Normal file
19
src/routes/demo/paraglide/+page.svelte
Normal file
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { setLocale } from '$lib/paraglide/runtime'
|
||||
import { page } from '$app/state'
|
||||
import { goto } from '$app/navigation'
|
||||
import { m } from '$lib/paraglide/messages.js'
|
||||
</script>
|
||||
|
||||
<h1>{m.hello_world({ name: 'SvelteKit User' })}</h1>
|
||||
<div>
|
||||
<button onclick={() => setLocale('en')}>en</button>
|
||||
<button onclick={() => setLocale('es')}>es</button>
|
||||
<button onclick={() => setLocale('de-de')}>de-de</button>
|
||||
</div>
|
||||
<p>
|
||||
If you use VSCode, install the <a
|
||||
href="https://marketplace.visualstudio.com/items?itemName=inlang.vs-code-extension"
|
||||
target="_blank">Sherlock i18n extension</a
|
||||
> for a better i18n experience.
|
||||
</p>
|
||||
Reference in New Issue
Block a user