Skip to content
Open
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
793fb80
Nextjs initialization
luke-rt Sep 22, 2024
9d1632d
Set up tailwind, cva, shadcn
luke-rt Sep 22, 2024
615d0fa
styling, fonts, began landing page
luke-rt Sep 29, 2024
a38b6f6
added about page
rydersitcawich Oct 6, 2024
7e72a72
faq page
rydersitcawich Oct 6, 2024
3bc9478
styling, scorebox, api helpers
luke-rt Oct 13, 2024
b0e0430
Nextjs route rewrites
luke-rt Oct 27, 2024
1e1e2ce
updated Next.js version, rewrites
luke-rt Nov 3, 2024
b87e529
Small fixes, began JWT auth migration
luke-rt Nov 3, 2024
f95225f
JWT tokens working with oidc somewhat working
luke-rt Nov 10, 2024
a01fc92
Removed old auth stuff
luke-rt Jan 23, 2025
ff0621b
Rollback to tailwind 3 for compatability
luke-rt Jan 23, 2025
06ad776
Progress with next-auth configuration
luke-rt Jan 26, 2025
7ed3316
Made some progress with nextauth and middleware
luke-rt Feb 10, 2025
0c9235a
Trying without nextauth
luke-rt Feb 10, 2025
14e62ef
JWT token logic working, need to cryptographically verify
luke-rt Feb 13, 2025
7e12b07
Create test route and setup JWK
luke-rt Feb 13, 2025
88306fb
Add Fetch Locations Management Command
shiva-menta Oct 12, 2024
f208676
Resolved dev environment bugs
luke-rt Feb 13, 2025
e00ecfa
Removed duplicate dependencies
luke-rt Feb 13, 2025
42cde55
Got auth middleware working >:)
luke-rt Feb 16, 2025
e95e0b8
rename of token
sritanmotati Feb 16, 2025
cc35dd7
Began working on design
luke-rt Feb 16, 2025
025af3f
Set up debounced course search
luke-rt Feb 17, 2025
314f801
Searchbar in header
luke-rt Feb 17, 2025
7d3d2cb
Bump dependencies, upgrade tailwind
luke-rt Feb 17, 2025
dd3e807
Fix expired token bug and cache JWKs
luke-rt Feb 18, 2025
3d9ae25
Use shadcn for searchbar
luke-rt Feb 18, 2025
1bd9fea
Reconfigure Shadcn, finish searchbar styling
luke-rt Feb 18, 2025
4761cf6
Nits
luke-rt Feb 19, 2025
08c6a53
Finished fuzzy searchbar/autocomplete
luke-rt Feb 20, 2025
c299c91
Progress with static rendering review data
luke-rt Feb 23, 2025
cb55040
deleted the semester field
el-agua Feb 23, 2025
32d02c7
Revert "deleted the semester field"
el-agua Feb 23, 2025
d8f9be4
Removed the semester url redirect
el-agua Feb 23, 2025
3679b7c
Change searchbar style in header
luke-rt Feb 23, 2025
1e19c3d
Lint :(
luke-rt Feb 28, 2025
fb22843
Merge branch 'master' into review-nextjs
luke-rt Mar 21, 2025
e56c2f3
Rollback postgres version to align w master
luke-rt Mar 21, 2025
2bdb648
Fixed frontend middleware, started oidc backend middleware
luke-rt Apr 3, 2025
aedff42
JWK verification finished in middleware
luke-rt Apr 4, 2025
83b0075
Caching JWKs client with thread locking
luke-rt Apr 5, 2025
e315367
jk made it a custom auth permission class
luke-rt Apr 5, 2025
0152de8
Broke something
luke-rt Apr 5, 2025
fd600c5
Allow AnonymousUser in JWT auth and working fetching on frontend
luke-rt Apr 5, 2025
f7354af
Linting
luke-rt Apr 6, 2025
e894ef2
Linting
luke-rt Apr 6, 2025
3feff7b
Frontend progress
luke-rt Apr 6, 2025
f398f10
Merge branch 'master' into review-nextjs
luke-rt Apr 6, 2025
072534e
Thank you copilot
luke-rt Apr 6, 2025
f7116b6
Linting
luke-rt Apr 6, 2025
6e0a6a2
Linting
luke-rt Apr 6, 2025
395dfec
Update docker
luke-rt Apr 6, 2025
0fab482
Rewrite to asyncio
luke-rt Apr 13, 2025
9855b6e
Update workflow hash
luke-rt Apr 13, 2025
a00674f
Merge branch 'master' into review-nextjs
luke-rt Apr 13, 2025
f4e59cc
Linting
luke-rt Apr 13, 2025
e7a5c00
Hide searchbar on redirect and unfocus
luke-rt Apr 13, 2025
9c6e8f7
Merge branch 'master' into review-nextjs
luke-rt Aug 29, 2025
9784e5c
add appDir true for next config
jamesdoh0109 Aug 30, 2025
c967627
limit search results to avoid search speed/crashing
jamesdoh0109 Aug 31, 2025
cfd6c75
add minor edits + fix tab spacing
jamesdoh0109 Aug 31, 2025
2a6ce37
Merge pull request #727 from pennlabs/james/review-nextjs
luke-rt Aug 31, 2025
e0592b6
edit middleware config paths to match new route structure
jamesdoh0109 Aug 31, 2025
876193f
fix redirect url from / to entered path
jamesdoh0109 Aug 31, 2025
1b6de02
Merge pull request #728 from pennlabs/james/fix-redirect-after-log-in
jamesdoh0109 Aug 31, 2025
72de08b
fix invalid jwt token due to missing cryptography package
jamesdoh0109 Sep 4, 2025
5ed47da
Merge pull request #730 from pennlabs/james/fix-invalid-jwt-token
luke-rt Sep 4, 2025
0b6e305
Merge remote-tracking branch 'origin/master' into review-nextjs
jamesdoh0109 Sep 7, 2025
538ffce
Moved async jwt refresh to read() function
luke-rt Sep 19, 2025
a6551a0
Heavy refactoring
luke-rt Sep 1, 2025
3b3aada
Indexed db stale-while-revalidate
luke-rt Sep 1, 2025
2118908
Small changes
luke-rt Sep 2, 2025
0e094a3
Add null check for data
luke-rt Sep 2, 2025
9efef5b
Moved JWT to Celery
luke-rt Sep 5, 2025
78ff701
Put review pages in header
luke-rt Sep 5, 2025
0eca275
Remove command autoscroll
luke-rt Sep 7, 2025
a65083a
Comment
luke-rt Sep 7, 2025
f43ed1d
Merge pull request #729 from pennlabs/luke/review-nextjs-search
luke-rt Sep 20, 2025
b400447
Lint
luke-rt Sep 21, 2025
4138661
Oops
luke-rt Sep 21, 2025
49a65f7
install cryptography per uv migration
jamesdoh0109 Sep 28, 2025
b17e5cd
remove print statements
jamesdoh0109 Sep 28, 2025
4496c46
Merge pull request #732 from pennlabs/james/install-cryptography-foll…
jamesdoh0109 Sep 28, 2025
f5573c2
Fix test cases (#733)
luke-rt Sep 29, 2025
e9d3ffb
Rename to review-2.0
luke-rt Oct 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions backend/Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM pennlabs/django-base:f0f05216db7c23c1dbb5b95c3bc9e8a2603bf2fd

LABEL maintainer="Penn Labs"

WORKDIR /backend

# Copy project dependencies
COPY Pipfile* ./

# Install backend dependencies
RUN pipenv install --dev

# Alias runserver command
RUN echo 'alias runserver="python manage.py runserver 0.0.0.0:8000"' >> ~/.bashrc
Empty file.
64 changes: 64 additions & 0 deletions backend/PennCourses/authentication/jwt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import logging
import pprint
import threading
import time

import jwt
from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from rest_framework import authentication, exceptions


logger = logging.getLogger(__name__)

REFRESH_INTERVAL = 3600 # 1 hour
jwks_client = jwt.PyJWKClient(settings.JWKS_URI)
jwks_client_lock = threading.Lock()


def get_jwks_client():
with jwks_client_lock:
return jwks_client


def start_jwks_refresh():
def refresh():
global jwks_client
while True:
try:
new_client = jwt.PyJWKClient(settings.JWKS_URI)
with jwks_client_lock:
jwks_client = new_client
logger.info("[JWK] JWKs client updated and cached.")
except Exception as e:
logger.info("[JWK] Error updating JWKs client: %s", e)
time.sleep(REFRESH_INTERVAL)

thread = threading.Thread(target=refresh, daemon=True)
thread.start()


def verify_jwt(token):
try:
signing_key = jwks_client.get_signing_key_from_jwt(token).key
payload = jwt.decode(
token, signing_key, audience=settings.AUTH_OIDC_CLIENT_ID, algorithms=["RS256"]
)
return payload
except jwt.PyJWTError:
raise exceptions.AuthenticationFailed("Invalid JWT Token.")


class JWTAuthentication(authentication.BaseAuthentication):
"""
Authentication based on JWT tokens stored in cookies.
"""

def authenticate(self, request):
id_token = request.COOKIES.get("id_token")
if id_token:
payload = verify_jwt(id_token)
if payload:
pprint.pprint(payload)
return (payload, None)
return (AnonymousUser, None)
9 changes: 9 additions & 0 deletions backend/PennCourses/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@

import boto3
import dj_database_url
import dotenv
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want this in prod?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not but rn we only have a test credential anyways



dotenv.load_dotenv()


DOMAINS = os.environ.get("DOMAINS", "example.com").split(",")
Expand Down Expand Up @@ -174,6 +178,10 @@
WEBHOOK_USERNAME = os.environ.get("WEBHOOK_USERNAME", "webhook")
WEBHOOK_PASSWORD = os.environ.get("WEBHOOK_PASSWORD", "password")

# Penn Labs Platform JWKs
JWKS_URI = f"https://platform.pennlabs.org/accounts/.well-known/jwks.json"
AUTH_OIDC_CLIENT_ID = os.environ.get("AUTH_OIDC_CLIENT_ID", "")

# Email Configuration
SMTP_HOST = os.environ.get("SMTP_HOST", "")
SMTP_PORT = os.environ.get("SMTP_PORT", 587)
Expand All @@ -199,6 +207,7 @@
"rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.BasicAuthentication",
"accounts.authentication.PlatformAuthentication",
"PennCourses.authentication.jwt.JWTAuthentication",
],
}

