Skip to content

Commit 75a7379

Browse files
committed
Merge branch 'main' of https://github.com/ericahan22/Wat2Do
2 parents 55a04d8 + 6e63692 commit 75a7379

File tree

61 files changed

+1548
-1356
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+1548
-1356
lines changed

backend/apps/clubs/migrations/0006_clubs_clubs_club_name_idx.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55

66
class Migration(migrations.Migration):
7-
87
dependencies = [
98
("clubs", "0005_alter_clubs_categories"),
109
]

backend/apps/clubs/views.py

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@ def get_clubs(request):
2525
limit = 50
2626

2727
queryset = Clubs.objects.all().order_by("id")
28-
28+
2929
# Apply search filter
3030
if search_term:
3131
queryset = queryset.filter(club_name__icontains=search_term)
32-
32+
3333
# Apply category filter
3434
if category_filter and category_filter.lower() != "all":
3535
queryset = queryset.filter(categories__icontains=category_filter)
36-
36+
3737
# Handle cursor-based pagination
3838
if cursor:
3939
try:
@@ -44,24 +44,34 @@ def get_clubs(request):
4444
{"error": "Invalid cursor format"},
4545
status=status.HTTP_400_BAD_REQUEST,
4646
)
47-
47+
4848
# Get total count for the filtered queryset
4949
total_count = queryset.count()
50-
50+
5151
# Fetch one more than limit to check if there are more results
52-
results = list(queryset.values("id", "club_name", "categories", "club_page", "ig", "discord", "club_type")[:limit + 1])
53-
52+
results = list(
53+
queryset.values(
54+
"id",
55+
"club_name",
56+
"categories",
57+
"club_page",
58+
"ig",
59+
"discord",
60+
"club_type",
61+
)[: limit + 1]
62+
)
63+
5464
# Determine if there's a next page
5565
has_more = len(results) > limit
5666
if has_more:
5767
results = results[:limit] # Remove the extra item
58-
68+
5969
# Generate next cursor from last item
6070
last_club = results[-1]
61-
next_cursor = str(last_club['id'])
71+
next_cursor = str(last_club["id"])
6272
else:
6373
next_cursor = None
64-
74+
6575
# Ensure categories is always a list for each club
6676
for club in results:
6777
categories = club.get("categories")
@@ -76,11 +86,13 @@ def get_clubs(request):
7686
else:
7787
club["categories"] = []
7888

79-
return Response({
80-
"results": results,
81-
"nextCursor": next_cursor,
82-
"hasMore": next_cursor is not None,
83-
"totalCount": total_count
84-
})
89+
return Response(
90+
{
91+
"results": results,
92+
"nextCursor": next_cursor,
93+
"hasMore": next_cursor is not None,
94+
"totalCount": total_count,
95+
}
96+
)
8597
except Exception as e:
8698
return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

backend/apps/core/auth.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
from functools import wraps
21
import logging
2+
from functools import wraps
33

4-
from clerk_backend_api import authenticate_request, AuthenticateRequestOptions
4+
from clerk_backend_api import AuthenticateRequestOptions, authenticate_request
55
from django.contrib.auth import authenticate
66
from django.contrib.auth.backends import BaseBackend
77
from django.contrib.auth.models import AnonymousUser, User
88
from django.http import JsonResponse
99

10-
from config.settings.base import CLERK_SECRET_KEY, CLERK_AUTHORIZED_PARTIES
10+
from config.settings.base import CLERK_AUTHORIZED_PARTIES, CLERK_SECRET_KEY
1111

1212
logger = logging.getLogger(__name__)
1313

1414

