final-video

This commit is contained in:
Hunter Johnston 2023-02-05 14:09:50 -05:00
parent 28b1cb60f1
commit de136939fc
14 changed files with 284 additions and 30 deletions

View File

@ -35,7 +35,10 @@
},
"type": "module",
"dependencies": {
"@lucia-auth/adapter-prisma": "^0.4.0",
"@lucia-auth/sveltekit": "^0.6.2",
"@picocss/pico": "^1.5.7",
"@prisma/client": "^4.9.0"
"@prisma/client": "^4.9.0",
"lucia-auth": "^0.6.0"
}
}

View File

@ -1,6 +1,8 @@
lockfileVersion: 5.4
specifiers:
'@lucia-auth/adapter-prisma': ^0.4.0
'@lucia-auth/sveltekit': ^0.6.2
'@picocss/pico': ^1.5.7
'@playwright/test': ^1.30.0
'@prisma/client': ^4.9.0
@ -11,6 +13,7 @@ specifiers:
eslint: ^8.33.0
eslint-config-prettier: ^8.6.0
eslint-plugin-svelte3: ^4.0.0
lucia-auth: ^0.6.0
prettier: ^2.8.3
prettier-plugin-svelte: ^2.9.0
prisma: ^4.9.0
@ -23,8 +26,11 @@ specifiers:
vitest: ^0.25.8
dependencies:
'@lucia-auth/adapter-prisma': 0.4.0_lucia-auth@0.6.0
'@lucia-auth/sveltekit': 0.6.2_iyasqsbplmktfcni36hpxhd3zi
'@picocss/pico': 1.5.7
'@prisma/client': 4.9.0_prisma@4.9.0
lucia-auth: 0.6.0
devDependencies:
'@playwright/test': 1.30.0
@ -299,6 +305,29 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.14
dev: true
/@lucia-auth/adapter-prisma/0.4.0_lucia-auth@0.6.0:
resolution: {integrity: sha512-HMaGbVfB5KTZBs6KPqI5z6RFKVl4AKfm9u2KrrQglthpd/rWJoJRDI2oYkdcEX4Xu1FNdNJJrEtKKBtVAXYqrw==}
peerDependencies:
lucia-auth: 0.6.x
dependencies:
lucia-auth: 0.6.0
dev: false
/@lucia-auth/sveltekit/0.6.2_iyasqsbplmktfcni36hpxhd3zi:
resolution: {integrity: sha512-+lOhgctcdVkPRYtJegTaEZYLFhPJOHFjdAm9cBFFjpkV6cNGuHSTxobfOn7yRy3pGVhzGFM91uOfYrJxmECOBA==}
peerDependencies:
lucia-auth: 0.5.x - 0.6.x
svelte: 3.x
dependencies:
'@noble/hashes': 1.2.0
lucia-auth: 0.6.0
svelte: 3.55.1
dev: false
/@noble/hashes/1.2.0:
resolution: {integrity: sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==}
dev: false
/@nodelib/fs.scandir/2.1.5:
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@ -1321,6 +1350,13 @@ packages:
yallist: 4.0.0
dev: true
/lucia-auth/0.6.0:
resolution: {integrity: sha512-8j5nPl3RbbqGoZWULER4q+2PP7i8F3Eq3OeN7EnL+bxi4YAn5I+5FcNq/ikUCj8neOxGZyzJiRzFJq+t3r28+g==}
dependencies:
'@noble/hashes': 1.2.0
nanoid: 4.0.1
dev: false
/magic-string/0.27.0:
resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==}
engines: {node: '>=12'}
@ -1389,6 +1425,12 @@ packages:
hasBin: true
dev: true
/nanoid/4.0.1:
resolution: {integrity: sha512-udKGtCCUafD3nQtJg9wBhRP3KMbPglUsgV5JVsXhvyBs/oefqb4sqMEhKBBgqZncYowu58p1prsZQBYvAj/Gww==}
engines: {node: ^14 || ^16 || >=18}
hasBin: true
dev: false
/natural-compare-lite/1.4.0:
resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==}
dev: true
@ -1807,7 +1849,6 @@ packages:
/svelte/3.55.1:
resolution: {integrity: sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==}
engines: {node: '>= 8'}
dev: true
/text-table/0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}

View File

