From d9242c4b247a14c693691225dec2532cfcd8c323 Mon Sep 17 00:00:00 2001 From: thevoid12 Date: Thu, 16 Oct 2025 00:21:52 +0530 Subject: [PATCH 1/3] feat: added admin filtering for event_field,events,message_template and qr_codes along will all the api changes, hardcoded cleark jwks url is moved to env --- backend/.env.example | 1 + backend/app/api/event_fields.py | 13 +- backend/app/api/events.py | 22 +- backend/app/api/message_templates.py | 10 +- backend/app/api/qr_codes.py | 8 +- backend/app/core/auth.py | 5 +- backend/app/core/schema_manager.py | 10 +- backend/app/models/branding.py | 2 +- backend/app/models/message_template.py | 1 + backend/app/schemas/event.py | 2 + backend/app/schemas/qr_code.py | 1 + backend/app/services/event_service.py | 159 ++++++++++---- .../app/services/message_template_service.py | 46 ++-- backend/app/services/qr_service.py | 27 ++- backend/tests/test_events_api.py | 1 + docs/API.md | 200 ++++++++++++++++-- docs/SETUP.md | 4 +- 17 files changed, 397 insertions(+), 115 deletions(-) diff --git a/backend/.env.example b/backend/.env.example index a17b1a4..9db5cf3 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -7,3 +7,4 @@ IS_LOCAL=false # Clerk Authentication CLERK_SECRET_KEY=sk_test_your_secret_key_here +CLERK_JWKS_URL=https://your-app-domain.clerk.accounts.dev/.well-known/jwks.json diff --git a/backend/app/api/event_fields.py b/backend/app/api/event_fields.py index 78a79f9..4852892 100644 --- a/backend/app/api/event_fields.py +++ b/backend/app/api/event_fields.py @@ -8,10 +8,13 @@ @router.get("/", response_model=List[EventFieldResponse]) -async def get_event_fields(event_id: str): +async def get_event_fields( + event_id: str, + auth: AuthenticatedUser = Depends(clerk_auth) +): """Get all fields for an event""" try: - event = await EventService.get_event(event_id) + event = await EventService.get_event(event_id, auth) if not event: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, @@ -35,7 +38,7 @@ async def update_event_fields( ): """Replace all fields for an event (protected)""" try: - updated_fields = await EventService.update_event_fields(event_id, fields) + updated_fields = await EventService.update_event_fields(event_id, fields, auth) if updated_fields is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, @@ -59,7 +62,7 @@ async def add_event_field( ): """Add a new field to an event (protected)""" try: - new_field = await EventService.add_event_field(event_id, field) + new_field = await EventService.add_event_field(event_id, field, auth) if not new_field: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, @@ -83,7 +86,7 @@ async def delete_event_field( ): """Delete a field from an event (protected)""" try: - success = await EventService.delete_event_field(event_id, field_id) + success = await EventService.delete_event_field(event_id, field_id, auth) if not success: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, diff --git a/backend/app/api/events.py b/backend/app/api/events.py index 8203b80..66c28a1 100644 --- a/backend/app/api/events.py +++ b/backend/app/api/events.py @@ -14,7 +14,7 @@ async def create_event( ): """Create a new event (protected)""" try: - return await EventService.create_event(event) + return await EventService.create_event(event, auth) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, @@ -26,9 +26,9 @@ async def create_event( async def get_all_events( auth: AuthenticatedUser = Depends(clerk_auth) ): - """Get all events (protected)""" + """Get all events for the authenticated admin (protected)""" try: - return await EventService.get_all_events() + return await EventService.get_all_events(auth) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, @@ -38,9 +38,9 @@ async def get_all_events( @router.get("/active", response_model=EventResponse) async def get_active_event(): - """Get currently active event""" + """Get currently active event for the authenticated admin (protected)""" try: - event = await EventService.get_active_event() + event = await EventService.get_active_events() if not event: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, @@ -63,7 +63,7 @@ async def get_event( ): """Get event by ID (protected)""" try: - event = await EventService.get_event(event_id) + event = await EventService.get_event(event_id, auth) if not event: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, @@ -87,7 +87,7 @@ async def update_event( ): """Update event (protected)""" try: - updated_event = await EventService.update_event(event_id, event) + updated_event = await EventService.update_event(event_id, event, auth) if not updated_event: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, @@ -110,7 +110,7 @@ async def toggle_event_status( ): """Toggle event active status (protected)""" try: - event = await EventService.toggle_event_status(event_id) + event = await EventService.toggle_event_status(event_id, auth) if not event: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, @@ -134,7 +134,7 @@ async def clone_event( ): """Clone an existing event (protected)""" try: - event = await EventService.clone_event(event_id, new_name) + event = await EventService.clone_event(event_id, new_name, auth) if not event: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, @@ -157,7 +157,7 @@ async def delete_event( ): """Delete event (protected)""" try: - await EventService.delete_event(event_id) + await EventService.delete_event(event_id, auth) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, @@ -172,7 +172,7 @@ async def get_event_registrations( ): """Get all registrations for an event (protected)""" try: - return await EventService.get_event_registrations(event_id) + return await EventService.get_event_registrations(event_id, auth) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, diff --git a/backend/app/api/message_templates.py b/backend/app/api/message_templates.py index 3bb7d4a..a70c1f4 100644 --- a/backend/app/api/message_templates.py +++ b/backend/app/api/message_templates.py @@ -14,7 +14,7 @@ async def create_template( ): """Create a new message template (protected)""" try: - return await message_template_service.create_template(template_data) + return await message_template_service.create_template(template_data, auth) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @@ -25,7 +25,7 @@ async def get_all_templates( ): """Get all message templates (protected)""" try: - return await message_template_service.get_all_templates() + return await message_template_service.get_all_templates(auth) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @@ -36,7 +36,7 @@ async def get_template( auth: AuthenticatedUser = Depends(clerk_auth) ): """Get a specific message template (protected)""" - template = await message_template_service.get_template(template_id) + template = await message_template_service.get_template(template_id, auth) if not template: raise HTTPException(status_code=404, detail="Template not found") return template @@ -49,7 +49,7 @@ async def update_template( auth: AuthenticatedUser = Depends(clerk_auth) ): """Update a message template (protected)""" - template = await message_template_service.update_template(template_id, template_data) + template = await message_template_service.update_template(template_id, template_data, auth) if not template: raise HTTPException(status_code=404, detail="Template not found") return template @@ -62,7 +62,7 @@ async def delete_template( ): """Delete a message template (protected)""" try: - await message_template_service.delete_template(template_id) + await message_template_service.delete_template(template_id, auth) return {"message": "Template deleted successfully"} except Exception as e: raise HTTPException(status_code=500, detail=str(e)) diff --git a/backend/app/api/qr_codes.py b/backend/app/api/qr_codes.py index f9fdf93..486fc7c 100644 --- a/backend/app/api/qr_codes.py +++ b/backend/app/api/qr_codes.py @@ -14,7 +14,7 @@ async def create_qr_code( ): """Create a QR code for an event (protected)""" try: - return await QRService.create_qr_code(qr_data) + return await QRService.create_qr_code(qr_data, auth) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, @@ -29,7 +29,7 @@ async def get_qr_code( ): """Get QR code details (protected)""" try: - qr_code = await QRService.get_qr_code(qr_id) + qr_code = await QRService.get_qr_code(qr_id, auth) if not qr_code: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, @@ -52,7 +52,7 @@ async def get_event_qr_codes( ): """Get all QR codes for an event (protected)""" try: - return await QRService.get_event_qr_codes(event_id) + return await QRService.get_event_qr_codes(event_id, auth) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, @@ -67,7 +67,7 @@ async def delete_qr_code( ): """Delete QR code (protected)""" try: - await QRService.delete_qr_code(qr_id) + await QRService.delete_qr_code(qr_id, auth) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, diff --git a/backend/app/core/auth.py b/backend/app/core/auth.py index 81cd0ce..2862496 100644 --- a/backend/app/core/auth.py +++ b/backend/app/core/auth.py @@ -25,7 +25,10 @@ logger.info(f"✅ CLERK_SECRET_KEY loaded: {CLERK_SECRET_KEY[:20]}...") # JWKS URL for Clerk -JWKS_URL = "https://steady-hawk-55.clerk.accounts.dev/.well-known/jwks.json" +JWKS_URL = os.getenv("CLERK_JWKS_URL") +if not JWKS_URL: + raise ValueError("CLERK_JWKS_URL environment variable is required") + logger.info(f"🔑 Using JWKS URL: {JWKS_URL}") # Initialize PyJWKClient with SSL verification disabled for localhost diff --git a/backend/app/core/schema_manager.py b/backend/app/core/schema_manager.py index d3d63f4..24f31a0 100644 --- a/backend/app/core/schema_manager.py +++ b/backend/app/core/schema_manager.py @@ -60,11 +60,13 @@ def _define_schema(self) -> Dict[str, Table]: Column('venue_address', 'TEXT', nullable=True), Column('venue_map_link', 'TEXT', nullable=True), Column('is_active', 'INTEGER', nullable=True, default='0'), + Column('admin_user_id', 'TEXT', nullable=False), Column('created_at', 'TEXT', nullable=True, default='CURRENT_TIMESTAMP'), Column('updated_at', 'TEXT', nullable=True, default='CURRENT_TIMESTAMP'), ], indexes=[ - Index('idx_events_active', 'events', ['is_active']) + Index('idx_events_active', 'events', ['is_active']), + Index('idx_events_admin', 'events', ['admin_user_id']) ] ), @@ -79,6 +81,7 @@ def _define_schema(self) -> Dict[str, Table]: Column('is_required', 'INTEGER', nullable=True, default='0'), Column('field_options', 'TEXT', nullable=True), Column('field_order', 'INTEGER', nullable=True, default='0'), + Column('admin_user_id', 'TEXT', nullable=False) ] ), @@ -121,6 +124,7 @@ def _define_schema(self) -> Dict[str, Table]: Column('event_id', 'TEXT', nullable=False, foreign_key='events(id) ON DELETE CASCADE'), Column('message', 'TEXT', nullable=False), Column('qr_type', 'TEXT', nullable=True, default="'message'"), + Column('admin_user_id', 'TEXT', nullable=False), Column('created_at', 'TEXT', nullable=True, default='CURRENT_TIMESTAMP'), ] ), @@ -144,11 +148,13 @@ def _define_schema(self) -> Dict[str, Table]: Column('id', 'TEXT', nullable=False, primary_key=True), Column('template_name', 'TEXT', nullable=False), Column('template_text', 'TEXT', nullable=False), + Column('admin_user_id', 'TEXT', nullable=False), Column('created_at', 'TEXT', nullable=True, default='CURRENT_TIMESTAMP'), Column('updated_at', 'TEXT', nullable=True, default='CURRENT_TIMESTAMP'), ], indexes=[ - Index('idx_message_templates_name', 'message_templates', ['template_name']) + Index('idx_message_templates_name', 'message_templates', ['template_name']), + Index('idx_message_templates_admin', 'message_templates', ['admin_user_id']) ] ), diff --git a/backend/app/models/branding.py b/backend/app/models/branding.py index 8b7de38..9a9d1ad 100644 --- a/backend/app/models/branding.py +++ b/backend/app/models/branding.py @@ -17,4 +17,4 @@ class BrandingUpdate(BaseModel): site_headline: Optional[str] = None logo_url: Optional[str] = None text_style: Optional[str] = None - theme: Optional[str] = None + theme: Optional[str] = None \ No newline at end of file diff --git a/backend/app/models/message_template.py b/backend/app/models/message_template.py index 0756626..33399c8 100644 --- a/backend/app/models/message_template.py +++ b/backend/app/models/message_template.py @@ -19,6 +19,7 @@ class MessageTemplateUpdate(BaseModel): class MessageTemplate(MessageTemplateBase): id: str + admin_user_id: str created_at: str updated_at: str variables: List[str] = Field(default_factory=list, description="Extracted variables from template") diff --git a/backend/app/schemas/event.py b/backend/app/schemas/event.py index c98f3d6..a51cdf7 100644 --- a/backend/app/schemas/event.py +++ b/backend/app/schemas/event.py @@ -19,6 +19,7 @@ class EventFieldResponse(EventFieldCreate): id: str event_id: str + admin_user_id:str #this can be useful in the future class EventCreate(BaseModel): @@ -60,6 +61,7 @@ class EventResponse(BaseModel): venue_address: Optional[str] venue_map_link: Optional[str] is_active: bool + admin_user_id:str created_at: str updated_at: str fields: List[EventFieldResponse] = [] diff --git a/backend/app/schemas/qr_code.py b/backend/app/schemas/qr_code.py index 0f6bd04..e5b93ab 100644 --- a/backend/app/schemas/qr_code.py +++ b/backend/app/schemas/qr_code.py @@ -17,5 +17,6 @@ class QRCodeResponse(BaseModel): event_id: str message: str qr_type: str + admin_user_id: str qr_image: str # Base64 encoded image created_at: str diff --git a/backend/app/services/event_service.py b/backend/app/services/event_service.py index b739fab..5ca66a1 100644 --- a/backend/app/services/event_service.py +++ b/backend/app/services/event_service.py @@ -2,6 +2,7 @@ import json from typing import List, Optional from app.core.database import db +from app.core.auth import AuthenticatedUser from app.schemas.event import ( EventCreate, EventUpdate, @@ -14,7 +15,7 @@ class EventService: """Service for event management""" @staticmethod - async def create_event(event_data: EventCreate) -> EventResponse: + async def create_event(event_data: EventCreate, auth: AuthenticatedUser) -> EventResponse: """Create a new event""" event_id = str(uuid.uuid4()) @@ -22,8 +23,8 @@ async def create_event(event_data: EventCreate) -> EventResponse: await db.execute( """ INSERT INTO events (id, name, description, date, time, venue, - venue_address, venue_map_link, is_active) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + venue_address, venue_map_link, is_active, admin_user_id) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, [ event_id, @@ -35,6 +36,7 @@ async def create_event(event_data: EventCreate) -> EventResponse: event_data.venue_address, event_data.venue_map_link, 1 if event_data.is_active else 0, + auth.user_id, ], ) @@ -44,8 +46,8 @@ async def create_event(event_data: EventCreate) -> EventResponse: await db.execute( """ INSERT INTO event_fields (id, event_id, field_name, field_type, - field_label, is_required, field_options, field_order) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) + field_label, is_required, field_options, field_order, admin_user_id) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) """, [ field_id, @@ -56,14 +58,59 @@ async def create_event(event_data: EventCreate) -> EventResponse: 1 if field.is_required else 0, field.field_options, field.field_order, + auth.user_id, ], ) - return await EventService.get_event(event_id) + return await EventService.get_event(event_id, auth) @staticmethod - async def get_event(event_id: str) -> Optional[EventResponse]: + async def get_event(event_id: str, auth: AuthenticatedUser) -> Optional[EventResponse]: """Get event by ID""" + event = await db.fetch_one("SELECT * FROM events WHERE id = ? AND admin_user_id = ?", [event_id, auth.user_id]) + if not event: + return None + + # Get event fields + fields_rows = await db.fetch_all( + "SELECT * FROM event_fields WHERE event_id = ? ORDER BY field_order", + [event_id], + ) + + fields = [ + EventFieldResponse( + id=row["id"], + event_id=row["event_id"], + field_name=row["field_name"], + field_type=row["field_type"], + field_label=row["field_label"], + is_required=bool(row["is_required"]), + field_options=row["field_options"], + field_order=row["field_order"], + admin_user_id=row["admin_user_id"], + ) + for row in fields_rows + ] + + return EventResponse( + id=event["id"], + name=event["name"], + description=event["description"], + date=event["date"], + time=event["time"], + venue=event["venue"], + venue_address=event["venue_address"], + venue_map_link=event["venue_map_link"], + is_active=bool(event["is_active"]), + admin_user_id=event["admin_user_id"], + created_at=event["created_at"], + updated_at=event["updated_at"], + fields=fields, + ) + + @staticmethod + async def get_event_without_admin_id(event_id: str) -> Optional[EventResponse]: + event = await db.fetch_one("SELECT * FROM events WHERE id = ?", [event_id]) if not event: return None @@ -84,6 +131,7 @@ async def get_event(event_id: str) -> Optional[EventResponse]: is_required=bool(row["is_required"]), field_options=row["field_options"], field_order=row["field_order"], + admin_user_id=row["admin_user_id"], ) for row in fields_rows ] @@ -98,34 +146,52 @@ async def get_event(event_id: str) -> Optional[EventResponse]: venue_address=event["venue_address"], venue_map_link=event["venue_map_link"], is_active=bool(event["is_active"]), + admin_user_id=event["admin_user_id"], created_at=event["created_at"], updated_at=event["updated_at"], fields=fields, ) @staticmethod - async def get_all_events() -> List[EventResponse]: - """Get all events""" - events = await db.fetch_all("SELECT * FROM events ORDER BY created_at DESC") + async def get_all_events(auth: AuthenticatedUser) -> List[EventResponse]: + """Get all events for a specific admin""" + events = await db.fetch_all( + "SELECT * FROM events WHERE admin_user_id = ? ORDER BY created_at DESC", + [auth.user_id] + ) result = [] for event in events: - event_response = await EventService.get_event(event["id"]) + event_response = await EventService.get_event(event["id"], auth) if event_response: result.append(event_response) return result + + @staticmethod - async def get_active_event() -> Optional[EventResponse]: - """Get currently active event""" - event = await db.fetch_one( - "SELECT * FROM events WHERE is_active = 1 ORDER BY created_at DESC LIMIT 1" + async def get_active_events() -> List[EventResponse]: + """Get all active events for a specific admin""" + event = await db.fetch_all( + "SELECT * FROM events WHERE is_active = 1 ORDER BY created_at DESC LIMIT 1", ) if not event: return None - return await EventService.get_event(event["id"]) + return await EventService.get_event_without_admin_id(event[0]["id"]) + # we are not using now but will come handy @staticmethod - async def update_event(event_id: str, event_data: EventUpdate) -> Optional[EventResponse]: + async def get_active_events_by_admin_id(auth: AuthenticatedUser) -> List[EventResponse]: + """Get all active events for a specific admin""" + event = await db.fetch_all( + "SELECT * FROM events WHERE admin_user_id = ? AND is_active = 1 ORDER BY created_at DESC LIMIT 1", + [auth.user_id] + ) + if not event: + return None + return await EventService.get_event(event[0]["id"], auth) + + @staticmethod + async def update_event(event_id: str, event_data: EventUpdate, auth: AuthenticatedUser) -> Optional[EventResponse]: """Update event""" # Build update query dynamically update_fields = [] @@ -159,38 +225,39 @@ async def update_event(event_id: str, event_data: EventUpdate) -> Optional[Event if update_fields: update_fields.append("updated_at = CURRENT_TIMESTAMP") params.append(event_id) - query = f"UPDATE events SET {', '.join(update_fields)} WHERE id = ?" + params.append(auth.user_id) + query = f"UPDATE events SET {', '.join(update_fields)} WHERE id = ? AND admin_user_id = ?" await db.execute(query, params) - return await EventService.get_event(event_id) + return await EventService.get_event(event_id, auth) @staticmethod - async def toggle_event_status(event_id: str) -> Optional[EventResponse]: + async def toggle_event_status(event_id: str, auth: AuthenticatedUser) -> Optional[EventResponse]: """Toggle event active status""" - event = await db.fetch_one("SELECT is_active FROM events WHERE id = ?", [event_id]) + event = await db.fetch_one("SELECT is_active FROM events WHERE id = ? AND admin_user_id = ?", [event_id, auth.user_id]) if not event: return None new_status = 0 if event["is_active"] else 1 - # If activating, deactivate all other events + # If activating, deactivate all other events for this admin if new_status == 1: - await db.execute("UPDATE events SET is_active = 0 WHERE id != ?", [event_id]) + await db.execute("UPDATE events SET is_active = 0 WHERE id != ? AND admin_user_id = ?", [event_id, auth.user_id]) - await db.execute("UPDATE events SET is_active = ? WHERE id = ?", [new_status, event_id]) + await db.execute("UPDATE events SET is_active = ? WHERE id = ? AND admin_user_id = ?", [new_status, event_id, auth.user_id]) - return await EventService.get_event(event_id) + return await EventService.get_event(event_id, auth) @staticmethod - async def delete_event(event_id: str) -> bool: + async def delete_event(event_id: str, auth: AuthenticatedUser) -> bool: """Delete event""" - await db.execute("DELETE FROM events WHERE id = ?", [event_id]) + await db.execute("DELETE FROM events WHERE id = ? AND admin_user_id = ?", [event_id, auth.user_id]) return True @staticmethod - async def clone_event(event_id: str, new_name: str) -> Optional[EventResponse]: + async def clone_event(event_id: str, new_name: str, auth: AuthenticatedUser) -> Optional[EventResponse]: """Clone an existing event""" - source_event = await EventService.get_event(event_id) + source_event = await EventService.get_event(event_id, auth) if not source_event: return None @@ -217,11 +284,16 @@ async def clone_event(event_id: str, new_name: str) -> Optional[EventResponse]: ], ) - return await EventService.create_event(new_event_data) + return await EventService.create_event(new_event_data, auth) @staticmethod - async def get_event_registrations(event_id: str) -> List[dict]: + async def get_event_registrations(event_id: str, auth: AuthenticatedUser) -> List[dict]: """Get all registrations for an event""" + # First verify the event belongs to the admin + event = await db.fetch_one("SELECT id FROM events WHERE id = ? AND admin_user_id = ?", [event_id, auth.user_id]) + if not event: + return [] + registrations = await db.fetch_all( "SELECT * FROM registrations WHERE event_id = ? ORDER BY created_at DESC", [event_id], @@ -241,7 +313,7 @@ async def get_event_registrations(event_id: str) -> List[dict]: ] @staticmethod - async def update_event_fields(event_id: str, fields: List) -> Optional[List[EventFieldResponse]]: + async def update_event_fields(event_id: str, fields: List, auth: AuthenticatedUser) -> Optional[List[EventFieldResponse]]: """Replace all fields for an event""" event = await db.fetch_one("SELECT id FROM events WHERE id = ?", [event_id]) if not event: @@ -256,8 +328,8 @@ async def update_event_fields(event_id: str, fields: List) -> Optional[List[Even await db.execute( """ INSERT INTO event_fields (id, event_id, field_name, field_type, - field_label, is_required, field_options, field_order) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) + field_label, is_required, field_options, field_order, admin_user_id) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) """, [ field_id, @@ -268,17 +340,18 @@ async def update_event_fields(event_id: str, fields: List) -> Optional[List[Even 1 if field.is_required else 0, field.field_options, field.field_order, + auth.user_id, ], ) # Return updated fields - event = await EventService.get_event(event_id) + event = await EventService.get_event(event_id, auth) return event.fields if event else [] @staticmethod - async def add_event_field(event_id: str, field) -> Optional[EventFieldResponse]: + async def add_event_field(event_id: str, field, auth: AuthenticatedUser) -> Optional[EventFieldResponse]: """Add a single field to an event""" - event = await db.fetch_one("SELECT id FROM events WHERE id = ?", [event_id]) + event = await db.fetch_one("SELECT id FROM events WHERE id = ? AND admin_user_id = ?", [event_id, auth.user_id]) if not event: return None @@ -294,8 +367,8 @@ async def add_event_field(event_id: str, field) -> Optional[EventFieldResponse]: await db.execute( """ INSERT INTO event_fields (id, event_id, field_name, field_type, - field_label, is_required, field_options, field_order) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) + field_label, is_required, field_options, field_order, admin_user_id) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) """, [ field_id, @@ -306,6 +379,7 @@ async def add_event_field(event_id: str, field) -> Optional[EventFieldResponse]: 1 if field.is_required else 0, field.field_options, next_order, + auth.user_id, ], ) @@ -318,13 +392,14 @@ async def add_event_field(event_id: str, field) -> Optional[EventFieldResponse]: is_required=field.is_required, field_options=field.field_options, field_order=next_order, + admin_user_id=auth.user_id, ) @staticmethod - async def delete_event_field(event_id: str, field_id: str) -> bool: + async def delete_event_field(event_id: str, field_id: str, auth: AuthenticatedUser) -> bool: """Delete a field from an event""" result = await db.execute( - "DELETE FROM event_fields WHERE id = ? AND event_id = ?", - [field_id, event_id] + "DELETE FROM event_fields WHERE id = ? AND event_id = ? AND admin_user_id = ?", + [field_id, event_id, auth.user_id] ) return True diff --git a/backend/app/services/message_template_service.py b/backend/app/services/message_template_service.py index b6b8ef8..804a36f 100644 --- a/backend/app/services/message_template_service.py +++ b/backend/app/services/message_template_service.py @@ -2,6 +2,7 @@ import re from typing import List, Optional from app.core.database import db +from app.core.auth import AuthenticatedUser from app.models.message_template import MessageTemplate, MessageTemplateCreate, MessageTemplateUpdate @@ -23,32 +24,34 @@ def substitute_variables(template_text: str, variables: dict) -> str: result = result.replace(f"{{{{{var_name}}}}}", str(var_value)) return result - async def create_template(self, template_data: MessageTemplateCreate) -> MessageTemplate: + async def create_template(self, template_data: MessageTemplateCreate, auth) -> MessageTemplate: """Create a new message template""" template_id = str(uuid.uuid4()) query = """ - INSERT INTO message_templates (id, template_name, template_text) - VALUES (?, ?, ?) + INSERT INTO message_templates (id, template_name, template_text, admin_user_id) + VALUES (?, ?, ?, ?) """ await db.execute(query, [ template_id, template_data.template_name, - template_data.template_text + template_data.template_text, + auth.user_id ]) - return await self.get_template(template_id) + return await self.get_template(template_id, auth) - async def get_all_templates(self) -> List[MessageTemplate]: - """Get all message templates""" + async def get_all_templates(self, auth: AuthenticatedUser) -> List[MessageTemplate]: + """Get all message templates for the authenticated admin""" query = """ - SELECT id, template_name, template_text, created_at, updated_at + SELECT id, template_name, template_text, admin_user_id, created_at, updated_at FROM message_templates + WHERE admin_user_id = ? ORDER BY template_name ASC """ - rows = await db.fetch_all(query) + rows = await db.fetch_all(query, [auth.user_id]) templates = [] for row in rows: @@ -57,6 +60,7 @@ async def get_all_templates(self) -> List[MessageTemplate]: id=row['id'], template_name=row['template_name'], template_text=row['template_text'], + admin_user_id=row['admin_user_id'], created_at=row['created_at'], updated_at=row['updated_at'], variables=variables @@ -64,15 +68,15 @@ async def get_all_templates(self) -> List[MessageTemplate]: return templates - async def get_template(self, template_id: str) -> Optional[MessageTemplate]: + async def get_template(self, template_id: str, auth: AuthenticatedUser) -> Optional[MessageTemplate]: """Get a specific message template""" query = """ - SELECT id, template_name, template_text, created_at, updated_at + SELECT id, template_name, template_text, admin_user_id, created_at, updated_at FROM message_templates - WHERE id = ? + WHERE id = ? AND admin_user_id = ? """ - row = await db.fetch_one(query, [template_id]) + row = await db.fetch_one(query, [template_id, auth.user_id]) if not row: return None @@ -82,12 +86,13 @@ async def get_template(self, template_id: str) -> Optional[MessageTemplate]: id=row['id'], template_name=row['template_name'], template_text=row['template_text'], + admin_user_id=row['admin_user_id'], created_at=row['created_at'], updated_at=row['updated_at'], variables=variables ) - async def update_template(self, template_id: str, template_data: MessageTemplateUpdate) -> Optional[MessageTemplate]: + async def update_template(self, template_id: str, template_data: MessageTemplateUpdate, auth: AuthenticatedUser) -> Optional[MessageTemplate]: """Update a message template""" # Build update query dynamically based on provided fields update_fields = [] @@ -102,24 +107,25 @@ async def update_template(self, template_id: str, template_data: MessageTemplate params.append(template_data.template_text) if not update_fields: - return await self.get_template(template_id) + return await self.get_template(template_id, auth) update_fields.append("updated_at = CURRENT_TIMESTAMP") params.append(template_id) + params.append(auth.user_id) query = f""" UPDATE message_templates SET {', '.join(update_fields)} - WHERE id = ? + WHERE id = ? AND admin_user_id = ? """ await db.execute(query, params) - return await self.get_template(template_id) + return await self.get_template(template_id, auth) - async def delete_template(self, template_id: str) -> bool: + async def delete_template(self, template_id: str, auth: AuthenticatedUser) -> bool: """Delete a message template""" - query = "DELETE FROM message_templates WHERE id = ?" - await db.execute(query, [template_id]) + query = "DELETE FROM message_templates WHERE id = ? AND admin_user_id = ?" + await db.execute(query, [template_id, auth.user_id]) return True diff --git a/backend/app/services/qr_service.py b/backend/app/services/qr_service.py index 59e1535..dc60821 100644 --- a/backend/app/services/qr_service.py +++ b/backend/app/services/qr_service.py @@ -3,6 +3,7 @@ import io import base64 from app.core.database import db +from app.core.auth import AuthenticatedUser from app.schemas.qr_code import QRCodeCreate, QRCodeResponse @@ -10,7 +11,7 @@ class QRService: """Service for QR code management""" @staticmethod - async def create_qr_code(qr_data: QRCodeCreate) -> QRCodeResponse: + async def create_qr_code(qr_data: QRCodeCreate, auth) -> QRCodeResponse: """Create a QR code for an event""" qr_id = str(uuid.uuid4()) @@ -38,10 +39,10 @@ async def create_qr_code(qr_data: QRCodeCreate) -> QRCodeResponse: # Save to database await db.execute( """ - INSERT INTO qr_codes (id, event_id, message, qr_type) - VALUES (?, ?, ?, ?) + INSERT INTO qr_codes (id, event_id, message, qr_type, admin_user_id) + VALUES (?, ?, ?, ?, ?) """, - [qr_id, qr_data.event_id, qr_data.message, qr_data.qr_type], + [qr_id, qr_data.event_id, qr_data.message, qr_data.qr_type, auth.user_id], ) qr_code = await db.fetch_one("SELECT * FROM qr_codes WHERE id = ?", [qr_id]) @@ -51,14 +52,15 @@ async def create_qr_code(qr_data: QRCodeCreate) -> QRCodeResponse: event_id=qr_code["event_id"], message=qr_code["message"], qr_type=qr_code["qr_type"], + admin_user_id=qr_code["admin_user_id"], qr_image=img_base64, created_at=qr_code["created_at"], ) @staticmethod - async def get_qr_code(qr_id: str) -> dict: + async def get_qr_code(qr_id: str, auth: AuthenticatedUser) -> dict: """Get QR code details""" - qr_code = await db.fetch_one("SELECT * FROM qr_codes WHERE id = ?", [qr_id]) + qr_code = await db.fetch_one("SELECT * FROM qr_codes WHERE id = ? AND admin_user_id = ?", [qr_id, auth.user_id]) if not qr_code: return None @@ -67,12 +69,18 @@ async def get_qr_code(qr_id: str) -> dict: "event_id": qr_code["event_id"], "message": qr_code["message"], "qr_type": qr_code["qr_type"], + "admin_user_id": qr_code["admin_user_id"], "created_at": qr_code["created_at"], } @staticmethod - async def get_event_qr_codes(event_id: str) -> list: + async def get_event_qr_codes(event_id: str, auth: AuthenticatedUser) -> list: """Get all QR codes for an event""" + # First verify the event belongs to the admin + event = await db.fetch_one("SELECT id FROM events WHERE id = ? AND admin_user_id = ?", [event_id, auth.user_id]) + if not event: + return [] + qr_codes = await db.fetch_all( "SELECT * FROM qr_codes WHERE event_id = ? ORDER BY created_at DESC", [event_id], @@ -84,13 +92,14 @@ async def get_event_qr_codes(event_id: str) -> list: "event_id": qr["event_id"], "message": qr["message"], "qr_type": qr["qr_type"], + "admin_user_id": qr["admin_user_id"], "created_at": qr["created_at"], } for qr in qr_codes ] @staticmethod - async def delete_qr_code(qr_id: str) -> bool: + async def delete_qr_code(qr_id: str, auth: AuthenticatedUser) -> bool: """Delete QR code""" - await db.execute("DELETE FROM qr_codes WHERE id = ?", [qr_id]) + await db.execute("DELETE FROM qr_codes WHERE id = ? AND admin_user_id = ?", [qr_id, auth.user_id]) return True diff --git a/backend/tests/test_events_api.py b/backend/tests/test_events_api.py index 982317d..120b143 100644 --- a/backend/tests/test_events_api.py +++ b/backend/tests/test_events_api.py @@ -15,6 +15,7 @@ def test_create_event(self, client, sample_event_data): assert data["description"] == sample_event_data["description"] assert data["venue"] == sample_event_data["venue"] assert data["time"] == sample_event_data["time"] + assert data["admin_user_id"] == "test_user_12345" # Mock user ID assert "id" in data def test_create_event_with_missing_fields(self, client): diff --git a/docs/API.md b/docs/API.md index f1ee36e..2c45f9e 100644 --- a/docs/API.md +++ b/docs/API.md @@ -9,7 +9,18 @@ Complete API reference for all endpoints with request/response examples. ## Authentication -Currently, no authentication is required (as per requirements). +All admin endpoints require Clerk authentication. Public endpoints (registration forms, QR check-in) do not require authentication. + +**Authentication Headers:** +``` +Authorization: Bearer +``` + +**Protected Endpoints:** All endpoints except: +- `POST /api/registrations/` (public registration) +- `GET /api/events/active/` (public active event view) +- `POST /api/registrations/check-in/{event_id}` (QR check-in) +- `GET /api/registrations/profile/autofill` (auto-fill functionality) --- @@ -33,7 +44,10 @@ GET /api/events/ "venue": "Tech Hub, Building A", "venue_map_link": "https://maps.google.com/...", "is_active": true, - "created_at": "2025-10-01T10:00:00Z" + "admin_user_id": "user_12345", + "created_at": "2025-10-01T10:00:00Z", + "updated_at": "2025-10-01T10:00:00Z", + "fields": [] } ] ``` @@ -50,13 +64,17 @@ GET /api/events/active/ "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Tech Workshop 2025", "is_active": true, + "admin_user_id": "user_12345", "fields": [ { "id": "field-001", - "label": "Full Name", - "type": "text", - "required": true, - "field_order": 0 + "event_id": "550e8400-e29b-41d4-a716-446655440000", + "field_name": "fullname", + "field_type": "text", + "field_label": "Full Name", + "is_required": true, + "field_order": 0, + "admin_user_id": "user_12345" } ] } @@ -101,7 +119,10 @@ POST /api/events/ "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Tech Workshop 2025", "is_active": false, - "created_at": "2025-10-01T10:00:00Z" + "admin_user_id": "user_12345", + "created_at": "2025-10-01T10:00:00Z", + "updated_at": "2025-10-01T10:00:00Z", + "fields": [] } ``` @@ -138,7 +159,11 @@ POST /api/events/{id}/clone?new_name=Cloned Event Name { "id": "new-event-id", "name": "Cloned Event Name", - "is_active": false + "is_active": false, + "admin_user_id": "user_12345", + "created_at": "2025-10-01T10:00:00Z", + "updated_at": "2025-10-01T10:00:00Z", + "fields": [] } ``` @@ -276,8 +301,8 @@ POST /api/qr-codes/ ```json { "event_id": "550e8400-e29b-41d4-a716-446655440000", - "qr_type": "text", - "qr_content": "WiFi Password: SecurePass123" + "message": "WiFi Password: SecurePass123", + "qr_type": "message" } ``` @@ -286,8 +311,8 @@ or ```json { "event_id": "550e8400-e29b-41d4-a716-446655440000", - "qr_type": "url", - "qr_content": "https://example.com/resources" + "message": "https://example.com/resources", + "qr_type": "url" } ``` @@ -296,8 +321,9 @@ or { "id": "qr-code-id", "event_id": "550e8400-e29b-41d4-a716-446655440000", + "message": "WiFi Password: SecurePass123", "qr_type": "text", - "qr_content": "WiFi Password: SecurePass123", + "admin_user_id": "user_12345", "qr_image": "base64_encoded_image_data", "created_at": "2025-10-01T10:00:00Z" } @@ -323,6 +349,131 @@ DELETE /api/qr-codes/{id} --- +## Message Templates API + +### Create Message Template + +```http +POST /api/message-templates/ +``` + +**Request Body**: +```json +{ + "template_name": "Event Reminder", + "template_text": "Hi {{name}}! Don't forget about our event on {{date}} at {{venue}}. See you there! 🎉" +} +``` + +**Response** (201): +```json +{ + "id": "template-id", + "template_name": "Event Reminder", + "template_text": "Hi {{name}}! Don't forget about our event on {{date}} at {{venue}}. See you there! 🎉", + "admin_user_id": "user_12345", + "created_at": "2025-10-01T10:00:00Z", + "updated_at": "2025-10-01T10:00:00Z", + "variables": ["name", "date", "venue"] +} +``` + +### Get All Message Templates + +```http +GET /api/message-templates/ +``` + +**Response** (200): +```json +[ + { + "id": "template-id", + "template_name": "Event Reminder", + "template_text": "Hi {{name}}! Don't forget about our event...", + "admin_user_id": "user_12345", + "created_at": "2025-10-01T10:00:00Z", + "updated_at": "2025-10-01T10:00:00Z", + "variables": ["name", "date", "venue"] + } +] +``` + +### Get Message Template + +```http +GET /api/message-templates/{template_id} +``` + +### Update Message Template + +```http +PUT /api/message-templates/{template_id} +``` + +**Request Body** (partial update): +```json +{ + "template_name": "Updated Reminder", + "template_text": "Hello {{name}}! Reminder for {{event_name}} on {{date}}." +} +``` + +### Delete Message Template + +```http +DELETE /api/message-templates/{template_id} +``` + +**Response** (200): +```json +{ + "message": "Template deleted successfully" +} +``` + +--- + +## Branding API + +### Get Branding Settings + +```http +GET /api/branding/ +``` + +**Response** (200): +```json +{ + "id": "default", + "site_title": "MagPie Events", + "site_headline": "Where Innovation Meets Community", + "logo_url": null, + "text_style": "gradient", + "theme": "default", + "updated_at": "2025-10-01T10:00:00Z" +} +``` + +### Update Branding Settings + +```http +PUT /api/branding/ +``` + +**Request Body** (partial update): +```json +{ + "site_title": "My Event Platform", + "site_headline": "Register for amazing events", + "logo_url": "https://example.com/logo.png", + "text_style": "solid", + "theme": "dark" +} +``` + +--- + ## WhatsApp API ### Send Bulk Messages @@ -335,7 +486,28 @@ POST /api/whatsapp/send-bulk/ ```json { "event_id": "550e8400-e29b-41d4-a716-446655440000", - "message": "Hi! Reminder about our event tomorrow. See you there! 🎉" + "message": "Hi! Reminder about our event tomorrow. See you there! 🎉", + "template_id": null, + "template_variables": null, + "send_to": "all", + "filter_field": null, + "filter_value": null +} +``` + +or using a template: + +```json +{ + "event_id": "550e8400-e29b-41d4-a716-446655440000", + "message": null, + "template_id": "template-id", + "template_variables": { + "name": "John", + "event_name": "Tech Workshop", + "date": "2025-10-15" + }, + "send_to": "all" } ``` diff --git a/docs/SETUP.md b/docs/SETUP.md index 1533f50..a15da69 100644 --- a/docs/SETUP.md +++ b/docs/SETUP.md @@ -75,6 +75,7 @@ FRONTEND_URL=http://localhost:3000 # Authentication (Required - see Authentication Setup section) CLERK_SECRET_KEY=sk_test_your_clerk_secret_key_here +CLERK_JWKS_URL=https://your-app-domain.clerk.accounts.dev/.well-known/jwks.json # WhatsApp Integration (Optional - see WHATSAPP_SETUP.md) TWILIO_ACCOUNT_SID=your_account_sid_here @@ -229,9 +230,10 @@ The dashboard is protected with Clerk authentication. You'll need a Clerk accoun - Create/edit/delete events - View all registrations - Generate QR codes -- Send WhatsApp messages +- Send WhatsApp messages (with templates) - Export CSV data - Configure branding/themes +- Manage message templates **Public Features (No Authentication)**: - Event registration form From 7c8fddb12483cbb22edf03ceff8795f290655339 Mon Sep 17 00:00:00 2001 From: thevoid12 Date: Fri, 17 Oct 2025 13:51:35 +0530 Subject: [PATCH 2/3] refactor: admin dashboard's create and edits can be performed by any admin --- backend/app/api/event_fields.py | 8 +-- backend/app/api/events.py | 14 ++--- backend/app/api/message_templates.py | 8 +-- backend/app/api/qr_codes.py | 6 +- backend/app/services/event_service.py | 56 +++++++++---------- .../app/services/message_template_service.py | 26 ++++----- backend/app/services/qr_service.py | 14 ++--- 7 files changed, 64 insertions(+), 68 deletions(-) diff --git a/backend/app/api/event_fields.py b/backend/app/api/event_fields.py index 4852892..4177a87 100644 --- a/backend/app/api/event_fields.py +++ b/backend/app/api/event_fields.py @@ -14,7 +14,7 @@ async def get_event_fields( ): """Get all fields for an event""" try: - event = await EventService.get_event(event_id, auth) + event = await EventService.get_event(event_id) if not event: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, @@ -38,7 +38,7 @@ async def update_event_fields( ): """Replace all fields for an event (protected)""" try: - updated_fields = await EventService.update_event_fields(event_id, fields, auth) + updated_fields = await EventService.update_event_fields(event_id, fields) if updated_fields is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, @@ -62,7 +62,7 @@ async def add_event_field( ): """Add a new field to an event (protected)""" try: - new_field = await EventService.add_event_field(event_id, field, auth) + new_field = await EventService.add_event_field(event_id, field) if not new_field: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, @@ -86,7 +86,7 @@ async def delete_event_field( ): """Delete a field from an event (protected)""" try: - success = await EventService.delete_event_field(event_id, field_id, auth) + success = await EventService.delete_event_field(event_id, field_id) if not success: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, diff --git a/backend/app/api/events.py b/backend/app/api/events.py index 66c28a1..69aa8a4 100644 --- a/backend/app/api/events.py +++ b/backend/app/api/events.py @@ -28,7 +28,7 @@ async def get_all_events( ): """Get all events for the authenticated admin (protected)""" try: - return await EventService.get_all_events(auth) + return await EventService.get_all_events() except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, @@ -40,7 +40,7 @@ async def get_all_events( async def get_active_event(): """Get currently active event for the authenticated admin (protected)""" try: - event = await EventService.get_active_events() + event = await EventService.get_active_event() if not event: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, @@ -63,7 +63,7 @@ async def get_event( ): """Get event by ID (protected)""" try: - event = await EventService.get_event(event_id, auth) + event = await EventService.get_event(event_id) if not event: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, @@ -87,7 +87,7 @@ async def update_event( ): """Update event (protected)""" try: - updated_event = await EventService.update_event(event_id, event, auth) + updated_event = await EventService.update_event(event_id, event) if not updated_event: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, @@ -110,7 +110,7 @@ async def toggle_event_status( ): """Toggle event active status (protected)""" try: - event = await EventService.toggle_event_status(event_id, auth) + event = await EventService.toggle_event_status(event_id) if not event: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, @@ -157,7 +157,7 @@ async def delete_event( ): """Delete event (protected)""" try: - await EventService.delete_event(event_id, auth) + await EventService.delete_event(event_id) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, @@ -172,7 +172,7 @@ async def get_event_registrations( ): """Get all registrations for an event (protected)""" try: - return await EventService.get_event_registrations(event_id, auth) + return await EventService.get_event_registrations(event_id) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, diff --git a/backend/app/api/message_templates.py b/backend/app/api/message_templates.py index a70c1f4..dfdb781 100644 --- a/backend/app/api/message_templates.py +++ b/backend/app/api/message_templates.py @@ -25,7 +25,7 @@ async def get_all_templates( ): """Get all message templates (protected)""" try: - return await message_template_service.get_all_templates(auth) + return await message_template_service.get_all_templates() except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @@ -36,7 +36,7 @@ async def get_template( auth: AuthenticatedUser = Depends(clerk_auth) ): """Get a specific message template (protected)""" - template = await message_template_service.get_template(template_id, auth) + template = await message_template_service.get_template(template_id) if not template: raise HTTPException(status_code=404, detail="Template not found") return template @@ -49,7 +49,7 @@ async def update_template( auth: AuthenticatedUser = Depends(clerk_auth) ): """Update a message template (protected)""" - template = await message_template_service.update_template(template_id, template_data, auth) + template = await message_template_service.update_template(template_id, template_data) if not template: raise HTTPException(status_code=404, detail="Template not found") return template @@ -62,7 +62,7 @@ async def delete_template( ): """Delete a message template (protected)""" try: - await message_template_service.delete_template(template_id, auth) + await message_template_service.delete_template(template_id ) return {"message": "Template deleted successfully"} except Exception as e: raise HTTPException(status_code=500, detail=str(e)) diff --git a/backend/app/api/qr_codes.py b/backend/app/api/qr_codes.py index 486fc7c..faa9a3a 100644 --- a/backend/app/api/qr_codes.py +++ b/backend/app/api/qr_codes.py @@ -29,7 +29,7 @@ async def get_qr_code( ): """Get QR code details (protected)""" try: - qr_code = await QRService.get_qr_code(qr_id, auth) + qr_code = await QRService.get_qr_code(qr_id) if not qr_code: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, @@ -52,7 +52,7 @@ async def get_event_qr_codes( ): """Get all QR codes for an event (protected)""" try: - return await QRService.get_event_qr_codes(event_id, auth) + return await QRService.get_event_qr_codes(event_id) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, @@ -67,7 +67,7 @@ async def delete_qr_code( ): """Delete QR code (protected)""" try: - await QRService.delete_qr_code(qr_id, auth) + await QRService.delete_qr_code(qr_id) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, diff --git a/backend/app/services/event_service.py b/backend/app/services/event_service.py index 5ca66a1..937a74a 100644 --- a/backend/app/services/event_service.py +++ b/backend/app/services/event_service.py @@ -62,12 +62,12 @@ async def create_event(event_data: EventCreate, auth: AuthenticatedUser) -> Even ], ) - return await EventService.get_event(event_id, auth) + return await EventService.get_event(event_id) @staticmethod - async def get_event(event_id: str, auth: AuthenticatedUser) -> Optional[EventResponse]: + async def get_event(event_id: str) -> Optional[EventResponse]: """Get event by ID""" - event = await db.fetch_one("SELECT * FROM events WHERE id = ? AND admin_user_id = ?", [event_id, auth.user_id]) + event = await db.fetch_one("SELECT * FROM events WHERE id = ?", [event_id]) if not event: return None @@ -153,15 +153,14 @@ async def get_event_without_admin_id(event_id: str) -> Optional[EventResponse]: ) @staticmethod - async def get_all_events(auth: AuthenticatedUser) -> List[EventResponse]: - """Get all events for a specific admin""" + async def get_all_events() -> List[EventResponse]: + """Get all events""" events = await db.fetch_all( - "SELECT * FROM events WHERE admin_user_id = ? ORDER BY created_at DESC", - [auth.user_id] + "SELECT * FROM events ORDER BY created_at DESC" ) result = [] for event in events: - event_response = await EventService.get_event(event["id"], auth) + event_response = await EventService.get_event(event["id"]) if event_response: result.append(event_response) return result @@ -169,7 +168,7 @@ async def get_all_events(auth: AuthenticatedUser) -> List[EventResponse]: @staticmethod - async def get_active_events() -> List[EventResponse]: + async def get_active_event() -> List[EventResponse]: """Get all active events for a specific admin""" event = await db.fetch_all( "SELECT * FROM events WHERE is_active = 1 ORDER BY created_at DESC LIMIT 1", @@ -188,10 +187,10 @@ async def get_active_events_by_admin_id(auth: AuthenticatedUser) -> List[EventRe ) if not event: return None - return await EventService.get_event(event[0]["id"], auth) + return await EventService.get_event(event[0]["id"]) @staticmethod - async def update_event(event_id: str, event_data: EventUpdate, auth: AuthenticatedUser) -> Optional[EventResponse]: + async def update_event(event_id: str, event_data: EventUpdate) -> Optional[EventResponse]: """Update event""" # Build update query dynamically update_fields = [] @@ -225,39 +224,38 @@ async def update_event(event_id: str, event_data: EventUpdate, auth: Authenticat if update_fields: update_fields.append("updated_at = CURRENT_TIMESTAMP") params.append(event_id) - params.append(auth.user_id) - query = f"UPDATE events SET {', '.join(update_fields)} WHERE id = ? AND admin_user_id = ?" + query = f"UPDATE events SET {', '.join(update_fields)} WHERE id = ?" await db.execute(query, params) - return await EventService.get_event(event_id, auth) + return await EventService.get_event(event_id) @staticmethod - async def toggle_event_status(event_id: str, auth: AuthenticatedUser) -> Optional[EventResponse]: + async def toggle_event_status(event_id: str) -> Optional[EventResponse]: """Toggle event active status""" - event = await db.fetch_one("SELECT is_active FROM events WHERE id = ? AND admin_user_id = ?", [event_id, auth.user_id]) + event = await db.fetch_one("SELECT is_active FROM events WHERE id = ?", [event_id]) if not event: return None new_status = 0 if event["is_active"] else 1 - # If activating, deactivate all other events for this admin + # If activating, deactivate all other events if new_status == 1: - await db.execute("UPDATE events SET is_active = 0 WHERE id != ? AND admin_user_id = ?", [event_id, auth.user_id]) + await db.execute("UPDATE events SET is_active = 0 WHERE id != ?", [event_id]) - await db.execute("UPDATE events SET is_active = ? WHERE id = ? AND admin_user_id = ?", [new_status, event_id, auth.user_id]) + await db.execute("UPDATE events SET is_active = ? WHERE id = ?", [new_status, event_id]) - return await EventService.get_event(event_id, auth) + return await EventService.get_event(event_id) @staticmethod - async def delete_event(event_id: str, auth: AuthenticatedUser) -> bool: + async def delete_event(event_id: str) -> bool: """Delete event""" - await db.execute("DELETE FROM events WHERE id = ? AND admin_user_id = ?", [event_id, auth.user_id]) + await db.execute("DELETE FROM events WHERE id = ?", [event_id]) return True @staticmethod async def clone_event(event_id: str, new_name: str, auth: AuthenticatedUser) -> Optional[EventResponse]: """Clone an existing event""" - source_event = await EventService.get_event(event_id, auth) + source_event = await EventService.get_event(event_id) if not source_event: return None @@ -287,10 +285,10 @@ async def clone_event(event_id: str, new_name: str, auth: AuthenticatedUser) -> return await EventService.create_event(new_event_data, auth) @staticmethod - async def get_event_registrations(event_id: str, auth: AuthenticatedUser) -> List[dict]: + async def get_event_registrations(event_id: str) -> List[dict]: """Get all registrations for an event""" - # First verify the event belongs to the admin - event = await db.fetch_one("SELECT id FROM events WHERE id = ? AND admin_user_id = ?", [event_id, auth.user_id]) + # First verify the event exists + event = await db.fetch_one("SELECT id FROM events WHERE id = ?", [event_id]) if not event: return [] @@ -345,13 +343,13 @@ async def update_event_fields(event_id: str, fields: List, auth: AuthenticatedUs ) # Return updated fields - event = await EventService.get_event(event_id, auth) + event = await EventService.get_event(event_id) return event.fields if event else [] @staticmethod - async def add_event_field(event_id: str, field, auth: AuthenticatedUser) -> Optional[EventFieldResponse]: + async def add_event_field(event_id: str, field) -> Optional[EventFieldResponse]: """Add a single field to an event""" - event = await db.fetch_one("SELECT id FROM events WHERE id = ? AND admin_user_id = ?", [event_id, auth.user_id]) + event = await db.fetch_one("SELECT id FROM events WHERE id = ?", [event_id]) if not event: return None diff --git a/backend/app/services/message_template_service.py b/backend/app/services/message_template_service.py index 804a36f..5e72d72 100644 --- a/backend/app/services/message_template_service.py +++ b/backend/app/services/message_template_service.py @@ -42,16 +42,15 @@ async def create_template(self, template_data: MessageTemplateCreate, auth) -> M return await self.get_template(template_id, auth) - async def get_all_templates(self, auth: AuthenticatedUser) -> List[MessageTemplate]: + async def get_all_templates(self) -> List[MessageTemplate]: """Get all message templates for the authenticated admin""" query = """ SELECT id, template_name, template_text, admin_user_id, created_at, updated_at FROM message_templates - WHERE admin_user_id = ? ORDER BY template_name ASC """ - rows = await db.fetch_all(query, [auth.user_id]) + rows = await db.fetch_all(query) templates = [] for row in rows: @@ -68,15 +67,15 @@ async def get_all_templates(self, auth: AuthenticatedUser) -> List[MessageTempla return templates - async def get_template(self, template_id: str, auth: AuthenticatedUser) -> Optional[MessageTemplate]: + async def get_template(self, template_id: str) -> Optional[MessageTemplate]: """Get a specific message template""" query = """ SELECT id, template_name, template_text, admin_user_id, created_at, updated_at FROM message_templates - WHERE id = ? AND admin_user_id = ? + WHERE id = ? """ - row = await db.fetch_one(query, [template_id, auth.user_id]) + row = await db.fetch_one(query, [template_id]) if not row: return None @@ -92,7 +91,7 @@ async def get_template(self, template_id: str, auth: AuthenticatedUser) -> Optio variables=variables ) - async def update_template(self, template_id: str, template_data: MessageTemplateUpdate, auth: AuthenticatedUser) -> Optional[MessageTemplate]: + async def update_template(self, template_id: str, template_data: MessageTemplateUpdate) -> Optional[MessageTemplate]: """Update a message template""" # Build update query dynamically based on provided fields update_fields = [] @@ -107,25 +106,24 @@ async def update_template(self, template_id: str, template_data: MessageTemplate params.append(template_data.template_text) if not update_fields: - return await self.get_template(template_id, auth) + return await self.get_template(template_id) update_fields.append("updated_at = CURRENT_TIMESTAMP") params.append(template_id) - params.append(auth.user_id) query = f""" UPDATE message_templates SET {', '.join(update_fields)} - WHERE id = ? AND admin_user_id = ? + WHERE id = ? """ await db.execute(query, params) - return await self.get_template(template_id, auth) + return await self.get_template(template_id) - async def delete_template(self, template_id: str, auth: AuthenticatedUser) -> bool: + async def delete_template(self, template_id: str) -> bool: """Delete a message template""" - query = "DELETE FROM message_templates WHERE id = ? AND admin_user_id = ?" - await db.execute(query, [template_id, auth.user_id]) + query = "DELETE FROM message_templates WHERE id = ?" + await db.execute(query, [template_id]) return True diff --git a/backend/app/services/qr_service.py b/backend/app/services/qr_service.py index dc60821..e14aa72 100644 --- a/backend/app/services/qr_service.py +++ b/backend/app/services/qr_service.py @@ -58,9 +58,9 @@ async def create_qr_code(qr_data: QRCodeCreate, auth) -> QRCodeResponse: ) @staticmethod - async def get_qr_code(qr_id: str, auth: AuthenticatedUser) -> dict: + async def get_qr_code(qr_id: str) -> dict: """Get QR code details""" - qr_code = await db.fetch_one("SELECT * FROM qr_codes WHERE id = ? AND admin_user_id = ?", [qr_id, auth.user_id]) + qr_code = await db.fetch_one("SELECT * FROM qr_codes WHERE id = ?", [qr_id]) if not qr_code: return None @@ -74,10 +74,10 @@ async def get_qr_code(qr_id: str, auth: AuthenticatedUser) -> dict: } @staticmethod - async def get_event_qr_codes(event_id: str, auth: AuthenticatedUser) -> list: + async def get_event_qr_codes(event_id: str) -> list: """Get all QR codes for an event""" - # First verify the event belongs to the admin - event = await db.fetch_one("SELECT id FROM events WHERE id = ? AND admin_user_id = ?", [event_id, auth.user_id]) + # First verify the event exists + event = await db.fetch_one("SELECT id FROM events WHERE id = ?", [event_id]) if not event: return [] @@ -99,7 +99,7 @@ async def get_event_qr_codes(event_id: str, auth: AuthenticatedUser) -> list: ] @staticmethod - async def delete_qr_code(qr_id: str, auth: AuthenticatedUser) -> bool: + async def delete_qr_code(qr_id: str) -> bool: """Delete QR code""" - await db.execute("DELETE FROM qr_codes WHERE id = ? AND admin_user_id = ?", [qr_id, auth.user_id]) + await db.execute("DELETE FROM qr_codes WHERE id = ?", [qr_id]) return True From 123015b02c4f5f6a7230ebda738f2356dc698202 Mon Sep 17 00:00:00 2001 From: thevoid12 Date: Fri, 17 Oct 2025 14:39:12 +0530 Subject: [PATCH 3/3] misc: fixed documentation --- backend/app/api/event_fields.py | 2 +- backend/app/api/events.py | 8 +-- backend/app/services/event_service.py | 49 ++----------------- .../app/services/message_template_service.py | 2 +- 4 files changed, 9 insertions(+), 52 deletions(-) diff --git a/backend/app/api/event_fields.py b/backend/app/api/event_fields.py index 4177a87..7df8c16 100644 --- a/backend/app/api/event_fields.py +++ b/backend/app/api/event_fields.py @@ -12,7 +12,7 @@ async def get_event_fields( event_id: str, auth: AuthenticatedUser = Depends(clerk_auth) ): - """Get all fields for an event""" + """Get all fields for an event (protected)""" try: event = await EventService.get_event(event_id) if not event: diff --git a/backend/app/api/events.py b/backend/app/api/events.py index 69aa8a4..c063eb5 100644 --- a/backend/app/api/events.py +++ b/backend/app/api/events.py @@ -26,7 +26,7 @@ async def create_event( async def get_all_events( auth: AuthenticatedUser = Depends(clerk_auth) ): - """Get all events for the authenticated admin (protected)""" + """Get all events (protected)""" try: return await EventService.get_all_events() except Exception as e: @@ -38,7 +38,7 @@ async def get_all_events( @router.get("/active", response_model=EventResponse) async def get_active_event(): - """Get currently active event for the authenticated admin (protected)""" + """Get currently active event. currently one event can be active (protected)""" try: event = await EventService.get_active_event() if not event: @@ -61,7 +61,7 @@ async def get_event( event_id: str, auth: AuthenticatedUser = Depends(clerk_auth) ): - """Get event by ID (protected)""" + """Get event by event ID (protected)""" try: event = await EventService.get_event(event_id) if not event: @@ -85,7 +85,7 @@ async def update_event( event: EventUpdate, auth: AuthenticatedUser = Depends(clerk_auth) ): - """Update event (protected)""" + """Update event details (protected)""" try: updated_event = await EventService.update_event(event_id, event) if not updated_event: diff --git a/backend/app/services/event_service.py b/backend/app/services/event_service.py index 937a74a..dd70a84 100644 --- a/backend/app/services/event_service.py +++ b/backend/app/services/event_service.py @@ -108,49 +108,6 @@ async def get_event(event_id: str) -> Optional[EventResponse]: fields=fields, ) - @staticmethod - async def get_event_without_admin_id(event_id: str) -> Optional[EventResponse]: - - event = await db.fetch_one("SELECT * FROM events WHERE id = ?", [event_id]) - if not event: - return None - - # Get event fields - fields_rows = await db.fetch_all( - "SELECT * FROM event_fields WHERE event_id = ? ORDER BY field_order", - [event_id], - ) - - fields = [ - EventFieldResponse( - id=row["id"], - event_id=row["event_id"], - field_name=row["field_name"], - field_type=row["field_type"], - field_label=row["field_label"], - is_required=bool(row["is_required"]), - field_options=row["field_options"], - field_order=row["field_order"], - admin_user_id=row["admin_user_id"], - ) - for row in fields_rows - ] - - return EventResponse( - id=event["id"], - name=event["name"], - description=event["description"], - date=event["date"], - time=event["time"], - venue=event["venue"], - venue_address=event["venue_address"], - venue_map_link=event["venue_map_link"], - is_active=bool(event["is_active"]), - admin_user_id=event["admin_user_id"], - created_at=event["created_at"], - updated_at=event["updated_at"], - fields=fields, - ) @staticmethod async def get_all_events() -> List[EventResponse]: @@ -169,13 +126,13 @@ async def get_all_events() -> List[EventResponse]: @staticmethod async def get_active_event() -> List[EventResponse]: - """Get all active events for a specific admin""" + """Get active event. at this point only 1 event can be active""" event = await db.fetch_all( "SELECT * FROM events WHERE is_active = 1 ORDER BY created_at DESC LIMIT 1", ) if not event: return None - return await EventService.get_event_without_admin_id(event[0]["id"]) + return await EventService.get_event(event[0]["id"]) # we are not using now but will come handy @staticmethod @@ -312,7 +269,7 @@ async def get_event_registrations(event_id: str) -> List[dict]: @staticmethod async def update_event_fields(event_id: str, fields: List, auth: AuthenticatedUser) -> Optional[List[EventFieldResponse]]: - """Replace all fields for an event""" + """Replace fields for an event""" event = await db.fetch_one("SELECT id FROM events WHERE id = ?", [event_id]) if not event: return None diff --git a/backend/app/services/message_template_service.py b/backend/app/services/message_template_service.py index 5e72d72..87c1221 100644 --- a/backend/app/services/message_template_service.py +++ b/backend/app/services/message_template_service.py @@ -43,7 +43,7 @@ async def create_template(self, template_data: MessageTemplateCreate, auth) -> M return await self.get_template(template_id, auth) async def get_all_templates(self) -> List[MessageTemplate]: - """Get all message templates for the authenticated admin""" + """Get all message templates""" query = """ SELECT id, template_name, template_text, admin_user_id, created_at, updated_at FROM message_templates