# Bongo Question Package - Cursor AI Instructions

## Overview

This is a Laravel package for managing customer questions/testimonials. It provides both backend (admin) and frontend (public) interfaces for creating, managing, and displaying customer questions with star ratings.

**Key Features:**
- Customer question submission with reCAPTCHA protection
- Admin question management with datatable interface
- Star rating system (1-5 stars)
- Email notifications (to customer and admin)
- Question moderation (pending/active/inactive status)
- Excel export functionality
- Cached statistics (average rating, question count, latest question)
- Honeypot spam protection
- UUID-based public URLs

## Package Information

- **Namespace:** `Bongo\Question`
- **Dependencies:**
  - `bongo/framework` ^3.0 (provides AbstractServiceProvider, AbstractModel, AbstractController)
  - `bongo/captcha` (for reCAPTCHA validation)
  - PHP 8.2+, Laravel 10+
- **Service Provider:** `Bongo\Question\QuestionServiceProvider`

## Project Structure

```
src/
├── Config/
│   └── question.php                           # Configuration (route prefix, reCAPTCHA settings)
├── Http/
│   ├── Controllers/
│   │   ├── Backend/
│   │   │   ├── QuestionController.php         # Admin CRUD operations
│   │   │   └── QuestionDatatableController.php # Datatable API
│   │   └── Frontend/
│   │       └── QuestionController.php         # Public question submission/display
│   ├── Requests/
│   │   ├── StoreFrontendQuestionRequest.php   # Frontend validation with reCAPTCHA
│   │   ├── StoreQuestionRequest.php           # Backend store validation
│   │   └── UpdateQuestionRequest.php          # Backend update validation
│   └── ViewComposers/
│       ├── AverageRatingComposer.php        # Injects average rating into views
│       ├── LatestQuestionComposer.php         # Injects latest question into views
│       └── NumberOfQuestionsComposer.php      # Injects question count into views
├── Models/
│   └── Question.php                           # Question model with HasContent, HasStatus, HasUUID traits
├── Events/
│   ├── QuestionCreated.php                    # Fired when question created
│   ├── QuestionUpdated.php                    # Fired when question updated
│   └── QuestionDeleted.php                    # Fired when question deleted
├── Listeners/
│   └── ClearQuestionCache.php                 # Clears cache when questions change (queued)
├── Mailables/
│   ├── QuestionMailable.php                   # Email sent to customer
│   └── AdminQuestionMailable.php              # Email sent to admin
├── Commands/
│   └── ExportQuestionsCommand.php             # Export questions to CSV
├── Exports/
│   └── QuestionExport.php                     # Excel export handler
├── Migrations/
│   ├── 2021_01_01_000001_create_questions_table.php
│   ├── 2021_01_01_000002_add_title_column_to_questions_table.php
│   ├── 2021_01_01_000003_add_email_column_to_questions_table.php
│   └── 2021_01_01_000004_add_date_column_to_questions_table.php
├── Views/
│   ├── backend/                             # Admin interface views
│   ├── frontend/                            # Public interface views
│   ├── mail/                                # Email templates (HTML + plain text)
│   └── exports/                             # Excel export template
├── Routes/
│   ├── backend.php                          # Admin routes (/admin/questions)
│   └── frontend.php                         # Public routes (/questions)
├── Seeders/
│   └── PackageSeeder.php                    # Seeds package metadata
├── Translations/
│   └── en/
│       ├── backend.php                      # Admin translations
│       └── frontend.php                     # Frontend translations
└── QuestionServiceProvider.php                # Service provider
```

## Architecture Patterns

### Service Provider Bootstrapping

The package extends `Bongo\Framework\Providers\AbstractServiceProvider` which automatically:
- Loads config from `src/Config/question.php`
- Registers routes from `src/Routes/backend.php` and `src/Routes/frontend.php`
- Loads views from `src/Views/question/`
- Runs migrations from `src/Migrations/`
- Registers commands, listeners, and view composers via arrays

