mirror of
https://github.com/torrentpier/torrentpier
synced 2025-08-24 07:05:47 -07:00
feat: implement Laravel-style CLI commands and enhance console architecture
- Introduced a new command structure for the TorrentPier CLI, named 'Dexter', following Laravel conventions. - Added base command class and several commands including ClearCacheCommand, InfoCommand, and MigrateCommand. - Integrated Symfony Console components for improved command handling and user interaction. - Updated console bootstrap process to register commands dynamically from the application container. - Removed legacy command structure to streamline CLI functionality and improve maintainability. This update modernizes the command-line interface, aligning it with Laravel practices while enhancing usability and extensibility.
This commit is contained in:
parent
3848c6f2c0
commit
314ceacbe7
13 changed files with 887 additions and 198 deletions
266
CLAUDE.md
266
CLAUDE.md
|
@ -10,38 +10,69 @@ TorrentPier is a BitTorrent tracker engine written in PHP, designed for hosting
|
||||||
|
|
||||||
- **PHP 8.3+** with modern features
|
- **PHP 8.3+** with modern features
|
||||||
- **MySQL/MariaDB/Percona** database
|
- **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
|
- **Composer** for dependency management
|
||||||
- **Custom BitTorrent tracker** implementation
|
- **Custom BitTorrent tracker** implementation
|
||||||
|
- **Laravel-style MVC Architecture** with familiar patterns
|
||||||
|
|
||||||
## Key Directory Structure
|
## Key Directory Structure
|
||||||
|
|
||||||
- `/src/` - Modern PHP classes (PSR-4 autoloaded as `TorrentPier\`)
|
### Laravel-style Structure
|
||||||
- `/library/` - Core application logic and legacy code
|
- `/app/` - Main application directory (PSR-4 autoloaded as `App\`)
|
||||||
- `/admin/` - Administrative interface
|
- `/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)
|
- `/bt/` - BitTorrent tracker functionality (announce.php, scrape.php)
|
||||||
- `/styles/` - Templates, CSS, JS, images
|
- `/styles/` - Legacy templates, CSS, JS, images
|
||||||
- `/internal_data/` - Cache, logs, compiled templates
|
- `/internal_data/` - Legacy cache, logs, compiled templates
|
||||||
- `/install/` - Installation scripts and configuration examples
|
|
||||||
- `/migrations/` - Database migration files (Phinx)
|
|
||||||
|
|
||||||
## Entry Points & Key Files
|
## Entry Points & Key Files
|
||||||
|
|
||||||
- `index.php` - Main forum homepage
|
### Modern Entry Points
|
||||||
- `tracker.php` - Torrent search/browse interface
|
- `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/announce.php` - BitTorrent announce endpoint
|
||||||
- `bt/scrape.php` - BitTorrent scrape endpoint
|
- `bt/scrape.php` - BitTorrent scrape endpoint
|
||||||
- `admin/index.php` - Administrative panel
|
- `admin/index.php` - Legacy administrative panel
|
||||||
- `cron.php` - Background task runner (CLI only)
|
- `cron.php` - Background task runner
|
||||||
- `install.php` - Installation script (CLI only)
|
|
||||||
|
|
||||||
## Development Commands
|
## Development Commands
|
||||||
|
|
||||||
### Installation & Setup
|
### Installation & Setup
|
||||||
```bash
|
```bash
|
||||||
# Automated installation (CLI)
|
|
||||||
php install.php
|
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
composer install
|
composer install
|
||||||
|
|
||||||
|
@ -58,24 +89,82 @@ php cron.php
|
||||||
### Code Quality
|
### Code Quality
|
||||||
The project uses **StyleCI** with PSR-2 preset for code style enforcement. StyleCI configuration is in `.styleci.yml` targeting `src/` directory.
|
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/`)
|
### 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
|
- Modern singleton pattern accessible via `DB()` function
|
||||||
- Support for multiple database connections and debug functionality
|
- 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/`)
|
### Cache System (`/src/Cache/`)
|
||||||
- **Unified caching** using Nette Caching internally
|
- **Unified caching** using Nette Caching internally
|
||||||
- Replaces existing `CACHE()` and $datastore systems
|
- Replaces existing `CACHE()` and $datastore systems
|
||||||
- Supports file, SQLite, memory, and Memcached storage
|
- Supports file, SQLite, memory, and Memcached storage
|
||||||
- **API changes planned** for improved developer experience
|
- Used by services and repositories
|
||||||
|
|
||||||
### Configuration Management
|
### Configuration Management
|
||||||
- Environment-based config with `.env` files
|
- Environment-based config with `.env` files
|
||||||
|
- **Illuminate Config** for Laravel-style configuration
|
||||||
- Modern singleton `Config` class accessible via `config()` function
|
- 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
|
## Configuration Files
|
||||||
- `.env` - Environment variables (copy from `.env.example`)
|
- `.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
|
- **GitHub Actions** for automated testing and deployment
|
||||||
- **StyleCI** for code style enforcement
|
- **StyleCI** for code style enforcement
|
||||||
- **Dependabot** for dependency updates
|
- **Dependabot** for dependency updates
|
||||||
- **FTP deployment** to demo environment
|
|
||||||
|
|
||||||
### Installation Methods
|
### Installation Methods
|
||||||
1. **Automated**: `php install.php` (recommended)
|
1. **Composer**: `composer create-project torrentpier/torrentpier`
|
||||||
2. **Composer**: `composer create-project torrentpier/torrentpier`
|
2. **Manual**: Git clone + `composer install`
|
||||||
3. **Manual**: Git clone + `composer install` + database setup
|
|
||||||
|
|
||||||
## Database & Schema
|
## 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 schema: `20250619000001_initial_schema.php`
|
||||||
- Initial seed data: `20250619000002_seed_initial_data.php`
|
- Initial seed data: `20250619000002_seed_initial_data.php`
|
||||||
- UTF-8 (utf8mb4) character set required
|
- UTF-8 (utf8mb4) character set required
|
||||||
|
@ -118,25 +205,132 @@ php vendor/bin/phinx migrate --fake --configuration=phinx.php
|
||||||
|
|
||||||
## TorrentPier 3.0 Modernization Strategy
|
## 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
|
- **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
|
- **Performance improvements**: Optimized database queries, efficient caching
|
||||||
- **Developer experience**: Better debugging, testing, and maintenance
|
|
||||||
- **Breaking changes**: Legacy code removal and API modernization
|
- **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.
|
**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
|
### Container & Dependency Injection
|
||||||
- **Cache system**: Replace existing CACHE() and $datastore calls with new unified API
|
- **Illuminate Container**: Laravel's container for dependency injection
|
||||||
- **Configuration**: Update legacy global $bb_cfg access to use config() singleton
|
- **Bootstrap**: Clean container setup in `/bootstrap/container.php`
|
||||||
- **Templates**: Legacy template syntax may be deprecated in favor of modern Twig features
|
- **Service Providers**: Laravel-style providers in `/app/Providers/`
|
||||||
- **Language system**: Update global $lang usage to new Language singleton methods
|
- **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
|
## Markdown File Guidelines
|
||||||
|
|
||||||
|
|
116
app/Console/Commands/ClearCacheCommand.php
Normal file
116
app/Console/Commands/ClearCacheCommand.php
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear Cache Command
|
||||||
|
*
|
||||||
|
* Clears application cache files
|
||||||
|
*/
|
||||||
|
class ClearCacheCommand extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The command signature
|
||||||
|
*/
|
||||||
|
protected string $signature = 'cache:clear';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The command description
|
||||||
|
*/
|
||||||
|
protected string $description = 'Clear application cache';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the command
|
||||||
|
*/
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this->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;
|
||||||
|
}
|
||||||
|
}
|
207
app/Console/Commands/Command.php
Normal file
207
app/Console/Commands/Command.php
Normal file
|
@ -0,0 +1,207 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Command\Command as SymfonyCommand;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
use Illuminate\Container\Container;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base Command Class
|
||||||
|
*
|
||||||
|
* Laravel-style base command class for TorrentPier console commands
|
||||||
|
*/
|
||||||
|
abstract class Command extends SymfonyCommand
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The command signature (Laravel-style)
|
||||||
|
* Example: 'cache:clear {--force : Force clearing without confirmation}'
|
||||||
|
*/
|
||||||
|
protected string $signature = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The command description
|
||||||
|
*/
|
||||||
|
protected string $description = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application container
|
||||||
|
*/
|
||||||
|
protected Container $app;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Console input interface
|
||||||
|
*/
|
||||||
|
protected InputInterface $input;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Console output interface
|
||||||
|
*/
|
||||||
|
protected OutputInterface $output;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Symfony style interface
|
||||||
|
*/
|
||||||
|
protected SymfonyStyle $io;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new command instance
|
||||||
|
*/
|
||||||
|
public function __construct(?string $name = null)
|
||||||
|
{
|
||||||
|
// Parse signature if provided
|
||||||
|
if ($this->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);
|
||||||
|
}
|
||||||
|
}
|
113
app/Console/Commands/InfoCommand.php
Normal file
113
app/Console/Commands/InfoCommand.php
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Info Command
|
||||||
|
*
|
||||||
|
* Display TorrentPier system information
|
||||||
|
*/
|
||||||
|
class InfoCommand extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The command signature
|
||||||
|
*/
|
||||||
|
protected string $signature = 'info';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The command description
|
||||||
|
*/
|
||||||
|
protected string $description = 'Display system information';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the command
|
||||||
|
*/
|
||||||
|
public function handle(): int
|
||||||
|
{
|
||||||
|
$basePath = $this->app->make('path.base');
|
||||||
|
|
||||||
|
$this->line('');
|
||||||
|
$this->line('<fg=cyan>TorrentPier System Information</>');
|
||||||
|
$this->line('<fg=cyan>==============================</>');
|
||||||
|
$this->line('');
|
||||||
|
|
||||||
|
// Application info
|
||||||
|
$this->line('<fg=yellow>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('<fg=yellow>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('<fg=yellow>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('<fg=yellow>Required Extensions:</>');
|
||||||
|
foreach ($requiredExtensions as $ext) {
|
||||||
|
$status = extension_loaded($ext) ? '<fg=green>✓</>' : '<fg=red>✗</>';
|
||||||
|
$this->line(" {$status} {$ext}");
|
||||||
|
}
|
||||||
|
$this->line('');
|
||||||
|
|
||||||
|
// File permissions
|
||||||
|
$this->line('<fg=yellow>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 ? '<fg=green>✓</>' : '<fg=red>✗</>';
|
||||||
|
$this->line(" {$status} {$path}");
|
||||||
|
} else {
|
||||||
|
$this->line(" <fg=yellow>?</> {$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('<fg=yellow>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('<fg=yellow>Database:</> Configuration error');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->line('');
|
||||||
|
return self::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
101
app/Console/Commands/MigrateCommand.php
Normal file
101
app/Console/Commands/MigrateCommand.php
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrate Command
|
||||||
|
*
|
||||||
|
* Run database migrations using Phinx
|
||||||
|
*/
|
||||||
|
class MigrateCommand extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The command signature
|
||||||
|
*/
|
||||||
|
protected string $signature = 'migrate';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The command description
|
||||||
|
*/
|
||||||
|
protected string $description = 'Run database migrations';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the command
|
||||||
|
*/
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this->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;
|
||||||
|
}
|
||||||
|
}
|
47
bootstrap/console.php
Normal file
47
bootstrap/console.php
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Console Bootstrap
|
||||||
|
*
|
||||||
|
* Bootstrap the console application
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||||
|
use Symfony\Component\Console\Application;
|
||||||
|
|
||||||
|
// Only define DEXTER_BINARY if not already defined
|
||||||
|
if (!defined('DEXTER_BINARY')) {
|
||||||
|
define('DEXTER_BINARY', true);
|
||||||
|
}
|
||||||
|
require_once __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
|
// Load container bootstrap
|
||||||
|
require_once __DIR__ . '/container.php';
|
||||||
|
|
||||||
|
// Create the application container
|
||||||
|
$container = createContainer(dirname(__DIR__));
|
||||||
|
|
||||||
|
// Create Symfony Console Application
|
||||||
|
$app = new Application('TorrentPier Console', '3.0-dev');
|
||||||
|
|
||||||
|
// Get registered commands from the container
|
||||||
|
try {
|
||||||
|
if ($container->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;
|
|
@ -79,6 +79,7 @@
|
||||||
"php-curl-class/php-curl-class": "^12.0.0",
|
"php-curl-class/php-curl-class": "^12.0.0",
|
||||||
"robmorgan/phinx": "^0.16.9",
|
"robmorgan/phinx": "^0.16.9",
|
||||||
"samdark/sitemap": "2.4.1",
|
"samdark/sitemap": "2.4.1",
|
||||||
|
"symfony/console": "^7.3",
|
||||||
"symfony/mailer": "^7.3",
|
"symfony/mailer": "^7.3",
|
||||||
"symfony/polyfill": "v1.32.0",
|
"symfony/polyfill": "v1.32.0",
|
||||||
"vlucas/phpdotenv": "^5.5",
|
"vlucas/phpdotenv": "^5.5",
|
||||||
|
|
50
composer.lock
generated
50
composer.lock
generated
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "b6064b3dc730bc6bf800fdf90be38b43",
|
"content-hash": "697bc73a6378851ccaea6fb2c3c4f61d",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "arokettu/bencode",
|
"name": "arokettu/bencode",
|
||||||
|
@ -2350,6 +2350,54 @@
|
||||||
},
|
},
|
||||||
"time": "2025-05-13T15:08:45+00:00"
|
"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",
|
"name": "illuminate/container",
|
||||||
"version": "v12.19.3",
|
"version": "v12.19.3",
|
||||||
|
|
21
dexter
Executable file
21
dexter
Executable file
|
@ -0,0 +1,21 @@
|
||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dexter - TorrentPier Command Line Interface
|
||||||
|
*
|
||||||
|
* Laravel-style CLI for running commands
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Load the bootstrap and get the application
|
||||||
|
$app = require __DIR__ . '/bootstrap/console.php';
|
||||||
|
|
||||||
|
// Run the console application with proper error handling
|
||||||
|
try {
|
||||||
|
exit($app->run());
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
fwrite(STDERR, "Error: " . $e->getMessage() . "\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ This document specifies the MVC (Model-View-Controller) architecture directory s
|
||||||
# Laravel-style root structure
|
# Laravel-style root structure
|
||||||
/app/ # Application code (PSR-4: App\)
|
/app/ # Application code (PSR-4: App\)
|
||||||
├── Console/ # Console commands
|
├── Console/ # Console commands
|
||||||
│ └── Commands/ # Artisan-style commands
|
│ └── Commands/ # Artisan-style commands for Dexter
|
||||||
├── Http/ # HTTP layer
|
├── Http/ # HTTP layer
|
||||||
│ ├── Controllers/ # Controllers
|
│ ├── Controllers/ # Controllers
|
||||||
│ │ ├── Admin/ # Admin panel controllers
|
│ │ ├── Admin/ # Admin panel controllers
|
||||||
|
@ -109,7 +109,7 @@ This document specifies the MVC (Model-View-Controller) architecture directory s
|
||||||
.env # Environment variables
|
.env # Environment variables
|
||||||
.env.example # Environment example
|
.env.example # Environment example
|
||||||
composer.json # Dependencies (App\ and TorrentPier\ namespaces)
|
composer.json # Dependencies (App\ and TorrentPier\ namespaces)
|
||||||
artisan # CLI interface
|
dexter # CLI interface
|
||||||
index.php # Legacy entry point (redirects to public/)
|
index.php # Legacy entry point (redirects to public/)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -1,65 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Tests\Unit\Config;
|
|
||||||
|
|
||||||
use Tests\TestCase;
|
|
||||||
|
|
||||||
class ConfigSystemTest extends TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Test that config helper function works
|
|
||||||
*/
|
|
||||||
public function testConfigHelper(): void
|
|
||||||
{
|
|
||||||
// Test getting the config repository
|
|
||||||
$config = config();
|
|
||||||
$this->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'));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,94 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Tests\Unit\Events;
|
|
||||||
|
|
||||||
use App\Events\UserRegistered;
|
|
||||||
use App\Events\TorrentUploaded;
|
|
||||||
use Tests\TestCase;
|
|
||||||
|
|
||||||
class EventSystemTest extends TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Test that events can be dispatched
|
|
||||||
*/
|
|
||||||
public function testCanDispatchEvents(): void
|
|
||||||
{
|
|
||||||
// Create a mock listener
|
|
||||||
$called = false;
|
|
||||||
$eventData = null;
|
|
||||||
|
|
||||||
app('events')->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);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue