# Contributing Guide

## Prerequisites

- PHP 8.2+
- MySQL 8.0+ (for local dev) / SQLite (for tests — no setup needed)
- Redis
- Node.js 20+ (for assets)

## Local Setup

```bash
git clone <repo>
cd elshrkawy_base
composer install && npm install
cp .env.example .env
php artisan key:generate
php artisan jwt:secret
php artisan migrate
composer dev
```

---

## Git Workflow

### Branch Naming

```
feature/short-description       # new functionality
fix/short-description           # bug fix
refactor/short-description      # code improvement without behavior change
chore/short-description         # tooling, dependencies, CI
docs/short-description          # documentation only
```

### Always branch from `main`

```bash
git checkout main && git pull
git checkout -b feature/add-product-crud
```

### Commit Messages

Format: `type: short description` (max 72 chars)

```
feat: add product CRUD with category filter
fix: prevent double OTP send on resend click
refactor: extract coupon validation to CouponService
test: add PermissionMiddlewareTest for wildcard permissions
chore: upgrade Laravel to 12.1
docs: update payment module setup guide
```

Types: `feat`, `fix`, `refactor`, `test`, `chore`, `docs`, `style`

### Pull Requests

1. Tests must pass: `php artisan test`
2. At least one review before merge
3. Squash commits if the history is messy
4. Link to the related issue/ticket in the PR description

PR title format: same as commit message format.

---

## Adding a New Feature

### New Admin CRUD

```bash
# 1. Create & run the migration
php artisan make:migration create_products_table
php artisan migrate

# 2. Scaffold everything
php artisan make:crud Product --with-tests --with-factory

# 3. Add permissions to the seeder or admin panel

# 4. Run tests
php artisan test --filter ProductTest
```

### New User Type

```bash
php artisan make:user-type {Name} {user|dashboard} --auth-type={email|phone}
```

### New Payment Gateway

1. Create `modules/Payment/src/Gateways/{Name}Gateway.php` extending `BasePaymentGateway`
2. Implement `PaymentGatewayInterface`: `initiate()`, `verify()`, `refund()`
3. Add to `PaymentGatewayEnum`
4. Register in `PaymentService::$registry`
5. Add config to `config/payment.php` and `.env.example`
6. Write tests in `modules/Payment/tests/`

---

## Testing

```bash
# Run all tests
php artisan test

# Run specific suite
php artisan test --testsuite Unit
php artisan test --testsuite Feature

# Run specific class or method
php artisan test --filter CountriesTest
php artisan test --filter "super_admin_can_create_country"

# With coverage
php artisan test --coverage --min=70
```

### Test Database

Tests use **SQLite in-memory** — no MySQL needed, no seed data needed. The `RefreshDatabase` trait in `TestCase` handles everything.

### Writing Tests

Every new feature needs at least:
- **Unit test** if it has non-trivial business logic
- **Feature test** for the HTTP endpoints (happy path + auth/permission checks)

```php
// Use the base TestCase for helpers
class ProductTest extends TestCase
{
    public function test_super_admin_can_list_products(): void
    {
        Product::factory()->count(3)->create();

        $this->actingAsSuperAdmin()
            ->getJson('/api/admin/products')
            ->assertOk();
    }

    public function test_client_cannot_access_admin_products(): void
    {
        $this->actingAsClient()
            ->getJson('/api/admin/products')
            ->assertStatus(403);
    }
}
```

---

## Code Standards

### Controllers

Keep them thin — delegate to services:

```php
// ✓ Good
public function store(ProductRequest $request): JsonResponse
{
    return DB::transaction(function () use ($request) {
        $product = $this->service->create($request);
        return $this->sendResponse(ProductResource::make($product), __('Created'));
    });
}

// ✗ Bad — business logic in controller
public function store(Request $request): JsonResponse
{
    $product = Product::create($request->all());
    // ...
}
```

### Services

Override these hooks in `DashboardCRUDService` subclasses:

```php
class ProductService extends DashboardCRUDService
{
    protected function applyGlobalScopes(Builder $query): void
    {
        $query->where('is_published', true);
    }

    protected function eagerLoad(): array
    {
        return ['category', 'media'];   // ← always declare relations here
    }

    protected function afterSave($request, $model): void
    {
        // post-save hooks
    }
}
```

### API Responses

Always use the `json()` helper:

```php
json($data)                          // 200 success
json($data, 'Created')               // 200 with message
json('Error message', status: 'fail', headerStatus: 422)
json(null, status: 'fail', headerStatus: 403)
```

### Translations

Request payloads use locale as the parent key:

```json
{
  "en": { "name": "Saudi Arabia" },
  "ar": { "name": "المملكة العربية السعودية" }
}
```

### Permissions

Permission names must match route names exactly:

```php
// Route name: countries.index
// Permission: countries.index
$this->middleware('permission:countries.index')->only('index');
```

---

## Environment Variables

Copy `.env.example` to `.env` and fill in:

| Variable | Required | Notes |
|----------|----------|-------|
| `APP_KEY` | Yes | Run `php artisan key:generate` |
| `JWT_SECRET` | Yes | Run `php artisan jwt:secret` |
| `DB_*` | Yes | MySQL credentials |
| `REDIS_*` | Yes | Redis for cache + queue |
| `FCM_*` | Push only | Firebase credentials |
| `PAYMENT_MODE` | Payments | `test` or `live` |
| `TELESCOPE_ALLOWED_EMAILS` | Dev | Emails that can view Telescope |

---

## Common Issues

**`php artisan` throws class not found after adding new files:**
```bash
composer dump-autoload
```

**Permission changes not taking effect:**
```bash
php artisan clear:permission-cache
```

**Tests fail on missing table:**
The test database uses `RefreshDatabase` and runs all migrations fresh. If you added a migration but forgot to run it, tests still work. But if the migration has errors, tests will fail — check `php artisan migrate --database=sqlite`.

**Rate limiting blocking tests:**
The `TestCase::setUp()` clears rate limiters for `login`, `register`, `forget`. If you add a new throttle key, clear it there too.
