Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.88% covered (success)
93.88%
46 / 49
0.00% covered (danger)
0.00%
0 / 1
CRAP
0.00% covered (danger)
0.00%
0 / 1
ToggleNewsReactionEndpoint
93.88% covered (success)
93.88%
46 / 49
0.00% covered (danger)
0.00%
0 / 1
19.08
0.00% covered (danger)
0.00%
0 / 1
 handle
93.88% covered (success)
93.88%
46 / 49
0.00% covered (danger)
0.00%
0 / 1
19.08
1<?php
2
3namespace Olz\News\Endpoints;
4
5use Olz\Api\OlzTypedEndpoint;
6use Olz\Entity\News\NewsEntry;
7use Olz\Entity\News\NewsReaction;
8use Olz\Entity\Users\User;
9use PhpTypeScriptApi\HttpError;
10
11/**
12 * @phpstan-import-type OlzReaction from ListNewsReactionsEndpoint
13 *
14 * @extends OlzTypedEndpoint<
15 *   array{
16 *     userId?: ?int<1, max>,
17 *     newsEntryId: int<1, max>,
18 *     emoji: non-empty-string,
19 *     action: 'on'|'off'|'toggle',
20 *   },
21 *   array{
22 *     result: ?OlzReaction,
23 *   }
24 * >
25 */
26class ToggleNewsReactionEndpoint extends OlzTypedEndpoint {
27    protected function handle(mixed $input): mixed {
28        $has_access = $this->authUtils()->hasPermission('any');
29        $user = $this->authUtils()->getCurrentUser();
30        if (($input['userId'] ?? null) !== null) {
31            $user_repo = $this->entityManager()->getRepository(User::class);
32            $user = $user_repo->findOneBy(['id' => $input['userId']]);
33        }
34        if (!$has_access || !$user) {
35            throw new HttpError(403, 'Kein Zugriff!');
36        }
37        $auth_user_id = $this->session()->get('auth_user_id');
38        $is_parent = $auth_user_id && intval($user->getParentUserId()) === intval($auth_user_id);
39        $is_self = $auth_user_id && intval($user->getId()) === intval($auth_user_id);
40        if (!$is_self && !$is_parent) {
41            throw new HttpError(403, "Kein Zugriff!");
42        }
43        if (!$this->generalUtils()->isOneEmoji($input['emoji'])) {
44            $enc_emoji = urlencode($input['emoji']);
45            throw new HttpError(400, "Ungültiges Emoji: {$input['emoji']} ({$enc_emoji})");
46        }
47
48        $news_reaction_repo = $this->entityManager()->getRepository(NewsReaction::class);
49        $reactions = $news_reaction_repo->findBy([
50            'news_entry' => $input['newsEntryId'],
51            'emoji' => $input['emoji'],
52            'user' => $user,
53        ]);
54        // Hack for prod not applying the emoji filter correctly.
55        $reactions = array_filter(
56            $reactions,
57            fn ($reaction) => $input['emoji'] === $reaction->getEmoji(),
58        );
59        $has_reactions = count($reactions) > 0;
60        $want_reaction = $input['action'] === 'on' || ($input['action'] === 'toggle' && !$has_reactions);
61        $result = null;
62
63        if (!$has_reactions && $want_reaction) {
64            $news_repo = $this->entityManager()->getRepository(NewsEntry::class);
65            $news_entry = $news_repo->findOneBy(['id' => $input['newsEntryId']]);
66            if (!$news_entry) {
67                throw new HttpError(400, "Kein solcher News-Eintrag");
68            }
69            $reaction = new NewsReaction();
70            $reaction->setNewsEntry($news_entry);
71            $reaction->setUser($user);
72            $reaction->setEmoji($input['emoji']);
73            $this->entityManager()->persist($reaction);
74            $this->entityManager()->flush();
75            $result = [
76                'userId' => $reaction->getUser()->getId() ?? 0,
77                'name' => $reaction->getUser()->getFullName() ?: '?',
78                'emoji' => $reaction->getEmoji() ?: '?',
79            ];
80        }
81        if ($has_reactions && !$want_reaction) {
82            foreach ($reactions as $reaction) {
83                $this->entityManager()->remove($reaction);
84            }
85            $this->entityManager()->flush();
86        }
87
88        return ['result' => $result];
89    }
90}