Skip to content

Latest commit

 

History

History
782 lines (657 loc) · 16 KB

File metadata and controls

782 lines (657 loc) · 16 KB

PinPoint Backend API Documentation

Base URL

http://localhost:8000

Authentication

  • Type: JWT Bearer Token
  • Token Location: Query parameter authorization OR Authorization header
  • Token Lifespan: 30 days
  • Format: Bearer <token> (header) or ?authorization=<token> (query param)

📋 API Routes Overview

Category Route Method Protected Purpose
Auth /auth/register POST Register new user
/auth/login POST Login user
/auth/me GET Get current user profile
Questions /questions/create POST Create new question
/questions/nearby GET Get nearby questions
/questions/{id} GET Get question detail with answers
/questions/search/results GET Search questions by title
Answers /answers/submit POST Submit answer with ML verification
/answers/{id} GET Get answer detail
/answers/{id}/approve POST Approve answer (admin)
/answers/{id}/reject POST Reject answer (admin)
Votes /votes/answers/{id} POST Upvote/downvote answer
Leaderboard /leaderboard GET Get leaderboard rankings
/leaderboard/user/{id} GET Get user's rank
Notifications /notifications GET Get user notifications
/notifications/{id}/read POST Mark notification as read

🔐 Authentication Routes

Register

POST /auth/register

Request Body:

{
  "username": "john_doe",
  "email": "john@example.com",
  "password": "secure_password_123"
}

Response (200):

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "user": {
    "id": "user-uuid-123",
    "username": "john_doe",
    "email": "john@example.com",
    "credits": 0,
    "reputation": 0,
    "total_answers": 0,
    "upvotes_received": 0
  }
}

Error Responses:

  • 400 - Email already registered or username taken
  • 500 - Failed to create user

Login

POST /auth/login

Request Body:

{
  "email": "john@example.com",
  "password": "secure_password_123"
}

Response (200):

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "user": {
    "id": "user-uuid-123",
    "username": "john_doe",
    "email": "john@example.com",
    "credits": 0,
    "reputation": 0
  }
}

Error Responses:

  • 401 - Invalid email or password

Get Current User

GET /auth/me?authorization=<token>

Headers (Alternative):

Authorization: Bearer <token>

Response (200):

{
  "id": "user-uuid-123",
  "username": "john_doe",
  "email": "john@example.com",
  "credits": 10,
  "reputation": 25,
  "total_answers": 3,
  "upvotes_received": 5
}

Error Responses:

  • 401 - Authorization required / Invalid token / Token expired

❓ Questions Routes

Create Question

POST /questions/create?authorization=<token>

Request Body:

{
  "title": "What's this building?",
  "description": "Can anyone identify this historic building in downtown?",
  "latitude": 40.7128,
  "longitude": -74.0060,
  "radius_meters": 500,
  "category": "architecture"
}

Response (200):

{
  "success": true,
  "message": "Question created successfully",
  "data": {
    "question_id": "q-uuid-456"
  }
}

Error Responses:

  • 401 - Authorization required
  • 500 - Failed to create question

Get Nearby Questions

GET /questions/nearby?latitude=40.7128&longitude=-74.0060&radius=5000

Query Parameters:

Parameter Type Required Default Description
latitude float - Latitude coordinate
longitude float - Longitude coordinate
radius integer 1000 Search radius in meters

Response (200):

{
  "questions": [
    {
      "id": "q-uuid-456",
      "user_id": "user-uuid-123",
      "title": "What's this building?",
      "description": "Can anyone identify this historic building?",
      "latitude": 40.7128,
      "longitude": -74.0060,
      "created_at": "2026-03-01T10:30:00Z",
      "status": "active",
      "answer_count": 3,
      "upvote_count": 5,
      "user": {
        "username": "john_doe",
        "reputation": 25
      }
    }
  ]
}

Get Question Detail

GET /questions/{question_id}

Path Parameters:

  • question_id (string, required) - UUID of question

