['icon' => 'fa-crown', 'title' => 'Präsidium'], 'board' => ['icon' => 'fa-users', 'title' => 'Vizepräsidium'], ]; protected $listeners = [ 'nostrLoggedIn' => 'handleNostrLoggedIn', 'nostrLoggedOut' => 'handleNostrLoggedOut', 'echo:votes,.newVote' => 'handleNewVote', ]; #[Computed] public function loadedEvents(): \Illuminate\Support\Collection { return collect($this->events) ->map(function ($event) { $profile = Profile::query() ->where('pubkey', $event['pubkey']) ->first() ?->toArray(); $votedFor = Profile::query() ->where('pubkey', str($event['content'])->before(',')->toString()) ->first() ?->toArray(); return [ 'id' => $event['id'], 'kind' => $event['kind'], 'content' => $event['content'], 'pubkey' => $event['pubkey'], 'tags' => $event['tags'], 'created_at' => $event['created_at'], 'profile' => $profile, 'votedFor' => $votedFor, 'type' => str($event['content'])->after(',')->toString(), ]; }) ->sortByDesc('created_at') ->unique(fn ($event) => $event['pubkey'].$event['type']) ->values(); } #[Computed] public function loadedBoardEvents(): \Illuminate\Support\Collection { return collect($this->boardEvents) ->map(function ($event) { $profile = Profile::query() ->where('pubkey', $event['pubkey']) ->first() ?->toArray(); $votedFor = Profile::query() ->where('pubkey', str($event['content'])->before(',')->toString()) ->first() ?->toArray(); return [ 'id' => $event['id'], 'kind' => $event['kind'], 'content' => $event['content'], 'pubkey' => $event['pubkey'], 'tags' => $event['tags'], 'created_at' => $event['created_at'], 'profile' => $profile, 'votedFor' => $votedFor, 'type' => str($event['content'])->after(',')->toString(), ]; }) ->sortByDesc('created_at') ->values(); } #[Computed] public function electionConfig(): \Illuminate\Support\Collection { $loadedEvents = $this->loadedEvents(); return collect($this->election->candidates) ->map(function ($c) use ($loadedEvents) { $candidates = Profile::query() ->whereIn('pubkey', $c['c']) ->get() ->map(function ($p) use ($loadedEvents, $c) { $votedClass = ' bg-green-500/20 text-green-700'; $notVotedClass = ' bg-zinc-500/20 text-zinc-100'; $hasVoted = $loadedEvents ->filter(fn ($e) => $e['type'] === $c['type'] && $e['pubkey'] === $this->currentPubkey) ->firstWhere('votedFor.pubkey', $p->pubkey); return [ 'pubkey' => $p->pubkey, 'name' => $p->name, 'picture' => $p->picture, 'votedClass' => $hasVoted ? $votedClass : $notVotedClass, ]; }); return [ 'type' => $c['type'], 'c' => $c['c'], 'candidates' => $candidates, ]; }); } #[Computed] public function electionConfigBoard(): \Illuminate\Support\Collection { $loadedBoardEvents = $this->loadedBoardEvents(); return collect($this->election->candidates) ->map(function ($c) use ($loadedBoardEvents) { $candidates = Profile::query() ->whereIn('pubkey', $c['c']) ->get() ->map(function ($p) use ($loadedBoardEvents, $c) { $votedClass = ' bg-green-500/20 text-green-700'; $notVotedClass = ' bg-zinc-500/20 text-zinc-100'; $hasVoted = $loadedBoardEvents ->filter(fn ($e) => $e['type'] === $c['type'] && $e['pubkey'] === $this->currentPubkey) ->firstWhere('votedFor.pubkey', $p->pubkey); return [ 'pubkey' => $p->pubkey, 'name' => $p->name, 'picture' => $p->picture, 'votedClass' => $hasVoted ? $votedClass : $notVotedClass, 'hasVoted' => $hasVoted, ]; }); return [ 'type' => $c['type'], 'c' => $c['c'], 'candidates' => $candidates, ]; }); } public function mount(Election $election): void { $this->election = $election; $this->plebs = EinundzwanzigPleb::query() ->with(['profile']) ->whereIn('association_status', [3, 4]) ->orderBy('association_status', 'desc') ->get() ->toArray(); $this->loadEvents(); $this->loadBoardEvents(); if ($this->election->end_time?->isPast() || ! config('services.voting')) { $this->isNotClosed = false; } $nostrUser = NostrAuth::user(); if ($nostrUser) { $this->currentPubkey = $nostrUser->getPubkey(); $this->currentPleb = $nostrUser->getPleb(); $this->isAllowed = Gate::forUser($nostrUser)->allows('vote', $this->election); } } public function handleNostrLoggedIn(string $pubkey): void { $executed = RateLimiter::attempt( 'nostr-login:'.request()->ip(), 10, function () {}, ); if (! $executed) { abort(429, 'Too many login attempts.'); } NostrAuth::login($pubkey); $this->currentPubkey = $pubkey; $this->currentPleb = EinundzwanzigPleb::query() ->where('pubkey', $pubkey)->first(); $nostrUser = NostrAuth::user(); $this->isAllowed = $nostrUser && Gate::forUser($nostrUser)->allows('vote', $this->election); } public function handleNostrLoggedOut(): void { $this->currentPubkey = null; $this->currentPleb = null; $this->isAllowed = false; } public function updatedSearch($value): void { $this->plebs = EinundzwanzigPleb::query() ->with(['profile']) ->whereIn('association_status', [3, 4]) ->where(fn ($query) => $query ->where('pubkey', 'like', "%$value%") ->orWhereHas('profile', fn ($query) => $query->where('name', 'ilike', "%$value%"))) ->orderBy('association_status', 'desc') ->get() ->toArray(); } public function handleNewVote(): void { $this->loadEvents(); $this->loadBoardEvents(); } public function loadEvents(): void { $this->events = $this->loadNostrEvents([32122]); } public function loadBoardEvents(): void { $this->boardEvents = $this->loadNostrEvents([2121]); } public function loadNostrEvents($kinds): array { $relayUrl = config('services.relay'); if (! $relayUrl) { return []; } $subscription = new Subscription; $subscriptionId = $subscription->setId(); $filter = new Filter; $filter->setKinds($kinds); $requestMessage = new RequestMessage($subscriptionId, [$filter]); $relaySet = new RelaySet; $relaySet->setRelays([new Relay($relayUrl)]); $request = new Request($relaySet, $requestMessage); $response = $request->send(); return collect($response[$relayUrl] ?? []) ->map(function ($event) { if (! isset($event->event)) { return false; } return [ 'id' => $event->event->id, 'kind' => $event->event->kind, 'content' => $event->event->content, 'pubkey' => $event->event->pubkey, 'tags' => $event->event->tags, 'created_at' => $event->event->created_at, ]; }) ->filter() ->toArray(); } public function vote($pubkey, $type, $board = false): void { Gate::forUser(NostrAuth::user())->authorize('vote', $this->election); $executed = RateLimiter::attempt( 'voting:'.request()->ip(), 10, function () {}, ); if (! $executed) { abort(429, 'Too many voting attempts.'); } if ($this->election->end_time?->isPast()) { $this->isNotClosed = false; return; } $note = new NostrEvent; $note->setKind($board ? 2121 : 32122); if (! $board) { $dTag = sprintf('%s,%s,%s', $this->currentPleb->pubkey, date('Y'), $type); $note->setTags([['d', $dTag]]); } $note->setContent("$pubkey,$type"); $this->signThisEvent = $note->toJson(); } public function checkElection(): void { if ($this->election->end_time?->isPast()) { $this->isNotClosed = false; } } public function signEvent($event): void { $executed = RateLimiter::attempt( 'voting:'.request()->ip(), 10, function () {}, ); if (! $executed) { abort(429, 'Too many voting attempts.'); } $note = new NostrEvent; $note->setId($event['id']); $note->setSignature($event['sig']); $note->setKind($event['kind']); $note->setContent($event['content']); $note->setPublicKey($event['pubkey']); $note->setTags($event['tags']); $note->setCreatedAt($event['created_at']); $relayUrl = config('services.relay'); if (! $relayUrl) { return; } $eventMessage = new EventMessage($note); $relay = new Relay($relayUrl); $relay->setMessage($eventMessage); $relay->send(); \App\Support\Broadcast::on('votes')->as('newVote')->sendNow(); } }; ?>
Zugriff auf die Wahlen ist nur für spezielle autorisierte Benutzer möglich.
@if(!NostrAuth::check()) Bitte melde dich zunächst mit Nostr an. @else Dein Benutzer-Account ist nicht für diese Funktion autorisiert. Bitte kontaktiere den Vorstand, wenn du Zugriff benötigst. @endif