```php
class QuestionServiceProvider extends AbstractServiceProvider
{
    protected string $module = 'question';

    protected array $commands = [ExportQuestionsCommand::class];

    protected array $composers = [
        NumberOfQuestionsComposer::class => ['question::frontend.partials.summary'],
        LatestQuestionComposer::class => ['question::frontend.partials.summary'],
        AverageRatingComposer::class => ['question::frontend.partials.summary'],
    ];

    protected array $listeners = [
        QuestionCreated::class => [ClearQuestionCache::class],
        QuestionUpdated::class => [ClearQuestionCache::class, UpdateSitemap::class],
    ];
}
```

### Route Structure

**Backend routes** (`src/Routes/backend.php`):
- Prefix: `/admin/{config('question.prefix')}` (default: `/admin/questions`)
- Named: `backend.question.*`
- Middleware: `auth` + `employee` (automatically applied by AbstractServiceProvider)
- Routes: index, create, store, show, edit, update, destroy, datatable

**Frontend routes** (`src/Routes/frontend.php`):
- Prefix: `/{config('question.prefix')}` (default: `/questions`)
- Named: `frontend.question.*`
- Middleware: Standard web middleware
- Routes: index, show (by UUID), store (with honeypot protection)

### Model Architecture

**Question Model** (`src/Models/Question.php`):
```php
class Question extends AbstractModel
{
    use HasContent;   // Provides content field handling
    use HasStatus;    // Provides status scopes (active(), pending(), inactive())
    use HasUUID;      // Auto-generates UUID on creation
    use SoftDeletes;  // Soft deletion support

    const PENDING = 'pending';
    const ACTIVE = 'active';
    const INACTIVE = 'inactive';

    protected $fillable = ['date', 'title', 'content', 'rating', 'status', 'name', 'email'];
    protected $casts = ['date' => Date::class];
}
```

### Event-Driven Architecture

**Events:**
- `QuestionCreated` - Fired after question creation (both frontend and backend)
- `QuestionUpdated` - Fired after question update
- `QuestionDeleted` - Fired after question deletion

**Listeners:**
- `ClearQuestionCache` - Implements `ShouldQueue`, clears three cache keys:
  - `no_of_questions`
  - `latest_question`
  - `average_rating`

### Caching Strategy

View composers use Laravel's cache to optimize statistics:

```php
// Example from AverageRatingComposer
$averageRating = Cache::remember('average_rating', config('settings.cache_default'), function () {
    return ceil(Question::active()->average('rating'));
});
```

Cache is automatically cleared by `ClearQuestionCache` listener on question changes.

### Email Workflow

When a customer submits a question via frontend:
1. Question is created with 'pending' status
2. `QuestionCreated` event fired
3. Two emails sent:
   - `QuestionMailable` → customer email (confirmation)
   - `AdminQuestionMailable` → admin email (notification)
4. Redirect to `config('question.success_url')`

## Coding Conventions

### Naming Conventions
- **Models:** Singular (Question)
- **Controllers:** Singular + Controller suffix (QuestionController)
- **Events:** Past tense (QuestionCreated, QuestionUpdated)
- **Listeners:** Action-based (ClearQuestionCache)
- **Requests:** Action + Request suffix (StoreFrontendQuestionRequest)
- **View Composers:** Purpose + Composer suffix (AverageRatingComposer)

### Return Types
- Controller methods return `View` or `RedirectResponse` explicitly
- Model scopes return `Builder` (inherited from HasStatus trait)
- Event listeners return `void`
- View composers return `void`

### Controller Patterns

**Backend Controller:**
```php
public function store(StoreQuestionRequest $request): RedirectResponse
{
    $question = $this->question->create($request->all());
    event(new QuestionCreated($question));

    return redirect()
        ->route('backend.question.show', $question->id)
        ->success(trans('question::backend.store_success'));
}
```

