# Bongo Post Package - Cursor AI Rules

This package provides blog post and post category management for the Bongo CMS.

## Project Structure

```
src/
├── Config/
│   └── post.php                    # Package configuration (URL prefixes)
├── Events/
│   ├── PostCreated.php             # Fired when post is created
│   ├── PostUpdated.php             # Fired when post is updated
│   ├── PostDeleted.php             # Fired when post is deleted
│   ├── PostCategoryCreated.php     # Fired when category is created
│   ├── PostCategoryUpdated.php     # Fired when category is updated
│   └── PostCategoryDeleted.php     # Fired when category is deleted
├── Http/
│   ├── Controllers/
│   │   ├── Api/                    # API endpoints (auth:sanctum)
│   │   │   ├── PostController.php
│   │   │   └── PostCategoryController.php
│   │   ├── Backend/                # Admin controllers (auth + employee)
│   │   │   ├── PostController.php
│   │   │   ├── PostDatatableController.php
│   │   │   ├── PostImageController.php
│   │   │   ├── PostCategoryController.php
│   │   │   ├── PostCategoryDatatableController.php
│   │   │   └── PostCategoryImageController.php
│   │   └── Frontend/               # Public-facing controllers
│   │       ├── PostController.php
│   │       ├── PostImageController.php
│   │       ├── PostBackgroundImageController.php
│   │       └── PostCategoryController.php
│   ├── Requests/
│   │   ├── StorePostRequest.php    # Validates unique slug, max 75 chars
│   │   ├── UpdatePostRequest.php
│   │   ├── StorePostCategoryRequest.php
│   │   └── UpdatePostCategoryRequest.php
│   ├── Resources/
│   │   ├── PostResource.php        # API resource transformation
│   │   └── PostCategoryResource.php
│   └── ViewComposers/
│       ├── PostComposer.php        # Provides posts for dropdowns
│       ├── PostCategoryComposer.php # Provides categories for dropdowns
│       └── RecentPostsComposer.php # Provides recent posts
├── Listeners/
│   ├── SetPostDate.php             # Auto-sets post date on creation
│   └── SetPostUser.php             # Auto-sets user_id on creation
├── Migrations/                     # 15 migration files
├── Models/
│   ├── Post.php                    # Main post model with Imageable
│   └── PostCategory.php            # Category model with Imageable
├── Routes/
│   ├── api.php                     # API routes (/api/posts, /api/post-categories)
│   ├── backend.php                 # Admin routes (/admin/posts, /admin/categories)
│   └── frontend.php                # Public routes (/posts, /categories)
├── Seeders/
│   ├── PackageSeeder.php
│   └── DataSeeder.php
├── Traits/
│   └── HasRelated.php              # Related posts functionality
├── Translations/
└── Views/
    ├── backend/                    # Admin Blade templates
    └── frontend/                   # Public Blade templates
```

## Architecture Patterns

### Service Provider Pattern
- Extends `Bongo\Framework\Providers\AbstractServiceProvider`
- `$module = 'post'` enables automatic bootstrapping:
  - Config from `src/Config/post.php`
  - Routes from `src/Routes/*.php`
  - Views from `src/Views/post/`
  - Migrations from `src/Migrations/`
  - Translations from `src/Translations/`
- Event listeners registered via `$listeners` array
- View composers registered via `$composers` array
- Registers `PostCategory` alias for facade access

### Model Architecture
Both `Post` and `PostCategory` models:
- Extend `Bongo\Framework\Models\AbstractModel`
- Implement `Bongo\Image\Interfaces\Imageable` (image attachment support)
- Use multiple framework traits:
  - `HasUUID` - UUID generation and indexing
  - `HasKey` - Auto-generates URL-friendly key from name
  - `HasStatus` - Status management (pending/active/inactive)
  - `HasContent` - Content field with helper methods
  - `HasSeo` - Meta title, description, canonical, index fields
  - `HasHeaderClass` - Sticky/transparent header configuration
  - `HasImages` - Image attachment via bongo/image package
  - `SoftDeletes` - Laravel soft delete functionality

### Post Model Specifics
- Uses `Date` cast for date field (custom Bongo cast)
- Implements `HasRelated` trait:
  - `getRelated()` - Query builder for related posts by category
  - `getRelatedByRandom($limit)` - Random related posts collection
  - `getPrimaryCategory()` - First category relationship
  - `getPrevious()` - Previous post in current category context
  - `getNext()` - Next post in current category context
