From e5ca170b4c983da647317a7a5fe13c8280251d1f Mon Sep 17 00:00:00 2001 From: Yury Pikhtarev Date: Mon, 23 Jun 2025 15:46:49 +0400 Subject: [PATCH] chore!: mark codebase as fully broken BREAKING CHANGE: The current codebase is in a non-functional state and requires significant refactoring before it can be used. Notes: - Core functionality is currently broken - Dependencies may be missing or incompatible - Configuration files may need updates - Database migrations may be incomplete - Legacy code integration issues present - Requires comprehensive testing and fixes before deployment This commit serves as a checkpoint to document the current broken state before beginning major refactoring efforts. --- README.md | 137 ++++++++++ app/Http/Controllers/Api/UserController.php | 170 ++++++++++++ app/Http/Controllers/Controller.php | 94 +++++++ .../Controllers/Web/HelloWorldController.php | 153 +++++++++++ app/Http/Controllers/Web/LegacyController.php | 219 ++++++++++++++++ app/Http/Requests/FormRequest.php | 116 +++++++++ app/Http/Requests/RegisterUserRequest.php | 120 +++++++++ app/Http/Routing/Router.php | 223 ++++++++++++++++ app/Listeners/SendWelcomeEmail.php | 41 +++ app/Listeners/UpdateUserStatistics.php | 32 +++ app/Models/Model.php | 223 ++++++++++++++++ app/Models/Torrent.php | 109 ++++++++ app/Models/User.php | 139 ++++++++++ app/Providers/AppServiceProvider.php | 66 +++++ app/Providers/ConsoleServiceProvider.php | 76 ++++++ app/Providers/EventServiceProvider.php | 64 +++++ app/Providers/RouteServiceProvider.php | 123 +++++++++ app/Providers/ServiceProvider.php | 17 ++ app/Services/Tracker/TorrentService.php | 244 ++++++++++++++++++ app/Services/User/UserService.php | 196 ++++++++++++++ app/Services/UserService.php | 42 +++ app/helpers.php | 243 +++++++++++++++++ bootstrap/app.php | 41 +++ bootstrap/container.php | 115 +++++++++ config/app.php | 77 ++++++ config/auth.php | 92 +++++++ config/cache.php | 77 ++++++ config/database.php | 86 ++++++ config/filesystems.php | 71 +++++ config/services.php | 41 +-- config/tracker.php | 91 +++++++ database/factories/UserFactory.php | 81 ++++++ database/seeders/DatabaseSeeder.php | 34 +++ library/attach_mod/displaying.php | 4 +- library/config.php | 2 +- {admin => public/admin}/admin_attach_cp.php | 0 {admin => public/admin}/admin_attachments.php | 0 {admin => public/admin}/admin_board.php | 0 .../admin}/admin_bt_forum_cfg.php | 0 {admin => public/admin}/admin_cron.php | 0 {admin => public/admin}/admin_disallow.php | 0 {admin => public/admin}/admin_extensions.php | 0 {admin => public/admin}/admin_forum_prune.php | 0 {admin => public/admin}/admin_forumauth.php | 0 .../admin}/admin_forumauth_list.php | 0 {admin => public/admin}/admin_forums.php | 0 {admin => public/admin}/admin_groups.php | 0 {admin => public/admin}/admin_log.php | 0 {admin => public/admin}/admin_mass_email.php | 0 {admin => public/admin}/admin_migrations.php | 0 {admin => public/admin}/admin_phpinfo.php | 0 {admin => public/admin}/admin_ranks.php | 0 .../admin}/admin_rebuild_search.php | 0 {admin => public/admin}/admin_robots.php | 0 {admin => public/admin}/admin_sitemap.php | 0 {admin => public/admin}/admin_smilies.php | 0 {admin => public/admin}/admin_terms.php | 0 {admin => public/admin}/admin_ug_auth.php | 0 {admin => public/admin}/admin_user_ban.php | 0 {admin => public/admin}/admin_user_search.php | 0 {admin => public/admin}/admin_words.php | 0 {admin => public/admin}/index.php | 0 {admin => public/admin}/pagestart.php | 0 {admin => public/admin}/stats/tr_stats.php | 0 {admin => public/admin}/stats/tracker.php | 0 public/index.php | 37 +++ resources/views/hello.php | 139 ++++++++++ routes/admin.php | 56 ++++ routes/api.php | 49 ++++ routes/web.php | 80 ++++++ 70 files changed, 3999 insertions(+), 21 deletions(-) create mode 100644 app/Http/Controllers/Api/UserController.php create mode 100644 app/Http/Controllers/Controller.php create mode 100644 app/Http/Controllers/Web/HelloWorldController.php create mode 100644 app/Http/Controllers/Web/LegacyController.php create mode 100644 app/Http/Requests/FormRequest.php create mode 100644 app/Http/Requests/RegisterUserRequest.php create mode 100644 app/Http/Routing/Router.php create mode 100644 app/Listeners/SendWelcomeEmail.php create mode 100644 app/Listeners/UpdateUserStatistics.php create mode 100644 app/Models/Model.php create mode 100644 app/Models/Torrent.php create mode 100644 app/Models/User.php create mode 100644 app/Providers/AppServiceProvider.php create mode 100644 app/Providers/ConsoleServiceProvider.php create mode 100644 app/Providers/EventServiceProvider.php create mode 100644 app/Providers/RouteServiceProvider.php create mode 100644 app/Providers/ServiceProvider.php create mode 100644 app/Services/Tracker/TorrentService.php create mode 100644 app/Services/User/UserService.php create mode 100644 app/Services/UserService.php create mode 100644 app/helpers.php create mode 100644 bootstrap/app.php create mode 100644 bootstrap/container.php create mode 100644 config/app.php create mode 100644 config/auth.php create mode 100644 config/cache.php create mode 100644 config/database.php create mode 100644 config/filesystems.php create mode 100644 config/tracker.php create mode 100644 database/factories/UserFactory.php create mode 100644 database/seeders/DatabaseSeeder.php rename {admin => public/admin}/admin_attach_cp.php (100%) rename {admin => public/admin}/admin_attachments.php (100%) rename {admin => public/admin}/admin_board.php (100%) rename {admin => public/admin}/admin_bt_forum_cfg.php (100%) rename {admin => public/admin}/admin_cron.php (100%) rename {admin => public/admin}/admin_disallow.php (100%) rename {admin => public/admin}/admin_extensions.php (100%) rename {admin => public/admin}/admin_forum_prune.php (100%) rename {admin => public/admin}/admin_forumauth.php (100%) rename {admin => public/admin}/admin_forumauth_list.php (100%) rename {admin => public/admin}/admin_forums.php (100%) rename {admin => public/admin}/admin_groups.php (100%) rename {admin => public/admin}/admin_log.php (100%) rename {admin => public/admin}/admin_mass_email.php (100%) rename {admin => public/admin}/admin_migrations.php (100%) rename {admin => public/admin}/admin_phpinfo.php (100%) rename {admin => public/admin}/admin_ranks.php (100%) rename {admin => public/admin}/admin_rebuild_search.php (100%) rename {admin => public/admin}/admin_robots.php (100%) rename {admin => public/admin}/admin_sitemap.php (100%) rename {admin => public/admin}/admin_smilies.php (100%) rename {admin => public/admin}/admin_terms.php (100%) rename {admin => public/admin}/admin_ug_auth.php (100%) rename {admin => public/admin}/admin_user_ban.php (100%) rename {admin => public/admin}/admin_user_search.php (100%) rename {admin => public/admin}/admin_words.php (100%) rename {admin => public/admin}/index.php (100%) rename {admin => public/admin}/pagestart.php (100%) rename {admin => public/admin}/stats/tr_stats.php (100%) rename {admin => public/admin}/stats/tracker.php (100%) create mode 100644 public/index.php create mode 100644 resources/views/hello.php create mode 100644 routes/admin.php create mode 100644 routes/api.php create mode 100644 routes/web.php diff --git a/README.md b/README.md index 5e7a13049..989eaa4aa 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,143 @@ TorrentPier includes a comprehensive testing suite built with **Pest PHP**. Run For detailed testing documentation, see [tests/README.md](tests/README.md). +## ๐Ÿ–ฅ๏ธ CLI Usage + +TorrentPier 3.0 includes a Laravel-style CLI tool called **Dexter** for managing your application: + +```shell +# Basic usage +php dexter # Shows available commands +php dexter info # System information +php dexter cache:clear # Clear caches +php dexter migrate # Run database migrations +php dexter help # Command help +``` + +## ๐ŸฆŒ Laravel Herd Configuration + +If you're using [Laravel Herd](https://herd.laravel.com) for local development, you'll need special configuration to properly serve the legacy `/admin/` and `/bt/` directories. By default, Herd routes all requests through the modern front controller, but these directories need to be served directly. + +### The Problem + +TorrentPier has legacy directories (`/admin/` and `/bt/`) that contain their own `index.php` files and should be processed directly by the web server, not through the Laravel-style routing system. Laravel Herd's default nginx configuration sends all requests to `public/index.php`, which causes these legacy endpoints to fail. + +### Solution: Site-Specific Nginx Configuration + +#### Step 1: Generate Custom Nginx Config + +Run one of these commands to create a site-specific nginx configuration: + +```shell +# Option A: Isolate PHP version +herd isolate php@8.4 + +# Option B: Secure the site with SSL +herd secure +``` + +This generates a custom nginx configuration file at: +`~/Library/Application\ Support/Herd/config/valet/Nginx/[your-site-name]` + +#### Step 2: Edit the Generated Config + +Open the generated nginx configuration file and add these location blocks **before** the main Laravel location block: + +```nginx +# Serve /admin/ directory directly +location /admin/ { + alias /path/to/your/torrentpier/admin/; + index index.php; + + location ~ \.php$ { + fastcgi_pass unix:/opt/homebrew/var/run/php/php8.4-fpm.sock; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $request_filename; + include fastcgi_params; + } +} + +# Serve /bt/ directory directly +location /bt/ { + alias /path/to/your/torrentpier/bt/; + index index.php; + + location ~ \.php$ { + fastcgi_pass unix:/opt/homebrew/var/run/php/php8.4-fpm.sock; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $request_filename; + include fastcgi_params; + } +} +``` + +> **Note**: Replace `/path/to/your/torrentpier/` with the actual path to your TorrentPier installation. + +#### Step 3: Restart Herd + +```shell +herd restart +``` + +### Alternative Solutions + +#### Option 1: Root .htaccess (May work with some Herd configurations) + +Create a `.htaccess` file in your project root: + +```apache +RewriteEngine On + +# Exclude admin and bt directories from Laravel routing +RewriteRule ^admin/ - [L] +RewriteRule ^bt/ - [L] + +# Handle everything else through Laravel +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule ^(.*)$ public/index.php [L] +``` + +#### Option 2: Move to Public Directory + +Move the directories to the public folder: + +```shell +mv admin/ public/admin/ +mv bt/ public/bt/ +``` + +> **Warning**: This requires updating any hardcoded paths in the legacy code. + +### Testing Your Configuration + +After applying the configuration, test these URLs: + +```shell +# Admin panel should load +curl -I http://your-site.test/admin/ + +# BitTorrent tracker should respond +curl -I http://your-site.test/bt/ + +# Announce endpoint should work +curl -I http://your-site.test/bt/announce.php + +# Modern routes should still work +curl -I http://your-site.test/hello +``` + +All should return HTTP 200 status codes. + +### Troubleshooting + +- **502 Bad Gateway**: Check PHP-FPM socket path in nginx config +- **404 Not Found**: Verify directory paths in nginx location blocks +- **403 Forbidden**: Check file permissions on admin/bt directories +- **Still routing through Laravel**: Ensure location blocks are placed before the main Laravel location block + +For more details about Herd nginx configuration, see the [official Herd documentation](https://herd.laravel.com/docs/macos/sites/nginx-configuration). + ## ๐Ÿ“Œ Our recommendations * *It's recommended to run `cron.php`.* - For significant tracker speed increase it may be required to replace the built-in cron.php with an operating system daemon. diff --git a/app/Http/Controllers/Api/UserController.php b/app/Http/Controllers/Api/UserController.php new file mode 100644 index 000000000..dfcea39af --- /dev/null +++ b/app/Http/Controllers/Api/UserController.php @@ -0,0 +1,170 @@ +validated(); + + // Create the user using the service + $user = $this->userService->register($validated); + + return $this->json([ + 'success' => true, + 'message' => 'User registered successfully', + 'data' => [ + 'id' => $user->getKey(), + 'username' => $user->username, + 'email' => $user->user_email, + 'registered_at' => now()->toISOString() + ] + ], 201); + + } catch (ValidationException $e) { + return $this->json([ + 'success' => false, + 'message' => 'Validation failed', + 'errors' => $e->errors() + ], 422); + + } catch (\Exception $e) { + return $this->json([ + 'success' => false, + 'message' => 'Registration failed', + 'error' => $e->getMessage() + ], 500); + } + } + + /** + * Get user profile + */ + public function show(Request $request, int $userId): JsonResponse + { + try { + $user = User::find($userId); + + if (!$user) { + return $this->json([ + 'success' => false, + 'message' => 'User not found' + ], 404); + } + + $stats = $user->getStats(); + + return $this->json([ + 'success' => true, + 'data' => [ + 'id' => $user->getKey(), + 'username' => $user->username, + 'level' => $user->user_level, + 'active' => $user->isActive(), + 'registered' => now()->createFromTimestamp($user->user_regdate)->toISOString(), + 'last_visit' => now()->createFromTimestamp($user->user_lastvisit)->diffForHumans(), + 'stats' => [ + 'uploaded' => $stats['u_up_total'] ?? 0, + 'downloaded' => $stats['u_down_total'] ?? 0, + 'ratio' => $user->getRatio(), + ], + 'permissions' => [ + 'is_admin' => $user->isAdmin(), + 'is_moderator' => $user->isModerator(), + ] + ] + ]); + + } catch (\Exception $e) { + return $this->json([ + 'success' => false, + 'message' => 'Failed to retrieve user', + 'error' => $e->getMessage() + ], 500); + } + } + + /** + * List users with filtering and search + */ + public function index(Request $request): JsonResponse + { + try { + // Use Laravel-style parameter handling + $page = (int)$request->get('page', 1); + $perPage = min((int)$request->get('per_page', 20), 100); + $search = Str::limit(trim($request->get('search', '')), 50); + $level = $request->get('level'); + + // Get users using collection helpers + $users = collect(User::all()) + ->when(!empty($search), function ($collection) use ($search) { + return $collection->filter(function ($user) use ($search) { + return Str::contains(Str::lower($user->username), Str::lower($search)) || + Str::contains(Str::lower($user->user_email), Str::lower($search)); + }); + }) + ->when($level !== null, function ($collection) use ($level) { + return $collection->where('user_level', $level); + }) + ->where('user_active', 1) + ->sortBy('username') + ->forPage($page, $perPage) + ->map(function ($user) { + return [ + 'id' => $user->getKey(), + 'username' => $user->username, + 'level' => $user->user_level, + 'registered' => now()->createFromTimestamp($user->user_regdate)->format('Y-m-d'), + 'is_admin' => $user->isAdmin(), + 'is_moderator' => $user->isModerator(), + ]; + }) + ->values(); + + return $this->json([ + 'success' => true, + 'data' => $users->toArray(), + 'meta' => [ + 'page' => $page, + 'per_page' => $perPage, + 'search' => (string)$search, + 'level_filter' => $level + ] + ]); + + } catch (\Exception $e) { + return $this->json([ + 'success' => false, + 'message' => 'Failed to retrieve users', + 'error' => $e->getMessage() + ], 500); + } + } +} diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php new file mode 100644 index 000000000..c1db9aba9 --- /dev/null +++ b/app/Http/Controllers/Controller.php @@ -0,0 +1,94 @@ +validator = $validator; + } + /** + * Render a view with data + */ + protected function view(string $view, array $data = []): Response + { + $viewPath = $this->resolveViewPath($view); + + if (!file_exists($viewPath)) { + return new Response("View not found: {$view}", 404); + } + + // Extract data for use in view + extract($data); + + // Capture view output + ob_start(); + require $viewPath; + $content = ob_get_clean(); + + return new Response($content); + } + + /** + * Return a JSON response + */ + protected function json(array $data, int $status = 200, array $headers = []): JsonResponse + { + return new JsonResponse($data, $status, $headers); + } + + /** + * Redirect to a URL + */ + protected function redirect(string $url, int $status = 302): RedirectResponse + { + return new RedirectResponse($url, $status); + } + + /** + * Return a plain response + */ + protected function response(string $content = '', int $status = 200, array $headers = []): Response + { + return new Response($content, $status, $headers); + } + + /** + * Validate request data using Illuminate validation + */ + protected function validate(Request $request, array $rules, array $messages = [], array $attributes = []): array + { + $validator = $this->validator->make($request->all(), $rules, $messages, $attributes); + + if ($validator->fails()) { + throw new ValidationException($validator); + } + + return $validator->validated(); + } + + + /** + * Resolve view file path + */ + private function resolveViewPath(string $view): string + { + $view = str_replace('.', '/', $view); + return resource_path('views/' . $view . '.php'); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Web/HelloWorldController.php b/app/Http/Controllers/Web/HelloWorldController.php new file mode 100644 index 000000000..108ef970c --- /dev/null +++ b/app/Http/Controllers/Web/HelloWorldController.php @@ -0,0 +1,153 @@ +config->get('sitename', 'TorrentPier'); + $currentTime = now()->format('Y-m-d H:i:s'); + + $data = [ + 'siteName' => $siteName, + 'currentTime' => $currentTime, + 'request' => (object) [ + 'uri' => $request->fullUrl(), + 'method' => $request->method() + ], + 'architecture' => 'MVC with Illuminate HTTP' + ]; + + return $this->view('hello', $data); + } + + /** + * Return JSON response for hello world + */ + public function jsonResponse(Request $request): JsonResponse + { + $siteName = $this->config->get('sitename', 'TorrentPier'); + + return $this->json([ + 'message' => 'Hello World from TorrentPier!', + 'site' => $siteName, + 'timestamp' => now()->timestamp, + 'datetime' => now()->toISOString(), + 'route' => [ + 'uri' => $request->fullUrl(), + 'method' => $request->method(), + 'controller' => self::class, + ], + 'architecture' => [ + 'pattern' => 'Laravel-style MVC', + 'router' => 'Custom Laravel-style Router', + 'http' => 'Illuminate HTTP', + 'support' => 'Illuminate Support', + 'di' => 'Illuminate Container' + ], + 'features' => [ + 'illuminate_http' => 'Response and Request handling', + 'illuminate_support' => 'Collections, Str, Arr helpers', + 'carbon' => 'Date manipulation with now() and today()', + 'validation' => 'Laravel-style request validation', + 'collections' => 'collect() helper for data manipulation' + ] + ]); + } + + /** + * Demonstrate modern Laravel-style features + */ + public function features(Request $request): JsonResponse + { + // Demonstrate collections + $users = collect([ + ['name' => 'Alice', 'age' => 25], + ['name' => 'Bob', 'age' => 30], + ['name' => 'Charlie', 'age' => 35] + ]); + + $adults = $users->where('age', '>=', 18)->pluck('name'); + + // Demonstrate string helpers + $title = str('hello world')->title()->append('!'); + + // Demonstrate array helpers + $config = [ + 'app' => [ + 'name' => 'TorrentPier', + 'version' => '3.0' + ] + ]; + + $appName = data_get($config, 'app.name', 'Unknown'); + + return $this->json([ + 'collections_demo' => [ + 'original_users' => $users->toArray(), + 'adult_names' => $adults->toArray() + ], + 'string_demo' => [ + 'original' => 'hello world', + 'transformed' => (string) $title + ], + 'array_helpers_demo' => [ + 'config' => $config, + 'app_name' => $appName + ], + 'date_helpers' => [ + 'now' => now()->toISOString(), + 'today' => today()->toDateString(), + 'timestamp' => now()->timestamp + ] + ]); + } + + /** + * Extended features demonstration + */ + public function extended(Request $request): JsonResponse + { + $siteName = $this->config->get('sitename', 'TorrentPier'); + + return $this->json([ + 'message' => 'Extended Laravel-style features!', + 'site' => $siteName, + 'timestamp' => now()->timestamp, + 'datetime' => now()->toISOString(), + 'request_info' => [ + 'url' => $request->fullUrl(), + 'method' => $request->method(), + 'ip' => $request->ip(), + 'user_agent' => $request->userAgent(), + ], + 'laravel_features' => [ + 'collections' => 'Native Laravel collections', + 'request' => 'Pure Illuminate Request', + 'response' => 'Pure Illuminate JsonResponse', + 'validation' => 'Built-in validation', + 'helpers' => 'Laravel helper functions' + ] + ]); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Web/LegacyController.php b/app/Http/Controllers/Web/LegacyController.php new file mode 100644 index 000000000..06684d128 --- /dev/null +++ b/app/Http/Controllers/Web/LegacyController.php @@ -0,0 +1,219 @@ +config = $config; + } + + public function index(Request $request): Response + { + return $this->handleController($request, 'index'); + } + + public function ajax(Request $request): Response + { + return $this->handleController($request, 'ajax'); + } + + public function dl(Request $request): Response + { + return $this->handleController($request, 'dl'); + } + + public function dl_list(Request $request): Response + { + return $this->handleController($request, 'dl_list'); + } + + public function feed(Request $request): Response + { + return $this->handleController($request, 'feed'); + } + + public function filelist(Request $request): Response + { + return $this->handleController($request, 'filelist'); + } + + public function group(Request $request): Response + { + return $this->handleController($request, 'group'); + } + + public function group_edit(Request $request): Response + { + return $this->handleController($request, 'group_edit'); + } + + public function info(Request $request): Response + { + return $this->handleController($request, 'info'); + } + + public function login(Request $request): Response + { + return $this->handleController($request, 'login'); + } + + public function memberlist(Request $request): Response + { + return $this->handleController($request, 'memberlist'); + } + + public function modcp(Request $request): Response + { + return $this->handleController($request, 'modcp'); + } + + public function playback_m3u(Request $request): Response + { + return $this->handleController($request, 'playback_m3u'); + } + + public function poll(Request $request): Response + { + return $this->handleController($request, 'poll'); + } + + public function posting(Request $request): Response + { + return $this->handleController($request, 'posting'); + } + + public function privmsg(Request $request): Response + { + return $this->handleController($request, 'privmsg'); + } + + public function profile(Request $request): Response + { + return $this->handleController($request, 'profile'); + } + + public function search(Request $request): Response + { + return $this->handleController($request, 'search'); + } + + public function terms(Request $request): Response + { + return $this->handleController($request, 'terms'); + } + + public function tracker(Request $request): Response + { + return $this->handleController($request, 'tracker'); + } + + public function viewforum(Request $request): Response + { + return $this->handleController($request, 'viewforum'); + } + + public function viewtopic(Request $request): Response + { + return $this->handleController($request, 'viewtopic'); + } + + + public function handleController(Request $request, string $controller): Response + { + $rootPath = dirname(__DIR__, 4); + $controllerPath = $rootPath . '/controllers/' . $controller . '.php'; + + if (!file_exists($controllerPath)) { + return new Response( + "

