From 2eaccd52e4475b7986c2aa1e93228a59dd0f0153 Mon Sep 17 00:00:00 2001 From: Fayorg Date: Fri, 15 Dec 2023 15:21:51 +0100 Subject: [PATCH 1/6] add: using nextauth WIP --- app/api/auth/[...nextauth]/route.ts | 6 ++ app/dashboard/page.tsx | 5 ++ app/page.tsx | 35 ++++---- lib/authenticate.ts | 73 ++++++++++++++++ package-lock.json | 124 +++++++++++++++++++++++++++- package.json | 1 + types/next-auth.d.ts | 28 +++++++ 7 files changed, 255 insertions(+), 17 deletions(-) create mode 100644 app/api/auth/[...nextauth]/route.ts create mode 100644 lib/authenticate.ts create mode 100644 types/next-auth.d.ts diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..4ac2005 --- /dev/null +++ b/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,6 @@ +import NextAuth from "next-auth"; + +import { authOptions } from "@lib/authenticate"; + +const handler = NextAuth(authOptions); +export { handler as GET, handler as POST }; \ No newline at end of file diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index 7cf6c54..1ea4753 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -1,5 +1,6 @@ import prisma from '@/lib/prisma'; import ActiveCard from './ActiveCard'; +import { getAuthServerSession } from '@/lib/authenticate'; export default async function Dashboard() { const tests = await prisma.test.findMany({ select: { isActive: true, isPassed: true, id: true, testOf: { select: { id: true, firstName: true, lastName: true, isTeacher: true } } } }); @@ -7,6 +8,10 @@ export default async function Dashboard() { const activeTests = tests.filter((test) => test.isActive); const passedTests = tests.filter((test) => test.isPassed); + const authSession = await getAuthServerSession(); + + console.log(authSession); + return (

Dashboard

diff --git a/app/page.tsx b/app/page.tsx index f0bd99a..3949590 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,14 +1,15 @@ 'use client'; -import { Poppins } from 'next/font/google' -import { Check } from 'lucide-react' +import { Poppins } from 'next/font/google'; +import { Check } from 'lucide-react'; import { useState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import logo from '../images/logo.svg'; -import Image from "next/image"; -import {Input} from "@components/ui/input"; -import {white} from "next/dist/lib/picocolors"; +import Image from 'next/image'; +import { Input } from '@components/ui/input'; +import { white } from 'next/dist/lib/picocolors'; +import { signIn } from 'next-auth/react'; export default function Home() { const [password, setPassword] = useState(''); @@ -20,21 +21,27 @@ export default function Home() { } }, [router]); - function handleSubmit(event: React.FormEvent) { + async function handleSubmit(event: React.FormEvent) { event.preventDefault(); - localStorage.setItem('@password', password); - router.push('/play'); + // localStorage.setItem('@password', password); + // router.push('/play'); + + console.log('Trying to sign in'); + const result = await signIn('credentials', { + key: password, + callbackUrl: '/play', + }); } return ( -
- {"Logo"} -
+
+ {'Logo'} + setPassword(e.target.value)}> - -
diff --git a/lib/authenticate.ts b/lib/authenticate.ts new file mode 100644 index 0000000..409f9aa --- /dev/null +++ b/lib/authenticate.ts @@ -0,0 +1,73 @@ +import Credentials from "next-auth/providers/credentials"; +import prisma from "./prisma"; +import { getServerSession, RequestInternal, type NextAuthOptions, User } from "next-auth"; + +export async function authenticate(key: string) { + console.log("Running authenticate function with key: " + key); + const user = await prisma.users.findUnique({ + select: { + id: true, + firstName: true, + lastName: true, + isTeacher: true + }, + where: { + key: key + } + }); + + console.log("Found user: " + JSON.stringify(user)); + + if(!user) return null; + + return user; +} + +export const authOptions: NextAuthOptions = { + session: { + strategy: "jwt", + }, + callbacks: { + async jwt({ token, user }) { + if (user) { + token.userId = user.id; + token.firstName = user.firstName; + token.lastName = user.lastName; + token.isTeacher = user.isTeacher; + } + return token; + }, + async session({ session, token, user }) { + session.user.id = token.userId; + session.user.firstName = token.firstName; + session.user.lastName = token.lastName; + session.user.isTeacher = token.isTeacher; + return session; + }, + }, + pages: { + signIn: '/', + }, + providers: [ + Credentials({ + name: "Credentials", + credentials: { + key: { label: "Key", type: "password" } + }, + async authorize(credentials: Record<"key", string> | undefined, req: Pick) { + const { key } = credentials as { + key: string + }; + console.log("Running authorize function with key: " + key) + + const user = await authenticate(key) as User | null + + console.log("Found user in authorize: " + JSON.stringify(user)); + + return user; + } + }) + ], + }; + +export const getAuthServerSession = () => getServerSession(authOptions); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3f0538a..de67b82 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "clsx": "^2.0.0", "lucide-react": "^0.294.0", "next": "14.0.3", + "next-auth": "^4.24.5", "prisma": "^5.6.0", "react": "^18", "react-chartjs-2": "^5.2.0", @@ -425,6 +426,14 @@ "node": ">= 8" } }, + "node_modules/@panva/hkdf": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.1.1.tgz", + "integrity": "sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/@prisma/client": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.6.0.tgz", @@ -3498,6 +3507,14 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jose": { + "version": "4.15.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.4.tgz", + "integrity": "sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3649,7 +3666,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -3815,6 +3831,41 @@ } } }, + "node_modules/next-auth": { + "version": "4.24.5", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.5.tgz", + "integrity": "sha512-3RafV3XbfIKk6rF6GlLE4/KxjTcuMCifqrmD+98ejFq73SRoj2rmzoca8u764977lH/Q7jo6Xu6yM+Re1Mz/Og==", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@panva/hkdf": "^1.0.2", + "cookie": "^0.5.0", + "jose": "^4.11.4", + "oauth": "^0.9.15", + "openid-client": "^5.4.0", + "preact": "^10.6.3", + "preact-render-to-string": "^5.1.19", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "next": "^12.2.5 || ^13 || ^14", + "nodemailer": "^6.6.5", + "react": "^17.0.2 || ^18", + "react-dom": "^17.0.2 || ^18" + }, + "peerDependenciesMeta": { + "nodemailer": { + "optional": true + } + } + }, + "node_modules/next-auth/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -3865,6 +3916,11 @@ "node": ">=0.10.0" } }, + "node_modules/oauth": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3990,6 +4046,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oidc-token-hash": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz", + "integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==", + "engines": { + "node": "^10.13.0 || >=12.0.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -3998,6 +4062,28 @@ "wrappy": "1" } }, + "node_modules/openid-client": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.6.1.tgz", + "integrity": "sha512-PtrWsY+dXg6y8mtMPyL/namZSYVz8pjXz3yJiBNZsEdCnu9miHLB4ELVC85WvneMKo2Rg62Ay7NkuCpM0bgiLQ==", + "dependencies": { + "jose": "^4.15.1", + "lru-cache": "^6.0.0", + "object-hash": "^2.2.0", + "oidc-token-hash": "^5.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/openid-client/node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "engines": { + "node": ">= 6" + } + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -4267,6 +4353,26 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, + "node_modules/preact": { + "version": "10.19.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.3.tgz", + "integrity": "sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz", + "integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==", + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -4276,6 +4382,11 @@ "node": ">= 0.8.0" } }, + "node_modules/pretty-format": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==" + }, "node_modules/prisma": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.6.0.tgz", @@ -5316,6 +5427,14 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -5463,8 +5582,7 @@ "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yaml": { "version": "2.3.4", diff --git a/package.json b/package.json index 84282f2..58cf82e 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "clsx": "^2.0.0", "lucide-react": "^0.294.0", "next": "14.0.3", + "next-auth": "^4.24.5", "prisma": "^5.6.0", "react": "^18", "react-chartjs-2": "^5.2.0", diff --git a/types/next-auth.d.ts b/types/next-auth.d.ts new file mode 100644 index 0000000..e139bbc --- /dev/null +++ b/types/next-auth.d.ts @@ -0,0 +1,28 @@ +import NextAuth, { DefaultSession, DefaultJWT, DefaultUser } from "next-auth"; +import { JWT } from "next-auth/jwt"; + +declare module "next-auth" { + interface Session extends Omit { + user: { + id: number; + firstName: string; + lastName: string + isTeacher: boolean; + }; + } + interface User { + id: number; + firstName: string; + lastName: string + isTeacher: boolean; + } +} + +declare module "next-auth/jwt" { + interface JWT { + userId: number; + firstName: string; + lastName: string + isTeacher: boolean; + } +} \ No newline at end of file From c48f1e23a396de5a48b1563d8062385ad602e176 Mon Sep 17 00:00:00 2001 From: Fayorg Date: Mon, 18 Dec 2023 10:01:22 +0100 Subject: [PATCH 2/6] add: authentication WIP --- app/dashboard/page.tsx | 10 ++++--- app/play/page.tsx | 26 +++++++++--------- components/custom/testCard.tsx | 48 +++++++++++++++++----------------- lib/authenticate.ts | 12 +++++---- types/next-auth.d.ts | 4 +-- 5 files changed, 52 insertions(+), 48 deletions(-) diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index 1ea4753..e0b56e6 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -1,17 +1,19 @@ import prisma from '@/lib/prisma'; import ActiveCard from './ActiveCard'; import { getAuthServerSession } from '@/lib/authenticate'; +import { red } from 'next/dist/lib/picocolors'; +import { redirect } from 'next/navigation'; export default async function Dashboard() { + const authSession = await getAuthServerSession(); + console.log(authSession); + if (!authSession || !authSession.user.isTeacher) return redirect('/'); + const tests = await prisma.test.findMany({ select: { isActive: true, isPassed: true, id: true, testOf: { select: { id: true, firstName: true, lastName: true, isTeacher: true } } } }); const activeTests = tests.filter((test) => test.isActive); const passedTests = tests.filter((test) => test.isPassed); - const authSession = await getAuthServerSession(); - - console.log(authSession); - return (

Dashboard

diff --git a/app/play/page.tsx b/app/play/page.tsx index 709cb7a..2c602aa 100644 --- a/app/play/page.tsx +++ b/app/play/page.tsx @@ -5,9 +5,9 @@ import { useRouter } from 'next/navigation'; import { useState, useEffect } from 'react'; import useSWR from 'swr'; import { fetcher } from '@/lib/fetcher'; -import Image from "next/image"; -import Logo from "/images/logo.svg"; -import LogOut from "@images/logout.svg"; +import Image from 'next/image'; +import Logo from '/images/logo.svg'; +import LogOut from '@images/logout.svg'; export default function Play() { const router = useRouter(); @@ -15,17 +15,17 @@ export default function Play() { const { data, error, isLoading } = useSWR('/api/test?key=' + password, fetcher); - useEffect(() => { - const pass = localStorage.getItem('@password'); - setPassword(pass); - if (!pass) { - router.push('/'); - } - }, [router]); + // useEffect(() => { + // const pass = localStorage.getItem('@password'); + // setPassword(pass); + // if (!pass) { + // router.push('/'); + // } + // }, [router]); return ( -
- {"Logo"} +
+ {'Logo'} {data && data.status == 200 && password && }
@@ -35,7 +35,7 @@ export default function Play() { router.push('/'); }} > - {"Logo"} + {'Logo'}
diff --git a/components/custom/testCard.tsx b/components/custom/testCard.tsx index 68d4001..754d420 100644 --- a/components/custom/testCard.tsx +++ b/components/custom/testCard.tsx @@ -1,49 +1,49 @@ 'use client'; -import Calendar from "../../images/date.svg"; -import Image from "next/image"; +import Calendar from '../../images/date.svg'; +import Image from 'next/image'; -import { Ban, Snowflake, CircleOff } from "lucide-react"; +import { Ban, Snowflake, CircleOff } from 'lucide-react'; export function TestCard({ data, error, isLoading }: { data: any; error: any; isLoading: boolean }) { - console.log(data); + //console.log(data); if (isLoading) return ( -
- {"Calendrier -
- - Chargement +
+ {'Calendrier +
+ + Chargement
); if (error) return ( -
- {"Calendrier -
- - Erreur +
+ {'Calendrier +
+ + Erreur
); if (data.status == 404) return ( -
- {"Calendrier -
- - Pas de test +
+ {'Calendrier +
+ + Pas de test
); return ( -
- {"Calendrier -
- {new Date().getDate()} - {data.testOf.firstName} +
+ {'Calendrier +
+ {new Date().getDate()} + {data.testOf.firstName}
); diff --git a/lib/authenticate.ts b/lib/authenticate.ts index 409f9aa..e7c56b1 100644 --- a/lib/authenticate.ts +++ b/lib/authenticate.ts @@ -30,7 +30,7 @@ export const authOptions: NextAuthOptions = { callbacks: { async jwt({ token, user }) { if (user) { - token.userId = user.id; + token.userId = parseInt(user.id as string); token.firstName = user.firstName; token.lastName = user.lastName; token.isTeacher = user.isTeacher; @@ -38,10 +38,12 @@ export const authOptions: NextAuthOptions = { return token; }, async session({ session, token, user }) { - session.user.id = token.userId; - session.user.firstName = token.firstName; - session.user.lastName = token.lastName; - session.user.isTeacher = token.isTeacher; + if(token) { + session.user.id = token.userId; + session.user.firstName = token.firstName; + session.user.lastName = token.lastName; + session.user.isTeacher = token.isTeacher; + } return session; }, }, diff --git a/types/next-auth.d.ts b/types/next-auth.d.ts index e139bbc..5b2fe1c 100644 --- a/types/next-auth.d.ts +++ b/types/next-auth.d.ts @@ -2,13 +2,13 @@ import NextAuth, { DefaultSession, DefaultJWT, DefaultUser } from "next-auth"; import { JWT } from "next-auth/jwt"; declare module "next-auth" { - interface Session extends Omit { + interface Session { user: { id: number; firstName: string; lastName: string isTeacher: boolean; - }; + } & DefaultSession["user"]; } interface User { id: number; From b68fa2e3c397a43f983d0a1614469ad99d739dd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABlle=20Viso?= Date: Mon, 18 Dec 2023 14:20:18 +0100 Subject: [PATCH 3/6] fix: typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b341baf..f621d9f 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ npm run start Designed & made with love by [Elie Baier](https://github.com/fayorg), [Tim Haller](https://github.com/timhaller) & [Michal Polka](https://github.com/michalpolka). -We'd like to thanks Ms. Tixhon for the good idea and the great gifts! +We'd like to thanks Mrs. Tixhon for the good idea and the great gifts! Don't forget : FUCK MATH! From 226100f25a9881e387c8dcdfdb2105680485d796 Mon Sep 17 00:00:00 2001 From: Fayorg Date: Mon, 18 Dec 2023 14:59:34 +0100 Subject: [PATCH 4/6] add: full authentication with NextAuth --- actions/grades.ts | 34 ++++++++++++++++ actions/mangeTest.ts | 62 ++++++++++++++++++++++++++++++ app/page.tsx | 45 ++++------------------ app/play/TodayTest.tsx | 47 +++++++++++++++++++++++ app/play/layout.tsx | 12 ++++++ app/play/page.tsx | 44 +++++++++------------ components/custom/gradingForm.tsx | 64 ------------------------------- components/custom/index.ts | 1 - components/custom/testCard.tsx | 3 +- components/forms/GradingForm.tsx | 55 ++++++++++++++++++++++++++ components/forms/LoginForm.tsx | 30 +++++++++++++++ lib/authenticate.ts | 6 +++ 12 files changed, 273 insertions(+), 130 deletions(-) create mode 100644 actions/grades.ts create mode 100644 app/play/TodayTest.tsx create mode 100644 app/play/layout.tsx delete mode 100644 components/custom/gradingForm.tsx create mode 100644 components/forms/GradingForm.tsx create mode 100644 components/forms/LoginForm.tsx diff --git a/actions/grades.ts b/actions/grades.ts new file mode 100644 index 0000000..dee4386 --- /dev/null +++ b/actions/grades.ts @@ -0,0 +1,34 @@ +"use server"; + +import prisma from "@/lib/prisma"; + +export async function getGrade(testId: number, userId: number) { + const grade = await prisma.grade.findFirst({ + select: { + grade: true, + id: true, + testId: true, + note: true, + createdAt: true, + }, + where: { + testId: testId, + userId: userId + } + }); + + return grade; +} + +export async function addGrade(testId: number, userId: number, grade: number, note: string | null) { + const newGrade = await prisma.grade.create({ + data: { + grade: grade, + note: note, + testId: testId, + userId: userId, + } + }); + + return newGrade; +} \ No newline at end of file diff --git a/actions/mangeTest.ts b/actions/mangeTest.ts index 74822da..02a569c 100644 --- a/actions/mangeTest.ts +++ b/actions/mangeTest.ts @@ -9,4 +9,66 @@ export async function setTestActive(id: number, active: boolean) { return true; +} + +export async function getActiveTest(date: Date) { + return await prisma.test.findFirst({ + select: { + id: true, + isActive: true, + isPassed: true, + testOf: { + select: { + id: true, + firstName: true, + lastName: true, + isTeacher: true, + }, + }, + createdAt: true, + testOn: true, + }, + where: { + isActive: true, + isPassed: false, + testOn: new Date(date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + (date.getDate().toString().length === 1 ? '0' + date.getDate() : date.getDate())), + }, + }); +} + +export async function getActiveTestWithGrade(date: Date, userId: number) { + return await prisma.test.findFirst({ + select: { + id: true, + isActive: true, + isPassed: true, + testOf: { + select: { + id: true, + firstName: true, + lastName: true, + isTeacher: true, + }, + }, + grades: { + select: { + id: true, + grade: true, + note: true, + createdAt: true, + }, + take: 1, + where: { + userId: userId + } + }, + createdAt: true, + testOn: true, + }, + where: { + isActive: true, + isPassed: false, + testOn: new Date(date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + (date.getDate().toString().length === 1 ? '0' + date.getDate() : date.getDate())), + }, + }); } \ No newline at end of file diff --git a/app/page.tsx b/app/page.tsx index 3949590..05ac1f5 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,49 +1,20 @@ -'use client'; - -import { Poppins } from 'next/font/google'; -import { Check } from 'lucide-react'; -import { useState, useEffect } from 'react'; -import { useRouter } from 'next/navigation'; - import logo from '../images/logo.svg'; import Image from 'next/image'; -import { Input } from '@components/ui/input'; -import { white } from 'next/dist/lib/picocolors'; -import { signIn } from 'next-auth/react'; +import { LoginForm } from '@/components/forms/LoginForm'; +import { getAuthServerSession } from '@/lib/authenticate'; +import { redirect } from 'next/navigation'; -export default function Home() { - const [password, setPassword] = useState(''); - const router = useRouter(); +export default async function Home() { + const session = await getAuthServerSession(); - useEffect(() => { - if (localStorage.getItem('@password')) { - router.push('/play'); - } - }, [router]); - - async function handleSubmit(event: React.FormEvent) { - event.preventDefault(); - - // localStorage.setItem('@password', password); - // router.push('/play'); - - console.log('Trying to sign in'); - const result = await signIn('credentials', { - key: password, - callbackUrl: '/play', - }); + if (session != null) { + redirect('/play'); } return (
{'Logo'} -
- setPassword(e.target.value)}> - - -
+
); } diff --git a/app/play/TodayTest.tsx b/app/play/TodayTest.tsx new file mode 100644 index 0000000..af33660 --- /dev/null +++ b/app/play/TodayTest.tsx @@ -0,0 +1,47 @@ +'use client'; + +import { getActiveTestWithGrade } from '@/actions/mangeTest'; +import { TestCard } from '@/components/custom'; +import { Session } from 'next-auth'; +import { signOut } from 'next-auth/react'; +import { useEffect, useState } from 'react'; +import Image from 'next/image'; +import LogOut from '@images/logout.svg'; +import { GradingForm } from '@/components/forms/GradingForm'; +import YourGrade from '@images/your-grade.svg'; + +export function TodayTest({ session }: { session: Session }) { + const [activeTest, setActiveTest] = useState<{ data: any | null; error: Error | null; isLoading: boolean }>({ isLoading: true, data: null, error: null }); + + useEffect(() => { + getActiveTestWithGrade(new Date(), session.user.id) + .catch((err) => setActiveTest({ data: null, error: err, isLoading: false })) + .then((data) => setActiveTest({ data, error: null, isLoading: false })); + }, [session.user.id]); + + return ( + <> + + {activeTest.data && + (activeTest.data.grades[0] ? ( +
+
+ {'Your + {activeTest.data.grades[0].grade} +
+
+ ) : ( + + ))} +
+ +
+ + ); +} diff --git a/app/play/layout.tsx b/app/play/layout.tsx new file mode 100644 index 0000000..e71b472 --- /dev/null +++ b/app/play/layout.tsx @@ -0,0 +1,12 @@ +import { getAuthServerSession } from '@/lib/authenticate'; +import { redirect } from 'next/navigation'; + +export default async function Layout({ children }: { children: React.ReactNode }) { + const session = await getAuthServerSession(); + + if (session == null) { + redirect('/'); + } + + return <>{children}; +} diff --git a/app/play/page.tsx b/app/play/page.tsx index 2c602aa..00cabc9 100644 --- a/app/play/page.tsx +++ b/app/play/page.tsx @@ -1,43 +1,35 @@ -'use client'; - -import { GradingForm, TestCard } from '@/components/custom'; -import { useRouter } from 'next/navigation'; -import { useState, useEffect } from 'react'; -import useSWR from 'swr'; -import { fetcher } from '@/lib/fetcher'; import Image from 'next/image'; import Logo from '/images/logo.svg'; -import LogOut from '@images/logout.svg'; +import { getAuthServerSession } from '@/lib/authenticate'; +import { redirect } from 'next/navigation'; +import { getActiveTest } from '@/actions/mangeTest'; +import { TodayTest } from './TodayTest'; -export default function Play() { - const router = useRouter(); - const [password, setPassword] = useState(''); +export default async function Play() { + const session = await getAuthServerSession(); - const { data, error, isLoading } = useSWR('/api/test?key=' + password, fetcher); + if (session == null) { + redirect('/'); + } - // useEffect(() => { - // const pass = localStorage.getItem('@password'); - // setPassword(pass); - // if (!pass) { - // router.push('/'); - // } - // }, [router]); + const now = new Date(); + const todayTest = await getActiveTest(now); return (
{'Logo'} - - {data && data.status == 200 && password && } -
+ + {/* */} + {/* {data && data.status == 200 && password && } */} + {/*
-
+
*/}
); } diff --git a/components/custom/gradingForm.tsx b/components/custom/gradingForm.tsx deleted file mode 100644 index d0ef52a..0000000 --- a/components/custom/gradingForm.tsx +++ /dev/null @@ -1,64 +0,0 @@ -'use client'; - -import { useRouter } from 'next/navigation'; -import { useState } from 'react'; -import Image from 'next/image'; -import YourGrade from '@images/your-grade.svg'; -import {Check} from "lucide-react"; - -export function GradingForm({ password, data }: { password: string; data: any }) { - const [grade, setGrade] = useState(0); - const [hasVoted, setHasVoted] = useState(false); - const router = useRouter(); - - async function handleSubmit(event: React.FormEvent) { - event.preventDefault(); - - const response = await fetch('/api/grade', { - method: 'POST', - body: JSON.stringify({ grade, key: password, testId: data.id }), - }); - const json = await response.json(); - if (response.status == 200) { - setHasVoted(true); - } else { - alert(json.message); - } - } - - if (data.vote.hasVoted || hasVoted) { - return ( -
-
- {YourGrade} - {data.vote.hasVoted ? data.vote.grade : grade} -
-
- ); - } - - return ( -
-
-
- setGrade(Number(e.target.value) / 2)} - /> -
-
- {YourGrade} - {grade} - - -
-
-
- ); -} diff --git a/components/custom/index.ts b/components/custom/index.ts index 415a7d9..07b5a02 100644 --- a/components/custom/index.ts +++ b/components/custom/index.ts @@ -1,4 +1,3 @@ export { DayCard as DayCard } from './dayCard'; export { TestCard as TestCard } from './testCard'; -export { GradingForm } from "./gradingForm"; export { Santa } from "./santa"; diff --git a/components/custom/testCard.tsx b/components/custom/testCard.tsx index 754d420..351cb22 100644 --- a/components/custom/testCard.tsx +++ b/components/custom/testCard.tsx @@ -6,7 +6,6 @@ import Image from 'next/image'; import { Ban, Snowflake, CircleOff } from 'lucide-react'; export function TestCard({ data, error, isLoading }: { data: any; error: any; isLoading: boolean }) { - //console.log(data); if (isLoading) return (
@@ -27,7 +26,7 @@ export function TestCard({ data, error, isLoading }: { data: any; error: any; is
); - if (data.status == 404) + if (!data) return (
{'Calendrier diff --git a/components/forms/GradingForm.tsx b/components/forms/GradingForm.tsx new file mode 100644 index 0000000..5d1be4c --- /dev/null +++ b/components/forms/GradingForm.tsx @@ -0,0 +1,55 @@ +'use client'; + +import { useState } from 'react'; +import Image from 'next/image'; +import YourGrade from '@images/your-grade.svg'; +import { Check } from 'lucide-react'; +import { Session } from 'next-auth'; +import { addGrade } from '@/actions/grades'; + +export function GradingForm({ session, testId }: { session: Session; testId: number }) { + const [grade, setGrade] = useState(0); + const [hasVoted, setHasVoted] = useState(false); + + async function handleSubmit(event: React.FormEvent) { + event.preventDefault(); + + const result = await addGrade(testId, session.user.id, grade, null); + if (result.id) { + console.log('ok'); + setHasVoted(true); + setGrade(result.grade); + } else { + console.log('error'); + } + } + + if (hasVoted && grade != 0) { + return ( +
+
+ {YourGrade} + {grade} +
+
+ ); + } + + return ( +
+
+
+ setGrade(Number(e.target.value) / 2)} /> +
+
+ {YourGrade} + {grade} + + +
+
+
+ ); +} diff --git a/components/forms/LoginForm.tsx b/components/forms/LoginForm.tsx new file mode 100644 index 0000000..1c9f826 --- /dev/null +++ b/components/forms/LoginForm.tsx @@ -0,0 +1,30 @@ +'use client'; + +import { signIn } from 'next-auth/react'; +import { useState } from 'react'; +import { Input } from '../ui/input'; +import { Check } from 'lucide-react'; + +export function LoginForm() { + const [password, setPassword] = useState(''); + + async function handleSubmit(event: React.FormEvent) { + event.preventDefault(); + + console.log('Trying to sign in'); + await signIn('credentials', { + key: password, + callbackUrl: '/play', + }); + } + + return ( +
+ setPassword(e.target.value)}> + + +
+ ); +} diff --git a/lib/authenticate.ts b/lib/authenticate.ts index e7c56b1..407e937 100644 --- a/lib/authenticate.ts +++ b/lib/authenticate.ts @@ -1,6 +1,7 @@ import Credentials from "next-auth/providers/credentials"; import prisma from "./prisma"; import { getServerSession, RequestInternal, type NextAuthOptions, User } from "next-auth"; +import type { GetServerSidePropsContext, NextApiRequest, NextApiResponse } from "next" export async function authenticate(key: string) { console.log("Running authenticate function with key: " + key); @@ -72,4 +73,9 @@ export const authOptions: NextAuthOptions = { ], }; +// Use it in server contexts +export function auth(...args: [GetServerSidePropsContext["req"], GetServerSidePropsContext["res"]] | [NextApiRequest, NextApiResponse] | []) { + return getServerSession(...args, authOptions) +} + export const getAuthServerSession = () => getServerSession(authOptions); \ No newline at end of file From ff6133d80b4ce07718a03306ed75bb1ef52671b0 Mon Sep 17 00:00:00 2001 From: Fayorg Date: Mon, 18 Dec 2023 15:02:07 +0100 Subject: [PATCH 5/6] fix: removed unused api route --- app/api/grade/route.ts | 93 ------------------------------------------ app/api/me/route.ts | 36 ---------------- app/api/test/route.ts | 59 --------------------------- 3 files changed, 188 deletions(-) delete mode 100644 app/api/grade/route.ts delete mode 100644 app/api/me/route.ts delete mode 100644 app/api/test/route.ts diff --git a/app/api/grade/route.ts b/app/api/grade/route.ts deleted file mode 100644 index f5f264d..0000000 --- a/app/api/grade/route.ts +++ /dev/null @@ -1,93 +0,0 @@ -import {NextRequest, NextResponse} from "next/server"; - -import prisma from "@/lib/prisma"; -interface IBody { - key: string - grade: number - testId: number -} - -export async function POST(req: Request){ - const body: IBody = await req.json(); - - const test = await prisma.test.findFirst({ - select: { - id: true, - testOn: true, - testOf: { - select: { - id: true, - firstName: true, - lastName: true, - isTeacher: true - } - }, - grades: { - select: { - note: true, - grade: true, - createdAt: true, - }, - where: { - user: { - key: body.key - } - } - } - }, - where: { - id: body.testId - } - }); - - if(!test){ - return NextResponse.json({error: "Test not found"}, {status: 404}); - } - - if(test.grades.length > 0){ - return NextResponse.json({error: "You have already voted"}, {status: 403}); - } - - const grade = await prisma.grade.create({ - data: { - note: "", - grade: body.grade, - user: { - connect: { - key: body.key - } - }, - test: { - connect: { - id: test.id - } - } - } - }); - - return NextResponse.json({ - id: test.id, - testOn: test.testOn, - testOf: { - firstName: test.testOf.firstName, - lastName: test.testOf.lastName, - isTeacher: test.testOf.isTeacher - }, - vote: { - hasVoted: test.grades?.length > 0, - grade: grade.grade, - note: grade.note, - createdAt: grade.createdAt - } - }); -} - -export async function GET(req: NextRequest){ - const key = req.nextUrl.searchParams.get("key"); - - if(!key) return NextResponse.json({error: "No key provided"}, {status: 400}); - - const grades = await prisma.grade.findMany({}) - - return NextResponse.json(grades, {status: 200}); -} diff --git a/app/api/me/route.ts b/app/api/me/route.ts deleted file mode 100644 index ef79a32..0000000 --- a/app/api/me/route.ts +++ /dev/null @@ -1,36 +0,0 @@ -import {NextRequest, NextResponse} from "next/server"; -import prisma from "@/lib/prisma"; - -export async function GET(req: NextRequest){ - const key = req.nextUrl.searchParams.get("key"); - - if(!key) return NextResponse.json({error: "No key provided"}, {status: 400}); - - const user = await prisma.users.findUnique({ - select: { - id: true, - firstName: true, - lastName: true, - isTeacher: true, - test: { - select: { - testOn: true - } - } - }, - where: { - key - } - }); - - if(!user) return NextResponse.json({error: "Key not found"}, {status: 404}); - - return NextResponse.json({ - id: user.id, - firstName: user.firstName, - lastName: user.lastName, - isTeacher: user.isTeacher, - testOn: user.test?.testOn - }); - -} diff --git a/app/api/test/route.ts b/app/api/test/route.ts deleted file mode 100644 index 2a8d7fe..0000000 --- a/app/api/test/route.ts +++ /dev/null @@ -1,59 +0,0 @@ -import {NextRequest, NextResponse} from "next/server"; -import prisma from "@/lib/prisma"; - -export async function GET(req: NextRequest){ - const date = req.nextUrl.searchParams.get("date"); - const key = req.nextUrl.searchParams.get("key"); - - const usableDate = new Date(date || new Date()); - - const test = await prisma.test.findFirst({ - select: { - id: true, - testOn: true, - testOf: { - select: { - id: true, - firstName: true, - lastName: true, - isTeacher: true - } - }, - grades: { - select: { - note: true, - grade: true, - createdAt: true, - }, - where: { - user: { - key: key || "" - } - } - } - }, - where: { - testOn: new Date(usableDate.getFullYear() + "-" + (usableDate.getMonth() + 1) + "-" + (usableDate.getDate().toString().length === 1 ? "0" + usableDate.getDate() : usableDate.getDate())) - } - }); - - if(!test){ - return NextResponse.json({error: "Test not found"}, {status: 404}); - } - - return NextResponse.json({ - id: test.id, - testOn: test.testOn, - testOf: { - firstName: test.testOf.firstName, - lastName: test.testOf.lastName, - isTeacher: test.testOf.isTeacher - }, - vote: { - hasVoted: test.grades?.length > 0, - grade: test.grades[0]?.grade, - note: test.grades[0]?.note, - createdAt: test.grades[0]?.createdAt - } - }); -} From 70877ea8dc823d480e57681967f0daa47129aace Mon Sep 17 00:00:00 2001 From: Fayorg Date: Mon, 18 Dec 2023 15:03:42 +0100 Subject: [PATCH 6/6] fix: removed console.log in different authentication methods --- components/forms/LoginForm.tsx | 1 - lib/authenticate.ts | 6 ------ 2 files changed, 7 deletions(-) diff --git a/components/forms/LoginForm.tsx b/components/forms/LoginForm.tsx index 1c9f826..e0a8f76 100644 --- a/components/forms/LoginForm.tsx +++ b/components/forms/LoginForm.tsx @@ -11,7 +11,6 @@ export function LoginForm() { async function handleSubmit(event: React.FormEvent) { event.preventDefault(); - console.log('Trying to sign in'); await signIn('credentials', { key: password, callbackUrl: '/play', diff --git a/lib/authenticate.ts b/lib/authenticate.ts index 407e937..fbd4372 100644 --- a/lib/authenticate.ts +++ b/lib/authenticate.ts @@ -4,7 +4,6 @@ import { getServerSession, RequestInternal, type NextAuthOptions, User } from "n import type { GetServerSidePropsContext, NextApiRequest, NextApiResponse } from "next" export async function authenticate(key: string) { - console.log("Running authenticate function with key: " + key); const user = await prisma.users.findUnique({ select: { id: true, @@ -17,8 +16,6 @@ export async function authenticate(key: string) { } }); - console.log("Found user: " + JSON.stringify(user)); - if(!user) return null; return user; @@ -61,11 +58,8 @@ export const authOptions: NextAuthOptions = { const { key } = credentials as { key: string }; - console.log("Running authorize function with key: " + key) const user = await authenticate(key) as User | null - - console.log("Found user in authorize: " + JSON.stringify(user)); return user; }