Skip to content

Commit d2397b2

Browse files
committed
save
1 parent 9ba8947 commit d2397b2

File tree

9 files changed

+275
-229
lines changed

9 files changed

+275
-229
lines changed

backend/apps/core/auth.py

Lines changed: 15 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
1-
from functools import wraps
1+
from functools import wraps, lru_cache
22
import logging
33

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

1010
from config.settings.base import CLERK_SECRET_KEY, CLERK_AUTHORIZED_PARTIES
11-
import httpx
1211

1312
logger = logging.getLogger(__name__)
1413

14+
# Create a single Clerk instance to reuse across requests
15+
_clerk_client = Clerk(bearer_auth=CLERK_SECRET_KEY)
16+
1517

1618
class JwtAuthBackend(BaseBackend):
1719
def authenticate(self, request, **kwargs):
@@ -42,10 +44,11 @@ def authenticate(self, request, **kwargs):
4244
request.error_message = request_state.message
4345
logger.warning("JWT auth: Clerk rejected token: %s", request_state.message)
4446
return None
47+
print(request_state.payload, 'request_state.payload')
4548
# Ideally at this point user object must be fetched from DB and returned, but we will just return a dummy
4649
# user object
4750
user = User(username=request_state.payload.get("sub", "unknown"), password="None")
48-
# Attach payload for downstream usage if needed
51+
# Attach payload for downstream usage
4952
try:
5053
request.auth_payload = request_state.payload
5154
except Exception:
@@ -74,42 +77,22 @@ def _wrapped_view(request, *args, **kwargs):
7477
return _wrapped_view
7578

7679

77-
def is_admin(request) -> bool:
78-
"""Return True if Clerk public metadata contains role == 'admin'."""
80+
@lru_cache(maxsize=100)
81+
def _get_user_metadata(user_id: str) -> dict:
82+
"""Fetch user public_metadata from Clerk API."""
7983
try:
80-
payload = getattr(request, "auth_payload", {}) or {}
81-
public_meta = payload.get("public_metadata") or payload.get("publicMetadata") or {}
82-
if not public_meta:
83-
user_id = payload.get("sub") or payload.get("id")
84-
if user_id and CLERK_SECRET_KEY:
85-
try:
86-
with httpx.Client(timeout=5.0) as client:
87-
resp = client.get(
88-
f"https://api.clerk.com/v1/users/{user_id}",
89-
headers={
90-
"Authorization": f"Bearer {CLERK_SECRET_KEY}",
91-
"Content-Type": "application/json",
92-
},
93-
)
94-
if resp.status_code == 200:
95-
data = resp.json() or {}
96-
public_meta = data.get("public_metadata") or {}
97-
try:
98-
request.clerk_user = data
99-
except Exception:
100-
pass
101-
except Exception:
102-
public_meta = {}
103-
return public_meta.get("role") == "admin"
84+
user = _clerk_client.users.get(user_id=user_id)
85+
return getattr(user, "public_metadata", None) or getattr(user, "publicMetadata", None) or {}
10486
except Exception:
105-
return False
87+
return {}
10688

10789

10890
def admin_required(view_func):
10991
@wraps(view_func)
11092
@jwt_required
11193
def _wrapped_view(request, *args, **kwargs):
112-
if not is_admin(request):
94+
user_id = getattr(request, "auth_payload", {}).get("sub")
95+
if not user_id or _get_user_metadata(user_id).get("role") != "admin":
11396
return JsonResponse({'detail': 'Admin only'}, status=403)
11497
return view_func(request, *args, **kwargs)
11598

backend/apps/events/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ class EventSubmission(models.Model):
156156
max_length=20, choices=STATUS_CHOICES, default="pending", db_index=True
157157
)
158158
submitted_by = models.CharField(
159-
max_length=255, null=True, blank=True, help_text="Clerk user ID who submitted this event"
159+
max_length=255, help_text="Clerk user ID who submitted this event"
160160
)
161161

162162
# Timestamps

backend/apps/events/views.py

Lines changed: 50 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -508,14 +508,10 @@ def submit_event(request):
508508
cleaned["status"] = "PENDING"
509509
event = Events.objects.create(**cleaned)
510510

511-
# Attempt to capture submitting user if available (optional for anonymous)
512-
clerk_user_id = None
513-
try:
514-
clerk_user_id = (
515-
getattr(request, "user", None) or {}
516-
).get("id")
517-
except Exception:
518-
clerk_user_id = None
511+
user_info = getattr(request, "auth_payload", {})
512+
clerk_user_id = user_info.get("sub")
513+
if not clerk_user_id:
514+
return Response({"error": "User authentication required"}, status=status.HTTP_401_UNAUTHORIZED)
519515

