diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a186cd2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 + +[compose.yaml] +indent_size = 4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a98f09..48687cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,63 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.4.0] - 2026-04-28 + +### Added + +- **Visitor Tracker**: First-party visitor analytics for dev-tools.online, + powered by the `ghdj/laravel-visitor-tracker` package. + - New `/tools/visitor-tracker` public-facing tool page (and sitemap entry) + - `TrackVisitor` middleware wired into the `web` group so all web requests + are recorded + - Built-in dashboard at `/admin/visitor-tracker`, with env-driven auth: + token-based locally, `allow_unprotected` in production where Cloudflare + Access gates `/admin/*` at the edge + - Geolocation enabled (ip-api provider) for country breakdowns + - Test scaffolding: `phpunit.xml`, `tests/TestCase.php`, and unit/feature + tests for the Base64, CSV, Markdown, SQL, and YAML services/APIs + +### Operations + +- New `cron/migrate.php` entrypoint that bootstraps Laravel and runs + `migrate --force`, used by the OVH cron to apply package migrations on + shared hosting. +- Deploy workflow (`deploy.yml`) now strips `database/*.sqlite*` and + `bootstrap/cache/*.php` from the build before SFTP, so dev artifacts + never reach production and the server re-caches config/routes after + deploy. +- `.gitignore` excludes local SQLite databases and cached bootstrap files. +- Tests workflow (`tests.yml`) sets `VISITOR_TRACKER_DASHBOARD_ENABLED=false` + so the package's boot-time auth guard does not trip during CI. + +### Operator notes + +When deploying to a fresh environment, set in the server `.env`: + +- `VISITOR_TRACKER_ALLOW_UNPROTECTED=true` (or `VISITOR_TRACKER_TOKEN=...`) + — required, otherwise the package's service provider throws on boot. +- `DB_CONNECTION=sqlite` if no DB server is available. + +## [1.3.0] - 2025-12-15 + +### Added + +- **Sort Lines**: New text line manipulation tool + - Sort alphabetically (A-Z, Z-A) + - Natural sort for alphanumeric strings (file1, file2, file10) + - Numeric sort (ascending/descending) + - Sort by line length + - Reverse line order + - Remove duplicates (dedupe) + - Shuffle/randomize lines + - Options: case sensitive, trim whitespace, remove empty lines + +## [1.2.1] - 2025-12-15 + +### Fixed + +- Code Editor: Fix PHP parse error when loading the page (` 'tools.diff', 'icon' => 'diff', ], + [ + 'name' => 'Sort Lines', + 'description' => 'Sort, deduplicate, reverse, and shuffle text lines', + 'route' => 'tools.sort-lines', + 'icon' => 'sort', + ], [ 'name' => 'Visitor Tracker', 'description' => 'Server-side visitor analytics for Laravel applications', @@ -273,6 +279,11 @@ public function diff(): View return view('tools.diff'); } + public function sortLines(): View + { + return view('tools.sort-lines'); + } + public function visitorTracker(): View { $stats = app(\Ghdj\VisitorTracker\Services\StatisticsService::class); diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index 4e1851a..972ddb3 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -151,6 +151,11 @@ @break + @case('sort') + + + + @break @endswitch
diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index ca2ea7e..ec5b34c 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -126,7 +126,7 @@ class="absolute top-1 w-6 h-6 rounded-full shadow-lg transition-all duration-500 About Privacy GitHub - v1.2.0 + v1.4.0
diff --git a/resources/views/tools/code-editor.blade.php b/resources/views/tools/code-editor.blade.php index 731ebfa..09699b4 100644 --- a/resources/views/tools/code-editor.blade.php +++ b/resources/views/tools/code-editor.blade.php @@ -575,7 +575,7 @@ function getDefaultContent(language) { case 'css': return '/* Styles */\n'; case 'javascript': return '// JavaScript\n'; case 'json': return '{\n \n}'; - case 'php': return 'name('jwt'); Route::get('/timestamp', [ToolController::class, 'timestamp'])->name('timestamp'); Route::get('/diff', [ToolController::class, 'diff'])->name('diff'); + Route::get('/sort-lines', [ToolController::class, 'sortLines'])->name('sort-lines'); Route::get('/visitor-tracker', [ToolController::class, 'visitorTracker'])->name('visitor-tracker'); }); @@ -63,6 +64,7 @@ ['loc' => route('tools.jwt'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.timestamp'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.diff'), 'priority' => '0.8', 'changefreq' => 'monthly'], + ['loc' => route('tools.sort-lines'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.visitor-tracker'), 'priority' => '0.9', 'changefreq' => 'monthly'], ['loc' => route('about'), 'priority' => '0.5', 'changefreq' => 'monthly'], ['loc' => route('privacy'), 'priority' => '0.3', 'changefreq' => 'yearly'], diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php new file mode 100644 index 0000000..8364a84 --- /dev/null +++ b/tests/Feature/ExampleTest.php @@ -0,0 +1,19 @@ +get('/'); + + $response->assertStatus(200); + } +} diff --git a/tests/Feature/WebRoutesTest.php b/tests/Feature/WebRoutesTest.php index d99e52e..b4c1203 100644 --- a/tests/Feature/WebRoutesTest.php +++ b/tests/Feature/WebRoutesTest.php @@ -41,6 +41,7 @@ public function test_home_page_displays_all_tools(): void $response->assertSee('JWT Decoder'); $response->assertSee('Timestamp Converter'); $response->assertSee('Diff Checker'); + $response->assertSee('Sort Lines'); } public function test_home_page_has_tool_links(): void @@ -70,6 +71,7 @@ public function test_home_page_has_tool_links(): void $response->assertSee('href="' . route('tools.jwt') . '"', false); $response->assertSee('href="' . route('tools.timestamp') . '"', false); $response->assertSee('href="' . route('tools.diff') . '"', false); + $response->assertSee('href="' . route('tools.sort-lines') . '"', false); } public function test_csv_tool_page_loads(): void @@ -516,9 +518,30 @@ public function test_diff_tool_has_required_elements(): void $response->assertSee('Compare'); } + public function test_sort_lines_page_loads(): void + { + $response = $this->get('/tools/sort-lines'); + + $response->assertStatus(200); + $response->assertSee('Sort Lines'); + $response->assertSee('Sort, deduplicate, reverse, and shuffle text lines'); + } + + public function test_sort_lines_has_required_elements(): void + { + $response = $this->get('/tools/sort-lines'); + + $response->assertStatus(200); + $response->assertSee('Input Text'); + $response->assertSee('Sort Options'); + $response->assertSee('Sort A-Z'); + $response->assertSee('Remove Duplicates'); + $response->assertSee('Shuffle'); + } + public function test_all_pages_have_navigation(): void { - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp', '/tools/diff']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp', '/tools/diff', '/tools/sort-lines']; foreach ($pages as $page) { $response = $this->get($page); @@ -530,7 +553,7 @@ public function test_all_pages_have_navigation(): void public function test_all_pages_have_theme_toggle(): void { - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp', '/tools/diff']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp', '/tools/diff', '/tools/sort-lines']; foreach ($pages as $page) { $response = $this->get($page); @@ -543,7 +566,7 @@ public function test_all_pages_have_theme_toggle(): void public function test_all_pages_load_vite_assets(): void { // Code editor uses standalone template without Vite - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp', '/tools/diff']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp', '/tools/diff', '/tools/sort-lines']; foreach ($pages as $page) { $response = $this->get($page); @@ -556,7 +579,7 @@ public function test_all_pages_load_vite_assets(): void public function test_all_tool_pages_have_back_link(): void { // Code editor uses standalone template with home link instead of back - $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp', '/tools/diff']; + $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp', '/tools/diff', '/tools/sort-lines']; foreach ($toolPages as $page) { $response = $this->get($page); @@ -609,7 +632,7 @@ public function test_api_routes_reject_get_requests(): void public function test_pages_have_csrf_token(): void { - $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp', '/tools/diff']; + $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp', '/tools/diff', '/tools/sort-lines']; foreach ($pages as $page) { $response = $this->get($page); diff --git a/tests/Unit/ExampleTest.php b/tests/Unit/ExampleTest.php new file mode 100644 index 0000000..5773b0c --- /dev/null +++ b/tests/Unit/ExampleTest.php @@ -0,0 +1,16 @@ +assertTrue(true); + } +}