@ -14,4 +14,39 @@ model Article {
id Int @id @default(autoincrement())
title String
content String
User User @relation(fields: [userId], references: [id])
userId String
}
model User {
id String @id @unique
name String
username String @unique
articles Article[]
session Session[]
Key Key[]
@@map("user")
}
model Session {
id String @id @unique
user_id String
active_expires BigInt
idle_expires BigInt
user User @relation(references: [id], fields: [user_id], onDelete: Cascade)
@@index([user_id])
@@map("session")
}
model Key {
id String @id @unique
hashed_password String?
user_id String
primary Boolean
user User @relation(references: [id], fields: [user_id], onDelete: Cascade)
@@index([user_id])
@@map("key")
}

15
src/app.d.ts vendored
View File

@ -3,11 +3,24 @@ import type { PrismaClient } from "@prisma/client"
declare global {
namespace App {
// interface Error {}
// interface Locals {}
interface Locals {
validate: import("@lucia-auth/sveltekit").Validate
validateUser: import("@lucia-auth/sveltekit").ValidateUser
setSession: import("@lucia-auth/sveltekit").SetSession
}
// interface PageData {}
// interface Platform {}
}
var __prisma: PrismaClient
/// <reference types="lucia-auth" />
declare namespace Lucia {
type Auth = import("$lib/server/lucia").Auth
type UserAttributes = {
username: string
name: string
}
}
}
export {}

10
src/hooks.server.ts Normal file
View File

@ -0,0 +1,10 @@
import { handleHooks } from "@lucia-auth/sveltekit"
import { auth } from "$lib/server/lucia"
import type { Handle } from "@sveltejs/kit"
import { sequence } from "@sveltejs/kit/hooks"
export const customHandle: Handle = async ({ resolve, event }) => {
return resolve(event)
}
export const handle: Handle = sequence(handleHooks(auth), customHandle)

18
src/lib/server/lucia.ts Normal file
View File

@ -0,0 +1,18 @@
import lucia from "lucia-auth"
import prismaAdapter from "@lucia-auth/adapter-prisma"
import { dev } from "$app/environment"
import { prisma } from "$lib/server/prisma"
export const auth = lucia({
adapter: prismaAdapter(prisma),
env: dev ? "DEV" : "PROD",
transformUserData: (userData) => {
return {
userId: userData.id,
username: userData.username,
name: userData.name,
}
},
})
export type Auth = typeof auth

View File

@ -1,3 +1,6 @@
import type { LayoutServerLoad } from "./$types"
export const load: LayoutServerLoad = async () => {}
export const load: LayoutServerLoad = async ({ locals }) => {
const { user, session } = await locals.validateUser()
return { user }
}

View File