- `duplicate()` method - Clones post with new UUID, slug, key, status
- `getSummaryAttribute()` - Fallback to auto-generated summary if empty
- Many-to-many relationship with PostCategory via `post_categories_pivot`

### Event-Driven Architecture
Events fire on model lifecycle and integrate with other packages:
- `PostCreated` → `SetPostDate`, `SetPostUser` (queued listeners)
- `PostUpdated` → `ClearMenuCache` (bongo/menu), `UpdateSitemap` (bongo/sitemap)
- `PostDeleted` → `ClearMenuCache`, `UpdateSitemap`
- `PostCategoryUpdated` → `ClearMenuCache`, `UpdateSitemap`
- `PostCategoryDeleted` → `ClearMenuCache`, `UpdateSitemap`

### Route Structure
**Frontend Routes** (src/Routes/frontend.php):
- Prefix: configurable via `config('post.prefix')` (default: 'posts')
- Named: `frontend.post.*`
- Middleware: `hasRedirects`, `hasShortCodes`, `minifyHtml`
- Editor routes: `post.edit`, `post.update` (auth + employee middleware)
- Image upload routes: `post.image`, `post.background_image`
- Public routes: `post.index`, `post.show`

**Backend Routes** (src/Routes/backend.php):
- Prefix: configurable via `config('post.prefix')` (default: 'posts')
- Named: `backend.post.*`
- Middleware: `auth` + `employee` (auto-applied by AbstractServiceProvider)
- CRUD: index, create, store, show, edit, update, destroy
- Datatables: `post.datatable`, `post_category.datatable`
- Special: `post.duplicate` - duplicates existing post

**API Routes** (src/Routes/api.php):
- Prefix: `/api/posts`, `/api/post-categories`
- Named: `api.post.*`, `api.post_category.*`
- Middleware: `auth:sanctum` (auto-applied by AbstractServiceProvider)
- Currently: index endpoints only for both resources

## Coding Conventions

### Naming Standards
- Controllers: Singular resource name + Controller (e.g., `PostController`)
- Models: Singular PascalCase (e.g., `Post`, `PostCategory`)
- Table names: Plural snake_case (e.g., `posts`, `post_categories`)
- Route names: Dot notation with resource prefix (e.g., `backend.post.show`)
- View paths: Module namespace + path (e.g., `post::backend.index`)
- Event classes: Past tense verb + model (e.g., `PostCreated`, `PostDeleted`)
- Listener classes: Descriptive action (e.g., `SetPostDate`, `SetPostUser`)

### Return Types
- Always use return type declarations on public methods
- Controller actions return `View`, `RedirectResponse`, or `JsonResponse`
- Model methods return specific types (e.g., `?PostCategory`, `Collection`)
- Use nullable types (`?Type`) when appropriate

### Status Constants
- Use model constants for status values:
  - `Post::PENDING`, `Post::ACTIVE`, `Post::INACTIVE`
  - `Post::INDEX`, `Post::NO_INDEX`
  - `Post::ENABLED`, `Post::DISABLED`
- Never use magic strings for status values

### Form Requests
- Separate request classes for store vs update operations
- Validation rules must check for soft-deleted records:
  - `unique:posts,slug,NULL,id,deleted_at,NULL`
- Max length for post names: 75 characters (slug generation limit)

### Controller Patterns
Backend controllers follow consistent patterns:
```php
// Always inject model in constructor
public function __construct(Post $post)
{
    $this->post = $post;
}

// Store/Update fire events after persistence
public function store(StorePostRequest $request): RedirectResponse
{
    $post = $this->post->create($request->all());
    $this->syncPostCategories($post);
    event(new PostCreated($post));

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

// Destroy checks if deletion succeeded
public function destroy(Post $post)
{
    if ($post->delete()) {
        event(new PostDeleted($post));
        return redirect()->success(trans('post::backend.delete_success'));
    }
    return back()->error(trans('post::backend.delete_failed'));
}
```

## Common Tasks

### Adding a New Post Field
1. Create migration: `src/Migrations/YYYY_MM_DD_HHMMSS_add_field_to_posts_table.php`
2. Add field to `Post::$fillable` array in `src/Models/Post.php`
3. Add to form request validation rules if needed
4. Update views: `src/Views/backend/edit.blade.php`, `src/Views/frontend/show.blade.php`
5. Add to `PostResource::toArray()` if exposed via API

