diff --git a/config/plausible-proxy.php b/config/plausible-proxy.php index a16dcf8..487a4fe 100644 --- a/config/plausible-proxy.php +++ b/config/plausible-proxy.php @@ -1,6 +1,8 @@ env('PLAUSIBLE_DOMAIN', 'https://plausible.io'), + 'site' => env('PLAUSIBLE_SITE', 'example.com'), ]; diff --git a/routes/api.php b/routes/api.php new file mode 100644 index 0000000..4e41866 --- /dev/null +++ b/routes/api.php @@ -0,0 +1,26 @@ +all(), [ + 'n' => 'required|string', + 'u' => 'required|url', + 'd' => 'required|string', + 'r' => 'nullable|url', + 'm' => 'nullable|string', + 'p' => 'nullable|array', + ]); + + if ($validator->fails()) { + return response()->json(['status' => 'error', 'errors' => $validator->errors()], 422); + } + + Concurrency::defer(fn () => Http::post(config('plausible-proxy.domain').'/api/event', $request->all())); + + return response()->json(['status' => 'queued']); +}); diff --git a/routes/web.php b/routes/web.php new file mode 100644 index 0000000..07f79ce --- /dev/null +++ b/routes/web.php @@ -0,0 +1,34 @@ +header('Content-Type', 'application/javascript') + ->header('Cache-Control', 'public, max-age=86400'); + } + + $response = Http::timeout(5)->get("{$plausibleDomain}/js/script.js"); + + if ($response->successful()) { + $script = $response->body(); + Cache::put('plausible_script_js', $script, now()->addHours(6)); + + return response($script) + ->header('Content-Type', 'application/javascript') + ->header('Cache-Control', 'public, max-age=86400'); + } + + return response('Error fetching Plausible script', 500); +}); + +Route::get('/api/routes', function () { + return response()->json( + collect(Route::getRoutes())->map(fn ($route) => $route->uri()) + ); +}); diff --git a/src/LaravelPlausibleProxyServiceProvider.php b/src/LaravelPlausibleProxyServiceProvider.php index eb14fa4..a1c39cd 100644 --- a/src/LaravelPlausibleProxyServiceProvider.php +++ b/src/LaravelPlausibleProxyServiceProvider.php @@ -2,7 +2,6 @@ namespace MattitjaAB\LaravelPlausibleProxy; -use MattitjaAB\LaravelPlausibleProxy\Commands\LaravelPlausibleProxyCommand; use Spatie\LaravelPackageTools\Package; use Spatie\LaravelPackageTools\PackageServiceProvider; @@ -10,16 +9,9 @@ class LaravelPlausibleProxyServiceProvider extends PackageServiceProvider { public function configurePackage(Package $package): void { - /* - * This class is a Package Service Provider - * - * More info: https://github.com/spatie/laravel-package-tools - */ $package ->name('laravel-plausible-proxy') ->hasConfigFile() - ->hasViews() - ->hasMigration('create_laravel_plausible_proxy_table') - ->hasCommand(LaravelPlausibleProxyCommand::class); + ->hasRoutes(['web', 'api']); } } diff --git a/tests/ExampleTest.php b/tests/ExampleTest.php deleted file mode 100644 index 5d36321..0000000 --- a/tests/ExampleTest.php +++ /dev/null @@ -1,5 +0,0 @@ -toBeTrue(); -}); diff --git a/tests/PlausibleProxyTest.php b/tests/PlausibleProxyTest.php new file mode 100644 index 0000000..c715258 --- /dev/null +++ b/tests/PlausibleProxyTest.php @@ -0,0 +1,74 @@ +addHours(6)); + + $response = $this->get('/js/script.js'); + + $response->assertStatus(200) + ->assertHeader('Content-Type', 'application/javascript') + ->assertSee('Cached script'); +}); + +it('fetches plausible script if not cached', function () { + Http::fake([ + 'https://plausible.io/js/script.js' => Http::response('console.log("Plausible script");', 200), + ]); + + $response = $this->get('/js/script.js'); + + $response->assertStatus(200) + ->assertHeader('Content-Type', 'application/javascript') + ->assertSee('Plausible script'); + + expect(Cache::get('plausible_script_js'))->toBe('console.log("Plausible script");'); +}); + +it('handles failed script fetch', function () { + Http::fake([ + 'https://plausible.io/js/script.js' => Http::response('', 500), + ]); + + $response = $this->get('/js/script.js'); + + $response->assertStatus(500) + ->assertSee('Error fetching Plausible script'); +}); + +it('rejects invalid event data', function () { + $response = $this->postJson('/api/event', [ + 'n' => 'pageview', + 'u' => 'invalid-url', + 'd' => 'example.com', + ]); + + $response->assertStatus(422) + ->assertJsonStructure(['status', 'errors']); +}); + +it('accepts valid event data and defers request', function () { + Http::fake(); + + $concurrencyMock = Mockery::mock('alias:Illuminate\Support\Facades\Concurrency'); + $concurrencyMock->shouldReceive('defer')->once()->andReturnUsing(function ($callback) { + $callback(); + }); + + $response = $this->postJson('/api/event', [ + 'n' => 'pageview', + 'u' => 'https://example.com/page', + 'd' => 'example.com', + ]); + + $response->assertStatus(200) + ->assertJson(['status' => 'queued']); + + Http::assertSent(fn ($request) => $request->url() === config('plausible-proxy.domain').'/api/event' && + $request['n'] === 'pageview' + ); + + Mockery::close(); +});