Expand Down
1 change: 1 addition & 0 deletions backend/Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ drf-nested-routers = "*"
google-api-python-client = "*"
asyncio = "*"
aiohttp = "*"
pyjwt = "*"

[requires]
python_full_version = "3.11"
2,540 changes: 1,313 additions & 1,227 deletions backend/Pipfile.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion backend/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
version: "3"

services:
db:
image: postgres:15.8
Expand Down Expand Up @@ -29,4 +31,4 @@ services:
environment:
- REDIS_URL=redis://redis:6379/1
- DATABASE_URL=postgres://penn-courses:postgres@db:5432/postgres
command: pipenv run python manage.py runserver 0.0.0.0:8000
command: pipenv run python manage.py runserver 0.0.0.0:8000
2 changes: 2 additions & 0 deletions backend/review/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
department_reviews,
instructor_for_course_reviews,
instructor_reviews,
test_jwt,
)


Expand Down Expand Up @@ -42,4 +43,5 @@
name="course-history",
),
path("autocomplete", cache_page(MONTH_IN_SECONDS)(autocomplete), name="review-autocomplete"),
path("testjwt", test_jwt, name="test-jwt"),
]
21 changes: 19 additions & 2 deletions backend/review/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,8 @@ def course_plots(request, course_code):
instructors__id__in=instructor_ids,
).distinct()

