From e1cdba824a12094e1a348d8a66b050a24dbaf6ec Mon Sep 17 00:00:00 2001 From: andrzej Date: Wed, 18 Sep 2024 11:56:08 +0200 Subject: [PATCH] redirect on login now works --- prisma/dev.db | Bin 69632 -> 69632 bytes src/app/api/auth/actions.ts | 15 +-- src/app/lib/create.ts | 45 ++++++--- src/app/login/page.tsx | 89 ++++++++++-------- src/app/login/revalidate.ts | 11 +++ src/app/ui/forms/fancyForm.tsx | 14 +-- src/app/ui/forms/schemas.ts | 7 ++ src/app/ui/forms/story.tsx | 28 +++--- src/app/ui/tables/inputs/genrePickerInput.tsx | 2 +- src/middleware.ts | 11 ++- 10 files changed, 130 insertions(+), 92 deletions(-) create mode 100644 src/app/login/revalidate.ts create mode 100644 src/app/ui/forms/schemas.ts diff --git a/prisma/dev.db b/prisma/dev.db index 674acf08d9d73e9512843a62a16c7f76cdc94f23..761320fe59a40421387616d5b8485af098227177 100644 GIT binary patch delta 243 zcmZozz|ydQWr8$gkBcjs9Hv?7Ymvi~*%de3M`MOG>cwU1#Dy$#_&){3jcah(bpP&CQ-%oyJ{xJSq{I`JSb@NSL+gB>VBBsH@ zz@*Fg)ww9Ks#3utv7jI|FEcNFvQ@u4KOd7M^ULCt#FXO1dWIWvBh)C1DStan2)QWf0ti;^-^Qd092f=lv?Di=*?008+TNqqnS delta 204 zcmZozz|ydQWr8%L=R_H2M$e52js9Hf?7Ymvi~*%dypv!1OG>cuU1#Dy$#t>wGO@5s;3f0*wl-**rMn%2uVd2L^*gbb4@(>wQ!{NfS? z$DGX6ykdp?GzH(pqRCeM@;pLJ)~t8J5+xW_7+EH#_Ls5*GyZm;oYAkeXhH)3z-2<6 diff --git a/src/app/api/auth/actions.ts b/src/app/api/auth/actions.ts index 61885d9..a7bf069 100644 --- a/src/app/api/auth/actions.ts +++ b/src/app/api/auth/actions.ts @@ -6,28 +6,21 @@ import { loginSchema, LoginSchema } from 'app/login/schema'; import { NextResponse } from 'next/server'; export async function getJWTSecretKey() { - console.log('getJWTSecretKey called!') const secret = process.env.JWT_SECRET - console.log('secret: ' + secret) if (!secret) throw new Error("There is no JWT secret key") - console.log('encoding...') try { const enc: Uint8Array = new TextEncoder().encode(secret) - console.log('enc') return enc } catch (error) { - console.error('aw shit: ' + error.message) + throw new Error("failed to getJWTSecretKey", error.message) } } export async function verifyJwt(token: string): Promise { - console.log('verifyJwt called for token: ' + token) - const key = await getJWTSecretKey() - console.log('key: ' + key) - const { payload } = await jwtVerify(token, key) - console.log('payload: ' + payload) - return payload try { + const key = await getJWTSecretKey() + const { payload } = await jwtVerify(token, key) + return payload } catch { return null } diff --git a/src/app/lib/create.ts b/src/app/lib/create.ts index 1d1e3ec..31850d6 100644 --- a/src/app/lib/create.ts +++ b/src/app/lib/create.ts @@ -3,24 +3,39 @@ import { Genre, Story } from "@prisma/client" import prisma from "./db" import { revalidatePath } from "next/cache" import { redirect } from "next/navigation" +import { z } from "zod" +import { storySchema } from "app/ui/forms/schemas" -export async function createStory(data: Story & { genres: number[] }) { +//TODO - data validation, error handling, unauthorized access handling + +export async function createStory(data: Story & { genres: number[] }): Promise | undefined { + // will return undefined if middleware authorization fails "use server" + //Prepare data const genresArray = data.genres.map((e) => { return { id: e } }) - const res = await prisma.story.create({ - data: { - title: data.title, - word_count: data.word_count, - } - }) - console.log(res) - const genresRes = await prisma.story.update({ - where: { id: res.id }, - data: { - genres: { set: genresArray } - } - }) - console.log(genresRes) + const storyData = { + title: data.title, + word_count: data.word_count, + } + //prepare schemas + const schema = storySchema.omit({ id: true, genres: true }) + const genreSchema = z.object({ id: z.number() }) + const genresSchema = z.array(genreSchema) + //validate + const isSafe = schema.safeParse(storyData) && genresSchema.safeParse(genresArray) + if (!isSafe) throw new Error("server-side validation failed") + //submit + try { + const res = await prisma.story.create({ data: storyData }) + await prisma.story.update({ + where: { id: res.id }, + data: { + genres: { set: genresArray } + } + }) + } catch (error) { + throw new Error("database failure") + } revalidatePath("/story") redirect("/story") } diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 80c4a70..93bf5d0 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -9,22 +9,22 @@ import { Button } from "@/components/ui/button"; import { redirect } from "next/navigation"; import { loginSchema } from "./schema"; import { useRouter } from "next/navigation"; - +import { useSearchParams } from "next/navigation"; +import revalidate from "./revalidate"; +import { useState } from "react"; +import Link from "next/link"; export default function LoginForm() { const router = useRouter() + const searchParams = useSearchParams() + const redirect = searchParams.get("from") const form = useForm>({ resolver: zodResolver(loginSchema), }) + const [submitted, setSubmitted] = useState(false) - const onSubmit = form.handleSubmit(async (data) => { - // const res = await login(data) - // if (res?.error) { - // toast({ title: "Whoops!", description: res.error }) - // form.reset() - // } else { - // toast({ title: "login successful" }) - // } + const onSubmit = form.handleSubmit(async (data, event) => { + event.preventDefault() const res = await fetch('/api/auth/login', { method: 'POST', headers: { @@ -34,7 +34,10 @@ export default function LoginForm() { }) if (res.status === 200) { toast({ title: "login successful!" }) - router.push('/submission') + setSubmitted(true) + await revalidate(redirect) + router.refresh() + router.push(redirect) } else { toast({ title: "login failed!" }) } @@ -42,37 +45,41 @@ export default function LoginForm() { return ( -
- - ( - - Email Address - - - - - - )} - > - ( - - Password - - - - - - )} - > - -
- + <> + {submitted ?

Logging in...

: +
+ + ( + + Email Address + + + + + + )} + > + ( + + Password + + + + + + )} + > + +
+ + } + ) } diff --git a/src/app/login/revalidate.ts b/src/app/login/revalidate.ts new file mode 100644 index 0000000..0a9e8ce --- /dev/null +++ b/src/app/login/revalidate.ts @@ -0,0 +1,11 @@ +"use server" +import { revalidatePath } from "next/cache" +export default async function revalidate(path: string) { + try { + revalidatePath(path) + return true + } catch (error) { + console.error(error) + return false + } +} diff --git a/src/app/ui/forms/fancyForm.tsx b/src/app/ui/forms/fancyForm.tsx index 19f10f9..6dd8020 100644 --- a/src/app/ui/forms/fancyForm.tsx +++ b/src/app/ui/forms/fancyForm.tsx @@ -16,18 +16,12 @@ import { } from "@/components/ui/form" import { Input } from "@/components/ui/input" import { Checkbox } from "@/components/ui/checkbox" - -const formSchema = z.object({ - title: z.string().min(2).max(50), - word_count: z.number(), - genres: z.object({ id: z.number(), name: z.string() }).array() -}) - +import { storySchema } from "./schemas" export default function FancyForm({ genres }) { // 1. Define your form. - const form = useForm>({ - resolver: zodResolver(formSchema), + const form = useForm>({ + resolver: zodResolver(storySchema), defaultValues: { title: "", word_count: 0, @@ -35,7 +29,7 @@ export default function FancyForm({ genres }) { }, }) // 2. Define a submit handler. - function onSubmit(values: z.infer) { + function onSubmit(values: z.infer) { // Do something with the form values. // ✅ This will be type-safe and validated. console.log(values) diff --git a/src/app/ui/forms/schemas.ts b/src/app/ui/forms/schemas.ts new file mode 100644 index 0000000..753b98c --- /dev/null +++ b/src/app/ui/forms/schemas.ts @@ -0,0 +1,7 @@ +import { z } from "zod"; + +export const storySchema = z.object({ + title: z.string().min(2).max(50), + word_count: z.number(), + genres: z.object({ id: z.number(), name: z.string() }).array() +}) diff --git a/src/app/ui/forms/story.tsx b/src/app/ui/forms/story.tsx index 665bb92..233e8d6 100644 --- a/src/app/ui/forms/story.tsx +++ b/src/app/ui/forms/story.tsx @@ -40,17 +40,23 @@ export default function StoryForm({ genres, createStory, className, existingData - function onSubmit(values: z.infer) { - toast({ - title: "You submitted the following values:", - description: ( -
-					{JSON.stringify(values, null, 2)}
-				
- ), - }) - createStory(values) - console.log(values) + async function onSubmit(values: z.infer) { + try { + const res = await createStory(values) + //server actions return undefined if middleware authentication fails + if (res === undefined) throw new Error("something went wrong") + toast({ + title: "Successfully submitted:", + description: values.title, + }) + //TODO refresh data without reloading page? + } catch (error) { + toast({ + title: "UH-OH", + description: error.message + }) + console.error(error) + } } diff --git a/src/app/ui/tables/inputs/genrePickerInput.tsx b/src/app/ui/tables/inputs/genrePickerInput.tsx index 61e6938..3264547 100644 --- a/src/app/ui/tables/inputs/genrePickerInput.tsx +++ b/src/app/ui/tables/inputs/genrePickerInput.tsx @@ -24,7 +24,7 @@ export default function GenrePickerInputCell(props: CellContext) { const genres = props.table.options.meta.genres const [isActive, setIsActive] = useState(false) - async function onSubmit({ genres }: { genres: number[] }) { + async function onSubmit({ genres }: { genres: number[] }, event: Event) { event.preventDefault() const genresArray = genres.map((e) => { return { id: e } }) console.log(`genres: ${genres}, genresArray: ${JSON.stringify(genresArray)}`) diff --git a/src/middleware.ts b/src/middleware.ts index db2b509..5fd702c 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -13,15 +13,18 @@ function matchesWildcard(path: string, pattern: string): boolean { return path === pattern; } -export default async function(request: NextRequest) { - // const url = `${process.env.NEXT_PUBLIC_BASE_URL}/login?redirect=${request.nextUrl.pathname + request.nextUrl.search}` +export default async function(request: NextRequest): Promise | undefined { + + const url = request.nextUrl.clone() url.pathname = "/login" + url.searchParams.set('from', request.nextUrl.pathname) if (protectedRoutes.some(pattern => matchesWildcard(request.nextUrl.pathname, pattern))) { const token = request.cookies.get('token') //NOTE - may need to add logic to return 401 for api routes if (!token) { + console.log("there is no jwt") return NextResponse.redirect(url) } @@ -34,12 +37,14 @@ export default async function(request: NextRequest) { request.cookies.delete('token') return NextResponse.redirect(url) } - } catch { + } catch (error) { //delete token (failsafe) + console.error("failed to very jwt", error.message) request.cookies.delete('token') return NextResponse.redirect(url) } + //TODO - TEST THIS BECAUSE IT PROBABLY DOESN'T WORK //redirect from login if already logged in let redirectToApp = false if (request.nextUrl.pathname === "/login") {