diff --git a/CLAUDE.md b/CLAUDE.md index 22af6c3cf..b3b58a20c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,38 +10,69 @@ TorrentPier is a BitTorrent tracker engine written in PHP, designed for hosting - **PHP 8.3+** with modern features - **MySQL/MariaDB/Percona** database -- **Nette Database** with temporary backward-compatible wrapper +- **Nette Database** for data access (primary) +- **Illuminate Database** for Eloquent ORM (optional) +- **Illuminate Container** for dependency injection +- **Illuminate Support** for collections and helpers +- **Illuminate Collections** for enhanced collection handling +- **Illuminate Events** for event-driven architecture +- **Illuminate Routing** for Laravel-style routing +- **Illuminate Validation** for request validation +- **Illuminate HTTP** for request/response handling - **Composer** for dependency management - **Custom BitTorrent tracker** implementation +- **Laravel-style MVC Architecture** with familiar patterns ## Key Directory Structure -- `/src/` - Modern PHP classes (PSR-4 autoloaded as `TorrentPier\`) -- `/library/` - Core application logic and legacy code -- `/admin/` - Administrative interface +### Laravel-style Structure +- `/app/` - Main application directory (PSR-4 autoloaded as `App\`) + - `/Console/Commands/` - Artisan-style CLI commands for Dexter + - `/Http/Controllers/` - Web, API, and Admin controllers + - `/Http/Middleware/` - HTTP middleware + - `/Http/Routing/` - Routing components (uses Illuminate Routing) + - `/Models/` - Data models using Nette Database + - `/Services/` - Business logic services + - `/Providers/` - Service providers + - `/Container/` - Container wrapper and extensions + - `/Support/` - Helper classes and utilities +- `/bootstrap/` - Application bootstrap files (app.php, container.php) +- `/config/` - Laravel-style configuration files (app.php, database.php, etc.) +- `/database/` - Migrations, seeders, factories +- `/public/` - Web root with front controller (index.php) +- `/resources/` - Views, language files, assets +- `/routes/` - Route definitions (web.php, api.php, admin.php) +- `/storage/` - Application storage (app/, framework/, logs/) +- `dexter` - CLI interface + +### Core Utilities & Legacy +- `/src/` - Core utilities and services (PSR-4 autoloaded as `TorrentPier\`) +- `/library/` - Legacy core application logic +- `/controllers/` - Legacy PHP controllers (being migrated) +- `/admin/` - Legacy administrative interface - `/bt/` - BitTorrent tracker functionality (announce.php, scrape.php) -- `/styles/` - Templates, CSS, JS, images -- `/internal_data/` - Cache, logs, compiled templates -- `/install/` - Installation scripts and configuration examples -- `/migrations/` - Database migration files (Phinx) +- `/styles/` - Legacy templates, CSS, JS, images +- `/internal_data/` - Legacy cache, logs, compiled templates ## Entry Points & Key Files -- `index.php` - Main forum homepage -- `tracker.php` - Torrent search/browse interface +### Modern Entry Points +- `public/index.php` - Laravel-style front controller (web requests) +- `dexter` - CLI interface (console commands) +- `bootstrap/app.php` - Application bootstrap +- `bootstrap/container.php` - Container setup and configuration +- `bootstrap/console.php` - Console bootstrap + +### Legacy Entry Points (Backward Compatibility) - `bt/announce.php` - BitTorrent announce endpoint - `bt/scrape.php` - BitTorrent scrape endpoint -- `admin/index.php` - Administrative panel -- `cron.php` - Background task runner (CLI only) -- `install.php` - Installation script (CLI only) +- `admin/index.php` - Legacy administrative panel +- `cron.php` - Background task runner ## Development Commands ### Installation & Setup ```bash -# Automated installation (CLI) -php install.php - # Install dependencies composer install @@ -58,24 +89,82 @@ php cron.php ### Code Quality The project uses **StyleCI** with PSR-2 preset for code style enforcement. StyleCI configuration is in `.styleci.yml` targeting `src/` directory. -## Modern Architecture Components +## MVC Architecture Components + +### Models (`/app/Models/`) +- **Simple Active Record pattern** using Nette Database (primary) +- **Eloquent ORM** available via Illuminate Database (optional) +- Base `Model` class provides common CRUD operations +- No complex ORM required, just straightforward database access +- Example: `Torrent`, `User`, `Forum`, `Post` models + +### Controllers (`/app/Http/Controllers/`) +- **Thin controllers** that delegate to services +- Organized by area: `Web/`, `Api/`, `Admin/` +- `LegacyController` maintains backward compatibility +- Base `Controller` class provides common methods + +### Services (`/app/Services/`) +- **Business logic layer** between controllers and models +- Handles complex operations and workflows +- Example: `TorrentService`, `AuthService`, `ForumService` +- Injected via dependency injection + +### Views (`/resources/views/`) +- **PHP templates** (planning future Twig integration) +- Organized by feature areas +- Layouts for consistent structure +- Partials for reusable components + +## Infrastructure Components ### Database Layer (`/src/Database/`) -- **Nette Database** replacing legacy SqlDb system +- **Nette Database** for all data access (primary) +- **Illuminate Database** available for Eloquent ORM features - Modern singleton pattern accessible via `DB()` function - Support for multiple database connections and debug functionality -- **Breaking changes expected** during 3.0 migration to ORM-style queries +- Direct SQL queries when needed ### Cache System (`/src/Cache/`) - **Unified caching** using Nette Caching internally - Replaces existing `CACHE()` and $datastore systems - Supports file, SQLite, memory, and Memcached storage -- **API changes planned** for improved developer experience +- Used by services and repositories ### Configuration Management - Environment-based config with `.env` files +- **Illuminate Config** for Laravel-style configuration - Modern singleton `Config` class accessible via `config()` function -- **Legacy config access will be removed** in favor of new patterns +- Configuration files in `/config/` directory + +### Event System (`/app/Events/` & `/app/Listeners/`) +- **Illuminate Events** for decoupled, event-driven architecture +- Event classes in `/app/Events/` +- Listener classes in `/app/Listeners/` +- Event-listener mappings in `EventServiceProvider` +- Global `event()` helper function for dispatching events +- Support for queued listeners (when queue system is configured) + +### Routing System +- **Illuminate Routing** for full Laravel-compatible routing (as of TorrentPier 3.0) +- Route definitions in `/routes/` directory (web.php, api.php, admin.php) +- Support for route groups, middleware, named routes, route model binding +- Resource controllers and RESTful routing patterns +- Custom `IlluminateRouter` wrapper for smooth Laravel integration +- Legacy custom router preserved as `LegacyRouter` for reference +- Backward compatibility maintained through `Router` alias + +### Validation Layer +- **Illuminate Validation** for robust input validation +- Form request classes in `/app/Http/Requests/` +- Base `FormRequest` class for common validation logic +- Custom validation rules and messages +- Automatic validation exception handling + +### HTTP Layer +- **Illuminate HTTP** for request/response handling +- Middleware support for request filtering +- JSON response helpers and content negotiation ## Configuration Files - `.env` - Environment variables (copy from `.env.example`) @@ -89,16 +178,14 @@ The project uses **StyleCI** with PSR-2 preset for code style enforcement. Style - **GitHub Actions** for automated testing and deployment - **StyleCI** for code style enforcement - **Dependabot** for dependency updates -- **FTP deployment** to demo environment ### Installation Methods -1. **Automated**: `php install.php` (recommended) -2. **Composer**: `composer create-project torrentpier/torrentpier` -3. **Manual**: Git clone + `composer install` + database setup +1. **Composer**: `composer create-project torrentpier/torrentpier` +2. **Manual**: Git clone + `composer install` ## Database & Schema -- **Database migrations** managed via Phinx in `/migrations/` directory +- **Database migrations** managed via Phinx in `/database/migrations/` directory - Initial schema: `20250619000001_initial_schema.php` - Initial seed data: `20250619000002_seed_initial_data.php` - UTF-8 (utf8mb4) character set required @@ -118,25 +205,132 @@ php vendor/bin/phinx migrate --fake --configuration=phinx.php ## TorrentPier 3.0 Modernization Strategy -The TorrentPier 3.0 release represents a major architectural shift focused on: +The TorrentPier 3.0 release represents a major architectural shift to Laravel-style MVC: +- **Laravel-style MVC Architecture**: Clean Model-View-Controller pattern +- **Illuminate Container**: Laravel's dependency injection container - **Modern PHP practices**: PSR standards, namespaces, autoloading -- **Clean architecture**: Separation of concerns, dependency injection +- **Developer friendly**: Familiar Laravel patterns for easier contribution - **Performance improvements**: Optimized database queries, efficient caching -- **Developer experience**: Better debugging, testing, and maintenance - **Breaking changes**: Legacy code removal and API modernization **Important**: TorrentPier 3.0 will introduce breaking changes to achieve these modernization goals. Existing deployments should remain on 2.x versions until they're ready to migrate to the new architecture. -## Migration Path for 3.0 +## Current Architecture -- **Database layer**: Legacy SqlDb calls will be removed, migrate to new Database class -- **Cache system**: Replace existing CACHE() and $datastore calls with new unified API -- **Configuration**: Update legacy global $bb_cfg access to use config() singleton -- **Templates**: Legacy template syntax may be deprecated in favor of modern Twig features -- **Language system**: Update global $lang usage to new Language singleton methods +### Container & Dependency Injection +- **Illuminate Container**: Laravel's container for dependency injection +- **Bootstrap**: Clean container setup in `/bootstrap/container.php` +- **Service Providers**: Laravel-style providers in `/app/Providers/` +- **Helper Functions**: Global helpers - `app()`, `config()`, `event()` -When working with this codebase, prioritize modern architecture patterns and clean code practices. Focus on the new systems in `/src/` directory rather than maintaining legacy compatibility. +### MVC Structure +- **Controllers**: All controllers in `/app/Http/Controllers/` +- **Models**: Simple models in `/app/Models/` (using Nette Database or Eloquent) +- **Services**: Business logic in `/app/Services/` +- **Routes**: Laravel-style route definitions in `/routes/` +- **Middleware**: HTTP middleware in `/app/Http/Middleware/` +- **Events**: Event classes in `/app/Events/` +- **Listeners**: Event listeners in `/app/Listeners/` +- **Requests**: Form request validation in `/app/Http/Requests/` + +### Migration Steps for New Features +1. Create models in `/app/Models/` extending base `Model` class (or Eloquent models) +2. Add business logic to services in `/app/Services/` +3. Create form request classes in `/app/Http/Requests/` for validation +4. Create thin controllers in `/app/Http/Controllers/` +5. Define routes in `/routes/` files using Illuminate Routing syntax +6. Create events in `/app/Events/` and listeners in `/app/Listeners/` +7. Register event listeners in `EventServiceProvider` +8. Use helper functions: `app()`, `config()`, `event()` for easy access + +### What to Avoid +- Don't use complex DDD patterns (aggregates, value objects) +- Don't implement CQRS or event sourcing +- Don't create repository interfaces (use concrete classes if needed) +- Don't over-engineer - keep it simple and Laravel-like + +When working with this codebase, prioritize simplicity and maintainability. New features should be built in the `/app/` directory using Laravel-style MVC patterns. + +## Example Usage + +### Events and Listeners +```php +// Dispatch an event from a service +use App\Events\UserRegistered; + +event(new UserRegistered($user)); + +// Create an event listener +class SendWelcomeEmail +{ + public function handle(UserRegistered $event): void + { + // Send welcome email to $event->user + } +} +``` + +### Form Validation +```php +// Create a form request class +class RegisterUserRequest extends FormRequest +{ + public function rules(): array + { + return [ + 'email' => 'required|email|unique:users', + 'username' => 'required|string|min:3|max:20', + 'password' => 'required|string|min:8', + ]; + } +} + +// Use in controller +public function register(RegisterUserRequest $request): JsonResponse +{ + // Request is automatically validated + $validated = $request->validated(); + // ... create user +} +``` + +### Routing with Groups and Middleware +```php +// In routes/api.php +$router->group(['prefix' => 'v1', 'middleware' => 'auth'], function () use ($router) { + $router->resource('torrents', 'TorrentController'); + $router->get('stats', 'StatsController::index'); +}); +``` + +### Using Collections +```php +use Illuminate\Support\Collection; + +$users = collect(User::all()); +$activeUsers = $users->filter(fn($user) => $user->isActive()) + ->sortBy('last_seen') + ->take(10); +``` + +### Middleware Usage +```php +// Apply middleware to routes +$router->middleware(['auth', 'admin'])->group(function () use ($router) { + $router->get('/admin/users', 'AdminController::users'); +}); + +// Create custom middleware +class CustomMiddleware +{ + public function handle(Request $request, \Closure $next) + { + // Middleware logic here + return $next($request); + } +} +``` ## Markdown File Guidelines diff --git a/app/Console/Commands/.keep b/app/Console/Commands/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/Console/Commands/ClearCacheCommand.php b/app/Console/Commands/ClearCacheCommand.php new file mode 100644 index 000000000..1daad14c0 --- /dev/null +++ b/app/Console/Commands/ClearCacheCommand.php @@ -0,0 +1,116 @@ +addOption( + 'force', + 'f', + InputOption::VALUE_NONE, + 'Force clearing cache without confirmation' + ); + } + + /** + * Handle the command + */ + public function handle(): int + { + $force = $this->option('force'); + + if (!$force && !$this->confirm('Are you sure you want to clear all cache?', true)) { + $this->info('Cache clear cancelled.'); + return self::SUCCESS; + } + + $this->info('Clearing application cache...'); + + $cleared = 0; + + // Clear file cache + $cacheDir = $this->app->make('path.base') . '/storage/framework/cache'; + if (is_dir($cacheDir)) { + $files = glob($cacheDir . '/*'); + foreach ($files as $file) { + if (is_file($file)) { + unlink($file); + $cleared++; + } + } + $this->line("✓ File cache cleared ({$cleared} files)"); + } + + // Clear view cache + $viewCacheDir = $this->app->make('path.base') . '/storage/framework/views'; + if (is_dir($viewCacheDir)) { + $viewFiles = glob($viewCacheDir . '/*'); + $viewCleared = 0; + foreach ($viewFiles as $file) { + if (is_file($file)) { + unlink($file); + $viewCleared++; + } + } + $this->line("✓ View cache cleared ({$viewCleared} files)"); + $cleared += $viewCleared; + } + + // Clear legacy cache directories + $legacyCacheDir = $this->app->make('path.base') . '/internal_data/cache'; + if (is_dir($legacyCacheDir)) { + $legacyCleared = $this->clearDirectoryRecursive($legacyCacheDir); + $this->line("✓ Legacy cache cleared ({$legacyCleared} files)"); + $cleared += $legacyCleared; + } + + $this->success("Cache cleared successfully! Total files removed: {$cleared}"); + + return self::SUCCESS; + } + + /** + * Recursively clear a directory + */ + private function clearDirectoryRecursive(string $dir): int + { + $cleared = 0; + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS), + \RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ($iterator as $file) { + if ($file->isFile()) { + unlink($file->getRealPath()); + $cleared++; + } + } + + return $cleared; + } +} diff --git a/app/Console/Commands/Command.php b/app/Console/Commands/Command.php new file mode 100644 index 000000000..e529b9cfc --- /dev/null +++ b/app/Console/Commands/Command.php @@ -0,0 +1,207 @@ +signature) { + $name = $this->parseSignature(); + } + + parent::__construct($name); + + // Set description + if ($this->description) { + $this->setDescription($this->description); + } + + // Get container instance + $this->app = Container::getInstance(); + } + + /** + * Execute the command + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $this->input = $input; + $this->output = $output; + $this->io = new SymfonyStyle($input, $output); + + try { + $result = $this->handle(); + return is_int($result) ? $result : self::SUCCESS; + } catch (\Exception $e) { + $this->error('Command failed: ' . $e->getMessage()); + return self::FAILURE; + } + } + + /** + * Handle the command (implement in subclasses) + */ + abstract public function handle(): int; + + /** + * Parse Laravel-style signature + */ + protected function parseSignature(): string + { + // Simple signature parsing - just extract command name for now + // Full Laravel signature parsing would be more complex + $parts = explode(' ', trim($this->signature)); + return $parts[0]; + } + + /** + * Get an argument value + */ + protected function argument(?string $key = null): mixed + { + if ($key === null) { + return $this->input->getArguments(); + } + + return $this->input->getArgument($key); + } + + /** + * Get an option value + */ + protected function option(?string $key = null): mixed + { + if ($key === null) { + return $this->input->getOptions(); + } + + return $this->input->getOption($key); + } + + /** + * Display an info message + */ + protected function info(string $message): void + { + $this->io->info($message); + } + + /** + * Display an error message + */ + protected function error(string $message): void + { + $this->io->error($message); + } + + /** + * Display a warning message + */ + protected function warn(string $message): void + { + $this->io->warning($message); + } + + /** + * Display a success message + */ + protected function success(string $message): void + { + $this->io->success($message); + } + + /** + * Display a line of text + */ + protected function line(string $message): void + { + $this->output->writeln($message); + } + + /** + * Ask a question + */ + protected function ask(string $question, ?string $default = null): ?string + { + return $this->io->ask($question, $default); + } + + /** + * Ask for confirmation + */ + protected function confirm(string $question, bool $default = false): bool + { + return $this->io->confirm($question, $default); + } + + /** + * Ask the user to select from a list of options + */ + protected function choice(string $question, array $choices, ?string $default = null): string + { + return $this->io->choice($question, $choices, $default); + } + + /** + * Get application configuration + */ + protected function config(?string $key = null, mixed $default = null): mixed + { + $config = $this->app->make('config'); + + if ($key === null) { + return $config; + } + + return $config->get($key, $default); + } +} diff --git a/app/Console/Commands/InfoCommand.php b/app/Console/Commands/InfoCommand.php new file mode 100644 index 000000000..8368de43b --- /dev/null +++ b/app/Console/Commands/InfoCommand.php @@ -0,0 +1,113 @@ +app->make('path.base'); + + $this->line(''); + $this->line('TorrentPier System Information'); + $this->line('=============================='); + $this->line(''); + + // Application info + $this->line('Application:'); + $this->line(' Name: TorrentPier'); + $this->line(' Version: 3.0-dev'); + $this->line(' Environment: ' . ($_ENV['APP_ENV'] ?? 'production')); + $this->line(' Debug Mode: ' . (($_ENV['APP_DEBUG'] ?? false) ? 'enabled' : 'disabled')); + $this->line(''); + + // Paths + $this->line('Paths:'); + $this->line(' Base: ' . $basePath); + $this->line(' App: ' . $this->app->make('path.app')); + $this->line(' Config: ' . $this->app->make('path.config')); + $this->line(' Storage: ' . $this->app->make('path.storage')); + $this->line(' Public: ' . $this->app->make('path.public')); + $this->line(''); + + // PHP info + $this->line('PHP:'); + $this->line(' Version: ' . PHP_VERSION); + $this->line(' SAPI: ' . PHP_SAPI); + $this->line(' Memory Limit: ' . ini_get('memory_limit')); + $this->line(' Max Execution Time: ' . ini_get('max_execution_time') . 's'); + $this->line(''); + + // Extensions + $requiredExtensions = ['pdo', 'curl', 'gd', 'mbstring', 'openssl', 'zip']; + $this->line('Required Extensions:'); + foreach ($requiredExtensions as $ext) { + $status = extension_loaded($ext) ? '✓' : '✗'; + $this->line(" {$status} {$ext}"); + } + $this->line(''); + + // File permissions + $this->line('File Permissions:'); + $writablePaths = [ + 'storage', + 'storage/app', + 'storage/framework', + 'storage/logs', + 'internal_data/cache', + 'data/uploads' + ]; + + foreach ($writablePaths as $path) { + $fullPath = $basePath . '/' . $path; + if (file_exists($fullPath)) { + $writable = is_writable($fullPath); + $status = $writable ? '✓' : '✗'; + $this->line(" {$status} {$path}"); + } else { + $this->line(" ? {$path} (not found)"); + } + } + $this->line(''); + + // Database + try { + if ($this->app->bound('config')) { + $config = $this->app->make('config'); + $dbConfig = $config->get('database', []); + + if (!empty($dbConfig)) { + $this->line('Database:'); + $this->line(' Host: ' . ($dbConfig['host'] ?? 'not configured')); + $this->line(' Database: ' . ($dbConfig['dbname'] ?? 'not configured')); + $this->line(' Driver: ' . ($dbConfig['driver'] ?? 'not configured')); + } + } + } catch (\Exception $e) { + $this->line('Database: Configuration error'); + } + + $this->line(''); + return self::SUCCESS; + } +} diff --git a/app/Console/Commands/MigrateCommand.php b/app/Console/Commands/MigrateCommand.php new file mode 100644 index 000000000..b5696f9bd --- /dev/null +++ b/app/Console/Commands/MigrateCommand.php @@ -0,0 +1,101 @@ +addOption( + 'fake', + null, + InputOption::VALUE_NONE, + 'Mark migrations as run without actually running them' + ) + ->addOption( + 'target', + 't', + InputOption::VALUE_REQUIRED, + 'Target migration version' + ) + ->addOption( + 'force', + 'f', + InputOption::VALUE_NONE, + 'Force running migrations in production' + ); + } + + /** + * Handle the command + */ + public function handle(): int + { + $basePath = $this->app->make('path.base'); + $phinxConfig = $basePath . '/phinx.php'; + + if (!file_exists($phinxConfig)) { + $this->error('Phinx configuration file not found at: ' . $phinxConfig); + return self::FAILURE; + } + + $this->info('Running database migrations...'); + + // Build phinx command + $command = 'cd ' . escapeshellarg($basePath) . ' && '; + $command .= 'vendor/bin/phinx migrate'; + $command .= ' --configuration=' . escapeshellarg($phinxConfig); + + if ($this->option('fake')) { + $command .= ' --fake'; + } + + if ($this->option('target')) { + $command .= ' --target=' . escapeshellarg($this->option('target')); + } + + if ($this->option('force')) { + $command .= ' --no-interaction'; + } + + // Execute the command + $output = []; + $returnCode = 0; + exec($command . ' 2>&1', $output, $returnCode); + + // Display output + foreach ($output as $line) { + $this->line($line); + } + + if ($returnCode === 0) { + $this->success('Migrations completed successfully!'); + } else { + $this->error('Migration failed with exit code: ' . $returnCode); + } + + return $returnCode === 0 ? self::SUCCESS : self::FAILURE; + } +} diff --git a/bootstrap/console.php b/bootstrap/console.php new file mode 100644 index 000000000..efd395b39 --- /dev/null +++ b/bootstrap/console.php @@ -0,0 +1,47 @@ +bound('console.commands')) { + $commands = $container->make('console.commands'); + foreach ($commands as $command) { + try { + $app->add($container->make($command)); + } catch (BindingResolutionException $e) { + // Skip commands that can't be resolved - console still works with built-in commands + continue; + } + } + } +} catch (BindingResolutionException $e) { + // No commands registered or service binding failed - console still works with built-in commands +} + +// Return the console application +return $app; diff --git a/composer.json b/composer.json index aa314804c..cf3792c55 100644 --- a/composer.json +++ b/composer.json @@ -79,6 +79,7 @@ "php-curl-class/php-curl-class": "^12.0.0", "robmorgan/phinx": "^0.16.9", "samdark/sitemap": "2.4.1", + "symfony/console": "^7.3", "symfony/mailer": "^7.3", "symfony/polyfill": "v1.32.0", "vlucas/phpdotenv": "^5.5", diff --git a/composer.lock b/composer.lock index 118c243e6..92c963901 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b6064b3dc730bc6bf800fdf90be38b43", + "content-hash": "697bc73a6378851ccaea6fb2c3c4f61d", "packages": [ { "name": "arokettu/bencode", @@ -2350,6 +2350,54 @@ }, "time": "2025-05-13T15:08:45+00:00" }, + { + "name": "illuminate/config", + "version": "v12.19.3", + "source": { + "type": "git", + "url": "https://github.com/illuminate/config.git", + "reference": "76824d4e853dba43359a201eba2422ab11e21e4d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/config/zipball/76824d4e853dba43359a201eba2422ab11e21e4d", + "reference": "76824d4e853dba43359a201eba2422ab11e21e4d", + "shasum": "" + }, + "require": { + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "php": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Config\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Config package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-03-19T20:10:05+00:00" + }, { "name": "illuminate/container", "version": "v12.19.3", diff --git a/dexter b/dexter new file mode 100755 index 000000000..aab033990 --- /dev/null +++ b/dexter @@ -0,0 +1,21 @@ +#!/usr/bin/env php +run()); +} catch (\Exception $e) { + fwrite(STDERR, "Error: " . $e->getMessage() . "\n"); + exit(1); +} diff --git a/docs/specs/mvc-architecture-spec.md b/docs/specs/mvc-architecture-spec.md index 54da5b03c..e7b88ba84 100644 --- a/docs/specs/mvc-architecture-spec.md +++ b/docs/specs/mvc-architecture-spec.md @@ -10,7 +10,7 @@ This document specifies the MVC (Model-View-Controller) architecture directory s # Laravel-style root structure /app/ # Application code (PSR-4: App\) ├── Console/ # Console commands -│ └── Commands/ # Artisan-style commands +│ └── Commands/ # Artisan-style commands for Dexter ├── Http/ # HTTP layer │ ├── Controllers/ # Controllers │ │ ├── Admin/ # Admin panel controllers @@ -109,7 +109,7 @@ This document specifies the MVC (Model-View-Controller) architecture directory s .env # Environment variables .env.example # Environment example composer.json # Dependencies (App\ and TorrentPier\ namespaces) -artisan # CLI interface +dexter # CLI interface index.php # Legacy entry point (redirects to public/) ``` diff --git a/tests/Unit/Config/ConfigSystemTest.php b/tests/Unit/Config/ConfigSystemTest.php deleted file mode 100644 index eb1d051f7..000000000 --- a/tests/Unit/Config/ConfigSystemTest.php +++ /dev/null @@ -1,65 +0,0 @@ -assertInstanceOf(\Illuminate\Config\Repository::class, $config); - } - - /** - * Test getting config values - */ - public function testGettingConfigValues(): void - { - // Assuming app.php config exists - $appConfig = config('app'); - $this->assertIsArray($appConfig); - - // Test with default value - $nonExistent = config('non.existent.key', 'default'); - $this->assertEquals('default', $nonExistent); - } - - /** - * Test setting config values - */ - public function testSettingConfigValues(): void - { - // Set a single value - config(['test.key' => 'test value']); - $this->assertEquals('test value', config('test.key')); - - // Set multiple values - config([ - 'test.foo' => 'bar', - 'test.baz' => 'qux' - ]); - - $this->assertEquals('bar', config('test.foo')); - $this->assertEquals('qux', config('test.baz')); - } - - /** - * Test dot notation access - */ - public function testDotNotationAccess(): void - { - config(['deeply.nested.config.value' => 'found it']); - - $this->assertEquals('found it', config('deeply.nested.config.value')); - $this->assertIsArray(config('deeply.nested')); - $this->assertArrayHasKey('config', config('deeply.nested')); - } -} \ No newline at end of file diff --git a/tests/Unit/Events/EventSystemTest.php b/tests/Unit/Events/EventSystemTest.php deleted file mode 100644 index 7ce70c0c0..000000000 --- a/tests/Unit/Events/EventSystemTest.php +++ /dev/null @@ -1,94 +0,0 @@ -listen(UserRegistered::class, function ($event) use (&$called, &$eventData) { - $called = true; - $eventData = $event; - }); - - // Dispatch the event - $event = new UserRegistered( - userId: 123, - username: 'testuser', - email: 'test@example.com', - registeredAt: new \DateTime('2025-01-01 12:00:00') - ); - - event($event); - - // Assert the listener was called - $this->assertTrue($called); - $this->assertInstanceOf(UserRegistered::class, $eventData); - $this->assertEquals(123, $eventData->getUserId()); - $this->assertEquals('testuser', $eventData->getUsername()); - $this->assertEquals('test@example.com', $eventData->getEmail()); - } - - /** - * Test event helper function - */ - public function testEventHelperFunction(): void - { - $listenerCalled = false; - - app('events')->listen(TorrentUploaded::class, function () use (&$listenerCalled) { - $listenerCalled = true; - }); - - // Use the event() helper - event(new TorrentUploaded( - torrentId: 456, - uploaderId: 789, - torrentName: 'Test Torrent', - size: 1024 * 1024 * 100, // 100MB - uploadedAt: new \DateTime() - )); - - $this->assertTrue($listenerCalled); - } - - /** - * Test that multiple listeners can be attached to an event - */ - public function testMultipleListeners(): void - { - $listener1Called = false; - $listener2Called = false; - - app('events')->listen(UserRegistered::class, function () use (&$listener1Called) { - $listener1Called = true; - }); - - app('events')->listen(UserRegistered::class, function () use (&$listener2Called) { - $listener2Called = true; - }); - - event(new UserRegistered( - userId: 999, - username: 'multitest', - email: 'multi@test.com', - registeredAt: new \DateTime() - )); - - $this->assertTrue($listener1Called); - $this->assertTrue($listener2Called); - } -} \ No newline at end of file