mirror of
https://github.com/torrentpier/torrentpier
synced 2025-08-21 13:54:02 -07:00
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.
This commit is contained in:
parent
9a1e17de8d
commit
e5ca170b4c
70 changed files with 3999 additions and 21 deletions
137
README.md
137
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> # 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.
|
||||
|
|
170
app/Http/Controllers/Api/UserController.php
Normal file
170
app/Http/Controllers/Api/UserController.php
Normal file
|
@ -0,0 +1,170 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\RegisterUserRequest;
|
||||
use App\Models\User;
|
||||
use App\Services\User\UserService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
/**
|
||||
* User API Controller - Pure Laravel-style implementation
|
||||
*/
|
||||
class UserController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private UserService $userService
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new user
|
||||
*/
|
||||
public function register(RegisterUserRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
// Laravel automatically validates the request via RegisterUserRequest
|
||||
$validated = $request->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);
|
||||
}
|
||||
}
|
||||
}
|
94
app/Http/Controllers/Controller.php
Normal file
94
app/Http/Controllers/Controller.php
Normal file
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Validation\Factory as ValidationFactory;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
/**
|
||||
* Base Controller class using Illuminate HTTP components
|
||||
*/
|
||||
abstract class Controller
|
||||
{
|
||||
protected ValidationFactory $validator;
|
||||
|
||||
public function __construct(ValidationFactory $validator)
|
||||
{
|
||||
$this->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');
|
||||
}
|
||||
}
|
153
app/Http/Controllers/Web/HelloWorldController.php
Normal file
153
app/Http/Controllers/Web/HelloWorldController.php
Normal file
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Web;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Validation\Factory as ValidationFactory;
|
||||
use TorrentPier\Config;
|
||||
|
||||
class HelloWorldController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
ValidationFactory $validator,
|
||||
private Config $config
|
||||
) {
|
||||
parent::__construct($validator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the hello world page
|
||||
*/
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$siteName = $this->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'
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
219
app/Http/Controllers/Web/LegacyController.php
Normal file
219
app/Http/Controllers/Web/LegacyController.php
Normal file
|
@ -0,0 +1,219 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Web;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use TorrentPier\Config;
|
||||
|
||||
class LegacyController
|
||||
{
|
||||
private Config $config;
|
||||
|
||||
public function __construct(Config $config)
|
||||
{
|
||||
$this->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(
|
||||
"<h1>404 - Not Found</h1><p>Legacy controller '{$controller}' not found</p>",
|
||||
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 = "
|
||||
<h1>Legacy Controller Error</h1>
|
||||
<p><strong>Controller:</strong> {$controller}</p>
|
||||
<p><strong>Error:</strong> " . htmlspecialchars($e->getMessage()) . "</p>
|
||||
<p><strong>File:</strong> " . htmlspecialchars($e->getFile()) . ":" . $e->getLine() . "</p>
|
||||
";
|
||||
|
||||
if (function_exists('dev') && dev()->isDebugEnabled()) {
|
||||
$errorHtml .= "<pre>" . htmlspecialchars($e->getTraceAsString()) . "</pre>";
|
||||
}
|
||||
|
||||
return new Response($errorHtml, 500, ['Content-Type' => 'text/html']);
|
||||
}
|
||||
}
|
||||
}
|
116
app/Http/Requests/FormRequest.php
Normal file
116
app/Http/Requests/FormRequest.php
Normal file
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
/**
|
||||
* Base Form Request class for validation
|
||||
*/
|
||||
abstract class FormRequest
|
||||
{
|
||||
protected Request $request;
|
||||
protected array $validated = [];
|
||||
|
||||
public function __construct(Request $request)
|
||||
{
|
||||
$this->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();
|
||||
}
|
||||
}
|
120
app/Http/Requests/RegisterUserRequest.php
Normal file
120
app/Http/Requests/RegisterUserRequest.php
Normal file
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
/**
|
||||
* User Registration Request Validation
|
||||
*/
|
||||
class RegisterUserRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'username' => [
|
||||
'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');
|
||||
}
|
||||
}
|
223
app/Http/Routing/Router.php
Normal file
223
app/Http/Routing/Router.php
Normal file
|
@ -0,0 +1,223 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Routing;
|
||||
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Events\Dispatcher;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Routing\Router as LaravelRouter;
|
||||
use Illuminate\Routing\UrlGenerator;
|
||||
|
||||
/**
|
||||
* Router
|
||||
*
|
||||
* Illuminate Routing wrapper for TorrentPier
|
||||
*/
|
||||
class Router
|
||||
{
|
||||
private LaravelRouter $router;
|
||||
private Container $container;
|
||||
|
||||
public function __construct(Container $container)
|
||||
{
|
||||
$this->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);
|
||||
}
|
||||
}
|
||||
}
|
41
app/Listeners/SendWelcomeEmail.php
Normal file
41
app/Listeners/SendWelcomeEmail.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\UserRegistered;
|
||||
use Illuminate\Contracts\Events\ShouldHandleEventsAfterCommit;
|
||||
|
||||
/**
|
||||
* Send welcome email to newly registered users
|
||||
*/
|
||||
class SendWelcomeEmail implements ShouldHandleEventsAfterCommit
|
||||
{
|
||||
/**
|
||||
* Handle the event
|
||||
*/
|
||||
public function handle(UserRegistered $event): void
|
||||
{
|
||||
// TODO: Implement email sending logic
|
||||
// This is where you would queue an email to be sent
|
||||
// For now, just log the event
|
||||
|
||||
if (function_exists('bb_log')) {
|
||||
bb_log(sprintf(
|
||||
'Welcome email queued for user: %s (ID: %d, Email: %s)',
|
||||
$event->getUsername(),
|
||||
$event->getUserId(),
|
||||
$event->getEmail()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the listener should be queued
|
||||
*/
|
||||
public function shouldQueue(UserRegistered $event): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
32
app/Listeners/UpdateUserStatistics.php
Normal file
32
app/Listeners/UpdateUserStatistics.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\TorrentUploaded;
|
||||
|
||||
/**
|
||||
* Update user statistics when a torrent is uploaded
|
||||
*/
|
||||
class UpdateUserStatistics
|
||||
{
|
||||
/**
|
||||
* Handle the event
|
||||
*/
|
||||
public function handle(TorrentUploaded $event): void
|
||||
{
|
||||
// TODO: Implement statistics update logic
|
||||
// This would typically update the user's upload count, ratio, etc.
|
||||
|
||||
if (function_exists('bb_log')) {
|
||||
bb_log(sprintf(
|
||||
'User statistics update triggered for user %d after uploading torrent: %s (ID: %d, Size: %d bytes)',
|
||||
$event->getUploaderId(),
|
||||
$event->getTorrentName(),
|
||||
$event->getTorrentId(),
|
||||
$event->getSize()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
223
app/Models/Model.php
Normal file
223
app/Models/Model.php
Normal file
|
@ -0,0 +1,223 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use TorrentPier\Database\Database;
|
||||
|
||||
/**
|
||||
* Base Model class for all models
|
||||
* Provides basic database operations using Nette Database
|
||||
*/
|
||||
abstract class Model
|
||||
{
|
||||
protected string $table;
|
||||
protected string $primaryKey = 'id';
|
||||
protected array $attributes = [];
|
||||
protected array $original = [];
|
||||
|
||||
public function __construct(
|
||||
protected Database $db,
|
||||
array $attributes = []
|
||||
) {
|
||||
$this->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;
|
||||
}
|
||||
}
|
109
app/Models/Torrent.php
Normal file
109
app/Models/Torrent.php
Normal file
|
@ -0,0 +1,109 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
/**
|
||||
* Torrent Model
|
||||
*
|
||||
* Represents a torrent in the database
|
||||
*/
|
||||
class Torrent extends Model
|
||||
{
|
||||
protected string $table = 'bb_bt_torrents';
|
||||
protected string $primaryKey = 'topic_id';
|
||||
|
||||
/**
|
||||
* Get active peers for this torrent
|
||||
*/
|
||||
public function getPeers(): array
|
||||
{
|
||||
return $this->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;
|
||||
}
|
||||
}
|
139
app/Models/User.php
Normal file
139
app/Models/User.php
Normal file
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
/**
|
||||
* User Model
|
||||
*
|
||||
* Represents a user in the system
|
||||
*/
|
||||
class User extends Model
|
||||
{
|
||||
protected string $table = 'bb_users';
|
||||
protected string $primaryKey = 'user_id';
|
||||
|
||||
/**
|
||||
* Find user by username
|
||||
*/
|
||||
public static function findByUsername(string $username): ?self
|
||||
{
|
||||
return self::findBy('username', $username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find user by email
|
||||
*/
|
||||
public static function findByEmail(string $email): ?self
|
||||
{
|
||||
return self::findBy('user_email', $email);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's torrents
|
||||
*/
|
||||
public function getTorrents(): array
|
||||
{
|
||||
return $this->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);
|
||||
}
|
||||
}
|
66
app/Providers/AppServiceProvider.php
Normal file
66
app/Providers/AppServiceProvider.php
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
/**
|
||||
* Application Service Provider
|
||||
*
|
||||
* Bootstrap any application services here
|
||||
*/
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
// Register application services
|
||||
$this->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);
|
||||
}
|
||||
}
|
76
app/Providers/ConsoleServiceProvider.php
Normal file
76
app/Providers/ConsoleServiceProvider.php
Normal file
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
/**
|
||||
* Console Service Provider
|
||||
*
|
||||
* Automatically discovers and registers console commands
|
||||
*/
|
||||
class ConsoleServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register console commands
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
64
app/Providers/EventServiceProvider.php
Normal file
64
app/Providers/EventServiceProvider.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Events\UserRegistered;
|
||||
use App\Events\TorrentUploaded;
|
||||
use App\Listeners\SendWelcomeEmail;
|
||||
use App\Listeners\UpdateUserStatistics;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
/**
|
||||
* Event Service Provider
|
||||
*
|
||||
* Register event listeners and subscribers
|
||||
*/
|
||||
class EventServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* The event listener mappings for the application
|
||||
*/
|
||||
protected array $listen = [
|
||||
UserRegistered::class => [
|
||||
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;
|
||||
}
|
||||
}
|
123
app/Providers/RouteServiceProvider.php
Normal file
123
app/Providers/RouteServiceProvider.php
Normal file
|
@ -0,0 +1,123 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Http\Routing\Router;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* Route Service Provider
|
||||
*
|
||||
* Loads and registers application routes using Illuminate Routing
|
||||
*/
|
||||
class RouteServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* The path to the "home" route for your application
|
||||
*/
|
||||
public const HOME = '/';
|
||||
|
||||
/**
|
||||
* Register services
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
// Register the Router
|
||||
$this->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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
17
app/Providers/ServiceProvider.php
Normal file
17
app/Providers/ServiceProvider.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider as IlluminateServiceProvider;
|
||||
|
||||
/**
|
||||
* Base Service Provider
|
||||
*
|
||||
* All application service providers should extend this class
|
||||
*/
|
||||
abstract class ServiceProvider extends IlluminateServiceProvider
|
||||
{
|
||||
// Add any application-specific service provider functionality here
|
||||
}
|
244
app/Services/Tracker/TorrentService.php
Normal file
244
app/Services/Tracker/TorrentService.php
Normal file
|
@ -0,0 +1,244 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Tracker;
|
||||
|
||||
use App\Models\Torrent;
|
||||
use App\Models\User;
|
||||
use TorrentPier\Cache\CacheManager;
|
||||
use TorrentPier\Database\Database;
|
||||
|
||||
/**
|
||||
* Torrent Service
|
||||
*
|
||||
* Handles business logic for torrent operations
|
||||
*/
|
||||
class TorrentService
|
||||
{
|
||||
public function __construct(
|
||||
private Database $db,
|
||||
private CacheManager $cache
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Register a new torrent
|
||||
*/
|
||||
public function register(array $data, User $user): Torrent
|
||||
{
|
||||
// Validate torrent data
|
||||
$this->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('*');
|
||||
}
|
||||
}
|
196
app/Services/User/UserService.php
Normal file
196
app/Services/User/UserService.php
Normal file
|
@ -0,0 +1,196 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\User;
|
||||
|
||||
use App\Models\User;
|
||||
use TorrentPier\Cache\CacheManager;
|
||||
use TorrentPier\Database\Database;
|
||||
|
||||
/**
|
||||
* User Service
|
||||
*
|
||||
* Handles business logic for user operations
|
||||
*/
|
||||
class UserService
|
||||
{
|
||||
public function __construct(
|
||||
private Database $db,
|
||||
private CacheManager $cache
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Register a new user
|
||||
*/
|
||||
public function register(array $data): User
|
||||
{
|
||||
// Validate data
|
||||
$this->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');
|
||||
}
|
||||
}
|
||||
}
|
42
app/Services/UserService.php
Normal file
42
app/Services/UserService.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Events\UserRegistered;
|
||||
|
||||
/**
|
||||
* User Service
|
||||
*
|
||||
* Example service demonstrating event usage
|
||||
*/
|
||||
class UserService
|
||||
{
|
||||
/**
|
||||
* Register a new user
|
||||
*
|
||||
* @param array $userData User registration data
|
||||
* @return int User ID
|
||||
*/
|
||||
public function registerUser(array $userData): int
|
||||
{
|
||||
// TODO: Implement actual user registration logic
|
||||
// This is a simplified example
|
||||
|
||||
// Simulate user creation
|
||||
$userId = random_int(1000, 9999);
|
||||
$username = $userData['username'] ?? 'user' . $userId;
|
||||
$email = $userData['email'] ?? $username . '@example.com';
|
||||
|
||||
// Dispatch the UserRegistered event
|
||||
event(new UserRegistered(
|
||||
userId: $userId,
|
||||
username: $username,
|
||||
email: $email,
|
||||
registeredAt: new \DateTime()
|
||||
));
|
||||
|
||||
return $userId;
|
||||
}
|
||||
}
|
243
app/helpers.php
Normal file
243
app/helpers.php
Normal file
|
@ -0,0 +1,243 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Laravel-style helper functions for TorrentPier
|
||||
*/
|
||||
|
||||
if (!function_exists('app_path')) {
|
||||
/**
|
||||
* Get the path to the app directory
|
||||
*/
|
||||
function app_path(string $path = ''): string
|
||||
{
|
||||
return rtrim(__DIR__, '/') . ($path ? '/' . ltrim($path, '/') : '');
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('base_path')) {
|
||||
/**
|
||||
* Get the path to the base directory
|
||||
*/
|
||||
function base_path(string $path = ''): string
|
||||
{
|
||||
return rtrim(dirname(__DIR__), '/') . ($path ? '/' . ltrim($path, '/') : '');
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('config_path')) {
|
||||
/**
|
||||
* Get the path to the config directory
|
||||
*/
|
||||
function config_path(string $path = ''): string
|
||||
{
|
||||
return base_path('config') . ($path ? '/' . ltrim($path, '/') : '');
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('database_path')) {
|
||||
/**
|
||||
* Get the path to the database directory
|
||||
*/
|
||||
function database_path(string $path = ''): string
|
||||
{
|
||||
return base_path('database') . ($path ? '/' . ltrim($path, '/') : '');
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('public_path')) {
|
||||
/**
|
||||
* Get the path to the public directory
|
||||
*/
|
||||
function public_path(string $path = ''): string
|
||||
{
|
||||
return base_path('public') . ($path ? '/' . ltrim($path, '/') : '');
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('resource_path')) {
|
||||
/**
|
||||
* Get the path to the resources directory
|
||||
*/
|
||||
function resource_path(string $path = ''): string
|
||||
{
|
||||
return base_path('resources') . ($path ? '/' . ltrim($path, '/') : '');
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('storage_path')) {
|
||||
/**
|
||||
* Get the path to the storage directory
|
||||
*/
|
||||
function storage_path(string $path = ''): string
|
||||
{
|
||||
return base_path('storage') . ($path ? '/' . ltrim($path, '/') : '');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!function_exists('view')) {
|
||||
/**
|
||||
* Create a view response (simple implementation)
|
||||
*/
|
||||
function view(string $view, array $data = []): string
|
||||
{
|
||||
$viewPath = resource_path('views/' . str_replace('.', '/', $view) . '.php');
|
||||
|
||||
if (!file_exists($viewPath)) {
|
||||
throw new \InvalidArgumentException("View [{$view}] not found.");
|
||||
}
|
||||
|
||||
extract($data);
|
||||
ob_start();
|
||||
require $viewPath;
|
||||
return ob_get_clean();
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('fake')) {
|
||||
/**
|
||||
* Create fake data using FakerPHP (Laravel-style implementation)
|
||||
*/
|
||||
function fake(?string $locale = null): \Faker\Generator
|
||||
{
|
||||
return \Faker\Factory::create($locale ?? 'en_US');
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('collect')) {
|
||||
/**
|
||||
* Create a collection from the given value (using Illuminate Support)
|
||||
*/
|
||||
function collect(mixed $value = []): \Illuminate\Support\Collection
|
||||
{
|
||||
return new \Illuminate\Support\Collection($value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('data_get')) {
|
||||
/**
|
||||
* Get an item from an array or object using "dot" notation
|
||||
*/
|
||||
function data_get(mixed $target, string|array|int|null $key, mixed $default = null): mixed
|
||||
{
|
||||
return \Illuminate\Support\Arr::get($target, $key, $default);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('data_set')) {
|
||||
/**
|
||||
* Set an item on an array or object using "dot" notation
|
||||
*/
|
||||
function data_set(mixed &$target, string|array $key, mixed $value, bool $overwrite = true): mixed
|
||||
{
|
||||
return \Illuminate\Support\Arr::set($target, $key, $value, $overwrite);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('str')) {
|
||||
/**
|
||||
* Create a new stringable object from the given string
|
||||
*/
|
||||
function str(string $string = ''): \Illuminate\Support\Stringable
|
||||
{
|
||||
return \Illuminate\Support\Str::of($string);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('now')) {
|
||||
/**
|
||||
* Create a new Carbon instance for the current time
|
||||
*/
|
||||
function now($tz = null): \Carbon\Carbon
|
||||
{
|
||||
return \Carbon\Carbon::now($tz);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('today')) {
|
||||
/**
|
||||
* Create a new Carbon instance for today
|
||||
*/
|
||||
function today($tz = null): \Carbon\Carbon
|
||||
{
|
||||
return \Carbon\Carbon::today($tz);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('tap')) {
|
||||
/**
|
||||
* Call the given Closure with the given value then return the value
|
||||
*/
|
||||
function tap(mixed $value, ?callable $callback = null): mixed
|
||||
{
|
||||
if (is_null($callback)) {
|
||||
return new \Illuminate\Support\HigherOrderTapProxy($value);
|
||||
}
|
||||
|
||||
$callback($value);
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('optional')) {
|
||||
/**
|
||||
* Provide access to optional objects
|
||||
*/
|
||||
function optional(mixed $value = null, ?callable $callback = null): mixed
|
||||
{
|
||||
if (is_null($callback)) {
|
||||
return new \Illuminate\Support\Optional($value);
|
||||
}
|
||||
|
||||
if (!is_null($value)) {
|
||||
return $callback($value);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('app')) {
|
||||
/**
|
||||
* Get the available container instance
|
||||
*/
|
||||
function app(?string $abstract = null, array $parameters = []): mixed
|
||||
{
|
||||
if (is_null($abstract)) {
|
||||
return \Illuminate\Container\Container::getInstance();
|
||||
}
|
||||
|
||||
return \Illuminate\Container\Container::getInstance()->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);
|
||||
}
|
||||
}
|
41
bootstrap/app.php
Normal file
41
bootstrap/app.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Application Bootstrap
|
||||
*
|
||||
* This file creates and configures the application instance
|
||||
*/
|
||||
|
||||
// Load environment variables
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
|
||||
$dotenv->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;
|
115
bootstrap/container.php
Normal file
115
bootstrap/container.php
Normal file
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Container Bootstrap
|
||||
*
|
||||
* Creates and configures the Illuminate Container instance
|
||||
*/
|
||||
|
||||
use Illuminate\Container\Container;
|
||||
|
||||
/**
|
||||
* Create and configure the application container
|
||||
*/
|
||||
function createContainer(string $rootPath): Container
|
||||
{
|
||||
// Load environment variables first
|
||||
$dotenv = \Dotenv\Dotenv::createImmutable($rootPath);
|
||||
$dotenv->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']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
77
config/app.php
Normal file
77
config/app.php
Normal file
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Application Configuration
|
||||
*/
|
||||
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Name
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
'name' => 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
|
||||
],
|
||||
];
|
92
config/auth.php
Normal file
92
config/auth.php
Normal file
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Authentication Configuration
|
||||
*/
|
||||
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Defaults
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
'defaults' => [
|
||||
'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',
|
||||
],
|
||||
];
|
77
config/cache.php
Normal file
77
config/cache.php
Normal file
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Cache Configuration
|
||||
*/
|
||||
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Cache Store
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
'default' => 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,
|
||||
],
|
||||
];
|
86
config/database.php
Normal file
86
config/database.php
Normal file
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Database Configuration
|
||||
*/
|
||||
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Database Connection Name
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
'default' => 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),
|
||||
],
|
||||
],
|
||||
];
|
71
config/filesystems.php
Normal file
71
config/filesystems.php
Normal file
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Filesystem Configuration
|
||||
*/
|
||||
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Filesystem Disk
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
'default' => 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'),
|
||||
],
|
||||
];
|
|
@ -1,30 +1,35 @@
|
|||
<?php
|
||||
|
||||
use function DI\factory;
|
||||
use function DI\get;
|
||||
use function DI\autowire;
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Service Container Bindings
|
||||
*
|
||||
* Define service bindings for the Illuminate Container
|
||||
*/
|
||||
|
||||
return [
|
||||
// Add custom service definitions here as they are implemented
|
||||
|
||||
// Examples (uncomment and modify when implementing):
|
||||
// Config service binding
|
||||
\TorrentPier\Config::class => 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();
|
||||
// },
|
||||
];
|
||||
|
|
91
config/tracker.php
Normal file
91
config/tracker.php
Normal file
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* BitTorrent Tracker Configuration
|
||||
*/
|
||||
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Tracker Settings
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
'announce_interval' => 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
|
||||
];
|
81
database/factories/UserFactory.php
Normal file
81
database/factories/UserFactory.php
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
/**
|
||||
* User Factory
|
||||
*
|
||||
* Create fake user data for testing
|
||||
*/
|
||||
class UserFactory
|
||||
{
|
||||
/**
|
||||
* Define the model's default state using Laravel-style helpers
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'username' => 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 = [];
|
||||
}
|
34
database/seeders/DatabaseSeeder.php
Normal file
34
database/seeders/DatabaseSeeder.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
/**
|
||||
* Database Seeder
|
||||
*
|
||||
* Run all seeders for the application
|
||||
*/
|
||||
class DatabaseSeeder
|
||||
{
|
||||
/**
|
||||
* Seed the application's database
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// Call individual seeders here
|
||||
// $this->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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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',
|
||||
|
|
37
public/index.php
Normal file
37
public/index.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* TorrentPier - The public entry point for the web application
|
||||
*
|
||||
* This is the front controller that handles all incoming HTTP requests
|
||||
* Following Laravel-style architecture with illuminate/http
|
||||
*/
|
||||
|
||||
// Define the application start time
|
||||
define('TORRENTPIER_START', microtime(true));
|
||||
|
||||
// Register the autoloader
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
// Bootstrap the application
|
||||
$router = require_once __DIR__ . '/../bootstrap/app.php';
|
||||
|
||||
// Create Laravel-style request from globals
|
||||
$request = \Illuminate\Http\Request::createFromGlobals();
|
||||
|
||||
try {
|
||||
// Dispatch the request through the router
|
||||
$response = $router->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();
|
||||
}
|
139
resources/views/hello.php
Normal file
139
resources/views/hello.php
Normal file
|
@ -0,0 +1,139 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Hello World - <?= htmlspecialchars($siteName) ?></title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
max-width: 900px;
|
||||
margin: 50px auto;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
color: #333;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
||||
}
|
||||
h1 {
|
||||
color: #2c3e50;
|
||||
margin-bottom: 20px;
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
.info {
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.feature-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 20px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.feature-card {
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #667eea;
|
||||
}
|
||||
.feature-card h3 {
|
||||
margin-top: 0;
|
||||
color: #2c3e50;
|
||||
}
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
background: #667eea;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 6px;
|
||||
margin: 5px;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
.btn:hover {
|
||||
background: #5a6fd8;
|
||||
}
|
||||
.code {
|
||||
background: #2d3748;
|
||||
color: #e2e8f0;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
font-family: 'Monaco', 'Menlo', monospace;
|
||||
font-size: 0.9rem;
|
||||
overflow-x: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🚀 Hello World from <?= htmlspecialchars($siteName) ?>!</h1>
|
||||
|
||||
<p>Welcome to TorrentPier 3.0 with modern MVC architecture! This page demonstrates the new Laravel-inspired structure using <strong><?= htmlspecialchars($architecture) ?></strong>.</p>
|
||||
|
||||
<div class="info">
|
||||
<h3>🎯 Route Information</h3>
|
||||
<strong>URI:</strong> <?= htmlspecialchars($request->uri) ?><br>
|
||||
<strong>Method:</strong> <?= htmlspecialchars($request->method) ?><br>
|
||||
<strong>Time:</strong> <?= htmlspecialchars($currentTime) ?><br>
|
||||
<strong>Controller:</strong> HelloWorldController
|
||||
</div>
|
||||
|
||||
<h2>✨ New Features & Technologies</h2>
|
||||
|
||||
<div class="feature-grid">
|
||||
<div class="feature-card">
|
||||
<h3>🏗️ MVC Architecture</h3>
|
||||
<p>Clean separation of concerns with Models, Views, and Controllers following Laravel conventions.</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-card">
|
||||
<h3>📦 Illuminate HTTP</h3>
|
||||
<p>Powerful request/response handling with JsonResponse, RedirectResponse, and more.</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-card">
|
||||
<h3>🛠️ Illuminate Support</h3>
|
||||
<p>Collections, string helpers, array utilities, and Carbon date handling.</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-card">
|
||||
<h3>📁 Laravel-style Structure</h3>
|
||||
<p>Familiar directory layout: /app, /config, /database, /routes, /storage, etc.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>🧪 Try the New Features</h2>
|
||||
|
||||
<div class="code">
|
||||
// Modern helper functions now available:<br>
|
||||
$users = collect(['Alice', 'Bob', 'Charlie']);<br>
|
||||
$title = str('hello world')->title();<br>
|
||||
$time = now()->format('Y-m-d H:i:s');<br>
|
||||
$value = data_get($config, 'app.name', 'default');
|
||||
</div>
|
||||
|
||||
<div style="margin: 30px 0;">
|
||||
<a href="/hello.json" class="btn">📊 View JSON API</a>
|
||||
<a href="/hello/features" class="btn">🎨 Feature Demo</a>
|
||||
<a href="/" class="btn">🏠 Back to Main Site</a>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 30px; padding: 20px; background: #f8f9fa; border-radius: 8px; font-size: 0.9rem; color: #666;">
|
||||
<strong>Technical Stack:</strong><br>
|
||||
🔹 League/Route for routing<br>
|
||||
🔹 Illuminate HTTP & Support packages<br>
|
||||
🔹 PHP-DI dependency injection<br>
|
||||
🔹 Nette Database for data access<br>
|
||||
🔹 PSR-7 compatibility layer
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
56
routes/admin.php
Normal file
56
routes/admin.php
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Http\Routing\Router;
|
||||
|
||||
/**
|
||||
* Admin Routes
|
||||
*
|
||||
* Define all admin panel routes here
|
||||
* These routes will be prefixed with /admin and protected by admin middleware
|
||||
*/
|
||||
|
||||
/** @var Router $router */
|
||||
$router = app(Router::class);
|
||||
|
||||
// Admin Dashboard
|
||||
// $router->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');
|
49
routes/api.php
Normal file
49
routes/api.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Http\Routing\Router;
|
||||
|
||||
/**
|
||||
* API Routes
|
||||
*
|
||||
* Define all API routes here
|
||||
* These routes will be prefixed with /api
|
||||
*/
|
||||
|
||||
/** @var Router $router */
|
||||
$router = app(Router::class);
|
||||
|
||||
// API routes demonstrating Laravel-style functionality
|
||||
|
||||
// User API (showcasing illuminate/http and illuminate/validation)
|
||||
$router->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');
|
80
routes/web.php
Normal file
80
routes/web.php
Normal file
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Http\Routing\Router;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* Web Routes
|
||||
*
|
||||
* Define all web-accessible routes here
|
||||
*/
|
||||
|
||||
/** @var Router $router */
|
||||
$router = app(Router::class);
|
||||
|
||||
// Test routes showcasing new Laravel-style features
|
||||
$router->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');
|
Loading…
Add table
Add a link
Reference in a new issue