{"id":31642169,"url":"https://github.com/earth-app/mantle2","last_synced_at":"2026-05-05T14:07:33.712Z","repository":{"id":310528351,"uuid":"1040094467","full_name":"earth-app/mantle2","owner":"earth-app","description":"Backend v2 API, Powered by Drupal","archived":false,"fork":false,"pushed_at":"2025-10-06T17:03:26.000Z","size":528,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-10-06T19:10:20.620Z","etag":null,"topics":["drupal","drupal-backend","earth-app","openapi","php","symfony"],"latest_commit_sha":null,"homepage":"https://api.earth-app.com","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/earth-app.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"patreon":"gmitch215","liberapay":"gmitch215","buy_me_a_coffee":"gmitch215"}},"created_at":"2025-08-18T12:56:32.000Z","updated_at":"2025-10-06T14:06:45.000Z","dependencies_parsed_at":null,"dependency_job_id":"321ff957-328a-4669-bb6e-94fa1ae56dbf","html_url":"https://github.com/earth-app/mantle2","commit_stats":null,"previous_names":["earth-app/mantle2"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/earth-app/mantle2","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/earth-app%2Fmantle2","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/earth-app%2Fmantle2/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/earth-app%2Fmantle2/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/earth-app%2Fmantle2/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/earth-app","download_url":"https://codeload.github.com/earth-app/mantle2/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/earth-app%2Fmantle2/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278717448,"owners_count":26033542,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-07T02:00:06.786Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["drupal","drupal-backend","earth-app","openapi","php","symfony"],"created_at":"2025-10-07T03:56:56.967Z","updated_at":"2026-05-05T14:07:33.689Z","avatar_url":"https://github.com/earth-app.png","language":"PHP","funding_links":["https://patreon.com/gmitch215","https://liberapay.com/gmitch215","https://buymeacoffee.com/gmitch215"],"categories":[],"sub_categories":[],"readme":"# mantle2\n\n\u003e Backend for The Earth App, powered by Drupal 11\n\nThis is the second version of the backend system for The Earth App,\na comprehensive RESTful API built on top of PHP 8.4 and Drupal 11.3.\nThe module provides a complete backend infrastructure for a social\nnetworking platform focused on novelty, activities, and user engagement.\n\n## Table of Contents\n\n- [Overview](#overview)\n- [Technical Stack](#technical-stack)\n- [Architecture](#architecture)\n- [Installation](#installation)\n- [Core Components](#core-components)\n- [Security \u0026 Performance](#security--performance)\n- [Development](#development)\n- [Testing](#testing)\n\n## Overview\n\nMantle2 is a custom Drupal 11 module that implements a RESTful API backend\nfor The Earth App. It leverages Drupal's entity system, field API, and routing\ninfrastructure while adding custom controllers, services, and event subscribers\nto create a modern, scalable API platform.\n\n### Key Features\n\n- **RESTful API** with OpenAPI/Swagger documentation\n- **User Management** with token auth, OAuth, profiles, and social features\n- **Activity Tracking** for environmental activities with custom fields\n- **Event Management** with participation, cancellation, and image submissions\n- **Prompt System** with visibility-aware discovery and response threads\n- **Article Content** with quizzes, moderation, and user attribution\n- **Gamification** with badges, points, quests, and profile cosmetics\n- **Rate Limiting** with configurable per-endpoint and global limits\n- **CORS Support** with origin whitelisting\n- **Redis Caching** with automatic fallback to Drupal cache\n- **Email Notifications** with HTML rendering and verification codes\n\n## Technical Stack\n\n### Core Technologies\n\n- **PHP**: 8.4+\n- **Drupal Core**: 11.3+\n- **Symfony**: 7.3+ (Event Dispatcher, Rate Limiter, Cache)\n- **Redis**: Optional caching layer via `drupal/redis` module\n- **PostgreSQL/MySQL**: Database backend (Drupal standard)\n\n### Development Tools\n\n- **Composer**: PHP dependency management\n- **Bun**: JavaScript runtime for development tooling\n- **PHPUnit**: 11.5+ for unit testing\n- **Drush**: 13.7+ for Drupal CLI operations\n- **PHPStan**: Static analysis\n- **PHP CodeSniffer**: Code quality enforcement\n- **Prettier**: Code formatting for PHP, XML, YAML, JSON\n\n### Key Dependencies\n\n```json\n{\n\t\"drupal/core\": \"^11.3\",\n\t\"drupal/json_field\": \"^1.4\", // JSON field storage\n\t\"drupal/key\": \"1.22.0\", // API key management\n\t\"drupal/smtp\": \"^1.4\", // Email delivery\n\t\"drupal/redis\": \"^1.10\", // Redis integration\n\t\"drupal/openid_connect\": \"^3.0@alpha\", // OAuth/OpenID providers\n\t\"symfony/rate-limiter\": \"^7.3\", // Rate limiting\n\t\"symfony/event-dispatcher\": \"^7.3\", // Event system\n\t\"symfony/cache\": \"^7.3\" // Cache abstractions\n}\n```\n\n## Architecture\n\n### Directory Structure\n\n```text\nmantle2/\n├── src/\n│   ├── Controller/          # API endpoint controllers\n│   │   └── Schema/          # OpenAPI schema generators\n│   ├── Custom/              # Domain models and enums\n│   ├── EventSubscriber/     # Symfony event subscribers\n│   ├── Plugin/              # OpenID Connect client plugins\n│   └── Service/             # Business logic helpers\n├── tests/\n│   └── src/\n│       ├── Unit/            # PHPUnit tests\n│       └── Mocks.php        # Test fixtures\n├── mantle2.info.yml         # Module metadata\n├── mantle2.module           # Hook implementations\n├── mantle2.install          # Installation \u0026 schema\n├── mantle2.routing.yml      # API route definitions (200+ endpoints)\n├── mantle2.caching.yml      # Custom server-side caching definitions\n├── mantle2.services.yml     # Service container definitions\n├── composer.json            # PHP dependencies\n├── package.json             # Dev tooling\n└── phpunit.xml.dist         # Test configuration\n```\n\n### Design Patterns\n\n#### 1. Controller Layer\n\nAll API endpoints are implemented as controller methods extending Drupal's `ControllerBase`:\n\n```php\nclass UsersController extends ControllerBase\n{\n\tpublic function users(Request $request): JsonResponse\n\t{\n\t\t// Handle paginated user listing\n\t}\n\n\tpublic function login(Request $request): JsonResponse\n\t{\n\t\t// Authenticate and return bearer token\n\t}\n}\n```\n\n#### 2. Service Layer\n\nBusiness logic is encapsulated in helper services registered in `mantle2.services.yml`:\n\n- **GeneralHelper**: Common utilities (pagination, validation)\n- **UsersHelper**: User operations and authentication\n- **ActivityHelper**: Activity-related business logic\n- **PointsHelper**: Points, badges, cosmetics, and quest lifecycle\n- **OAuthHelper**: OAuth token validation and provider linking\n- **CampaignHelper**: Email campaign content and placeholder expansion\n- **CloudHelper**: Cloud service requests and websocket notifications\n- **RedisHelper**: Cache abstraction with fallback\n\n#### 3. Domain Models\n\nCustom PHP classes in `src/Custom/` represent business entities:\n\n- Implement `JsonSerializable` for API responses\n- Enforce validation in constructors\n- Provide type-safe interfaces\n\n```php\nclass Activity implements JsonSerializable\n{\n\tprotected string $id;\n\tprotected string $name;\n\tprotected array $types = [];\n\tprotected ?string $description = null;\n\n\tpublic const int MAX_TYPES = 5;\n\n\tpublic function __construct(\n\t\tstring $id,\n\t\tstring $name,\n\t\tarray $types = [],\n\t\t?string $description = null,\n\t\tarray $aliases = [],\n\t\tarray $fields = [],\n\t) {\n\t\tif (count($types) \u003e self::MAX_TYPES) {\n\t\t\tthrow new InvalidArgumentException('Too many activity types');\n\t\t}\n\t\t// ... validation and initialization\n\t}\n}\n```\n\n#### 4. Event Subscribers\n\nSymfony's event dispatcher handles cross-cutting concerns:\n\n- **RateLimitSubscriber**: Pre-request rate limit enforcement\n- **RateLimitResponseSubscriber**: Appends global and endpoint rate headers\n- **CorsSubscriber**: CORS header injection\n- **ApiExceptionSubscriber**: Global error handling\n- **ResponseCacheSubscriber**: Config-driven read-through/invalidation caching\n- **PostResponseSubscriber**: Post-response badge progress tracking\n\n## Installation\n\n### Prerequisites\n\n- PHP 8.4 or higher\n- Composer 2.x\n- Drupal 11.3+ installed and configured\n- Redis server (optional, recommended for production)\n- SMTP server credentials for email functionality\n\n### Steps\n\n1. **Clone the repository** into your Drupal modules directory:\n\n    ```bash\n    cd /path/to/drupal/modules/custom\n    git clone \u003crepository-url\u003e mantle2\n    cd mantle2\n    ```\n\n2. **Install PHP dependencies**:\n\n    ```bash\n    composer install\n    ```\n\n3. **Enable required Drupal modules**:\n\n    ```bash\n    \tdrush en node user comment json_field key field options datetime smtp redis \\\n    \t\topenid_connect -y\n    ```\n\n4. **Enable mantle2**:\n\n    ```bash\n    drush en mantle2 -y\n    ```\n\n    This runs the installation hooks in `mantle2.install` which:\n    - Creates custom content types (Activity, Event, Article, Prompt)\n    - Creates custom comment types (Activity Comments, Article Comments)\n    - Defines extensive custom fields with JSON storage\n    - Sets up user profile fields\n    - Configures field display settings\n\n5. **Configure Redis** (optional):\n   Edit `settings.php`:\n\n    ```php\n    $settings['redis.connection']['interface'] = 'PhpRedis';\n    $settings['redis.connection']['host'] = '127.0.0.1';\n    $settings['redis.connection']['port'] = 6379;\n    $settings['cache']['default'] = 'cache.backend.redis';\n    ```\n\n6. **Configure SMTP** (required for email features):\n    - Navigate to `/admin/config/system/smtp`\n    - Enter SMTP server credentials\n    - Test email delivery\n\n7. **Clear cache**:\n\n    ```bash\n    drush cr\n    ```\n\n### Verification\n\nAccess the API documentation:\n\n- OpenAPI Schema: `https://your-domain.com/openapi`\n- Swagger UI: `https://your-domain.com/swagger-ui`\n\nTest a simple endpoint:\n\n```bash\ncurl https://your-domain.com/v2/hello\n```\n\n## Core Components\n\n### Controllers\n\n#### UsersController\n\n**Responsibilities:**\n\n- User CRUD operations\n- Authentication (token login/logout and provider-based OAuth)\n- Profile management (photos, privacy, account tiers)\n- Social graph (friends and circle management)\n- Notifications, badges, and points\n- Quest lifecycle and cosmetics\n- Email verification, unsubscribe, and password reset flows\n- Activity, prompt, article, and event associations\n\n**Database Queries:**\n\n- Uses both Entity API (`$storage-\u003egetQuery()`) and direct SQL (`Drupal::database()`)\n- Random sorting implemented via `orderRandom()` for discovery features\n- Supports search across multiple fields (username, first name, last name)\n\n#### ActivityController\n\n**Responsibilities:**\n\n- Activity catalog management\n- Type filtering and categorization\n- Activity-user associations\n\n**Key Features:**\n\n- Supports up to 5 activity types per activity\n- JSON field storage for flexible metadata\n- Full-text search across name, description, aliases\n- Randomized and deterministic list retrieval modes\n\n#### EventsController\n\n**Responsibilities:**\n\n- Event lifecycle management\n- Participation tracking\n- Date-based filtering\n- Event visibility and attendee list management\n- Event image submission moderation\n\n**Features:**\n\n- Date range queries for event discovery\n- RSVP/signup/leave participation management\n- Event cancellation and uncancel flows\n- Event type enumeration\n- Geographic location and activity tagging support\n\n#### PromptsController\n\n**Responsibilities:**\n\n- Prompt catalog and random prompt delivery\n- User response collection\n- Visibility-aware filtering and moderation\n\n**Logic:**\n\n- Tracks user responses\n- Prevents duplicate responses per user per prompt\n- Supports response update/delete and expiration checks\n\n#### ArticlesController\n\n**Responsibilities:**\n\n- Article content management\n- Content moderation\n- Author attribution\n- Expiration checks and quiz retrieval\n\n**Features:**\n\n- Rich text content support via JSON fields\n- Tag/ocean metadata support\n- Role-gated article creation and updates\n\n### Services\n\n#### GeneralHelper\n\n**Utilities:**\n\n- `paginatedParameters(Request)`: Validates and extracts pagination params\n- `findOrdinal(array, enum)`: Maps enum values to database integers\n- `validateJson(string)`: JSON validation\n- Various format converters and validators\n\n#### UsersHelper\n\n**User Operations:**\n\n- `getOwnerOfRequest(Request)`: Extract authenticated user from request\n- `findBy(string)`: Flexible user lookup by ID/username/email\n- `issueToken(UserInterface)`: Create bearer token with bounded session count\n- `getUserByToken(string)`: Resolve and validate bearer tokens (with sliding expiry)\n- `revokeToken(string)`: Revoke active authentication token\n- Friend/circle relationship management\n\n#### RedisHelper\n\n**Caching Abstraction:**\n\n```php\nRedisHelper::set(string $key, array $data, int $ttl = 900): bool\nRedisHelper::get(string $key): ?array\nRedisHelper::delete(mixed $key): bool\nRedisHelper::exists(string $key): bool\nRedisHelper::ttl(string $key): int\nRedisHelper::list(string $pattern): array\nRedisHelper::cache(?string $key, callable $callback, int $ttl = 900): array\n```\n\n**Features:**\n\n- Automatic fallback to Drupal cache backend if Redis unavailable\n- JSON serialization of complex data structures\n- TTL support for automatic expiration\n- Connection pooling via Drupal Redis module\n\n**Usage Example:**\n\n```php\n// Store email verification code\nRedisHelper::set(\n\t\"email_verify:{$userId}\",\n\t[\n\t\t'code' =\u003e $code,\n\t\t'email' =\u003e $email,\n\t\t'created' =\u003e time(),\n\t],\n\t900,\n); // 15 minutes TTL\n\n// Retrieve and validate\n$data = RedisHelper::get(\"email_verify:{$userId}\");\nif ($data \u0026\u0026 $data['code'] === $userInputCode) {\n\t// Verify successful\n\tRedisHelper::delete(\"email_verify:{$userId}\");\n}\n```\n\n#### HTMLFactory\n\n**Email Rendering:**\n\n- Converts markdown to HTML for email templates\n- Applies consistent styling\n- Handles inline CSS for email clients\n- Supports template variables\n\n### Domain Models\n\n#### Activity\n\n```php\nclass Activity implements JsonSerializable\n{\n\tprotected string $id;\n\tprotected string $name;\n\tprotected array $types; // ActivityType[]\n\tprotected ?string $description;\n\tprotected array $aliases; // Alternative names\n\tprotected array $fields; // Custom metadata\n\n\tpublic const int MAX_TYPES = 5;\n}\n```\n\n#### Event\n\n```php\nclass Event implements JsonSerializable\n{\n\tprivate string $id;\n\tprivate int $hostId;\n\tprivate string $name;\n\tprivate string $description;\n\tprivate EventType $type;\n\tprivate array $activities; // Activity[]|ActivityType[]\n\tprivate float $latitude;\n\tprivate float $longitude;\n\tprivate int $date; // Unix timestamp in milliseconds\n\tprivate ?int $endDate;\n\tprivate Visibility $visibility;\n\tprivate array $attendees;\n\tprivate array $fields;\n}\n```\n\n#### Notification\n\n```php\nclass Notification implements JsonSerializable\n{\n\tpublic string $id;\n\tpublic string $userId;\n\tpublic string $title;\n\tpublic string $message;\n\tpublic ?string $link;\n\tpublic string $type; // info, warning, error, success\n\tpublic string $source;\n\tpublic bool $isRead;\n\tpublic int $timestamp;\n}\n```\n\n#### Enums (PHP 8.1+)\n\n**AccountType:**\n\n- `FREE`: Regular user\n- `PRO`: Pro user tier\n- `WRITER`: Writer privileges\n- `ORGANIZER`: Organizer privileges\n- `ADMINISTRATOR`: Administrative access\n\n**Visibility:**\n\n- `PUBLIC`: Visible to all\n- `UNLISTED`: Hidden from broad listing, visible by context/owner\n- `PRIVATE`: Only user\n\n**Privacy:**\n\n- `PRIVATE`, `CIRCLE`, `MUTUAL`, `PUBLIC` settings for profile field visibility\n\n**ActivityType and EventType:**\n\n- Enumerated types for categorization (notifications use string severity values)\n\n### Event Subscribers\n\n#### RateLimitSubscriber\n\n**Configuration:**\n\n```php\n// Global limits\nAuthenticated: 120 requests / 60 seconds\nAnonymous: 60 requests / 60 seconds\n\n// Per-endpoint limits (examples)\nPOST /v2/users/login: 3 requests / 60 seconds\nPOST /v2/users/create: 5 requests / 5 minutes\nPOST /v2/events/create: 3 requests / 2 minutes\n```\n\n**Implementation:**\n\n- Uses Drupal's expirable key-value store\n- Separate counters for global and per-endpoint limits\n- Environment variable overrides for global limits\n- IP-based tracking (Cloudflare-aware)\n- Returns `429 Too Many Requests` with retry headers\n\n**Headers Added:**\n\n```text\nX-RateLimit-Limit: 3\nX-RateLimit-Remaining: 2\nX-RateLimit-Reset: 1698765432\nX-Global-RateLimit-Limit: 120\nX-Global-RateLimit-Remaining: 119\n```\n\n#### CorsSubscriber\n\n**Allowed Origins:**\n\n```php\n[\n\t'https://api.earth-app.com',\n\t'https://earth-app.com',\n\t'https://app.earth-app.com',\n\t'https://cloud.earth-app.com',\n\t'capacitor://localhost', // iOS\n\t'http://localhost', // Android\n\t'http://localhost:3000', // Development only\n\t'http://127.0.0.1:3000', // Development only\n\t'http://localhost:3001', // Development only\n\t'http://127.0.0.1:3001', // Development only\n];\n```\n\n**Headers Set:**\n\n```text\nAccess-Control-Allow-Origin: \u003cmatched-origin\u003e\nAccess-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS\nAccess-Control-Allow-Headers: Content-Type, Authorization, Accept,\n\tX-Requested-With, X-Admin-Key\nAccess-Control-Allow-Credentials: true\nAccess-Control-Max-Age: 3600\nVary: Origin\n```\n\n#### ApiExceptionSubscriber\n\n**Error Handling:**\n\n- Catches unhandled exceptions in API routes\n- Converts to consistent JSON error responses\n- Logs errors with context\n- Prevents sensitive information leakage in production\n\n## Security \u0026 Performance\n\n### Security Features\n\n#### 1. Authentication \u0026 Authorization\n\n- Bearer token authentication with issuance/revocation and sliding expiry\n- OAuth provider sign-in/linking (Google, Microsoft, Discord, GitHub, Facebook)\n- Password hashing using Drupal's password API (bcrypt)\n- Password strength validation\n- Email verification for account creation\n- Admin key validation for privileged operations\n\n#### 2. Input Validation\n\n- Request parameter sanitization\n- JSON schema validation\n- SQL injection prevention via Entity API and parameterized queries\n- XSS protection through Drupal's filtering system\n- File upload validation (type, size, permissions)\n\n#### 3. Rate Limiting\n\n- IP-based rate limiting\n- Separate limits for authenticated vs. anonymous users\n- Per-endpoint rate limiting for sensitive operations\n- Configurable time windows and thresholds\n- Cloudflare IP detection support\n\n#### 4. Privacy Controls\n\n- User-level visibility settings (PUBLIC, UNLISTED, PRIVATE)\n- Field-level privacy for profile attributes\n- Friend/circle-based content filtering\n- Profile photo access control\n\n#### 5. CORS Protection\n\n- Origin whitelist enforcement\n- Credentials support for trusted domains\n- Preflight request handling\n\n### Performance Optimizations\n\n#### 1. Caching Strategy\n\n```php\n// Redis/key-value for token and short-lived verification data\nRedisHelper::set(\"email_verify:{$userId}\", ['code' =\u003e $code], 900);\n\n// Entity caching via Drupal cache tags\n$users = $storage-\u003eloadMultiple($uids);\n\n// Config-driven response caching (mantle2.caching.yml)\n// ResponseCacheSubscriber sets X-Cache: HIT/MISS\n```\n\n#### 2. Database Optimization\n\n- Indexed fields for common queries (username, email, ID)\n- Pagination to limit result sets\n- Direct SQL for complex queries (random sorting)\n- Query object cloning for count queries to avoid duplication\n\n#### 3. Lazy Loading\n\n- User entities loaded on-demand\n- Related entities fetched only when needed\n- JSON fields decoded on access\n\n#### 4. Efficient Queries\n\n```php\n// Good: Load multiple entities at once\n$users = $storage-\u003eloadMultiple($uids);\n\n// Good: Paginated with limit\n$query-\u003erange($offset, $limit);\n\n// Good: Specific field loading\n$query-\u003efields('u', ['uid', 'name', 'mail']);\n```\n\n### Monitoring \u0026 Logging\n\n**Drupal Logger Integration:**\n\n```php\nDrupal::logger('mantle2')-\u003eerror('Error message', ['context' =\u003e $data]);\nDrupal::logger('mantle2')-\u003ewarning('Warning message');\nDrupal::logger('mantle2')-\u003einfo('Info message');\n```\n\n**Logged Events:**\n\n- Failed login attempts\n- Rate limit violations\n- Email delivery failures\n- Redis connection issues\n- API exceptions\n- User registrations\n- Password changes\n\n## Development\n\n### Local Setup\n\n1. **Install dependencies:**\n\n    ```bash\n    composer install\n    bun install\n    ```\n\n2. **Configure local environment:**\n\n    ```php\n    // settings.local.php\n    $config['system.logging']['error_level'] = 'verbose';\n    $settings['redis.connection']['host'] = 'localhost';\n    ```\n\n3. **Enable development modules:**\n\n    ```bash\n    drush en devel devel_generate dblog -y\n    ```\n\n4. **Generate test data:**\n\n    ```bash\n    drush devel-generate-users 50\n    drush devel-generate-content 100 --types=activity,event,article\n    ```\n\n### Code Quality\n\n**Formatting:**\n\n```bash\n# PHP, YAML, XML, JSON\nbun run prettier          # Format all files\nbun run prettier:check    # Check formatting\n\n# PHP-specific\nvendor/bin/phpcbf        # Auto-fix coding standards\nvendor/bin/phpcs         # Check coding standards\n```\n\n**Static Analysis:**\n\n```bash\nvendor/bin/phpstan analyse src/\n```\n\n**Pre-commit Hooks:**\nConfigured via Husky and lint-staged in `package.json`:\n\n```json\n{\n\t\"lint-staged\": {\n\t\t\"*.{php,xml,dist,json,install,module,yml,md}\": \"prettier --write\"\n\t}\n}\n```\n\n### API Documentation\n\n**Generate OpenAPI Schema:**\nVisit `/openapi` to see auto-generated schema based on route definitions in `mantle2.routing.yml`.\n\n**Interactive Swagger UI:**\nVisit `/swagger-ui` for interactive API testing and documentation.\n\n**Schema Annotations:**\nRoutes include OpenAPI metadata:\n\n```yaml\nmantle2.users:\n    path: '/v2/users'\n    options:\n        tags: Users\n        description: Retrieves a list of Earth App users\n        schema/200: '#Users'\n        schema/400: Invalid Pagination Parameters\n        query: # Query parameter schema\n            limit:\n                type: integer\n                minimum: 1\n                maximum: 100\n```\n\n### Debugging\n\n**Enable Verbose Errors:**\n\n```php\n// settings.local.php\n$config['system.logging']['error_level'] = 'verbose';\nerror_reporting(E_ALL);\nini_set('display_errors', true);\n```\n\n**Database Queries:**\n\n```bash\ndrush watchdog:show --type=mantle2\ndrush ws --tail  # Live log tail\n```\n\n**Clear Caches:**\n\n```bash\ndrush cr                  # Full cache rebuild\ndrush cc views            # Clear specific bin\ndrush redis-cli flushall  # Clear Redis\n```\n\n## Testing\n\n### PHPUnit Configuration\n\n**Location:** `phpunit.xml.dist`\n\n**Run Tests:**\n\n```bash\n# All tests\nvendor/bin/phpunit\n\n# Specific test file\nvendor/bin/phpunit tests/src/Unit/GeneralUnitTest.php\n\n# With coverage\nvendor/bin/phpunit --coverage-html coverage/\n```\n\n### Unit Tests\n\n**Test Structure:**\n\n```php\nnamespace Drupal\\Tests\\mantle2\\Unit;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Drupal\\mantle2\\Service\\GeneralHelper;\n\nclass GeneralUnitTest extends TestCase\n{\n\tpublic function testFormatId()\n\t{\n\t\t$this-\u003eassertEquals('000000000000000000000123', GeneralHelper::formatId(123));\n\t}\n}\n```\n\n### Mocks\n\n**Location:** `tests/src/Mocks.php`\n\nProvides test fixtures for:\n\n- User entities\n- Activity nodes\n- Event nodes\n- Request objects\n- Service mocks\n\n### Integration Testing\n\n**Use Drush:**\n\n```bash\n# Test API endpoints\ndrush php-eval \"print_r(\\Drupal::service('http_kernel')-\u003ehandle(Request::create('/v2/hello')));\"\n\n# Test services\ndrush php-eval \"print(\\Drupal\\mantle2\\Service\\GeneralHelper::formatId(123));\"\n```\n\n### API Testing\n\n**Using cURL:**\n\n```bash\n# Health check\ncurl http://localhost/v2/hello\n\n# Login\ncurl -X POST http://localhost/v2/users/login \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"username\":\"testuser\",\"password\":\"testpass\"}'\n\n# Get users (authenticated)\ncurl http://localhost/v2/users \\\n  -H \"Authorization: Bearer \u003ctoken\u003e\"\n```\n\n**Using Postman/Insomnia:**\nImport the OpenAPI schema from `/openapi` for automatic request generation.\n\n## License\n\nSee [LICENSE](LICENSE) file for details.\n\n## Contributing\n\n1. Fork the repository\n2. Create a feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit your changes (`git commit -m 'Add amazing feature'`)\n4. Run tests and formatting (`vendor/bin/phpunit \u0026\u0026 bun run prettier:check`)\n5. Push to the branch (`git push origin feature/amazing-feature`)\n6. Open a Pull Request\n\n## Support\n\nFor issues, questions, or contributions, please open an issue on the GitHub repository.\n\n---\n\nBuilt with ❤️ for The Earth App\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fearth-app%2Fmantle2","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fearth-app%2Fmantle2","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fearth-app%2Fmantle2/lists"}