section_map = defaultdict(dict) # a dict mapping semester to section id to section object
# a dict mapping semester to section id to section object
section_map = defaultdict(dict)
for section in filtered_sections:
section_map[section.efficient_semester][section.id] = section

Expand Down Expand Up @@ -753,6 +754,7 @@ def instructor_for_course_reviews(request, course_code, instructor_id):
)


@permission_classes([])
@api_view(["GET"])
@schema(
PcxAutoSchema(
Expand Down Expand Up @@ -797,7 +799,7 @@ def autocomplete(request):
{
"title": semester_prefix(course) + course["full_code"],
"desc": [course["title"]],
"url": f"/course/{course['full_code']}/{course['max_semester']}",
"url": f"/course/{course['full_code']}",
}
for course in courses
],
Expand Down Expand Up @@ -854,3 +856,18 @@ def join_depts(depts):
return Response(
{"courses": course_set, "departments": department_set, "instructors": instructor_set}
)


@permission_classes([IsAuthenticated])
@api_view(["GET"])
@schema(
PcxAutoSchema(
response_codes={
"test-jwt": {
"GET": {200: "[DESCRIBE_RESPONSE_SCHEMA]Test dump retrieved successfully."},
},
},
)
)
def test_jwt(request):
return Response({"hello": "hi"})
6 changes: 6 additions & 0 deletions frontend/review-nextjs/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extends": ["next/core-web-vitals", "next/typescript"],
"rules": {
"@typescript-eslint/explicit-function-return-type": "off"
}
}
36 changes: 36 additions & 0 deletions frontend/review-nextjs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
36 changes: 36 additions & 0 deletions frontend/review-nextjs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
113 changes: 113 additions & 0 deletions frontend/review-nextjs/app/(header)/about/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React from "react";
import Link from "next/link";
import { cn } from "@/lib/utils";
import Image from "next/image";

