php-framework/packages/core-php/TODO.md

297 lines
7.6 KiB
Markdown
Raw Normal View History

# Core-PHP TODO
## Implemented
### Actions Pattern ✓
`Core\Actions\Action` trait for single-purpose business logic classes.
```php
use Core\Actions\Action;
class CreateThing
{
use Action;
public function handle(User $user, array $data): Thing
{
// Complex business logic here
}
}
// Usage
$thing = CreateThing::run($user, $data);
```
**Location:** `src/Core/Actions/Action.php`, `src/Core/Actions/Actionable.php`
---
## Seeder Auto-Discovery
**Priority:** Medium
**Context:** Currently apps need a `database/seeders/DatabaseSeeder.php` that manually lists module seeders in order. This is boilerplate that core-php could handle.
### Requirements
- Auto-discover seeders from registered modules (`*/Database/Seeders/*Seeder.php`)
- Support priority ordering via property or attribute (e.g., `public int $priority = 50`)
- Support dependency ordering via `$after` or `$before` arrays
- Provide base `DatabaseSeeder` class that apps can extend or use directly
- Allow apps to override/exclude specific seeders if needed
### Example
```php
// In a module seeder
class FeatureSeeder extends Seeder
{
public int $priority = 10; // Run early
public function run(): void { ... }
}
class PackageSeeder extends Seeder
{
public array $after = [FeatureSeeder::class]; // Run after features
public function run(): void { ... }
}
```
### Notes
- Current Host Hub DatabaseSeeder has ~20 seeders with implicit ordering
- Key dependencies: features → packages → workspaces → system user → content
- Could use Laravel's service container to resolve seeder graph
---
## Team-Scoped Caching
**Priority:** Medium
**Context:** Repeated queries for workspace-scoped resources. Cache workspace-scoped queries with auto-invalidation.
### Implementation
Extend `BelongsToWorkspace` trait:
```php
trait BelongsToWorkspace
{
public static function ownedByCurrentWorkspaceCached(int $ttl = 300)
{
$workspace = currentWorkspace();
if (!$workspace) return collect();
return Cache::remember(
static::workspaceCacheKey($workspace->id),
$ttl,
fn() => static::ownedByCurrentWorkspace()->get()
);
}
protected static function bootBelongsToWorkspace(): void
{
static::saved(fn($m) => static::clearWorkspaceCache($m->workspace_id));
static::deleted(fn($m) => static::clearWorkspaceCache($m->workspace_id));
}
}
```
### Usage
```php
// Cached for 5 minutes, auto-clears on changes
$biolinks = Biolink::ownedByCurrentWorkspaceCached();
```
---
## Activity Logging
**Priority:** Low
**Context:** No audit trail of user actions across modules.
### Implementation
Add `spatie/laravel-activitylog` integration:
```php
// Core trait for models
trait LogsActivity
{
use \Spatie\Activitylog\Traits\LogsActivity;
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logOnlyDirty()
->dontSubmitEmptyLogs();
}
}
```
### Requirements
- Base trait modules can use
- Activity viewer Livewire component for admin
- Workspace-scoped activity queries
---
## Multi-Tenant Data Isolation
**Priority:** High (Security)
**Context:** Multiple modules have workspace isolation issues.
### Issues
1. **Fallback workspace_id** - Some code falls back to `workspace_id = 1` when no context
2. **Global queries** - Some commands query globally without workspace scope
3. **Session trust** - Using `session('workspace_id', 1)` with hardcoded fallback
### Solution
- `BelongsToWorkspace` trait should throw exception when workspace context is missing (not fallback)
- Add `WorkspaceScope` global scope that throws on missing context
- Audit all models for proper scoping
- Add middleware that ensures workspace context before any workspace-scoped operation
---
## Bouncer Request Whitelisting
**Priority:** Medium
**Context:** Every controller action must be explicitly permitted. Unknown actions are blocked (production) or prompt for approval (training mode).
**Philosophy:** If it wasn't trained, it doesn't exist.
### Concept
```
Training Mode (Development):
1. Developer hits /admin/products
2. Clicks "Create Product"
3. System: "BLOCKED - No permission defined for:"
- Role: admin
- Action: product.create
- Route: POST /admin/products
4. Developer clicks [Allow for admin]
5. Permission recorded
6. Continue working
Production Mode:
If permission not in whitelist → 403 Forbidden
No exceptions. No fallbacks. No "default allow".
```
### Database Schema
```php
// core_action_permissions
Schema::create('core_action_permissions', function (Blueprint $table) {
$table->id();
$table->string('action'); // product.create, order.refund
$table->string('scope')->nullable(); // Resource type or specific ID
$table->string('guard')->default('web'); // web, api, admin
$table->string('role')->nullable(); // admin, editor, or null for any auth
$table->boolean('allowed')->default(false);
$table->string('source'); // 'trained', 'seeded', 'manual'
$table->string('trained_route')->nullable();
$table->foreignId('trained_by')->nullable();
$table->timestamp('trained_at')->nullable();
$table->timestamps();
$table->unique(['action', 'scope', 'guard', 'role']);
});
// core_action_requests (audit log)
Schema::create('core_action_requests', function (Blueprint $table) {
$table->id();
$table->string('method');
$table->string('route');
$table->string('action');
$table->string('scope')->nullable();
$table->string('guard');
$table->string('role')->nullable();
$table->foreignId('user_id')->nullable();
$table->string('ip_address')->nullable();
$table->string('status'); // allowed, denied, pending
$table->boolean('was_trained')->default(false);
$table->timestamps();
$table->index(['action', 'status']);
});
```
### Action Resolution
```php
// Explicit via route attribute
Route::post('/products', [ProductController::class, 'store'])
->action('product.create');
// Or via controller attribute
#[Action('product.create')]
public function store(Request $request) { ... }
// Or auto-resolved from controller@method
// ProductController@store → product.store
```
### Integration with Existing Auth
```
Request
BouncerGate (action whitelisting)
│ "Is this action permitted at all?"
Laravel Gate/Policy (authorisation)
│ "Can THIS USER do this to THIS RESOURCE?"
Controller
```
### Implementation Phases
**Phase 1: Core**
- [ ] Database migrations
- [ ] `ActionPermission` model
- [ ] `BouncerService` with `check()` method
- [ ] `BouncerGate` middleware
**Phase 2: Training Mode**
- [ ] Training UI (modal prompt)
- [ ] Training controller/routes
- [ ] Request logging
**Phase 3: Tooling**
- [ ] `bouncer:export` command
- [ ] `bouncer:list` command
- [ ] Admin UI for viewing/editing permissions
**Phase 4: Integration**
- [ ] Apply to admin routes
- [ ] Apply to API routes
- [ ] Documentation
### Artisan Commands
```bash
php artisan bouncer:export # Export trained permissions to seeder
php artisan bouncer:seed # Import from seeder
php artisan bouncer:list # List all defined actions
php artisan bouncer:reset # Clear training data
```
### Benefits
1. **Complete audit trail** - Know exactly what actions exist in your app
2. **No forgotten routes** - If it's not trained, it doesn't work
3. **Role-based by default** - Actions scoped to guards and roles
4. **Deployment safety** - Export/import permissions between environments
5. **Discovery tool** - Training mode maps your entire app's surface area