From 48e8621dff7cb21d46df8f857011f083fb56fc38 Mon Sep 17 00:00:00 2001 From: Vikram Date: Fri, 2 Feb 2024 17:31:34 -0800 Subject: [PATCH 1/9] added a transaction for creating a lab and research profile --- pages/api/researcher/profile/create.js | 64 ++++++++++++++++++-------- 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/pages/api/researcher/profile/create.js b/pages/api/researcher/profile/create.js index c6e475e..1e3532a 100644 --- a/pages/api/researcher/profile/create.js +++ b/pages/api/researcher/profile/create.js @@ -26,29 +26,53 @@ class ResearcherProfileCreationRoute extends ApiRoute { const { firstName, lastName, labName, showPicture, labContactEmail, department } = value; // TODO: combine queries into a prisma transaction - - await prisma.lab.create({ - data: { - name: labName, - contactEmail: labContactEmail, - department, - adminResearchers: { - connect: { - email: req.session.user.email, + const result = await prisma.$transaction([ + prisma.lab.create({ + data: { + name: labName, + contactEmail: labContactEmail, + department, + adminResearchers: { + connect: { + email: req.session.user.email, + }, }, }, - }, - }); - const result = await prisma.researcherProfile.create({ - data: { - firstName, - lastName, - profilePicture: showPicture ? req.token.picture : null, - researcher: { - connect: { email: req.session.user.email }, + }), + prisma.researcherProfile.create({ + data: { + firstName, + lastName, + profilePicture: showPicture ? req.token.picture : null, + researcher: { + connect: { email: req.session.user.email }, + }, }, - }, - }); + }), + ]) + + // await prisma.lab.create({ + // data: { + // name: labName, + // contactEmail: labContactEmail, + // department, + // adminResearchers: { + // connect: { + // email: req.session.user.email, + // }, + // }, + // }, + // }); + // const result = await prisma.researcherProfile.create({ + // data: { + // firstName, + // lastName, + // profilePicture: showPicture ? req.token.picture : null, + // researcher: { + // connect: { email: req.session.user.email }, + // }, + // }, + // }); res.status(200).json(result); } catch (e) { From be9f6334430cc3ee1bb0e52d205a505e8d175486 Mon Sep 17 00:00:00 2001 From: Vikram Date: Fri, 2 Feb 2024 18:31:38 -0800 Subject: [PATCH 2/9] removed old commented code --- pages/api/researcher/profile/create.js | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/pages/api/researcher/profile/create.js b/pages/api/researcher/profile/create.js index 1e3532a..e622ad3 100644 --- a/pages/api/researcher/profile/create.js +++ b/pages/api/researcher/profile/create.js @@ -51,29 +51,6 @@ class ResearcherProfileCreationRoute extends ApiRoute { }), ]) - // await prisma.lab.create({ - // data: { - // name: labName, - // contactEmail: labContactEmail, - // department, - // adminResearchers: { - // connect: { - // email: req.session.user.email, - // }, - // }, - // }, - // }); - // const result = await prisma.researcherProfile.create({ - // data: { - // firstName, - // lastName, - // profilePicture: showPicture ? req.token.picture : null, - // researcher: { - // connect: { email: req.session.user.email }, - // }, - // }, - // }); - res.status(200).json(result); } catch (e) { // check for Node.js errors (data integrity, etc) From f065afdb0c499e044531d3aa57d782ef00a9564f Mon Sep 17 00:00:00 2001 From: Vikram Date: Mon, 5 Feb 2024 13:53:00 -0800 Subject: [PATCH 3/9] fixed formatting --- pages/api/researcher/profile/create.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pages/api/researcher/profile/create.js b/pages/api/researcher/profile/create.js index e622ad3..8727a2a 100644 --- a/pages/api/researcher/profile/create.js +++ b/pages/api/researcher/profile/create.js @@ -25,7 +25,6 @@ class ResearcherProfileCreationRoute extends ApiRoute { } const { firstName, lastName, labName, showPicture, labContactEmail, department } = value; - // TODO: combine queries into a prisma transaction const result = await prisma.$transaction([ prisma.lab.create({ data: { @@ -49,7 +48,7 @@ class ResearcherProfileCreationRoute extends ApiRoute { }, }, }), - ]) + ]); res.status(200).json(result); } catch (e) { From 69307d9cb9b80948cb6985579c108b7199e3dcd9 Mon Sep 17 00:00:00 2001 From: Vikram Date: Mon, 1 Apr 2024 16:27:05 -0700 Subject: [PATCH 4/9] added login page --- middleware.js | 9 +- pages/api/auth/[...nextauth].js | 27 +++--- .../student/profile/resumes/[profileId].js | 83 +++++++++++++++++ pages/auth/error.jsx | 88 ++++++++++++++++++ pages/login.jsx | 89 +++++++++++++++++++ 5 files changed, 280 insertions(+), 16 deletions(-) create mode 100644 pages/api/student/profile/resumes/[profileId].js create mode 100644 pages/auth/error.jsx create mode 100644 pages/login.jsx diff --git a/middleware.js b/middleware.js index 175d6e2..569d236 100644 --- a/middleware.js +++ b/middleware.js @@ -11,18 +11,19 @@ export async function middleware(req) { '/api/auth/signin/google', '/api/auth/callback/google', '/api/auth/error', + '/auth/signin', ]; const publicPaths = ['/_next', '/favicon.ico']; const { pathname, basePath, origin, search } = req.nextUrl; - if (signinPaths.includes(pathname) || publicPaths.some((p) => pathname.startsWith(p))) { + if (signinPaths.includes(pathname) || publicPaths.some((p) => pathname.startsWith(p)) || pathname.startsWith('/api/auth')) { return NextResponse.next(); } const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET }); if (!token) { - const signInPage = '/api/auth/signin'; - const signInUrl = new URL(`${basePath}${signInPage}`, origin); - signInUrl.searchParams.append('callbackUrl', `${basePath}${pathname}${search}`); + const signInPage = '/auth/signin'; + const signInUrl = new URL(`${ basePath }${ signInPage }`, origin); + signInUrl.searchParams.append('callbackUrl', `${ basePath }${ pathname }${ search }`); return NextResponse.redirect(signInUrl); } const url = req.nextUrl.clone(); diff --git a/pages/api/auth/[...nextauth].js b/pages/api/auth/[...nextauth].js index e137375..02297b0 100644 --- a/pages/api/auth/[...nextauth].js +++ b/pages/api/auth/[...nextauth].js @@ -42,25 +42,25 @@ export const authOptions = { const currentStudent = process.env.USE_SUPABASE ? (await supabase.from('Student').select().eq('email', token.email).limit(1).single()) - ?.data + ?.data : await prisma.student.findUnique({ - where: { email: token.email }, - }); + where: { email: token.email }, + }); if (currentStudent) { token.accountType = 'student'; } else { const currentResearcher = process.env.USE_SUPABASE ? ( - await supabase - .from('Researcher') - .select() - .eq('email', token.email) - .limit(1) - .single() - )?.data + await supabase + .from('Researcher') + .select() + .eq('email', token.email) + .limit(1) + .single() + )?.data : await prisma.researcher.findUnique({ - where: { email: token.email }, - }); + where: { email: token.email }, + }); if (currentResearcher) { token.accountType = 'researcher'; } else { @@ -71,5 +71,8 @@ export const authOptions = { return token; }, }, + pages: { + signIn: '/login' + } }; export default NextAuth(authOptions); diff --git a/pages/api/student/profile/resumes/[profileId].js b/pages/api/student/profile/resumes/[profileId].js new file mode 100644 index 0000000..e7da89b --- /dev/null +++ b/pages/api/student/profile/resumes/[profileId].js @@ -0,0 +1,83 @@ +import ApiRoute from '@lib/ApiRoute'; + +import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3'; +import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; + +// TODO: use edge route +class StudentResumeRoute extends ApiRoute { + /** + * application update endpoint + * @param {import('next').NextApiRequest & { session: import('next-auth').Session?}} req + * @param {import('next').NextApiResponse} res + * @param {import('prisma/prisma-client').PrismaClient} prisma + * @returns + */ + async get(req, res, prisma) { + try { + const profileId = req.query.profileId; + + if (profileId === undefined) { + return res.status(400).json({ message: 'Invalid request' }); + } + let resumeId = ''; + if (req.token.accountType === 'student') { + const result = await prisma.student.findUnique({ + where: { + email: req.session.user.email, + }, + select: { + studentProfile: { select: { id: true } }, + }, + }); + const viewerProfileId = result?.studentProfile?.id; + if (viewerProfileId === null || viewerProfileId === undefined) { + return res.status(404).json({ message: `You do not have a profile` }); + } + if (viewerProfileId !== profileId) { + return res.status(403).json({ message: `You cannot view other students' résumés.` }); + } + resumeId = req.session.user.email; + } else if (req.token.accountType === 'researcher') { + const appliedJob = await prisma.labeledJob.findFirst({ + where: { + status: 'APPLIED', + job: { poster: { email: req.session.user.email } }, + applicant: { studentProfile: { id: profileId } }, + }, + select: { applicantEmail: true }, + }); + if (appliedJob === null) { + return res.status(403).json({ + message: `You cannot view this résumé because this student has not applied to one of your posts.`, + }); + } + resumeId = appliedJob.applicantEmail; + } else { + return res.status(403); + } + const client = new S3Client({ + region: process.env.RESUMES_BUCKET_REGION, + credentials: { + accessKeyId: process.env.ACCESS_KEY_ID, + secretAccessKey: process.env.SECRET_ACCESS_KEY, + }, + }); + const command = new GetObjectCommand({ + Bucket: process.env.RESUMES_BUCKET, + Key: `${resumeId}.pdf`, + ContentType: 'application/pdf', + }); + const url = await getSignedUrl(client, command, { expiresIn: 3600 }); + + return res.status(200).json({ url }); + } catch (e) { + // check for Node.js errors (data integrity, etc) + console.error(e); + res.status(500).json({ message: 'something went wrong' }); + } finally { + res.end(); + } + } +} + +export default new StudentResumeRoute().as_handler(); diff --git a/pages/auth/error.jsx b/pages/auth/error.jsx new file mode 100644 index 0000000..ea5662c --- /dev/null +++ b/pages/auth/error.jsx @@ -0,0 +1,88 @@ + +import { signIn } from 'next-auth/react'; +import Google from 'next-auth/providers/google'; + +function LoginPage() { + + return ( +
+

+ Welcome to + + + + + + + + + + + + + +

+
+

+
+ +
+

+
+
+ ); +} + +export default LoginPage; diff --git a/pages/login.jsx b/pages/login.jsx new file mode 100644 index 0000000..56e15c9 --- /dev/null +++ b/pages/login.jsx @@ -0,0 +1,89 @@ + +import { signIn } from 'next-auth/react'; +import Google from 'next-auth/providers/google'; + +function LoginPage() { + + return ( +
+

+ Welcome to + + + + + + + + + + + + + +

+
+

+
+ +
+

+
+

UCLA Email required

+
+ ); +} + +export default LoginPage; From 00fb858807f4f1ad82ba87b3195fa78c4df35238 Mon Sep 17 00:00:00 2001 From: Vikram Date: Thu, 25 Apr 2024 19:27:32 -0700 Subject: [PATCH 5/9] login page working --- middleware.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/middleware.js b/middleware.js index 569d236..20cb8da 100644 --- a/middleware.js +++ b/middleware.js @@ -12,19 +12,24 @@ export async function middleware(req) { '/api/auth/callback/google', '/api/auth/error', '/auth/signin', + '/login' ]; const publicPaths = ['/_next', '/favicon.ico']; const { pathname, basePath, origin, search } = req.nextUrl; - if (signinPaths.includes(pathname) || publicPaths.some((p) => pathname.startsWith(p)) || pathname.startsWith('/api/auth')) { + if (signinPaths.includes(pathname) || publicPaths.some((p) => pathname.startsWith(p)) || pathname.startsWith('/api/auth/')) { return NextResponse.next(); } const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET }); if (!token) { const signInPage = '/auth/signin'; const signInUrl = new URL(`${ basePath }${ signInPage }`, origin); + console.log(signInUrl) signInUrl.searchParams.append('callbackUrl', `${ basePath }${ pathname }${ search }`); - return NextResponse.redirect(signInUrl); + console.log(signInUrl) + const url = req.nextUrl.clone() + url.pathname = '/login' + return NextResponse.redirect(url); } const url = req.nextUrl.clone(); if ( From 4e55efe000324582a8ab6fcca023e2c77b6caec8 Mon Sep 17 00:00:00 2001 From: Vikram Date: Thu, 25 Apr 2024 19:38:47 -0700 Subject: [PATCH 6/9] replaced old link --- middleware.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/middleware.js b/middleware.js index 20cb8da..7f1f08a 100644 --- a/middleware.js +++ b/middleware.js @@ -1,6 +1,6 @@ // export { default } from 'next-auth/middleware'; // locks down whole site, only for JWT // https://github.com/nextauthjs/next-auth/discussions/4265 -// https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/next/middleware.ts#L99 +// https://github.com/nextauthjs/next-auth/blob/next-auth%404.22.1/packages/next-auth/src/next/middleware.ts import { getToken } from 'next-auth/jwt'; import { NextResponse } from 'next/server'; From 02ed3f4b286fa41f1b634c4acf2f6774ffdeeded Mon Sep 17 00:00:00 2001 From: Vikram Date: Thu, 23 May 2024 19:43:02 -0700 Subject: [PATCH 7/9] formatting --- middleware.js | 20 ++++++++++++-------- pages/api/auth/[...nextauth].js | 28 ++++++++++++++-------------- pages/api/researcher/profile/edit.js | 2 +- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/middleware.js b/middleware.js index 7f1f08a..ca670a8 100644 --- a/middleware.js +++ b/middleware.js @@ -12,23 +12,27 @@ export async function middleware(req) { '/api/auth/callback/google', '/api/auth/error', '/auth/signin', - '/login' + '/login', ]; const publicPaths = ['/_next', '/favicon.ico']; const { pathname, basePath, origin, search } = req.nextUrl; - if (signinPaths.includes(pathname) || publicPaths.some((p) => pathname.startsWith(p)) || pathname.startsWith('/api/auth/')) { + if ( + signinPaths.includes(pathname) || + publicPaths.some((p) => pathname.startsWith(p)) || + pathname.startsWith('/api/auth/') + ) { return NextResponse.next(); } const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET }); if (!token) { const signInPage = '/auth/signin'; - const signInUrl = new URL(`${ basePath }${ signInPage }`, origin); - console.log(signInUrl) - signInUrl.searchParams.append('callbackUrl', `${ basePath }${ pathname }${ search }`); - console.log(signInUrl) - const url = req.nextUrl.clone() - url.pathname = '/login' + const signInUrl = new URL(`${basePath}${signInPage}`, origin); + console.log(signInUrl); + signInUrl.searchParams.append('callbackUrl', `${basePath}${pathname}${search}`); + console.log(signInUrl); + const url = req.nextUrl.clone(); + url.pathname = '/login'; return NextResponse.redirect(url); } const url = req.nextUrl.clone(); diff --git a/pages/api/auth/[...nextauth].js b/pages/api/auth/[...nextauth].js index 02297b0..5f35168 100644 --- a/pages/api/auth/[...nextauth].js +++ b/pages/api/auth/[...nextauth].js @@ -42,25 +42,25 @@ export const authOptions = { const currentStudent = process.env.USE_SUPABASE ? (await supabase.from('Student').select().eq('email', token.email).limit(1).single()) - ?.data + ?.data : await prisma.student.findUnique({ - where: { email: token.email }, - }); + where: { email: token.email }, + }); if (currentStudent) { token.accountType = 'student'; } else { const currentResearcher = process.env.USE_SUPABASE ? ( - await supabase - .from('Researcher') - .select() - .eq('email', token.email) - .limit(1) - .single() - )?.data + await supabase + .from('Researcher') + .select() + .eq('email', token.email) + .limit(1) + .single() + )?.data : await prisma.researcher.findUnique({ - where: { email: token.email }, - }); + where: { email: token.email }, + }); if (currentResearcher) { token.accountType = 'researcher'; } else { @@ -72,7 +72,7 @@ export const authOptions = { }, }, pages: { - signIn: '/login' - } + signIn: '/login', + }, }; export default NextAuth(authOptions); diff --git a/pages/api/researcher/profile/edit.js b/pages/api/researcher/profile/edit.js index dde944f..07136db 100644 --- a/pages/api/researcher/profile/edit.js +++ b/pages/api/researcher/profile/edit.js @@ -1,4 +1,4 @@ -import { Prisma } from 'prisma/prisma-client'; +import { } from 'prisma/prisma-client'; import { ResearcherProfileCreationValidator, isValidationError } from '@lib/validators'; import ApiRoute from '@lib/ApiRoute'; From 3f5f77290eec560aaf0d7e380d22b5f002333d22 Mon Sep 17 00:00:00 2001 From: Vikram Date: Thu, 23 May 2024 19:43:50 -0700 Subject: [PATCH 8/9] undid import --- pages/api/researcher/profile/edit.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/pages/api/researcher/profile/edit.js b/pages/api/researcher/profile/edit.js index 07136db..1f447a2 100644 --- a/pages/api/researcher/profile/edit.js +++ b/pages/api/researcher/profile/edit.js @@ -1,5 +1,3 @@ -import { } from 'prisma/prisma-client'; - import { ResearcherProfileCreationValidator, isValidationError } from '@lib/validators'; import ApiRoute from '@lib/ApiRoute'; From c7d5bd3218942ba47bdcf3187fe2084fdd62ab3f Mon Sep 17 00:00:00 2001 From: Vikram Date: Thu, 23 May 2024 19:46:15 -0700 Subject: [PATCH 9/9] login page with redirect --- middleware.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/middleware.js b/middleware.js index ca670a8..d5c4dc2 100644 --- a/middleware.js +++ b/middleware.js @@ -26,14 +26,10 @@ export async function middleware(req) { } const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET }); if (!token) { - const signInPage = '/auth/signin'; + const signInPage = '/login'; const signInUrl = new URL(`${basePath}${signInPage}`, origin); - console.log(signInUrl); signInUrl.searchParams.append('callbackUrl', `${basePath}${pathname}${search}`); - console.log(signInUrl); - const url = req.nextUrl.clone(); - url.pathname = '/login'; - return NextResponse.redirect(url); + return NextResponse.redirect(signInUrl); } const url = req.nextUrl.clone(); if (