# User API Implementation (User Management) This document describes the implementation of the User Management API endpoints for the MaplePress frontend, integrated with the MaplePress backend API. ## Overview The User API endpoints manage users within a tenant (organization). All operations require both JWT authentication and tenant context via the `X-Tenant-ID` header. Users are scoped to tenants, providing data isolation in the multi-tenant architecture. ## Backend API Endpoints ### Create User **Endpoint**: `POST /api/v1/users` **Authentication**: Required (JWT token) **Tenant Context**: Required (`X-Tenant-ID` header) ### Get User by ID **Endpoint**: `GET /api/v1/users/{id}` **Authentication**: Required (JWT token) **Tenant Context**: Required (`X-Tenant-ID` header) **Documentation**: `/cloud/maplepress-backend/docs/API.md` (lines 560-660) ## Request/Response Structures ### Create User Request ```json { "email": "jane@techstart.com", "name": "Jane Smith" } ``` **Headers Required:** - `Content-Type: application/json` - `Authorization: JWT {access_token}` - `X-Tenant-ID: {tenant_id}` (required in development mode) ### User Response ```json { "id": "950e8400-e29b-41d4-a716-446655440000", "email": "jane@techstart.com", "name": "Jane Smith", "created_at": "2024-10-24T00:00:00Z", "updated_at": "2024-10-24T00:00:00Z" } ``` ## Frontend Implementation ### UserService (`src/services/API/UserService.js`) Handles all user management operations with tenant context. **Key Features:** - Create new users within a tenant - Retrieve user by ID within tenant context - Client-side validation (email and name) - Tenant context support via X-Tenant-ID header - Response transformation (snake_case to camelCase) - User-friendly error message mapping **Methods:** #### `createUser(userData, tenantId)` Create a new user within a tenant. ```javascript import UserService from './services/API/UserService'; const user = await UserService.createUser({ email: "jane@techstart.com", name: "Jane Smith" }, "850e8400-..."); // tenant ID console.log(user); // Output: // { // id: "950e8400-...", // email: "jane@techstart.com", // name: "Jane Smith", // createdAt: Date object // } ``` **Parameters:** - `userData.email` (string, required): User's email address - `userData.name` (string, required): User's full name - `tenantId` (string, optional): Tenant ID for X-Tenant-ID header **Returns:** ```javascript { id: string, // User ID (UUID) email: string, // User email name: string, // User name createdAt: Date // Creation timestamp } ``` **Throws:** - "User data is required" - If userData is missing - "User email is required" - If email is missing - "User name is required" - If name is missing - "Invalid email format" - If email format is invalid - "User email already exists in this tenant." - Email conflict (409) - "Tenant context required. Please provide X-Tenant-ID header." - Missing tenant context - "Authentication required. Please log in to continue." - Missing/invalid token #### `getUserById(userId, tenantId)` Retrieve user information by ID within tenant context. ```javascript const user = await UserService.getUserById("950e8400-...", "850e8400-..."); console.log(user.name); // "Jane Smith" ``` **Parameters:** - `userId` (string, required): User ID (UUID format) - `tenantId` (string, optional): Tenant ID for X-Tenant-ID header **Returns:** ```javascript { id: string, email: string, name: string, createdAt: Date, updatedAt: Date } ``` **Throws:** - "User ID is required" - If ID is missing - "Invalid user ID format" - If ID is not a valid UUID - "User not found in this tenant." - If user doesn't exist in tenant (404) - "Tenant context required." - Missing tenant context - "Authentication required." - Missing/invalid token #### `validateEmail(email)` Validate an email address format. ```javascript const result = UserService.validateEmail("jane@example.com"); console.log(result); // { valid: true, error: null } const invalid = UserService.validateEmail("invalid-email"); console.log(invalid); // { valid: false, error: "Invalid email format" } ``` **Returns:** `{ valid: boolean, error: string|null }` **Validation Rules:** - Required (non-empty) - Valid email format (`user@domain.com`) - Maximum 255 characters #### `validateName(name)` Validate a user name. ```javascript const result = UserService.validateName("Jane Smith"); console.log(result); // { valid: true, error: null } ``` **Returns:** `{ valid: boolean, error: string|null }` **Validation Rules:** - Required (non-empty) - Length: 2-100 characters #### `isValidUUID(uuid)` Check if a string is a valid UUID. ```javascript const isValid = UserService.isValidUUID("950e8400-e29b-41d4-a716-446655440000"); console.log(isValid); // true ``` **Returns:** `boolean` ## Important: Tenant Context All user operations require tenant context via the `X-Tenant-ID` header. This header can be provided in two ways: ### Option 1: Explicit Tenant ID Pass tenant ID to each method call: ```javascript const tenantId = "850e8400-..."; const user = await UserService.createUser(userData, tenantId); ``` ### Option 2: Automatic from Current User (Recommended) Enhance ApiClient to automatically add X-Tenant-ID from the current user's tenant: ```javascript // In ApiClient.js import { authManager } from './Services'; // Add tenant header automatically const tenant = authManager.getTenant(); if (tenant && tenant.id) { requestHeaders["X-Tenant-ID"] = tenant.id; } ``` Then use without explicit tenant ID: ```javascript // Tenant ID automatically added from current user const user = await UserService.createUser(userData); ``` ## Data Flow ### Create User Flow ``` User provides email and name ↓ UserService.createUser() ↓ Validate email and name (client-side) ↓ ApiClient.post() with JWT token + X-Tenant-ID ↓ Token automatically refreshed if needed ↓ POST /api/v1/users with tenant context ↓ Backend validates email uniqueness within tenant ↓ Backend creates user in database (scoped to tenant) ↓ Backend returns user data ↓ UserService transforms response ↓ Component receives user data ``` ### Get User Flow ``` Component needs user data ↓ UserService.getUserById() ↓ Validate UUID format ↓ ApiClient.get() with JWT token + X-Tenant-ID ↓ Token automatically refreshed if needed ↓ GET /api/v1/users/{id} with tenant context ↓ Backend retrieves user from database (tenant-scoped) ↓ Backend returns user data ↓ UserService transforms response ↓ Component receives user data ``` ## Error Handling ### Error Types | Error Condition | Response | Frontend Behavior | |----------------|----------|-------------------| | Missing authentication | 401 Unauthorized | "Authentication required." | | Missing tenant context | 400 Bad Request | "Tenant context required." | | Invalid user data | 400 Bad Request | Specific validation error | | Email already exists | 409 Conflict | "User email already exists in this tenant." | | User not found | 404 Not Found | "User not found in this tenant." | | Server error | 500 Internal Server Error | Generic error message | ## Usage Examples ### Create a New User ```javascript import React, { useState } from 'react'; import UserService from '../../services/API/UserService'; import MeService from '../../services/API/MeService'; function CreateUserForm() { const [email, setEmail] = useState(''); const [name, setName] = useState(''); const [error, setError] = useState(''); const [loading, setLoading] = useState(false); const handleSubmit = async (e) => { e.preventDefault(); setError(''); setLoading(true); // Validate before sending const emailValidation = UserService.validateEmail(email); if (!emailValidation.valid) { setError(emailValidation.error); setLoading(false); return; } const nameValidation = UserService.validateName(name); if (!nameValidation.valid) { setError(nameValidation.error); setLoading(false); return; } try { // Get current user's tenant ID const profile = await MeService.getMe(); const tenantId = profile.tenantId; // Create user in current tenant const user = await UserService.createUser( { email, name }, tenantId ); console.log("User created:", user); // Reset form or redirect setEmail(''); setName(''); } catch (err) { setError(err.message); } finally { setLoading(false); } }; return (
setEmail(e.target.value)} placeholder="user@example.com" maxLength={255} />
setName(e.target.value)} placeholder="John Doe" maxLength={100} />
{error &&

{error}

}
); } export default CreateUserForm; ``` ### Display User Profile ```javascript import React, { useEffect, useState } from 'react'; import UserService from '../../services/API/UserService'; import MeService from '../../services/API/MeService'; function UserProfile({ userId }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); useEffect(() => { const fetchUser = async () => { try { // Get tenant context const profile = await MeService.getMe(); // Get user data const data = await UserService.getUserById(userId, profile.tenantId); setUser(data); } catch (err) { setError(err.message); } finally { setLoading(false); } }; fetchUser(); }, [userId]); if (loading) return
Loading user...
; if (error) return
Error: {error}
; if (!user) return null; return (

{user.name}

Email: {user.email}

ID: {user.id}

Created: {user.createdAt.toLocaleDateString()}

Updated: {user.updatedAt.toLocaleDateString()}

); } export default UserProfile; ``` ### List Users in Tenant (Helper) ```javascript // Note: There's no list endpoint in the API yet // This is a pattern for when it's added async function listUsersInTenant(tenantId) { // This would call GET /api/v1/users with tenant context // For now, you can only get users by ID console.log("List endpoint not yet available"); } ``` ## Testing ### Test Create User ```javascript // In browser console after login import UserService from './services/API/UserService'; import MeService from './services/API/MeService'; // Get tenant context const profile = await MeService.getMe(); const tenantId = profile.tenantId; // Validate email const validation = UserService.validateEmail("test@example.com"); console.log("Email valid:", validation.valid); // Create user const user = await UserService.createUser({ email: "test@example.com", name: "Test User" }, tenantId); console.log("Created user:", user); ``` ### Test Get User ```javascript // Using user ID from creation const user = await UserService.getUserById(user.id, tenantId); console.log("Retrieved user:", user); ``` ### Test with curl ```bash # 1. Login and get access token ACCESS_TOKEN=$(curl -X POST http://localhost:8000/api/v1/login \ -H "Content-Type: application/json" \ -d '{ "email": "test@example.com", "password": "SecurePass123!" }' | jq -r '.access_token') # Get tenant ID from login response TENANT_ID=$(curl -X GET http://localhost:8000/api/v1/me \ -H "Authorization: JWT $ACCESS_TOKEN" | jq -r '.tenant_id') # 2. Create user curl -X POST http://localhost:8000/api/v1/users \ -H "Content-Type: application/json" \ -H "Authorization: JWT $ACCESS_TOKEN" \ -H "X-Tenant-ID: $TENANT_ID" \ -d '{ "email": "newuser@example.com", "name": "New User" }' | jq # 3. Get user by ID (use ID from creation response) USER_ID="950e8400-..." curl -X GET "http://localhost:8000/api/v1/users/$USER_ID" \ -H "Authorization: JWT $ACCESS_TOKEN" \ -H "X-Tenant-ID: $TENANT_ID" | jq ``` ## Multi-Tenant Isolation Users are scoped to tenants for data isolation: ```javascript // Users in tenant A cannot see users in tenant B const tenantA = "850e8400-..."; const tenantB = "950e8400-..."; // Create user in tenant A const userA = await UserService.createUser(userData, tenantA); // Try to get user A from tenant B context (will fail - not found) try { const user = await UserService.getUserById(userA.id, tenantB); } catch (error) { console.log("Cannot access user from different tenant"); } ``` ## Related Files ### Created Files ``` src/services/API/UserService.js docs/USER_API.md ``` ### Backend Reference Files ``` cloud/maplepress-backend/docs/API.md (lines 560-660) ``` ## Related Documentation - [TENANT_API.md](./TENANT_API.md) - Tenant management (parent context) - [ME_API.md](./ME_API.md) - Current user profile includes tenant ID - [REGISTRATION_API.md](./REGISTRATION_API.md) - Initial user creation - [FRONTEND_ARCHITECTURE.md](./FRONTEND_ARCHITECTURE.md) - Architecture overview - [README.md](./README.md) - Documentation index ## Summary The User API implementation provides: 1. **User Creation**: Create users within tenant context 2. **User Retrieval**: Get user by ID with tenant isolation 3. **Validation Helpers**: Client-side validation before API calls 4. **Tenant Context**: Multi-tenant data isolation 5. **Error Handling**: Clear error messages and graceful failures Essential for managing team members within organizations (tenants) in a multi-tenant SaaS application. --- **Last Updated**: October 30, 2024 **Frontend Version**: 0.0.0 **Documentation Version**: 1.0.0