1515
class JwtAuthBackend(BaseBackend):
16-
def authenticate(self, request, **kwargs):
16+
def authenticate(self, request, **_kwargs):
1717
try:
1818
state = authenticate_request(
1919
request,
@@ -25,16 +25,17 @@ def authenticate(self, request, **kwargs):
2525

2626
if not state.is_signed_in:
2727
request.error_message = getattr(state, "message", "Not signed in")
28-
logger.warning("JWT auth: Clerk rejected token: %s", request.error_message)
28+
logger.warning(
29+
"JWT auth: Clerk rejected token: %s", request.error_message
30+
)
2931
return None
3032

3133
request.auth_payload = state.payload
32-
34+
3335
django_user = AnonymousUser()
3436
django_user.username = state.payload.get("sub") or "clerk_user"
3537
return django_user
3638

37-
3839
except Exception:
3940
request.error_message = "Unable to authenticate user"
4041
logger.exception("JWT auth: exception during authentication")
@@ -55,13 +56,14 @@ def _wrapped_view(request, *args, **kwargs):
5556
error = getattr(request, "error_message", "User not authenticated")
5657
return JsonResponse({"detail": error}, status=401)
5758
request.user = user
58-
59+
5960
# Extract user_id and is_admin from auth_payload (set by JwtAuthBackend)
6061
auth_payload = getattr(request, "auth_payload", {})
6162
request.user_id = auth_payload.get("sub") or auth_payload.get("id")
6263
request.is_admin = auth_payload.get("role") == "admin"
63-
64+
6465
return view_func(request, *args, **kwargs)
66+
6567
return _wrapped_view
6668

6769

@@ -71,6 +73,7 @@ def optional_jwt(view_func):
7173
If a token is provided and valid, populates request.auth_payload, request.user_id, and request.is_admin.
7274
If no token or invalid token, continues without error (for public endpoints).
7375
"""
76+
7477
@wraps(view_func)
7578
def _wrapped_view(request, *args, **kwargs):
7679
try:
@@ -81,7 +84,7 @@ def _wrapped_view(request, *args, **kwargs):
8184
authorized_parties=CLERK_AUTHORIZED_PARTIES,
8285
),
8386
)
84-
87+
8588
if state.is_signed_in:
8689
request.auth_payload = state.payload
8790
# Extract user_id (tries 'sub' first, falls back to 'id')
@@ -103,8 +106,9 @@ def _wrapped_view(request, *args, **kwargs):
103106
request.user_id = None
104107
request.is_admin = False
105108
request.user = AnonymousUser()
106-
109+
107110
return view_func(request, *args, **kwargs)
111+
108112
return _wrapped_view
109113

110114

@@ -115,4 +119,5 @@ def _wrapped_view(request, *args, **kwargs):
115119
if not getattr(request, "is_admin", False):
116120
return JsonResponse({"message": "Admin only"}, status=403)
117121
return view_func(request, *args, **kwargs)
122+
118123
return _wrapped_view

backend/apps/core/management/commands/fix_sequences.py

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33

44

55
class Command(BaseCommand):
6-
help = 'Fix PostgreSQL sequences that are out of sync for all tables'
6+
help = "Fix PostgreSQL sequences that are out of sync for all tables"
77

8-
def handle(self, *args, **options):
8+
def handle(self, *_args, **_options):
99
with connection.cursor() as cursor:
1010
# Get all sequences and their associated tables using pg_catalog
1111
cursor.execute("""
@@ -22,35 +22,39 @@ def handle(self, *args, **options):
2222
AND n.nspname = 'public'
2323
ORDER BY t.relname, a.attname;
2424
""")
25-
25+
2626
sequences = cursor.fetchall()
2727
fixed_count = 0
28-
28+
2929
if not sequences:
30-
self.stdout.write(self.style.WARNING("No sequences found in the public schema"))
30+
self.stdout.write(
31+
self.style.WARNING("No sequences found in the public schema")
32+
)
3133
return
32-
34+
3335
self.stdout.write(f"Found {len(sequences)} sequences to check...")
3436
self.stdout.write("")
35-
37+
3638
for sequence_name, table_name, column_name in sequences:
3739
try:
3840
# Get the current max value in the table
3941
cursor.execute(f"SELECT MAX({column_name}) FROM {table_name};")
4042
max_value = cursor.fetchone()[0]
41-
43+
4244
if max_value is None:
4345
max_value = 1
44-
46+
4547
# Get the current sequence value
4648
cursor.execute(f"SELECT last_value FROM {sequence_name};")
4749
current_seq_value = cursor.fetchone()[0]
48-
50+
4951
# Fix the sequence if needed
5052
if current_seq_value < max_value:
51-
cursor.execute(f"SELECT setval('{sequence_name}', {max_value}, true);")
53+
cursor.execute(
54+
f"SELECT setval('{sequence_name}', {max_value}, true);"
55+
)
5256
new_value = cursor.fetchone()[0]
53-
57+
5458
self.stdout.write(
5559
self.style.SUCCESS(
5660
f"✓ Fixed {table_name}.{column_name}: {sequence_name} "
@@ -62,14 +66,16 @@ def handle(self, *args, **options):
6266
self.stdout.write(
6367
f" {table_name}.{column_name}: {sequence_name} is OK (current: {current_seq_value}, max: {max_value})"
6468
)
65-
69+
6670
except Exception as e:
6771
self.stdout.write(
68-
self.style.ERROR(f"✗ Error fixing {table_name}.{column_name}: {str(e)}")
72+
self.style.ERROR(
73+
f"✗ Error fixing {table_name}.{column_name}: {e!s}"
74+
)
6975
)
70-
76+
7177
self.stdout.write("")
72-
self.stdout.write("="*60)
78+
self.stdout.write("=" * 60)
7379
if fixed_count > 0:
7480
self.stdout.write(
7581
self.style.SUCCESS(
@@ -80,4 +86,3 @@ def handle(self, *args, **options):
8086
self.stdout.write(
8187
self.style.SUCCESS("✓ All sequences are already in sync!")
8288
)
83-

backend/apps/core/middleware.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
from django.http import HttpResponse
22

3+
34
class HealthCheckMiddleware:
45
def __init__(self, get_response):
56
self.get_response = get_response
67

78
def __call__(self, request):
8-
if request.path_info == '/health':
9+
if request.path_info == "/health":
910
return HttpResponse("OK")
10-
return self.get_response(request)
11+
return self.get_response(request)

backend/apps/core/views.py

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
Views for the core app with Clerk authentication.
33
"""
44

5-
from apps.core.auth import jwt_required
65
from rest_framework import status
76
from rest_framework.decorators import api_view, permission_classes
87
from rest_framework.permissions import AllowAny
98
from rest_framework.response import Response
109

10+
from apps.core.auth import jwt_required
11+
1112

1213
@api_view(["GET"])
1314
@permission_classes([AllowAny])
@@ -48,34 +49,39 @@ def health(_request):
4849
@jwt_required
4950
def user_info(request):
5051
"""Get current user info from Clerk"""
51-
user_id = request.user.get('id')
52-
return Response({
53-
"id": user_id,
54-
"email": request.user.get('email_addresses', [{}])[0].get('email_address'),
55-
"first_name": request.user.get('first_name'),
56-
"last_name": request.user.get('last_name'),
57-
"username": request.user.get('username'),
58-
"image_url": request.user.get('image_url'),
59-
"created_at": request.user.get('created_at'),
60-
"updated_at": request.user.get('updated_at'),
61-
}, status=status.HTTP_200_OK)
52+
user_id = request.user.get("id")
53+
return Response(
54+
{
55+
"id": user_id,
56+
"email": request.user.get("email_addresses", [{}])[0].get("email_address"),
57+
"first_name": request.user.get("first_name"),
58+
"last_name": request.user.get("last_name"),
59+
"username": request.user.get("username"),
60+
"image_url": request.user.get("image_url"),
61+
"created_at": request.user.get("created_at"),
62+
"updated_at": request.user.get("updated_at"),
63+
},
64+
status=status.HTTP_200_OK,
65+
)
6266

6367

6468
@api_view(["GET"])
6569
@jwt_required
6670
def protected_view(request):
6771
"""Simple protected route that requires Clerk authentication"""
68-
user_id = request.user.get('id')
72+
user_id = request.user.get("id")
6973
return Response(
7074
{
7175
"message": "Welcome to the protected area!",
7276
"user": {
7377
"id": user_id,
74-
"email": request.user.get('email_addresses', [{}])[0].get('email_address'),
75-
"first_name": request.user.get('first_name'),
76-
"last_name": request.user.get('last_name'),
77-
"username": request.user.get('username'),
78-
"image_url": request.user.get('image_url'),
78+
"email": request.user.get("email_addresses", [{}])[0].get(
79+
"email_address"
80+
),
81+
"first_name": request.user.get("first_name"),
82+
"last_name": request.user.get("last_name"),
83+
"username": request.user.get("username"),
84+
"image_url": request.user.get("image_url"),
7985
},
8086
},
8187
status=status.HTTP_200_OK,

backend/apps/events/migrations/0012_eventsubmission.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66

77
class Migration(migrations.Migration):
8-
98
dependencies = [
109
("events", "0011_alter_events_dtstamp"),
1110
]

backend/apps/events/migrations/0013_eventsubmission_submitted_by.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77

88
class Migration(migrations.Migration):
9-
109
dependencies = [
1110
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
1211
("events", "0012_eventsubmission"),

backend/apps/events/migrations/0014_alter_eventsubmission_submitted_by.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55

66
class Migration(migrations.Migration):
7-
87
dependencies = [
98
("events", "0013_eventsubmission_submitted_by"),
109
]

backend/apps/events/migrations/0015_events_other_handle_and_more.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
# Generated by Django 4.2.7 on 2025-10-29 21:32
22

3-
from django.db import migrations, models
43
import django.db.models.deletion
4+
from django.db import migrations, models
55

66

77
class Migration(migrations.Migration):
8-
98
dependencies = [
109
("events", "0014_alter_eventsubmission_submitted_by"),
1110
]

0 commit comments

Comments
 (0)