### Creating a New Event Listener
1. Create listener class in `src/Listeners/`
2. Implement `ShouldQueue` interface for async processing
3. Add `public int $tries = 3;` for queue retry logic
4. Register in `PostServiceProvider::$listeners` array:
   ```php
   protected array $listeners = [
       PostCreated::class => [
           YourNewListener::class,
       ],
   ];
   ```

### Adding a Custom Route
1. Add route to appropriate file in `src/Routes/`:
   - `api.php` - API endpoints (requires authentication)
   - `backend.php` - Admin panel routes
   - `frontend.php` - Public-facing routes
2. Routes are auto-registered by AbstractServiceProvider
3. Use route naming convention: `{area}.post.{action}`

### Extending Post Queries
Use model scopes from framework traits:
- `Post::active()` - Only active posts (from HasStatus)
- `Post::pending()` - Only pending posts
- `Post::inactive()` - Only inactive posts
- Combine with eager loading: `Post::active()->with('categories', 'images')->get()`

### Working with Related Posts
```php
// Get random related posts (same categories)
$relatedPosts = $post->getRelatedByRandom(4);

// Get primary category
$category = $post->getPrimaryCategory();

// Get previous/next in category context
$previous = $post->getPrevious();
$next = $post->getNext();

// Get all related posts query builder
$query = $post->getRelated();
```

### Duplicating a Post
```php
// Built-in duplication method
$newPost = $post->duplicate();
// Creates copy with:
// - New UUID and slug
// - Current user as author
// - Current timestamp
// - Pending status
// - Cloned category and image relationships
```

## Testing

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

### Test Structure
- Feature tests: `tests/Feature/`
- Unit tests: `tests/Unit/`
- Test environment uses SQLite (DB_CONNECTION=testing in phpunit.xml)

## Available Commands
Check `composer.json` for defined scripts. Common commands:
```bash
vendor/bin/pint              # Fix code style (Laravel Pint)
vendor/bin/pint --test       # Check code style without fixing
vendor/bin/phpunit           # Run test suite
```

## Dependencies

### Required Packages
- `bongo/framework` (^3.0) - Base framework, AbstractServiceProvider, traits
- `bongo/image` (^3.0) - Image attachment system, Imageable interface
- `bongo/menu` (^3.0) - Menu cache clearing integration
- `bongo/package` (^3.0) - Package management
- `bongo/setting` (^3.0) - Settings system

### Optional Integrations
- `bongo/sitemap` - Sitemap updates on post changes (listener auto-registered)

## Key Configuration
- `post.prefix` - URL prefix for post routes (default: 'posts')
- `post.category_prefix` - URL prefix for category routes (default: 'categories')

## Database Schema

### Posts Table
Key columns:
- `uuid` - Unique identifier (indexed)
- `user_id` - Foreign key to users
- `date` - Publication timestamp (nullable)
- `name`, `slug`, `key` - Title and URL identifiers
- `content` - Long text content
- `summary` - Short description (nullable, auto-generated fallback)
- `status` - Enum (pending/active/inactive)
- `schema` - Structured data for SEO
- SEO fields: `meta_title`, `meta_description`, `meta_canonical`, `meta_index`
- Override fields: `css`, `js`
- Audit fields: `created_by`, `updated_by`, `deleted_by`
- Soft deletes enabled

### Post Categories Table
Similar structure to posts but without `date`, `summary`, or `schema` fields.

### Pivot Table
`post_categories_pivot` - Many-to-many relationship between posts and categories.

## Image Management
Both Post and PostCategory implement `Imageable`:
- Upload images via controller endpoints
- Access via `$post->images` relationship
- Get primary image: `$post->getPrimaryImage(['preset' => 'thumb'])`
- Check existence: `$post->hasImages()`

## Frontend Middleware
Public routes use three middleware automatically:
- `hasRedirects` - Handles URL redirects
- `hasShortCodes` - Processes shortcodes in content
- `minifyHtml` - Minifies HTML output

## Translation Keys
Translation files in `src/Translations/` namespace: `post::`
- Backend messages: `post::backend.store_success`, `post::backend.delete_failed`, etc.
- Use `trans('post::backend.key')` in controllers

## Notes
- Posts support session-based category context for previous/next navigation
- Summary field auto-generates if empty using `getSummary()` method from HasContent trait
- All create/update operations should sync category relationships via `syncPostCategories()`
- Image uploads happen separately from post creation/update
- Duplicate function preserves relationships but creates new unique identifiers
