diff --git a/docs/api/README.md b/docs/api/README.md new file mode 100644 index 0000000..5601309 --- /dev/null +++ b/docs/api/README.md @@ -0,0 +1,294 @@ +# PlantPal API Documentation + +Welcome to the PlantPal API documentation. This guide provides comprehensive information about our API endpoints, authentication, error handling, and integration examples. + +## Table of Contents + +1. [Authentication](#authentication) +2. [Endpoints](#endpoints) +3. [Error Handling](#error-handling) +4. [Integration Examples](#integration-examples) +5. [Testing Guide](#testing-guide) + +## Authentication + +PlantPal API uses JWT (JSON Web Tokens) for authentication. Here's how to authenticate: + +### Getting an API Key + +1. Register on PlantPal platform +2. Go to Developer Settings +3. Generate API Key + +### Using the API Key + +Include your API key in the request header: + +```http +Authorization: Bearer YOUR_API_KEY +``` + +### Token Refresh + +Tokens expire after 24 hours. Use the refresh endpoint to get a new token: + +```http +POST /api/auth/refresh +Authorization: Bearer YOUR_REFRESH_TOKEN +``` + +## Endpoints + +### Plant Detection + +#### Detect Plant Disease + +```http +POST /api/v1/plants/detect +Content-Type: multipart/form-data +Authorization: Bearer YOUR_API_KEY + +file: +``` + +Response: +```json +{ + "success": true, + "data": { + "plant_name": "Tomato", + "disease": "Early Blight", + "confidence": 0.95, + "treatment": { + "recommendations": [ + "Remove infected leaves", + "Apply fungicide", + "Improve air circulation" + ], + "products": [ + { + "name": "Organic Fungicide", + "type": "Natural", + "application": "Spray every 7 days" + } + ] + } + } +} +``` + +### Plant Care + +#### Get Care Schedule + +```http +GET /api/v1/plants/{plant_id}/care-schedule +Authorization: Bearer YOUR_API_KEY +``` + +Response: +```json +{ + "success": true, + "data": { + "watering_frequency": "Every 3 days", + "sunlight": "Partial shade", + "fertilizer": "Monthly", + "next_tasks": [ + { + "type": "watering", + "due_date": "2024-04-08", + "description": "Water thoroughly" + } + ] + } +} +``` + +### User Management + +#### Update User Profile + +```http +PUT /api/v1/users/profile +Authorization: Bearer YOUR_API_KEY +Content-Type: application/json + +{ + "name": "John Doe", + "email": "john@example.com", + "preferences": { + "notifications": true, + "language": "en" + } +} +``` + +Response: +```json +{ + "success": true, + "data": { + "id": "user_123", + "name": "John Doe", + "email": "john@example.com", + "preferences": { + "notifications": true, + "language": "en" + }, + "updated_at": "2024-04-05T10:30:00Z" + } +} +``` + +## Error Handling + +### Error Codes + +| Code | Description | +|------|-------------| +| 400 | Bad Request - Invalid input parameters | +| 401 | Unauthorized - Invalid or missing API key | +| 403 | Forbidden - Insufficient permissions | +| 404 | Not Found - Resource doesn't exist | +| 429 | Too Many Requests - Rate limit exceeded | +| 500 | Internal Server Error | + +### Error Response Format + +```json +{ + "success": false, + "error": { + "code": "ERROR_CODE", + "message": "Human readable error message", + "details": { + "field": "Additional error details" + } + } +} +``` + +## Integration Examples + +### Python + +```python +import requests + +API_KEY = 'your_api_key' +BASE_URL = 'https://api.plantpal.com/v1' + +def detect_plant_disease(image_path): + headers = { + 'Authorization': f'Bearer {API_KEY}' + } + + with open(image_path, 'rb') as image: + files = {'file': image} + response = requests.post( + f'{BASE_URL}/plants/detect', + headers=headers, + files=files + ) + + return response.json() + +# Usage +result = detect_plant_disease('plant_image.jpg') +print(result) +``` + +### JavaScript + +```javascript +const API_KEY = 'your_api_key'; +const BASE_URL = 'https://api.plantpal.com/v1'; + +async function detectPlantDisease(imageFile) { + const formData = new FormData(); + formData.append('file', imageFile); + + const response = await fetch(`${BASE_URL}/plants/detect`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${API_KEY}` + }, + body: formData + }); + + return await response.json(); +} + +// Usage +const imageFile = document.querySelector('input[type="file"]').files[0]; +detectPlantDisease(imageFile) + .then(result => console.log(result)) + .catch(error => console.error(error)); +``` + +## Testing Guide + +### Testing Endpoints + +1. **Setup Test Environment** + ```bash + # Install dependencies + npm install + + # Set environment variables + export PLANTPAL_API_KEY=your_test_api_key + export PLANTPAL_API_URL=https://api.plantpal.com/v1 + ``` + +2. **Run Tests** + ```bash + # Run all tests + npm test + + # Run specific test file + npm test -- tests/api/plants.test.js + ``` + +### Test Cases + +1. **Authentication Tests** + - Valid API key + - Invalid API key + - Expired token + - Token refresh + +2. **Plant Detection Tests** + - Valid image upload + - Invalid image format + - Image size limits + - Response format + +3. **Error Handling Tests** + - Rate limiting + - Invalid inputs + - Server errors + +### Example Test + +```javascript +const { expect } = require('chai'); +const { detectPlantDisease } = require('../src/api'); + +describe('Plant Detection API', () => { + it('should detect plant disease from valid image', async () => { + const result = await detectPlantDisease('test-image.jpg'); + expect(result.success).to.be.true; + expect(result.data).to.have.property('plant_name'); + expect(result.data).to.have.property('disease'); + }); + + it('should handle invalid image format', async () => { + try { + await detectPlantDisease('invalid.txt'); + } catch (error) { + expect(error.response.status).to.equal(400); + } + }); +}); +``` \ No newline at end of file diff --git a/docs/api/authentication.md b/docs/api/authentication.md new file mode 100644 index 0000000..89ddaf9 --- /dev/null +++ b/docs/api/authentication.md @@ -0,0 +1,162 @@ +# Authentication Guide + +## Overview +PlantPal API uses JWT (JSON Web Tokens) for authentication. This guide explains how to authenticate your requests and manage your API keys. + +## Getting Started + +### 1. Obtain API Keys +1. Sign up for a PlantPal account at https://plantpal.com/signup +2. Navigate to your dashboard +3. Go to Settings > API Keys +4. Click "Generate New API Key" +5. Save your API key securely - it won't be shown again + +### 2. Using Your API Key +Include your API key in the Authorization header of all requests: + +```http +Authorization: Bearer your_api_key_here +``` + +## Authentication Flow + +### 1. Initial Authentication +```http +POST /auth/login +Content-Type: application/json + +{ + "email": "your@email.com", + "password": "your_password" +} +``` + +Response: +```json +{ + "success": true, + "data": { + "access_token": "eyJhbGciOiJIUzI1NiIs...", + "refresh_token": "eyJhbGciOiJIUzI1NiIs...", + "expires_in": 3600 + } +} +``` + +### 2. Token Refresh +When your access token expires, use the refresh token to get a new one: + +```http +POST /auth/refresh +Content-Type: application/json + +{ + "refresh_token": "your_refresh_token" +} +``` + +Response: +```json +{ + "success": true, + "data": { + "access_token": "eyJhbGciOiJIUzI1NiIs...", + "expires_in": 3600 + } +} +``` + +## Security Best Practices + +1. **Never share your API keys** + - Keep your API keys secure + - Don't commit them to version control + - Use environment variables + +2. **Rotate keys regularly** + - Generate new keys every 90 days + - Revoke old keys immediately + +3. **Use HTTPS** + - All API requests must use HTTPS + - Never send API keys over HTTP + +4. **Implement rate limiting** + - Monitor your API usage + - Stay within rate limits + +## Error Handling + +### Common Authentication Errors + +| Error Code | Description | Solution | +|------------|-------------|----------| +| `AUTH_INVALID_CREDENTIALS` | Invalid email or password | Check your credentials | +| `AUTH_TOKEN_EXPIRED` | Access token has expired | Use refresh token to get new access token | +| `AUTH_INVALID_TOKEN` | Invalid or malformed token | Check token format | +| `AUTH_MISSING_TOKEN` | No token provided | Add Authorization header | + +Example error response: +```json +{ + "success": false, + "error": { + "code": "AUTH_INVALID_CREDENTIALS", + "message": "Invalid email or password", + "details": {} + } +} +``` + +## Code Examples + +### Python +```python +import requests + +API_KEY = 'your_api_key_here' +BASE_URL = 'https://api.plantpal.com/v1' + +headers = { + 'Authorization': f'Bearer {API_KEY}', + 'Content-Type': 'application/json' +} + +# Example API call +response = requests.get(f'{BASE_URL}/plants/detect', headers=headers) +``` + +### JavaScript +```javascript +const API_KEY = 'your_api_key_here'; +const BASE_URL = 'https://api.plantpal.com/v1'; + +const headers = { + 'Authorization': `Bearer ${API_KEY}`, + 'Content-Type': 'application/json' +}; + +// Example API call +fetch(`${BASE_URL}/plants/detect`, { + headers: headers +}) +.then(response => response.json()) +.then(data => console.log(data)); +``` + +## Rate Limits + +| Plan | Requests per minute | Requests per day | +|------|-------------------|-----------------| +| Free | 60 | 1,000 | +| Pro | 300 | 50,000 | +| Enterprise | Custom | Custom | + +## Support + +If you encounter any authentication issues: +1. Check the error message and code +2. Verify your API key is correct +3. Ensure you're using HTTPS +4. Contact support at support@plantpal.com \ No newline at end of file diff --git a/docs/api/error-handling.md b/docs/api/error-handling.md new file mode 100644 index 0000000..029e784 --- /dev/null +++ b/docs/api/error-handling.md @@ -0,0 +1,204 @@ +# Error Handling Guide + +## Overview +This guide explains how to handle errors in the PlantPal API. All API responses follow a consistent error format to help you identify and handle issues effectively. + +## Error Response Format + +All error responses follow this structure: + +```json +{ + "success": false, + "error": { + "code": "ERROR_CODE", + "message": "Human readable error message", + "details": { + // Additional error details specific to the error type + } + } +} +``` + +## Common Error Codes + +### Authentication Errors (1xx) + +| Code | Description | HTTP Status | +|------|-------------|-------------| +| `AUTH_INVALID_CREDENTIALS` | Invalid email or password | 401 | +| `AUTH_TOKEN_EXPIRED` | Access token has expired | 401 | +| `AUTH_INVALID_TOKEN` | Invalid or malformed token | 401 | +| `AUTH_MISSING_TOKEN` | No token provided | 401 | +| `AUTH_INSUFFICIENT_PERMISSIONS` | User lacks required permissions | 403 | + +### Validation Errors (2xx) + +| Code | Description | HTTP Status | +|------|-------------|-------------| +| `VALIDATION_REQUIRED_FIELD` | Required field is missing | 400 | +| `VALIDATION_INVALID_FORMAT` | Field format is invalid | 400 | +| `VALIDATION_INVALID_VALUE` | Field value is invalid | 400 | +| `VALIDATION_FILE_TOO_LARGE` | Uploaded file exceeds size limit | 400 | +| `VALIDATION_INVALID_FILE_TYPE` | Invalid file type | 400 | + +### Resource Errors (3xx) + +| Code | Description | HTTP Status | +|------|-------------|-------------| +| `RESOURCE_NOT_FOUND` | Requested resource not found | 404 | +| `RESOURCE_ALREADY_EXISTS` | Resource already exists | 409 | +| `RESOURCE_LOCKED` | Resource is locked | 423 | +| `RESOURCE_DELETED` | Resource has been deleted | 410 | + +### Rate Limit Errors (4xx) + +| Code | Description | HTTP Status | +|------|-------------|-------------| +| `RATE_LIMIT_EXCEEDED` | Too many requests | 429 | +| `RATE_LIMIT_RESET` | Rate limit reset time | 429 | + +### Server Errors (5xx) + +| Code | Description | HTTP Status | +|------|-------------|-------------| +| `SERVER_ERROR` | Internal server error | 500 | +| `SERVICE_UNAVAILABLE` | Service temporarily unavailable | 503 | +| `GATEWAY_TIMEOUT` | Gateway timeout | 504 | + +## Error Handling Examples + +### 1. Authentication Error +```json +{ + "success": false, + "error": { + "code": "AUTH_INVALID_CREDENTIALS", + "message": "Invalid email or password", + "details": { + "attempts_remaining": 4 + } + } +} +``` + +### 2. Validation Error +```json +{ + "success": false, + "error": { + "code": "VALIDATION_INVALID_FORMAT", + "message": "Invalid input format", + "details": { + "field": "email", + "expected": "valid email address", + "received": "invalid-email" + } + } +} +``` + +### 3. Resource Error +```json +{ + "success": false, + "error": { + "code": "RESOURCE_NOT_FOUND", + "message": "Plant not found", + "details": { + "plant_id": "123", + "suggestion": "Check if the plant ID is correct" + } + } +} +``` + +## Best Practices + +1. **Always Check Success Flag** + ```javascript + if (!response.success) { + // Handle error + } + ``` + +2. **Handle Specific Error Codes** + ```javascript + switch (error.code) { + case 'AUTH_TOKEN_EXPIRED': + // Refresh token + break; + case 'RATE_LIMIT_EXCEEDED': + // Implement backoff + break; + default: + // Handle unknown errors + } + ``` + +3. **Implement Retry Logic** + ```javascript + const maxRetries = 3; + let retryCount = 0; + + while (retryCount < maxRetries) { + try { + const response = await apiCall(); + if (response.success) break; + + if (response.error.code === 'RATE_LIMIT_EXCEEDED') { + retryCount++; + await delay(1000 * retryCount); + continue; + } + + throw new Error(response.error.message); + } catch (error) { + if (retryCount === maxRetries) throw error; + retryCount++; + } + } + ``` + +4. **Log Errors Appropriately** + ```javascript + function handleError(error) { + console.error({ + code: error.code, + message: error.message, + details: error.details, + timestamp: new Date().toISOString() + }); + } + ``` + +## Rate Limiting + +When you receive a rate limit error: + +1. Check the `Retry-After` header +2. Implement exponential backoff +3. Monitor your rate limit usage + +Example response with rate limit headers: +```http +HTTP/1.1 429 Too Many Requests +Retry-After: 60 +X-RateLimit-Limit: 100 +X-RateLimit-Remaining: 0 +X-RateLimit-Reset: 1617123456 +``` + +## Support + +If you encounter unexpected errors: + +1. Check the error code and message +2. Review the error details +3. Check our status page: https://status.plantpal.com +4. Contact support with: + - Error code + - Error message + - Request details + - Timestamp + - Your API key (masked) \ No newline at end of file diff --git a/docs/api/integration.md b/docs/api/integration.md new file mode 100644 index 0000000..b6efa97 --- /dev/null +++ b/docs/api/integration.md @@ -0,0 +1,403 @@ +# API Integration Guide + +## Overview +This guide provides detailed examples of integrating the PlantPal API into your applications. We'll cover common use cases and provide code examples in multiple languages. + +## Quick Start + +### 1. Install Dependencies + +#### Python +```bash +pip install requests pillow +``` + +#### Node.js +```bash +npm install axios form-data +``` + +#### PHP +```bash +composer require guzzlehttp/guzzle +``` + +### 2. Basic Configuration + +#### Python +```python +import requests + +API_KEY = 'your_api_key_here' +BASE_URL = 'https://api.plantpal.com/v1' + +headers = { + 'Authorization': f'Bearer {API_KEY}', + 'Content-Type': 'application/json' +} +``` + +#### Node.js +```javascript +const axios = require('axios'); + +const API_KEY = 'your_api_key_here'; +const BASE_URL = 'https://api.plantpal.com/v1'; + +const headers = { + 'Authorization': `Bearer ${API_KEY}`, + 'Content-Type': 'application/json' +}; +``` + +#### PHP +```php + { + if (result) { + console.log(`Plant: ${result.plantName}`); + console.log(`Disease: ${result.disease}`); + console.log(`Confidence: ${result.confidence}`); + console.log('Treatment:', result.treatment.recommendations); + } + }); +``` + +### 2. Plant Care Schedule + +#### Python +```python +def get_care_schedule(plant_id): + response = requests.get( + f'{BASE_URL}/plants/{plant_id}/care-schedule', + headers=headers + ) + + if response.status_code == 200: + result = response.json() + if result['success']: + return result['data'] + return None + +# Usage +schedule = get_care_schedule('plant_123') +if schedule: + print(f"Watering: {schedule['watering_frequency']}") + print(f"Sunlight: {schedule['sunlight']}") + print(f"Fertilizer: {schedule['fertilizer']}") + print("Next tasks:", schedule['next_tasks']) +``` + +#### Node.js +```javascript +async function getCareSchedule(plantId) { + try { + const response = await axios.get( + `${BASE_URL}/plants/${plantId}/care-schedule`, + { headers } + ); + + if (response.data.success) { + return response.data.data; + } + } catch (error) { + console.error('Error:', error.message); + } + return null; +} + +// Usage +getCareSchedule('plant_123') + .then(schedule => { + if (schedule) { + console.log(`Watering: ${schedule.watering_frequency}`); + console.log(`Sunlight: ${schedule.sunlight}`); + console.log(`Fertilizer: ${schedule.fertilizer}`); + console.log('Next tasks:', schedule.next_tasks); + } + }); +``` + +### 3. User Profile Management + +#### Python +```python +def update_user_profile(user_data): + response = requests.put( + f'{BASE_URL}/users/profile', + headers=headers, + json=user_data + ) + + if response.status_code == 200: + result = response.json() + if result['success']: + return result['data'] + return None + +# Usage +profile_data = { + 'name': 'John Doe', + 'email': 'john@example.com', + 'preferences': { + 'notifications': True, + 'language': 'en' + } +} + +updated_profile = update_user_profile(profile_data) +if updated_profile: + print(f"Profile updated: {updated_profile['name']}") +``` + +#### Node.js +```javascript +async function updateUserProfile(userData) { + try { + const response = await axios.put( + `${BASE_URL}/users/profile`, + userData, + { headers } + ); + + if (response.data.success) { + return response.data.data; + } + } catch (error) { + console.error('Error:', error.message); + } + return null; +} + +// Usage +const profileData = { + name: 'John Doe', + email: 'john@example.com', + preferences: { + notifications: true, + language: 'en' + } +}; + +updateUserProfile(profileData) + .then(updatedProfile => { + if (updatedProfile) { + console.log(`Profile updated: ${updatedProfile.name}`); + } + }); +``` + +## Advanced Integration Examples + +### 1. Batch Processing + +#### Python +```python +def process_multiple_images(image_paths): + results = [] + for image_path in image_paths: + result = detect_plant_disease(image_path) + if result: + results.append({ + 'image': image_path, + 'result': result + }) + return results + +# Usage +image_paths = [ + 'plant1.jpg', + 'plant2.jpg', + 'plant3.jpg' +] +results = process_multiple_images(image_paths) +for result in results: + print(f"Image: {result['image']}") + print(f"Plant: {result['result']['plant_name']}") + print(f"Disease: {result['result']['disease']}") + print("---") +``` + +### 2. Error Handling and Retries + +#### Python +```python +import time +from requests.exceptions import RequestException + +def api_call_with_retry(func, max_retries=3, delay=1): + for attempt in range(max_retries): + try: + return func() + except RequestException as e: + if attempt == max_retries - 1: + raise e + time.sleep(delay * (attempt + 1)) + +# Usage +def get_plant_data(plant_id): + return requests.get( + f'{BASE_URL}/plants/{plant_id}', + headers=headers + ) + +try: + response = api_call_with_retry(lambda: get_plant_data('plant_123')) + print(response.json()) +except RequestException as e: + print(f"Failed after retries: {e}") +``` + +### 3. Webhook Integration + +#### Python +```python +from flask import Flask, request, jsonify + +app = Flask(__name__) + +@app.route('/webhook', methods=['POST']) +def handle_webhook(): + data = request.json + + # Verify webhook signature + signature = request.headers.get('X-PlantPal-Signature') + if not verify_signature(data, signature): + return jsonify({'error': 'Invalid signature'}), 401 + + # Process webhook data + event_type = data.get('type') + if event_type == 'plant.disease_detected': + handle_disease_detection(data) + elif event_type == 'plant.care_reminder': + handle_care_reminder(data) + + return jsonify({'success': True}) + +def verify_signature(data, signature): + # Implement signature verification + return True + +def handle_disease_detection(data): + # Handle disease detection event + print(f"Disease detected: {data['plant_name']}") + print(f"Disease: {data['disease']}") + print(f"Treatment: {data['treatment']}") + +def handle_care_reminder(data): + # Handle care reminder event + print(f"Care reminder for: {data['plant_name']}") + print(f"Task: {data['task']}") + print(f"Due date: {data['due_date']}") + +if __name__ == '__main__': + app.run(port=5000) +``` + +## Best Practices + +1. **Error Handling** + - Always check response status codes + - Implement proper error handling + - Use retry mechanisms for transient failures + +2. **Rate Limiting** + - Monitor API usage + - Implement backoff strategies + - Cache responses when possible + +3. **Security** + - Store API keys securely + - Use environment variables + - Implement proper authentication + +4. **Performance** + - Use connection pooling + - Implement caching + - Optimize request frequency + +## Support + +For integration support: +1. Check our documentation +2. Join our developer community +3. Contact support at support@plantpal.com +4. Report issues on GitHub \ No newline at end of file diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml new file mode 100644 index 0000000..78a9ad3 --- /dev/null +++ b/docs/api/openapi.yaml @@ -0,0 +1,233 @@ +openapi: 3.0.0 +info: + title: PlantPal API + version: 1.0.0 + description: API for plant disease detection and care management + contact: + email: support@plantpal.com + +servers: + - url: https://api.plantpal.com/v1 + description: Production server + - url: https://staging-api.plantpal.com/v1 + description: Staging server + +components: + securitySchemes: + BearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + + schemas: + Error: + type: object + properties: + success: + type: boolean + example: false + error: + type: object + properties: + code: + type: string + example: "INVALID_INPUT" + message: + type: string + example: "Invalid input parameters" + details: + type: object + example: {"field": "Additional error details"} + + PlantDetectionResponse: + type: object + properties: + success: + type: boolean + example: true + data: + type: object + properties: + plant_name: + type: string + example: "Tomato" + disease: + type: string + example: "Early Blight" + confidence: + type: number + format: float + example: 0.95 + treatment: + type: object + properties: + recommendations: + type: array + items: + type: string + example: ["Remove infected leaves", "Apply fungicide"] + products: + type: array + items: + type: object + properties: + name: + type: string + example: "Organic Fungicide" + type: + type: string + example: "Natural" + application: + type: string + example: "Spray every 7 days" + +paths: + /plants/detect: + post: + summary: Detect plant disease from image + security: + - BearerAuth: [] + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + file: + type: string + format: binary + responses: + '200': + description: Successful detection + content: + application/json: + schema: + $ref: '#/components/schemas/PlantDetectionResponse' + '400': + description: Invalid input + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /plants/{plant_id}/care-schedule: + get: + summary: Get plant care schedule + security: + - BearerAuth: [] + parameters: + - name: plant_id + in: path + required: true + schema: + type: string + responses: + '200': + description: Successful retrieval + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + example: true + data: + type: object + properties: + watering_frequency: + type: string + example: "Every 3 days" + sunlight: + type: string + example: "Partial shade" + fertilizer: + type: string + example: "Monthly" + next_tasks: + type: array + items: + type: object + properties: + type: + type: string + example: "watering" + due_date: + type: string + format: date + example: "2024-04-08" + description: + type: string + example: "Water thoroughly" + + /users/profile: + put: + summary: Update user profile + security: + - BearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + example: "John Doe" + email: + type: string + format: email + example: "john@example.com" + preferences: + type: object + properties: + notifications: + type: boolean + example: true + language: + type: string + example: "en" + responses: + '200': + description: Profile updated successfully + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + example: true + data: + type: object + properties: + id: + type: string + example: "user_123" + name: + type: string + example: "John Doe" + email: + type: string + example: "john@example.com" + preferences: + type: object + properties: + notifications: + type: boolean + example: true + language: + type: string + example: "en" + updated_at: + type: string + format: date-time + example: "2024-04-05T10:30:00Z" \ No newline at end of file diff --git a/docs/api/testing.md b/docs/api/testing.md new file mode 100644 index 0000000..a36278f --- /dev/null +++ b/docs/api/testing.md @@ -0,0 +1,220 @@ +# API Testing Guide + +## Overview +This guide explains how to test the PlantPal API, including setting up a test environment, writing tests, and using our testing tools. + +## Test Environment Setup + +### 1. Test API Keys +1. Sign up for a developer account +2. Generate a test API key from the developer dashboard +3. Use the test environment URL: `https://staging-api.plantpal.com/v1` + +### 2. Test Data +We provide test data sets for common scenarios: + +```bash +# Download test images +curl -O https://staging-api.plantpal.com/v1/test-data/plant-images.zip + +# Download test responses +curl -O https://staging-api.plantpal.com/v1/test-data/sample-responses.json +``` + +## Testing Tools + +### 1. Postman Collection +We provide a Postman collection for testing all endpoints: + +1. Download the collection: [PlantPal API.postman_collection.json](https://staging-api.plantpal.com/v1/postman/PlantPal_API.postman_collection.json) +2. Import into Postman +3. Set up environment variables: + - `base_url`: https://staging-api.plantpal.com/v1 + - `api_key`: Your test API key + +### 2. API Testing Scripts +Example test scripts in different languages: + +#### Python +```python +import requests +import pytest + +BASE_URL = 'https://staging-api.plantpal.com/v1' +API_KEY = 'your_test_api_key' + +def test_plant_detection(): + headers = { + 'Authorization': f'Bearer {API_KEY}', + 'Content-Type': 'application/json' + } + + # Test with sample image + with open('test_data/healthy_plant.jpg', 'rb') as f: + files = {'file': f} + response = requests.post(f'{BASE_URL}/plants/detect', headers=headers, files=files) + + assert response.status_code == 200 + assert response.json()['success'] == True + assert 'plant_name' in response.json()['data'] +``` + +#### JavaScript +```javascript +const axios = require('axios'); +const assert = require('assert'); + +const BASE_URL = 'https://staging-api.plantpal.com/v1'; +const API_KEY = 'your_test_api_key'; + +async function testPlantDetection() { + const headers = { + 'Authorization': `Bearer ${API_KEY}`, + 'Content-Type': 'application/json' + }; + + const formData = new FormData(); + formData.append('file', fs.createReadStream('test_data/healthy_plant.jpg')); + + try { + const response = await axios.post(`${BASE_URL}/plants/detect`, formData, { headers }); + assert.strictEqual(response.status, 200); + assert.strictEqual(response.data.success, true); + assert.ok(response.data.data.plant_name); + } catch (error) { + console.error('Test failed:', error.message); + } +} +``` + +## Test Cases + +### 1. Authentication Tests +```python +def test_authentication(): + # Test valid credentials + response = requests.post(f'{BASE_URL}/auth/login', json={ + 'email': 'test@example.com', + 'password': 'test_password' + }) + assert response.status_code == 200 + assert 'access_token' in response.json()['data'] + + # Test invalid credentials + response = requests.post(f'{BASE_URL}/auth/login', json={ + 'email': 'test@example.com', + 'password': 'wrong_password' + }) + assert response.status_code == 401 + assert response.json()['error']['code'] == 'AUTH_INVALID_CREDENTIALS' +``` + +### 2. Plant Detection Tests +```python +def test_plant_detection_scenarios(): + headers = { + 'Authorization': f'Bearer {API_KEY}', + 'Content-Type': 'application/json' + } + + # Test healthy plant + with open('test_data/healthy_plant.jpg', 'rb') as f: + response = requests.post(f'{BASE_URL}/plants/detect', headers=headers, files={'file': f}) + assert response.status_code == 200 + assert response.json()['data']['disease'] is None + + # Test diseased plant + with open('test_data/diseased_plant.jpg', 'rb') as f: + response = requests.post(f'{BASE_URL}/plants/detect', headers=headers, files={'file': f}) + assert response.status_code == 200 + assert response.json()['data']['disease'] is not None +``` + +### 3. Error Handling Tests +```python +def test_error_handling(): + headers = { + 'Authorization': f'Bearer {API_KEY}', + 'Content-Type': 'application/json' + } + + # Test invalid file type + with open('test_data/invalid.txt', 'rb') as f: + response = requests.post(f'{BASE_URL}/plants/detect', headers=headers, files={'file': f}) + assert response.status_code == 400 + assert response.json()['error']['code'] == 'VALIDATION_INVALID_FILE_TYPE' + + # Test file too large + with open('test_data/large_image.jpg', 'rb') as f: + response = requests.post(f'{BASE_URL}/plants/detect', headers=headers, files={'file': f}) + assert response.status_code == 400 + assert response.json()['error']['code'] == 'VALIDATION_FILE_TOO_LARGE' +``` + +## Performance Testing + +### 1. Rate Limiting Tests +```python +def test_rate_limiting(): + headers = { + 'Authorization': f'Bearer {API_KEY}', + 'Content-Type': 'application/json' + } + + # Make requests up to rate limit + for _ in range(60): # Free tier limit + response = requests.get(f'{BASE_URL}/plants', headers=headers) + assert response.status_code == 200 + + # Next request should be rate limited + response = requests.get(f'{BASE_URL}/plants', headers=headers) + assert response.status_code == 429 + assert 'Retry-After' in response.headers +``` + +### 2. Response Time Tests +```python +def test_response_time(): + headers = { + 'Authorization': f'Bearer {API_KEY}', + 'Content-Type': 'application/json' + } + + start_time = time.time() + response = requests.get(f'{BASE_URL}/plants', headers=headers) + end_time = time.time() + + assert response.status_code == 200 + assert end_time - start_time < 1.0 # Response within 1 second +``` + +## Best Practices + +1. **Use Test Environment** + - Always use staging API URL + - Use test API keys + - Don't use production data + +2. **Test Coverage** + - Test all endpoints + - Test success and error cases + - Test rate limiting + - Test response times + +3. **Test Data Management** + - Use consistent test data + - Clean up test data after tests + - Don't modify production data + +4. **Error Handling** + - Test all error codes + - Verify error messages + - Check error details + +## Support + +For testing support: +1. Check our testing documentation +2. Join our developer community +3. Contact support at support@plantpal.com +4. Report issues on GitHub \ No newline at end of file diff --git a/docs/ui-components/Button.md b/docs/ui-components/Button.md new file mode 100644 index 0000000..424099c --- /dev/null +++ b/docs/ui-components/Button.md @@ -0,0 +1,198 @@ +# Button Component + +The Button component is a versatile and reusable UI element that follows our design system guidelines. It supports multiple variants, sizes, and states while maintaining accessibility standards. + +## Props + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| children | ReactNode | - | The content to be displayed inside the button | +| variant | 'primary' \| 'secondary' \| 'outline' | 'primary' | The variant style of the button | +| onClick | () => void | - | Optional click handler | +| disabled | boolean | false | Whether the button is disabled | +| size | 'small' \| 'medium' \| 'large' | 'medium' | The size of the button | +| className | string | '' | Additional CSS classes | + +## Usage Examples + +```tsx +import Button from '@/components/Button'; + +// Primary Button (Default) + + +// Secondary Button + + +// Outline Button (Disabled) + + +// Small Primary Button with Custom Class + +``` + +## Style Guide + +The Button component uses Tailwind CSS for styling and follows these design principles: + +### Colors +- Primary: Green-based theme (matches PlantPal's nature-focused brand) +- Secondary: Gray-scale for less prominent actions +- Outline: Bordered version for tertiary actions + +### Variants + +1. **Primary** (Default) + - High emphasis + - Used for main actions + - Filled background with white text + - Example: "Save Changes", "Submit", "Next" + +2. **Secondary** + - Medium emphasis + - Used for secondary actions + - Light background with dark text + - Example: "Cancel", "Back", "See More" + +3. **Outline** + - Low emphasis + - Used for tertiary actions + - Bordered with no fill + - Example: "Learn More", "Skip" + +### Sizes + +1. **Small** (`small`) + - Compact size: 24px height + - Used in tight spaces or dense UIs + - Font size: 14px (sm) + +2. **Medium** (`medium`) - Default + - Standard size: 32px height + - Used for most button instances + - Font size: 16px (base) + +3. **Large** (`large`) + - Prominent size: 40px height + - Used for main call-to-action buttons + - Font size: 18px (lg) + +## Theming + +The Button component uses these Tailwind CSS classes which can be customized in your `tailwind.config.js`: + +```js +// tailwind.config.js +module.exports = { + theme: { + extend: { + colors: { + green: { + 50: '#f0fdf4', + // ... other shades + 600: '#16a34a', + 700: '#15803d', + }, + gray: { + 200: '#e5e7eb', + 300: '#d1d5db', + 800: '#1f2937', + } + } + } + } +} +``` + +## Accessibility + +The Button component follows WCAG 2.1 guidelines: + +### Keyboard Navigation +- Fully focusable with keyboard (Tab key) +- Activatable with Enter or Space key +- Visual focus indicator with ring outline + +### Screen Readers +- Uses semantic `); + expect(getByText('Click me')).toBeInTheDocument(); + }); + + // Click Handler Test + it('handles click events', () => { + const handleClick = jest.fn(); + const { getByRole } = render( + + ); + fireEvent.click(getByRole('button')); + expect(handleClick).toHaveBeenCalled(); + }); + + // Disabled State Test + it('respects disabled state', () => { + const handleClick = jest.fn(); + const { getByRole } = render( + + ); + const button = getByRole('button'); + fireEvent.click(button); + expect(handleClick).not.toHaveBeenCalled(); + expect(button).toBeDisabled(); + }); + + // Style Tests + it('applies variant styles correctly', () => { + const { container } = render( + + ); + expect(container.firstChild).toHaveClass('bg-green-600'); + }); + + it('applies size styles correctly', () => { + const { container } = render( + + ); + expect(container.firstChild).toHaveClass('px-6 py-3 text-lg'); + }); +}); +``` \ No newline at end of file diff --git a/docs/ui-components/README.md b/docs/ui-components/README.md new file mode 100644 index 0000000..c6f9b79 --- /dev/null +++ b/docs/ui-components/README.md @@ -0,0 +1,108 @@ +# PlantPal UI Component Documentation + +Welcome to PlantPal's UI Component Documentation. This guide provides comprehensive information about our component library, including usage examples, styling guidelines, and best practices. + +## Table of Contents + +1. [Components](#components) +2. [Design System](#design-system) +3. [Accessibility](#accessibility) +4. [Testing](#testing) +5. [Contributing](#contributing) + +## Components + +Our component library includes the following components: + +- [Button](./Button.md) - A versatile button component with multiple variants + +## Design System + +Our components follow these core principles: + +### Colors +- **Primary**: Green-based theme reflecting our nature-focused brand +- **Secondary**: Neutral grays for supporting elements +- **Accent**: Strategic use of complementary colors + +### Typography +- **Font Family**: System-native fonts for optimal performance +- **Scale**: Consistent sizing using Tailwind's scale +- **Weights**: Regular (400), Medium (500), Bold (700) + +### Spacing +- Based on a 4px grid system +- Consistent spacing using Tailwind's scale +- Responsive spacing utilities + +### Elevation +- Subtle shadows for interactive elements +- Clear visual hierarchy +- Consistent z-index scale + +## Accessibility + +All components are built with accessibility in mind: + +### Standards +- WCAG 2.1 AA compliance +- Semantic HTML +- ARIA attributes where necessary +- Keyboard navigation support + +### Testing +- Regular accessibility audits +- Screen reader testing +- Keyboard navigation testing +- Color contrast verification + +## Testing + +Our testing strategy includes: + +### Unit Tests +- Component rendering +- Props validation +- Event handling +- State management + +### Integration Tests +- Component interactions +- Form submissions +- API interactions + +### E2E Tests +- User flows +- Critical paths +- Edge cases + +## Contributing + +### Adding New Components + +1. Create component in `src/frontend/components` +2. Add documentation in `docs/ui-components` +3. Include: + - Props documentation + - Usage examples + - Style guidelines + - Accessibility considerations + - Test cases + +### Documentation Structure + +Each component's documentation should include: + +1. Overview +2. Props API +3. Usage Examples +4. Style Guide +5. Accessibility Notes +6. Testing Guide + +### Review Process + +1. Code review +2. Documentation review +3. Accessibility review +4. Performance review \ No newline at end of file diff --git a/src/frontend/components/Button.tsx b/src/frontend/components/Button.tsx new file mode 100644 index 0000000..e700ff7 --- /dev/null +++ b/src/frontend/components/Button.tsx @@ -0,0 +1,72 @@ +import React from 'react'; + +interface ButtonProps { + /** + * The content to be displayed inside the button + */ + children: React.ReactNode; + + /** + * The variant style of the button + */ + variant?: 'primary' | 'secondary' | 'outline'; + + /** + * Optional click handler + */ + onClick?: () => void; + + /** + * Whether the button is disabled + */ + disabled?: boolean; + + /** + * The size of the button + */ + size?: 'small' | 'medium' | 'large'; + + /** + * Additional CSS classes + */ + className?: string; +} + +/** + * Primary UI component for user interaction + */ +export const Button: React.FC = ({ + children, + variant = 'primary', + onClick, + disabled = false, + size = 'medium', + className = '', +}) => { + const baseStyles = 'rounded-md font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2'; + + const variantStyles = { + primary: 'bg-green-600 text-white hover:bg-green-700 focus:ring-green-500', + secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300 focus:ring-gray-500', + outline: 'border-2 border-green-600 text-green-600 hover:bg-green-50 focus:ring-green-500' + }; + + const sizeStyles = { + small: 'px-3 py-1.5 text-sm', + medium: 'px-4 py-2 text-base', + large: 'px-6 py-3 text-lg' + }; + + return ( + + ); +}; + +export default Button; \ No newline at end of file