diff --git a/app/Auth/NostrUser.php b/app/Auth/NostrUser.php index 8d563c5..1c4f431 100644 --- a/app/Auth/NostrUser.php +++ b/app/Auth/NostrUser.php @@ -9,7 +9,7 @@ class NostrUser implements Authenticatable { protected string $pubkey; - protected ?object $pleb; + protected ?EinundzwanzigPleb $pleb; public function __construct(string $pubkey) { @@ -63,4 +63,9 @@ class NostrUser implements Authenticatable { return $this->pleb; } + + public function isBoardMember(): bool + { + return $this->pleb?->isBoardMember() ?? false; + } } diff --git a/app/Livewire/Traits/WithNostrAuth.php b/app/Livewire/Traits/WithNostrAuth.php index 6c00a75..dd44469 100644 --- a/app/Livewire/Traits/WithNostrAuth.php +++ b/app/Livewire/Traits/WithNostrAuth.php @@ -37,7 +37,7 @@ trait WithNostrAuth ->where('pubkey', $pubkey) ->first(); - if ($this->currentPleb && in_array($this->currentPleb->npub, config('einundzwanzig.config.current_board'), true)) { + if ($this->currentPleb && $this->currentPleb->isBoardMember()) { $this->canEdit = true; } @@ -62,7 +62,7 @@ trait WithNostrAuth $this->currentPleb = $user->getPleb(); $this->isAllowed = true; - if ($this->currentPleb && in_array($this->currentPleb->npub, config('einundzwanzig.config.current_board'), true)) { + if ($this->currentPleb && $this->currentPleb->isBoardMember()) { $this->canEdit = true; } } diff --git a/app/Models/EinundzwanzigPleb.php b/app/Models/EinundzwanzigPleb.php index d012b46..aef7faf 100644 --- a/app/Models/EinundzwanzigPleb.php +++ b/app/Models/EinundzwanzigPleb.php @@ -50,4 +50,18 @@ class EinundzwanzigPleb extends Authenticatable implements CipherSweetEncrypted { return $this->hasMany(PaymentEvent::class); } + + public function isBoardMember(): bool + { + return in_array($this->npub, config('einundzwanzig.config.current_board', []), true); + } + + public function hasPaidMembership(?int $year = null): bool + { + return $this->association_status->value > 1 + && $this->paymentEvents() + ->where('year', $year ?? (int) date('Y')) + ->where('paid', true) + ->exists(); + } } diff --git a/app/Policies/ElectionPolicy.php b/app/Policies/ElectionPolicy.php index 351fc9d..e5178fb 100644 --- a/app/Policies/ElectionPolicy.php +++ b/app/Policies/ElectionPolicy.php @@ -29,7 +29,7 @@ class ElectionPolicy */ public function create(NostrUser $user): bool { - return $this->isBoardMember($user); + return $user->isBoardMember(); } /** @@ -38,7 +38,7 @@ class ElectionPolicy */ public function update(NostrUser $user, Election $election): bool { - return $this->isBoardMember($user); + return $user->isBoardMember(); } /** @@ -47,7 +47,7 @@ class ElectionPolicy */ public function delete(NostrUser $user, Election $election): bool { - return $this->isBoardMember($user); + return $user->isBoardMember(); } /** @@ -64,15 +64,4 @@ class ElectionPolicy return $pleb->association_status->value >= 3; } - - private function isBoardMember(NostrUser $user): bool - { - $pleb = $user->getPleb(); - - if (! $pleb) { - return false; - } - - return in_array($pleb->npub, config('einundzwanzig.config.current_board'), true); - } } diff --git a/app/Policies/ProjectProposalPolicy.php b/app/Policies/ProjectProposalPolicy.php index 00ef9ad..9ece835 100644 --- a/app/Policies/ProjectProposalPolicy.php +++ b/app/Policies/ProjectProposalPolicy.php @@ -3,7 +3,6 @@ namespace App\Policies; use App\Auth\NostrUser; -use App\Models\EinundzwanzigPleb; use App\Models\ProjectProposal; class ProjectProposalPolicy @@ -26,7 +25,7 @@ class ProjectProposalPolicy /** * Determine whether the user can create project proposals. - * Requires: authenticated, association_status > 1, paid membership for current year. + * Allowed for: board members (always) OR active members with paid membership for the current year. */ public function create(NostrUser $user): bool { @@ -36,8 +35,7 @@ class ProjectProposalPolicy return false; } - return $pleb->association_status->value > 1 - && $pleb->paymentEvents()->where('year', date('Y'))->where('paid', true)->exists(); + return $pleb->isBoardMember() || $pleb->hasPaidMembership(); } /** @@ -53,7 +51,7 @@ class ProjectProposalPolicy } return $pleb->id === $projectProposal->einundzwanzig_pleb_id - || $this->isBoardMember($pleb); + || $pleb->isBoardMember(); } /** @@ -69,7 +67,7 @@ class ProjectProposalPolicy } return $pleb->id === $projectProposal->einundzwanzig_pleb_id - || $this->isBoardMember($pleb); + || $pleb->isBoardMember(); } /** @@ -84,14 +82,6 @@ class ProjectProposalPolicy return false; } - return $this->isBoardMember($pleb); - } - - /** - * @param EinundzwanzigPleb $pleb - */ - private function isBoardMember(object $pleb): bool - { - return in_array($pleb->npub, config('einundzwanzig.config.current_board'), true); + return $pleb->isBoardMember(); } } diff --git a/config/einundzwanzig/config.php b/config/einundzwanzig/config.php index 655660a..210b7bc 100644 --- a/config/einundzwanzig/config.php +++ b/config/einundzwanzig/config.php @@ -7,5 +7,7 @@ return [ 'npub10t8npnmqhpwx9w8k232kess7gqtdlr6kqjemdzf8jnughwqd0gwsez0924', 'npub1r8343wqpra05l3jnc4jud4xz7vlnyeslf7gfsty7ahpf92rhfmpsmqwym8', 'npub17fqtu2mgf7zueq2kdusgzwr2lqwhgfl2scjsez77ddag2qx8vxaq3vnr8y', + 'npub1v4lgwjv7qfn3t7qjscpsgz9vqvspf6hecdp2ckgp0dz89uqn5slsgrhw3p', + 'npub14r770s5wrqpm8jmzur5arnm9aum9x0wasaxwczael54xhjggl7ws5lygc6', ], ]; diff --git a/phpunit.xml b/phpunit.xml index 567e65c..61c031c 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -22,7 +22,8 @@ - + + diff --git a/resources/views/components/project-card.blade.php b/resources/views/components/project-card.blade.php index 26fb675..c38764b 100644 --- a/resources/views/components/project-card.blade.php +++ b/resources/views/components/project-card.blade.php @@ -2,7 +2,7 @@ @php $boardVotes = $project->votes->filter(function ($vote) { - return in_array($vote->einundzwanzigPleb->npub, config('einundzwanzig.config.current_board')); + return $vote->einundzwanzigPleb->isBoardMember(); }); $approveCount = $boardVotes->where('value', 1)->count(); $disapproveCount = $boardVotes->where('value', 0)->count(); diff --git a/resources/views/livewire/association/news.blade.php b/resources/views/livewire/association/news.blade.php index 0cfb97a..fd03eb9 100644 --- a/resources/views/livewire/association/news.blade.php +++ b/resources/views/livewire/association/news.blade.php @@ -38,28 +38,27 @@ class extends Component { #[Locked] public bool $canEdit = false; + #[Locked] + public ?\App\Models\EinundzwanzigPleb $currentPleb = null; + public ?int $confirmDeleteId = null; public function mount(): void { - if (NostrAuth::check()) { - $currentPubkey = NostrAuth::pubkey(); - $currentPleb = \App\Models\EinundzwanzigPleb::query()->where('pubkey', $currentPubkey)->first(); + $this->currentPleb = NostrAuth::user()?->getPleb(); - if ( - $currentPleb - && $currentPleb->association_status->value > 1 - && $currentPleb->paymentEvents()->where('year', date('Y'))->where('paid', true)->exists() - ) { - $this->isAllowed = true; - } - - if ($currentPleb && in_array($currentPleb->npub, config('einundzwanzig.config.current_board'))) { - $this->canEdit = true; - } - - $this->loadNews(); + if (! $this->currentPleb) { + return; } + + if ($this->currentPleb->isBoardMember()) { + $this->isAllowed = true; + $this->canEdit = true; + } elseif ($this->currentPleb->hasPaidMembership()) { + $this->isAllowed = true; + } + + $this->loadNews(); } #[Computed] @@ -101,13 +100,11 @@ class extends Component { 'form.description' => 'nullable|string', ]); - $currentPleb = \App\Models\EinundzwanzigPleb::query()->where('pubkey', NostrAuth::pubkey())->first(); - $news = new Notification; $news->name = $this->form['name']; $news->description = $this->form['description'] ?? null; $news->category = $this->form['category']; - $news->einundzwanzig_pleb_id = $currentPleb->id; + $news->einundzwanzig_pleb_id = $this->currentPleb->id; $news->save(); if ($this->file) { @@ -340,20 +337,15 @@ class extends Component { - @if(NostrAuth::check()) - @php - $currentPleb = \App\Models\EinundzwanzigPleb::query()->where('pubkey', NostrAuth::pubkey())->first(); - @endphp - @if($currentPleb) -
- {{ $currentPleb->profile?->name ?? 'Anonym' }} - {{ $currentPleb->profile?->name ?? str($currentPleb->npub)->limit(32) }} -
- @endif + @if($currentPleb) +
+ {{ $currentPleb->profile?->name ?? 'Anonym' }} + {{ $currentPleb->profile?->name ?? str($currentPleb->npub)->limit(32) }} +
@endif diff --git a/resources/views/livewire/association/project-support/form/create.blade.php b/resources/views/livewire/association/project-support/form/create.blade.php index 7fa59ea..f5557ae 100644 --- a/resources/views/livewire/association/project-support/form/create.blade.php +++ b/resources/views/livewire/association/project-support/form/create.blade.php @@ -43,11 +43,8 @@ class extends Component $this->isAllowed = true; } - if ($nostrUser) { - $pleb = $nostrUser->getPleb(); - if ($pleb && in_array($pleb->npub, config('einundzwanzig.config.current_board'), true)) { - $this->isAdmin = true; - } + if ($nostrUser && $nostrUser->isBoardMember()) { + $this->isAdmin = true; } } diff --git a/resources/views/livewire/association/project-support/index.blade.php b/resources/views/livewire/association/project-support/index.blade.php index ae20b64..fcf6187 100644 --- a/resources/views/livewire/association/project-support/index.blade.php +++ b/resources/views/livewire/association/project-support/index.blade.php @@ -50,7 +50,7 @@ new class extends Component { $this->projects = ProjectProposal::query() ->with([ 'einundzwanzigPleb.profile', - 'votes', + 'votes.einundzwanzigPleb', ]) ->where(function ($query) { $query diff --git a/resources/views/livewire/association/project-support/show.blade.php b/resources/views/livewire/association/project-support/show.blade.php index 2183d92..d22c4c7 100644 --- a/resources/views/livewire/association/project-support/show.blade.php +++ b/resources/views/livewire/association/project-support/show.blade.php @@ -45,7 +45,7 @@ new class extends Component { { return Vote::query() ->where('project_proposal_id', $this->projectProposal->id) - ->whereHas('einundzwanzigPleb', fn($q) => $q->whereIn('npub', config('einundzwanzig.config.current_board'))) + ->whereHas('einundzwanzigPleb', fn($q) => $q->whereIn('npub', config('einundzwanzig.config.current_board', []))) ->get(); } @@ -55,7 +55,7 @@ new class extends Component { ->where('project_proposal_id', $this->projectProposal->id) ->whereDoesntHave( 'einundzwanzigPleb', - fn($q) => $q->whereIn('npub', config('einundzwanzig.config.current_board')) + fn($q) => $q->whereIn('npub', config('einundzwanzig.config.current_board', [])) ) ->get(); } diff --git a/tests/Feature/Livewire/Association/NewsTest.php b/tests/Feature/Livewire/Association/NewsTest.php index 517a803..8545d60 100644 --- a/tests/Feature/Livewire/Association/NewsTest.php +++ b/tests/Feature/Livewire/Association/NewsTest.php @@ -54,6 +54,18 @@ it('allows board member to edit news', function () { ->assertSet('canEdit', true); }); +it('grants board member access even without active membership or paid year', function () { + $pleb = EinundzwanzigPleb::factory()->boardMember()->create([ + 'association_status' => AssociationStatus::DEFAULT, + ]); + + NostrAuth::login($pleb->pubkey); + + Livewire::test('association.news') + ->assertSet('isAllowed', true) + ->assertSet('canEdit', true); +}); + it('can create news entry with pdf', function () { $pleb = EinundzwanzigPleb::factory()->boardMember()->withPaidCurrentYear()->create(); diff --git a/tests/Feature/Policies/ProjectProposalPolicyTest.php b/tests/Feature/Policies/ProjectProposalPolicyTest.php index 2f01094..974e534 100644 --- a/tests/Feature/Policies/ProjectProposalPolicyTest.php +++ b/tests/Feature/Policies/ProjectProposalPolicyTest.php @@ -69,6 +69,15 @@ it('denies creation for unauthenticated users', function () { expect(Gate::forUser(null)->allows('create', ProjectProposal::class))->toBeFalse(); }); +it('allows board member to create project proposals without active membership or paid year', function () { + $pleb = EinundzwanzigPleb::factory()->boardMember()->create([ + 'association_status' => AssociationStatus::DEFAULT, + ]); + $nostrUser = new NostrUser($pleb->pubkey); + + expect(Gate::forUser($nostrUser)->allows('create', ProjectProposal::class))->toBeTrue(); +}); + // update it('allows project creator to update their project proposal', function () { $pleb = EinundzwanzigPleb::factory()->create();