Response (200):

{
  "id": "q-uuid-456",
  "user_id": "user-uuid-123",
  "title": "What's this building?",
  "description": "Can anyone identify this historic building?",
  "latitude": 40.7128,
  "longitude": -74.0060,
  "created_at": "2026-03-01T10:30:00Z",
  "status": "active",
  "answer_count": 3,
  "upvote_count": 5
}

Error Responses:

  • 404 - Question not found

Search Questions

GET /questions/search/results?q=building

Query Parameters:

Parameter Type Required Description
q string Search query (min 1 char)

Response (200):

[
  {
    "id": "q-uuid-456",
    "title": "What's this building?",
    "description": "Can anyone identify...",
    "latitude": 40.7128,
    "longitude": -74.0060,
    "created_at": "2026-03-01T10:30:00Z",
    "answer_count": 3
  }
]

✅ Answers Routes

Submit Answer

POST /answers/submit?authorization=<token>

Request Body:

{
  "question_id": "q-uuid-456",
  "image_base64": "data:image/jpeg;base64,/9j/4AAQSkZJRg...",
  "text_answer": "This is a historic city hall building built in 1850s",
  "gps_latitude": 40.7128,
  "gps_longitude": -74.0060,
  "gps_accuracy": 15.5,
  "verification": {
    "gyro_pitch": 12.34,
    "gyro_roll": -5.67,
    "gyro_yaw": 89.12,
    "gyro_valid": true,
    "ssim_score": 0.92,
    "ssim_valid": true,
    "timestamp": "2026-03-01T10:35:00Z"
  }
}

Notes:

  • Image must be base64 encoded
  • GPS coordinates must be within question's radius
  • Verification data is optional but recommended
  • ML pipeline runs in background (background task)
  • Image uploaded to Supabase Storage automatically

Response (200):

{
  "success": true,
  "message": "Answer submitted successfully",
  "data": {
    "answer_id": "a-uuid-789"
  }
}

Error Responses:

  • 401 - Authorization required
  • 404 - Question not found
  • 400 - Invalid image format
  • 500 - Failed to submit answer

Background ML Pipeline:

  1. ✅ Image Quality Check (blur detection)
  2. ✅ NSFW Detection
  3. ✅ Face Detection & Blur
  4. ✅ Object Detection
  5. ✅ OCR Text Extraction
  6. ✅ Semantic Similarity Matching (Gemini Embeddings)
  7. ✅ Auto-approval if relevant + high quality + non-NSFW
  8. ✅ Reputation & Credits awarded

Get Answer Detail

GET /answers/{answer_id}

Path Parameters:

  • answer_id (string, required) - UUID of answer

Response (200):

{
  "id": "a-uuid-789",
  "question_id": "q-uuid-456",
  "user_id": "user-uuid-123",
  "image_url": "https://storage.supabase.co/answers/q-uuid/user-uuid_answer_123456.jpg",
  "text_answer": "This is a historic city hall building",
  "created_at": "2026-03-01T10:35:00Z",
  "status": "approved",
  "upvote_count": 5,
  "downvote_count": 1,
  "helpful_score": 0.83,
  "analysis": {
    "quality": 0.85,
    "nsfw": 0.02,
    "faces": 0,
    "similarity": 0.779,
    "objects": ["building", "urban", "architecture"],
    "ocr": "Historic City Hall 1850s",
    "caption": "Detected: building, urban, architecture"
  }
}

Approve Answer (Admin)

POST /answers/{answer_id}/approve?authorization=<token>

Response (200):

{
  "success": true,
  "message": "Answer approved"
}

Reject Answer (Admin)

POST /answers/{answer_id}/reject?authorization=<token>

Response (200):

{
  "success": true,
  "message": "Answer rejected"
}

👍 Votes Routes

Vote Answer

POST /votes/answers/{answer_id}?authorization=<token>

Path Parameters:

  • answer_id (string, required) - UUID of answer

Request Body:

