feat: implement user profile endpoints (task 31)

- Add GET /api/users/:id for profile retrieval with stats
- Add PATCH /api/users/:id for profile updates
- Add GET /api/users/:id/reports to fetch user's reports
- Add GET /api/users/:id/events to fetch user's events (registered & organized)
- Add PATCH /api/users/:id/preferences for notification settings
- Add POST /api/users/:id/calculate-impact for impact score calculation
- Create userService with all business logic
- Create userController with request handling
- Create userRoutes and register in main server
- Update frontend userService with new endpoints
- Add comprehensive documentation in USER_PROFILE_ENDPOINTS.md
- Add test script for manual endpoint testing

Requirements satisfied: 6.3, 6.4, 6.5, 10.4
This commit is contained in:
2025-11-16 21:38:16 +00:00
parent f02f23e185
commit c84bcb2fd6
8 changed files with 1151 additions and 4 deletions

View File

@@ -0,0 +1,401 @@
# User Profile Endpoints Documentation
This document describes the user profile API endpoints implemented for the Beach Litter Mapping Platform.
## Overview
The user profile endpoints provide functionality for:
- Retrieving user profiles with statistics
- Updating user profile information
- Managing notification preferences
- Fetching user's reports and events
- Calculating impact scores
## Authentication
Most endpoints require authentication via Clerk session token:
```
Authorization: Bearer <clerk_session_token>
```
## Endpoints
### 1. Get User Profile
**Endpoint:** `GET /api/users/:id`
**Description:** Retrieve a user's profile including statistics about their contributions.
**Authentication:** Not required (public profiles)
**Parameters:**
- `id` (path) - User database ID
**Response:**
```json
{
"id": "uuid",
"clerkId": "user_xxx",
"email": "user@example.com",
"username": "johndoe",
"firstName": "John",
"lastName": "Doe",
"avatarUrl": "https://...",
"impactScore": 150,
"notificationEmail": true,
"notificationInApp": true,
"areasOfInterest": [
{
"lat": 53.3498,
"lng": -6.2603,
"radius": 10
}
],
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:00:00.000Z",
"stats": {
"reportsSubmitted": 5,
"eventsAttended": 3,
"eventsOrganized": 1
}
}
```
**Error Responses:**
- `404` - User not found
- `500` - Internal server error
---
### 2. Update User Profile
**Endpoint:** `PATCH /api/users/:id`
**Description:** Update user profile information (first name, last name, username).
**Authentication:** Required (users can only update their own profile)
**Parameters:**
- `id` (path) - User database ID
**Request Body:**
```json
{
"firstName": "John",
"lastName": "Doe",
"username": "johndoe123"
}
```
All fields are optional. Only provided fields will be updated.
**Response:**
```json
{
"id": "uuid",
"clerkId": "user_xxx",
"email": "user@example.com",
"username": "johndoe123",
"firstName": "John",
"lastName": "Doe",
"avatarUrl": "https://...",
"impactScore": 150,
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:00:00.000Z"
}
```
**Error Responses:**
- `400` - Username already taken
- `401` - Not authenticated
- `403` - Cannot update another user's profile
- `404` - User not found
- `500` - Internal server error
---
### 3. Get User Reports
**Endpoint:** `GET /api/users/:id/reports`
**Description:** Retrieve all reports submitted by a user.
**Authentication:** Not required
**Parameters:**
- `id` (path) - User database ID
**Response:**
```json
[
{
"id": "uuid",
"userId": "uuid",
"latitude": 53.3498,
"longitude": -6.2603,
"locationSource": "gps",
"photoUrls": ["/uploads/photo1.jpg"],
"photoTimestamp": "2024-01-01T12:00:00.000Z",
"litterType": "plastic",
"quantity": "moderate",
"description": "Plastic bottles on beach",
"verificationStatus": "verified",
"createdAt": "2024-01-01T12:00:00.000Z",
"environmentalConcerns": [],
"verifications": []
}
]
```
**Error Responses:**
- `500` - Internal server error
---
### 4. Get User Events
**Endpoint:** `GET /api/users/:id/events`
**Description:** Retrieve events the user has registered for and organized.
**Authentication:** Not required
**Parameters:**
- `id` (path) - User database ID
**Response:**
```json
{
"registered": [
{
"id": "uuid",
"title": "Beach Cleanup",
"description": "Community cleanup event",
"latitude": 53.3498,
"longitude": -6.2603,
"locationName": "Sandymount Beach",
"scheduledDate": "2024-02-01T10:00:00.000Z",
"duration": 120,
"status": "upcoming",
"participantCount": 15,
"organizer": {
"id": "uuid",
"username": "organizer",
"avatarUrl": "https://..."
},
"registeredAt": "2024-01-15T10:00:00.000Z",
"attended": false
}
],
"organized": [
{
"id": "uuid",
"title": "My Beach Cleanup",
"description": "Event I organized",
"latitude": 53.3498,
"longitude": -6.2603,
"locationName": "Dollymount Beach",
"scheduledDate": "2024-03-01T10:00:00.000Z",
"duration": 180,
"status": "upcoming",
"participantCount": 20,
"organizer": {
"id": "uuid",
"username": "johndoe",
"avatarUrl": "https://..."
},
"registrations": []
}
]
}
```
**Error Responses:**
- `500` - Internal server error
---
### 5. Update Notification Preferences
**Endpoint:** `PATCH /api/users/:id/preferences`
**Description:** Update user's notification preferences and areas of interest.
**Authentication:** Required (users can only update their own preferences)
**Parameters:**
- `id` (path) - User database ID
**Request Body:**
```json
{
"notificationEmail": true,
"notificationInApp": true,
"areasOfInterest": [
{
"lat": 53.3498,
"lng": -6.2603,
"radius": 10
},
{
"lat": 51.8985,
"lng": -8.4756,
"radius": 25
}
]
}
```
**Field Validation:**
- `radius` must be between 1 and 50 kilometers
- Each area must have `lat`, `lng`, and `radius`
**Response:**
```json
{
"id": "uuid",
"notificationEmail": true,
"notificationInApp": true,
"areasOfInterest": [
{
"lat": 53.3498,
"lng": -6.2603,
"radius": 10
}
]
}
```
**Error Responses:**
- `400` - Validation error (invalid radius, missing fields, etc.)
- `401` - Not authenticated
- `403` - Cannot update another user's preferences
- `404` - User not found
- `500` - Internal server error
---
### 6. Calculate Impact Score
**Endpoint:** `POST /api/users/:id/calculate-impact`
**Description:** Calculate and update the user's impact score based on their contributions.
**Authentication:** Not required
**Parameters:**
- `id` (path) - User database ID
**Impact Score Formula:**
```
Impact Score = (reports × 10) + (events attended × 20) + (events organized × 50)
```
**Response:**
```json
{
"impactScore": 150
}
```
**Error Responses:**
- `404` - User not found
- `500` - Internal server error
---
## Usage Examples
### Get User Profile
```bash
curl http://localhost:3005/api/users/abc-123
```
### Update Profile
```bash
curl -X PATCH http://localhost:3005/api/users/abc-123 \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"firstName": "John",
"lastName": "Doe",
"username": "johndoe"
}'
```
### Update Notification Preferences
```bash
curl -X PATCH http://localhost:3005/api/users/abc-123/preferences \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"notificationEmail": true,
"notificationInApp": true,
"areasOfInterest": [
{
"lat": 53.3498,
"lng": -6.2603,
"radius": 10
}
]
}'
```
### Calculate Impact Score
```bash
curl -X POST http://localhost:3005/api/users/abc-123/calculate-impact
```
## Frontend Integration
The frontend service (`src/services/userService.ts`) provides TypeScript methods for all endpoints:
```typescript
import { userService } from '@/services/userService';
// Get profile
const profile = await userService.getProfile(userId);
// Update profile
const updated = await userService.updateProfile(userId, {
firstName: 'John',
lastName: 'Doe',
username: 'johndoe'
});
// Get user's reports
const reports = await userService.getUserReports(userId);
// Get user's events
const events = await userService.getUserEvents(userId);
// Update notification preferences
const preferences = await userService.updateNotificationPreferences(userId, {
notificationEmail: true,
notificationInApp: true,
areasOfInterest: [
{ lat: 53.3498, lng: -6.2603, radius: 10 }
]
});
// Calculate impact score
const { impactScore } = await userService.calculateImpactScore(userId);
```
## Requirements Satisfied
This implementation satisfies the following requirements from the specification:
- **Requirement 6.3**: User profile display showing submitted reports and events attended
- **Requirement 6.4**: Impact score calculation based on contributions
- **Requirement 6.5**: User profile showing map of submitted report locations (data provided via reports endpoint)
- **Requirement 10.4**: Notification preferences with area of interest configuration (1-50km radius)
## Notes
- User authentication is handled by Clerk
- Users are automatically created in the database when they first interact with the API
- The `areasOfInterest` field is stored as JSON in the database
- Impact scores are calculated on-demand but can be cached for performance
- All endpoints follow RESTful conventions
- Error responses follow a consistent format with error codes and timestamps