Markdown Converter
Agent skill for markdown-converter
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Sign in to like and favorite skills
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Wat2Do (wat2do.ca) is a web app to help you discover club events at the University of Waterloo, scraped directly from Instagram. The application captures all student club events within 10 minutes and provides a web interface for browsing, submitting, and managing events and clubs.
Built with Django REST API backend and React + TypeScript frontend, Wat2Do uses AI to extract event details from Instagram posts.
Tech Stack:
Key Features:
Option 1: Docker Compose (Recommended)
docker compose up --build
Option 3: Local PostgreSQL
backend/config/settings/development.pycd backend python -m venv .venv source .venv/bin/activate pip install -r requirements.txt # Create and apply migrations python manage.py makemigrations python manage.py migrate # Optional: Populate local DB with production data python scripts/populate-local-db-with-prod-data.py python manage.py fix_sequences # Start development server python manage.py runserver 8000
cd frontend npm install npm run dev # Development server on port 5173
Important: The backend must be running for the frontend to work properly. Always start both servers during development.
python manage.py check - Django configuration checkpython manage.py test - Run Django testspython manage.py migrate - Apply database migrationspython manage.py makemigrations [app_name] - Create new migrationspython manage.py runserver 8000 - Start development serverpython scripts/populate-local-db-with-prod-data.py - Populate local DB with production datapython manage.py fix_sequences - Fix database sequence issues (after populating data)npm run build - Production buildnpm run lint - ESLint check (shows existing warnings, focus on new ones)npm run lint:fix - Auto-fix ESLint issuesnpm run dev - Development server (port 5173)npm run preview - Preview production buildruff for linting (configured in pyproject.toml)Purpose: Browse, search, filter, and interact with university club events scraped from Instagram
/frontend/src/features/events/)Key Pages:
/events - Main events listing page (frontend/src/features/events/pages/EventsPage.tsx)/events/:eventId - Single event detail page (frontend/src/features/events/pages/EventDetailPage.tsx)Components:
EventsGrid.tsx - Grid view with infinite scrollEventsCalendar.tsx - Calendar view using react-big-calendarEventsContent.tsx - View switcher (grid/calendar modes)EventPreview.tsx - Event card component with image, title, dates, locationEventLegend.tsx - Category legendQuickFilters.tsx - Quick filter chips (Newly Added, All, Interested, Export)EventEditForm.tsx - Edit event form for usersHooks:
useEvents.ts - Main events query with infinite scroll, URL-based filtering, cursor paginationuseEventSelection.ts - Multi-select events for exportuseEventInterest.ts - Mark/unmark events as interested with optimistic UI updatesuseQuickFilters.ts - Category filter logicKey Features:
File References:
frontend/src/features/events/pages/EventsPage.tsx:1frontend/src/features/events/hooks/useEvents.ts:1frontend/src/features/events/hooks/useEventInterest.ts:1/backend/apps/events/)Models:
Events - Core event model
EventDates - Individual event occurrences (one-to-many with Events)
EventInterest - User interest tracking (many-to-many)
API Endpoints:
GET /api/events/ - List events with filters, cursor pagination GET /api/events/latest-update/ - Get latest event update timestamp GET /api/events/<id>/ - Get single event with all occurrences PUT /api/events/<id>/update/ - Update event (submitter/admin only) DELETE /api/events/<id>/delete/ - Delete event (admin only) GET /api/events/export.ics - Export events as ICS calendar file GET /api/events/google-calendar-urls/ - Generate Google Calendar URLs for events GET /api/events/my-interests/ - Get user's interested event IDs (JWT required) POST /api/events/<id>/interest/mark/ - Mark interest in event (JWT required) DELETE /api/events/<id>/interest/unmark/ - Remove interest (JWT required)
Query Parameters for
:/api/events/
search - Search title/description (semicolon-separated OR)categories - Filter by categories (semicolon-separated OR)dtstart_utc - Filter by start date (ISO 8601)dtend_utc - Filter by end date (ISO 8601)added_at - Filter by date added (ISO 8601)min_price, max_price - Price range filtersclub_type - Filter by club type (WUSA, Athletics, Student Society)school - Filter by schoolinterested - Show only user's interested events (JWT required)cursor - Pagination cursorlimit - Results per page (default: 12)Rate Limiting:
File References:
backend/apps/events/models.py:1backend/apps/events/views.py:1backend/utils/filters.py:1Purpose: Allow users to submit events for review using AI-powered extraction from screenshots
/frontend/src/features/events/)Key Pages:
/submit - Submit new event page (frontend/src/features/events/pages/SubmitEventPage.tsx)/dashboard/submissions - User's submitted events (frontend/src/features/events/pages/MySubmissionsPage.tsx)Components:
EventFormFields.tsx - Reusable form fields for event submission/editingEventEditForm.tsx - Form for editing submitted eventsHooks:
useEventSubmission.ts - Submit event mutation with AI extractionuseUserSubmissions.ts - Fetch user's submitted eventsWorkflow:
/admin/submissionsSchema:
frontend/src/features/events/schemas/submissionSchema.tsFile References:
frontend/src/features/events/pages/SubmitEventPage.tsx:1frontend/src/features/events/hooks/useEventSubmission.ts:1/backend/apps/events/)Models:
EventSubmission - User-submitted events pending review
API Endpoints:
POST /api/events/extract/ - Extract event from screenshot using AI (JWT required) POST /api/events/submit/ - Submit new event for review (JWT required) GET /api/events/my-submissions/ - Get user's submissions (JWT required) GET /api/events/submissions/ - Get all submissions for review (admin only) POST /api/events/submissions/<id>/review/ - Approve/reject submission (admin only) DELETE /api/events/submissions/<id>/ - Delete submission (JWT required)
AI Extraction (OpenAI Service):
backend/services/openai_service.pyRate Limiting:
File References:
backend/apps/events/models.py:1backend/apps/events/views.py:1backend/services/openai_service.py:1Purpose: Browse university club directory with search functionality
/frontend/src/features/clubs/)Key Pages:
/clubs - Main clubs directory page (frontend/src/features/clubs/pages/ClubsPage.tsx)Components:
ClubsGrid.tsx - Grid of club cards with images, names, categoriesHooks:
useClubs.ts - Fetch clubs with search and filtersFeatures:
File References:
frontend/src/features/clubs/pages/ClubsPage.tsx:1/backend/apps/clubs/)Models:
Clubs
API Endpoints:
GET /api/clubs/ - List clubs with cursor pagination, search, category filter
Query Parameters:
search - Search by club namecategories - Filter by categorycursor - Pagination cursorlimit - Results per page (default: 50)File References:
backend/apps/clubs/models.py:1backend/apps/clubs/views.py:1Purpose: Admin panel for managing event promotions and reviewing submissions
/frontend/src/features/admin/)Key Pages:
/admin - Admin dashboard (frontend/src/features/admin/pages/AdminPage.tsx)/admin/promotions - Manage promoted events (frontend/src/features/admin/pages/PromotionsPage.tsx)/admin/submissions - Review user submissions (frontend/src/features/admin/pages/SubmissionsReviewPage.tsx)Components:
AdminLogin.tsx - Admin authentication UIPromotionsManager.tsx - Manage promoted events listPromoteEventForm.tsx - Form to promote events with priority/expirationHooks:
useEventPromotion.ts - Promotion mutations (promote, unpromote)useSubmissionsReview.ts - Review submissions (approve, reject)Protected Routes:
ProtectedAdminRoute componentuser.publicMetadata.role === 'admin' via Clerk/eventsFile References:
frontend/src/features/admin/pages/SubmissionsReviewPage.tsx:1frontend/src/shared/components/ProtectedAdminRoute.tsx:1/backend/apps/promotions/)Models:
EventPromotion (OneToOne with Events)
API Endpoints:
GET /api/promotions/events/ - Get promoted events POST /api/promotions/events/<id>/promote/ - Promote event (admin only) POST /api/promotions/events/<id>/unpromote/ - Unpromote event (admin only) GET /api/promotions/events/<id>/promotion-status/ - Get promotion status (admin only)
File References:
backend/apps/promotions/models.py:1backend/apps/promotions/views.py:1Purpose: User authentication and authorization using Clerk
/frontend/src/features/auth/)Key Pages:
/auth/sign-in - Sign in page (frontend/src/features/auth/pages/SignInPage.tsx)/auth/sign-up - Sign up page (frontend/src/features/auth/pages/SignUpPage.tsx)/auth/verify-email - Email verification (frontend/src/features/auth/pages/VerifyEmailPage.tsx)/user-profile/* - User profile management (frontend/src/features/auth/pages/UserProfilePage.tsx)/dashboard - User dashboard (frontend/src/features/auth/pages/DashboardPage.tsx)Components:
UserProfile.tsx - User profile componentEmailVerification.tsx - Email verification UIForgotPassword.tsx, ResetPassword.tsx - Password reset flowSuccessMessage.tsx - Success notification componentProtected Routes:
ProtectedRoute - Requires authentication, redirects admins to /adminProtectedAdminRoute - Requires admin role in user.publicMetadata.roleState Management:
frontend/src/shared/stores/clerkAuthStore.tsClerk Configuration:
frontend/src/shared/config/clerk.tsfrontend/src/shared/components/ClerkAppProvider.tsxFile References:
frontend/src/shared/stores/clerkAuthStore.ts:1frontend/src/shared/components/ProtectedRoute.tsx:1frontend/src/shared/components/ClerkAppProvider.tsx:1/backend/apps/core/)Authentication System:
Custom JWT Backend:
JwtAuthBackend in backend/apps/core/authentication.pyDecorators:
@jwt_required - Requires valid JWT token@optional_jwt - JWT optional, populates user info if present@admin_required - Requires admin role in JWT payloadAPI Endpoints:
GET /api/auth/me/ - Get current user info (JWT required) GET /api/protected/ - Protected test endpoint (JWT required)
Authentication Flow:
getToken()Authorization: Bearer <token> headeruser_id and is_admin attributesFile References:
backend/apps/core/authentication.py:1backend/apps/core/decorators.py:1Purpose: Newsletter subscription management with email notifications
/frontend/src/features/newsletter/)Key Pages:
/unsubscribe/:token - Unsubscribe page (frontend/src/features/newsletter/pages/UnsubscribePage.tsx)Components:
UnsubscribeForm.tsx - Unsubscribe form with reason selectionHooks:
useNewsletterSubscribe.ts - Subscribe mutationuseUnsubscribe.ts - Unsubscribe mutationFile References:
frontend/src/features/newsletter/pages/UnsubscribePage.tsx:1/backend/apps/newsletter/)Models:
NewsletterSubscriber
get_by_email(), create_subscriber(), get_email_display() (masked)API Endpoints:
POST /api/newsletter/subscribe/ - Subscribe to newsletter GET /api/newsletter/unsubscribe/<token>/ - Get unsubscribe info POST /api/newsletter/unsubscribe/<token>/ - Unsubscribe with reason POST /api/newsletter/test-email/ - Send test email (internal)
Email Service:
backend/services/email_service.pyEmail Features:
File References:
backend/apps/newsletter/models.py:1backend/services/email_service.py:1Purpose: Global search functionality with localStorage persistence
/frontend/src/features/search/)Components:
SearchInput.tsx - Search input with clear button and debouncingHooks:
useSearchState.ts - Search state with localStorage persistence and URL syncFeatures:
File References:
frontend/src/features/search/hooks/useSearchState.ts:1frontend/src/features/search/components/SearchInput.tsx:1/backend/apps/)backend/apps/ ├── events/ # Events, submissions, interests, calendar export ├── clubs/ # Club directory ├── newsletter/ # Newsletter subscriptions ├── promotions/ # Event promotions ├── core/ # Authentication, health checks, base functionality └── payments/ # Placeholder (not implemented yet)
/backend/services/)OpenAI Service (
openai_service.py)
Email Service (
email_service.py)
Storage Service (
storage_service.py)
Events (1) ←→ (1) EventPromotion Events (1) → (many) EventDates Events (1) → (many) EventInterest Events (1) ← (1) EventSubmission Clubs (standalone) NewsletterSubscriber (standalone)
Key Indexes:
frontend/src/features/ ├── events/ # 65+ files - Primary feature │ ├── components/ # EventsGrid, EventsCalendar, EventPreview, etc. │ ├── hooks/ # useEvents, useEventInterest, useEventSelection, etc. │ ├── pages/ # EventsPage, EventDetailPage, SubmitEventPage, etc. │ ├── schemas/ # Zod validation schemas │ └── types/ # TypeScript type definitions ├── clubs/ # Club directory ├── admin/ # Admin panel, promotions, submissions review ├── auth/ # Clerk authentication pages ├── newsletter/ # Newsletter unsubscribe └── search/ # Global search
/frontend/src/shared/)UI Components (
/shared/components/ui/)
Layout Components (
/shared/components/layout/)
Navbar.tsx - Main navigation with user menuFooter.tsx - Footer with linksTopBanner.tsx - Dismissible banner ("Events added in real time!")AboutPage.tsx, ContactPage.tsx, NotFoundPage.tsxCommon Components (
/shared/components/common/)
FloatingEventExportBar.tsx - Export bar for selected eventsBadge Components (
/shared/components/badges/)
EventBadges.tsx - Category badge displayServer State (TanStack Query)
frontend/src/app/main.tsxPatterns:
Client State (Zustand)
clerkAuthStore.ts - Clerk authentication state (persisted to localStorage)authStore.ts - Legacy auth store (minimal usage)URL State
useSearchParams()Architecture: Constructor pattern with dependency injection
// Base client with token injection const baseClient = new BaseAPIClient(() => getToken()); // Feature-specific clients const eventsClient = new EventsAPIClient(baseClient); const adminClient = new AdminAPIClient(baseClient);
API Clients (
/shared/api/):
BaseAPIClient.ts - Axios wrapper with auth token injectionEventsAPIClient.ts - Events CRUD + submissions + interestsAdminAPIClient.ts - Admin operations (promotions, reviews)NewsletterAPIClient.ts - Newsletter subscribe/unsubscribeClubsAPIClient.ts - Clubs APISimpleAPIClient.ts - Lightweight clientFile References:
frontend/src/shared/api/index.ts:1frontend/src/shared/api/BaseAPIClient.ts:1/frontend/src/app/App.tsx)// Public routes / → EventsPage /events → EventsPage /events/:eventId → EventDetailPage /clubs → ClubsPage /about → AboutPage /contact → ContactPage // Protected routes (authentication required) /submit → ProtectedRoute(SubmitEventPage) /user-profile/* → ProtectedRoute(UserProfilePage) /dashboard → ProtectedRoute(MySubmissionsPage) /dashboard/submissions → ProtectedRoute(MySubmissionsPage) // Admin routes (admin role required) /admin → ProtectedAdminRoute(AdminPage) /admin/promotions → ProtectedAdminRoute(PromotionsPage) /admin/submissions → ProtectedAdminRoute(SubmissionsReviewPage) // Auth routes /auth/sign-in → SignInPage /auth/sign-up → SignUpPage // Newsletter /unsubscribe/:token → UnsubscribePage // Fallback * → NotFoundPage
Core:
State Management:
Authentication:
UI:
Forms:
Date/Time:
Utilities:
/backend/scraping/)Key Files:
main.py - Orchestrates all Instagram scraping and post-processing (entrypoint for daily and single-user scrapes)instagram_scraper.py - Apify Instagram API client logicevent_processor.py - Post-processing logic for scraped events (image upload, AI extraction, DB insert)Features:
Base URL:
http://localhost:8000/
GET / - API information and endpoint listing GET /health/ - Health check GET /api/auth/me/ - Get current user info (JWT required) GET /api/protected/ - Protected test endpoint (JWT required)
GET /api/events/ - List events (pagination, filters) GET /api/events/latest-update/ - Latest event update timestamp GET /api/events/<id>/ - Event detail with all occurrences PUT /api/events/<id>/update/ - Update event (JWT required) DELETE /api/events/<id>/delete/ - Delete event (admin only) GET /api/events/export.ics - Export to iCalendar format GET /api/events/google-calendar-urls/ - Generate Google Calendar URLs POST /api/events/extract/ - AI event extraction (JWT required) POST /api/events/submit/ - Submit event (JWT required) GET /api/events/my-submissions/ - User's submissions (JWT required) GET /api/events/submissions/ - All submissions (admin only) POST /api/events/submissions/<id>/review/ - Approve/reject (admin only) DELETE /api/events/submissions/<id>/ - Delete submission (JWT required) GET /api/events/my-interests/ - User's interested events (JWT required) POST /api/events/<id>/interest/mark/ - Mark interest (JWT required) DELETE /api/events/<id>/interest/unmark/ - Unmark interest (JWT required)
GET /api/clubs/ - List clubs with cursor pagination, search, filters
POST /api/newsletter/subscribe/ - Subscribe to newsletter GET /api/newsletter/unsubscribe/<token>/ - Get unsubscribe info POST /api/newsletter/unsubscribe/<token>/ - Unsubscribe with reason POST /api/newsletter/test-email/ - Send test email (internal)
GET /api/promotions/events/ - Get promoted events POST /api/promotions/events/<id>/promote/ - Promote event (admin) POST /api/promotions/events/<id>/unpromote/ - Unpromote event (admin) GET /api/promotions/events/<id>/promotion-status/ - Get promotion status (admin)
Backend:
python manage.py startapp app_namebackend/apps/app_name/models.pybackend/apps/app_name/serializers.pybackend/apps/app_name/views.pybackend/apps/app_name/urls.pybackend/config/urls.pypython manage.py makemigrations app_namepython manage.py migrateFrontend:
/frontend/src/features/feature_name//frontend/src/shared/api//frontend/src/app/App.tsx/frontend/src/shared/components/ui/# Create migrations python manage.py makemigrations [app_name] # Review migrations (dry-run) python manage.py migrate --dry-run # Apply migrations python manage.py migrate # Rollback migration (if needed) python manage.py migrate app_name <migration_number>
# Health check curl http://localhost:8000/health/ # Test events endpoint curl http://localhost:8000/api/events/ # Test with filters curl "http://localhost:8000/api/events/?search=tech&categories=academic" # Test authenticated endpoint (replace TOKEN) curl -H "Authorization: Bearer TOKEN" http://localhost:8000/api/events/my-interests/
Frontend Pattern:
// Wrap page with ProtectedRoute for authentication <Route path="/submit" element={<ProtectedRoute><SubmitEventPage /></ProtectedRoute>} /> // Wrap page with ProtectedAdminRoute for admin-only access <Route path="/admin" element={<ProtectedAdminRoute><AdminPage /></ProtectedAdminRoute>} />
Backend Pattern:
from apps.core.decorators import jwt_required, admin_required @jwt_required def my_view(request): user_id = request.user.user_id # ... user-specific logic @admin_required def admin_view(request): # ... admin-only logic
@/* maps to ./src/*python scripts/populate-local-db-with-prod-data.py to get production data locallyAuthorization: Bearer <token> headergetToken() methodrole field in JWT payloadsub claimvalue1;value2;value3tz fieldEvents endpoints vary by operation type:
ruff for linting (configured in pyproject.toml)/admin/submissions for reviewing submissions/backend/apps/payments/ exists as placeholderCLERK_SECRET_KEY - Clerk authenticationOPENAI_API_KEY - AI extractionRESEND_API_KEY - Email sendingAWS_S3_BUCKET_NAME, AWS_DEFAULT_REGION - S3 storageSECRET_KEY - Django secret keyALLOWED_PARTIES - Authorized domainsVITE_CLERK_PUBLISHABLE_KEY - Clerk public keyVITE_API_BASE_URL - Backend API URLfrontend/src/app/App.tsx:1frontend/src/app/main.tsx:1frontend/src/features/events/pages/EventsPage.tsx:1frontend/src/features/events/hooks/useEvents.ts:1frontend/src/features/events/hooks/useEventInterest.ts:1frontend/src/shared/api/index.ts:1frontend/src/shared/components/ProtectedRoute.tsx:1frontend/src/shared/stores/clerkAuthStore.ts:1backend/apps/events/models.py:1backend/apps/events/views.py:1backend/utils/filters.py:1backend/apps/core/authentication.py:1backend/apps/core/decorators.py:1backend/services/openai_service.py:1backend/services/email_service.py:1backend/services/storage_service.py:1backend/config/urls.py:1backend/config/settings/base.py:1/health/ endpoint to verify backend is running