{
  "vote_type": "upvote"
}

Vote Types:

  • upvote - Like/helpful answer
  • downvote - Dislike/unhelpful answer

Response (200):

{
  "success": true,
  "message": "Vote recorded",
  "data": {
    "answer_id": "a-uuid-789",
    "upvotes": 6,
    "downvotes": 1,
    "helpful_score": 0.857
  }
}

Error Responses:

  • 401 - Authorization required
  • 400 - Invalid vote type
  • 500 - Failed to vote

🏆 Leaderboard Routes

Get Leaderboard

GET /leaderboard?limit=20&offset=0

Query Parameters:

Parameter Type Required Default Range Description
limit integer 10 1-100 Number of entries
offset integer 0 ≥0 Pagination offset

Response (200):

{
  "leaderboard": [
    {
      "rank": 1,
      "username": "top_contributor",
      "avatar_url": "https://...",
      "reputation": 250,
      "credits": 100,
      "total_answers": 25,
      "upvotes_received": 45
    },
    {
      "rank": 2,
      "username": "john_doe",
      "avatar_url": "https://...",
      "reputation": 150,
      "credits": 50,
      "total_answers": 15,
      "upvotes_received": 30
    }
  ]
}

Get User Rank

GET /leaderboard/user/{user_id}

Path Parameters:

  • user_id (string, required) - UUID of user

Response (200):

{
  "success": true,
  "rank": 5,
  "reputation": 120,
  "total_answers": 12,
  "upvotes_received": 20
}

Error Responses:

  • 404 - User not found on leaderboard

🔔 Notifications Routes

Get Notifications

GET /notifications?limit=20&offset=0&authorization=<token>

Query Parameters:

Parameter Type Required Default Range Description
limit integer 20 1-100 Number of notifications
offset integer 0 ≥0 Pagination offset

Headers OR Query:

  • authorization query parameter with token OR
  • Authorization: Bearer <token> header

Response (200):

{
  "notifications": [
    {
      "id": "notif-uuid-123",
      "notification_type": "answer_approved",
      "message": "Your answer was approved!",
      "question_id": "q-uuid-456",
      "is_read": false,
      "created_at": "2026-03-01T11:00:00Z"
    },
    {
      "id": "notif-uuid-124",
      "notification_type": "answer_upvoted",
      "message": "Someone upvoted your answer",
      "question_id": "q-uuid-456",
      "is_read": true,
      "created_at": "2026-03-01T10:50:00Z"
    }
  ]
}

Mark Notification as Read

POST /notifications/{notification_id}/read?authorization=<token>

Path Parameters:

  • notification_id (string, required) - UUID of notification

Response (200):

{
  "success": true,
  "message": "Notification marked as read"
}

Error Responses:

  • 401 - Authorization required
  • 500 - Failed to mark as read

📊 Data Models

User

{
  "id": "uuid",
  "username": "string",
  "email": "string",
  "password_hash": "string (hashed)",
  "avatar_url": "string (optional)",
  "credits": "integer",
  "reputation": "integer",
  "total_answers": "integer",
  "upvotes_received": "integer",
  "created_at": "datetime",
  "updated_at": "datetime"
}

Question

{
  "id": "uuid",
  "user_id": "uuid (foreign key)",
  "title": "string",
  "description": "string",
  "latitude": "float",
  "longitude": "float",
  "radius_meters": "integer",
  "category": "string",
  "status": "enum (active, closed, resolved)",
  "answer_count": "integer",
  "upvote_count": "integer",
  "created_at": "datetime"
}

Answer

{
  "id": "uuid",
  "question_id": "uuid (foreign key)",
  "user_id": "uuid (foreign key)",
  "image_url": "string (Supabase storage URL)",
  "text_answer": "string",
  "status": "enum (pending, approved, rejected)",
  "upvote_count": "integer",
  "downvote_count": "integer",
  "helpful_score": "float (0.0-1.0)",
  "created_at": "datetime"
}

ML Analysis Result