**Frontend Controller:**
```php
public function store(StoreFrontendQuestionRequest $request): RedirectResponse
{
    $question = Question::create(Arr::except($request->validated(), ['captcha-response']));
    event(new QuestionCreated($question));

    Mail::to($question->email)->send(new QuestionMailable($question));
    Mail::to(setting("client::company.email"))->send(new AdminQuestionMailable($question));

    return redirect()
        ->to(config('question.success_url'))
        ->success(trans('question::frontend.store_success'));
}
```

### Validation Patterns

Frontend requests include reCAPTCHA validation using `Bongo\Captcha\Rules\Captcha`:

```php
public function rules(): array
{
    $recaptchaEnabled = setting()->reCaptchaEnabled() && config('question.recaptcha.enabled');
    $recaptchaMinScore = config('question.recaptcha.min_score', 0.5);

    return [
        'title' => 'required',
        'content' => 'required',
        'rating' => 'required',
        'name' => 'required',
        'email' => 'required|string|email:rfc,dns|max:50',
        'captcha-response' => new Captcha(
            action: 'question',
            minScore: $recaptchaMinScore,
            enabled: $recaptchaEnabled,
        ),
    ];
}
```

## Common Tasks

### Adding a New Question Field

1. **Create migration:**
   ```php
   Schema::table('questions', function (Blueprint $table) {
       $table->string('new_field')->nullable();
   });
   ```

2. **Add to model fillable:**
   ```php
   protected $fillable = [..., 'new_field'];
   ```

3. **Update form requests:**
   - `StoreQuestionRequest::rules()`
   - `StoreFrontendQuestionRequest::rules()`
   - `UpdateQuestionRequest::rules()`

4. **Update views:**
   - Backend form: `src/Views/backend/partials/form/details.blade.php`
   - Frontend form: `src/Views/frontend/partials/form.blade.php`
   - Display views: `src/Views/backend/show.blade.php`, `src/Views/frontend/show.blade.php`

5. **Update export template:**
   - `src/Views/exports/questions.blade.php`

### Modifying Cache Behaviour

Cache keys are defined in `ClearQuestionCache` listener:
```php
Cache::forget('no_of_questions');
Cache::forget('latest_question');
Cache::forget('average_rating');
```

To add new cached data:
1. Create new ViewComposer in `src/Http/ViewComposers/`
2. Register in `QuestionServiceProvider::$composers`
3. Add cache key to `ClearQuestionCache::handle()`

### Customising Email Templates

Email templates are located in `src/Views/mail/`:
- `question.blade.php` - HTML version for customer
- `question_plain.blade.php` - Plain text version for customer
- `admin_question.blade.php` - HTML version for admin
- `admin_question_plain.blade.php` - Plain text version for admin

Mailable classes:
- `QuestionMailable` - Uses `question` and `question_plain` templates
- `AdminQuestionMailable` - Uses `admin_question` and `admin_question_plain` templates

### Adjusting Question Moderation

Question status constants are defined in `Question` model:
```php
const PENDING = 'pending';
const ACTIVE = 'active';
const INACTIVE = 'inactive';
```

Frontend submissions default to 'pending' (set in database migration).

To auto-approve questions, modify `Frontend/QuestionController::store()`:
```php
$question = Question::create(array_merge(
    Arr::except($request->validated(), ['captcha-response']),
    ['status' => Question::ACTIVE]
));
```

### Working with reCAPTCHA

reCAPTCHA configuration in `src/Config/question.php`:
```php
'recaptcha' => [
    'enabled' => true,      // Enable/disable reCAPTCHA
    'min_score' => 0.5,     // Score threshold (0.1 - 1.0)
],
```

The `Captcha` rule checks both global and package-level settings:
```php
$recaptchaEnabled = setting()->reCaptchaEnabled() && config('question.recaptcha.enabled');
```

## Testing

### Running Tests
```bash
vendor/bin/phpunit
```

