From d96fb297bc3925c2fae42781c7706aa50542024f Mon Sep 17 00:00:00 2001 From: Benjamin Nussbaum Date: Mon, 2 Mar 2026 19:18:06 +0100 Subject: [PATCH 1/7] chore: update dependencies --- composer.lock | 435 +++++++++++++++++++++++++------------------------- 1 file changed, 217 insertions(+), 218 deletions(-) diff --git a/composer.lock b/composer.lock index 03ceff2..82d7b7a 100644 --- a/composer.lock +++ b/composer.lock @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.370.1", + "version": "3.372.1", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "273a9bbed9e73016be390b8428f7925f15ea053e" + "reference": "a48ff951eaad7f038eca3e0e89f168048b99082b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/273a9bbed9e73016be390b8428f7925f15ea053e", - "reference": "273a9bbed9e73016be390b8428f7925f15ea053e", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/a48ff951eaad7f038eca3e0e89f168048b99082b", + "reference": "a48ff951eaad7f038eca3e0e89f168048b99082b", "shasum": "" }, "require": { @@ -135,11 +135,11 @@ "authors": [ { "name": "Amazon Web Services", - "homepage": "http://aws.amazon.com" + "homepage": "https://aws.amazon.com" } ], "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", - "homepage": "http://aws.amazon.com/sdkforphp", + "homepage": "https://aws.amazon.com/sdk-for-php", "keywords": [ "amazon", "aws", @@ -153,9 +153,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.370.1" + "source": "https://github.com/aws/aws-sdk-php/tree/3.372.1" }, - "time": "2026-02-23T19:05:30+00:00" + "time": "2026-03-06T21:27:21+00:00" }, { "name": "bacon/bacon-qr-code", @@ -214,16 +214,16 @@ }, { "name": "bnussbau/laravel-trmnl-blade", - "version": "2.3.1", + "version": "2.3.3", "source": { "type": "git", "url": "https://github.com/bnussbau/laravel-trmnl-blade.git", - "reference": "95b781733a6943acd9d908c3366a3d71e47df4a4" + "reference": "777af3e26a6bee154efd87caf06131cc18c84452" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bnussbau/laravel-trmnl-blade/zipball/95b781733a6943acd9d908c3366a3d71e47df4a4", - "reference": "95b781733a6943acd9d908c3366a3d71e47df4a4", + "url": "https://api.github.com/repos/bnussbau/laravel-trmnl-blade/zipball/777af3e26a6bee154efd87caf06131cc18c84452", + "reference": "777af3e26a6bee154efd87caf06131cc18c84452", "shasum": "" }, "require": { @@ -278,7 +278,7 @@ ], "support": { "issues": "https://github.com/bnussbau/laravel-trmnl-blade/issues", - "source": "https://github.com/bnussbau/laravel-trmnl-blade/tree/2.3.1" + "source": "https://github.com/bnussbau/laravel-trmnl-blade/tree/2.3.3" }, "funding": [ { @@ -294,7 +294,7 @@ "type": "github" } ], - "time": "2026-02-10T16:12:33+00:00" + "time": "2026-03-03T06:47:48+00:00" }, { "name": "bnussbau/trmnl-pipeline-php", @@ -1030,16 +1030,16 @@ }, { "name": "firebase/php-jwt", - "version": "v7.0.2", + "version": "v7.0.3", "source": { "type": "git", "url": "https://github.com/firebase/php-jwt.git", - "reference": "5645b43af647b6947daac1d0f659dd1fbe8d3b65" + "reference": "28aa0694bcfdfa5e2959c394d5a1ee7a5083629e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/5645b43af647b6947daac1d0f659dd1fbe8d3b65", - "reference": "5645b43af647b6947daac1d0f659dd1fbe8d3b65", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/28aa0694bcfdfa5e2959c394d5a1ee7a5083629e", + "reference": "28aa0694bcfdfa5e2959c394d5a1ee7a5083629e", "shasum": "" }, "require": { @@ -1087,9 +1087,9 @@ ], "support": { "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v7.0.2" + "source": "https://github.com/firebase/php-jwt/tree/v7.0.3" }, - "time": "2025-12-16T22:17:28+00:00" + "time": "2026-02-25T22:16:40+00:00" }, { "name": "fruitcake/php-cors", @@ -1695,16 +1695,16 @@ }, { "name": "keepsuit/laravel-liquid", - "version": "v0.5.4", + "version": "v0.5.5", "source": { "type": "git", "url": "https://github.com/keepsuit/laravel-liquid.git", - "reference": "ba426f44798042e3635a29ea91bbf2a4b2874a04" + "reference": "7f9996cc2eac16489d33d76e265939c2d556bded" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/keepsuit/laravel-liquid/zipball/ba426f44798042e3635a29ea91bbf2a4b2874a04", - "reference": "ba426f44798042e3635a29ea91bbf2a4b2874a04", + "url": "https://api.github.com/repos/keepsuit/laravel-liquid/zipball/7f9996cc2eac16489d33d76e265939c2d556bded", + "reference": "7f9996cc2eac16489d33d76e265939c2d556bded", "shasum": "" }, "require": { @@ -1712,7 +1712,7 @@ "keepsuit/liquid": "^0.7 || ^0.8 || ^0.9", "php": "^8.1", "spatie/laravel-package-tools": "^1.16", - "symfony/var-exporter": "^6.3 || ^7.0" + "symfony/var-exporter": "^6.3 || ^7.0 || ^8.0" }, "require-dev": { "itsgoingd/clockwork": "^5.3", @@ -1764,9 +1764,9 @@ ], "support": { "issues": "https://github.com/keepsuit/laravel-liquid/issues", - "source": "https://github.com/keepsuit/laravel-liquid/tree/v0.5.4" + "source": "https://github.com/keepsuit/laravel-liquid/tree/v0.5.5" }, - "time": "2025-06-15T12:06:40+00:00" + "time": "2026-03-04T16:46:40+00:00" }, { "name": "keepsuit/liquid", @@ -2437,16 +2437,16 @@ }, { "name": "league/commonmark", - "version": "2.8.0", + "version": "2.8.1", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb" + "reference": "84b1ca48347efdbe775426f108622a42735a6579" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/4efa10c1e56488e658d10adf7b7b7dcd19940bfb", - "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/84b1ca48347efdbe775426f108622a42735a6579", + "reference": "84b1ca48347efdbe775426f108622a42735a6579", "shasum": "" }, "require": { @@ -2471,9 +2471,9 @@ "phpstan/phpstan": "^1.8.2", "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", "scrutinizer/ocular": "^1.8.1", - "symfony/finder": "^5.3 | ^6.0 | ^7.0", - "symfony/process": "^5.4 | ^6.0 | ^7.0", - "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0", + "symfony/finder": "^5.3 | ^6.0 | ^7.0 || ^8.0", + "symfony/process": "^5.4 | ^6.0 | ^7.0 || ^8.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0 || ^8.0", "unleashedtech/php-coding-standard": "^3.1.1", "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0" }, @@ -2540,7 +2540,7 @@ "type": "tidelift" } ], - "time": "2025-11-26T21:48:24+00:00" + "time": "2026-03-05T21:37:03+00:00" }, { "name": "league/config", @@ -2626,16 +2626,16 @@ }, { "name": "league/flysystem", - "version": "3.31.0", + "version": "3.32.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "1717e0b3642b0df65ecb0cc89cdd99fa840672ff" + "reference": "254b1595b16b22dbddaaef9ed6ca9fdac4956725" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/1717e0b3642b0df65ecb0cc89cdd99fa840672ff", - "reference": "1717e0b3642b0df65ecb0cc89cdd99fa840672ff", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/254b1595b16b22dbddaaef9ed6ca9fdac4956725", + "reference": "254b1595b16b22dbddaaef9ed6ca9fdac4956725", "shasum": "" }, "require": { @@ -2703,9 +2703,9 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.31.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.32.0" }, - "time": "2026-01-23T15:38:47+00:00" + "time": "2026-02-25T17:01:41+00:00" }, { "name": "league/flysystem-local", @@ -3128,26 +3128,26 @@ }, { "name": "livewire/flux", - "version": "v2.12.1", + "version": "v2.13.0", "source": { "type": "git", "url": "https://github.com/livewire/flux.git", - "reference": "24c139b97b6df1e67c0235637f0e08c206bf4486" + "reference": "741be2d4526e90b97c7a59e079a2f27ecdce2461" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/livewire/flux/zipball/24c139b97b6df1e67c0235637f0e08c206bf4486", - "reference": "24c139b97b6df1e67c0235637f0e08c206bf4486", + "url": "https://api.github.com/repos/livewire/flux/zipball/741be2d4526e90b97c7a59e079a2f27ecdce2461", + "reference": "741be2d4526e90b97c7a59e079a2f27ecdce2461", "shasum": "" }, "require": { - "illuminate/console": "^10.0|^11.0|^12.0", - "illuminate/support": "^10.0|^11.0|^12.0", - "illuminate/view": "^10.0|^11.0|^12.0", + "illuminate/console": "^10.0|^11.0|^12.0|^13.0", + "illuminate/support": "^10.0|^11.0|^12.0|^13.0", + "illuminate/view": "^10.0|^11.0|^12.0|^13.0", "laravel/prompts": "^0.1|^0.2|^0.3", "livewire/livewire": "^3.7.4|^4.0", "php": "^8.1", - "symfony/console": "^6.0|^7.0" + "symfony/console": "^6.0|^7.0|^8.0" }, "conflict": { "livewire/blaze": "<1.0.0-beta.2" @@ -3188,42 +3188,42 @@ ], "support": { "issues": "https://github.com/livewire/flux/issues", - "source": "https://github.com/livewire/flux/tree/v2.12.1" + "source": "https://github.com/livewire/flux/tree/v2.13.0" }, - "time": "2026-02-17T21:12:27+00:00" + "time": "2026-03-03T03:32:35+00:00" }, { "name": "livewire/livewire", - "version": "v4.1.4", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/livewire/livewire.git", - "reference": "4697085e02a1f5f11410a1b5962400e3539f8843" + "reference": "93e972fa42c1b34fff1550093ab94f778d81ea5a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/livewire/livewire/zipball/4697085e02a1f5f11410a1b5962400e3539f8843", - "reference": "4697085e02a1f5f11410a1b5962400e3539f8843", + "url": "https://api.github.com/repos/livewire/livewire/zipball/93e972fa42c1b34fff1550093ab94f778d81ea5a", + "reference": "93e972fa42c1b34fff1550093ab94f778d81ea5a", "shasum": "" }, "require": { - "illuminate/database": "^10.0|^11.0|^12.0", - "illuminate/routing": "^10.0|^11.0|^12.0", - "illuminate/support": "^10.0|^11.0|^12.0", - "illuminate/validation": "^10.0|^11.0|^12.0", + "illuminate/database": "^10.0|^11.0|^12.0|^13.0", + "illuminate/routing": "^10.0|^11.0|^12.0|^13.0", + "illuminate/support": "^10.0|^11.0|^12.0|^13.0", + "illuminate/validation": "^10.0|^11.0|^12.0|^13.0", "laravel/prompts": "^0.1.24|^0.2|^0.3", "league/mime-type-detection": "^1.9", "php": "^8.1", - "symfony/console": "^6.0|^7.0", - "symfony/http-kernel": "^6.2|^7.0" + "symfony/console": "^6.0|^7.0|^8.0", + "symfony/http-kernel": "^6.2|^7.0|^8.0" }, "require-dev": { "calebporzio/sushi": "^2.1", - "laravel/framework": "^10.15.0|^11.0|^12.0", + "laravel/framework": "^10.15.0|^11.0|^12.0|^13.0", "mockery/mockery": "^1.3.1", - "orchestra/testbench": "^8.21.0|^9.0|^10.0", - "orchestra/testbench-dusk": "^8.24|^9.1|^10.0", - "phpunit/phpunit": "^10.4|^11.5", + "orchestra/testbench": "^8.21.0|^9.0|^10.0|^11.0", + "orchestra/testbench-dusk": "^8.24|^9.1|^10.0|^11.0", + "phpunit/phpunit": "^10.4|^11.5|^12.5", "psy/psysh": "^0.11.22|^0.12" }, "type": "library", @@ -3258,7 +3258,7 @@ "description": "A front-end framework for Laravel.", "support": { "issues": "https://github.com/livewire/livewire/issues", - "source": "https://github.com/livewire/livewire/tree/v4.1.4" + "source": "https://github.com/livewire/livewire/tree/v4.2.1" }, "funding": [ { @@ -3266,7 +3266,7 @@ "type": "github" } ], - "time": "2026-02-09T22:59:54+00:00" + "time": "2026-02-28T00:01:19+00:00" }, { "name": "maennchen/zipstream-php", @@ -4902,16 +4902,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.20", + "version": "v0.12.21", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "19678eb6b952a03b8a1d96ecee9edba518bb0373" + "reference": "4821fab5b7cd8c49a673a9fd5754dc9162bb9e97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/19678eb6b952a03b8a1d96ecee9edba518bb0373", - "reference": "19678eb6b952a03b8a1d96ecee9edba518bb0373", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/4821fab5b7cd8c49a673a9fd5754dc9162bb9e97", + "reference": "4821fab5b7cd8c49a673a9fd5754dc9162bb9e97", "shasum": "" }, "require": { @@ -4975,9 +4975,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.20" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.21" }, - "time": "2026-02-11T15:05:28+00:00" + "time": "2026-03-06T21:21:28+00:00" }, { "name": "ralouphie/getallheaders", @@ -5597,16 +5597,16 @@ }, { "name": "symfony/console", - "version": "v7.4.4", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894" + "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/41e38717ac1dd7a46b6bda7d6a82af2d98a78894", - "reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894", + "url": "https://api.github.com/repos/symfony/console/zipball/e1e6770440fb9c9b0cf725f81d1361ad1835329d", + "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d", "shasum": "" }, "require": { @@ -5671,7 +5671,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.4" + "source": "https://github.com/symfony/console/tree/v7.4.7" }, "funding": [ { @@ -5691,20 +5691,20 @@ "type": "tidelift" } ], - "time": "2026-01-13T11:36:38+00:00" + "time": "2026-03-06T14:06:20+00:00" }, { "name": "symfony/css-selector", - "version": "v8.0.0", + "version": "v8.0.6", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "6225bd458c53ecdee056214cb4a2ffaf58bd592b" + "reference": "2a178bf80f05dbbe469a337730eba79d61315262" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/6225bd458c53ecdee056214cb4a2ffaf58bd592b", - "reference": "6225bd458c53ecdee056214cb4a2ffaf58bd592b", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/2a178bf80f05dbbe469a337730eba79d61315262", + "reference": "2a178bf80f05dbbe469a337730eba79d61315262", "shasum": "" }, "require": { @@ -5740,7 +5740,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v8.0.0" + "source": "https://github.com/symfony/css-selector/tree/v8.0.6" }, "funding": [ { @@ -5760,7 +5760,7 @@ "type": "tidelift" } ], - "time": "2025-10-30T14:17:19+00:00" + "time": "2026-02-17T13:07:04+00:00" }, { "name": "symfony/deprecation-contracts", @@ -6074,16 +6074,16 @@ }, { "name": "symfony/filesystem", - "version": "v8.0.1", + "version": "v8.0.6", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "d937d400b980523dc9ee946bb69972b5e619058d" + "reference": "7bf9162d7a0dff98d079b72948508fa48018a770" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/d937d400b980523dc9ee946bb69972b5e619058d", - "reference": "d937d400b980523dc9ee946bb69972b5e619058d", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/7bf9162d7a0dff98d079b72948508fa48018a770", + "reference": "7bf9162d7a0dff98d079b72948508fa48018a770", "shasum": "" }, "require": { @@ -6120,7 +6120,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v8.0.1" + "source": "https://github.com/symfony/filesystem/tree/v8.0.6" }, "funding": [ { @@ -6140,20 +6140,20 @@ "type": "tidelift" } ], - "time": "2025-12-01T09:13:36+00:00" + "time": "2026-02-25T16:59:43+00:00" }, { "name": "symfony/finder", - "version": "v7.4.5", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb" + "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", - "reference": "ad4daa7c38668dcb031e63bc99ea9bd42196a2cb", + "url": "https://api.github.com/repos/symfony/finder/zipball/8655bf1076b7a3a346cb11413ffdabff50c7ffcf", + "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf", "shasum": "" }, "require": { @@ -6188,7 +6188,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.5" + "source": "https://github.com/symfony/finder/tree/v7.4.6" }, "funding": [ { @@ -6208,20 +6208,20 @@ "type": "tidelift" } ], - "time": "2026-01-26T15:07:59+00:00" + "time": "2026-01-29T09:40:50+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.4.5", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "446d0db2b1f21575f1284b74533e425096abdfb6" + "reference": "f94b3e7b7dafd40e666f0c9ff2084133bae41e81" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/446d0db2b1f21575f1284b74533e425096abdfb6", - "reference": "446d0db2b1f21575f1284b74533e425096abdfb6", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f94b3e7b7dafd40e666f0c9ff2084133bae41e81", + "reference": "f94b3e7b7dafd40e666f0c9ff2084133bae41e81", "shasum": "" }, "require": { @@ -6270,7 +6270,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.4.5" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.7" }, "funding": [ { @@ -6290,20 +6290,20 @@ "type": "tidelift" } ], - "time": "2026-01-27T16:16:02+00:00" + "time": "2026-03-06T13:15:18+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.4.5", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "229eda477017f92bd2ce7615d06222ec0c19e82a" + "reference": "3b3fcf386c809be990c922e10e4c620d6367cab1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/229eda477017f92bd2ce7615d06222ec0c19e82a", - "reference": "229eda477017f92bd2ce7615d06222ec0c19e82a", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/3b3fcf386c809be990c922e10e4c620d6367cab1", + "reference": "3b3fcf386c809be990c922e10e4c620d6367cab1", "shasum": "" }, "require": { @@ -6345,7 +6345,7 @@ "symfony/config": "^6.4|^7.0|^8.0", "symfony/console": "^6.4|^7.0|^8.0", "symfony/css-selector": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4.1|^7.0.1|^8.0", "symfony/dom-crawler": "^6.4|^7.0|^8.0", "symfony/expression-language": "^6.4|^7.0|^8.0", "symfony/finder": "^6.4|^7.0|^8.0", @@ -6389,7 +6389,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.4.5" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.7" }, "funding": [ { @@ -6409,20 +6409,20 @@ "type": "tidelift" } ], - "time": "2026-01-28T10:33:42+00:00" + "time": "2026-03-06T16:33:18+00:00" }, { "name": "symfony/mailer", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "7b750074c40c694ceb34cb926d6dffee231c5cd6" + "reference": "b02726f39a20bc65e30364f5c750c4ddbf1f58e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/7b750074c40c694ceb34cb926d6dffee231c5cd6", - "reference": "7b750074c40c694ceb34cb926d6dffee231c5cd6", + "url": "https://api.github.com/repos/symfony/mailer/zipball/b02726f39a20bc65e30364f5c750c4ddbf1f58e9", + "reference": "b02726f39a20bc65e30364f5c750c4ddbf1f58e9", "shasum": "" }, "require": { @@ -6473,7 +6473,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.4.4" + "source": "https://github.com/symfony/mailer/tree/v7.4.6" }, "funding": [ { @@ -6493,20 +6493,20 @@ "type": "tidelift" } ], - "time": "2026-01-08T08:25:11+00:00" + "time": "2026-02-25T16:50:00+00:00" }, { "name": "symfony/mime", - "version": "v7.4.5", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "b18c7e6e9eee1e19958138df10412f3c4c316148" + "reference": "da5ab4fde3f6c88ab06e96185b9922f48b677cd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/b18c7e6e9eee1e19958138df10412f3c4c316148", - "reference": "b18c7e6e9eee1e19958138df10412f3c4c316148", + "url": "https://api.github.com/repos/symfony/mime/zipball/da5ab4fde3f6c88ab06e96185b9922f48b677cd1", + "reference": "da5ab4fde3f6c88ab06e96185b9922f48b677cd1", "shasum": "" }, "require": { @@ -6517,7 +6517,7 @@ }, "conflict": { "egulias/email-validator": "~3.0.0", - "phpdocumentor/reflection-docblock": "<5.2|>=6", + "phpdocumentor/reflection-docblock": "<5.2|>=7", "phpdocumentor/type-resolver": "<1.5.1", "symfony/mailer": "<6.4", "symfony/serializer": "<6.4.3|>7.0,<7.0.3" @@ -6525,7 +6525,7 @@ "require-dev": { "egulias/email-validator": "^2.1.10|^3.1|^4", "league/html-to-markdown": "^5.0", - "phpdocumentor/reflection-docblock": "^5.2", + "phpdocumentor/reflection-docblock": "^5.2|^6.0", "symfony/dependency-injection": "^6.4|^7.0|^8.0", "symfony/process": "^6.4|^7.0|^8.0", "symfony/property-access": "^6.4|^7.0|^8.0", @@ -6562,7 +6562,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.5" + "source": "https://github.com/symfony/mime/tree/v7.4.7" }, "funding": [ { @@ -6582,7 +6582,7 @@ "type": "tidelift" } ], - "time": "2026-01-27T08:59:58+00:00" + "time": "2026-03-05T15:24:09+00:00" }, { "name": "symfony/polyfill-ctype", @@ -7480,16 +7480,16 @@ }, { "name": "symfony/routing", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "0798827fe2c79caeed41d70b680c2c3507d10147" + "reference": "238d749c56b804b31a9bf3e26519d93b65a60938" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/0798827fe2c79caeed41d70b680c2c3507d10147", - "reference": "0798827fe2c79caeed41d70b680c2c3507d10147", + "url": "https://api.github.com/repos/symfony/routing/zipball/238d749c56b804b31a9bf3e26519d93b65a60938", + "reference": "238d749c56b804b31a9bf3e26519d93b65a60938", "shasum": "" }, "require": { @@ -7541,7 +7541,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.4.4" + "source": "https://github.com/symfony/routing/tree/v7.4.6" }, "funding": [ { @@ -7561,7 +7561,7 @@ "type": "tidelift" } ], - "time": "2026-01-12T12:19:02+00:00" + "time": "2026-02-25T16:50:00+00:00" }, { "name": "symfony/service-contracts", @@ -7652,16 +7652,16 @@ }, { "name": "symfony/string", - "version": "v8.0.4", + "version": "v8.0.6", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "758b372d6882506821ed666032e43020c4f57194" + "reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/758b372d6882506821ed666032e43020c4f57194", - "reference": "758b372d6882506821ed666032e43020c4f57194", + "url": "https://api.github.com/repos/symfony/string/zipball/6c9e1108041b5dce21a9a4984b531c4923aa9ec4", + "reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4", "shasum": "" }, "require": { @@ -7718,7 +7718,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v8.0.4" + "source": "https://github.com/symfony/string/tree/v8.0.6" }, "funding": [ { @@ -7738,20 +7738,20 @@ "type": "tidelift" } ], - "time": "2026-01-12T12:37:40+00:00" + "time": "2026-02-09T10:14:57+00:00" }, { "name": "symfony/translation", - "version": "v8.0.4", + "version": "v8.0.6", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "db70c8ce7db74fd2da7b1d268db46b2a8ce32c10" + "reference": "13ff19bcf2bea492d3c2fbeaa194dd6f4599ce1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/db70c8ce7db74fd2da7b1d268db46b2a8ce32c10", - "reference": "db70c8ce7db74fd2da7b1d268db46b2a8ce32c10", + "url": "https://api.github.com/repos/symfony/translation/zipball/13ff19bcf2bea492d3c2fbeaa194dd6f4599ce1b", + "reference": "13ff19bcf2bea492d3c2fbeaa194dd6f4599ce1b", "shasum": "" }, "require": { @@ -7811,7 +7811,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v8.0.4" + "source": "https://github.com/symfony/translation/tree/v8.0.6" }, "funding": [ { @@ -7831,7 +7831,7 @@ "type": "tidelift" } ], - "time": "2026-01-13T13:06:50+00:00" + "time": "2026-02-17T13:07:04+00:00" }, { "name": "symfony/translation-contracts", @@ -7995,16 +7995,16 @@ }, { "name": "symfony/var-dumper", - "version": "v7.4.4", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "0e4769b46a0c3c62390d124635ce59f66874b282" + "reference": "045321c440ac18347b136c63d2e9bf28a2dc0291" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/0e4769b46a0c3c62390d124635ce59f66874b282", - "reference": "0e4769b46a0c3c62390d124635ce59f66874b282", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/045321c440ac18347b136c63d2e9bf28a2dc0291", + "reference": "045321c440ac18347b136c63d2e9bf28a2dc0291", "shasum": "" }, "require": { @@ -8058,7 +8058,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.4.4" + "source": "https://github.com/symfony/var-dumper/tree/v7.4.6" }, "funding": [ { @@ -8078,30 +8078,29 @@ "type": "tidelift" } ], - "time": "2026-01-01T22:13:48+00:00" + "time": "2026-02-15T10:53:20+00:00" }, { "name": "symfony/var-exporter", - "version": "v7.4.0", + "version": "v8.0.0", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "03a60f169c79a28513a78c967316fbc8bf17816f" + "reference": "7345f46c251f2eb27c7b3ebdb5bb076b3ffcae04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/03a60f169c79a28513a78c967316fbc8bf17816f", - "reference": "03a60f169c79a28513a78c967316fbc8bf17816f", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/7345f46c251f2eb27c7b3ebdb5bb076b3ffcae04", + "reference": "7345f46c251f2eb27c7b3ebdb5bb076b3ffcae04", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3" + "php": ">=8.4" }, "require-dev": { - "symfony/property-access": "^6.4|^7.0|^8.0", - "symfony/serializer": "^6.4|^7.0|^8.0", - "symfony/var-dumper": "^6.4|^7.0|^8.0" + "symfony/property-access": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -8139,7 +8138,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v7.4.0" + "source": "https://github.com/symfony/var-exporter/tree/v8.0.0" }, "funding": [ { @@ -8159,20 +8158,20 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:15:23+00:00" + "time": "2025-11-05T18:53:00+00:00" }, { "name": "symfony/yaml", - "version": "v7.4.1", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "24dd4de28d2e3988b311751ac49e684d783e2345" + "reference": "58751048de17bae71c5aa0d13cb19d79bca26391" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/24dd4de28d2e3988b311751ac49e684d783e2345", - "reference": "24dd4de28d2e3988b311751ac49e684d783e2345", + "url": "https://api.github.com/repos/symfony/yaml/zipball/58751048de17bae71c5aa0d13cb19d79bca26391", + "reference": "58751048de17bae71c5aa0d13cb19d79bca26391", "shasum": "" }, "require": { @@ -8215,7 +8214,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.4.1" + "source": "https://github.com/symfony/yaml/tree/v7.4.6" }, "funding": [ { @@ -8235,7 +8234,7 @@ "type": "tidelift" } ], - "time": "2025-12-04T18:11:45+00:00" + "time": "2026-02-09T09:33:46+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -8452,23 +8451,23 @@ }, { "name": "wnx/sidecar-browsershot", - "version": "v2.7.0", + "version": "v2.8.0", "source": { "type": "git", "url": "https://github.com/stefanzweifel/sidecar-browsershot.git", - "reference": "e42a996c6fab4357919cd5e3f3fab33f019cdd80" + "reference": "1d2a20a6723b74c139f98f7a020fe5c0f57d05a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/stefanzweifel/sidecar-browsershot/zipball/e42a996c6fab4357919cd5e3f3fab33f019cdd80", - "reference": "e42a996c6fab4357919cd5e3f3fab33f019cdd80", + "url": "https://api.github.com/repos/stefanzweifel/sidecar-browsershot/zipball/1d2a20a6723b74c139f98f7a020fe5c0f57d05a5", + "reference": "1d2a20a6723b74c139f98f7a020fe5c0f57d05a5", "shasum": "" }, "require": { - "hammerstone/sidecar": "^0.7", + "hammerstone/sidecar": "^0.7.1", "illuminate/contracts": "^12.0", "php": "^8.4", - "spatie/browsershot": "^4.0 || ^5.0", + "spatie/browsershot": "^5.0", "spatie/laravel-package-tools": "^1.9.2" }, "require-dev": { @@ -8484,7 +8483,7 @@ "phpstan/phpstan-phpunit": "^1.0|^2.0", "phpunit/phpunit": "^11.0 | ^12.0", "spatie/image": "^3.3", - "spatie/pixelmatch-php": "^1.0" + "spatie/pixelmatch-php": "^1.2" }, "type": "library", "extra": { @@ -8526,7 +8525,7 @@ ], "support": { "issues": "https://github.com/stefanzweifel/sidecar-browsershot/issues", - "source": "https://github.com/stefanzweifel/sidecar-browsershot/tree/v2.7.0" + "source": "https://github.com/stefanzweifel/sidecar-browsershot/tree/v2.8.0" }, "funding": [ { @@ -8534,7 +8533,7 @@ "type": "github" } ], - "time": "2025-11-22T08:49:08+00:00" + "time": "2026-03-07T18:24:28+00:00" } ], "packages-dev": [ @@ -8980,40 +8979,40 @@ }, { "name": "larastan/larastan", - "version": "v3.9.2", + "version": "v3.9.3", "source": { "type": "git", "url": "https://github.com/larastan/larastan.git", - "reference": "2e9ed291bdc1969e7f270fb33c9cdf3c912daeb2" + "reference": "64a52bcc5347c89fdf131cb59f96ebfbc8d1ad65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/larastan/larastan/zipball/2e9ed291bdc1969e7f270fb33c9cdf3c912daeb2", - "reference": "2e9ed291bdc1969e7f270fb33c9cdf3c912daeb2", + "url": "https://api.github.com/repos/larastan/larastan/zipball/64a52bcc5347c89fdf131cb59f96ebfbc8d1ad65", + "reference": "64a52bcc5347c89fdf131cb59f96ebfbc8d1ad65", "shasum": "" }, "require": { "ext-json": "*", "iamcal/sql-parser": "^0.7.0", - "illuminate/console": "^11.44.2 || ^12.4.1", - "illuminate/container": "^11.44.2 || ^12.4.1", - "illuminate/contracts": "^11.44.2 || ^12.4.1", - "illuminate/database": "^11.44.2 || ^12.4.1", - "illuminate/http": "^11.44.2 || ^12.4.1", - "illuminate/pipeline": "^11.44.2 || ^12.4.1", - "illuminate/support": "^11.44.2 || ^12.4.1", + "illuminate/console": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/container": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/contracts": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/database": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/http": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/pipeline": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/support": "^11.44.2 || ^12.4.1 || ^13", "php": "^8.2", "phpstan/phpstan": "^2.1.32" }, "require-dev": { "doctrine/coding-standard": "^13", - "laravel/framework": "^11.44.2 || ^12.7.2", + "laravel/framework": "^11.44.2 || ^12.7.2 || ^13", "mockery/mockery": "^1.6.12", "nikic/php-parser": "^5.4", - "orchestra/canvas": "^v9.2.2 || ^10.0.1", - "orchestra/testbench-core": "^9.12.0 || ^10.1", + "orchestra/canvas": "^v9.2.2 || ^10.0.1 || ^11", + "orchestra/testbench-core": "^9.12.0 || ^10.1 || ^11", "phpstan/phpstan-deprecation-rules": "^2.0.1", - "phpunit/phpunit": "^10.5.35 || ^11.5.15" + "phpunit/phpunit": "^10.5.35 || ^11.5.15 || ^12.5.8" }, "suggest": { "orchestra/testbench": "Using Larastan for analysing a package needs Testbench", @@ -9058,7 +9057,7 @@ ], "support": { "issues": "https://github.com/larastan/larastan/issues", - "source": "https://github.com/larastan/larastan/tree/v3.9.2" + "source": "https://github.com/larastan/larastan/tree/v3.9.3" }, "funding": [ { @@ -9066,29 +9065,29 @@ "type": "github" } ], - "time": "2026-01-30T15:16:32+00:00" + "time": "2026-02-20T12:07:12+00:00" }, { "name": "laravel/boost", - "version": "v2.2.0", + "version": "v2.2.3", "source": { "type": "git", "url": "https://github.com/laravel/boost.git", - "reference": "b4c5bed7b45e9cd9f705ef3ab1157d437376323c" + "reference": "44ab65a5455c2d6fceb71d6145f8d5d89c02d889" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/boost/zipball/b4c5bed7b45e9cd9f705ef3ab1157d437376323c", - "reference": "b4c5bed7b45e9cd9f705ef3ab1157d437376323c", + "url": "https://api.github.com/repos/laravel/boost/zipball/44ab65a5455c2d6fceb71d6145f8d5d89c02d889", + "reference": "44ab65a5455c2d6fceb71d6145f8d5d89c02d889", "shasum": "" }, "require": { "guzzlehttp/guzzle": "^7.9", - "illuminate/console": "^11.45.3|^12.41.1", - "illuminate/contracts": "^11.45.3|^12.41.1", - "illuminate/routing": "^11.45.3|^12.41.1", - "illuminate/support": "^11.45.3|^12.41.1", - "laravel/mcp": "^0.5.1", + "illuminate/console": "^11.45.3|^12.41.1|^13.0", + "illuminate/contracts": "^11.45.3|^12.41.1|^13.0", + "illuminate/routing": "^11.45.3|^12.41.1|^13.0", + "illuminate/support": "^11.45.3|^12.41.1|^13.0", + "laravel/mcp": "^0.5.1|^0.6.0", "laravel/prompts": "^0.3.10", "laravel/roster": "^0.5.0", "php": "^8.2" @@ -9096,7 +9095,7 @@ "require-dev": { "laravel/pint": "^1.27.0", "mockery/mockery": "^1.6.12", - "orchestra/testbench": "^9.15.0|^10.6", + "orchestra/testbench": "^9.15.0|^10.6|^11.0", "pestphp/pest": "^2.36.0|^3.8.4|^4.1.5", "phpstan/phpstan": "^2.1.27", "rector/rector": "^2.1" @@ -9132,20 +9131,20 @@ "issues": "https://github.com/laravel/boost/issues", "source": "https://github.com/laravel/boost" }, - "time": "2026-02-20T14:45:22+00:00" + "time": "2026-03-06T20:20:28+00:00" }, { "name": "laravel/mcp", - "version": "v0.5.9", + "version": "v0.6.0", "source": { "type": "git", "url": "https://github.com/laravel/mcp.git", - "reference": "39e8da60eb7bce4737c5d868d35a3fe78938c129" + "reference": "28860a10ca0cc5433e25d897ba7af844e6c7b6a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/mcp/zipball/39e8da60eb7bce4737c5d868d35a3fe78938c129", - "reference": "39e8da60eb7bce4737c5d868d35a3fe78938c129", + "url": "https://api.github.com/repos/laravel/mcp/zipball/28860a10ca0cc5433e25d897ba7af844e6c7b6a2", + "reference": "28860a10ca0cc5433e25d897ba7af844e6c7b6a2", "shasum": "" }, "require": { @@ -9205,7 +9204,7 @@ "issues": "https://github.com/laravel/mcp/issues", "source": "https://github.com/laravel/mcp" }, - "time": "2026-02-17T19:05:53+00:00" + "time": "2026-02-24T08:43:06+00:00" }, { "name": "laravel/pail", @@ -9356,16 +9355,16 @@ }, { "name": "laravel/roster", - "version": "v0.5.0", + "version": "v0.5.1", "source": { "type": "git", "url": "https://github.com/laravel/roster.git", - "reference": "56904a78f4d7360c1c490ced7deeebf9aecb8c0e" + "reference": "5089de7615f72f78e831590ff9d0435fed0102bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/roster/zipball/56904a78f4d7360c1c490ced7deeebf9aecb8c0e", - "reference": "56904a78f4d7360c1c490ced7deeebf9aecb8c0e", + "url": "https://api.github.com/repos/laravel/roster/zipball/5089de7615f72f78e831590ff9d0435fed0102bb", + "reference": "5089de7615f72f78e831590ff9d0435fed0102bb", "shasum": "" }, "require": { @@ -9413,7 +9412,7 @@ "issues": "https://github.com/laravel/roster/issues", "source": "https://github.com/laravel/roster" }, - "time": "2026-02-17T17:33:35+00:00" + "time": "2026-03-05T07:58:43+00:00" }, { "name": "laravel/sail", @@ -12055,16 +12054,16 @@ }, { "name": "webmozart/assert", - "version": "2.1.5", + "version": "2.1.6", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "79155f94852fa27e2f73b459f6503f5e87e2c188" + "reference": "ff31ad6efc62e66e518fbab1cde3453d389bcdc8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/79155f94852fa27e2f73b459f6503f5e87e2c188", - "reference": "79155f94852fa27e2f73b459f6503f5e87e2c188", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/ff31ad6efc62e66e518fbab1cde3453d389bcdc8", + "reference": "ff31ad6efc62e66e518fbab1cde3453d389bcdc8", "shasum": "" }, "require": { @@ -12111,9 +12110,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/2.1.5" + "source": "https://github.com/webmozarts/assert/tree/2.1.6" }, - "time": "2026-02-18T14:09:36+00:00" + "time": "2026-02-27T10:28:38+00:00" } ], "aliases": [], From d246ac2c599955f40585b6e81e48a1eb56088f42 Mon Sep 17 00:00:00 2001 From: Benjamin Nussbaum Date: Tue, 3 Mar 2026 17:11:21 +0100 Subject: [PATCH 2/7] chore(docker): add instructions on how to generate an APP_KEY --- docker/prod/docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/prod/docker-compose.yml b/docker/prod/docker-compose.yml index 38cac0e..fb7dfc6 100644 --- a/docker/prod/docker-compose.yml +++ b/docker/prod/docker-compose.yml @@ -4,6 +4,7 @@ services: ports: - "4567:8080" environment: + # Generate the APP_KEY with `echo "base64:$(openssl rand -base64 32)"` #- APP_KEY= - PHP_OPCACHE_ENABLE=1 - TRMNL_PROXY_REFRESH_MINUTES=15 From c194ab5db1ae084598a974b8758618bd01c0b7df Mon Sep 17 00:00:00 2001 From: Benjamin Nussbaum Date: Sun, 8 Mar 2026 12:57:11 +0100 Subject: [PATCH 3/7] feat(dev): seed default playlist with device --- database/seeders/DatabaseSeeder.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index c7125c5..cde0f20 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -3,6 +3,7 @@ namespace Database\Seeders; use App\Models\Device; +use App\Models\Playlist; use App\Models\Plugin; use App\Models\User; use Illuminate\Database\Seeder; @@ -23,9 +24,19 @@ class DatabaseSeeder extends Seeder 'password' => bcrypt('admin@example.com'), ]); - Device::factory(1)->create([ + $device = Device::factory()->create([ 'mac_address' => '00:00:00:00:00:00', 'api_key' => 'test-api-key', + 'provisioned' => false, + ]); + + Playlist::factory()->create([ + 'device_id' => $device->id, + 'name' => 'Default', + 'is_active' => true, + 'active_from' => null, + 'active_until' => null, + 'weekdays' => null ]); // Device::factory(5)->create(); From 26b5f3ceb1ad5f502fce8512360dc9d272df9b4e Mon Sep 17 00:00:00 2001 From: Benjamin Nussbaum Date: Fri, 27 Feb 2026 17:38:28 +0100 Subject: [PATCH 4/7] feat(#194): refactor cache to be device specific --- app/Jobs/GenerateScreenJob.php | 9 +- app/Models/Plugin.php | 2 + app/Services/ImageGenerationService.php | 104 +++++++++--- ...urrent_image_metadata_to_plugins_table.php | 28 +++ routes/api.php | 15 +- tests/Feature/GenerateScreenJobTest.php | 25 +++ tests/Feature/ImageGenerationServiceTest.php | 160 +++++++++++++----- .../Services/ImageGenerationServiceTest.php | 45 +---- 8 files changed, 278 insertions(+), 110 deletions(-) create mode 100644 database/migrations/2026_02_27_153837_add_current_image_metadata_to_plugins_table.php diff --git a/app/Jobs/GenerateScreenJob.php b/app/Jobs/GenerateScreenJob.php index b9661cc..4af43dd 100644 --- a/app/Jobs/GenerateScreenJob.php +++ b/app/Jobs/GenerateScreenJob.php @@ -34,8 +34,13 @@ class GenerateScreenJob implements ShouldQueue Device::find($this->deviceId)->update(['current_screen_image' => $newImageUuid]); if ($this->pluginId) { - // cache current image - Plugin::find($this->pluginId)->update(['current_image' => $newImageUuid]); + $plugin = Plugin::find($this->pluginId); + $update = ['current_image' => $newImageUuid]; + if ($plugin->plugin_type === 'recipe') { + $device = Device::with(['deviceModel', 'deviceModel.palette'])->find($this->deviceId); + $update['current_image_metadata'] = ImageGenerationService::buildImageMetadataFromDevice($device); + } + $plugin->update($update); } ImageGenerationService::cleanupFolder(); diff --git a/app/Models/Plugin.php b/app/Models/Plugin.php index 3f84bc5..d99d988 100644 --- a/app/Models/Plugin.php +++ b/app/Models/Plugin.php @@ -49,6 +49,7 @@ class Plugin extends Model 'preferred_renderer' => 'string', 'plugin_type' => 'string', 'alias' => 'boolean', + 'current_image_metadata' => 'array', ]; protected static function boot() @@ -71,6 +72,7 @@ class Plugin extends Model 'render_markup_shared', ])) { $model->current_image = null; + $model->current_image_metadata = null; } }); diff --git a/app/Services/ImageGenerationService.php b/app/Services/ImageGenerationService.php index fa6f325..75f374e 100644 --- a/app/Services/ImageGenerationService.php +++ b/app/Services/ImageGenerationService.php @@ -331,36 +331,88 @@ class ImageGenerationService } } - public static function resetIfNotCacheable(?Plugin $plugin): void + /** + * Ensure plugin image cache is valid for the current context. No-op for image_webhook. + * When deviceOrModel is provided (recipe only), clears cache if stored metadata does not match. + */ + public static function resetIfNotCacheable(?Plugin $plugin, Device|DeviceModel|null $deviceOrModel = null): void { - if ($plugin?->id) { - // Image webhook plugins have finalized images that shouldn't be reset - if ($plugin->plugin_type === 'image_webhook') { - return; - } - // Check if any devices have custom dimensions or use non-standard DeviceModels - $hasCustomDimensions = Device::query() - ->where(function ($query): void { - $query->where('width', '!=', 800) - ->orWhere('height', '!=', 480) - ->orWhere('rotate', '!=', 0); - }) - ->orWhereHas('deviceModel', function ($query): void { - // Only allow caching if all device models have standard dimensions (800x480, rotation=0) - $query->where(function ($subQuery): void { - $subQuery->where('width', '!=', 800) - ->orWhere('height', '!=', 480) - ->orWhere('rotation', '!=', 0); - }); - }) - ->exists(); + if (! $plugin?->id || $plugin->plugin_type === 'image_webhook') { + return; + } + if ($deviceOrModel === null || $plugin->plugin_type !== 'recipe') { + return; + } + if ($plugin->current_image === null) { + return; + } + if (self::imageMetadataMatches($plugin->current_image_metadata, $deviceOrModel)) { + return; + } + $plugin->update([ + 'current_image' => null, + 'current_image_metadata' => null, + ]); + Log::debug("Plugin {$plugin->id}: cleared image cache due to metadata mismatch"); + } - if ($hasCustomDimensions) { - // TODO cache image per device - $plugin->update(['current_image' => null]); - Log::debug('Skip cache as devices with custom dimensions or non-standard DeviceModels exist'); + /** + * Build canonical image metadata from a Device for cache comparison. + * + * @return array{width: int, height: int, rotation: int, palette_id: int|null, mime_type: string} + */ + public static function buildImageMetadataFromDevice(Device $device): array + { + $device->loadMissing(['deviceModel', 'deviceModel.palette']); + $settings = self::getImageSettings($device); + $paletteId = $device->palette_id ?? $device->deviceModel?->palette_id; + + return [ + 'width' => $settings['width'], + 'height' => $settings['height'], + 'rotation' => $settings['rotation'] ?? 0, + 'palette_id' => $paletteId, + 'mime_type' => $settings['mime_type'], + ]; + } + + /** + * Build canonical image metadata from a DeviceModel for cache comparison. + * + * @return array{width: int, height: int, rotation: int, palette_id: int|null, mime_type: string} + */ + public static function buildImageMetadataFromDeviceModel(DeviceModel $model): array + { + return [ + 'width' => $model->width, + 'height' => $model->height, + 'rotation' => $model->rotation ?? 0, + 'palette_id' => $model->palette_id, + 'mime_type' => $model->mime_type, + ]; + } + + /** + * Check if stored metadata matches the current device or device model. + * Returns false if stored is null or empty so cache is regenerated and metadata is stored. + */ + public static function imageMetadataMatches(?array $stored, Device|DeviceModel $deviceOrModel): bool + { + if ($stored === null || $stored === []) { + return false; + } + + $current = $deviceOrModel instanceof Device + ? self::buildImageMetadataFromDevice($deviceOrModel) + : self::buildImageMetadataFromDeviceModel($deviceOrModel); + + foreach (['width', 'height', 'rotation', 'palette_id', 'mime_type'] as $key) { + if (($stored[$key] ?? null) !== ($current[$key] ?? null)) { + return false; } } + + return true; } /** diff --git a/database/migrations/2026_02_27_153837_add_current_image_metadata_to_plugins_table.php b/database/migrations/2026_02_27_153837_add_current_image_metadata_to_plugins_table.php new file mode 100644 index 0000000..d212fe7 --- /dev/null +++ b/database/migrations/2026_02_27_153837_add_current_image_metadata_to_plugins_table.php @@ -0,0 +1,28 @@ +json('current_image_metadata')->nullable()->after('current_image'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('plugins', function (Blueprint $table) { + $table->dropColumn('current_image_metadata'); + }); + } +}; diff --git a/routes/api.php b/routes/api.php index c759d61..8fc040c 100644 --- a/routes/api.php +++ b/routes/api.php @@ -88,8 +88,8 @@ Route::get('/display', function (Request $request) { $refreshTimeOverride = $playlistItem->playlist()->first()->refresh_time; $plugin = $playlistItem->plugin; - // Reset cache if Devices with different dimensions exist - ImageGenerationService::resetIfNotCacheable($plugin); + ImageGenerationService::resetIfNotCacheable($plugin, $device); + $plugin->refresh(); // Check and update stale data if needed if ($plugin->isDataStale() || $plugin->current_image === null) { @@ -699,6 +699,9 @@ Route::get('/display/{uuid}/alias', function (Request $request, string $uuid) { ], 404); } + ImageGenerationService::resetIfNotCacheable($plugin, $deviceModel); + $plugin->refresh(); + // Check if we can use cached image (only for og_png and if data is not stale) $useCache = $deviceModelName === 'og_png' && ! $plugin->isDataStale() && $plugin->current_image !== null; @@ -744,9 +747,13 @@ Route::get('/display/{uuid}/alias', function (Request $request, string $uuid) { palette: $deviceModel->palette ); - // Update plugin cache if using og_png + // Update plugin cache if using og_png (recipes only get metadata for cache comparison) if ($deviceModelName === 'og_png') { - $plugin->update(['current_image' => $imageUuid]); + $update = ['current_image' => $imageUuid]; + if ($plugin->plugin_type === 'recipe') { + $update['current_image_metadata'] = ImageGenerationService::buildImageMetadataFromDeviceModel($deviceModel); + } + $plugin->update($update); } // Return the generated image diff --git a/tests/Feature/GenerateScreenJobTest.php b/tests/Feature/GenerateScreenJobTest.php index 115fb51..9482e8c 100644 --- a/tests/Feature/GenerateScreenJobTest.php +++ b/tests/Feature/GenerateScreenJobTest.php @@ -2,6 +2,8 @@ use App\Jobs\GenerateScreenJob; use App\Models\Device; +use App\Models\DeviceModel; +use App\Models\Plugin; use Bnussbau\TrmnlPipeline\TrmnlPipeline; use Illuminate\Support\Facades\Storage; @@ -58,3 +60,26 @@ test('it preserves gitignore file during cleanup', function (): void { Storage::disk('public')->assertExists('/images/generated/.gitignore'); }); + +test('it saves current_image_metadata for recipe plugins', function (): void { + $deviceModel = DeviceModel::factory()->create([ + 'width' => 800, + 'height' => 480, + 'rotation' => 0, + 'mime_type' => 'image/png', + 'palette_id' => null, + ]); + $device = Device::factory()->create(['device_model_id' => $deviceModel->id]); + $plugin = Plugin::factory()->create(['plugin_type' => 'recipe']); + + $job = new GenerateScreenJob($device->id, $plugin->id, '
Test
'); + $job->handle(); + + $plugin->refresh(); + expect($plugin->current_image)->not->toBeNull(); + expect($plugin->current_image_metadata)->toBeArray(); + expect($plugin->current_image_metadata)->toHaveKeys(['width', 'height', 'rotation', 'palette_id', 'mime_type']); + expect($plugin->current_image_metadata['width'])->toBe(800); + expect($plugin->current_image_metadata['height'])->toBe(480); + expect($plugin->current_image_metadata['mime_type'])->toBe('image/png'); +}); diff --git a/tests/Feature/ImageGenerationServiceTest.php b/tests/Feature/ImageGenerationServiceTest.php index 07bb6a6..9d26eb6 100644 --- a/tests/Feature/ImageGenerationServiceTest.php +++ b/tests/Feature/ImageGenerationServiceTest.php @@ -8,6 +8,7 @@ use App\Models\DeviceModel; use App\Services\ImageGenerationService; use Bnussbau\TrmnlPipeline\TrmnlPipeline; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Storage; uses(RefreshDatabase::class); @@ -22,6 +23,10 @@ afterEach(function (): void { TrmnlPipeline::restore(); }); +it('plugins table has current_image_metadata column', function (): void { + expect(Schema::hasColumn('plugins', 'current_image_metadata'))->toBeTrue(); +}); + it('generates image for device without device model', function (): void { // Create a device without a DeviceModel (legacy behavior) $device = Device::factory()->create([ @@ -270,39 +275,15 @@ it('cleanupFolder preserves .gitignore', function (): void { Storage::disk('public')->assertExists('/images/generated/.gitignore'); }); -it('resetIfNotCacheable resets when device models exist', function (): void { - // Create a plugin - $plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid']); +it('resetIfNotCacheable does not reset recipe cache based on other devices', function (): void { + // Cache validity is now determined at use-time via current_image_metadata + $plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid', 'plugin_type' => 'recipe']); - // Create a device with DeviceModel (should trigger cache reset) - Device::factory()->create([ - 'device_model_id' => DeviceModel::factory()->create()->id, - ]); - - // Run reset check + Device::factory()->create(['device_model_id' => DeviceModel::factory()->create()->id]); ImageGenerationService::resetIfNotCacheable($plugin); - // Assert plugin image was reset $plugin->refresh(); - expect($plugin->current_image)->toBeNull(); -}); - -it('resetIfNotCacheable resets when custom dimensions exist', function (): void { - // Create a plugin - $plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid']); - - // Create a device with custom dimensions (should trigger cache reset) - Device::factory()->create([ - 'width' => 1024, // Different from default 800 - 'height' => 768, // Different from default 480 - ]); - - // Run reset check - ImageGenerationService::resetIfNotCacheable($plugin); - - // Assert plugin image was reset - $plugin->refresh(); - expect($plugin->current_image)->toBeNull(); + expect($plugin->current_image)->toBe('test-uuid'); }); it('resetIfNotCacheable preserves image for standard devices', function (): void { @@ -325,27 +306,122 @@ it('resetIfNotCacheable preserves image for standard devices', function (): void }); it('cache is reset when plugin markup changes', function (): void { - // Create a plugin with cached image + // Create a plugin with cached image and metadata $plugin = App\Models\Plugin::factory()->create([ 'current_image' => 'cached-uuid', + 'current_image_metadata' => ['width' => 800, 'height' => 480, 'rotation' => 0, 'palette_id' => null, 'mime_type' => 'image/png'], 'render_markup' => '
Original markup
', ]); - // Create devices with standard dimensions (cacheable) - Device::factory()->count(2)->create([ - 'width' => 800, - 'height' => 480, - 'rotate' => 0, - ]); + $plugin->update(['render_markup' => '
Updated markup
']); - // Update the plugin markup - $plugin->update([ - 'render_markup' => '
Updated markup
', - ]); - - // Assert cache was reset when markup changed $plugin->refresh(); expect($plugin->current_image)->toBeNull(); + expect($plugin->current_image_metadata)->toBeNull(); +}); + +it('buildImageMetadataFromDevice returns canonical metadata shape', function (): void { + $deviceModel = DeviceModel::factory()->create([ + 'width' => 800, + 'height' => 480, + 'rotation' => 0, + 'mime_type' => 'image/png', + 'palette_id' => null, + ]); + $device = Device::factory()->create(['device_model_id' => $deviceModel->id]); + + $meta = ImageGenerationService::buildImageMetadataFromDevice($device); + + expect($meta)->toHaveKeys(['width', 'height', 'rotation', 'palette_id', 'mime_type']); + expect($meta['width'])->toBe(800); + expect($meta['height'])->toBe(480); + expect($meta['rotation'])->toBe(0); + expect($meta['mime_type'])->toBe('image/png'); +}); + +it('buildImageMetadataFromDeviceModel returns canonical metadata shape', function (): void { + $model = DeviceModel::factory()->create([ + 'width' => 1024, + 'height' => 768, + 'rotation' => 90, + 'mime_type' => 'image/bmp', + 'palette_id' => null, + ]); + + $meta = ImageGenerationService::buildImageMetadataFromDeviceModel($model); + + expect($meta)->toHaveKeys(['width', 'height', 'rotation', 'palette_id', 'mime_type']); + expect($meta['width'])->toBe(1024); + expect($meta['height'])->toBe(768); + expect($meta['rotation'])->toBe(90); + expect($meta['mime_type'])->toBe('image/bmp'); +}); + +it('imageMetadataMatches returns false when stored is null or empty', function (): void { + $device = Device::factory()->create(['width' => 800, 'height' => 480, 'rotate' => 0]); + + expect(ImageGenerationService::imageMetadataMatches(null, $device))->toBeFalse(); + expect(ImageGenerationService::imageMetadataMatches([], $device))->toBeFalse(); +}); + +it('imageMetadataMatches returns true when metadata matches device', function (): void { + $deviceModel = DeviceModel::factory()->create([ + 'width' => 800, + 'height' => 480, + 'rotation' => 0, + 'mime_type' => 'image/png', + 'palette_id' => null, + ]); + $device = Device::factory()->create(['device_model_id' => $deviceModel->id]); + $stored = ImageGenerationService::buildImageMetadataFromDevice($device); + + expect(ImageGenerationService::imageMetadataMatches($stored, $device))->toBeTrue(); +}); + +it('imageMetadataMatches returns false when metadata differs', function (): void { + $device = Device::factory()->create(['width' => 800, 'height' => 480, 'rotate' => 0]); + $stored = ['width' => 800, 'height' => 480, 'rotation' => 0, 'palette_id' => null, 'mime_type' => 'image/png']; + + $device->update(['width' => 1024]); + $device->refresh(); + + expect(ImageGenerationService::imageMetadataMatches($stored, $device))->toBeFalse(); +}); + +it('resetIfNotCacheable clears recipe cache when metadata does not match', function (): void { + $plugin = App\Models\Plugin::factory()->create([ + 'plugin_type' => 'recipe', + 'current_image' => 'cached-uuid', + 'current_image_metadata' => ['width' => 800, 'height' => 480, 'rotation' => 0, 'palette_id' => null, 'mime_type' => 'image/png'], + ]); + $device = Device::factory()->create(['width' => 1024, 'height' => 768, 'rotate' => 0]); + + ImageGenerationService::resetIfNotCacheable($plugin, $device); + + $plugin->refresh(); + expect($plugin->current_image)->toBeNull(); + expect($plugin->current_image_metadata)->toBeNull(); +}); + +it('resetIfNotCacheable preserves cache when metadata matches', function (): void { + $deviceModel = DeviceModel::factory()->create([ + 'width' => 800, + 'height' => 480, + 'rotation' => 0, + 'mime_type' => 'image/png', + ]); + $device = Device::factory()->create(['device_model_id' => $deviceModel->id]); + $meta = ImageGenerationService::buildImageMetadataFromDevice($device); + $plugin = App\Models\Plugin::factory()->create([ + 'plugin_type' => 'recipe', + 'current_image' => 'cached-uuid', + 'current_image_metadata' => $meta, + ]); + + ImageGenerationService::resetIfNotCacheable($plugin, $device); + + $plugin->refresh(); + expect($plugin->current_image)->toBe('cached-uuid'); }); it('determines correct image format from device model', function (): void { diff --git a/tests/Unit/Services/ImageGenerationServiceTest.php b/tests/Unit/Services/ImageGenerationServiceTest.php index 5e3dc47..da9bef9 100644 --- a/tests/Unit/Services/ImageGenerationServiceTest.php +++ b/tests/Unit/Services/ImageGenerationServiceTest.php @@ -176,37 +176,15 @@ it('cleanup_folder identifies active images correctly', function (): void { expect($activeImageUuids)->not->toContain(null); }); -it('reset_if_not_cacheable detects device models', function (): void { - // Create a plugin - $plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid']); +it('reset_if_not_cacheable does not reset recipe cache when other devices exist', function (): void { + // Cache validity is now determined at use-time via metadata + $plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid', 'plugin_type' => 'recipe']); + Device::factory()->create(['device_model_id' => DeviceModel::factory()->create()->id]); - // Create a device with DeviceModel - Device::factory()->create([ - 'device_model_id' => DeviceModel::factory()->create()->id, - ]); - - // Test that the method detects DeviceModels and resets cache ImageGenerationService::resetIfNotCacheable($plugin); $plugin->refresh(); - expect($plugin->current_image)->toBeNull(); -}); - -it('reset_if_not_cacheable detects custom dimensions', function (): void { - // Create a plugin - $plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid']); - - // Create a device with custom dimensions - Device::factory()->create([ - 'width' => 1024, // Different from default 800 - 'height' => 768, // Different from default 480 - ]); - - // Test that the method detects custom dimensions and resets cache - ImageGenerationService::resetIfNotCacheable($plugin); - - $plugin->refresh(); - expect($plugin->current_image)->toBeNull(); + expect($plugin->current_image)->toBe('test-uuid'); }); it('reset_if_not_cacheable preserves cache for standard devices', function (): void { @@ -258,26 +236,21 @@ it('reset_if_not_cacheable preserves cache for og_png and og_plus device models' expect($plugin->current_image)->toBe('test-uuid'); }); -it('reset_if_not_cacheable resets cache for non-standard device models', function (): void { - // Create a plugin - $plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid']); - - // Create a non-standard device model (e.g., kindle) +it('reset_if_not_cacheable does not reset cache for non-standard device models', function (): void { + // Cache is now validated at use-time via metadata comparison + $plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid', 'plugin_type' => 'recipe']); $kindleModel = DeviceModel::factory()->create([ 'name' => 'test_amazon_kindle_2024', 'width' => 1400, 'height' => 840, 'rotation' => 90, ]); - - // Create a device with the non-standard device model Device::factory()->create(['device_model_id' => $kindleModel->id]); - // Test that the method resets cache for non-standard device models ImageGenerationService::resetIfNotCacheable($plugin); $plugin->refresh(); - expect($plugin->current_image)->toBeNull(); + expect($plugin->current_image)->toBe('test-uuid'); }); it('reset_if_not_cacheable handles null plugin', function (): void { From ba541f62f17be52d9ecc6706e4028c27af200d35 Mon Sep 17 00:00:00 2001 From: Benjamin Nussbaum Date: Sun, 8 Mar 2026 17:40:36 +0100 Subject: [PATCH 5/7] feat(dev): seed default playlist with device --- database/seeders/DatabaseSeeder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index cde0f20..5811f4c 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -27,7 +27,7 @@ class DatabaseSeeder extends Seeder $device = Device::factory()->create([ 'mac_address' => '00:00:00:00:00:00', 'api_key' => 'test-api-key', - 'provisioned' => false, + 'proxy_cloud' => false, ]); Playlist::factory()->create([ From 9df538de168d1b386dcd8379eac98c5e4363c27a Mon Sep 17 00:00:00 2001 From: Benjamin Nussbaum Date: Wed, 4 Mar 2026 08:24:13 +0100 Subject: [PATCH 6/7] feat: add logo --- resources/views/components/app-logo.blade.php | 3 ++- resources/views/components/auth-header.blade.php | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/resources/views/components/app-logo.blade.php b/resources/views/components/app-logo.blade.php index 842020e..2bde6c1 100644 --- a/resources/views/components/app-logo.blade.php +++ b/resources/views/components/app-logo.blade.php @@ -2,5 +2,6 @@
- TRMNL BYOS Laravel + {{-- TRMNL BYOS Laravel --}} +
diff --git a/resources/views/components/auth-header.blade.php b/resources/views/components/auth-header.blade.php index e596a3f..410a62e 100644 --- a/resources/views/components/auth-header.blade.php +++ b/resources/views/components/auth-header.blade.php @@ -4,6 +4,7 @@ ])
- {{ $title }} +{{-- {{ $title }}--}} + {{ $description }}
From 3abc67ff67650d9c324d4ee737d67dff570526b9 Mon Sep 17 00:00:00 2001 From: Benjamin Nussbaum Date: Sun, 8 Mar 2026 14:18:17 +0100 Subject: [PATCH 7/7] feat: rebranding to LaraPaper --- .github/workflows/docker-build.yml | 5 +- Dockerfile | 4 +- README.md | 22 ++++----- app/Models/Plugin.php | 2 +- config/app.php | 6 ++- docker/prod/docker-compose.yml | 2 +- docs/DEVELOPMENT.md | 2 +- resources/views/components/app-logo.blade.php | 7 ++- .../views/components/auth-header.blade.php | 7 ++- .../views/default-screens/setup.blade.php | 4 +- .../views/default-screens/sleep.blade.php | 2 +- .../views/livewire/plugins/index.blade.php | 4 +- .../views/livewire/plugins/markup.blade.php | 4 +- resources/views/partials/head.blade.php | 2 +- resources/views/welcome.blade.php | 4 +- tests/Feature/PixelLogoConfigTest.php | 46 +++++++++++++++++++ 16 files changed, 89 insertions(+), 34 deletions(-) create mode 100644 tests/Feature/PixelLogoConfigTest.php diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index a4ff129..c8327d3 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -6,7 +6,6 @@ on: env: REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} jobs: build-and-push: @@ -40,7 +39,9 @@ jobs: id: meta uses: docker/metadata-action@v5 with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + images: | + ${{ env.REGISTRY }}/usetrmnl/byos_laravel + ${{ env.REGISTRY }}/usetrmnl/larapaper tags: | type=semver,pattern={{version}} diff --git a/Dockerfile b/Dockerfile index c679db8..4dc531b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,8 +3,8 @@ ######################## FROM bnussbau/serversideup-php:8.4-fpm-nginx-alpine-imagick-chromium@sha256:ed705a4060d50143ddc538c1288afff217eaf76ad5791f7556a97943854cf745 AS base -LABEL org.opencontainers.image.source=https://github.com/usetrmnl/byos_laravel -LABEL org.opencontainers.image.description="TRMNL BYOS Laravel" +LABEL org.opencontainers.image.source=https://github.com/usetrmnl/larapaper +LABEL org.opencontainers.image.description="LaraPaper" LABEL org.opencontainers.image.licenses=MIT ARG APP_VERSION diff --git a/README.md b/README.md index 442cc15..c1ebafe 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -## TRMNL BYOS (PHP/Laravel) +## LaraPaper (PHP/Laravel) -[![tests](https://github.com/usetrmnl/byos_laravel/actions/workflows/test.yml/badge.svg)](https://github.com/usetrmnl/byos_laravel/actions/workflows/test.yml) +[![tests](https://github.com/usetrmnl/larapaper/actions/workflows/test.yml/badge.svg)](https://github.com/usetrmnl/larapaper/actions/workflows/test.yml) -TRMNL BYOS Laravel is a self-hostable implementation of a TRMNL server, built with Laravel. +LaraPaper is a self-hostable implementation of a TRMNL server (BYOS), built with Laravel. It allows you to manage TRMNL devices, generate screens using **native plugins** (Screens API, Markup), **recipes** (130+ from the [OSS community catalog](https://bnussbau.github.io/trmnl-recipe-catalog/), 700+ from the [TRMNL catalog](https://trmnl.com/recipes), or your own), or the **API**, and can also act as a **proxy** for the native cloud service (Core). With over 50k downloads and 200+ stars, it’s the most popular community-driven BYOS. ![Screenshot](README_byos-screenshot.png) @@ -26,7 +26,7 @@ It allows you to manage TRMNL devices, generate screens using **native plugins** * Custom ESP32 with TRMNL firmware * E-Reader Devices * KOReader ([trmnl-koreader](https://github.com/usetrmnl/trmnl-koreader)) - * Kindle ([trmnl-kindle](https://github.com/usetrmnl/byos_laravel/pull/27)) + * Kindle ([trmnl-kindle](https://github.com/usetrmnl/larapaper/pull/27)) * Nook ([trmnl-nook](https://github.com/usetrmnl/trmnl-nook)) * Kobo ([trmnl-kobo](https://github.com/usetrmnl/trmnl-kobo)) * Android Devices with [trmnl-android](https://github.com/usetrmnl/trmnl-android) @@ -61,7 +61,7 @@ Docker Compose file located at: [docker/prod/docker-compose.yml](docker/prod/doc ##### Backup Database ```sh -docker ps #find container id of byos_laravel container +docker ps #find container id of larapaper container docker cp {{CONTAINER_ID}}:/var/www/html/database/storage/database.sqlite database_backup.sqlite ``` @@ -73,11 +73,11 @@ docker compose up -d ``` #### VPS -If you’re using a VPS (e.g., Hetzner) and prefer an alternative to native Docker, you can install Dokploy and deploy BYOS Laravel using the integrated [Template](https://templates.dokploy.com/?q=trmnl+byos+laravel). +If you’re using a VPS (e.g., Hetzner) and prefer an alternative to native Docker, you can install Dokploy and deploy LaraPaper using the integrated [Template](https://templates.dokploy.com/?q=trmnl+byos+laravel). It’s a quick way to get started without having to manually manage Docker setup. #### PikaPods -You can vote for TRMNL BYOS Laravel to be included as PikaPods Template here: [feedback.pikapods.com](https://feedback.pikapods.com/posts/842/add-app-trmnl-byos-laravel) +You can vote for LaraPaper to be included as PikaPods Template here: [feedback.pikapods.com](https://feedback.pikapods.com/posts/842/add-app-trmnl-byos-laravel) #### Umbrel Umbrel is supported through a community store, [see](http://github.com/bnussbau/umbrel-store). @@ -173,13 +173,13 @@ See this YouTube guide: [https://www.youtube.com/watch?v=3xehPW-PCOM](https://ww ### ☁️ Activate fresh TRMNL Device with Cloud Proxy 1) Setup the TRMNL as in the official docs with the cloud service (connect one of the plugins to later verify it works) -2) Setup Laravel BYOS, create a user and login -3) In Laravel BYOS in the header bar, activate the toggle "Permit Auto-Join" +2) Setup LaraPaper, create a user and login +3) In LaraPaper in the header bar, activate the toggle "Permit Auto-Join" 4) Press and hold the button on the back of your TRMNL for 5 seconds to reactivate the captive portal (or reflash). -5) Go through the setup process again, in the screen where you provide the Wi-Fi credentials there is also option to set the Server URL. Use the local address of your Laravel BYOS +5) Go through the setup process again, in the screen where you provide the Wi-Fi credentials there is also option to set the Server URL. Use the local address of LaraPaper. 6) The device should automatically appear in the device list; you can deactivate the "Permit Auto-Join" toggle again. 7) In the devices list, activate the toggle "☁️ Proxy" for your device. (Make sure that the queue worker is active. In the docker image it should be running automatically.) -8) As long as no Laravel BYOS plugin is scheduled, the device will show your cloud plugins. +8) As long as no LaraPaper plugin is scheduled, the device will show your cloud plugins. ###### Troubleshooting diff --git a/app/Models/Plugin.php b/app/Models/Plugin.php index d99d988..0a39553 100644 --- a/app/Models/Plugin.php +++ b/app/Models/Plugin.php @@ -224,7 +224,7 @@ class Plugin extends Model if ($this->data_strategy !== 'polling' || ! $this->polling_url) { return; } - $headers = ['User-Agent' => 'usetrmnl/byos_laravel', 'Accept' => 'application/json']; + $headers = ['User-Agent' => 'usetrmnl/larapaper', 'Accept' => 'application/json']; // resolve headers if ($this->polling_header) { diff --git a/config/app.php b/config/app.php index fe8499c..176342e 100644 --- a/config/app.php +++ b/config/app.php @@ -13,7 +13,7 @@ return [ | */ - 'name' => env('APP_NAME', 'Laravel'), + 'name' => env('APP_NAME', 'LaraPaper'), /* |-------------------------------------------------------------------------- @@ -127,6 +127,8 @@ return [ 'enabled' => env('REGISTRATION_ENABLED', true), ], + 'pixel_logo_enabled' => env('PIXELLOGO_ENABLED', true), + 'force_https' => env('FORCE_HTTPS', false), 'puppeteer_docker' => env('PUPPETEER_DOCKER', false), 'puppeteer_mode' => env('PUPPETEER_MODE', 'local'), @@ -154,5 +156,5 @@ return [ 'catalog_url' => env('CATALOG_URL', 'https://raw.githubusercontent.com/bnussbau/trmnl-recipe-catalog/refs/heads/main/catalog.yaml'), - 'github_repo' => env('GITHUB_REPO', 'usetrmnl/byos_laravel'), + 'github_repo' => env('GITHUB_REPO', 'usetrmnl/larapaper'), ]; diff --git a/docker/prod/docker-compose.yml b/docker/prod/docker-compose.yml index fb7dfc6..eaa48fb 100644 --- a/docker/prod/docker-compose.yml +++ b/docker/prod/docker-compose.yml @@ -1,6 +1,6 @@ services: app: - image: ghcr.io/usetrmnl/byos_laravel:latest + image: ghcr.io/usetrmnl/larapaper:latest ports: - "4567:8080" environment: diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 40bcbd3..e2246bc 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -9,7 +9,7 @@ #### Clone the repository ```bash -git clone git@github.com:usetrmnl/byos_laravel.git +git clone git@github.com:usetrmnl/larapaper.git ``` #### Copy environment file diff --git a/resources/views/components/app-logo.blade.php b/resources/views/components/app-logo.blade.php index 2bde6c1..f50634a 100644 --- a/resources/views/components/app-logo.blade.php +++ b/resources/views/components/app-logo.blade.php @@ -2,6 +2,9 @@
- {{-- TRMNL BYOS Laravel --}} - + @if(config('app.pixel_logo_enabled')) + + @else + LaraPaper + @endif
diff --git a/resources/views/components/auth-header.blade.php b/resources/views/components/auth-header.blade.php index 410a62e..78aa2e5 100644 --- a/resources/views/components/auth-header.blade.php +++ b/resources/views/components/auth-header.blade.php @@ -4,7 +4,10 @@ ])
-{{-- {{ $title }}--}} - + @if(config('app.pixel_logo_enabled')) + + @else + LaraPaper + @endif {{ $description }}
diff --git a/resources/views/default-screens/setup.blade.php b/resources/views/default-screens/setup.blade.php index ab7ec60..9113eb6 100644 --- a/resources/views/default-screens/setup.blade.php +++ b/resources/views/default-screens/setup.blade.php @@ -15,10 +15,10 @@ - Welcome to BYOS Laravel! + Welcome to LaraPaper! Your device is connected. - + diff --git a/resources/views/default-screens/sleep.blade.php b/resources/views/default-screens/sleep.blade.php index fa0c8cd..ef0a80c 100644 --- a/resources/views/default-screens/sleep.blade.php +++ b/resources/views/default-screens/sleep.blade.php @@ -25,6 +25,6 @@ Sleep Mode - + diff --git a/resources/views/livewire/plugins/index.blade.php b/resources/views/livewire/plugins/index.blade.php index 848fc67..0985841 100644 --- a/resources/views/livewire/plugins/index.blade.php +++ b/resources/views/livewire/plugins/index.blade.php @@ -264,7 +264,7 @@ new class extends Component {{--
  • date: "%N" is unsupported. Use date: "u" instead
  • --}} {{-- --}} - Please report issues on GitHub. Include your example zip file. + Please report issues on GitHub. Include your example zip file.
    @@ -315,7 +315,7 @@ new class extends Component
  • API responses in formats other than JSON are not yet fully supported.
  • There are limitations in payload size (Data Payload, Template).
  • - Please report issues, aside from the known limitations, on GitHub. Include the recipe URL. + Please report issues, aside from the known limitations, on GitHub. Include the recipe URL. diff --git a/resources/views/livewire/plugins/markup.blade.php b/resources/views/livewire/plugins/markup.blade.php index 150e626..392b0b9 100644 --- a/resources/views/livewire/plugins/markup.blade.php +++ b/resources/views/livewire/plugins/markup.blade.php @@ -72,8 +72,8 @@ new class extends Component - TRMNL BYOS Laravel - “This screen was rendered by BYOS Laravel” + LaraPaper + “This screen was rendered by BYOS LaraPaper” Benjamin Nussbaum diff --git a/resources/views/partials/head.blade.php b/resources/views/partials/head.blade.php index fa0f31a..26a42a6 100644 --- a/resources/views/partials/head.blade.php +++ b/resources/views/partials/head.blade.php @@ -1,7 +1,7 @@ -{{ $title ?? 'TRMNL BYOS Laravel' }} +{{ $title ?? 'LaraPaper' }} diff --git a/resources/views/welcome.blade.php b/resources/views/welcome.blade.php index abf6a69..ba608f9 100644 --- a/resources/views/welcome.blade.php +++ b/resources/views/welcome.blade.php @@ -1,6 +1,6 @@
    - +
    @if (Route::has('login')) @@ -33,7 +33,7 @@
    @auth @if(config('app.version')) - Version: Version: {{ config('app.version') }} @endif diff --git a/tests/Feature/PixelLogoConfigTest.php b/tests/Feature/PixelLogoConfigTest.php new file mode 100644 index 0000000..ba009c9 --- /dev/null +++ b/tests/Feature/PixelLogoConfigTest.php @@ -0,0 +1,46 @@ +get('/login'); + + $response->assertStatus(200); + $response->assertSee('viewBox="0 0 1000 150"', false); +}); + +test('auth pages show heading instead of pixel logo when pixel_logo_enabled is false', function (): void { + Config::set('app.pixel_logo_enabled', false); + + $response = $this->get('/login'); + + $response->assertStatus(200); + $response->assertDontSee('viewBox="0 0 1000 150"', false); + $response->assertSee('LaraPaper'); +}); + +test('app logo shows text when pixel_logo_enabled is false', function (): void { + Config::set('app.pixel_logo_enabled', false); + + $user = User::factory()->create(); + $response = $this->actingAs($user)->get(route('dashboard')); + + $response->assertStatus(200); + $response->assertSee('LaraPaper'); + $response->assertDontSee('viewBox="0 0 1000 150"', false); +}); + +test('app logo shows pixel logo SVG when pixel_logo_enabled is true', function (): void { + Config::set('app.pixel_logo_enabled', true); + + $user = User::factory()->create(); + $response = $this->actingAs($user)->get(route('dashboard')); + + $response->assertStatus(200); + $response->assertSee('viewBox="0 0 1000 150"', false); +});