404 - Not Found

Legacy controller '{$controller}' not found

", + 404, + ['Content-Type' => 'text/html'] + ); + } + + // Capture the legacy controller output + $output = ''; + $originalObLevel = ob_get_level(); + + try { + // Ensure legacy common.php is loaded for legacy controllers + if (!defined('BB_PATH')) { + require_once $rootPath . '/common.php'; + } + + ob_start(); + + // No need to save/restore superglobals - legacy controllers may modify them intentionally + + // Signal to legacy code that we're running through modern routing + if (!defined('MODERN_ROUTING')) { + define('MODERN_ROUTING', true); + } + + // Import essential legacy globals into local scope + global $bb_cfg, $config, $user, $template, $datastore, $lang, $userdata, $userinfo, $images, + $tracking_topics, $tracking_forums, $theme, $bf, $attach_config, $gen_simple_header, + $client_ip, $user_ip, $log_action, $html, $wordCensor, $search_id, + $session_id, $items_found, $per_page, $topic_id, $req_topics, $forum_id, $mode, + $is_auth, $t_data, $postrow, $group_id, $group_info, $post_id, $folder, $post_info, + $tor, $post_data, $privmsg, $forums, $redirect, $attachment, $forum_data, $search_all, + $redirect_url, $topic_csv, $poster_id, $emailer, $s_hidden_fields, $opt, $msg, $stats, + $page_cfg, $ads, $cat_forums, $last_session_data, $announce_interval, $auth_pages, + $lastvisit, $current_time, $excluded_forums_csv, $sphinx, $dl_link_css, $dl_status_css, + $upload_dir, $topic_data, $attachments; + + // GPC variables created dynamically via $GLOBALS in tracker.php and search.php + global $all_words_key, $all_words_val, $active_key, $active_val, $cat_key, $cat_val, + $dl_cancel_key, $dl_cancel_val, $dl_compl_key, $dl_compl_val, $dl_down_key, $dl_down_val, + $dl_will_key, $dl_will_val, $forum_key, $forum_val, $my_key, $my_val, $new_key, $new_val, + $title_match_key, $title_match_val, $order_key, $order_val, $poster_id_key, $poster_id_val, + $poster_name_key, $poster_name_val, $user_releases_key, $user_releases_val, $sort_key, $sort_val, + $seed_exist_key, $seed_exist_val, $show_author_key, $show_author_val, $show_cat_key, $show_cat_val, + $show_forum_key, $show_forum_val, $show_speed_key, $show_speed_val, $s_rg_key, $s_rg_val, + $s_not_seen_key, $s_not_seen_val, $time_key, $time_val, $tor_type_key, $tor_type_val, + $hash_key, $hash_val, $chars_key, $chars_val, $display_as_key, $display_as_val, + $dl_user_id_key, $dl_user_id_val, $my_topics_key, $my_topics_val, $new_topics_key, $new_topics_val, + $text_match_key, $text_match_val, $title_only_key, $title_only_val, $topic_key, $topic_val; + + // Include the legacy controller + // Note: We don't use require_once to allow multiple includes if needed + include $controllerPath; + + // Get the captured output - make sure we only clean our own buffer + $output = ob_get_clean(); + + // Return the output as HTML response + return new Response($output, 200, ['Content-Type' => 'text/html']); + + } catch (\Throwable $e) { + // Clean up any extra output buffers that were started, but preserve original level + while (ob_get_level() > $originalObLevel) { + ob_end_clean(); + } + + // Return error response + $errorHtml = " +

