• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ericfortmeyer / activity-log / 20521974594

26 Dec 2025 11:53AM UTC coverage: 43.123% (-6.0%) from 49.13%
20521974594

push

github

web-flow
feat: add webhook for deployments (#63)

Signed-off-by: Eric Fortmeyer <e.fortmeyer01@gmail.com>

11 of 128 new or added lines in 13 files covered. (8.59%)

1 existing line in 1 file now uncovered.

348 of 807 relevant lines covered (43.12%)

1.33 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/Http/NotifyReleaseEventMiddleware.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace EricFortmeyer\ActivityLog\Http;
6

7
use EricFortmeyer\ActivityLog\AppReleaseAction;
8
use EricFortmeyer\ActivityLog\AppReleaseEvent;
9
use EricFortmeyer\ActivityLog\Services\AppConfigService;
10
use EricFortmeyer\ActivityLog\Utils\Hasher;
11
use PhpCommonEnums\HttpResponseCode\Enumeration\HttpResponseCodeEnum;
12
use Psr\Http\Message\ResponseFactoryInterface;
13
use Psr\Http\Message\ServerRequestInterface;
14
use Psr\Http\Message\ResponseInterface;
15
use Psr\Http\Server\MiddlewareInterface;
16
use Psr\Http\Server\RequestHandlerInterface;
17

18
final class NotifyReleaseEventMiddleware implements MiddlewareInterface
19
{
20
    private const EVENT_TYPE_HEADER_KEY = "X-GitHub-Event";
21
    private const HOOK_ID_HEADER_KEY = "X-GitHub-Hook-ID";
22
    private const SIGNATURE_HEADER_KEY = "X-Hub-Signature-256";
23
    private const RELEASE_EVENT_TYPE = "release";
24

25
    public function __construct(
26
        private string $releaseEventDestination,
27
        private string $releaseEventHookPath,
28
        private bool $releaseEventHookRetryEnabled,
29
        private ResponseFactoryInterface $responseFactory,
30
        private AppConfigService $appVersionUpdater,
31
        private Hasher $verifier,
NEW
32
    ) {}
×
33

34
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
35
    {
NEW
36
        $requestPath = $request->getUri()->getPath();
×
37

NEW
38
        if (in_array($requestPath, [$this->releaseEventHookPath]) === false) {
×
NEW
39
            return $handler->handle($request);
×
40
        }
41

NEW
42
        $requestBody = $request->getBody()->getContents();
×
NEW
43
        $eventType = $request->getHeader(self::EVENT_TYPE_HEADER_KEY)[0] ?? "ignore";
×
NEW
44
        $hookId = $request->getHeader(self::HOOK_ID_HEADER_KEY)[0] ?? "ignore";
×
NEW
45
        $signature = ltrim($request->getHeader(self::SIGNATURE_HEADER_KEY)[0] ?? "ignore", "sha256=");
×
46

NEW
47
        if ($this->verifier->verify($requestBody, $signature) === false) {
×
NEW
48
            return $this->responseFactory->createResponse(
×
NEW
49
                (int) HttpResponseCodeEnum::Unauthorized->value,
×
NEW
50
                HttpResponseCodeEnum::Unauthorized->name
×
NEW
51
            );
×
52
        }
53

NEW
54
        $jsonDecodeResult = json_decode($requestBody);
×
NEW
55
        $releaseEvent = new AppReleaseEvent(is_object($jsonDecodeResult) ? $jsonDecodeResult : []);
×
56

NEW
57
        return match (false) {
×
58
            // do not process
59
            // missing required header
NEW
60
            $eventType === self::RELEASE_EVENT_TYPE => $this->responseFactory->createResponse(
×
NEW
61
                (int) HttpResponseCodeEnum::NotImplemented->value,
×
NEW
62
                HttpResponseCodeEnum::NotImplemented->name
×
NEW
63
            ),
×
64
            // validate
NEW
65
            is_numeric($hookId) => $this->responseFactory->createResponse(
×
NEW
66
                (int) HttpResponseCodeEnum::BadRequest->value,
×
NEW
67
                HttpResponseCodeEnum::BadRequest->name
×
NEW
68
            ),
×
NEW
69
            $this->releaseEventHookRetryEnabled && apcu_exists($hookId) => $this->responseFactory->createResponse(
×
NEW
70
                (int) HttpResponseCodeEnum::TooManyRequests->value,
×
NEW
71
                HttpResponseCodeEnum::TooManyRequests->name
×
NEW
72
            ),
×
NEW
73
            $releaseEvent->isValid() => $this->responseFactory->createResponse(
×
NEW
74
                (int) HttpResponseCodeEnum::BadRequest->value,
×
NEW
75
                HttpResponseCodeEnum::BadRequest->name
×
NEW
76
            ),
×
77
            // only handle created releases
NEW
78
            $releaseEvent->action === AppReleaseAction::Created => $this->responseFactory->createResponse(
×
NEW
79
                (int) HttpResponseCodeEnum::Accepted->value,
×
NEW
80
                HttpResponseCodeEnum::Accepted->name
×
NEW
81
            ),
×
82
            // event save error?
NEW
83
            apcu_add($hookId, $hookId, 0) === true
×
NEW
84
                && file_put_contents(
×
NEW
85
                    sprintf(
×
NEW
86
                        "%s/%d.json",
×
NEW
87
                        $this->releaseEventDestination,
×
NEW
88
                        $hookId,
×
NEW
89
                    ),
×
NEW
90
                    $requestBody,
×
NEW
91
                ) !== false
×
NEW
92
                && $this->appVersionUpdater->updateVersion($releaseEvent->release->tagName) =>
×
NEW
93
            $this->responseFactory->createResponse(
×
NEW
94
                (int) HttpResponseCodeEnum::InternalServerError->value,
×
NEW
95
                HttpResponseCodeEnum::InternalServerError->name
×
NEW
96
            ),
×
97
            // event save succesful
NEW
98
            default => $this->responseFactory->createResponse(
×
NEW
99
                (int) HttpResponseCodeEnum::Accepted->value,
×
NEW
100
                HttpResponseCodeEnum::Accepted->name
×
NEW
101
            ),
×
NEW
102
        };
×
103
    }
104
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc