Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 2 additions & 2 deletions .env.ci
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
APP_NAME="Laravel Template"
APP_NAME="codebar Solutions AG"
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_TIMEZONE=UTC
APP_URL=https://laravel-template.test
APP_URL=https://web.codebar.test

NOVA_LICENSE_KEY=

Expand Down
20 changes: 9 additions & 11 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
APP_NAME="Laravel Template"
APP_NAME="codebar Solutions AG"
APP_ENV=local
APP_KEY=base64:F+mHMDBbavrsp/I3WYA5lDSwDJJI/0wQG4eM3csq/lo=
APP_KEY=
APP_DEBUG=true
APP_URL=https://laravel-template-l11.test

NOVA_LICENSE_KEY=
APP_URL=https://web.codebar.test

LOG_CHANNEL=stack
LOG_STACK=single,flare
Expand All @@ -13,12 +11,12 @@ LOG_LEVEL=debug

FLARE_KEY=

DB_CONNECTION=mysql
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel-template
DB_USERNAME=root
DB_PASSWORD=
DB_PORT=5432
DB_DATABASE=web-codebar
DB_USERNAME=postgres
DB_PASSWORD=postgres

FPH_ENABLED=true
CSP_ENABLED=true
Expand All @@ -27,7 +25,7 @@ SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=laravel-template-l11.test
SESSION_DOMAIN=web.codebar.test

BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
Expand Down
2 changes: 2 additions & 0 deletions app/Actions/FetchLlmUsageAction.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace App\Actions;

use Carbon\CarbonImmutable;
Expand Down
23 changes: 23 additions & 0 deletions app/Actions/LlmUsageStatsAction.php
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
<?php

declare(strict_types=1);

namespace App\Actions;

use App\Models\AiModelDailyUsage;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use Closure;
use DateTimeInterface;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Collection;
Expand Down Expand Up @@ -54,6 +58,25 @@ public function years(): Collection
});
}

/**
* When the sync last wrote usage rows to the database.
*/
public function lastSyncedAt(): ?Carbon
{
return $this->remember('last_synced_at', function (): ?Carbon {
return $this->toDate(AiModelDailyUsage::query()->max('updated_at'));
});
}

private function toDate(mixed $value): ?Carbon
{
return match (true) {
is_string($value) => Carbon::parse($value),
$value instanceof DateTimeInterface => Carbon::instance($value),
default => null,
};
}

/**
* @return Collection<int, array{label: string, prompt_tokens: int<0, max>, completion_tokens: int<0, max>, total_tokens: int<0, max>, requests: int<0, max>}>
*/
Expand Down
2 changes: 2 additions & 0 deletions app/Actions/LocaleAction.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace App\Actions;

use App\Enums\SessionKeyEnum;
Expand Down
9 changes: 7 additions & 2 deletions app/Actions/PageAction.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace App\Actions;

use App\DTO\PageDTO;
Expand All @@ -11,6 +13,7 @@
use App\Models\Product;
use App\Models\Service;
use App\Models\Technology;
use App\Support\LocalizedRouteParameters;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
Expand Down Expand Up @@ -64,10 +67,12 @@ public function news(News $news, bool $withReferences = false, ?string $locale =
routeName: Str::slug(title: $locale).'.news.show',
title: $this->translatedString($news->getTranslation('title', $locale)),
description: $this->translatedString($news->getTranslation('teaser', $locale)),
image: $news->image,
image: $news->hero_image,
lastModificationDate: Carbon::parse($news->updated_at ?? now()),
routeParameters: ['locale' => $locale, 'news' => $news],
routeParameters: LocalizedRouteParameters::for(['locale' => $locale, 'news' => $news], $locale),
referencePages: $withReferences ? $this->alternateLocalePages($news, $locale, fn (News $n, string $l) => $this->news($n, false, $l)) : null,
publishedAt: $news->published_at !== null ? Carbon::parse($news->published_at) : null,
authorName: $news->authorName(),
);
}

Expand Down
2 changes: 2 additions & 0 deletions app/Actions/StoreLlmUsageAction.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace App\Actions;

use App\Models\AiModel;
Expand Down
31 changes: 21 additions & 10 deletions app/Actions/ViewDataAction.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
<?php

declare(strict_types=1);

namespace App\Actions;

use App\DTO\ContactDTO;
use App\Enums\AiModelCategoryEnum;
use App\Enums\CacheKeyEnum;
use App\Enums\ContactSectionEnum;
use App\Models\AiModel;
use App\Models\Contact;
Expand All @@ -14,7 +17,6 @@
use App\Models\Technology;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;