Legacy Controller Error

+

Controller: {$controller}

+

Error: " . htmlspecialchars($e->getMessage()) . "

+

File: " . htmlspecialchars($e->getFile()) . ":" . $e->getLine() . "

+ "; + + if (function_exists('dev') && dev()->isDebugEnabled()) { + $errorHtml .= "
" . htmlspecialchars($e->getTraceAsString()) . "
"; + } + + return new Response($errorHtml, 500, ['Content-Type' => 'text/html']); + } + } +} diff --git a/app/Http/Requests/FormRequest.php b/app/Http/Requests/FormRequest.php new file mode 100644 index 000000000..1bc1fbd1f --- /dev/null +++ b/app/Http/Requests/FormRequest.php @@ -0,0 +1,116 @@ +request = $request; + $this->runValidation(); + } + + /** + * Get the validation rules that apply to the request + */ + abstract public function rules(): array; + + /** + * Get custom validation messages + */ + public function messages(): array + { + return []; + } + + /** + * Get custom attributes for validator errors + */ + public function attributes(): array + { + return []; + } + + /** + * Determine if the user is authorized to make this request + */ + public function authorize(): bool + { + return true; + } + + /** + * Get validated data + */ + public function validated(): array + { + return $this->validated; + } + + /** + * Get specific validated field + */ + public function get(string $key, mixed $default = null): mixed + { + return data_get($this->validated, $key, $default); + } + + /** + * Get all request data + */ + public function all(): array + { + return $this->request->all(); + } + + /** + * Get only specific fields from request + */ + public function only(array $keys): array + { + return $this->request->only($keys); + } + + /** + * Get request data except specific fields + */ + public function except(array $keys): array + { + return $this->request->except($keys); + } + + /** + * Run the validation + */ + protected function runValidation(): void + { + if (!$this->authorize()) { + throw new \Illuminate\Auth\Access\AuthorizationException('This action is unauthorized.'); + } + + $validator = Validator::make( + $this->request->all(), + $this->rules(), + $this->messages(), + $this->attributes() + ); + + if ($validator->fails()) { + throw new ValidationException($validator); + } + + $this->validated = $validator->validated(); + } +} \ No newline at end of file diff --git a/app/Http/Requests/RegisterUserRequest.php b/app/Http/Requests/RegisterUserRequest.php new file mode 100644 index 000000000..c821b2fe7 --- /dev/null +++ b/app/Http/Requests/RegisterUserRequest.php @@ -0,0 +1,120 @@ + [ + 'required', + 'string', + 'min:3', + 'max:25', + 'regex:/^[a-zA-Z0-9_-]+$/', + 'unique:bb_users,username' + ], + 'email' => [ + 'required', + 'email', + 'max:255', + 'unique:bb_users,user_email' + ], + 'password' => [ + 'required', + 'string', + 'min:6', + 'max:72' + ], + 'password_confirmation' => [ + 'required', + 'same:password' + ], + 'terms' => [ + 'accepted' + ] + ]; + } + + /** + * Get custom validation messages + */ + public function messages(): array + { + return [ + 'username.required' => 'Username is required.', + 'username.min' => 'Username must be at least 3 characters.', + 'username.max' => 'Username cannot exceed 25 characters.', + 'username.regex' => 'Username can only contain letters, numbers, hyphens, and underscores.', + 'username.unique' => 'This username is already taken.', + + 'email.required' => 'Email address is required.', + 'email.email' => 'Please enter a valid email address.', + 'email.unique' => 'This email address is already registered.', + + 'password.required' => 'Password is required.', + 'password.min' => 'Password must be at least 6 characters.', + 'password.max' => 'Password cannot exceed 72 characters.', + + 'password_confirmation.required' => 'Password confirmation is required.', + 'password_confirmation.same' => 'Password confirmation does not match.', + + 'terms.accepted' => 'You must accept the terms and conditions.' + ]; + } + + /** + * Get custom attributes for validator errors + */ + public function attributes(): array + { + return [ + 'username' => 'username', + 'email' => 'email address', + 'password' => 'password', + 'password_confirmation' => 'password confirmation', + 'terms' => 'terms and conditions' + ]; + } + + /** + * Get the username + */ + public function getUsername(): string + { + return str($this->get('username'))->trim()->lower(); + } + + /** + * Get the email + */ + public function getEmail(): string + { + return str($this->get('email'))->trim()->lower(); + } + + /** + * Get the password + */ + public function getPassword(): string + { + return $this->get('password'); + } + + /** + * Check if terms are accepted + */ + public function hasAcceptedTerms(): bool + { + return (bool) $this->get('terms'); + } +} \ No newline at end of file diff --git a/app/Http/Routing/Router.php b/app/Http/Routing/Router.php new file mode 100644 index 000000000..b13d2be56 --- /dev/null +++ b/app/Http/Routing/Router.php @@ -0,0 +1,223 @@ +container = $container; + + // Create event dispatcher if not already bound + if (!$container->bound('events')) { + $container->singleton('events', function () { + return new Dispatcher($this->container); + }); + } + + // Create the Illuminate Router + $this->router = new LaravelRouter($container->make('events'), $container); + + // Register middleware aliases + $this->registerMiddleware(); + + // Bind router instance + $container->instance('router', $this->router); + $container->instance(LaravelRouter::class, $this->router); + + // Create and bind URL generator + $request = $container->bound('request') ? $container->make('request') : Request::capture(); + $container->instance('request', $request); + + $url = new UrlGenerator($this->router->getRoutes(), $request); + $container->instance('url', $url); + $container->instance(UrlGenerator::class, $url); + } + + /** + * Get the underlying Laravel Router instance + */ + public function getRouter(): LaravelRouter + { + return $this->router; + } + + /** + * Register a GET route + */ + public function get(string $uri, $action): \Illuminate\Routing\Route + { + return $this->router->get($uri, $this->parseAction($action)); + } + + /** + * Register a POST route + */ + public function post(string $uri, $action): \Illuminate\Routing\Route + { + return $this->router->post($uri, $this->parseAction($action)); + } + + /** + * Register a PUT route + */ + public function put(string $uri, $action): \Illuminate\Routing\Route + { + return $this->router->put($uri, $this->parseAction($action)); + } + + /** + * Register a PATCH route + */ + public function patch(string $uri, $action): \Illuminate\Routing\Route + { + return $this->router->patch($uri, $this->parseAction($action)); + } + + /** + * Register a DELETE route + */ + public function delete(string $uri, $action): \Illuminate\Routing\Route + { + return $this->router->delete($uri, $this->parseAction($action)); + } + + /** + * Register an OPTIONS route + */ + public function options(string $uri, $action): \Illuminate\Routing\Route + { + return $this->router->options($uri, $this->parseAction($action)); + } + + /** + * Register a route for any HTTP verb + */ + public function any(string $uri, $action): \Illuminate\Routing\Route + { + return $this->router->any($uri, $this->parseAction($action)); + } + + /** + * Register a route group + */ + public function group(array $attributes, \Closure $callback): void + { + $this->router->group($attributes, $callback); + } + + /** + * Register a route prefix + */ + public function prefix(string $prefix): \Illuminate\Routing\RouteRegistrar + { + return $this->router->prefix($prefix); + } + + /** + * Register middleware + */ + public function middleware($middleware): \Illuminate\Routing\RouteRegistrar + { + return $this->router->middleware($middleware); + } + + /** + * Register a resource controller + */ + public function resource(string $name, string $controller, array $options = []): \Illuminate\Routing\PendingResourceRegistration + { + return $this->router->resource($name, $controller, $options); + } + + /** + * Dispatch the request to the application + */ + public function dispatch(Request $request): Response|JsonResponse + { + try { + $response = $this->router->dispatch($request); + + // Ensure we always return a Response object + if (!$response instanceof Response && !$response instanceof JsonResponse) { + if (is_array($response) || is_object($response)) { + return new JsonResponse($response); + } + return new Response($response); + } + + return $response; + } catch (\Symfony\Component\HttpKernel\Exception\NotFoundHttpException $e) { + return new Response('Not Found', 404); + } catch (\Exception $e) { + // Log the error if logger is available + if ($this->container->bound('log')) { + $this->container->make('log')->error($e->getMessage(), ['exception' => $e]); + } + + return new Response('Internal Server Error', 500); + } + } + + /** + * Parse the action to convert Class::method to array format + */ + private function parseAction($action) + { + if (is_string($action) && str_contains($action, '::')) { + return str_replace('::', '@', $action); + } + + return $action; + } + + /** + * Get all registered routes + */ + public function getRoutes(): \Illuminate\Routing\RouteCollection + { + return $this->router->getRoutes(); + } + + /** + * Set the fallback route + */ + public function fallback($action): \Illuminate\Routing\Route + { + return $this->router->fallback($this->parseAction($action)); + } + + /** + * Register middleware aliases + */ + private function registerMiddleware(): void + { + $middlewareAliases = [ + 'auth' => \App\Http\Middleware\AuthMiddleware::class, + 'admin' => \App\Http\Middleware\AdminMiddleware::class, + 'cors' => \App\Http\Middleware\CorsMiddleware::class, + ]; + + foreach ($middlewareAliases as $alias => $middleware) { + $this->router->aliasMiddleware($alias, $middleware); + } + } +} \ No newline at end of file diff --git a/app/Listeners/SendWelcomeEmail.php b/app/Listeners/SendWelcomeEmail.php new file mode 100644 index 000000000..b06971f5d --- /dev/null +++ b/app/Listeners/SendWelcomeEmail.php @@ -0,0 +1,41 @@ +getUsername(), + $event->getUserId(), + $event->getEmail() + )); + } + } + + /** + * Determine whether the listener should be queued + */ + public function shouldQueue(UserRegistered $event): bool + { + return true; + } +} \ No newline at end of file diff --git a/app/Listeners/UpdateUserStatistics.php b/app/Listeners/UpdateUserStatistics.php new file mode 100644 index 000000000..10bb51d22 --- /dev/null +++ b/app/Listeners/UpdateUserStatistics.php @@ -0,0 +1,32 @@ +getUploaderId(), + $event->getTorrentName(), + $event->getTorrentId(), + $event->getSize() + )); + } + } +} \ No newline at end of file diff --git a/app/Models/Model.php b/app/Models/Model.php new file mode 100644 index 000000000..5a7e3cb7c --- /dev/null +++ b/app/Models/Model.php @@ -0,0 +1,223 @@ +fill($attributes); + $this->syncOriginal(); + } + + /** + * Find a model by its primary key + */ + public static function find(int|string $id): ?static + { + $instance = new static(DB()); + $data = $instance->db->table($instance->table) + ->where($instance->primaryKey, $id) + ->fetch(); + + if (!$data) { + return null; + } + + return new static(DB(), (array) $data); + } + + /** + * Find a model by a specific column + */ + public static function findBy(string $column, mixed $value): ?static + { + $instance = new static(DB()); + $data = $instance->db->table($instance->table) + ->where($column, $value) + ->fetch(); + + if (!$data) { + return null; + } + + return new static(DB(), (array) $data); + } + + /** + * Get all models + */ + public static function all(): array + { + $instance = new static(DB()); + $rows = $instance->db->table($instance->table)->fetchAll(); + + $models = []; + foreach ($rows as $row) { + $models[] = new static(DB(), (array) $row); + } + + return $models; + } + + /** + * Fill the model with an array of attributes + */ + public function fill(array $attributes): self + { + foreach ($attributes as $key => $value) { + $this->attributes[$key] = $value; + } + + return $this; + } + + /** + * Save the model to the database + */ + public function save(): bool + { + if ($this->exists()) { + return $this->update(); + } + + return $this->insert(); + } + + /** + * Insert a new record + */ + protected function insert(): bool + { + $this->db->table($this->table)->insert($this->attributes); + + if (!isset($this->attributes[$this->primaryKey])) { + $this->attributes[$this->primaryKey] = $this->db->getInsertId(); + } + + $this->syncOriginal(); + return true; + } + + /** + * Update an existing record + */ + protected function update(): bool + { + $dirty = $this->getDirty(); + + if (empty($dirty)) { + return true; + } + + $this->db->table($this->table) + ->where($this->primaryKey, $this->getKey()) + ->update($dirty); + + $this->syncOriginal(); + return true; + } + + /** + * Delete the model + */ + public function delete(): bool + { + if (!$this->exists()) { + return false; + } + + $this->db->table($this->table) + ->where($this->primaryKey, $this->getKey()) + ->delete(); + + return true; + } + + /** + * Check if the model exists in the database + */ + public function exists(): bool + { + return isset($this->original[$this->primaryKey]); + } + + /** + * Get the primary key value + */ + public function getKey(): int|string|null + { + return $this->attributes[$this->primaryKey] ?? null; + } + + /** + * Get attributes that have been changed + */ + public function getDirty(): array + { + $dirty = []; + + foreach ($this->attributes as $key => $value) { + if (!array_key_exists($key, $this->original) || $value !== $this->original[$key]) { + $dirty[$key] = $value; + } + } + + return $dirty; + } + + /** + * Sync the original attributes with the current + */ + protected function syncOriginal(): void + { + $this->original = $this->attributes; + } + + /** + * Get an attribute + */ + public function __get(string $key): mixed + { + return $this->attributes[$key] ?? null; + } + + /** + * Set an attribute + */ + public function __set(string $key, mixed $value): void + { + $this->attributes[$key] = $value; + } + + /** + * Check if an attribute exists + */ + public function __isset(string $key): bool + { + return isset($this->attributes[$key]); + } + + /** + * Convert the model to an array + */ + public function toArray(): array + { + return $this->attributes; + } +} \ No newline at end of file diff --git a/app/Models/Torrent.php b/app/Models/Torrent.php new file mode 100644 index 000000000..f9da5d7c9 --- /dev/null +++ b/app/Models/Torrent.php @@ -0,0 +1,109 @@ +db->table('bb_bt_tracker') + ->where('topic_id', $this->getKey()) + ->where('complete', 0) + ->fetchAll(); + } + + /** + * Get completed peers (seeders) for this torrent + */ + public function getSeeders(): array + { + return $this->db->table('bb_bt_tracker') + ->where('topic_id', $this->getKey()) + ->where('complete', 1) + ->fetchAll(); + } + + /** + * Get torrent statistics + */ + public function getStats(): array + { + $stats = $this->db->table('bb_bt_tracker_snap') + ->where('topic_id', $this->getKey()) + ->fetch(); + + return $stats ? (array) $stats : [ + 'seeders' => 0, + 'leechers' => 0, + 'complete' => 0 + ]; + } + + /** + * Get the user who uploaded this torrent + */ + public function getUploader(): ?User + { + return User::find($this->poster_id); + } + + /** + * Get the forum topic associated with this torrent + */ + public function getTopic(): array + { + $topic = $this->db->table('bb_topics') + ->where('topic_id', $this->getKey()) + ->fetch(); + + return $topic ? (array) $topic : []; + } + + /** + * Check if torrent is active + */ + public function isActive(): bool + { + return (int) $this->tor_status === 0; + } + + /** + * Find torrent by info hash + */ + public static function findByInfoHash(string $infoHash): ?self + { + return self::findBy('info_hash', $infoHash); + } + + /** + * Get recent torrents + */ + public static function getRecent(int $limit = 10): array + { + $instance = new static(DB()); + $rows = $instance->db->table($instance->table) + ->orderBy('reg_time DESC') + ->limit($limit) + ->fetchAll(); + + $torrents = []; + foreach ($rows as $row) { + $torrents[] = new static(DB(), (array) $row); + } + + return $torrents; + } +} \ No newline at end of file diff --git a/app/Models/User.php b/app/Models/User.php new file mode 100644 index 000000000..b54b7eaf3 --- /dev/null +++ b/app/Models/User.php @@ -0,0 +1,139 @@ +db->table('bb_bt_torrents') + ->where('poster_id', $this->getKey()) + ->orderBy('reg_time DESC') + ->fetchAll(); + } + + /** + * Get user's posts + */ + public function getPosts(int $limit = 10): array + { + return $this->db->table('bb_posts') + ->where('poster_id', $this->getKey()) + ->orderBy('post_time DESC') + ->limit($limit) + ->fetchAll(); + } + + /** + * Get user's groups + */ + public function getGroups(): array + { + return $this->db->table('bb_user_group') + ->where('user_id', $this->getKey()) + ->where('user_pending', 0) + ->fetchAll(); + } + + /** + * Check if user is admin + */ + public function isAdmin(): bool + { + return (int) $this->user_level === 1; + } + + /** + * Check if user is moderator + */ + public function isModerator(): bool + { + return (int) $this->user_level === 2; + } + + /** + * Check if user is active + */ + public function isActive(): bool + { + return (int) $this->user_active === 1; + } + + /** + * Get user's upload/download statistics + */ + public function getStats(): array + { + $stats = $this->db->table('bb_bt_users') + ->where('user_id', $this->getKey()) + ->fetch(); + + return $stats ? (array) $stats : [ + 'u_up_total' => 0, + 'u_down_total' => 0, + 'u_up_release' => 0, + 'u_up_bonus' => 0 + ]; + } + + /** + * Get user's ratio + */ + public function getRatio(): float + { + $stats = $this->getStats(); + $downloaded = (int) $stats['u_down_total']; + $uploaded = (int) $stats['u_up_total']; + + if ($downloaded === 0) { + return 0.0; + } + + return round($uploaded / $downloaded, 2); + } + + /** + * Verify password + */ + public function verifyPassword(string $password): bool + { + return password_verify($password, $this->user_password); + } + + /** + * Update password + */ + public function updatePassword(string $newPassword): void + { + $this->user_password = password_hash($newPassword, PASSWORD_BCRYPT); + } +} \ No newline at end of file diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php new file mode 100644 index 000000000..c6c849bda --- /dev/null +++ b/app/Providers/AppServiceProvider.php @@ -0,0 +1,66 @@ +registerEvents(); + $this->registerValidation(); + } + + /** + * Bootstrap any application services + */ + public function boot(): void + { + // Bootstrap services that need the application to be fully loaded + } + + + /** + * Register the event dispatcher + */ + protected function registerEvents(): void + { + $this->app->singleton('events', function ($app) { + return new \Illuminate\Events\Dispatcher($app); + }); + + $this->app->alias('events', \Illuminate\Events\Dispatcher::class); + $this->app->alias('events', \Illuminate\Contracts\Events\Dispatcher::class); + } + + /** + * Register the validation factory + */ + protected function registerValidation(): void + { + $this->app->singleton('validator', function ($app) { + $loader = new \Illuminate\Translation\ArrayLoader(); + $translator = new \Illuminate\Translation\Translator($loader, 'en'); + return new \Illuminate\Validation\Factory($translator, $app); + }); + + $this->app->bind(\Illuminate\Validation\Factory::class, function ($app) { + return $app['validator']; + }); + + $this->app->alias('validator', \Illuminate\Validation\Factory::class); + $this->app->alias('validator', \Illuminate\Contracts\Validation\Factory::class); + } +} \ No newline at end of file diff --git a/app/Providers/ConsoleServiceProvider.php b/app/Providers/ConsoleServiceProvider.php new file mode 100644 index 000000000..2409f3125 --- /dev/null +++ b/app/Providers/ConsoleServiceProvider.php @@ -0,0 +1,76 @@ +registerCommands(); + } + + /** + * Register console commands + */ + protected function registerCommands(): void + { + $commands = $this->discoverCommands(); + + $this->app->bind('console.commands', function () use ($commands) { + return $commands; + }); + + // Register each command in the container + foreach ($commands as $command) { + $this->app->bind($command, function ($app) use ($command) { + return new $command(); + }); + } + } + + /** + * Discover commands in the Commands directory + */ + protected function discoverCommands(): array + { + $commands = []; + $commandsPath = $this->app->make('path.app') . '/Console/Commands'; + + if (!is_dir($commandsPath)) { + return $commands; + } + + $finder = new Finder(); + $finder->files()->name('*Command.php')->in($commandsPath); + + foreach ($finder as $file) { + $relativePath = $file->getRelativePathname(); + $className = 'App\\Console\\Commands\\' . str_replace(['/', '.php'], ['\\', ''], $relativePath); + + if (class_exists($className)) { + $reflection = new \ReflectionClass($className); + + // Only include concrete command classes that extend our base Command + if (!$reflection->isAbstract() && + $reflection->isSubclassOf(\App\Console\Commands\Command::class)) { + $commands[] = $className; + } + } + } + + return $commands; + } +} \ No newline at end of file diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php new file mode 100644 index 000000000..589769256 --- /dev/null +++ b/app/Providers/EventServiceProvider.php @@ -0,0 +1,64 @@ + [ + SendWelcomeEmail::class, + ], + TorrentUploaded::class => [ + UpdateUserStatistics::class, + ], + ]; + + /** + * The event subscriber classes to register + */ + protected array $subscribe = [ + // Add event subscribers here + ]; + + /** + * Register any events for your application + */ + public function boot(): void + { + $events = $this->app->make('events'); + + foreach ($this->listen as $event => $listeners) { + foreach ($listeners as $listener) { + $events->listen($event, $listener); + } + } + + foreach ($this->subscribe as $subscriber) { + $events->subscribe($subscriber); + } + } + + /** + * Determine if events and listeners should be automatically discovered + */ + public function shouldDiscoverEvents(): bool + { + return false; + } +} \ No newline at end of file diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php new file mode 100644 index 000000000..7c3c19484 --- /dev/null +++ b/app/Providers/RouteServiceProvider.php @@ -0,0 +1,123 @@ +app->singleton(Router::class, function ($app) { + return new Router($app); + }); + + // Alias for convenience + $this->app->alias(Router::class, 'router'); + } + + /** + * Define your route model bindings, pattern filters, etc. + */ + public function boot(): void + { + $this->configureRateLimiting(); + + $this->routes(function () { + $this->mapApiRoutes(); + $this->mapWebRoutes(); + $this->mapAdminRoutes(); + }); + } + + /** + * Configure the rate limiters for the application + */ + protected function configureRateLimiting(): void + { + // Rate limiting can be configured here when needed + // Example: + // RateLimiter::for('api', function (Request $request) { + // return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip()); + // }); + } + + /** + * Define the routes for the application + */ + protected function routes(\Closure $callback): void + { + $callback(); + } + + /** + * Define the "web" routes for the application + */ + protected function mapWebRoutes(): void + { + $router = $this->app->make(Router::class); + $routeFile = $this->app->make('path.base') . '/routes/web.php'; + + if (file_exists($routeFile)) { + $router->group([], function () use ($routeFile) { + require $routeFile; + }); + } + } + + /** + * Define the "api" routes for the application + */ + protected function mapApiRoutes(): void + { + $router = $this->app->make(Router::class); + $routeFile = $this->app->make('path.base') . '/routes/api.php'; + + if (file_exists($routeFile)) { + $router->group([ + 'prefix' => 'api', + ], function () use ($routeFile) { + require $routeFile; + }); + } + } + + /** + * Define the "admin" routes for the application + */ + protected function mapAdminRoutes(): void + { + $router = $this->app->make(Router::class); + $routeFile = $this->app->make('path.base') . '/routes/admin.php'; + + if (file_exists($routeFile)) { + $router->group([ + 'prefix' => 'admin', + 'middleware' => [ + 'auth', + 'admin', + ] + ], function () use ($routeFile) { + require $routeFile; + }); + } + } +} diff --git a/app/Providers/ServiceProvider.php b/app/Providers/ServiceProvider.php new file mode 100644 index 000000000..b7dff9204 --- /dev/null +++ b/app/Providers/ServiceProvider.php @@ -0,0 +1,17 @@ +validateTorrentData($data); + + // Create torrent record + $torrent = new Torrent($this->db); + $torrent->fill([ + 'info_hash' => $data['info_hash'], + 'poster_id' => $user->getKey(), + 'size' => $data['size'], + 'reg_time' => time(), + 'tor_status' => 0, + 'checked_user_id' => 0, + 'checked_time' => 0, + 'tor_type' => 0, + 'speed_up' => 0, + 'speed_down' => 0, + ]); + $torrent->save(); + + // Clear cache + $this->cache->delete('recent_torrents'); + + return $torrent; + } + + /** + * Update torrent information + */ + public function update(Torrent $torrent, array $data): bool + { + $torrent->fill($data); + $result = $torrent->save(); + + // Clear relevant caches + $this->cache->delete('torrent:' . $torrent->info_hash); + $this->cache->delete('recent_torrents'); + + return $result; + } + + /** + * Delete a torrent + */ + public function delete(Torrent $torrent): bool + { + // Delete related data + $this->db->table('bb_bt_tracker') + ->where('topic_id', $torrent->getKey()) + ->delete(); + + $this->db->table('bb_bt_tracker_snap') + ->where('topic_id', $torrent->getKey()) + ->delete(); + + // Delete torrent + $result = $torrent->delete(); + + // Clear cache + $this->cache->delete('torrent:' . $torrent->info_hash); + $this->cache->delete('recent_torrents'); + + return $result; + } + + /** + * Get paginated torrents using Laravel-style collections + */ + public function paginate(int $page = 1, ?string $category = null, int $perPage = 20): array + { + $offset = ($page - 1) * $perPage; + + $query = $this->db->table('bb_bt_torrents as t') + ->select('t.*, ts.seeders, ts.leechers, ts.complete') + ->leftJoin('bb_bt_tracker_snap as ts', 'ts.topic_id = t.topic_id') + ->where('t.tor_status', 0) + ->orderBy('t.reg_time DESC') + ->limit($perPage) + ->offset($offset); + + if ($category !== null) { + $query->where('t.tor_type', $category); + } + + $rows = $query->fetchAll(); + + // Use Laravel-style collection for better data manipulation + $torrents = collect($rows) + ->map(fn($row) => new Torrent($this->db, (array) $row)) + ->values() + ->toArray(); + + return [ + 'data' => $torrents, + 'page' => $page, + 'per_page' => $perPage, + 'total' => $this->countTorrents($category), + 'from' => $offset + 1, + 'to' => min($offset + $perPage, $this->countTorrents($category)), + 'last_page' => ceil($this->countTorrents($category) / $perPage) + ]; + } + + /** + * Search torrents using collections and modern string helpers + */ + public function search(string $query, array $filters = []): array + { + $cacheKey = 'search:' . str($query . serialize($filters))->hash('md5'); + + return $this->cache->remember($cacheKey, 300, function() use ($query, $filters) { + $qb = $this->db->table('bb_bt_torrents as t') + ->select('t.*, ts.seeders, ts.leechers') + ->leftJoin('bb_bt_tracker_snap as ts', 'ts.topic_id = t.topic_id') + ->leftJoin('bb_topics as top', 'top.topic_id = t.topic_id') + ->where('t.tor_status', 0); + + // Search in topic title (cleaned query) + if (!empty($query)) { + $cleanQuery = str($query)->trim()->lower()->limit(100); + $qb->where('LOWER(top.topic_title) LIKE ?', '%' . $cleanQuery . '%'); + } + + // Apply filters using data_get helper + if ($category = data_get($filters, 'category')) { + $qb->where('t.tor_type', $category); + } + + if ($minSeeders = data_get($filters, 'min_seeders')) { + $qb->where('ts.seeders >= ?', $minSeeders); + } + + $rows = $qb->limit(100)->fetchAll(); + + // Use collection to transform and filter results + $torrents = collect($rows) + ->map(fn($row) => new Torrent($this->db, (array) $row)) + ->when(data_get($filters, 'sort') === 'popular', function ($collection) { + return $collection->sortByDesc(fn($torrent) => $torrent->seeders ?? 0); + }) + ->when(data_get($filters, 'sort') === 'recent', function ($collection) { + return $collection->sortByDesc('reg_time'); + }) + ->values() + ->toArray(); + + return $torrents; + }); + } + + /** + * Get torrent statistics + */ + public function getStatistics(): array + { + return $this->cache->remember('torrent_stats', 3600, function() { + $stats = []; + + // Total torrents + $stats['total_torrents'] = $this->db->table('bb_bt_torrents') + ->where('tor_status', 0) + ->count('*'); + + // Total size + $totalSize = $this->db->table('bb_bt_torrents') + ->where('tor_status', 0) + ->sum('size'); + $stats['total_size'] = $totalSize ?: 0; + + // Active peers + $stats['active_peers'] = $this->db->table('bb_bt_tracker') + ->count('*'); + + // Completed downloads + $stats['total_completed'] = $this->db->table('bb_bt_torrents') + ->where('tor_status', 0) + ->sum('complete_count'); + + return $stats; + }); + } + + /** + * Validate torrent data + */ + private function validateTorrentData(array $data): void + { + if (empty($data['info_hash']) || strlen($data['info_hash']) !== 40) { + throw new \InvalidArgumentException('Invalid info hash'); + } + + if (empty($data['size']) || $data['size'] <= 0) { + throw new \InvalidArgumentException('Invalid torrent size'); + } + + // Check if torrent already exists + $existing = Torrent::findByInfoHash($data['info_hash']); + if ($existing !== null) { + throw new \InvalidArgumentException('Torrent already exists'); + } + } + + /** + * Count torrents + */ + private function countTorrents(?string $category = null): int + { + $query = $this->db->table('bb_bt_torrents') + ->where('tor_status', 0); + + if ($category !== null) { + $query->where('tor_type', $category); + } + + return $query->count('*'); + } +} \ No newline at end of file diff --git a/app/Services/User/UserService.php b/app/Services/User/UserService.php new file mode 100644 index 000000000..4e7730121 --- /dev/null +++ b/app/Services/User/UserService.php @@ -0,0 +1,196 @@ +validateRegistrationData($data); + + // Create user + $user = new User($this->db); + $user->fill([ + 'username' => $data['username'], + 'user_email' => $data['email'], + 'user_password' => password_hash($data['password'], PASSWORD_BCRYPT), + 'user_level' => 0, // Regular user + 'user_active' => 1, + 'user_regdate' => now()->timestamp, + 'user_lastvisit' => now()->timestamp, + 'user_timezone' => 0, + 'user_lang' => 'en', + 'user_dateformat' => 'd M Y H:i', + ]); + + $user->save(); + + // Clear user cache + $this->cache->delete('user_count'); + + return $user; + } + + /** + * Update user profile + */ + public function updateProfile(User $user, array $data): bool + { + $allowedFields = [ + 'user_timezone', + 'user_lang', + 'user_dateformat', + ]; + + $updateData = collect($data) + ->only($allowedFields) + ->filter() + ->toArray(); + + if (empty($updateData)) { + return true; + } + + $user->fill($updateData); + $result = $user->save(); + + // Clear user cache + $this->cache->delete('user:' . $user->getKey()); + + return $result; + } + + /** + * Change user password + */ + public function changePassword(User $user, string $currentPassword, string $newPassword): bool + { + if (!$user->verifyPassword($currentPassword)) { + throw new \InvalidArgumentException('Current password is incorrect'); + } + + $user->updatePassword($newPassword); + return $user->save(); + } + + /** + * Get user statistics + */ + public function getUserStats(User $user): array + { + $cacheKey = 'user_stats:' . $user->getKey(); + + return $this->cache->remember($cacheKey, 1800, function() use ($user) { + $stats = $user->getStats(); + $torrents = $user->getTorrents(); + $posts = $user->getPosts(5); + + return [ + 'upload_stats' => [ + 'total_uploaded' => $stats['u_up_total'] ?? 0, + 'total_downloaded' => $stats['u_down_total'] ?? 0, + 'ratio' => $user->getRatio(), + ], + 'activity' => [ + 'torrents_count' => count($torrents), + 'recent_posts' => count($posts), + 'last_visit' => now()->createFromTimestamp($user->user_lastvisit)->diffForHumans(), + ], + 'permissions' => [ + 'level' => $user->user_level, + 'is_admin' => $user->isAdmin(), + 'is_moderator' => $user->isModerator(), + 'is_active' => $user->isActive(), + ] + ]; + }); + } + + /** + * Search users using modern collection methods + */ + public function searchUsers(string $query, array $filters = []): array + { + $cacheKey = 'user_search:' . str($query . serialize($filters))->hash('md5'); + + return $this->cache->remember($cacheKey, 600, function() use ($query, $filters) { + // Get all active users (in a real app, this would be paginated) + $users = collect(User::all()) + ->where('user_active', 1); + + // Apply search filter + if (!empty($query)) { + $searchTerm = str($query)->lower(); + $users = $users->filter(function ($user) use ($searchTerm) { + return str($user->username)->lower()->contains($searchTerm) || + str($user->user_email)->lower()->contains($searchTerm); + }); + } + + // Apply level filter + if ($level = data_get($filters, 'level')) { + $users = $users->where('user_level', $level); + } + + // Apply sorting + $sortBy = data_get($filters, 'sort', 'username'); + $sortDirection = data_get($filters, 'direction', 'asc'); + + $users = $sortDirection === 'desc' + ? $users->sortByDesc($sortBy) + : $users->sortBy($sortBy); + + return $users->values()->toArray(); + }); + } + + /** + * Validate registration data + */ + private function validateRegistrationData(array $data): void + { + if (empty($data['username'])) { + throw new \InvalidArgumentException('Username is required'); + } + + if (empty($data['email'])) { + throw new \InvalidArgumentException('Email is required'); + } + + if (empty($data['password'])) { + throw new \InvalidArgumentException('Password is required'); + } + + // Check if username already exists + $existingUser = User::findByUsername($data['username']); + if ($existingUser) { + throw new \InvalidArgumentException('Username already exists'); + } + + // Check if email already exists + $existingEmail = User::findByEmail($data['email']); + if ($existingEmail) { + throw new \InvalidArgumentException('Email already exists'); + } + } +} \ No newline at end of file diff --git a/app/Services/UserService.php b/app/Services/UserService.php new file mode 100644 index 000000000..df199d30f --- /dev/null +++ b/app/Services/UserService.php @@ -0,0 +1,42 @@ +make($abstract, $parameters); + } +} + +if (!function_exists('config')) { + /** + * Get / set the specified configuration value + */ + function config(array|string|null $key = null, mixed $default = null): mixed + { + if (is_null($key)) { + return app('config'); + } + + if (is_array($key)) { + return app('config')->set($key); + } + + return app('config')->get($key, $default); + } +} + +if (!function_exists('event')) { + /** + * Dispatch an event and call the listeners + */ + function event(string|object $event, mixed $payload = [], bool $halt = false): array|null + { + return app('events')->dispatch($event, $payload, $halt); + } +} diff --git a/bootstrap/app.php b/bootstrap/app.php new file mode 100644 index 000000000..43c5862db --- /dev/null +++ b/bootstrap/app.php @@ -0,0 +1,41 @@ +safeLoad(); + +// Load legacy common.php only if not already loaded (will be loaded by LegacyController when needed) +// This prevents header conflicts for modern API routes +if (!defined('BB_PATH')) { + // Only load for legacy routes - modern routes will skip this + $requestUri = $_SERVER['REQUEST_URI'] ?? ''; + $urlPath = parse_url($requestUri, PHP_URL_PATH) ?: $requestUri; + $isLegacyRoute = str_ends_with($urlPath, '.php') || $requestUri === '/' || str_contains($requestUri, 'tracker') || str_contains($requestUri, 'forum'); + + if ($isLegacyRoute) { + require_once dirname(__DIR__) . '/common.php'; + } +} + +// Define application constants +define('IN_TORRENTPIER', true); + +// Load container bootstrap +require_once __DIR__ . '/container.php'; + +// Create the application container +$container = createContainer(dirname(__DIR__)); + +// Get the Router instance (it will be created and registered by RouteServiceProvider) +$router = $container->make(\App\Http\Routing\Router::class); + +// Return the router for handling requests +return $router; \ No newline at end of file diff --git a/bootstrap/container.php b/bootstrap/container.php new file mode 100644 index 000000000..05e7f98ad --- /dev/null +++ b/bootstrap/container.php @@ -0,0 +1,115 @@ +safeLoad(); + + $container = new Container(); + + // Set the container instance globally + Container::setInstance($container); + + // Register base paths + $container->instance('path.base', $rootPath); + $container->instance('path.app', $rootPath . '/app'); + $container->instance('path.config', $rootPath . '/config'); + $container->instance('path.database', $rootPath . '/database'); + $container->instance('path.public', $rootPath . '/public'); + $container->instance('path.resources', $rootPath . '/resources'); + $container->instance('path.storage', $rootPath . '/storage'); + + // Register the container itself + $container->instance(Container::class, $container); + $container->alias(Container::class, 'app'); + $container->alias(Container::class, Illuminate\Contracts\Container\Container::class); + $container->alias(Container::class, Psr\Container\ContainerInterface::class); + + // Load configuration + loadConfiguration($container, $rootPath); + + // Register service providers + registerServiceProviders($container); + + return $container; +} + +/** + * Load configuration files + */ +function loadConfiguration(Container $container, string $rootPath): void +{ + $configPath = $rootPath . '/config'; + + // Create unified config repository + $config = new \Illuminate\Config\Repository(); + + // Load services configuration + if (file_exists($configPath . '/services.php')) { + $services = require $configPath . '/services.php'; + foreach ($services as $abstract => $concrete) { + if (is_callable($concrete)) { + $container->bind($abstract, $concrete); + } else { + $container->bind($abstract, $concrete); + } + } + } + + // Load all config files into the repository + foreach (glob($configPath . '/*.php') as $file) { + $key = basename($file, '.php'); + $value = require $file; + $config->set($key, $value); + // Also register individual config files for backward compatibility + $container->instance("config.{$key}", $value); + } + + // Register the unified config repository + $container->instance('config', $config); + $container->bind(\Illuminate\Config\Repository::class, function() use ($config) { + return $config; + }); +} + +/** + * Register service providers + */ +function registerServiceProviders(Container $container): void +{ + $providers = [ + // Register your service providers here + \App\Providers\AppServiceProvider::class, + \App\Providers\EventServiceProvider::class, + \App\Providers\RouteServiceProvider::class, + \App\Providers\ConsoleServiceProvider::class, + ]; + + foreach ($providers as $providerClass) { + if (class_exists($providerClass)) { + $provider = new $providerClass($container); + + if (method_exists($provider, 'register')) { + $provider->register(); + } + + if (method_exists($provider, 'boot')) { + $container->call([$provider, 'boot']); + } + } + } +} \ No newline at end of file diff --git a/config/app.php b/config/app.php new file mode 100644 index 000000000..11f203a3e --- /dev/null +++ b/config/app.php @@ -0,0 +1,77 @@ + env('APP_NAME', 'TorrentPier'), + + /* + |-------------------------------------------------------------------------- + | Application Environment + |-------------------------------------------------------------------------- + */ + 'env' => env('APP_ENV', 'production'), + + /* + |-------------------------------------------------------------------------- + | Application Debug Mode + |-------------------------------------------------------------------------- + */ + 'debug' => env('APP_DEBUG', false), + + /* + |-------------------------------------------------------------------------- + | Application URL + |-------------------------------------------------------------------------- + */ + 'url' => env('APP_URL', 'http://localhost'), + + /* + |-------------------------------------------------------------------------- + | Application Timezone + |-------------------------------------------------------------------------- + */ + 'timezone' => env('APP_TIMEZONE', 'UTC'), + + /* + |-------------------------------------------------------------------------- + | Application Locale Configuration + |-------------------------------------------------------------------------- + */ + 'locale' => env('APP_LOCALE', 'en'), + 'fallback_locale' => 'en', + + /* + |-------------------------------------------------------------------------- + | Application Key + |-------------------------------------------------------------------------- + */ + 'key' => env('APP_KEY'), + + /* + |-------------------------------------------------------------------------- + | Autoloaded Service Providers + |-------------------------------------------------------------------------- + */ + 'providers' => [ + // Add service providers here + ], + + /* + |-------------------------------------------------------------------------- + | Class Aliases + |-------------------------------------------------------------------------- + */ + 'aliases' => [ + // Add class aliases here + ], +]; \ No newline at end of file diff --git a/config/auth.php b/config/auth.php new file mode 100644 index 000000000..43b4271e8 --- /dev/null +++ b/config/auth.php @@ -0,0 +1,92 @@ + [ + 'guard' => 'web', + 'passwords' => 'users', + ], + + /* + |-------------------------------------------------------------------------- + | Authentication Guards + |-------------------------------------------------------------------------- + */ + 'guards' => [ + 'web' => [ + 'driver' => 'session', + 'provider' => 'users', + ], + + 'api' => [ + 'driver' => 'token', + 'provider' => 'users', + 'hash' => false, + ], + ], + + /* + |-------------------------------------------------------------------------- + | User Providers + |-------------------------------------------------------------------------- + */ + 'providers' => [ + 'users' => [ + 'driver' => 'database', + 'table' => 'bb_users', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Resetting Passwords + |-------------------------------------------------------------------------- + */ + 'passwords' => [ + 'users' => [ + 'provider' => 'users', + 'table' => 'password_resets', + 'expire' => 60, + 'throttle' => 60, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Password Confirmation Timeout + |-------------------------------------------------------------------------- + */ + 'password_timeout' => 10800, + + /* + |-------------------------------------------------------------------------- + | Session Configuration + |-------------------------------------------------------------------------- + */ + 'session' => [ + 'lifetime' => env('SESSION_LIFETIME', 120), + 'expire_on_close' => false, + 'encrypt' => false, + 'files' => storage_path('framework/sessions'), + 'connection' => null, + 'table' => 'sessions', + 'store' => null, + 'lottery' => [2, 100], + 'cookie' => env('SESSION_COOKIE', 'torrentpier_session'), + 'path' => '/', + 'domain' => env('SESSION_DOMAIN', null), + 'secure' => env('SESSION_SECURE_COOKIE', false), + 'http_only' => true, + 'same_site' => 'lax', + ], +]; \ No newline at end of file diff --git a/config/cache.php b/config/cache.php new file mode 100644 index 000000000..d9506b5f1 --- /dev/null +++ b/config/cache.php @@ -0,0 +1,77 @@ + env('CACHE_DRIVER', 'file'), + + /* + |-------------------------------------------------------------------------- + | Cache Stores + |-------------------------------------------------------------------------- + */ + 'stores' => [ + 'file' => [ + 'driver' => 'file', + 'path' => storage_path('framework/cache'), + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => 'cache', + 'lock_connection' => 'default', + ], + + 'memcached' => [ + 'driver' => 'memcached', + 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), + 'sasl' => [ + env('MEMCACHED_USERNAME'), + env('MEMCACHED_PASSWORD'), + ], + 'options' => [ + // Memcached::OPT_CONNECT_TIMEOUT => 2000, + ], + 'servers' => [ + [ + 'host' => env('MEMCACHED_HOST', '127.0.0.1'), + 'port' => env('MEMCACHED_PORT', 11211), + 'weight' => 100, + ], + ], + ], + + 'array' => [ + 'driver' => 'array', + 'serialize' => false, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Cache Key Prefix + |-------------------------------------------------------------------------- + */ + 'prefix' => env('CACHE_PREFIX', 'tp_cache'), + + /* + |-------------------------------------------------------------------------- + | Cache Tags + |-------------------------------------------------------------------------- + */ + 'tags' => [ + 'torrents' => 3600, + 'users' => 1800, + 'forums' => 900, + 'stats' => 300, + ], +]; \ No newline at end of file diff --git a/config/database.php b/config/database.php new file mode 100644 index 000000000..b7be4c6f8 --- /dev/null +++ b/config/database.php @@ -0,0 +1,86 @@ + env('DB_CONNECTION', 'mysql'), + + /* + |-------------------------------------------------------------------------- + | Database Connections + |-------------------------------------------------------------------------- + */ + 'connections' => [ + 'mysql' => [ + 'driver' => 'mysql', + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', 3306), + 'database' => env('DB_DATABASE', 'torrentpier'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'prefix' => env('DB_PREFIX', 'bb_'), + 'options' => [ + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, + PDO::ATTR_STRINGIFY_FETCHES => false, + ], + ], + + 'sqlite' => [ + 'driver' => 'sqlite', + 'database' => env('DB_DATABASE_SQLITE', storage_path('database.sqlite')), + 'prefix' => '', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Migration Repository Table + |-------------------------------------------------------------------------- + */ + 'migrations' => 'migrations', + + /* + |-------------------------------------------------------------------------- + | Redis Databases + |-------------------------------------------------------------------------- + */ + 'redis' => [ + 'client' => env('REDIS_CLIENT', 'phpredis'), + + 'options' => [ + 'cluster' => env('REDIS_CLUSTER', 'redis'), + 'prefix' => env('REDIS_PREFIX', 'tp_'), + ], + + 'default' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'username' => env('REDIS_USERNAME'), + 'password' => env('REDIS_PASSWORD'), + 'port' => env('REDIS_PORT', 6379), + 'database' => env('REDIS_DB', 0), + ], + + 'cache' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'username' => env('REDIS_USERNAME'), + 'password' => env('REDIS_PASSWORD'), + 'port' => env('REDIS_PORT', 6379), + 'database' => env('REDIS_CACHE_DB', 1), + ], + ], +]; \ No newline at end of file diff --git a/config/filesystems.php b/config/filesystems.php new file mode 100644 index 000000000..06f4a52fe --- /dev/null +++ b/config/filesystems.php @@ -0,0 +1,71 @@ + env('FILESYSTEM_DISK', 'local'), + + /* + |-------------------------------------------------------------------------- + | Filesystem Disks + |-------------------------------------------------------------------------- + */ + 'disks' => [ + 'local' => [ + 'driver' => 'local', + 'root' => storage_path('app'), + 'throw' => false, + ], + + 'public' => [ + 'driver' => 'local', + 'root' => storage_path('app/public'), + 'url' => env('APP_URL') . '/storage', + 'visibility' => 'public', + 'throw' => false, + ], + + 'torrents' => [ + 'driver' => 'local', + 'root' => storage_path('app/torrents'), + 'throw' => false, + ], + + 'avatars' => [ + 'driver' => 'local', + 'root' => public_path('images/avatars'), + 'url' => env('APP_URL') . '/images/avatars', + 'visibility' => 'public', + ], + + 's3' => [ + 'driver' => 's3', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION'), + 'bucket' => env('AWS_BUCKET'), + 'url' => env('AWS_URL'), + 'endpoint' => env('AWS_ENDPOINT'), + 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), + 'throw' => false, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Symbolic Links + |-------------------------------------------------------------------------- + */ + 'links' => [ + public_path('storage') => storage_path('app/public'), + ], +]; \ No newline at end of file diff --git a/config/services.php b/config/services.php index 5070cdf57..8f9f29a2c 100644 --- a/config/services.php +++ b/config/services.php @@ -1,30 +1,35 @@ function () { + return \TorrentPier\Config::getInstance(); + }, + // Future service bindings can be added here: + // Logger service example - // 'logger' => factory(function () { + // 'logger' => function () { // $logger = new \Monolog\Logger('torrentpier'); // $logger->pushHandler(new \Monolog\Handler\StreamHandler(__DIR__ . '/../internal_data/logs/app.log')); // return $logger; - // }), + // }, - // Configuration service example - // 'config' => factory(function () { - // return [ - // 'app' => require __DIR__ . '/app.php', - // 'database' => require __DIR__ . '/database.php', - // 'cache' => require __DIR__ . '/cache.php', - // ]; - // }), + // Database service example + // 'database' => function () { + // return \TorrentPier\Database\DB::getInstance(); + // }, - // Interface to implementation binding example - // 'ServiceInterface' => autowire('ConcreteService'), + // Cache service example + // 'cache' => function () { + // return \TorrentPier\Cache\Cache::getInstance(); + // }, ]; diff --git a/config/tracker.php b/config/tracker.php new file mode 100644 index 000000000..7bf49540a --- /dev/null +++ b/config/tracker.php @@ -0,0 +1,91 @@ + env('TRACKER_ANNOUNCE_INTERVAL', 900), // 15 minutes + 'min_interval' => env('TRACKER_MIN_INTERVAL', 300), // 5 minutes + 'max_peers' => env('TRACKER_MAX_PEERS', 50), + 'max_peers_per_torrent' => env('TRACKER_MAX_PEERS_PER_TORRENT', 100), + + /* + |-------------------------------------------------------------------------- + | Peer Settings + |-------------------------------------------------------------------------- + */ + 'peer_timeout' => env('TRACKER_PEER_TIMEOUT', 1800), // 30 minutes + 'peer_compact' => env('TRACKER_PEER_COMPACT', true), + 'peer_no_peer_id' => env('TRACKER_PEER_NO_PEER_ID', false), + + /* + |-------------------------------------------------------------------------- + | Ratio Requirements + |-------------------------------------------------------------------------- + */ + 'ratio_required' => env('TRACKER_RATIO_REQUIRED', false), + 'min_ratio' => env('TRACKER_MIN_RATIO', 0.5), + 'ratio_warning_threshold' => env('TRACKER_RATIO_WARNING', 0.8), + + /* + |-------------------------------------------------------------------------- + | Upload/Download Limits + |-------------------------------------------------------------------------- + */ + 'max_upload_speed' => env('TRACKER_MAX_UPLOAD_SPEED', 0), // 0 = unlimited + 'max_download_speed' => env('TRACKER_MAX_DOWNLOAD_SPEED', 0), // 0 = unlimited + 'max_torrents_per_user' => env('TRACKER_MAX_TORRENTS_PER_USER', 0), // 0 = unlimited + + /* + |-------------------------------------------------------------------------- + | Security Settings + |-------------------------------------------------------------------------- + */ + 'passkey_required' => env('TRACKER_PASSKEY_REQUIRED', true), + 'ip_validation' => env('TRACKER_IP_VALIDATION', true), + 'user_agent_validation' => env('TRACKER_USER_AGENT_VALIDATION', false), + + /* + |-------------------------------------------------------------------------- + | Allowed Clients + |-------------------------------------------------------------------------- + */ + 'allowed_clients' => [ + 'qBittorrent', + 'uTorrent', + 'BitTorrent', + 'Transmission', + 'Deluge', + 'rtorrent', + 'libtorrent', + ], + + /* + |-------------------------------------------------------------------------- + | Banned Clients + |-------------------------------------------------------------------------- + */ + 'banned_clients' => [ + 'BitComet', + 'BitLord', + 'Thunder', + 'Xunlei', + ], + + /* + |-------------------------------------------------------------------------- + | Statistics + |-------------------------------------------------------------------------- + */ + 'stats_update_interval' => env('TRACKER_STATS_UPDATE_INTERVAL', 300), // 5 minutes + 'enable_scrape' => env('TRACKER_ENABLE_SCRAPE', true), + 'scrape_interval' => env('TRACKER_SCRAPE_INTERVAL', 600), // 10 minutes +]; \ No newline at end of file diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php new file mode 100644 index 000000000..359f8994c --- /dev/null +++ b/database/factories/UserFactory.php @@ -0,0 +1,81 @@ + str('user_')->append(str()->random(8))->lower(), + 'user_email' => fake()->email(), + 'user_password' => password_hash('password', PASSWORD_BCRYPT), + 'user_level' => 0, // Regular user + 'user_active' => 1, + 'user_regdate' => now()->timestamp, + 'user_lastvisit' => now()->timestamp, + 'user_timezone' => 0, + 'user_lang' => 'en', + 'user_dateformat' => 'd M Y H:i', + ]; + } + + /** + * Create a user instance + */ + public function make(array $attributes = []): User + { + $data = array_merge($this->definition(), $attributes); + return new User(DB(), $data); + } + + /** + * Create and save a user instance + */ + public function create(array $attributes = []): User + { + $user = $this->make($attributes); + $user->save(); + return $user; + } + + /** + * Create an admin user + */ + public function admin(): self + { + return $this->state(['user_level' => 1]); + } + + /** + * Create a moderator user + */ + public function moderator(): self + { + return $this->state(['user_level' => 2]); + } + + /** + * Apply state to the factory + */ + public function state(array $state): self + { + $clone = clone $this; + $clone->states[] = $state; + return $clone; + } + + private array $states = []; +} \ No newline at end of file diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php new file mode 100644 index 000000000..7d76605cd --- /dev/null +++ b/database/seeders/DatabaseSeeder.php @@ -0,0 +1,34 @@ +call(UserSeeder::class); + // $this->call(ForumSeeder::class); + // $this->call(TorrentSeeder::class); + } + + /** + * Call a seeder class + */ + protected function call(string $seederClass): void + { + echo "Seeding: {$seederClass}\n"; + $seeder = new $seederClass(); + $seeder->run(); + } +} \ No newline at end of file diff --git a/library/attach_mod/displaying.php b/library/attach_mod/displaying.php index 9f6ee8394..5d10d3479 100644 --- a/library/attach_mod/displaying.php +++ b/library/attach_mod/displaying.php @@ -92,7 +92,7 @@ function init_display_template($template_var, $replacement, $filename = 'viewtop */ function display_post_attachments($post_id, $switch_attachment) { - global $attach_config, $is_auth; + global $attach_config, $is_auth, $attachments; if ((int)$switch_attachment == 0 || (int)$attach_config['disable_mod']) { return; @@ -170,7 +170,7 @@ function display_attachments($post_id) { global $template, $upload_dir, $userdata, $allowed_extensions, $display_categories, $download_modes, $lang, $attachments, $upload_icons, $attach_config; - $num_attachments = @count($attachments['_' . $post_id]); + $num_attachments = isset($attachments['_' . $post_id]) ? count($attachments['_' . $post_id]) : 0; if ($num_attachments == 0) { return; diff --git a/library/config.php b/library/config.php index b5c95b7f9..f46963db5 100644 --- a/library/config.php +++ b/library/config.php @@ -54,7 +54,7 @@ $bb_cfg['db_alias'] = [ // Cache $bb_cfg['cache'] = [ - 'db_dir' => realpath(BB_ROOT) . '/internal_data/cache/filecache/', + 'db_dir' => BB_PATH . '/storage/framework/cache/', 'prefix' => 'tp_', 'memcached' => [ 'host' => '127.0.0.1', diff --git a/admin/admin_attach_cp.php b/public/admin/admin_attach_cp.php similarity index 100% rename from admin/admin_attach_cp.php rename to public/admin/admin_attach_cp.php diff --git a/admin/admin_attachments.php b/public/admin/admin_attachments.php similarity index 100% rename from admin/admin_attachments.php rename to public/admin/admin_attachments.php diff --git a/admin/admin_board.php b/public/admin/admin_board.php similarity index 100% rename from admin/admin_board.php rename to public/admin/admin_board.php diff --git a/admin/admin_bt_forum_cfg.php b/public/admin/admin_bt_forum_cfg.php similarity index 100% rename from admin/admin_bt_forum_cfg.php rename to public/admin/admin_bt_forum_cfg.php diff --git a/admin/admin_cron.php b/public/admin/admin_cron.php similarity index 100% rename from admin/admin_cron.php rename to public/admin/admin_cron.php diff --git a/admin/admin_disallow.php b/public/admin/admin_disallow.php similarity index 100% rename from admin/admin_disallow.php rename to public/admin/admin_disallow.php diff --git a/admin/admin_extensions.php b/public/admin/admin_extensions.php similarity index 100% rename from admin/admin_extensions.php rename to public/admin/admin_extensions.php diff --git a/admin/admin_forum_prune.php b/public/admin/admin_forum_prune.php similarity index 100% rename from admin/admin_forum_prune.php rename to public/admin/admin_forum_prune.php diff --git a/admin/admin_forumauth.php b/public/admin/admin_forumauth.php similarity index 100% rename from admin/admin_forumauth.php rename to public/admin/admin_forumauth.php diff --git a/admin/admin_forumauth_list.php b/public/admin/admin_forumauth_list.php similarity index 100% rename from admin/admin_forumauth_list.php rename to public/admin/admin_forumauth_list.php diff --git a/admin/admin_forums.php b/public/admin/admin_forums.php similarity index 100% rename from admin/admin_forums.php rename to public/admin/admin_forums.php diff --git a/admin/admin_groups.php b/public/admin/admin_groups.php similarity index 100% rename from admin/admin_groups.php rename to public/admin/admin_groups.php diff --git a/admin/admin_log.php b/public/admin/admin_log.php similarity index 100% rename from admin/admin_log.php rename to public/admin/admin_log.php diff --git a/admin/admin_mass_email.php b/public/admin/admin_mass_email.php similarity index 100% rename from admin/admin_mass_email.php rename to public/admin/admin_mass_email.php diff --git a/admin/admin_migrations.php b/public/admin/admin_migrations.php similarity index 100% rename from admin/admin_migrations.php rename to public/admin/admin_migrations.php diff --git a/admin/admin_phpinfo.php b/public/admin/admin_phpinfo.php similarity index 100% rename from admin/admin_phpinfo.php rename to public/admin/admin_phpinfo.php diff --git a/admin/admin_ranks.php b/public/admin/admin_ranks.php similarity index 100% rename from admin/admin_ranks.php rename to public/admin/admin_ranks.php diff --git a/admin/admin_rebuild_search.php b/public/admin/admin_rebuild_search.php similarity index 100% rename from admin/admin_rebuild_search.php rename to public/admin/admin_rebuild_search.php diff --git a/admin/admin_robots.php b/public/admin/admin_robots.php similarity index 100% rename from admin/admin_robots.php rename to public/admin/admin_robots.php diff --git a/admin/admin_sitemap.php b/public/admin/admin_sitemap.php similarity index 100% rename from admin/admin_sitemap.php rename to public/admin/admin_sitemap.php diff --git a/admin/admin_smilies.php b/public/admin/admin_smilies.php similarity index 100% rename from admin/admin_smilies.php rename to public/admin/admin_smilies.php diff --git a/admin/admin_terms.php b/public/admin/admin_terms.php similarity index 100% rename from admin/admin_terms.php rename to public/admin/admin_terms.php diff --git a/admin/admin_ug_auth.php b/public/admin/admin_ug_auth.php similarity index 100% rename from admin/admin_ug_auth.php rename to public/admin/admin_ug_auth.php diff --git a/admin/admin_user_ban.php b/public/admin/admin_user_ban.php similarity index 100% rename from admin/admin_user_ban.php rename to public/admin/admin_user_ban.php diff --git a/admin/admin_user_search.php b/public/admin/admin_user_search.php similarity index 100% rename from admin/admin_user_search.php rename to public/admin/admin_user_search.php diff --git a/admin/admin_words.php b/public/admin/admin_words.php similarity index 100% rename from admin/admin_words.php rename to public/admin/admin_words.php diff --git a/admin/index.php b/public/admin/index.php similarity index 100% rename from admin/index.php rename to public/admin/index.php diff --git a/admin/pagestart.php b/public/admin/pagestart.php similarity index 100% rename from admin/pagestart.php rename to public/admin/pagestart.php diff --git a/admin/stats/tr_stats.php b/public/admin/stats/tr_stats.php similarity index 100% rename from admin/stats/tr_stats.php rename to public/admin/stats/tr_stats.php diff --git a/admin/stats/tracker.php b/public/admin/stats/tracker.php similarity index 100% rename from admin/stats/tracker.php rename to public/admin/stats/tracker.php diff --git a/public/index.php b/public/index.php new file mode 100644 index 000000000..c38707d17 --- /dev/null +++ b/public/index.php @@ -0,0 +1,37 @@ +dispatch($request); +} catch (\Exception $e) { + // Simple error handling - create Laravel-style response + $response = new \Illuminate\Http\Response('Internal Server Error', 500); +} + +// Send the response - handle JsonResponse specially if headers already sent +if ($response instanceof \Illuminate\Http\JsonResponse && headers_sent()) { + echo $response->getContent(); +} else { + $response->send(); +} \ No newline at end of file diff --git a/resources/views/hello.php b/resources/views/hello.php new file mode 100644 index 000000000..cd3beb249 --- /dev/null +++ b/resources/views/hello.php @@ -0,0 +1,139 @@ + + + + + + Hello World - <?= htmlspecialchars($siteName) ?> + + + +
+