export default function About() {
return (
<div className={cn("mx-[10%]")}>
<h2>Hey there!</h2>
<p>Welcome to the new Penn Course Review!</p>
<p>
The student-run Penn Course Review has served as a valuable
guide for course selection since the 1960s. In 2014, Penn Course
Review was completely redesigned to simplify the search
experience. In 2018, we updated the site again to continue
providing you with the best insights on courses. In 2021, we
migrated the site over to a shared backend with{" "}
<a
target="_blank"
rel="noopener noreferrer"
href="https://penncoursealert.com/"
>
Penn Course Alert
</a>{" "}
and{" "}
<a
target="_blank"
rel="noopener noreferrer"
href="https://penncourseplan.com/"
>
Penn Course Plan
</a>
, allowing us to serve additional metrics about course
registration difficulty (based on Penn Course Alert usage data
and course status updates). We hope to continue updating and
improving Penn Course Review in the years to come!
</p>

<h2>About</h2>
<p>
Penn Course Review is a student-run service that provides
numerical ratings and metrics for undergraduate courses and
professors at the University of Pennsylvania. PCR has a long
history of being a valuable and influential guide for course
selection.
</p>
<p>
PCR is developed and managed by{" "}
<a
target="_blank"
rel="noopener noreferrer"
href="https://pennlabs.org/"
>
Penn Labs
</a>
, a student developer organization on Penn&apos;s campus.
</p>

<p>
Penn Course Review compiles its information from online course
evaluations conducted at the end of each semester by the
Provost&apos;s office in conjunction with ISC.
</p>
<p>
Your evaluations and comments feed the Review, so the more
information you provide about your courses and professors, the
more comprehensive Penn Course Review will be.
</p>

<p>
If you want to look at courses on the go,{" "}
<a
target="_blank"
rel="noopener noreferrer"
href="https://pennlabs.org/mobile/"
>
PennMobile
</a>{" "}
is available for download! In the courses section, you are able
to view course descriptions and ratings!
</p>
<p>
Interested in building something using the Penn Courses API?
Check our our{" "}
<a
target="_blank"
rel="noopener noreferrer"
href="https://penncoursereview.com/api/documentation/"
>
API documentation
</a>
.
</p>
<p>Thanks and happy searching,</p>
<p>
<strong>Penn Labs</strong>
</p>

<Image
alt="Penn Labs"
src="/image/labs.png"
width={100}
height={100}
/>

<h2>Questions</h2>
<p>
If you have any questions, take a look at our{" "}
<Link href="/faq">FAQs</Link> section.
</p>
</div>
);
}
Loading
Loading