This is a Node.js backend for the Treegens video upload application that uploads videos to IPFS via Filebase and stores metadata in MongoDB.
- Video upload to IPFS via Filebase S3-compatible API
- GPS coordinates tracking for video uploads
- User management system with wallet address authentication
- MongoDB metadata storage with proper IPFS CID extraction
- Content moderation workflow with status tracking
- AWS SDK v3 implementation
- Rate limiting and error handling
- Docker containerization
- Follow the Single Responsibility Principle - Each module, class, or function should have one clear purpose
- Use a consistent directory structure - Group related code together (e.g., features, components, services, etc.)
- Keep functions small and focused - Aim for functions under 20-30 lines that do one thing well
- Don't over engineer while development - Try to not make the functions overly complicated
- Use the KISS coding principle (Keep It Simple, Stupid) while developing the project, to make it easily maintainable.
- Write self-documenting code - Use meaningful names for variables, functions, and classes
- Document public APIs - Include clear comments for interfaces and complex logic
- Maintain an up-to-date README - Include setup instructions, architecture overview, and development workflow
- Define and enforce coding standards - Use linters and formatters (ESLint, Prettier, etc.)
- Avoid duplication - Follow the DRY principle (Don't Repeat Yourself)
- Handle errors consistently - Implement proper error handling and logging
- Design for modularity - Create loosely coupled, highly cohesive components
- Use dependency injection with ENV variables for configuration
- Follow established patterns - Use design patterns appropriate for your technology stack
- Runtime: Node.js
- Framework: Express.js
- Database: MongoDB with Mongoose ODM
- File Storage: Filebase (IPFS) via S3-compatible API
- Cloud SDK: AWS SDK v3
- File Processing: Multer
- Containerization: Docker
{
"@aws-sdk/client-s3": "^3.540.0",
"@aws-sdk/s3-request-presigner": "^3.540.0",
"express": "^4.18.2",
"mongoose": "^7.5.0",
"multer": "^1.4.5-lts.1",
"cors": "^2.8.5",
"helmet": "^7.0.0",
"joi": "^17.9.2",
"uuid": "^9.0.0"
}{
originalFilename: String (required), // Original video filename
ipfsHash: String (required, unique), // S3 ETag/upload hash
videoCID: String (required, unique), // Actual IPFS CID for gateways
uploadTimestamp: Date (default: now), // Upload timestamp
type: String (optional, enum: ['before', 'after'], default: 'before'), // Video type for comparison
userWalletAddress: String (optional, indexed), // User wallet address
status: String (optional, enum: ['rejected', 'pending', 'approved'], default: 'pending'), // Content moderation status
submissionId: String (optional), // Unique submission identifier
treesPlanted: Number (optional, min: 0), // Number of trees planted (required when type='after')
treetype: String (optional), // Type of tree planted
gpsCoordinates: {
latitude: Number (required, -90 to 90),
longitude: Number (required, -180 to 180)
}
}{
walletAddress: String (required, unique, indexed), // Blockchain wallet address
name: String (optional), // User display name
ensName: String (optional), // ENS domain name
phone: String (optional), // Phone number
experience: String (optional, max 500 chars), // User experience description
treesPlanted: Number (optional, default: 0), // Number of trees planted
tokensClaimed: Number (optional, default: 0), // Tokens claimed by user
// Automatic timestamps: createdAt, updatedAt
}{ uploadTimestamp: -1 }- For chronological queries{ ipfsHash: 1 }- For lookup by upload hash{ videoCID: 1 }- For lookup by IPFS CID{ userWalletAddress: 1 }- For user-specific video queries
{ walletAddress: 1 }- Unique index for wallet lookups{ createdAt: -1 }- For chronological user queries
GET /
Response: Basic API information (name, version, status, timestamp)
GET /health
Response: Comprehensive health status of all services (MongoDB, Filebase)
Status codes: 200 (OK), 503 (DEGRADED), 500 (UNHEALTHY)
GET /health/s3-test
Response: Filebase S3 connection test with status and timestamp
POST /api/users
Content-Type: application/json
Body:
{
"walletAddress": "0x1234567890abcdef1234567890abcdef12345678", // Required
"name": "John Doe", // Optional
"ensName": "johndoe.eth", // Optional
"phone": "+1234567890", // Optional
"experience": "Experienced tree planter with 5+ years" // Optional
}
Response (Create):
{
"message": "User created successfully",
"data": {
"user": {
"_id": "ObjectId",
"walletAddress": "0x1234567890abcdef1234567890abcdef12345678",
"name": "John Doe",
"ensName": "johndoe.eth",
"phone": "+1234567890",
"experience": "Experienced tree planter with 5+ years",
"treesPlanted": 0,
"tokensClaimed": 0,
"createdAt": "2025-07-22T12:00:00.000Z",
"updatedAt": "2025-07-22T12:00:00.000Z"
},
"action": "created"
}
}
Response (Update):
{
"message": "User updated successfully",
"data": {
"user": { ... },
"action": "updated"
}
}
GET /api/users/:walletAddress
Response:
{
"message": "User retrieved successfully",
"data": {
"_id": "ObjectId",
"walletAddress": "0x1234567890abcdef1234567890abcdef12345678",
"name": "John Doe",
"ensName": "johndoe.eth",
"treesPlanted": 5,
"tokensClaimed": 100,
"createdAt": "2025-07-22T12:00:00.000Z",
"updatedAt": "2025-07-22T15:30:00.000Z"
}
}
GET /api/users?page=1&limit=10
Response:
{
"message": "Users retrieved successfully",
"data": {
"users": [
{
"_id": "ObjectId",
"walletAddress": "0x1234567890abcdef1234567890abcdef12345678",
"name": "John Doe",
"treesPlanted": 5,
"tokensClaimed": 100
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 50,
"pages": 5
}
}
}
PUT /api/users/:walletAddress/stats
Content-Type: application/json
Body:
{
"treesPlanted": 10,
"tokensClaimed": 250
}
Response:
{
"message": "User stats updated successfully",
"data": {
"_id": "ObjectId",
"walletAddress": "0x1234567890abcdef1234567890abcdef12345678",
"treesPlanted": 10,
"tokensClaimed": 250,
"updatedAt": "2025-07-22T16:00:00.000Z"
}
}
GET /api/users/leaderboard/trees-planted?page=1&limit=10
Response:
{
"message": "Trees planted leaderboard retrieved successfully",
"data": {
"users": [
{
"_id": "ObjectId",
"walletAddress": "0x1234567890abcdef1234567890abcdef12345678",
"name": "John Doe",
"ensName": "johndoe.eth",
"treesPlanted": 18000,
"rank": 1,
"createdAt": "2025-07-31T12:00:00.000Z"
},
{
"_id": "ObjectId",
"walletAddress": "0xabcdef1234567890abcdef1234567890abcdef12",
"name": "Jane Smith",
"ensName": "janesmith.eth",
"treesPlanted": 14500,
"rank": 2,
"createdAt": "2025-07-30T10:30:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 25,
"pages": 3
}
}
}
POST /api/videos/upload
Content-Type: multipart/form-data
Fields:
- video: File (required) - Video file
- userWalletAddress: String (optional) - User wallet address (defaults to null)
- latitude: Number (required) - GPS latitude
- longitude: Number (required) - GPS longitude
- type: String (optional) - 'before' or 'after' (defaults to 'before')
- status: String (optional) - 'rejected', 'pending', or 'approved' (defaults to 'pending')
- submissionId: String (optional) - Unique submission identifier
- treesPlanted: Number (optional, required when type='after') - Number of trees planted (min: 0)
- treetype: String (optional) - Type of tree planted
Response:
{
"message": "Video uploaded successfully to IPFS",
"data": {
"videoId": "ObjectId",
"ipfsHash": "upload_hash",
"videoCID": "QmXXXXXX...",
"filebaseUrl": "https://...",
"publicUrl": "https://ipfs.io/ipfs/QmXXXXXX...",
"uploadTimestamp": "2025-07-15T12:05:16.169Z",
"type": "before",
"status": "pending"
}
}
GET /api/videos/user/:userWalletAddress?page=1&limit=10
Response:
{
"message": "User videos retrieved successfully",
"data": {
"videos": [
{
"videoId": "ObjectId",
"videoCID": "QmXXXXXX...",
"type": "before",
"uploadTimestamp": "2025-07-15T12:05:16.169Z"
},
{
"videoId": "ObjectId",
"videoCID": "QmYYYYYY...",
"type": "after",
"uploadTimestamp": "2025-07-20T15:30:45.123Z"
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 2
}
}
}
(Rest of the file remains the same...)
- Added
typefield: Enum ['before', 'after'] with default 'before' - Added
userWalletAddressfield: Optional indexed field for user identification - Added
statusfield: Enum ['rejected', 'pending', 'approved'] with default 'pending' - Backward Compatible: Existing uploads continue to work without requiring new fields
- Migration v1.1.0: Non-destructive migration adds userWalletAddress index
- Migration v1.2.0: Non-destructive migration adds status field support
- User Model: Complete user profile management with wallet address as primary key
- CRUD Operations: Create, read, update user profiles with extensible schema
- Stats Tracking: Built-in fields for trees planted and tokens claimed
- Wallet Integration: Designed for blockchain wallet authentication
- Migration v1.3.0: Users collection with proper indexes and constraints
-
Migration Duplicate Key Error:
- Fixed E11000 error when migration runs multiple times
- Added robust index checking to prevent conflicts
- Migration now idempotent and safe to run repeatedly
-
Frontend Upload Compatibility:
- Made
typeanduserWalletAddressoptional in validation - Added graceful handling of missing fields in service layer
- Existing frontend code works without modifications
- Made
-
Missing Service Method:
- Implemented
getVideosByUser()method with pagination - Added helper flags:
hasBeforeVideo,hasAfterVideo - Full user video management support
- Implemented
- Backward Compatible: Old frontend uploads work seamlessly
- Future Ready: Support for before/after video workflow
- Content Moderation: Built-in status field for approval workflow
- User Management: Complete user profile system with wallet authentication
- Stats Tracking: User progress tracking with trees planted and tokens claimed
- Production Stable: No breaking changes to existing data
- Enhanced UX: User video tracking with type and status indicators
- Extensible Schema: User model designed for future feature additions
// Upload endpoint - NEW FIELDS OPTIONAL for backward compatibility
POST /api/videos/upload
{
video: File (required),
latitude: Number (required),
longitude: Number (required),
userWalletAddress: String (optional), // If not provided, defaults to null
type: String (optional), // If not provided, defaults to 'before'
status: String (optional), // If not provided, defaults to 'pending'
submissionId: String (optional), // Unique submission identifier
treesPlanted: Number (optional), // Required when type='after', min: 0
treetype: String (optional) // Type of tree planted
}// Create/Update user endpoint
POST /api/users
{
walletAddress: String (required, 1-200 chars), // Blockchain wallet address
name: String (optional, 1-100 chars), // User display name
ensName: String (optional, 1-100 chars), // ENS domain name
phone: String (optional, 1-20 chars), // Phone number
experience: String (optional, 1-500 chars) // Experience description
}
// Get user endpoint
GET /api/users/:walletAddress
// walletAddress: String (required, 1-200 chars)
// Update user stats endpoint
PUT /api/users/:walletAddress/stats
{
treesPlanted: Number (optional, >= 0), // Number of trees planted
tokensClaimed: Number (optional, >= 0) // Tokens claimed by user
}- Added status field:
/api/videos/uploadendpoint now accepts optionalstatusfield - Enhanced validation: Updated input validation to support status field with enum values
- Database migration v1.2.0: Added support for content moderation workflow
- Backward compatibility maintained: All existing uploads continue to work without changes
- Default behavior: New uploads default to 'pending' status for moderation review
- Complete user endpoints: Added full CRUD operations for user management
- Wallet-based authentication: Users identified by unique wallet addresses
- Extensible user profiles: Support for name, ENS, phone, experience fields
- Stats tracking: Built-in support for trees planted and tokens claimed
- Database migration v1.3.0: Created users collection with proper indexes
- Validation middleware: Comprehensive input validation for all user endpoints
- Service layer: Robust user service with create/update upsert logic
POST /api/users- Create or update user profileGET /api/users/:walletAddress- Fetch user by wallet addressGET /api/users- List all users with paginationPUT /api/users/:walletAddress/stats- Update user statisticsDELETE /api/users/:walletAddress- Delete user (admin)
- New Leaderboard Endpoint:
GET /api/users/leaderboard/trees-plantedwith full pagination support - Ranked Results: Users automatically receive rank numbers (#1, #2, #3, etc.) based on trees planted
- Filtered Data: Only displays users with
treesPlanted > 0to show active planters - Smart Sorting: Primary sort by trees planted (descending), secondary by user creation date
- Complete User Info: Returns name, ENS name, wallet address, trees planted, and rank
- Public Access: No authentication required for leaderboard viewing
- Swagger Documentation: Full API documentation with request/response schemas
- Pagination Support: Standard
pageandlimitquery parameters (default: 10 per page) - Efficient Queries: Uses MongoDB indexes for optimal performance
- Frontend Ready: API response format matches leaderboard UI requirements exactly
- Scalable Design: Handles large user bases with efficient pagination
// Get top 10 users (default)
GET /api/users/leaderboard/trees-planted
// Get specific page with custom limit
GET /api/users/leaderboard/trees-planted?page=2&limit=20
// Response format:
{
"message": "Trees planted leaderboard retrieved successfully",
"data": {
"users": [
{
"_id": "user_id",
"walletAddress": "0x...",
"name": "User Name",
"ensName": "user.treegen.eth",
"treesPlanted": 18000,
"rank": 1,
"createdAt": "2025-07-31T..."
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 50,
"pages": 5
}
}
}- UserService Method:
getTreesPlantedLeaderboard(page, limit)handles business logic - Route Handler:
/api/users/leaderboard/trees-plantedendpoint with query parameter validation - Efficient Aggregation: MongoDB aggregation pipeline sums
treesPlantedfrom Videos collection - Cross-Collection Lookup: Joins Videos and Users collections for complete user data
- Single Source of Truth: Trees planted data aggregated directly from Videos collection
- Rank Calculation: Automatic rank assignment accounting for pagination offset
// Efficient MongoDB aggregation pipeline
const pipeline = [
// 1. Match videos with trees planted > 0
{ $match: { treesPlanted: { $gt: 0 }, userWalletAddress: { $ne: null } } },
// 2. Group by userId and sum trees planted
{ $group: {
_id: "$userWalletAddress",
totalTreesPlanted: { $sum: "$treesPlanted" },
videoCount: { $sum: 1 }
}},
// 3. Lookup user details from Users collection
{ $lookup: {
from: 'users',
let: { userWalletAddress: '$_id' },
pipeline: [/* Match by ObjectId or wallet address */],
as: 'userDetails'
}},
// 4. Sort by trees planted descending
{ $sort: { treesPlanted: -1, createdAt: -1 } }
];- Single Query: One aggregation query vs multiple database operations
- Always Accurate: Real-time data from Videos collection, no sync issues
- Scalable: Efficient MongoDB aggregation with indexed queries
- Maintainable: Single source of truth, no data duplication
- Fixed Video Fetching Route:
/api/videos/user/:userWalletAddressnow ignores route parameter and always returns authenticated user's videos for security - Automatic User Identification: Video uploads use authenticated user's wallet address automatically, no userWalletAddress parameter needed from frontend
- Case-Insensitive Wallet Handling: All user service methods normalize wallet addresses to lowercase for consistency
- Swagger Documentation Updated: Clarified that userWalletAddress route parameter is ignored, always returns authenticated user's data
- Backward Compatibility: Frontend can continue using existing API calls without modification
-
Smart Route Handling:
/api/videos/user/:userWalletAddressextracts wallet address from JWT token instead of route parameter- Prevents cross-user data access regardless of frontend parameter
- Maintains API compatibility while enhancing security
-
Case Normalization:
- All UserService methods normalize wallet addresses to lowercase
- Consistent with AuthService token storage format
- Prevents case sensitivity mismatches between frontend and backend
-
Removed Authorization Middleware Conflicts:
- Removed
requireWalletOwnershipfrom video fetching route - Uses direct wallet address extraction from JWT token instead
- Simplified and more reliable authorization approach
- Removed
- "Access denied" errors: Fixed wallet address case sensitivity mismatches
- "Failed to load videos": Route now uses correct wallet address from JWT token
- Empty submission lists: Videos are now properly associated with lowercase wallet addresses
- Frontend compatibility: Existing frontend code works without modification
- Simplified Authentication: Removed Gmail and Thirdweb authentication methods, keeping only wallet-based authentication
- Enhanced Security: Made challenge endpoint mandatory to prevent signature bypass vulnerabilities
- Challenge Validation: Implemented nonce-based challenge system with expiration and single-use validation
- Wallet-Based Authorization: Added
requireWalletOwnershipmiddleware to ensure users can only access their own resources - User-Specific Endpoints: Applied authorization to video and user endpoints so users can only access their own data
- Automatic User Identification: Video uploads now automatically use authenticated user's wallet address from JWT token
- Updated Documentation: Swagger documentation reflects simplified wallet-only authentication flow
- Backward Compatibility: Existing JWT tokens continue to work without modification
-
Challenge Bypass Prevention:
- Challenge messages must be generated via
/api/auth/challengeendpoint first - Each challenge has unique nonce and 10-minute expiration
- Challenges are single-use and deleted after successful authentication
- Challenge messages must be generated via
-
Resource Access Control:
- Users can only fetch videos associated with their wallet address
- User stats can only be updated by the wallet owner
- Cross-user data access has been eliminated
-
Simplified Attack Surface:
- Removed multiple authentication providers that could introduce vulnerabilities
- Single wallet-based authentication flow is easier to audit and secure
- JWT tokens contain wallet address for consistent authorization
- Generate Challenge:
GET /api/auth/challenge/:walletAddress- Get nonce-based challenge message - Sign Challenge: User signs the exact challenge message with their wallet
- Authenticate:
POST /api/auth/signin/wallet- Submit signature for JWT token - Access Resources: Use JWT token in Authorization header for authenticated endpoints
- Added
submissionIdfield: Optional unique submission identifier for tracking video submissions - Added
treesPlantedfield: Number tracking with conditional validation (required when type='after') - Added
treetypefield: Optional tree species/type identification - Enhanced validation: Conditional validation ensures treesPlanted is required for 'after' videos
- Database migration v1.4.0: Non-destructive migration adds new optional fields
- Backward compatibility maintained: All existing uploads continue to work seamlessly
- Submission Tracking: Link videos to specific tree planting submissions
- Tree Count Validation: Enforce tree count reporting for 'after' videos
- Species Documentation: Optional tree type classification
- Data Integrity: Robust validation prevents invalid submissions
- Analytics Ready: Schema designed for tree planting analytics and reporting
- Complete JWT authentication: Token-based authentication with wallet and Gmail support
- Thirdweb integration: Full compatibility with thirdweb authentication flows
- Multi-provider support: Wallet signature verification and Google OAuth
- Protected endpoints: All video uploads and user management require authentication
- Database migration v1.5.0: Added authentication fields to user model
- Security features: Token expiration, signature validation, user session management
- Swagger/OpenAPI 3.0: Complete API documentation with interactive interface
- Available at
/docs: Comprehensive documentation accessible via web interface - Schema definitions: Complete request/response models for all endpoints
- Authentication support: JWT Bearer token authentication in Swagger UI
- Interactive testing: Try-it-out functionality for all endpoints
- Custom styling: Branded Swagger UI with Treegens theme
- Interactive API Testing: Developers can test all endpoints directly from
/docs - Authentication Integration: JWT tokens can be entered once and used for all protected endpoints
- Schema Validation: Complete request/response documentation with examples
- Endpoint Discovery: All available endpoints clearly documented with descriptions
- Error Response Standards: Consistent error handling documentation across all endpoints
POST /api/auth/challenge- Generate challenge message for wallet signingPOST /api/auth/signin/wallet- Sign in with wallet signaturePOST /api/auth/signin/thirdweb- Sign in with thirdweb payloadPOST /api/auth/signin/gmail- Sign in with Google OAuthGET /api/auth/verify- Verify JWT token validityPOST /api/auth/signout- Sign out and invalidate tokenGET /api/auth/me- Get current user information
- All video upload endpoints now require authentication
- User management endpoints have comprehensive Swagger documentation
- Health check endpoints include detailed status response schemas
- Root endpoint now includes link to API documentation
- Current User Highlighting: Leaderboard endpoint now accepts optional
userWalletAddressparameter - Individual User Rankings: Returns current user's position and statistics in leaderboard
- Efficient Aggregation: Single MongoDB aggregation calculates both leaderboard and user ranking
- Backward Compatible: Existing leaderboard functionality unchanged when no userWalletAddress provided
- Case-Insensitive Matching: Robust wallet address normalization for consistent user identification
GET /api/users/leaderboard/trees-planted?page=1&limit=10&userWalletAddress=0x1234...
Query Parameters:
- page: Number (optional, default: 1) - Page number for pagination
- limit: Number (optional, default: 10) - Users per page (max: 50)
- userWalletAddress: String (optional) - Wallet address to highlight current user
Response with Current User:
{
"message": "Trees planted leaderboard retrieved successfully",
"data": {
"users": [
{
"_id": "ObjectId",
"walletAddress": "0x1234567890abcdef1234567890abcdef12345678",
"name": "John Doe",
"treesPlanted": 2000,
"videoCount": 2,
"rank": 1,
"createdAt": "2025-07-31T12:00:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 50,
"pages": 5
},
"currentUser": {
"_id": "ObjectId",
"walletAddress": "0x1234567890abcdef1234567890abcdef12345678",
"name": "John Doe",
"treesPlanted": 2000,
"videoCount": 2,
"rank": 1,
"createdAt": "2025-07-31T12:00:00.000Z"
}
}
}
- Cross-Collection Lookup: Efficiently joins Videos and Users collections
- Trees Planted Calculation: Aggregates tree counts from video submissions
- Ranking Algorithm: Calculates user positions with tie-breaking by creation date
- Performance Optimized: Single aggregation query handles both leaderboard and user ranking
- Current User Position:
currentUserfield provides user's exact ranking and statistics - Leaderboard Data:
usersarray contains paginated top performers - Complete Statistics: Each user includes trees planted, video count, and global rank
- Flexible Pagination: Standard pagination with configurable page size
- Optional User Highlighting: Works with or without userWalletAddress parameter
- Videos Collection: Source of trees planted data via
treesPlantedfield - Users Collection: Provides user profiles and metadata
- Wallet Address Matching: Case-insensitive matching ensures reliable user identification
- Null Handling: Gracefully handles users without profile data
The enhanced leaderboard endpoint provides all necessary data for frontend implementations:
- Current User Badge: Highlight authenticated user's position
- Ranking Display: Show user's global rank and statistics
- Trees Planted Count: Display user's total contribution
- Video Submissions: Show number of verified submissions
- Leaderboard Context: User's position within overall rankings
Last updated: July 31, 2025 Project Status: Production Ready with Enhanced Trees Planted Leaderboard & Current User Ranking