class ViewDataAction
{
Expand All @@ -23,7 +25,7 @@ class ViewDataAction
*/
public function products(string $locale): Collection
{
$key = Str::slug("products_published_{$locale}");
$key = CacheKeyEnum::PRODUCTS_PUBLISHED->forLocale($locale);

return Cache::rememberForever($key, function () {
return Product::where('published', true)->orderBy('order')->get();
Expand All @@ -35,7 +37,7 @@ public function products(string $locale): Collection
*/
public function services(string $locale): Collection
{
$key = Str::slug("services_published_{$locale}");
$key = CacheKeyEnum::SERVICES_PUBLISHED->forLocale($locale);

return Cache::rememberForever($key, function () {
return Service::where('published', true)->orderBy('order')->get();
Expand All @@ -47,10 +49,16 @@ public function services(string $locale): Collection
*/
public function news(string $locale): Collection
{
$key = Str::slug("news_published_{$locale}");
$key = CacheKeyEnum::NEWS_PUBLISHED->forLocale($locale);

return Cache::rememberForever($key, function () {
return News::whereNotNull('published_at')->orderByDesc('published_at')->get();
// Eager loaded: the cards read the series title and the author's picture,
// the index filters on tags, and Model::shouldBeStrict() turns a lazy load
// into an exception locally.
return News::with(['series', 'authorContact', 'newsTags'])
->published()
->orderByDesc('published_at')
->get();
});
}

Expand All @@ -59,7 +67,7 @@ public function news(string $locale): Collection
*/
public function technologies(string $locale): Collection
{
$key = Str::slug("technologies_published_{$locale}");
$key = CacheKeyEnum::TECHNOLOGIES_PUBLISHED->forLocale($locale);

return Cache::rememberForever($key, function () {
return Technology::where('published', true)->orderBy('order')->get();
Expand All @@ -71,7 +79,7 @@ public function technologies(string $locale): Collection
*/
public function aiModelGroups(): Collection
{
return Cache::rememberForever('ai_models_active', function () {
return Cache::rememberForever(CacheKeyEnum::AI_MODELS_ACTIVE->value, function () {
return $this->groupAiModelsByCategory(
AiModel::whereNull('archived_at')->orderBy('order')->get()
);
Expand All @@ -83,7 +91,7 @@ public function aiModelGroups(): Collection
*/
public function aiModelArchive(): Collection
{
return Cache::rememberForever('ai_models_archived', function () {
return Cache::rememberForever(CacheKeyEnum::AI_MODELS_ARCHIVED->value, function () {
return $this->groupAiModelsByCategory(
AiModel::whereNotNull('archived_at')->with('replacedBy')->orderBy('order')->get()
);
Expand All @@ -110,7 +118,7 @@ private function groupAiModelsByCategory(Collection $models): Collection
*/
public function openSource(string $locale): Collection
{
$key = Str::slug("open_source_published_{$locale}");
$key = CacheKeyEnum::OPEN_SOURCE_PUBLISHED->forLocale($locale);

return Cache::rememberForever($key, function () {
return OpenSource::where('published', true)->orderByDesc('downloads')->get();
Expand All @@ -119,11 +127,14 @@ public function openSource(string $locale): Collection

public function contacts(string $locale): \stdClass
{
$key = Str::slug("contacts_published_{$locale}");
$key = CacheKeyEnum::CONTACTS_PUBLISHED->forLocale($locale);

return Cache::rememberForever($key, function () use ($locale) {
// Ordered by the `sort` field from the YAML files rather than alphabetically:
// the team page has an intended order that a surname does not express.
$publishedContacts = Contact::query()
->where('published', true)
->orderBy('sort')
->orderBy('name')
->get();

Expand Down
2 changes: 2 additions & 0 deletions app/Checks/FailedJobsCheck.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace App\Checks;

use Illuminate\Support\Facades\DB;
Expand Down
2 changes: 2 additions & 0 deletions app/Checks/FilesystemsDefaultCheck.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace App\Checks;

use App\Enums\CacheKeyEnum;
Expand Down
44 changes: 27 additions & 17 deletions app/Checks/JobsCheck.php
Original file line number Diff line number Diff line change
@@ -1,43 +1,53 @@
<?php

declare(strict_types=1);

namespace App\Checks;

use Illuminate\Support\Collection;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Spatie\Health\Checks\Check;
use Spatie\Health\Checks\Result;

/**
* A queue is healthy when it is either empty or moving. Depth alone says nothing —
* a thousand jobs dispatched a second ago is fine, one job stuck for an hour is not.
* The age of the oldest pending job is what separates the two, and it costs one query
* rather than sampling the depth once a second for a minute and a half.
*/
class JobsCheck extends Check
{
public const int TIME = 90;
public const int STALLED_AFTER_MINUTES = 5;

public function run(): Result
{
$count = DB::table('jobs')->count();
$pending = DB::table('jobs')->count();

$result = Result::make();
$result->shortSummary("jobs table count: {$count}");
$result = Result::make()->shortSummary("pending jobs: {$pending}");

if ($count <= 0) {
if ($pending === 0) {
return $result->ok();
}

$newCounts = Collection::times(self::TIME, function () {
$count = DB::table('jobs')->count();
sleep(1);
$waitingMinutes = $this->oldestPendingJobAgeInMinutes();

return $count;
});
if ($waitingMinutes === null || $waitingMinutes < self::STALLED_AFTER_MINUTES) {
return $result->ok();
}

$allMatch = $newCounts->every(fn (int $value) => $value === $count);
return $result->failed(
"The queue is not draining: {$pending} pending job(s), the oldest waiting {$waitingMinutes} minute(s)."
);
}

$result->shortSummary("jobs table count: {$allMatch}");
private function oldestPendingJobAgeInMinutes(): ?int
{
$availableAt = DB::table('jobs')->min('available_at');

if ($count != $allMatch) {
return $result->ok();
if (! is_numeric($availableAt)) {
return null;
}

return $result->failed();

return (int) Carbon::createFromTimestamp((int) $availableAt)->diffInMinutes(absolute: true);
}
}
2 changes: 2 additions & 0 deletions app/Console/Commands/FetchLlmAnalyticsCommand.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace App\Console\Commands;

use App\Jobs\FetchLlmUsageJob;
Expand Down
Loading