520516
submission = EventSubmission.objects.create(
521517
screenshot_url=screenshot_url,
@@ -537,24 +533,29 @@ def submit_event(request):
537533

538534
@api_view(["GET"])
539535
@ratelimit(key="ip", rate="100/hr", block=True)
536+
@jwt_required
540537
@admin_required
541538
def get_submissions(request):
542539
try:
543-
print('request', request.user)
544-
submissions = EventSubmission.objects.all().order_by("-submitted_at")
545-
data = [
546-
{
540+
user_info = getattr(request, "auth_payload", {})
541+
email_from_request = user_info.get("email_addresses", [{}])[0].get("email_address", None)
542+
543+
submissions = EventSubmission.objects.select_related("created_event").all().order_by("-submitted_at")
544+
data = []
545+
for s in submissions:
546+
data.append({
547547
"id": s.id,
548548
"screenshot_url": s.screenshot_url,
549549
"source_url": s.source_url,
550550
"status": s.status,
551551
"submitted_by": s.submitted_by,
552+
"submitted_by_email": email_from_request,
552553
"submitted_at": s.submitted_at,
553554
"extracted_data": s.extracted_data,
554555
"event_id": s.created_event_id,
555-
}
556-
for s in submissions
557-
]
556+
"event_title": s.created_event.title if s.created_event else None,
557+
"admin_notes": s.admin_notes,
558+
})
558559
return Response(data)
559560

560561
except Exception as e:
@@ -603,56 +604,43 @@ def process_submission(request, submission_id):
603604
@admin_required
604605
@ratelimit(key="ip", rate="100/hr", block=True)
605606
def review_submission(request, submission_id):
606-
"""Approve, reject, or edit submission"""
607-
try:
608-
submission = get_object_or_404(EventSubmission, id=submission_id)
609-
action = request.data.get("action") # 'approve', 'reject', 'edit'
610-
611-
if action == "approve":
612-
# Ensure event exists (it should from submission time)
613-
event = submission.created_event
614-
if not event:
615-
return Response({"error": "No linked event to approve"}, status=status.HTTP_400_BAD_REQUEST)
616-
617-
submission.status = "approved"
618-
submission.reviewed_at = timezone.now()
619-
submission.reviewed_by = request.user.get('email_addresses', [{}])[0].get('email_address')
620-
submission.save()
621-
622-
return Response(
623-
{"message": "Event approved", "event_id": event.id}
624-
)
625-
626-
elif action == "reject":
627-
submission.status = "rejected"
628-
submission.reviewed_at = timezone.now()
629-
submission.reviewed_by = request.user.get('email_addresses', [{}])[0].get('email_address')
630-
submission.admin_notes = request.data.get("notes", "")
631-
submission.save()
632-
633-
return Response({"message": "Event rejected"})
634-
635-
elif action == "edit":
636-
# Update event data and extracted_data
637-
event_data = request.data.get("event_data") or {}
638-
submission.extracted_data = event_data
639-
submission.save()
640-
607+
"""Approve or reject submission."""
608+
submission = get_object_or_404(EventSubmission, id=submission_id)
609+
action = request.data.get("action")
610+
reviewer_id = getattr(request, "auth_payload", {}).get("sub")
611+
612+
if action == "approve":
613+
if not submission.created_event:
614+
return Response({"error": "No linked event to approve"}, status=status.HTTP_400_BAD_REQUEST)
615+
616+
# Get edited extracted_data if provided, otherwise use existing
617+
edited_data = request.data.get("extracted_data")
618+
if edited_data:
619+
submission.extracted_data = edited_data
620+
621+
# Update the linked event with the edited data
641622
event = submission.created_event
642-
if event and isinstance(event_data, dict):
623+
if event and isinstance(edited_data, dict):
643624
for field in [f.name for f in Events._meta.get_fields()]:
644-
if field in event_data:
645-
setattr(event, field, event_data[field])
625+
if field in edited_data:
626+
setattr(event, field, edited_data[field])
646627
event.save()
647-
648-
return Response({"message": "Event data updated"})
649-
650-
return Response(
651-
{"error": "Invalid action"}, status=status.HTTP_400_BAD_REQUEST
652-
)
653-
654-
except Exception as e:
655-
return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
628+
629+
submission.status = "approved"
630+
submission.reviewed_at = timezone.now()
631+
submission.reviewed_by = reviewer_id
632+
submission.save()
633+
return Response({"message": "Event approved", "event_id": submission.created_event.id})
634+
635+
elif action == "reject":
636+
submission.status = "rejected"
637+
submission.reviewed_at = timezone.now()
638+
submission.reviewed_by = reviewer_id
639+
submission.admin_notes = request.data.get("admin_notes", "")
640+
submission.save()
641+
return Response({"message": "Event rejected"})
642+
643+
return Response({"error": "Invalid action. Use 'approve' or 'reject'"}, status=status.HTTP_400_BAD_REQUEST)
656644

657645

658646
@api_view(["GET"])

backend/config/settings/base.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -232,19 +232,19 @@
232232
}
233233

234234
# Database query logging (only in DEBUG mode)
235-
if DEBUG:
236-
LOGGING = {
237-
"version": 1,
238-
"disable_existing_loggers": False,
239-
"handlers": {
240-
"console": {
241-
"class": "logging.StreamHandler",
242-
},
243-
},
244-
"loggers": {
245-
"django.db.backends": {
246-
"handlers": ["console"],
247-
"level": "DEBUG", # Shows all SQL queries
248-
},
249-
},
250-
}
235+
# if DEBUG:
236+
# LOGGING = {
237+
# "version": 1,
238+
# "disable_existing_loggers": False,
239+
# "handlers": {
240+
# "console": {
241+
# "class": "logging.StreamHandler",
242+
# },
243+
# },
244+
# "loggers": {
245+
# "django.db.backends": {
246+
# "handlers": ["console"],
247+
# "level": "DEBUG", # Shows all SQL queries
248+
# },
249+
# },
250+
# }

0 commit comments

Comments
 (0)