๐Ÿš€ Hello World from !

+ +

Welcome to TorrentPier 3.0 with modern MVC architecture! This page demonstrates the new Laravel-inspired structure using .

+ +
+

๐ŸŽฏ Route Information

+ URI: uri) ?>
+ Method: method) ?>
+ Time:
+ Controller: HelloWorldController +
+ +

โœจ New Features & Technologies

+ +
+
+

๐Ÿ—๏ธ MVC Architecture

+

Clean separation of concerns with Models, Views, and Controllers following Laravel conventions.

+
+ +
+

๐Ÿ“ฆ Illuminate HTTP

+

Powerful request/response handling with JsonResponse, RedirectResponse, and more.

+
+ +
+

๐Ÿ› ๏ธ Illuminate Support

+

Collections, string helpers, array utilities, and Carbon date handling.

+
+ +
+

๐Ÿ“ Laravel-style Structure

+

Familiar directory layout: /app, /config, /database, /routes, /storage, etc.

+
+
+ +

๐Ÿงช Try the New Features

+ +
+// Modern helper functions now available:
+$users = collect(['Alice', 'Bob', 'Charlie']);
+$title = str('hello world')->title();
+$time = now()->format('Y-m-d H:i:s');
+$value = data_get($config, 'app.name', 'default'); +
+ +
+ ๐Ÿ“Š View JSON API + ๐ŸŽจ Feature Demo + ๐Ÿ  Back to Main Site +
+ +
+ Technical Stack:
+ ๐Ÿ”น League/Route for routing
+ ๐Ÿ”น Illuminate HTTP & Support packages
+ ๐Ÿ”น PHP-DI dependency injection
+ ๐Ÿ”น Nette Database for data access
+ ๐Ÿ”น PSR-7 compatibility layer +
+
+ + \ No newline at end of file diff --git a/routes/admin.php b/routes/admin.php new file mode 100644 index 000000000..d817dd63e --- /dev/null +++ b/routes/admin.php @@ -0,0 +1,56 @@ +get('/', 'App\Http\Controllers\Admin\DashboardController::index')->name('admin.dashboard'); + +// User Management +// $router->get('/users', 'App\Http\Controllers\Admin\UserController::index')->name('admin.users.index'); +// $router->get('/users/{id}/edit', 'App\Http\Controllers\Admin\UserController::edit')->name('admin.users.edit'); +// $router->put('/users/{id}', 'App\Http\Controllers\Admin\UserController::update')->name('admin.users.update'); +// $router->delete('/users/{id}', 'App\Http\Controllers\Admin\UserController::destroy')->name('admin.users.destroy'); + +// Torrent Management +// $router->get('/torrents', 'App\Http\Controllers\Admin\TorrentController::index')->name('admin.torrents.index'); +// $router->get('/torrents/{id}', 'App\Http\Controllers\Admin\TorrentController::show')->name('admin.torrents.show'); +// $router->delete('/torrents/{id}', 'App\Http\Controllers\Admin\TorrentController::destroy')->name('admin.torrents.destroy'); + +// Forum Management +// $router->resource('forums', 'App\Http\Controllers\Admin\ForumController'); + +// Settings +// $router->get('/settings', 'App\Http\Controllers\Admin\SettingsController::index')->name('admin.settings.index'); +// $router->put('/settings', 'App\Http\Controllers\Admin\SettingsController::update')->name('admin.settings.update'); + +// Logs +// $router->get('/logs', 'App\Http\Controllers\Admin\LogController::index')->name('admin.logs.index'); +// $router->get('/logs/{type}', 'App\Http\Controllers\Admin\LogController::show')->name('admin.logs.show'); + +// Reports +// $router->get('/reports', 'App\Http\Controllers\Admin\ReportController::index')->name('admin.reports.index'); +// $router->get('/reports/{id}', 'App\Http\Controllers\Admin\ReportController::show')->name('admin.reports.show'); +// $router->put('/reports/{id}/resolve', 'App\Http\Controllers\Admin\ReportController::resolve')->name('admin.reports.resolve'); + +// Bans +// $router->get('/bans', 'App\Http\Controllers\Admin\BanController::index')->name('admin.bans.index'); +// $router->post('/bans', 'App\Http\Controllers\Admin\BanController::store')->name('admin.bans.store'); +// $router->delete('/bans/{id}', 'App\Http\Controllers\Admin\BanController::destroy')->name('admin.bans.destroy'); + +// Statistics +// $router->get('/stats', 'App\Http\Controllers\Admin\StatsController::index')->name('admin.stats.index'); +// $router->get('/stats/users', 'App\Http\Controllers\Admin\StatsController::users')->name('admin.stats.users'); +// $router->get('/stats/torrents', 'App\Http\Controllers\Admin\StatsController::torrents')->name('admin.stats.torrents'); +// $router->get('/stats/tracker', 'App\Http\Controllers\Admin\StatsController::tracker')->name('admin.stats.tracker'); \ No newline at end of file diff --git a/routes/api.php b/routes/api.php new file mode 100644 index 000000000..466ce4771 --- /dev/null +++ b/routes/api.php @@ -0,0 +1,49 @@ +get('/users', 'App\Http\Controllers\Api\UserController::index')->name('api.users.index'); +$router->get('/users/{id}', 'App\Http\Controllers\Api\UserController::show')->name('api.users.show'); +$router->post('/users/register', 'App\Http\Controllers\Api\UserController::register')->name('api.users.register'); + +// Future API routes: + +// Authentication +// $router->post('/auth/login', 'App\Http\Controllers\Api\AuthController::login')->name('api.auth.login'); +// $router->post('/auth/logout', 'App\Http\Controllers\Api\AuthController::logout')->name('api.auth.logout'); +// $router->post('/auth/refresh', 'App\Http\Controllers\Api\AuthController::refresh')->name('api.auth.refresh'); + +// Tracker API +// $router->get('/torrents', 'App\Http\Controllers\Api\TorrentController::index')->name('api.torrents.index'); +// $router->get('/torrents/{id}', 'App\Http\Controllers\Api\TorrentController::show')->name('api.torrents.show'); +// $router->post('/torrents', 'App\Http\Controllers\Api\TorrentController::store')->name('api.torrents.store'); +// $router->put('/torrents/{id}', 'App\Http\Controllers\Api\TorrentController::update')->name('api.torrents.update'); +// $router->delete('/torrents/{id}', 'App\Http\Controllers\Api\TorrentController::destroy')->name('api.torrents.destroy'); + +// Forum API +// $router->resource('/forums', 'App\Http\Controllers\Api\ForumController'); + +// Stats API +// $router->get('/stats/tracker', 'App\Http\Controllers\Api\StatsController::tracker')->name('api.stats.tracker'); +// $router->get('/stats/forum', 'App\Http\Controllers\Api\StatsController::forum')->name('api.stats.forum'); +// $router->get('/stats/users', 'App\Http\Controllers\Api\StatsController::users')->name('api.stats.users'); + +// Search API +// $router->get('/search/torrents', 'App\Http\Controllers\Api\SearchController::torrents')->name('api.search.torrents'); +// $router->get('/search/users', 'App\Http\Controllers\Api\SearchController::users')->name('api.search.users'); +// $router->get('/search/forums', 'App\Http\Controllers\Api\SearchController::forums')->name('api.search.forums'); \ No newline at end of file diff --git a/routes/web.php b/routes/web.php new file mode 100644 index 000000000..baaf599fa --- /dev/null +++ b/routes/web.php @@ -0,0 +1,80 @@ +get('/hello', 'App\Http\Controllers\Web\HelloWorldController::index')->name('hello'); +$router->get('/hello.json', 'App\Http\Controllers\Web\HelloWorldController::jsonResponse')->name('hello.json'); +$router->get('/hello/json', 'App\Http\Controllers\Web\HelloWorldController::jsonResponse')->name('hello.json.alt'); +$router->get('/hello/features', 'App\Http\Controllers\Web\HelloWorldController::features')->name('hello.features'); + +// Future routes can be added here: + +// Home page +// $router->get('/', 'App\Http\Controllers\Web\HomeController::index')->name('home'); + +// Forum routes +// $router->get('/forum', 'App\Http\Controllers\Web\ForumController::index')->name('forum.index'); +// $router->get('/forum/{id}', 'App\Http\Controllers\Web\ForumController::show')->name('forum.show'); +// $router->get('/forum/{id}/topics', 'App\Http\Controllers\Web\TopicController::index')->name('forum.topics'); + +// Tracker routes +// $router->get('/tracker', 'App\Http\Controllers\Web\TrackerController::index')->name('tracker.index'); +// $router->get('/tracker/torrent/{id}', 'App\Http\Controllers\Web\TrackerController::show')->name('torrent.show'); +// $router->get('/tracker/categories', 'App\Http\Controllers\Web\CategoryController::index')->name('categories.index'); + +// User routes +// $router->get('/profile/{id}', 'App\Http\Controllers\Web\UserController::profile')->name('user.profile'); +// $router->get('/users', 'App\Http\Controllers\Web\UserController::index')->name('users.index'); + +// Authentication routes (if using custom auth) +// $router->get('/login', 'App\Http\Controllers\Auth\LoginController::showLoginForm')->name('login'); +// $router->post('/login', 'App\Http\Controllers\Auth\LoginController::login'); +// $router->post('/logout', 'App\Http\Controllers\Auth\LoginController::logout')->name('logout'); +// $router->get('/register', 'App\Http\Controllers\Auth\RegisterController::showRegistrationForm')->name('register'); +// $router->post('/register', 'App\Http\Controllers\Auth\RegisterController::register'); + +// Legacy routes - explicit routes for each legacy controller +$router->get('/', 'App\Http\Controllers\Web\LegacyController@index')->name('legacy.index'); +$router->get('/ajax.php', 'App\Http\Controllers\Web\LegacyController@ajax')->name('legacy.ajax'); +$router->get('/dl.php', 'App\Http\Controllers\Web\LegacyController@dl')->name('legacy.dl'); +$router->get('/dl_list.php', 'App\Http\Controllers\Web\LegacyController@dl_list')->name('legacy.dl_list'); +$router->get('/feed.php', 'App\Http\Controllers\Web\LegacyController@feed')->name('legacy.feed'); +$router->get('/filelist.php', 'App\Http\Controllers\Web\LegacyController@filelist')->name('legacy.filelist'); +$router->get('/group.php', 'App\Http\Controllers\Web\LegacyController@group')->name('legacy.group'); +$router->get('/group_edit.php', 'App\Http\Controllers\Web\LegacyController@group_edit')->name('legacy.group_edit'); +$router->get('/index.php', 'App\Http\Controllers\Web\LegacyController@index')->name('legacy.index_php'); +$router->get('/info.php', 'App\Http\Controllers\Web\LegacyController@info')->name('legacy.info'); +$router->get('/login.php', 'App\Http\Controllers\Web\LegacyController@login')->name('legacy.login'); +$router->get('/memberlist.php', 'App\Http\Controllers\Web\LegacyController@memberlist')->name('legacy.memberlist'); +$router->get('/modcp.php', 'App\Http\Controllers\Web\LegacyController@modcp')->name('legacy.modcp'); +$router->get('/playback_m3u.php', 'App\Http\Controllers\Web\LegacyController@playback_m3u')->name('legacy.playback_m3u'); +$router->get('/poll.php', 'App\Http\Controllers\Web\LegacyController@poll')->name('legacy.poll'); +$router->get('/posting.php', 'App\Http\Controllers\Web\LegacyController@posting')->name('legacy.posting'); +$router->get('/privmsg.php', 'App\Http\Controllers\Web\LegacyController@privmsg')->name('legacy.privmsg'); +$router->get('/profile.php', 'App\Http\Controllers\Web\LegacyController@profile')->name('legacy.profile'); +$router->get('/search.php', 'App\Http\Controllers\Web\LegacyController@search')->name('legacy.search'); +$router->get('/terms.php', 'App\Http\Controllers\Web\LegacyController@terms')->name('legacy.terms'); +$router->get('/tracker.php', 'App\Http\Controllers\Web\LegacyController@tracker')->name('legacy.tracker'); +$router->get('/viewforum.php', 'App\Http\Controllers\Web\LegacyController@viewforum')->name('legacy.viewforum'); +$router->get('/viewtopic.php', 'App\Http\Controllers\Web\LegacyController@viewtopic')->name('legacy.viewtopic'); + +// POST routes for legacy controllers that need them +$router->post('/ajax.php', 'App\Http\Controllers\Web\LegacyController@ajax')->name('legacy.ajax.post'); +$router->post('/login.php', 'App\Http\Controllers\Web\LegacyController@login')->name('legacy.login.post'); +$router->post('/posting.php', 'App\Http\Controllers\Web\LegacyController@posting')->name('legacy.posting.post'); +$router->post('/privmsg.php', 'App\Http\Controllers\Web\LegacyController@privmsg')->name('legacy.privmsg.post'); +$router->post('/profile.php', 'App\Http\Controllers\Web\LegacyController@profile')->name('legacy.profile.post'); +$router->post('/search.php', 'App\Http\Controllers\Web\LegacyController@search')->name('legacy.search.post'); \ No newline at end of file