Implement leadership-based permissions for Meetup management

- 🔒 Restrict event creation, editing, and deletion to Meetup leaders (`is_leader`) and creators for consistency across APIs, frontend, and MCP.
-  Add new APIs for leader delegation: assign/remove Meetup leaders via `meetup_user.is_leader`.
- 🛠️ Replace loose member checks with specific leadership checks in policies, controllers, and views.
- 🧪 Add exhaustive tests to ensure only eligible leaders execute critical actions (e.g., event creation/edit, Meetup updates).
- 🔄 Refactor pivot relationships and models (`leadByMe`, `isLeader`) for explicit leadership handling.
-  Introduce artisan command `meetups:promote-existing-leaders` to transition legacy data.
This commit is contained in:
HolgerHatGarKeineNode
2026-06-16 22:04:34 +02:00
parent 39af153f52
commit 9f8fda294a
26 changed files with 691 additions and 70 deletions
+7
View File
@@ -8,6 +8,7 @@ use App\Http\Controllers\Api\CourseEventController;
use App\Http\Controllers\Api\LecturerController;
use App\Http\Controllers\Api\MeetupController;
use App\Http\Controllers\Api\MeetupEventController;
use App\Http\Controllers\Api\MeetupLeaderController;
use App\Http\Controllers\Api\MeetupMapController;
use App\Http\Controllers\Api\NostrPlebController;
use App\Http\Controllers\Api\UserController;
@@ -82,6 +83,12 @@ Route::middleware('auth:sanctum')
Route::delete('my-meetups/{meetup:slug}', [MeetupController::class, 'removeFromMine'])->name('meetup.mine.remove');
Route::get('my-meetups/{meetup}', [MeetupController::class, 'mineShow'])->name('meetup.mine.show');
// Leader-Delegation: bestehende Leader setzen weitere Leader per npub
// ein bzw. entziehen sie (meetup_user.is_leader). Siehe MeetupPolicy.
Route::get('meetup/{meetup}/leaders', [MeetupLeaderController::class, 'index'])->name('meetup.leaders.index');
Route::post('meetup/{meetup}/leaders', [MeetupLeaderController::class, 'store'])->name('meetup.leaders.store');
Route::delete('meetup/{meetup}/leaders/{user}', [MeetupLeaderController::class, 'destroy'])->name('meetup.leaders.destroy');
Route::post('meetup-events', [MeetupEventController::class, 'store'])->name('meetup-events.store');
Route::patch('meetup-events/{meetupEvent}', [MeetupEventController::class, 'update'])->name('meetup-events.update');
Route::get('my-meetup-events', [MeetupEventController::class, 'mine'])->name('meetup-events.mine');