Started on the database
This commit is contained in:
0
src/lib/server/users.db
Normal file
0
src/lib/server/users.db
Normal file
26
src/lib/server/users.ts
Normal file
26
src/lib/server/users.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import Database from 'better-sqlite3'
|
||||
import Services from '$lib/services.json'
|
||||
import { generateUUID } from '$lib/utils'
|
||||
|
||||
const db = new Database('./src/lib/server/users.db', { verbose: console.info })
|
||||
db.pragma('foreign_keys = ON')
|
||||
const initUsersTable = 'CREATE TABLE IF NOT EXISTS Users(id VARCHAR(36) PRIMARY KEY, username VARCHAR(30) UNIQUE NOT NULL, password VARCHAR(72) NOT NULL)'
|
||||
db.exec(initUsersTable)
|
||||
|
||||
export interface User {
|
||||
id: string
|
||||
username: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export class Users {
|
||||
static addUser = (username: string, hashedPassword: string): User => {
|
||||
const userId = generateUUID()
|
||||
db.prepare('INSERT INTO Users(id, username, password) VALUES(?, ?, ?)').run(userId, username, hashedPassword)
|
||||
return this.getUser(userId)
|
||||
}
|
||||
|
||||
static getUser = (id: string): User => {
|
||||
return db.prepare('SELECT * FROM Users WHERE id = ?').get(id) as User
|
||||
}
|
||||
}
|
||||
14
src/lib/services.json
Normal file
14
src/lib/services.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"jellyfin": {
|
||||
"displayName": "Jellyfin",
|
||||
"type": ["streaming"],
|
||||
"icon": "https://raw.githubusercontent.com/jellyfin/jellyfin-ux/55616553b692b1a6c7d8e786eeb7d8216e9b50df/branding/SVG/icon-transparent.svg",
|
||||
"primaryColor": "--jellyfin-blue"
|
||||
},
|
||||
"youtube-music": {
|
||||
"displayName": "YouTube Music",
|
||||
"type": ["streaming"],
|
||||
"icon": "https://upload.wikimedia.org/wikipedia/commons/6/6a/Youtube_Music_icon.svg",
|
||||
"primaryColor": "--youtube-red"
|
||||
}
|
||||
}
|
||||
3
src/lib/utils.ts
Normal file
3
src/lib/utils.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const generateUUID = (): string => {
|
||||
return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, (c: any) => (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16))
|
||||
}
|
||||
@@ -5,8 +5,8 @@
|
||||
import { newestAlert, backgroundImage, pageWidth } from '$lib/stores'
|
||||
import { fade } from 'svelte/transition'
|
||||
|
||||
let alertBox: AlertBox;
|
||||
$: if ($newestAlert !== null && alertBox) alertBox.addAlert(...$newestAlert);
|
||||
let alertBox: AlertBox
|
||||
$: if ($newestAlert !== null && alertBox) alertBox.addAlert(...$newestAlert)
|
||||
</script>
|
||||
|
||||
<svelte:window bind:innerWidth={$pageWidth} />
|
||||
@@ -14,7 +14,7 @@
|
||||
<div class="fixed isolate -z-10 h-full w-screen bg-black">
|
||||
<div id="background-gradient" class="absolute z-10 h-1/2 w-full bg-cover" />
|
||||
{#key $backgroundImage}
|
||||
<img id="background-image" src={$backgroundImage} alt="" class="absolute blur-lg h-1/2 w-full object-cover" transition:fade={{ duration: 1000 }} />
|
||||
<img id="background-image" src={$backgroundImage} alt="" class="absolute h-1/2 w-full object-cover blur-lg" transition:fade={{ duration: 1000 }} />
|
||||
{/key}
|
||||
</div>
|
||||
<slot />
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { SECRET_JWT_KEY } from '$env/static/private'
|
||||
import { fail, redirect } from '@sveltejs/kit'
|
||||
import { genSaltSync, hashSync } from 'bcrypt-ts'
|
||||
import type { PageServerLoad, Actions } from './$types'
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const redirectLocation = url.searchParams.get('redirect')
|
||||
return { redirectLocation }
|
||||
}
|
||||
|
||||
export const actions: Actions = {
|
||||
signIn: async ({ request, cookies }) => {
|
||||
const formData = await request.formData()
|
||||
const { username, password, redirectLocation } = Object.fromEntries(formData)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { enhance } from "$app/forms";
|
||||
import { goto } from "$app/navigation";
|
||||
import { fade } from "svelte/transition";
|
||||
import { newestAlert } from "$lib/stores";
|
||||
import type { PageServerData } from "../$types";
|
||||
import type { SubmitFunction } from "@sveltejs/kit";
|
||||
import { enhance } from '$app/forms'
|
||||
import { goto } from '$app/navigation'
|
||||
import { fade } from 'svelte/transition'
|
||||
import { newestAlert } from '$lib/stores'
|
||||
import type { PageServerData } from '../$types'
|
||||
import type { SubmitFunction } from '@sveltejs/kit'
|
||||
|
||||
// export let data: PageServerData
|
||||
|
||||
type FormMode = 'signIn' | 'newUser'
|
||||
let formMode: FormMode = 'signIn'
|
||||
|
||||
let passwordInput: string, confirmPasswordInput: string
|
||||
let passwordsMatch: boolean = false
|
||||
$: passwordsMatch = (passwordInput === confirmPasswordInput)
|
||||
$: console.log(passwordsMatch)
|
||||
let passwordVisible: boolean = false
|
||||
|
||||
const handleForm: SubmitFunction = ({ formData, cancel, action }) => {
|
||||
const actionType: string = action.search.substring(2)
|
||||
@@ -45,16 +42,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Passed all checks")
|
||||
console.log('Passed all checks')
|
||||
cancel()
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="grid h-full place-items-center">
|
||||
<main class="w-full max-w-4xl">
|
||||
<div class="grid h-full place-items-center overflow-clip px-8">
|
||||
<main class="relative w-full max-w-3xl">
|
||||
<div class="flex h-14 justify-center">
|
||||
{#key formMode}
|
||||
<h1 class="absolute text-5xl" transition:fade={{ duration: 100 }}>{formMode === 'signIn' ? 'Sign In' : 'Create New User'}</h1>
|
||||
<h1 class="absolute whitespace-nowrap text-5xl" transition:fade={{ duration: 100 }}>{formMode === 'signIn' ? 'Sign In' : 'Create New User'}</h1>
|
||||
{/key}
|
||||
</div>
|
||||
<form method="post" use:enhance={handleForm}>
|
||||
@@ -62,27 +59,34 @@
|
||||
<div class="p-4">
|
||||
<input name="username" type="text" autocomplete="off" placeholder="Username" class="h-10 w-full border-b-2 border-lazuli-primary bg-transparent px-1 outline-none" />
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div class="w-full p-4">
|
||||
<input bind:value={passwordInput} name="password" type="password" placeholder="Password" class="h-10 w-full border-b-2 border-lazuli-primary bg-transparent px-1 outline-none" />
|
||||
<div class="flex items-center gap-4 p-4">
|
||||
<div class="w-full">
|
||||
<input name="password" type={passwordVisible ? 'text' : 'password'} placeholder="Password" class="h-10 w-full border-b-2 border-lazuli-primary bg-transparent px-1 outline-none" />
|
||||
</div>
|
||||
<div class="overflow-hidden py-4 transition-[width] duration-300" style="width: {formMode === 'newUser' ? '100%': 0};" aria-hidden={formMode !== 'newUser'}>
|
||||
<div class="px-4">
|
||||
<input bind:value={confirmPasswordInput} name="confirmPassword" type="password" placeholder="Confirm Password" class="h-10 w-full border-b-2 border-lazuli-primary bg-transparent px-1 outline-none" tabindex="{formMode === 'newUser' ? 0 : -1}" />
|
||||
</div>
|
||||
<div class="overflow-hidden transition-[width] duration-300" style="width: {formMode === 'newUser' ? '100%' : 0};" aria-hidden={formMode !== 'newUser'}>
|
||||
<input
|
||||
name="confirmPassword"
|
||||
type={passwordVisible ? 'text' : 'password'}
|
||||
placeholder="Confirm Password"
|
||||
class="h-10 w-full border-b-2 border-lazuli-primary bg-transparent px-1 outline-none"
|
||||
tabindex={formMode === 'newUser' ? 0 : -1}
|
||||
/>
|
||||
</div>
|
||||
<button on:click|preventDefault={() => (passwordVisible = !passwordVisible)} class="aspect-square h-9 rounded-full bg-neutral-800 transition-transform duration-100 active:scale-90">
|
||||
<i class="fa-solid {passwordVisible ? 'fa-eye' : 'fa-eye-slash'}" />
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
<section class="mt-6 flex items-center justify-around gap-2">
|
||||
<button formaction="?/signIn" class="h-12 w-1/3 rounded-md transition-all active:scale-[97%]" style="background-color: {formMode === 'signIn' ? 'var(--lazuli-primary)' : '#262626'};">
|
||||
<button formaction="?/signIn" class="h-12 w-1/3 overflow-clip whitespace-nowrap rounded-md transition-all active:scale-[97%] {formMode === 'signIn' ? 'bg-lazuli-primary' : 'bg-neutral-800'}">
|
||||
Sign In
|
||||
<i class="fa-solid fa-right-to-bracket ml-1" />
|
||||
</button>
|
||||
<button formaction="?/newUser" class="h-12 w-1/3 rounded-md transition-all active:scale-[97%]" style="background-color: {formMode === 'newUser' ? 'var(--lazuli-primary)' : '#262626'};">
|
||||
<button formaction="?/newUser" class="h-12 w-1/3 overflow-clip whitespace-nowrap rounded-md transition-all active:scale-[97%] {formMode === 'newUser' ? 'bg-lazuli-primary' : 'bg-neutral-800'}">
|
||||
Create New User
|
||||
<i class="fa-solid fa-user-plus ml-1" />
|
||||
</button>
|
||||
</section>
|
||||
</form>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user