mirror of
https://github.com/HolgerHatGarKeineNode/einundzwanzig-app.git
synced 2026-06-17 16:40:31 +00:00
✨ 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:
@@ -185,6 +185,19 @@ class Meetup extends Model implements HasMedia
|
||||
return $this->users()->whereKey($user->id)->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ist der Nutzer Leader dieses Meetups (meetup_user.is_leader = true)?
|
||||
* Nur Leader (und der Ersteller/Super-Admin) dürfen Stammdaten bearbeiten
|
||||
* und weitere Leader einsetzen/entziehen.
|
||||
*/
|
||||
public function isLeader(User $user): bool
|
||||
{
|
||||
return $this->users()
|
||||
->whereKey($user->id)
|
||||
->wherePivot('is_leader', true)
|
||||
->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Den Nutzer als Mitglied (nicht Leader) zu „Meine Meetups" hinzufügen.
|
||||
* Idempotent: ein bereits hinzugefügter Nutzer bleibt unverändert. Gibt
|
||||
@@ -233,6 +246,32 @@ class Meetup extends Model implements HasMedia
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Meetups, die der Nutzer als Leader führt (meetup_user.is_leader = true).
|
||||
* Maßgeblich dafür, wer Stammdaten UND Termine bearbeiten darf.
|
||||
*/
|
||||
public function scopeLedBy(Builder $query, int $userId): void
|
||||
{
|
||||
$query->whereHas('users', fn (Builder $user) => $user->whereKey($userId)->wherePivot('is_leader', true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Führt der eingeloggte Nutzer dieses Meetup als Leader? Steuert die
|
||||
* Sichtbarkeit der Bearbeiten-/Termin-Affordances im Portal-Frontend
|
||||
* (Gegenstück zu {@see belongsToMe()}, aber leader- statt mitgliedschafts-
|
||||
* basiert).
|
||||
*/
|
||||
protected function leadByMe(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn (): bool => auth()->check() && DB::table('meetup_user')
|
||||
->where('meetup_id', $this->id)
|
||||
->where('user_id', auth()->id())
|
||||
->where('is_leader', true)
|
||||
->exists()
|
||||
);
|
||||
}
|
||||
|
||||
public function city(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(City::class);
|
||||
|
||||
+1
-1
@@ -105,7 +105,7 @@ class User extends Authenticatable implements CipherSweetEncrypted
|
||||
|
||||
public function meetups()
|
||||
{
|
||||
return $this->belongsToMany(Meetup::class);
|
||||
return $this->belongsToMany(Meetup::class)->withPivot('is_leader');
|
||||
}
|
||||
|
||||
public function reputations()
|
||||
|
||||
Reference in New Issue
Block a user