fixes and cleanup

This commit is contained in:
Renan Bernordi 2026-07-24 18:55:03 -03:00
parent 65ff1bd88f
commit a2fda5cfa7
15 changed files with 78 additions and 72 deletions

11
.dockerignore Normal file
View file

@ -0,0 +1,11 @@
.git
.github
.env
.env.backup
.env.production
storage/app/*
storage/logs/*
storage/framework/cache/data/*
storage/framework/sessions/*
storage/framework/views/*
node_modules

View file

@ -6,14 +6,10 @@ APP_DEBUG=false
APP_URL=http://localhost
APP_LOCALE=pt-br
DNS_SERVERS=1.1.1.1,8.8.8.8
BROWSER_WS_ENDPOINT=ws://marreta_browser:9222
BROWSER_TIMEOUT_MS=30000
DISABLE_CACHE=false
CLEANUP_DAYS=7
LOG_DAYS_TO_KEEP=7
# Proxy (optional)
# PROXY_URL=

2
.gitignore vendored
View file

@ -22,5 +22,3 @@
Homestead.json
Homestead.yaml
Thumbs.db
/cache
/database

View file

@ -7,7 +7,6 @@ WORKDIR /var/www/html
COPY . .
RUN composer install --no-interaction --optimize-autoloader --no-dev \
&& php artisan config:cache \
&& php artisan route:cache \
&& php artisan view:cache

View file

@ -45,8 +45,8 @@ Agora modifique com suas preferencias:
- `APP_NAME`: Nome do seu Marreta
- `APP_DESCRIPTION`: Conta pra que serve
- `APP_URL`: Onde vai rodar, endereço completo com `https://`. Se você alterar a porta no docker-compose (ex: 8080:80), você também deve incluir a porta no SITE_URL (ex: https://seusite:8080)
- `APP_LANGUAGE`: pt-br (Português Brasil), en (Inglês), es (Espanhol) ou de-de (Alemão), ru-ru (Russo)
- `APP_URL`: Onde vai rodar, endereço completo com `https://`. Se você alterar a porta no docker-compose (ex: 8080:80), você também deve incluir a porta no APP_URL (ex: https://seusite:8080)
- `APP_LOCALE`: pt-br (Português Brasil), en (Inglês), es (Espanhol) ou de-de (Alemão), ru-ru (Russo)
- `ADMIN_EMAIL`: admin@marreta.local
- `ADMIN_PASSWORD`: password

View file

@ -4,12 +4,12 @@ declare(strict_types=1);
namespace App\Http\Controllers\Api;
use App\Enums\MarretaError;
use App\Exceptions\MarretaException;
use App\Http\Controllers\Controller;
use App\Services\ProxyService;
use App\Services\UrlNormalizer;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
final class ProxyController extends Controller
@ -19,19 +19,17 @@ final class ProxyController extends Controller
private readonly UrlNormalizer $urlNormalizer,
) {}
public function __invoke(Request $request, string $url): JsonResponse|RedirectResponse
public function __invoke(Request $request, string $url): JsonResponse
{
$processed = $this->urlNormalizer->processRouteUrl($url, $request->query->all());
if (! $processed['valid']) {
return redirect('/?message=INVALID_URL');
}
if ($processed['needs_redirect']) {
return redirect(config('marreta.site_url').'/api/'.$processed['url']);
}
$headers = ['Access-Control-Allow-Origin' => '*'];
try {
$processed = $this->urlNormalizer->processRouteUrl($url, $request->query->all());
if (! $processed['valid']) {
throw new MarretaException(MarretaError::InvalidUrl);
}
$this->proxyService->analyze($processed['url']);
$displayUrl = preg_replace('#^https?://#', '', $processed['url']);
@ -39,15 +37,9 @@ final class ProxyController extends Controller
return response()->json([
'status' => 200,
'url' => config('marreta.site_url').'/p/'.$displayUrl,
], 200, [
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Methods' => 'GET',
]);
], 200, $headers + ['Access-Control-Allow-Methods' => 'GET']);
} catch (MarretaException $e) {
$headers = [
'Access-Control-Allow-Origin' => '*',
'X-Error-Type' => $e->errorType(),
];
$headers['X-Error-Type'] = $e->errorType();
if ($e->additionalInfo()) {
$headers['X-Error-Info'] = $e->additionalInfo();
@ -56,7 +48,7 @@ final class ProxyController extends Controller
return response()->json([
'status' => $e->error()->httpCode(),
'error' => [
'type' => $e->errorType(),
'code' => $e->errorType(),
'message' => $e->getMessage(),
'details' => $e->additionalInfo() ?: null,
],

View file

@ -4,14 +4,13 @@ declare(strict_types=1);
namespace App\Http\Controllers;
use App\Services\MarretaCacheService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
final class HomeController extends Controller
{
public function __invoke(Request $request, MarretaCacheService $cache): View|RedirectResponse
public function __invoke(Request $request): View|RedirectResponse
{
app()->setLocale(config('marreta.language'));
@ -47,7 +46,6 @@ final class HomeController extends Controller
'message' => $message,
'message_type' => $messageType,
'url' => $url,
'cache_count' => $cache->getCacheFileCount(),
'site_name' => config('marreta.site_name'),
'site_description' => config('marreta.site_description'),
'site_url' => config('marreta.site_url'),

View file

@ -35,6 +35,10 @@ final class ProxyService
*/
public function analyze(string $url): string
{
if (! preg_match('#^https?://#', $url)) {
$url = 'https://'.$url;
}
$host = parse_url($url, PHP_URL_HOST);
if (! $host) {
throw new MarretaException(MarretaError::InvalidUrl);

View file

@ -41,7 +41,7 @@ final class UrlNormalizer
$url = preg_replace('#/+#', '/', $url);
$url = 'https://'.$url;
if (filter_var($url, FILTER_VALIDATE_URL) === false) {
if (! $this->isValidUrl($url)) {
return ['valid' => false, 'url' => '', 'needs_redirect' => false];
}
@ -66,7 +66,7 @@ final class UrlNormalizer
{
$url = trim($url);
if (filter_var($url, FILTER_VALIDATE_URL) === false) {
if (! $this->isValidUrl($url)) {
return '';
}
@ -85,8 +85,21 @@ final class UrlNormalizer
$cleaned .= isset($parts['query']) ? '?'.$parts['query'] : '';
$cleaned .= isset($parts['fragment']) ? '#'.$parts['fragment'] : '';
$cleaned = preg_replace('/[\x00-\x1F\x7F]/', '', $cleaned);
return preg_replace('/[\x00-\x1F\x7F]/', '', $cleaned);
}
return (string) filter_var($cleaned, FILTER_SANITIZE_URL);
/**
* Validate a URL, tolerating non-ASCII characters that FILTER_VALIDATE_URL rejects
* (e.g. accented letters in Wikipedia-style paths) by percent-encoding them first.
*/
private function isValidUrl(string $url): bool
{
$ascii = preg_replace_callback(
'/[^\x21-\x7E]/',
fn (array $m): string => rawurlencode($m[0]),
$url,
);
return filter_var($ascii, FILTER_VALIDATE_URL) !== false;
}
}

View file

@ -41,13 +41,11 @@
"composer install",
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"",
"@php artisan key:generate",
"@php artisan migrate --force",
"npm install",
"npm run build"
"@php artisan migrate --force"
],
"dev": [
"Composer\\Config::disableProcessTimeout",
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1 --timeout=0\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite --kill-others"
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185\" \"php artisan serve\" \"php artisan queue:listen --tries=1 --timeout=0\" \"php artisan pail --timeout=0\" --names=server,queue,logs --kill-others"
],
"test": [
"@php artisan config:clear --ansi",

26
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "43bc61ffbf39fedb6f934290bc5fadf6",
"content-hash": "4b8ce4c8ae1ef4c82a90d5d4ea094c21",
"packages": [
{
"name": "anourvalar/eloquent-serialize",
@ -2211,16 +2211,16 @@
},
{
"name": "kirschbaum-development/eloquent-power-joins",
"version": "4.3.2",
"version": "4.3.3",
"source": {
"type": "git",
"url": "https://github.com/kirschbaum-development/eloquent-power-joins.git",
"reference": "33c189bd51a510c1ceba67222395ead08a29863a"
"reference": "c609dbbe4ad2051b667e937f1ab554067519d64b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/kirschbaum-development/eloquent-power-joins/zipball/33c189bd51a510c1ceba67222395ead08a29863a",
"reference": "33c189bd51a510c1ceba67222395ead08a29863a",
"url": "https://api.github.com/repos/kirschbaum-development/eloquent-power-joins/zipball/c609dbbe4ad2051b667e937f1ab554067519d64b",
"reference": "c609dbbe4ad2051b667e937f1ab554067519d64b",
"shasum": ""
},
"require": {
@ -2268,22 +2268,22 @@
],
"support": {
"issues": "https://github.com/kirschbaum-development/eloquent-power-joins/issues",
"source": "https://github.com/kirschbaum-development/eloquent-power-joins/tree/4.3.2"
"source": "https://github.com/kirschbaum-development/eloquent-power-joins/tree/4.3.3"
},
"time": "2026-05-28T20:35:55+00:00"
"time": "2026-07-23T11:41:37+00:00"
},
{
"name": "laravel/framework",
"version": "v13.21.1",
"version": "v13.22.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "303b5f8dc899f89e8c38c350b639b8fbd193ed16"
"reference": "d354afa45334cdac523fbc015f203a78d7e2f5c6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/303b5f8dc899f89e8c38c350b639b8fbd193ed16",
"reference": "303b5f8dc899f89e8c38c350b639b8fbd193ed16",
"url": "https://api.github.com/repos/laravel/framework/zipball/d354afa45334cdac523fbc015f203a78d7e2f5c6",
"reference": "d354afa45334cdac523fbc015f203a78d7e2f5c6",
"shasum": ""
},
"require": {
@ -2497,7 +2497,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2026-07-21T14:27:35+00:00"
"time": "2026-07-24T20:38:48+00:00"
},
{
"name": "laravel/prompts",
@ -10518,7 +10518,7 @@
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
"php": "^8.3"
"php": "^8.4"
},
"platform-dev": {},
"plugin-api-version": "2.9.0"

View file

@ -35,7 +35,7 @@ return [
'sqlite' => [
'driver' => 'sqlite',
'url' => env('DB_URL'),
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'database' => env('DB_DATABASE', storage_path('app/database.sqlite')),
'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
'busy_timeout' => null,

View file

@ -28,21 +28,18 @@ return [
|--------------------------------------------------------------------------
*/
'dns_servers' => array_values(array_filter(explode(',', (string) env('DNS_SERVERS', '1.1.1.1,8.8.8.8')))),
'proxy_url' => env('PROXY_URL'),
/*
|--------------------------------------------------------------------------
| Browser engine (Lightpanda via CDP)
| BROWSER_WS_ENDPOINT takes precedence; legacy SELENIUM_HOST is honoured
| as a deprecated fallback (translated into a ws:// endpoint).
|--------------------------------------------------------------------------
*/
'browser' => [
'ws_endpoint' => env('BROWSER_WS_ENDPOINT')
?: (env('SELENIUM_HOST') ? 'ws://'.preg_replace('#^https?://#', '', (string) env('SELENIUM_HOST')) : 'ws://localhost:9222'),
// A trailing path is required: the underlying WebSocket client rejects a URI with no path.
// Left null when unset — the browser fetch strategy is skipped in that case.
'ws_endpoint' => env('BROWSER_WS_ENDPOINT') ? rtrim((string) env('BROWSER_WS_ENDPOINT'), '/').'/' : null,
'timeout_ms' => (int) env('BROWSER_TIMEOUT_MS', 30000),
],
@ -54,18 +51,10 @@ return [
'cache' => [
'disabled' => (bool) env('DISABLE_CACHE', false),
'directory' => env('CACHE_DIR', base_path('cache')),
'directory' => storage_path('app/marreta-cache'),
'compress_level' => 3,
],
/*
|--------------------------------------------------------------------------
| Maintenance
|--------------------------------------------------------------------------
*/
'cleanup_days' => (int) env('CLEANUP_DAYS', 0),
/*
|--------------------------------------------------------------------------
| Admin (Filament)

View file

@ -5,9 +5,6 @@
colors="true"
>
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory>tests/Feature</directory>
</testsuite>

View file

@ -0,0 +1,11 @@
<x-filament-panels::page>
<form wire:submit="save">
{{ $this->form }}
<div class="mt-6">
<x-filament::button type="submit">
{{ __('admin.save') }}
</x-filament::button>
</div>
</form>
</x-filament-panels::page>