Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f0c4ad4a8 | ||
|
|
d42263023f
|
||
|
|
7af348cbb4
|
||
|
|
6d9206df80
|
||
|
|
ded8967551
|
||
|
|
e3a0aa54e9
|
||
|
|
16257e3383 | ||
|
|
a97baf5921
|
No files matched your search
@@ -1,52 +1,59 @@
|
|||||||
name: run-tests
|
name: CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
paths:
|
paths:
|
||||||
- '**.php'
|
- '**.php'
|
||||||
- '.github/workflows/run-tests.yml'
|
- '.gitea/workflows/ci.yml'
|
||||||
- 'phpunit.xml.dist'
|
- 'phpunit.xml.dist'
|
||||||
|
- 'phpstan.neon.dist'
|
||||||
|
- 'phpstan-baseline.neon'
|
||||||
- 'composer.json'
|
- 'composer.json'
|
||||||
- 'composer.lock'
|
- 'composer.lock'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
phpstan:
|
||||||
runs-on: ${{ matrix.os }}
|
name: PHPStan
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: '8.5'
|
||||||
|
coverage: none
|
||||||
|
|
||||||
|
- name: Install composer dependencies
|
||||||
|
run: composer install --prefer-dist --no-interaction --no-progress
|
||||||
|
|
||||||
|
- name: Run PHPStan
|
||||||
|
run: vendor/bin/phpstan --error-format=github
|
||||||
|
|
||||||
|
pest:
|
||||||
|
name: Pest (PHP ${{ matrix.php }}, Laravel ${{ matrix.laravel }}, ${{ matrix.stability }})
|
||||||
|
needs: phpstan
|
||||||
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 5
|
timeout-minutes: 5
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest]
|
php: ['8.3', '8.4', '8.5']
|
||||||
php: [8.3, 8.4]
|
laravel: ['12.*', '13.*']
|
||||||
laravel: [11.*, 12.*, 13.*]
|
|
||||||
stability: [prefer-lowest, prefer-stable]
|
stability: [prefer-lowest, prefer-stable]
|
||||||
include:
|
include:
|
||||||
- laravel: 11.*
|
- laravel: '12.*'
|
||||||
php: 8.3
|
testbench: '10.*'
|
||||||
testbench: 9.*
|
- laravel: '13.*'
|
||||||
carbon: ^2.63
|
testbench: '11.*'
|
||||||
- laravel: 11.*
|
|
||||||
php: 8.4
|
|
||||||
testbench: 9.*
|
|
||||||
carbon: ^2.63
|
|
||||||
- laravel: 12.*
|
|
||||||
php: 8.3
|
|
||||||
testbench: 10.*
|
|
||||||
carbon: ^3.8.4
|
|
||||||
- laravel: 12.*
|
|
||||||
php: 8.4
|
|
||||||
testbench: 10.*
|
|
||||||
carbon: ^3.8.4
|
|
||||||
- laravel: 13.*
|
|
||||||
php: 8.3
|
|
||||||
testbench: 11.*
|
|
||||||
carbon: ^3.8.4
|
|
||||||
- laravel: 13.*
|
|
||||||
php: 8.4
|
|
||||||
testbench: 11.*
|
|
||||||
carbon: ^3.8.4
|
|
||||||
|
|
||||||
name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -69,12 +76,12 @@ jobs:
|
|||||||
composer require \
|
composer require \
|
||||||
"laravel/framework:${{ matrix.laravel }}" \
|
"laravel/framework:${{ matrix.laravel }}" \
|
||||||
"orchestra/testbench:${{ matrix.testbench }}" \
|
"orchestra/testbench:${{ matrix.testbench }}" \
|
||||||
"nesbot/carbon:${{ matrix.carbon }}" \
|
"nesbot/carbon:^3.8.4" \
|
||||||
--no-interaction --no-update
|
--dev --no-interaction --no-update
|
||||||
|
|
||||||
composer update --${{ matrix.stability }} --prefer-dist --no-interaction
|
composer update --${{ matrix.stability }} --prefer-dist --no-interaction
|
||||||
|
|
||||||
- name: List Installed Dependencies
|
- name: List installed dependencies
|
||||||
run: composer show -D
|
run: composer show -D
|
||||||
|
|
||||||
- name: Execute tests
|
- name: Execute tests
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,7 +1,7 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: Ask a question or request a feature
|
- name: Ask a question or request a feature
|
||||||
url: https://git.mattitja.se/Github/laravel-plausible-proxy/issues
|
url: https://git.mattitja.se/559342-9607/laravel-plausible-proxy/issues
|
||||||
about: Open an issue in the repository
|
about: Open an issue in the repository
|
||||||
- name: Report a security issue
|
- name: Report a security issue
|
||||||
url: mailto:hej@mattitja.se
|
url: mailto:hej@mattitja.se
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
name: PHPStan
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- '**.php'
|
|
||||||
- 'phpstan.neon.dist'
|
|
||||||
- '.github/workflows/phpstan.yml'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
phpstan:
|
|
||||||
name: phpstan
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 5
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
php-version: '8.4'
|
|
||||||
coverage: none
|
|
||||||
|
|
||||||
- name: Install composer dependencies
|
|
||||||
uses: ramsey/composer-install@v3
|
|
||||||
|
|
||||||
- name: Run PHPStan
|
|
||||||
run: ./vendor/bin/phpstan --error-format=github
|
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
# laravel-plausible-proxy
|
# laravel-plausible-proxy
|
||||||
|
|
||||||
[](https://packagist.org/packages/mattitja/laravel-plausible-proxy)
|
[](https://packagist.org/packages/mattitja/laravel-plausible-proxy)
|
||||||
[](https://git.mattitja.se/Github/laravel-plausible-proxy/actions?workflow=run-tests.yml)
|
[](https://git.mattitja.se/559342-9607/laravel-plausible-proxy/actions?workflow=ci.yml)
|
||||||
[](https://git.mattitja.se/Github/laravel-plausible-proxy/actions?workflow=fix-php-code-style-issues.yml)
|
[](https://git.mattitja.se/559342-9607/laravel-plausible-proxy/actions?workflow=fix-php-code-style-issues.yml)
|
||||||
[](https://packagist.org/packages/mattitja/laravel-plausible-proxy)
|
[](https://packagist.org/packages/mattitja/laravel-plausible-proxy)
|
||||||
|
|
||||||
A Laravel package for proxying Plausible Analytics, improving performance and privacy.
|
A Laravel package for proxying Plausible Analytics, improving performance and privacy.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Laravel 11, 12 or 13
|
- Laravel 12 or 13
|
||||||
- PHP 8.3 or 8.4
|
- PHP 8.3, 8.4 or 8.5
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -7,7 +7,7 @@
|
|||||||
"laravel-plausible-proxy",
|
"laravel-plausible-proxy",
|
||||||
"plausible"
|
"plausible"
|
||||||
],
|
],
|
||||||
"homepage": "https://git.mattitja.se/Github/laravel-plausible-proxy",
|
"homepage": "https://git.mattitja.se/559342-9607/laravel-plausible-proxy",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
@@ -19,13 +19,13 @@
|
|||||||
"require": {
|
"require": {
|
||||||
"php": "^8.3",
|
"php": "^8.3",
|
||||||
"spatie/laravel-package-tools": "^1.16",
|
"spatie/laravel-package-tools": "^1.16",
|
||||||
"illuminate/contracts": "^11.0||^12.0||^13.0"
|
"illuminate/contracts": "^12.0||^13.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"laravel/pint": "^1.14",
|
"laravel/pint": "^1.14",
|
||||||
"nunomaduro/collision": "^8.1",
|
"nunomaduro/collision": "^8.1",
|
||||||
"larastan/larastan": "^3.0",
|
"larastan/larastan": "^3.0",
|
||||||
"orchestra/testbench": "^9.0||^10.0||^11.0",
|
"orchestra/testbench": "^10.0||^11.0",
|
||||||
"pestphp/pest": "^3.0||^4.0",
|
"pestphp/pest": "^3.0||^4.0",
|
||||||
"pestphp/pest-plugin-arch": "^3.0||^4.0",
|
"pestphp/pest-plugin-arch": "^3.0||^4.0",
|
||||||
"pestphp/pest-plugin-laravel": "^3.0||^4.0",
|
"pestphp/pest-plugin-laravel": "^3.0||^4.0",
|
||||||
|
|||||||
@@ -2,18 +2,30 @@
|
|||||||
|
|
||||||
namespace Mattitja\LaravelPlausibleProxy\Http\Controllers;
|
namespace Mattitja\LaravelPlausibleProxy\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Client\ConnectionException;
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
use Mattitja\LaravelPlausibleProxy\Http\Requests\PlausibleEventRequest;
|
use Mattitja\LaravelPlausibleProxy\Http\Requests\PlausibleEventRequest;
|
||||||
|
|
||||||
class PlausibleEventApiController
|
class PlausibleEventApiController
|
||||||
{
|
{
|
||||||
public function __invoke(PlausibleEventRequest $request): Response
|
public function __invoke(PlausibleEventRequest $request): Response
|
||||||
{
|
{
|
||||||
$upstream = Http::withHeaders([
|
$url = rtrim((string) config('plausible-proxy.domain'), '/').'/api/event';
|
||||||
'User-Agent' => (string) $request->userAgent(),
|
|
||||||
'X-Forwarded-For' => (string) $request->ip(),
|
try {
|
||||||
])->post(config('plausible-proxy.domain').'/api/event', $request->all());
|
$upstream = Http::timeout(5)
|
||||||
|
->withHeaders([
|
||||||
|
'User-Agent' => (string) $request->userAgent(),
|
||||||
|
'X-Forwarded-For' => (string) $request->ip(),
|
||||||
|
])
|
||||||
|
->post($url, $request->all());
|
||||||
|
} catch (ConnectionException $e) {
|
||||||
|
Log::error('Failed to forward Plausible event: '.$e->getMessage());
|
||||||
|
|
||||||
|
return response('Bad Gateway', 502);
|
||||||
|
}
|
||||||
|
|
||||||
return response($upstream->body(), $upstream->status())
|
return response($upstream->body(), $upstream->status())
|
||||||
->header('Content-Type', $upstream->header('Content-Type') ?: 'text/plain');
|
->header('Content-Type', $upstream->header('Content-Type') ?: 'text/plain');
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
namespace Mattitja\LaravelPlausibleProxy\Http\Requests;
|
namespace Mattitja\LaravelPlausibleProxy\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Validation\Validator;
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Http\Exceptions\HttpResponseException;
|
||||||
|
|
||||||
class PlausibleEventRequest extends FormRequest
|
class PlausibleEventRequest extends FormRequest
|
||||||
{
|
{
|
||||||
@@ -11,6 +13,32 @@ class PlausibleEventRequest extends FormRequest
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Plausible tracker sends its JSON payload with a text/plain content
|
||||||
|
* type, which Laravel does not parse automatically.
|
||||||
|
*/
|
||||||
|
protected function prepareForValidation(): void
|
||||||
|
{
|
||||||
|
if ($this->isJson()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$payload = json_decode($this->getContent(), true);
|
||||||
|
|
||||||
|
if (is_array($payload)) {
|
||||||
|
$this->merge($payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function failedValidation(Validator $validator): void
|
||||||
|
{
|
||||||
|
if (! $this->expectsJson()) {
|
||||||
|
throw new HttpResponseException(response('Bad Request', 400));
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::failedValidation($validator);
|
||||||
|
}
|
||||||
|
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Http\Client\ConnectionException;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
it('fetches plausible script and caches it', function () {
|
it('fetches plausible script and caches it', function () {
|
||||||
@@ -145,3 +146,56 @@ it('rejects invalid props', function () {
|
|||||||
'p' => 'not-json',
|
'p' => 'not-json',
|
||||||
])->assertStatus(422)->assertJsonValidationErrors(['p']);
|
])->assertStatus(422)->assertJsonValidationErrors(['p']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('accepts events sent as text/plain by the plausible tracker', function () {
|
||||||
|
Http::fake([
|
||||||
|
'https://plausible.io/api/event' => Http::response('ok', 202),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->call('POST', '/api/event', [], [], [], ['CONTENT_TYPE' => 'text/plain'], json_encode([
|
||||||
|
'n' => 'pageview',
|
||||||
|
'u' => 'https://example.com/page',
|
||||||
|
'd' => 'example.com',
|
||||||
|
'r' => null,
|
||||||
|
]))->assertStatus(202);
|
||||||
|
|
||||||
|
Http::assertSent(fn ($request) => $request->url() === 'https://plausible.io/api/event'
|
||||||
|
&& $request['n'] === 'pageview'
|
||||||
|
&& $request['u'] === 'https://example.com/page'
|
||||||
|
&& $request['d'] === 'example.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('responds with 400 for invalid text/plain events', function () {
|
||||||
|
Http::fake();
|
||||||
|
|
||||||
|
$this->call('POST', '/api/event', [], [], [], ['CONTENT_TYPE' => 'text/plain'], 'not-json')
|
||||||
|
->assertStatus(400);
|
||||||
|
|
||||||
|
Http::assertNothingSent();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles a trailing slash in the domain when forwarding events', function () {
|
||||||
|
config()->set('plausible-proxy.domain', 'https://plausible.example.com/');
|
||||||
|
|
||||||
|
Http::fake([
|
||||||
|
'https://plausible.example.com/api/event' => Http::response('ok', 202),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->postJson('/api/event', [
|
||||||
|
'n' => 'pageview',
|
||||||
|
'u' => 'https://example.com/page',
|
||||||
|
'd' => 'example.com',
|
||||||
|
])->assertStatus(202);
|
||||||
|
|
||||||
|
Http::assertSent(fn ($request) => $request->url() === 'https://plausible.example.com/api/event');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('responds with 502 when plausible is unreachable', function () {
|
||||||
|
Http::fake(fn () => throw new ConnectionException('Connection refused'));
|
||||||
|
|
||||||
|
$this->postJson('/api/event', [
|
||||||
|
'n' => 'pageview',
|
||||||
|
'u' => 'https://example.com/page',
|
||||||
|
'd' => 'example.com',
|
||||||
|
])->assertStatus(502);
|
||||||
|
});
|
||||||
Reference in new issue
Block a user