### Test Structure
- Base test case: `tests/TestCase.php`
- Database connection: `testing` (configured in phpunit.xml)

### Writing Tests

Create tests in `tests/` directory:
```php
namespace Bongo\Question\Tests;

class QuestionTest extends TestCase
{
    public function test_can_create_question()
    {
        $question = Question::factory()->create([
            'name' => 'John Doe',
            'rating' => 5,
        ]);

        $this->assertDatabaseHas('questions', [
            'name' => 'John Doe',
            'rating' => 5,
        ]);
    }
}
```

## Artisan Commands

### Export Questions
```bash
php artisan question:export
```
Exports all questions to `storage/app/questions.csv` using the `QuestionExport` class and the `question::exports.questions` Blade template.

### Code Quality
```bash
# Check code style
vendor/bin/pint --test

# Fix code style
vendor/bin/pint
```

## Configuration

### Config File (`src/Config/question.php`)

```php
return [
    'prefix' => 'questions',                       // Route prefix
    'success_url' => '/thank-you-for-your-question', // Post-submission redirect
    'recaptcha' => [
        'enabled' => true,                       // Package-level reCAPTCHA toggle
        'min_score' => 0.5,                      // Score threshold (0.1 - 1.0)
    ],
];
```

### Publishing Config
```bash
php artisan vendor:publish --provider="Bongo\Question\QuestionServiceProvider"
```

## Dependencies on Bongo Framework

This package relies on `bongo/framework` for:

### Base Classes
- `AbstractServiceProvider` - Auto-bootstrapping service provider
- `AbstractModel` - Base model with audit fields (created_by, updated_by, deleted_by)
- `AbstractController` - Base controller

### Traits
- `HasContent` - Provides content field handling
- `HasStatus` - Provides status scopes (active(), pending(), inactive())
- `HasUUID` - Auto-generates UUID on model creation

### Casts
- `Date` - Custom date cast used for question date field

### Helpers
- `setting()` - Global settings helper
- `console_print()` - Console output helper

## View Namespacing

Views are namespaced as `question::` and organized by context:
- `question::backend.*` - Admin interface views
- `question::frontend.*` - Public interface views
- `question::mail.*` - Email templates
- `question::exports.*` - Export templates

## Translation Keys

Translations are namespaced as `question::` and organized by context:
- `question::backend.*` - Admin interface translations
- `question::frontend.*` - Frontend interface translations

## Security Features

1. **reCAPTCHA v3** - Prevents automated spam submissions
2. **Honeypot** - Additional spam protection via `Spatie\Honeypot`
3. **Email Validation** - RFC and DNS validation
4. **Route Middleware** - Admin routes protected by `auth` + `employee`
5. **Status Moderation** - Questions default to 'pending' status
6. **UUID URLs** - Public question URLs use UUID instead of ID

## Extension Points

### Adding Custom Question Types

Create additional status constants or enum types:
```php
const FEATURED = 'featured';
```

Add migration to update enum:
```php
DB::statement("ALTER TABLE questions MODIFY COLUMN status ENUM('pending', 'active', 'inactive', 'featured')");
```

Add scope to model:
```php
public function scopeFeatured($query)
{
    return $query->where('status', self::FEATURED);
}
```

### Integrating with Other Packages

The package listens for `QuestionUpdated` and fires `UpdateSitemap` (from `bongo/sitemap` package) to keep sitemaps synchronized.

To add custom listeners:
```php
protected array $listeners = [
    QuestionCreated::class => [
        ClearQuestionCache::class,
        YourCustomListener::class,
    ],
];
```

### Customising Datatable

Override `QuestionDatatableController` to customize columns, filters, or sorting:
```php
namespace App\Http\Controllers\Backend;

use Bongo\Question\Http\Controllers\Backend\QuestionDatatableController as BaseController;

class QuestionDatatableController extends BaseController
{
    // Override methods here
}
```

Update route in your application routes file to use custom controller.