@ -1,5 +1,7 @@
<script lang="ts">
import '@picocss/pico'
import type { PageData } from './$types'
export let data: PageData
</script>
<div class="container">
@ -12,9 +14,17 @@
</li>
</ul>
<ul>
<form method="POST">
<li><a href="/">Home</a></li>
{#if !data.user}
<li><a href="/register">Register</a></li>
<li><a href="/login" role="button">Login</a></li>
{:else}
<li>
<button formaction="/logout" type="submit" role="button">Logout</button>
</li>
{/if}
</form>
</ul>
</nav>
<slot />

View File

@ -1,6 +1,6 @@
import type { Actions, PageServerLoad } from "./$types"
import { prisma } from "$lib/server/prisma"
import { fail } from "@sveltejs/kit"
import { error, fail, redirect } from "@sveltejs/kit"
export const load: PageServerLoad = async () => {
return {
@ -9,7 +9,12 @@ export const load: PageServerLoad = async () => {
}
export const actions: Actions = {
createArticle: async ({ request }) => {
createArticle: async ({ request, locals }) => {
const { user, session } = await locals.validateUser()
if (!(user && session)) {
throw redirect(302, "/")
}
const { title, content } = Object.fromEntries(
await request.formData(),
) as Record<string, string>
@ -19,6 +24,7 @@ export const actions: Actions = {
data: {
title,
content,
userId: user.userId,
},
})
} catch (err) {
@ -30,13 +36,27 @@ export const actions: Actions = {
status: 201,
}
},
deleteArticle: async ({ url }) => {
deleteArticle: async ({ url, locals }) => {
const { user, session } = await locals.validateUser()
if (!(user && session)) {
throw redirect(302, "/")
}
const id = url.searchParams.get("id")
if (!id) {
return fail(400, { message: "Invalid request" })
}
try {
const article = await prisma.article.findUniqueOrThrow({
where: {
id: Number(id),
},
})
if (article.userId !== user.userId) {
throw error(403, "Not authorized")
}
await prisma.article.delete({
where: {
id: Number(id),

View File

@ -15,15 +15,18 @@
<p>
{article.content}
</p>
{#if article.userId === data.user?.userId}
<form action="?/deleteArticle&id={article.id}" method="POST">
<button type="submit" class="outline secondary">Delete Article</button>
</form>
<a href="/{article.id}" role="button" class="outline constrast" style="width: 100%;"
>Edit Article</a
>
{/if}
</article>
{/each}
</div>
{#if data.user}
<form action="?/createArticle" method="POST">
<h3>New Article</h3>
<label for="title"> Title </label>
@ -32,4 +35,5 @@
<textarea id="content" name="content" rows={5} />
<button type="submit">Add Article</button>
</form>
{/if}
</div>

View File

@ -2,8 +2,13 @@ import type { Actions, PageServerLoad } from "./$types"
import { prisma } from "$lib/server/prisma"
import { error, fail } from "@sveltejs/kit"
export const load: PageServerLoad = async ({ params }) => {
const getArticle = async () => {
export const load: PageServerLoad = async ({ params, locals }) => {
const { user, session } = await locals.validateUser()
if (!(user && session)) {
throw error(401, "Unauthorized")
}
const getArticle = async (userId: string) => {
const article = await prisma.article.findUnique({
where: {
id: Number(params.articleId),
@ -12,21 +17,39 @@ export const load: PageServerLoad = async ({ params }) => {
if (!article) {
throw error(404, "Article not found")
}
if (article.userId !== user.userId) {
throw error(403, "Unauthorized")
}
return article
}
return {
article: getArticle(),
article: getArticle(user.userId),
}
}
export const actions: Actions = {
updateArticle: async ({ request, params }) => {
updateArticle: async ({ request, params, locals }) => {
const { user, session } = await locals.validateUser()
if (!(user && session)) {
throw error(401, "Unauthorized")
}
const { title, content } = Object.fromEntries(
await request.formData(),
) as Record<string, string>
try {
const article = await prisma.article.findUniqueOrThrow({
where: {
id: Number(params.articleId),
},
})
if (article.userId !== user.userId) {
throw error(403, "Forbidden to edit this article.")
}
await prisma.article.update({
where: {
id: Number(params.articleId),

View File

@ -0,0 +1,28 @@
import { auth } from "$lib/server/lucia"
import { fail, redirect } from "@sveltejs/kit"
import type { Actions, PageServerLoad } from "./$types"
export const load: PageServerLoad = async ({ locals }) => {
const session = await locals.validate()
if (session) {
throw redirect(302, "/")
}
}
export const actions: Actions = {
default: async ({ request, locals }) => {
const { username, password } = Object.fromEntries(
await request.formData(),
) as Record<string, string>
try {
const key = await auth.validateKeyPassword("username", username, password)
const session = await auth.createSession(key.userId)
locals.setSession(session)
} catch (err) {
console.error(err)
return fail(400, { message: "Could not login user." })
}
throw redirect(302, "/")
},
}

View File

@ -1,5 +1,15 @@
import { auth } from "$lib/server/lucia"
import { redirect } from "@sveltejs/kit"
import type { RequestHandler } from "./$types"
export const POST: RequestHandler = async () => {
return new Response()
export const POST: RequestHandler = async ({ locals }) => {
const session = await locals.validate()
if (!session) {
throw redirect(302, "/")
}
await auth.invalidateSession(session.sessionId)
locals.setSession(null)
throw redirect(302, "/")
}

View File

@ -0,0 +1,36 @@
import { auth } from "$lib/server/lucia"
import { fail, redirect } from "@sveltejs/kit"
import type { Actions, PageServerLoad } from "./$types"
export const load: PageServerLoad = async ({ locals }) => {
const session = await locals.validate()
if (session) {
throw redirect(302, "/")
}
}
export const actions: Actions = {
default: async ({ request }) => {
const { name, username, password } = Object.fromEntries(
await request.formData(),
) as Record<string, string>
try {
await auth.createUser({
key: {
providerId: "username",
providerUserId: username,
password,
},
attributes: {
name,
username,
},
})
} catch (err) {
console.error(err)
return fail(400, { message: "Could not register user" })
}
throw redirect(302, "/login")
},
}