{
  "id": "uuid",
  "answer_id": "uuid (foreign key)",
  "image_quality": "float (0.0-1.0)",
  "is_motion_blurred": "boolean",
  "nsfw_score": "float (0.0-1.0)",
  "contains_faces": "boolean",
  "face_count": "integer",
  "objects_detected": ["string"],
  "scene_caption": "string",
  "ocr_text": "string",
  "semantic_similarity": "float (0.0-1.0)",
  "final_status": "enum (approved, pending_review, rejected)"
}

🚀 Example Workflows

Complete Answer Submission Workflow

1. User submits question with GPS location
   POST /questions/create
   
2. Frontend captures nearby questions
   GET /questions/nearby?latitude=40.7128&longitude=-74.0060
   
3. User selects answer question and captures photo
   - Camera capture
   - Get GPS coordinates
   - Capture gyroscope data
   
4. Submit answer with all verifications
   POST /answers/submit
   - Image base64
   - Text answer
   - GPS coordinates (must match question radius)
   - Gyroscope data
   
5. Backend ML Pipeline (background task):
   - Analyze image quality
   - Check for NSFW content
   - Blur faces for privacy
   - Detect objects
   - Extract OCR text
   - Match semantic similarity with Gemini
   - Auto-approve if all checks pass
   
6. User gets upvotes on answer
   POST /votes/answers/{answer_id}
   - Vote count increases
   - Reputation earned
   
7. View leaderboard rankings
   GET /leaderboard?limit=100

Authentication Flow

1. Register new user
   POST /auth/register
   → Get JWT token valid for 30 days
   
2. Use token in subsequent requests
   GET /auth/me?authorization=<token>
   OR
   GET /auth/me
   Header: Authorization: Bearer <token>
   
3. Token expires after 30 days
   → User must login again
   POST /auth/login

🔧 Technical Details

Error Handling

All errors follow this format:

{
  "detail": "Error message here"
}

Rate Limiting

  • No rate limits implemented (can be added)
  • Consider adding for production

CORS

  • Configured for frontend at http://localhost:3000
  • Can accept requests from specified origins

Image Storage

  • Images stored in Supabase Storage bucket: answers
  • Auto-bucket creation on first upload
  • Public URL generation for frontend access
  • Base64 encoding/decoding handled server-side

ML Pipeline Specifications

  • Quality Check: Blur score via Laplacian variance
  • NSFW Detection: Custom CNN model
  • Face Detection: MTCNN with automatic blur
  • Object Detection: YOLOv5 pre-trained
  • OCR: EasyOCR
  • Embeddings: Google Gemini text-embedding-004
  • Similarity Threshold: 0.65 (auto-approved if > threshold)

Token Format

JWT Header: {
  "alg": "HS256",
  "typ": "JWT"
}

JWT Payload: {
  "sub": "user-uuid-123",
  "email": "user@example.com",
  "exp": 1809523200,
  "iat": 1741987200
}

📱 Frontend Integration

The React frontend uses Axios to call these endpoints with:

  • Automatic token injection from Zustand store
  • Toast notifications for errors
  • Background ML processing status polling
  • Real-time vote count updates
  • Map-based question browser with Leaflet.js

See frontend/src/services/api.ts for complete frontend wrapper implementation.


🐛 Debugging Tips

  1. Check token validity: Call /auth/me to verify token
  2. Test without auth: Use public endpoints first
  3. Monitor ML pipeline: Check backend terminal for [ML PIPELINE] logs
  4. Image upload issues: Ensure base64 is properly formatted
  5. GPS verification: Ensure answer GPS is within question radius
  6. Database: Check Supabase console for data

📝 Notes

  • All timestamps in UTC (ISO 8601 format)
  • All IDs are UUIDs (v4)
  • All distances in meters
  • All coordinates in WGS84 (lat/lng)
  • Reputation system: +10 per approved answer, +1 per upvote
  • Credits system: +10 per approved answer, usable for badges/features