From 5a81d4ea9ee8a6be122fd63687e6ba83b1bcb8c2 Mon Sep 17 00:00:00 2001 From: momijizukamori Date: Fri, 3 Jul 2026 19:07:44 -0400 Subject: [PATCH 1/3] Backlog updater (#76) * Add a job to post item counts to a webhook * Change message format slightly * Add scheduler --------- Co-authored-by: Cocoa --- app/Console/Kernel.php | 4 +-- app/Jobs/backlogUpdate.php | 54 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 app/Jobs/backlogUpdate.php diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index a8c51585..4d274d20 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -4,6 +4,7 @@ use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; +use App\Jobs\backlogUpdate; class Kernel extends ConsoleKernel { @@ -24,8 +25,7 @@ class Kernel extends ConsoleKernel */ protected function schedule(Schedule $schedule) { - // $schedule->command('inspire') - // ->hourly(); + $schedule->job(new backlogUpdate)->dailyAt('13:00'); } /** diff --git a/app/Jobs/backlogUpdate.php b/app/Jobs/backlogUpdate.php new file mode 100644 index 00000000..5e4bac03 --- /dev/null +++ b/app/Jobs/backlogUpdate.php @@ -0,0 +1,54 @@ +where('status', '=', Item::PUBLISHED)->count(); + $pending = DB::table('items')->where('status', '=', Item::PENDING)->count(); + $changes = DB::table('items')->where('status', '=', Item::CHANGES_REQUESTED)->count(); + $draft = DB::table('items')->where('status', '=', Item::DRAFT)->count(); + + $msg = <<request('POST', $webhook, ["json" => ["content" => $msg]]); + } +} From 746f6963d273526e191932e67b9ce734cf8c5e91 Mon Sep 17 00:00:00 2001 From: Amelia Wylie <52548998+MellieVT@users.noreply.github.com> Date: Mon, 6 Jul 2026 14:48:16 +0100 Subject: [PATCH 2/3] chore: use config instead of env() for webhooks --- .../{backlogUpdate.php => BacklogUpdate.php} | 22 +++++++++++++++---- config/services.php | 8 +++++++ 2 files changed, 26 insertions(+), 4 deletions(-) rename app/Jobs/{backlogUpdate.php => BacklogUpdate.php} (67%) diff --git a/app/Jobs/backlogUpdate.php b/app/Jobs/BacklogUpdate.php similarity index 67% rename from app/Jobs/backlogUpdate.php rename to app/Jobs/BacklogUpdate.php index 5e4bac03..1000b099 100644 --- a/app/Jobs/backlogUpdate.php +++ b/app/Jobs/BacklogUpdate.php @@ -2,6 +2,7 @@ namespace App\Jobs; +use GuzzleHttp\Exception\RequestException; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; @@ -10,8 +11,10 @@ use Illuminate\Support\Facades\DB; use App\Models\Item; use GuzzleHttp\Client; +use Sentry\Severity; +use Sentry\State\Scope; -class backlogUpdate implements ShouldQueue +class BacklogUpdate implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; @@ -32,7 +35,7 @@ public function __construct() */ public function handle() { - $webhook = env("WEBHOOK"); + $webhook = config('services.discord.webhooks.updates'); $published = DB::table('items')->where('status', '=', Item::PUBLISHED)->count(); $pending = DB::table('items')->where('status', '=', Item::PENDING)->count(); $changes = DB::table('items')->where('status', '=', Item::CHANGES_REQUESTED)->count(); @@ -44,11 +47,22 @@ public function handle() **$pending** pending review **$changes** post-review, changes requested **$published** published - - + + EOD; $client = new Client(); $res = $client->request('POST', $webhook, ["json" => ["content" => $msg]]); + + if ($res->getStatusCode() !== 200) { + $scope = new Scope(); + $scope->setContext('response', [ + 'body' => $res->getBody()->getContents(), + 'status_code' => $res->getStatusCode(), + 'headers' => $res->getHeaders(), + ]); + + app('sentry')->reportMessage('Failed to send discord webhook', Severity::error(), $scope); + } } } diff --git a/config/services.php b/config/services.php index 2a1d616c..684e8782 100644 --- a/config/services.php +++ b/config/services.php @@ -30,4 +30,12 @@ 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), ], + 'discord' => [ + 'webhooks' => [ + 'updates' => env("DISCORD_UPDATE_WEBHOOK"), + 'publish' => env("DISCORD_PUBLISH_WEBHOOK"), + 'draft' => env("DISCORD_DRAFT_WEBHOOK"), + ], + ], + ]; From 968dc2e88ff096ea71e2b3c10efc63bf621c529f Mon Sep 17 00:00:00 2001 From: Amelia Wylie <52548998+MellieVT@users.noreply.github.com> Date: Mon, 6 Jul 2026 14:48:40 +0100 Subject: [PATCH 3/3] chore: add python healthcheck proxy for testing --- bin/healthcheck.py | 73 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 bin/healthcheck.py diff --git a/bin/healthcheck.py b/bin/healthcheck.py new file mode 100644 index 00000000..9390a8ff --- /dev/null +++ b/bin/healthcheck.py @@ -0,0 +1,73 @@ +import requests +import os +import urllib3 + +from requests.adapters import HTTPAdapter +from http.server import BaseHTTPRequestHandler, HTTPServer + +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + +hostname = os.getenv("HEALTH_CHECK_HOST", 'lolibrary.org') + +class HostHeaderSSLAdapter(HTTPAdapter): + """ + A HTTPS Adapter for Python Requests that sets the hostname for certificate + verification based on the Host header. + + This allows requesting the IP address directly via HTTPS without getting + a "hostname doesn't match" exception. + + Example usage: + + >>> s.mount('https://', HostHeaderSSLAdapter()) + >>> s.get("https://93.184.216.34", headers={"Host": "example.org"}) + + """ + def send(self, request, **kwargs): + # HTTP headers are case-insensitive (RFC 7230) + host_header = None + for header in request.headers: + if header.lower() == "host": + host_header = request.headers[header] + break + + connection_pool_kwargs = self.poolmanager.connection_pool_kw + + if host_header: + connection_pool_kwargs["assert_hostname"] = host_header + connection_pool_kwargs["server_hostname"] = host_header + elif "assert_hostname" in connection_pool_kwargs: + # an assert_hostname from a previous request may have been left + connection_pool_kwargs.pop("assert_hostname", None) + connection_pool_kwargs.pop("server_hostname", None) + + return super(HostHeaderSSLAdapter, self).send(request, **kwargs) + +class Server(BaseHTTPRequestHandler): + def do_GET(self): + try: + res = check() + if res.status_code != 200: + raise Exception(res.json()) + self.send_response(200) + self.end_headers() + self.wfile.write(b'UP') + except: + self.send_response(503) + self.end_headers() + self.wfile.write(b'DOWN') + +def check(): + s = requests.Session() + s.mount('https://', HostHeaderSSLAdapter()) + return s.get('https://localhost/', headers={'Host': hostname}, verify=False) + +def main(server_class=HTTPServer, handler_class=Server, port=12443): + server_address = ('', port) + httpd = server_class(server_address, handler_class) + print("running checks on", port) + httpd.serve_forever() + httpd.server_close() + +if __name__ == '__main__': + main()