Files
deepseek beaf0e1f37 Toolvana: production-ready media tools platform (CodeIgniter 4)
- 134-tool registry with programmatic SEO (unique titles/H1/descriptions,
  JSON-LD graphs, sitemap index, canonical 301 enforcement via required filter)
- DB-backed job queue (SKIP LOCKED) with drivers: Ffmpeg, Images (GD),
  Pdf (qpdf/gs/poppler), Youtube (thumbnails), Qr (server-side PNG)
- Security: SSRF guard, MIME validation, rate limits, API-key auth,
  bcrypt admin login, security headers
- Admin panel: dashboard, tools/categories/guides CRUD, SEO audit,
  analytics, job inspector with retry, system health, feature flags
- Docker deployment (nginx + web/api FPM pools + scalable workers),
  PHPUnit suite (19 tests / 1139 assertions), PWA manifest + service worker
2026-08-23 07:10:30 +00:00

162 lines
7.4 KiB
PHP

<?php
use CodeIgniter\Router\RouteCollection;
/**
* ---------------------------------------------------------------------------------------------
* Toolvana routing map
* ---------------------------------------------------------------------------------------------
*
* URL architecture (strongest SEO structure):
*
* / homepage
* /{tool-slug} every tool at the root: /youtube-to-mp3, /jpg-to-png ...
* /tools searchable tool directory (+ /tools/category/{cat})
* /{category-slug} category landing pages: /youtube-tools, /video-tools ...
* /blog, /blog/{guide-slug} guides cluster
* /search crawlable search results page
* /privacy /terms ... legal pages
* /sitemap*.xml, /robots.txt SEO plumbing
* /api/v1/... public API (noindex)
* /admin/... admin panel (denied to robots)
*/
/** @var RouteCollection $routes */
$routes->setAutoRoute(false);
$routes->setDefaultNamespace('App\Controllers');
$routes->setDefaultController('Home');
$routes->setDefaultMethod('index');
// 404 override -> useful "Tool not found" page with popular tools.
$routes->set404Override(static function () {
return (new \App\Controllers\Errors())->notFound();
});
// Health probes (unfiltered, no session, no analytics).
$routes->get('/health', 'Health::index');
$routes->get('/ready', 'Health::ready');
// ---------------------------------------------------------------------
// SEO plumbing
// ---------------------------------------------------------------------
$routes->get('/robots.txt', 'Seo::robots');
$routes->get('/sitemap.xml', 'Seo::sitemapIndex');
$routes->get('/sitemap-tools-([0-9]+).xml', 'Seo::sitemapTools/$1');
$routes->get('/sitemap-guides.xml', 'Seo::sitemapGuides');
$routes->get('/sitemap-categories.xml', 'Seo::sitemapCategories');
$routes->get('/sitemap-pages.xml', 'Seo::sitemapPages');
$routes->get('/opensearch.xml', 'Seo::openSearch');
$routes->get('/manifest.webmanifest', 'Seo::manifest');
$routes->get('/service-worker.js', 'Seo::serviceWorker');
$routes->get('/og/(:segment).png', 'Seo::ogImage/$1'); // cached social cards
// ---------------------------------------------------------------------
// Public API (internal + v1). Noindex; blocked in robots.txt.
// ---------------------------------------------------------------------
$routes->group('api', ['namespace' => 'App\Controllers\Api'], static function ($routes) {
// Browser-facing endpoints used by tool pages.
$routes->post('jobs', 'Jobs::create');
$routes->get('jobs/(:segment)', 'Jobs::show/$1');
$routes->delete('jobs/(:segment)', 'Jobs::delete/$1');
$routes->post('youtube/info', 'Youtube::info');
$routes->post('events', 'Events::track'); // analytics beacon
$routes->get('suggest', 'SearchApi::suggest'); // search-as-you-type
// Versioned developer API (token auth).
$routes->group('v1', ['filter' => 'api-auth'], static function ($routes) {
$routes->get('tools', 'ToolsApi::index');
$routes->get('tools/(:segment)', 'ToolsApi::show/$1');
$routes->post('convert', 'Jobs::create');
$routes->get('jobs/(:segment)', 'Jobs::show/$1');
$routes->delete('jobs/(:segment)', 'Jobs::delete/$1');
});
});
// File upload endpoint for tool pages (multipart).
$routes->post('/upload', 'Media::upload');
$routes->post('/jobs/start', 'Media::startJob');
// Result download endpoint — temporary, never indexed.
$routes->get('/download/(:segment)', 'Media::download/$1');
$routes->get('/preview/(:segment).(:segment)', 'Media::preview/$1/$2');
// ---------------------------------------------------------------------
// Static / legal pages
// ---------------------------------------------------------------------
$routes->get('/privacy', 'Pages::privacy');
$routes->get('/terms', 'Pages::terms');
$routes->get('/cookies', 'Pages::cookies');
$routes->get('/dmca', 'Pages::dmca');
$routes->get('/contact', 'Pages::contact');
// ---------------------------------------------------------------------
// Search & directory
// ---------------------------------------------------------------------
$routes->get('/search', 'Search::index');
$routes->get('/tools', 'Directory::index');
$routes->get('/tools/popular', 'Directory::popular');
$routes->get('/tools/recent', 'Directory::recent');
$routes->get('/tools/a-z', 'Directory::az');
$routes->get('/tools/category/(:segment)', 'Directory::category/$1');
// ---------------------------------------------------------------------
// Guides cluster
// ---------------------------------------------------------------------
$routes->get('/blog', 'Guides::index');
$routes->get('/blog/rss.xml', 'Guides::rss');
$routes->get('/blog/(:segment)', 'Guides::show/$1');
// ---------------------------------------------------------------------
// Category landing pages: /youtube-tools, /video-tools, ...
// ---------------------------------------------------------------------
$routes->get('/([a-z0-9]+-tools)', 'Categories::show/$1');
// ---------------------------------------------------------------------
// Admin panel (session auth, CSRF protected, noindex)
// ---------------------------------------------------------------------
$routes->group('admin', ['namespace' => 'App\Controllers\Admin', 'filter' => 'admin-auth'], static function ($routes) {
$routes->get('/', 'Dashboard::index');
$routes->get('login', 'Auth::loginForm');
$routes->post('login', 'Auth::attempt');
$routes->get('logout', 'Auth::logout');
$routes->get('tools', 'ToolsAdmin::index');
$routes->get('tools/new', 'ToolsAdmin::create');
$routes->post('tools', 'ToolsAdmin::store');
$routes->get('tools/(:num)/edit', 'ToolsAdmin::edit/$1');
$routes->post('tools/(:num)', 'ToolsAdmin::update/$1');
$routes->post('tools/(:num)/delete', 'ToolsAdmin::delete/$1');
$routes->get('categories', 'CategoriesAdmin::index');
$routes->post('categories', 'CategoriesAdmin::store');
$routes->get('categories/(:num)/edit', 'CategoriesAdmin::edit/$1');
$routes->post('categories/(:num)', 'CategoriesAdmin::update/$1');
$routes->get('guides', 'GuidesAdmin::index');
$routes->get('guides/new', 'GuidesAdmin::create');
$routes->post('guides', 'GuidesAdmin::store');
$routes->get('guides/(:num)/edit', 'GuidesAdmin::edit/$1');
$routes->post('guides/(:num)', 'GuidesAdmin::update/$1');
$routes->post('guides/(:num)/delete', 'GuidesAdmin::delete/$1');
$routes->get('seo', 'SeoAdmin::index');
$routes->get('analytics', 'AnalyticsAdmin::index');
$routes->get('jobs', 'JobsAdmin::index');
$routes->post('jobs/(:segment)/retry', 'JobsAdmin::retry/$1');
$routes->get('system', 'SystemAdmin::index');
$routes->get('settings', 'SettingsAdmin::index');
$routes->post('settings', 'SettingsAdmin::save');
});
// ---------------------------------------------------------------------
// Homepage last but one...
// ---------------------------------------------------------------------
$routes->get('/', 'Home::index');
// ---------------------------------------------------------------------
// ...and the catch-all: every active tool slug lives at the root.
// Registered last so concrete routes always win. Uppercase and
// trailing-slash variants are canonicalized by the SeoFilter.
// ---------------------------------------------------------------------
$routes->get('/([a-z0-9-]+)', 'Tools::show/$1');