refactor!: migrate from custom architecture to Laravel-like framework structure

BREAKING CHANGE: Complete architectural overhaul replacing custom DDD patterns with Laravel-inspired structure

- Move static assets from styles/ to public/ directory for better web serving
- Remove obsolete architecture layers (Application, Domain, Infrastructure, Presentation)
- Delete duplicated language-specific icon files, keeping only English version
- Replace custom DI container with Laravel-style service providers
- Introduce modern app/ directory structure with Controllers, Models, Services
- Add Laravel-style bootstrap/, config/, database/, routes/ directories
- Update storage structure with proper gitignore files
- Refactor legacy code to work with new framework structure
- Remove outdated test infrastructure and add new test structure

This migration modernizes the codebase architecture while maintaining backward compatibility for core functionality.
This commit is contained in:
Yury Pikhtarev 2025-06-22 23:48:23 +04:00
commit 5bac20b54a
No known key found for this signature in database
1424 changed files with 2490 additions and 6515 deletions

View file

@ -1,7 +0,0 @@
9766c534bddad8e82e6d19f9bad5cf70b9887f9a
92ce77ec0ec703c08a659419087a373f76e711f7
2d53efc945c7747be1755d0b66557a86bdc12cbd
602137b65129b817811b80975a369ebde3270c6d
4eb26ae37e1f4c82a45961517ffeb54c20200408
e59adce848a9e10ee5775254045cbbd915236b8b
9e0a64108d62236ab07b3f8d10e8c78269b8e1d1

View file

@ -1,7 +0,0 @@
---
name: Feature / Enhancement request
about: Suggest an idea for TorrentPier
title: "[Feature]"
labels: [Feature, Enhancement]
assignees: ''
---

View file

@ -1,80 +0,0 @@
name: Continuous Deployment
on:
push:
tags:
- "v*.*.*"
jobs:
generate-changelog:
name: Generate changelog
runs-on: ubuntu-22.04
outputs:
release_body: ${{ steps.git-cliff.outputs.content }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate a changelog
uses: orhun/git-cliff-action@v4
id: git-cliff
with:
config: cliff.toml
args: -vv --latest --no-exec --github-repo ${{ github.repository }}
- name: Print the changelog
run: cat "${{ steps.git-cliff.outputs.changelog }}"
release:
name: Create release
needs: [ generate-changelog ]
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Set the release version
shell: bash
run: echo "RELEASE_VERSION=${GITHUB_REF:11}" >> $GITHUB_ENV
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
- name: Install Composer dependencies
run: composer install --no-dev --no-progress --prefer-dist --optimize-autoloader
- name: Cleanup
run: php _cleanup.php && rm _cleanup.php
- name: Create archive
id: create-zip
run: |
ZIP_NAME="torrentpier-v${{ env.RELEASE_VERSION }}.zip"
zip -r "$ZIP_NAME" . -x ".git/*"
echo "ZIP_NAME=$ZIP_NAME" >> $GITHUB_OUTPUT
- name: Publish to GitHub
if: ${{ !contains(github.ref, '-') }}
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ${{ steps.create-zip.outputs.ZIP_NAME }}
overwrite: true
tag: ${{ github.ref }}
release_name: "v${{ env.RELEASE_VERSION }}"
body: "${{ needs.generate-changelog.outputs.release_body }}"
- name: Publish to GitHub (pre-release)
if: ${{ contains(github.ref, '-') }}
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ${{ steps.create-zip.outputs.ZIP_NAME }}
overwrite: true
tag: ${{ github.ref }}
release_name: "v${{ env.RELEASE_VERSION }}"
body: "${{ needs.generate-changelog.outputs.release_body }}"
prerelease: true

View file

@ -1,74 +0,0 @@
name: Continuous Integration
on:
push:
branches:
- master
jobs:
nightly:
name: Nightly builds 📦
runs-on: ubuntu-22.04
steps:
- name: Checkout code 🗳
uses: actions/checkout@v4
- name: Setup PHP 🔩
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
- name: Install Composer dependencies 🪚
run: composer install --no-dev --no-progress --prefer-dist --optimize-autoloader
- name: Get commit hash 🔗
id: get-commit-hash
run: |
COMMIT_HASH=$(git rev-parse --short HEAD)
echo "COMMIT_HASH=$COMMIT_HASH" >> $GITHUB_OUTPUT
- name: Cleanup
run: php _cleanup.php && rm _cleanup.php
- name: Create archive 🗞
id: create-zip
run: |
ZIP_NAME="torrentpier-${{ steps.get-commit-hash.outputs.COMMIT_HASH }}.zip"
zip -r "$ZIP_NAME" . -x ".git/*"
echo "ZIP_NAME=$ZIP_NAME" >> $GITHUB_OUTPUT
- name: Upload Archive 📤
uses: actions/upload-artifact@v4
with:
name: TorrentPier-master
path: ${{ steps.create-zip.outputs.ZIP_NAME }}
deploy:
name: 🎉 Deploy
runs-on: ubuntu-22.04
steps:
- name: 🚚 Get latest code
uses: actions/checkout@v4
- name: 🔩 Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
- name: 🖇 Install Composer dependencies
run: composer install --no-dev --no-progress --prefer-dist --optimize-autoloader
- name: 📂 Sync files
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
with:
server: ${{ secrets.FTP_SERVER }}
username: ${{ secrets.FTP_USERNAME }}
password: ${{ secrets.FTP_PASSWORD }}
server-dir: ${{ secrets.FTP_DIR }}
protocol: ${{ secrets.FTP_PROTOCOL }}
port: ${{ secrets.FTP_PORT }}
exclude: |
**/.git*
**/.git*/**
.env

View file

@ -1,57 +0,0 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# PHPMD is a spin-off project of PHP Depend and
# aims to be a PHP equivalent of the well known Java tool PMD.
# What PHPMD does is: It takes a given PHP source code base
# and look for several potential problems within that source.
# These problems can be things like:
# Possible bugs
# Suboptimal code
# Overcomplicated expressions
# Unused parameters, methods, properties
# More details at https://phpmd.org/
name: PHPMD
on:
push:
branches: [ "master" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "master" ]
schedule:
- cron: '40 0 * * 3'
permissions:
contents: read
jobs:
PHPMD:
name: Run PHPMD scanning
runs-on: ubuntu-latest
permissions:
contents: read # for checkout to fetch code
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@aa1fe473f9c687b6fb896056d771232c0bc41161
with:
coverage: none
tools: phpmd
- name: Run PHPMD
run: phpmd . sarif codesize --reportfile phpmd-results.sarif
continue-on-error: true
- name: Upload analysis results to GitHub
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: phpmd-results.sarif
wait-for-processing: true

View file

@ -1,41 +0,0 @@
name: Changelog generation
on:
schedule:
- cron: '0 0 * * *'
workflow_dispatch:
jobs:
changelog:
name: Changelog generation
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: master
token: ${{ secrets.REPO_TOKEN }}
- name: Generate a changelog
uses: orhun/git-cliff-action@v4
id: git-cliff
with:
config: cliff.toml
args: v2.4.6-alpha.4.. --verbose
env:
OUTPUT: CHANGELOG.md
GITHUB_REPO: ${{ github.repository }}
- name: Print the changelog
run: cat "${{ steps.git-cliff.outputs.changelog }}"
- name: Commit changelog
run: |
git checkout master
git config --local user.name 'belomaxorka'
git config --local user.email 'roman25052006.kelesh@gmail.com'
set +e
git add CHANGELOG.md
git commit -m "changelog: Update CHANGELOG.md 📖"
git push https://${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git master

View file

@ -1,18 +0,0 @@
## set default server charset
AddDefaultCharset UTF-8
## folder listing access control
Options All -Indexes
## sitemap and atom rewrite
RewriteEngine On
RewriteRule ^sitemap.xml$ sitemap/sitemap.xml [L]
RewriteRule ^/internal_data/atom/(.*) /atom$1 [L]
## deny access to git folder
RedirectMatch 404 /\\.git(/|$)
## deny access to system files
<FilesMatch "\.(.*sql|tpl|db|inc|log|md|env)|(config|common).php$">
Require all denied
</FilesMatch>

View file

@ -1,57 +0,0 @@
<?php
/**
* TorrentPier Bull-powered BitTorrent tracker engine
*
* @copyright Copyright (c) 2005-2025 TorrentPier (https://torrentpier.com)
* @link https://github.com/torrentpier/torrentpier for the canonical source repository
* @license https://github.com/torrentpier/torrentpier/blob/master/LICENSE MIT License
*/
if (!defined('BB_ROOT')) {
define('BB_ROOT', __DIR__ . DIRECTORY_SEPARATOR);
define('BB_PATH', BB_ROOT);
}
// Check CLI mode
if (PHP_SAPI != 'cli') {
exit;
}
if (!function_exists('removeFile')) {
// Get all constants
require_once BB_ROOT . 'library/defines.php';
// Include CLI functions
require INC_DIR . '/functions_cli.php';
}
$items = [
'.github',
'.cliffignore',
'.editorconfig',
'.gitignore',
'.styleci.yml',
'_release.php',
'CHANGELOG.md',
'CLAUDE.md',
'cliff.toml',
'CODE_OF_CONDUCT.md',
'CONTRIBUTING.md',
'crowdin.yml',
'HISTORY.md',
'phpunit.xml',
'README.md',
'SECURITY.md',
'tests',
'UPGRADE_GUIDE.md'
];
foreach ($items as $item) {
$path = BB_ROOT . $item;
if (is_file($path)) {
removeFile($path);
} elseif (is_dir($path)) {
removeDir($path);
}
}

View file

@ -1,130 +0,0 @@
<?php
/**
* TorrentPier Bull-powered BitTorrent tracker engine
*
* @copyright Copyright (c) 2005-2025 TorrentPier (https://torrentpier.com)
* @link https://github.com/torrentpier/torrentpier for the canonical source repository
* @license https://github.com/torrentpier/torrentpier/blob/master/LICENSE MIT License
*/
define('BB_ROOT', __DIR__ . DIRECTORY_SEPARATOR);
define('BB_PATH', BB_ROOT);
// Check CLI mode
if (PHP_SAPI != 'cli') {
die('Please run <code style="background:#222;color:#00e01f;padding:2px 6px;border-radius:3px;">php ' . basename(__FILE__) . '</code> in CLI mode');
}
// Get all constants
require_once BB_ROOT . 'library/defines.php';
// Include CLI functions
require INC_DIR . '/functions_cli.php';
// Welcoming message
out("--- Release creation tool ---\n", 'info');
$configFile = BB_PATH . '/library/config.php';
if (!is_file($configFile)) {
out('- Config file ' . basename($configFile) . ' not found', 'error');
exit;
}
if (!is_readable($configFile)) {
out('- Config file ' . basename($configFile) . ' is not readable', 'error');
exit;
}
if (!is_writable($configFile)) {
out('- Config file ' . basename($configFile) . ' is not writable', 'error');
exit;
}
// Ask for version
fwrite(STDOUT, 'Enter version number (e.g, v2.4.0): ');
$version = trim(fgets(STDIN));
if (empty($version)) {
out("- Version cannot be empty. Please enter a valid version number", 'error');
exit;
} else {
// Add 'v' prefix if missing
if (!str_starts_with($version, 'v')) {
$version = 'v' . $version;
}
out("- Using version: $version", 'info');
}
// Ask for version emoji
fwrite(STDOUT, 'Enter version emoji: ');
$versionEmoji = trim(fgets(STDIN));
if (!empty($versionEmoji)) {
out("- Using version emoji: $versionEmoji", 'info');
}
// Ask for release date or use today's date
fwrite(STDOUT, "Enter release date (e.g. 25-05-2025), leave empty to use today's date: ");
$date = trim(fgets(STDIN));
if (empty($date)) {
$date = date('d-m-Y');
out("- Using current date: $date", 'info');
} else {
// Validate date format (dd-mm-yyyy)
$dateObj = DateTime::createFromFormat('d-m-Y', $date);
if (!$dateObj || $dateObj->format('d-m-Y') !== $date) {
out("- Invalid date format. Expected format: DD-MM-YYYY", 'error');
exit;
}
out("- Using date: $date", 'info');
}
// Read config file content
$content = file_get_contents($configFile);
// Update version
$content = preg_replace(
"/\\\$bb_cfg\['tp_version'\]\s*=\s*'[^']*';/",
"\$bb_cfg['tp_version'] = '$version';",
$content
);
// Update release date
$content = preg_replace(
"/\\\$bb_cfg\['tp_release_date'\]\s*=\s*'[^']*';/",
"\$bb_cfg['tp_release_date'] = '$date';",
$content
);
// Save updated config
$bytesWritten = file_put_contents($configFile, $content);
if ($bytesWritten === false) {
out("- Failed to write to config file", 'error');
exit;
}
if ($bytesWritten === 0) {
out("- Config file was not updated (0 bytes written)", 'error');
exit;
}
out("\n- Config file has been updated!", 'success');
// Update CHANGELOG.md
runProcess('npx git-cliff v2.4.6-alpha.4.. --config cliff.toml --tag "' . $version . '" > CHANGELOG.md');
// Git add & commit
runProcess('git add -A && git commit -m "release: ' . escapeshellarg($version) . (!empty($versionEmoji) ? (' ' . $versionEmoji) : '') . '"');
// Git tag
runProcess("git tag -a \"$version\" -m \"Release $version\"");
runProcess("git tag -v \"$version\"");
// Git push
runProcess("git push origin master");
runProcess("git push origin $version");
out("\n- Release $version has been successfully prepared, committed and pushed!", 'success');

View file

View file

@ -1,126 +0,0 @@
# git-cliff ~ TorrentPier configuration file
# https://git-cliff.org/docs/configuration
#
# Lines starting with "#" are comments.
# Configuration options are organized into tables and keys.
# See documentation for more information on available options.
[remote.github]
owner = "torrentpier"
repo = "torrentpier"
[changelog]
# template for the changelog header
header = """
[![TorrentPier](https://raw.githubusercontent.com/torrentpier/.github/refs/heads/main/versions/Cattle.png)](https://github.com/torrentpier)\n
# 📖 Change Log\n
"""
# template for the changelog body
# https://keats.github.io/tera/docs/#introduction
body = """
{%- macro remote_url() -%}
https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
{%- endmacro -%}
{%- macro nightly_url() -%}
https://nightly.link/{{ remote.github.owner }}/{{ remote.github.repo }}/workflows/ci/master/TorrentPier-master
{%- endmacro -%}
{% macro print_commit(commit) -%}
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}\
{% if commit.breaking %}[**breaking**] {% endif %}\
{{ commit.message | upper_first }} - \
([{{ commit.id | truncate(length=7, end="") }}]({{ self::remote_url() }}/commit/{{ commit.id }}))\
{% endmacro -%}
{% if version %}\
{% if previous.version %}\
## [{{ version }}]\
({{ self::remote_url() }}/compare/{{ previous.version }}..{{ version }}) ({{ timestamp | date(format="%Y-%m-%d") }})
{% else %}\
## {{ version }} ({{ timestamp | date(format="%Y-%m-%d") }})
{% endif %}\
{% else %}\
## [nightly]({{ self::nightly_url() }})
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | striptags | trim | upper_first }}
{% for commit in commits
| filter(attribute="scope")
| sort(attribute="scope") %}
{{ self::print_commit(commit=commit) }}
{%- endfor %}
{% for commit in commits %}
{%- if not commit.scope -%}
{{ self::print_commit(commit=commit) }}
{% endif -%}
{% endfor -%}
{% endfor -%}
{%- if github -%}
{% if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
## New Contributors ❤️
{% endif %}\
{% for contributor in github.contributors | filter(attribute="is_first_time", value=true) %}
* @{{ contributor.username }} made their first contribution
{%- if contributor.pr_number %} in \
[#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \
{%- endif %}
{%- endfor -%}
{%- endif %}
"""
# template for the changelog footer
footer = """
"""
# remove the leading and trailing whitespace from the templates
trim = true
# postprocessors
postprocessors = [
{ pattern = '<REPO>', replace = "https://github.com/torrentpier/torrentpier" }, # replace repository URL
]
[git]
# parse the commits based on https://www.conventionalcommits.org
conventional_commits = true
# filter out the commits that are not conventional
filter_unconventional = true
# process each line of a commit as an individual commit
split_commits = false
# regex for preprocessing the commit messages
commit_preprocessors = [
# Replace issue numbers
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/pull/${2}))" },
# Check spelling of the commit with https://github.com/crate-ci/typos
# If the spelling is incorrect, it will be automatically fixed.
# { pattern = '.*', replace_command = 'typos --write-changes -' },
]
# regex for parsing and grouping commits
commit_parsers = [
{ message = "^feat", group = "<!-- 0 -->🚀 Features" },
{ message = "^fix", group = "<!-- 1 -->🐛 Bug Fixes" },
{ message = "^doc", group = "<!-- 3 -->📚 Documentation" },
{ message = "^perf", group = "<!-- 4 -->⚡ Performance" },
{ message = "^refactor", group = "<!-- 2 -->🚜 Refactor" },
{ message = "^style", group = "<!-- 5 -->🎨 Styling" },
{ message = "^test", group = "<!-- 6 -->🧪 Testing" },
{ message = "^ignore|^release|^changelog", skip = true },
{ message = "^chore|^ci|^misc", group = "<!-- 7 -->⚙️ Miscellaneous" },
{ body = ".*security", group = "<!-- 8 -->🛡️ Security" },
{ message = "^revert", group = "<!-- 9 -->◀️ Revert" },
{ message = "^crowdin|^crodwin", group = "<!-- 10 -->🈳 New translations" }, # crowdin pulls supporting
{ message = "^Composer", group = "<!-- 11 -->📦 Dependencies" }, # dependabot pulls supporting
{ message = "^rem|^drop|^removed", group = "<!-- 12 -->🗑️ Removed" },
{ message = ".*", group = "<!-- 13 -->💼 Other" },
]
# protect breaking changes from being skipped due to matching a skipping commit_parser
protect_breaking_commits = false
# filter out the commits that are not matched by commit parsers
filter_commits = false
# regex for matching git tags
tag_pattern = "v[0-9].*"
# sort the tags topologically
topo_order = false
# sort the commits inside sections by oldest/newest order
sort_commits = "newest"

View file

@ -55,18 +55,6 @@ if (!is_file(BB_PATH . '/vendor/autoload.php')) {
}
require_once BB_PATH . '/vendor/autoload.php';
/**
* Gets the value of an environment variable.
*
* @param string $key
* @param mixed|null $default
* @return mixed
*/
function env(string $key, mixed $default = null): mixed
{
return \TorrentPier\Env::get($key, $default);
}
// Load ENV
try {
$dotenv = Dotenv\Dotenv::createMutable(BB_PATH);

View file

@ -59,16 +59,17 @@
"gemorroj/m3u-parser": "^6.0.1",
"gigablah/sphinxphp": "2.0.8",
"google/recaptcha": "^1.3",
"illuminate/container": "^12.19",
"illuminate/http": "^12.19",
"illuminate/support": "^12.19",
"jacklul/monolog-telegram": "^3.1",
"josantonius/cookie": "^2.0",
"league/flysystem": "^3.28",
"league/route": "^6.2",
"longman/ip-tools": "1.2.1",
"monolog/monolog": "^3.4",
"nette/caching": "^3.3",
"nette/database": "^3.2",
"php-curl-class/php-curl-class": "^12.0.0",
"php-di/php-di": "^7.0",
"robmorgan/phinx": "^0.16.9",
"samdark/sitemap": "2.4.1",
"symfony/mailer": "^7.3",
@ -83,10 +84,12 @@
},
"autoload": {
"psr-4": {
"TorrentPier\\": "src/"
"TorrentPier\\": "src/",
"App\\": "app/"
},
"files": [
"src/helpers.php"
"src/helpers.php",
"app/helpers.php"
]
},
"autoload-dev": {

2136
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,99 +0,0 @@
<?php
/**
* Example: How to use the DI Container in TorrentPier 3.0
*
* NOTE: These are examples for future implementation following the hexagonal architecture spec.
* Most services referenced here don't exist yet and will be implemented in phases.
*/
use TorrentPier\Infrastructure\DependencyInjection\Bootstrap;
use TorrentPier\Infrastructure\DependencyInjection\ContainerFactory;
// ===== PHASE 1: Foundation (CURRENT) =====
// 1. Bootstrap the container (typically done once in index.php or bootstrap file)
$rootPath = __DIR__ . '/../..';
$container = Bootstrap::init($rootPath);
// 2. Basic container usage (works now)
$containerInstance = app(); // Get container itself
$hasService = $container->has('some.service'); // Check if service exists
// ===== PHASE 2: Domain Modeling (FUTURE) =====
// 3. Repository interfaces (when implemented in Domain layer)
// $userRepository = app('TorrentPier\Domain\User\Repository\UserRepositoryInterface');
// $torrentRepository = app('TorrentPier\Domain\Tracker\Repository\TorrentRepositoryInterface');
// $forumRepository = app('TorrentPier\Domain\Forum\Repository\ForumRepositoryInterface');
// ===== PHASE 3: Application Services (FUTURE) =====
// 4. Command/Query handlers (when implemented)
// $registerUserHandler = app('TorrentPier\Application\User\Handler\RegisterUserHandler');
// $announceHandler = app('TorrentPier\Application\Tracker\Handler\ProcessAnnounceHandler');
// $createPostHandler = app('TorrentPier\Application\Forum\Handler\CreatePostHandler');
// 5. Making command instances with parameters
// $command = $container->make('TorrentPier\Application\User\Command\RegisterUserCommand', [
// 'username' => 'john_doe',
// 'email' => 'john@example.com',
// 'password' => 'secure_password'
// ]);
// ===== PHASE 4: Infrastructure (FUTURE) =====
// 6. Database and cache (when infrastructure is implemented)
// $database = app('database.connection.default');
// $cache = app('cache.factory')('forum'); // Get cache instance for 'forum' namespace
// ===== PHASE 5: Presentation (FUTURE) =====
// 7. Controllers (when implemented)
// $userController = app('TorrentPier\Presentation\Http\Controllers\Api\UserController');
// $trackerController = app('TorrentPier\Presentation\Http\Controllers\Web\TrackerController');
// ===== TESTING EXAMPLES =====
// 8. Testing with custom container (works now)
$testContainer = ContainerFactory::create([
'definitions' => [
'test.service' => \DI\factory(function () {
return new class {
public function test() { return 'test'; }
};
}),
],
'environment' => 'testing',
]);
// 9. Safe service resolution (works now)
try {
$service = app('optional.service');
} catch (RuntimeException $e) {
// Service not found, handle gracefully
$service = null;
}
// ===== LEGACY INTEGRATION (CURRENT) =====
// 10. Integration with legacy code
// In legacy files, after including common.php or similar:
if (!Bootstrap::getContainer()) {
Bootstrap::init(BB_ROOT ?? __DIR__ . '/../..');
}
// 11. Method injection (works now if service exists)
class ExampleService
{
public function processData(string $data)
{
// Container can inject dependencies when calling this method
return "Processed: $data";
}
}
$exampleService = new ExampleService();
$result = $container->call([$exampleService, 'processData'], [
'data' => 'test data'
]);

View file

@ -1,436 +0,0 @@
# Hexagonal Architecture Directory Structure Specification
## Overview
This document specifies the new hexagonal architecture directory structure for TorrentPier 3.0. The structure follows Domain-Driven Design (DDD) principles and implements a clean separation of concerns through hexagonal architecture (ports and adapters pattern).
## Directory Structure
```
/src/
├── Domain/ # Core business logic - no framework dependencies
│ ├── Forum/ # Forum bounded context
│ │ ├── Model/ # Aggregates and entities
│ │ ├── ValueObject/ # Value objects (PostId, ThreadTitle, etc.)
│ │ ├── Repository/ # Repository interfaces
│ │ └── Exception/ # Domain-specific exceptions
│ ├── Tracker/ # BitTorrent tracker bounded context
│ │ ├── Model/ # Torrent, Peer aggregates
│ │ ├── ValueObject/ # InfoHash, PeerId, etc.
│ │ ├── Repository/ # Repository interfaces
│ │ └── Exception/ # Tracker-specific exceptions
│ ├── User/ # User management bounded context
│ │ ├── Model/ # User aggregate
│ │ ├── ValueObject/ # UserId, Email, Username
│ │ ├── Repository/ # User repository interface
│ │ └── Exception/ # Authentication/authorization exceptions
│ └── Shared/ # Shared kernel - minimal shared concepts
│ ├── Model/ # Base classes (AggregateRoot, Entity)
│ ├── ValueObject/ # Common value objects (Id, DateTime)
│ └── Event/ # Domain events base classes
├── Application/ # Application services - orchestration layer
│ ├── Forum/
│ │ ├── Command/ # Commands (CreatePost, LockThread)
│ │ ├── Query/ # Queries (GetThreadList, SearchPosts)
│ │ └── Handler/ # Command and query handlers
│ ├── Tracker/
│ │ ├── Command/ # Commands (RegisterTorrent, ProcessAnnounce)
│ │ ├── Query/ # Queries (GetPeerList, GetTorrentStats)
│ │ └── Handler/ # Command and query handlers
│ └── User/
│ ├── Command/ # Commands (RegisterUser, ChangePassword)
│ ├── Query/ # Queries (GetUserProfile, SearchUsers)
│ └── Handler/ # Command and query handlers
├── Infrastructure/ # External concerns and implementations
│ ├── Persistence/ # Data persistence layer
│ │ ├── Database/ # Database adapter and connection management
│ │ ├── Migration/ # Database migrations
│ │ └── Repository/ # Repository implementations
│ ├── Cache/ # Caching implementations
│ │ ├── Redis/ # Redis adapter
│ │ ├── Memcached/ # Memcached adapter
│ │ └── File/ # File-based cache adapter
│ ├── Email/ # Email service implementations
│ │ ├── Template/ # Email templates
│ │ └── Transport/ # SMTP, API transports
│ └── FileStorage/ # File storage abstractions
│ ├── Local/ # Local filesystem storage
│ └── S3/ # AWS S3 storage adapter
└── Presentation/ # User interface layer
├── Http/ # Web interface
│ ├── Controllers/ # HTTP controllers
│ │ ├── Admin/ # Admin panel controllers
│ │ ├── Api/ # REST API controllers
│ │ └── Web/ # Web UI controllers
│ ├── Middleware/ # HTTP middleware (auth, CORS, etc.)
│ ├── Requests/ # Request DTOs and validation
│ └── Responses/ # Response transformers
└── Cli/ # Command line interface
└── Commands/ # Console commands
# Additional directories (outside /src/)
/config/ # Application configuration
├── app.php # Main application settings
├── database.php # Database connections
├── cache.php # Cache drivers configuration
├── tracker.php # BitTorrent tracker settings
└── environments/ # Environment-specific overrides
/tests/ # Test suites (Pest)
├── Unit/ # Unit tests (mirrors src/ structure)
├── Feature/ # Feature/Integration tests
├── Pest.php # Pest configuration
└── TestCase.php # Base test case
```
## Directory README.md Templates
### Domain Layer READMEs
#### `/src/Domain/README.md`
```markdown
# Domain Layer
This directory contains the core business logic of TorrentPier. Code here should:
- Have no dependencies on frameworks or infrastructure
- Represent pure business rules and domain models
- Be testable in isolation
- Use only PHP language features and domain concepts
## Bounded Contexts
- **Forum**: Discussion forums, posts, threads
- **Tracker**: BitTorrent tracking, peers, torrents
- **User**: User management, authentication, profiles
- **Shared**: Minimal shared concepts between contexts
```
#### `/src/Domain/Tracker/Model/README.md`
```markdown
# Tracker Domain Models
Contains aggregate roots and entities for the BitTorrent tracker:
- `Torrent`: Aggregate root for torrent management
- `Peer`: Entity representing a BitTorrent peer
- `TorrentStatistics`: Value object for torrent stats
Example:
```php
class Torrent extends AggregateRoot
{
public function announce(Peer $peer, AnnounceEvent $event): void
{
// Business logic for handling announces
}
}
```
#### `/src/Domain/Tracker/ValueObject/README.md`
```markdown
# Tracker Value Objects
Immutable objects representing domain concepts:
- `InfoHash`: 20-byte torrent identifier
- `PeerId`: Peer client identifier
- `Port`: Network port (1-65535)
- `BytesTransferred`: Upload/download bytes
Example:
```php
final class InfoHash
{
private string $hash;
public function __construct(string $hash)
{
$this->guardAgainstInvalidHash($hash);
$this->hash = $hash;
}
}
```
### Application Layer READMEs
#### `/src/Application/README.md`
```markdown
# Application Layer
Contains application services that orchestrate domain objects to fulfill use cases.
- Commands: Write operations that change state
- Queries: Read operations for data retrieval
- Handlers: Process commands and queries
This layer should:
- Coordinate domain objects
- Handle transactions
- Dispatch domain events
- Not contain business logic
```
#### `/src/Application/Tracker/Command/README.md`
```markdown
# Tracker Commands
Commands representing write operations:
- `RegisterTorrentCommand`: Register new torrent
- `UpdateTorrentCommand`: Modify torrent details
- `DeleteTorrentCommand`: Remove torrent from tracker
Example:
```php
final class RegisterTorrentCommand
{
public function __construct(
public readonly string $infoHash,
public readonly int $uploaderId,
public readonly string $name,
public readonly int $size
) {}
}
```
### Infrastructure Layer READMEs
#### `/src/Infrastructure/README.md`
```markdown
# Infrastructure Layer
Technical implementations and external service adapters:
- Database persistence
- Caching mechanisms
- Email services
- File storage
- Third-party integrations
Infrastructure depends on domain, not vice versa.
```
#### `/src/Infrastructure/Persistence/Repository/README.md`
```markdown
# Repository Implementations
Concrete implementations of domain repository interfaces:
- Uses database adapter for persistence
- Implements caching strategies
- Handles query optimization
- Supports multiple database backends
Example:
```php
class TorrentRepository implements TorrentRepositoryInterface
{
public function __construct(
private DatabaseAdapterInterface $db
) {}
public function findByInfoHash(InfoHash $infoHash): ?Torrent
{
// Database adapter implementation
$row = $this->db->select('torrents')
->where('info_hash', $infoHash->toString())
->first();
return $row ? $this->hydrateFromRow($row) : null;
}
}
```
### Presentation Layer READMEs
#### `/src/Presentation/README.md`
```markdown
# Presentation Layer
User interface implementations:
- HTTP controllers for web and API
- CLI commands for console operations
- Request/response handling
- Input validation
- Output formatting
This layer translates between external format and application format.
```
#### `/src/Presentation/Http/Controllers/Api/README.md`
```markdown
# API Controllers
RESTful API endpoints following OpenAPI specification:
- JSON request/response format
- Proper HTTP status codes
- HATEOAS where applicable
- Rate limiting aware
Example:
```php
class UserController
{
public function register(RegisterRequest $request): JsonResponse
{
$command = new RegisterUserCommand(
$request->getUsername(),
$request->getEmail(),
$request->getPassword()
);
$userId = $this->commandBus->handle($command);
return new JsonResponse([
'id' => $userId,
'username' => $request->getUsername()
], Response::HTTP_CREATED);
}
}
```
#### `/src/Presentation/Http/Controllers/Admin/README.md`
```markdown
# Admin Panel Controllers
Administrative interface controllers with enhanced security:
- Role-based access control (RBAC)
- Audit logging for all actions
- Additional authentication checks
- Administrative dashboards and reports
Example:
```php
class AdminUserController
{
public function index(Request $request): Response
{
$query = new GetUsersQuery(
page: $request->getPage(),
filters: $request->getFilters()
);
$users = $this->queryBus->handle($query);
return $this->render('admin/users/index', [
'users' => $users,
'filters' => $request->getFilters()
]);
}
}
```
#### `/config/README.md`
```markdown
# Application Configuration
System configuration files using PHP arrays for type safety and IDE support:
- **app.php**: Core application settings
- Site name, URL, timezone
- Debug mode, environment
- Feature flags and toggles
- **database.php**: Database connection settings
- Multiple connection definitions
- Read/write splitting configuration
- Connection pooling settings
- **cache.php**: Cache driver configurations
- Redis, Memcached, file-based settings
- TTL defaults per cache type
- Cache key prefixes
- **tracker.php**: BitTorrent tracker settings
- Announce intervals
- Peer limits
- Ratio requirements
- **environments/**: Environment-specific overrides
- Development, staging, production settings
- Local developer configurations
Example database configuration:
```php
<?php
return [
'default' => env('DB_CONNECTION', 'mysql'),
'connections' => [
'mysql' => [
'driver' => 'mysql',
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', 3306),
'database' => env('DB_DATABASE', 'tp'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'options' => [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
],
],
],
];
```
## Implementation Order
1. **Phase 1: Foundation**
- Create directory structure
- Set up base classes in Domain/Shared
- Configure dependency injection
2. **Phase 2: Domain Modeling**
- Implement core aggregates
- Create value objects
- Define repository interfaces
3. **Phase 3: Application Services**
- Create commands and queries
- Implement handlers
- Set up event dispatching
4. **Phase 4: Infrastructure**
- Implement repositories
- Configure database adapter
- Set up caching
5. **Phase 5: Presentation**
- Create controllers
- Implement middleware
- Build CLI commands
## Migration Strategy
- Existing code remains in current locations
- New features built in hexagonal architecture
- Gradual migration using strangler fig pattern
- Legacy adapters bridge old and new code
- Feature flags control rollout
## Key Principles
1. **Dependency Rule**: Dependencies point inward (Presentation → Application → Domain)
2. **Domain Isolation**: Business logic has no framework dependencies
3. **Interface Segregation**: Small, focused interfaces
4. **CQRS**: Separate read and write models
5. **Event-Driven**: Domain events for cross-context communication
## Testing Strategy
- **Domain**: Pure unit tests, no mocks needed
- **Application**: Unit tests with mocked repositories
- **Infrastructure**: Integration tests with real services
- **Presentation**: E2E tests for user journeys
## Notes for Developers
- Start reading code from the Domain layer
- Business rules live in aggregates, not services
- Use value objects for type safety
- Prefer composition over inheritance
- Keep bounded contexts loosely coupled

View file

@ -0,0 +1,496 @@
# MVC Architecture Directory Structure Specification
## Overview
This document specifies the MVC (Model-View-Controller) architecture directory structure for TorrentPier 3.0. The structure follows a simple, Laravel-inspired approach that prioritizes developer familiarity and ease of maintenance over complex enterprise patterns.
## Directory Structure
```
# Laravel-style root structure
/app/ # Application code (PSR-4: App\)
├── Console/ # Console commands
│ └── Commands/ # Artisan-style commands
├── Http/ # HTTP layer
│ ├── Controllers/ # Controllers
│ │ ├── Admin/ # Admin panel controllers
│ │ ├── Api/ # API controllers
│ │ └── Web/ # Web controllers
│ ├── Middleware/ # HTTP middleware
│ └── Requests/ # Form request validation
├── Models/ # Data models (using Nette Database)
│ ├── Forum/ # Forum models (Post, Thread, Forum)
│ ├── Tracker/ # Tracker models (Torrent, Peer, Announce)
│ └── User/ # User models (User, Profile, Permission)
├── Services/ # Business logic services
│ ├── Forum/ # Forum services
│ ├── Tracker/ # Tracker services
│ └── User/ # User services
├── Repositories/ # Data access layer (optional)
├── Events/ # Event classes
├── Listeners/ # Event listeners
├── Jobs/ # Background jobs
├── Mail/ # Mailable classes
├── Notifications/ # Notification classes
└── Exceptions/ # Exception handling
/bootstrap/ # Application bootstrap
├── app.php # Application bootstrap
├── console.php # Console bootstrap
└── cache/ # Bootstrap cache
/config/ # Configuration files
├── app.php # Application config
├── auth.php # Authentication config
├── cache.php # Cache configuration
├── database.php # Database connections
├── filesystems.php # File storage config
├── tracker.php # BitTorrent tracker settings
└── (legacy configs...) # Existing config files
/database/ # Database files
├── migrations/ # Database migrations (moved from /migrations/)
├── factories/ # Model factories for testing
└── seeders/ # Database seeders
/public/ # Public web root
├── index.php # Front controller
├── css/ # Public CSS
├── js/ # Public JavaScript
├── images/ # Public images
└── fonts/ # Web fonts
/resources/ # Resources
├── views/ # View templates
│ ├── admin/ # Admin panel views
│ ├── forum/ # Forum views
│ ├── tracker/ # Tracker views
│ └── layouts/ # Layout templates
├── lang/ # Language files
├── js/ # JavaScript source
└── css/ # CSS/SCSS source
/routes/ # Route definitions
├── web.php # Web routes
├── api.php # API routes
├── admin.php # Admin routes
└── console.php # Console routes
/src/ # Framework/Infrastructure code (PSR-4: TorrentPier\)
├── Database/ # Database abstraction
├── Cache/ # Cache system
├── Infrastructure/ # DI container, HTTP routing, etc.
├── Legacy/ # Legacy code adapters
└── helpers.php # Global helper functions
/storage/ # Storage directory
├── app/ # Application storage
│ ├── public/ # Publicly accessible files
│ └── private/ # Private files
├── framework/ # Framework storage
│ ├── cache/ # File cache
│ ├── sessions/ # Session files
│ └── views/ # Compiled view cache
└── logs/ # Application logs
/tests/ # Test suites
├── Feature/ # Feature tests
├── Unit/ # Unit tests
└── TestCase.php # Base test case
# Legacy directories (being migrated)
/library/ # Legacy core code
/controllers/ # Legacy PHP controllers
/admin/ # Legacy admin interface
/styles/ # Legacy templates/assets
/internal_data/ # Legacy cache/logs
# Root files
.env # Environment variables
.env.example # Environment example
composer.json # Dependencies (App\ and TorrentPier\ namespaces)
artisan # CLI interface
index.php # Legacy entry point (redirects to public/)
```
## Directory README.md Templates
### Application Layer READMEs
#### `/app/README.md`
```markdown
# Application Directory
This directory contains the core application code following MVC pattern:
- **Models**: Database models and business entities
- **Controllers**: Handle HTTP requests and responses
- **Services**: Business logic and application services
- **Console**: CLI commands for maintenance and operations
## Key Components
- **Http**: Web and API controllers, middleware, requests
- **Models**: Database models using Nette Database
- **Services**: Reusable business logic
- **Events**: Application events and listeners
```
#### `/app/Models/Tracker/README.md`
```markdown
# Tracker Models
Database models for BitTorrent tracker functionality:
- `Torrent`: Torrent information and metadata
- `Peer`: Active peers in swarms
- `Announce`: Announce history and statistics
Example:
```php
class Torrent extends Model
{
protected string $table = 'bb_torrents';
public function getPeers(): array
{
return $this->db->table('bb_peers')
->where('torrent_id', $this->id)
->fetchAll();
}
public function getUser(): ?User
{
return User::find($this->user_id);
}
}
```
#### `/app/Services/Tracker/README.md`
```markdown
# Tracker Services
Business logic for tracker operations:
- `AnnounceService`: Handle peer announces
- `ScrapeService`: Provide torrent statistics
- `TorrentService`: Torrent management operations
Example:
```php
class AnnounceService
{
public function __construct(
private TorrentRepository $torrents,
private PeerRepository $peers
) {}
public function handleAnnounce(string $infoHash, array $data): array
{
$torrent = $this->torrents->findByInfoHash($infoHash);
$peers = $this->peers->getActivePeers($torrent->id);
return ['peers' => $peers, 'interval' => 900];
}
}
```
### Controllers READMEs
#### `/app/Http/Controllers/README.md`
```markdown
# Controllers
HTTP controllers following RESTful conventions:
- Accept HTTP requests
- Validate input
- Call services for business logic
- Return appropriate responses
Controllers should be thin - delegate business logic to services.
```
#### `/app/Http/Controllers/Web/TrackerController.php`
```markdown
# Tracker Web Controller
Handles web interface for tracker functionality:
Example:
```php
class TrackerController extends Controller
{
public function __construct(
private TorrentService $torrentService
) {}
public function index(Request $request)
{
$torrents = $this->torrentService->paginate(
$request->get('page', 1),
$request->get('category')
);
return view('tracker.index', compact('torrents'));
}
public function store(StoreTorrentRequest $request)
{
$torrent = $this->torrentService->create(
$request->validated(),
$request->user()
);
return redirect()->route('torrents.show', $torrent);
}
}
```
### Services Layer READMEs
#### `/app/Services/README.md`
```markdown
# Services
Reusable business logic organized by feature:
- Encapsulate complex operations
- Coordinate between models
- Handle external integrations
- Maintain single responsibility
Services are injected into controllers and commands.
```
#### `/app/Repositories/README.md`
```markdown
# Repositories (Optional)
Data access layer for complex queries:
- Abstracts database queries from models
- Implements caching strategies
- Handles query optimization
Example:
```php
class TorrentRepository
{
public function __construct(
private Database $db,
private CacheManager $cache
) {}
public function findByInfoHash(string $infoHash): ?Torrent
{
return $this->cache->remember("torrent:{$infoHash}", 3600, function() use ($infoHash) {
$data = $this->db->table('bb_torrents')
->where('info_hash', $infoHash)
->fetch();
return $data ? new Torrent($data) : null;
});
}
public function getPopularTorrents(int $limit = 10): array
{
return $this->db->table('bb_torrents')
->select('torrents.*, COUNT(peers.id) as peer_count')
->leftJoin('bb_peers', 'peers.torrent_id = torrents.id')
->groupBy('torrents.id')
->orderBy('peer_count DESC')
->limit($limit)
->fetchAll();
}
}
```
### Views READMEs
#### `/resources/views/README.md`
```markdown
# Views
Template files for rendering HTML:
- Layouts for consistent structure
- Partials for reusable components
- Feature-specific views
- Email templates
Using PHP templates with simple helper functions.
```
#### `/app/Http/Controllers/Api/README.md`
```markdown
# API Controllers
RESTful API endpoints:
- JSON request/response format
- Proper HTTP status codes
- API versioning support
- Rate limiting aware
Example:
```php
class UserController extends Controller
{
public function __construct(
private UserService $userService
) {}
public function register(Request $request): JsonResponse
{
$validatedData = $this->validate($request, [
'username' => 'required|unique:users',
'email' => 'required|email|unique:users',
'password' => 'required|min:8'
]);
$user = $this->userService->register($validatedData);
return response()->json([
'id' => $user->id,
'username' => $user->username
], 201);
}
}
```
#### `/app/Http/Controllers/Admin/README.md`
```markdown
# Admin Panel Controllers
Administrative interface controllers:
- Protected by admin middleware
- Activity logging
- Bulk operations support
- Dashboard and reports
Example:
```php
class AdminUserController extends Controller
{
public function __construct(
private UserService $userService
) {
$this->middleware('admin');
}
public function index(Request $request)
{
$query = $request->get('search');
$users = $this->userService->searchUsers($query)
->paginate(20);
return view('admin.users.index', compact('users'));
}
}
```
#### `/config/README.md`
```markdown
# Application Configuration
System configuration files using PHP arrays:
- **app.php**: Core application settings
- Site name, URL, timezone
- Debug mode, environment
- Feature flags and toggles
- **database.php**: Database connection settings
- Multiple connection definitions
- Read/write splitting configuration
- Connection pooling settings
- **cache.php**: Cache driver configurations
- Redis, Memcached, file-based settings
- TTL defaults per cache type
- Cache key prefixes
- **tracker.php**: BitTorrent tracker settings
- Announce intervals
- Peer limits
- Ratio requirements
Example database configuration:
```php
<?php
return [
'default' => env('DB_CONNECTION', 'mysql'),
'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',
'options' => [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
],
],
],
];
```
## Implementation Order
1. **Phase 1: Foundation**
- Create directory structure
- Set up routing files
- Configure service providers
- Set up middleware
2. **Phase 2: Models & Database**
- Create database models
- Define relationships
- Set up migrations
- Create seeders
3. **Phase 3: Services & Business Logic**
- Implement service classes
- Create repositories (if needed)
- Set up events and listeners
4. **Phase 4: Controllers & Routes**
- Create web controllers
- Build API endpoints
- Set up admin controllers
- Define routes
5. **Phase 5: Views & Frontend**
- Create template layouts
- Build view components
- Set up assets pipeline
## Migration Strategy
- Move existing controllers to /app/Http/Controllers/Legacy
- Gradually rewrite to new MVC structure
- Use service classes to encapsulate business logic
- Maintain backward compatibility through routing
- Progressive enhancement approach
## Key Principles
1. **Simplicity**: Straightforward MVC pattern
2. **Convention over Configuration**: Consistent naming and structure
3. **Fat Models, Skinny Controllers**: Business logic in models/services
4. **Service Layer**: Complex operations in service classes
5. **Repository Pattern**: Optional for complex queries
## Testing Strategy
- **Unit Tests**: Models and services
- **Feature Tests**: HTTP endpoints and user flows
- **Integration Tests**: Database and external services
- **Browser Tests**: Critical user journeys
## Notes for Developers
- Keep controllers thin, move logic to services
- Use dependency injection for testability
- Use Nette Database for data access
- Write readable code over clever code
- Focus on maintainability

View file

@ -1,35 +0,0 @@
<?php /** @noinspection PhpDefineCanBeReplacedWithConstInspection */
declare(strict_types=1);
/**
* Modern routing entry point for TorrentPier 3.0
*
* This file bootstraps the new hexagonal architecture routing system
* using league/route and the dependency injection container.
*/
// Bootstrap autoloader
require_once __DIR__ . '/vendor/autoload.php';
// Load legacy common.php to initialize global functions and legacy components
require_once __DIR__ . '/common.php';
// Using define() instead of const to ensure global accessibility for legacy controllers
define('IN_TORRENTPIER', true);
use TorrentPier\Infrastructure\DependencyInjection\Bootstrap;
use TorrentPier\Presentation\Http\Kernel;
// Initialize the dependency injection container
$container = Bootstrap::init(__DIR__);
// Get the HTTP kernel from the container
$kernel = $container->get(Kernel::class);
// Load web routes
$routesFile = __DIR__ . '/src/Presentation/Http/Routes/web.php';
$kernel->loadRoutes($routesFile);
// Handle the request and send response
$kernel->run();

View file

@ -1,331 +0,0 @@
<?php
/**
* TorrentPier Bull-powered BitTorrent tracker engine
*
* @copyright Copyright (c) 2005-2025 TorrentPier (https://torrentpier.com)
* @link https://github.com/torrentpier/torrentpier for the canonical source repository
* @license https://github.com/torrentpier/torrentpier/blob/master/LICENSE MIT License
*/
define('BB_ROOT', __DIR__ . DIRECTORY_SEPARATOR);
define('BB_PATH', BB_ROOT);
// Check CLI mode
if (PHP_SAPI != 'cli') {
die('Please run <code style="background:#222;color:#00e01f;padding:2px 6px;border-radius:3px;">php ' . basename(__FILE__) . '</code> in CLI mode');
}
// Get all constants
require_once BB_ROOT . 'library/defines.php';
// Include CLI functions
require INC_DIR . '/functions_cli.php';
/**
* System requirements
*/
const CHECK_REQUIREMENTS = [
'php_min_version' => '8.3.0',
'ext_list' => [
'json',
'curl',
'readline',
'mysqli',
'bcmath',
'mbstring',
'intl',
'xml',
'xmlwriter',
'zip',
'gd'
],
];
// Welcoming message
out("--- TorrentPier Installer ---\n", 'info');
// Checking extensions
out("- Checking installed extensions...", 'info');
// [1] Check PHP Version
if (!version_compare(PHP_VERSION, CHECK_REQUIREMENTS['php_min_version'], '>=')) {
out("- TorrentPier requires PHP version " . CHECK_REQUIREMENTS['php_min_version'] . "+ Your PHP version " . PHP_VERSION, 'warning');
}
// [2] Check installed PHP Extensions on server
foreach (CHECK_REQUIREMENTS['ext_list'] as $ext) {
if (!extension_loaded($ext)) {
out("- ext-$ext not installed. Check out php.ini file", 'error');
if (!defined('EXTENSIONS_NOT_INSTALLED')) {
define('EXTENSIONS_NOT_INSTALLED', true);
}
} else {
out("- ext-$ext installed!");
}
}
if (!defined('EXTENSIONS_NOT_INSTALLED')) {
out("- All extensions are installed!\n", 'success');
} else {
exit;
}
// Check if already installed
if (is_file(BB_ROOT . '.env')) {
out('- TorrentPier already installed', 'warning');
echo 'Are you sure want to re-install TorrentPier? [y/N]: ';
if (str_starts_with(mb_strtolower(trim(readline())), 'y')) {
out("\n- Re-install process started...", 'info');
// environment
if (is_file(BB_ROOT . '.env')) {
if (unlink(BB_ROOT . '.env')) {
out('- Environment file successfully removed!');
} else {
out('- Cannot remove environment (.env) file. Delete it manually', 'error');
exit;
}
}
// composer.phar
if (is_file(BB_ROOT . 'composer.phar')) {
if (unlink(BB_ROOT . 'composer.phar')) {
out("- composer.phar file successfully removed!");
} else {
out('- Cannot remove composer.phar file. Delete it manually', 'error');
exit;
}
}
// composer dir
if (is_dir(BB_ROOT . 'vendor')) {
removeDir(BB_ROOT . 'vendor', true);
if (!is_dir(BB_ROOT . 'vendor')) {
out("- Composer directory successfully removed!");
} else {
out('- Cannot remove Composer directory. Delete it manually', 'error');
exit;
}
}
out("- Re-install process completed!\n", 'success');
out('- Starting installation...', 'info');
} else {
exit;
}
}
// Applying permissions
out("- Applying permissions for folders...", 'info');
chmod_r(BB_ROOT . 'data', 0755, 0644);
chmod_r(BB_ROOT . 'internal_data', 0755, 0644);
chmod_r(BB_ROOT . 'sitemap', 0755, 0644);
out("- Permissions successfully applied!\n", 'success');
// Check composer installation
if (!is_file(BB_ROOT . 'vendor/autoload.php')) {
out('- Hmm, it seems there are no Composer dependencies', 'info');
// Downloading composer
if (!is_file(BB_ROOT . 'composer.phar')) {
out('- Downloading Composer...', 'info');
if (copy('https://getcomposer.org/installer', BB_ROOT . 'composer-setup.php')) {
out("- Composer successfully downloaded!\n", 'success');
runProcess('php ' . BB_ROOT . 'composer-setup.php --install-dir=' . BB_ROOT);
} else {
out('- Cannot download Composer. Please, download it (composer.phar) manually', 'error');
exit;
}
if (is_file(BB_ROOT . 'composer-setup.php')) {
if (unlink(BB_ROOT . 'composer-setup.php')) {
out("- Composer installation file successfully removed!\n", 'success');
} else {
out('- Cannot remove Composer installation file (composer-setup.php). Please, delete it manually', 'warning');
}
}
} else {
out("- composer.phar file found!\n", 'success');
}
// Installing dependencies
if (is_file(BB_ROOT . 'composer.phar')) {
out('- Installing dependencies...', 'info');
runProcess('php ' . BB_ROOT . 'composer.phar install --no-interaction --no-ansi');
define('COMPOSER_COMPLETED', true);
} else {
out('- composer.phar not found. Please, download it (composer.phar) manually', 'error');
exit;
}
} else {
out('- Composer dependencies are present!', 'success');
out("- Note: Remove 'vendor' folder if you want to re-install dependencies\n");
}
// Check composer dependencies
if (defined('COMPOSER_COMPLETED')) {
if (is_file(BB_ROOT . 'vendor/autoload.php')) {
out("- Completed! Composer dependencies are installed!\n", 'success');
} else {
exit;
}
}
// Preparing ENV
if (is_file(BB_ROOT . '.env.example') && !is_file(BB_ROOT . '.env')) {
if (copy(BB_ROOT . '.env.example', BB_ROOT . '.env')) {
out("- Environment file created!\n", 'success');
} else {
out('- Cannot create environment file', 'error');
exit;
}
}
// Editing ENV file
$DB_HOST = 'localhost';
$DB_PORT = 3306;
$DB_DATABASE = '';
$DB_USERNAME = '';
$DB_PASSWORD = '';
if (is_file(BB_ROOT . '.env')) {
out("--- Configuring TorrentPier ---", 'info');
$envContent = file_get_contents(BB_ROOT . '.env');
if ($envContent === false) {
out('- Cannot open environment file', 'error');
exit;
}
$envLines = explode("\n", $envContent);
$editedLines = [];
foreach ($envLines as $line) {
if (trim($line) !== '' && !str_starts_with($line, '#')) {
$parts = explode('=', $line, 2);
$key = trim($parts[0]);
$value = (!empty($parts[1]) && $key !== 'DB_PASSWORD') ? trim($parts[1]) : '';
out("\nCurrent value of $key: $value", 'debug');
echo "Enter a new value for $key (or leave empty to not change): ";
$newValue = trim(readline());
if (!empty($newValue) || $key === 'DB_PASSWORD') {
if ($key === 'TP_HOST') {
if (!preg_match('/^https?:\/\//', $newValue)) {
$newValue = 'https://' . $newValue;
}
$newValue = parse_url($newValue, PHP_URL_HOST);
}
$line = "$key=$newValue";
$$key = $newValue;
} else {
$$key = $value;
}
}
$editedLines[] = $line;
}
$newEnvContent = implode("\n", $editedLines);
if (file_put_contents(BB_ROOT . '.env', $newEnvContent)) {
out("- TorrentPier successfully configured!\n", 'success');
} else {
out('- Cannot save environment file', 'error');
exit;
}
} else {
out('- Environment file not found', 'error');
exit;
}
if (!empty($DB_HOST) && !empty($DB_DATABASE) && !empty($DB_USERNAME)) {
out("--- Checking environment settings ---\n", 'info');
// Connecting to database
out("- Trying connect to MySQL...", 'info');
// Checking mysqli extension installed
if (!extension_loaded('mysqli')) {
out('- ext-mysqli not found. Check out php.ini file', 'error');
exit;
}
// Connect to MySQL server
try {
$conn = new mysqli($DB_HOST, $DB_USERNAME, $DB_PASSWORD, port: $DB_PORT);
} catch (mysqli_sql_exception $exception) {
out("- Connection failed: {$exception->getMessage()}", 'error');
exit;
}
if (!$conn->connect_error) {
out('- Connected successfully!', 'success');
}
// Creating database if not exist
if ($conn->query("CREATE DATABASE IF NOT EXISTS $DB_DATABASE CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci")) {
out('- Database created successfully!', 'success');
} else {
out("- Cannot create database: $DB_DATABASE", 'error');
exit;
}
$conn->select_db($DB_DATABASE);
// Close database connection - migrations will handle their own connections
$conn->close();
// Run database migrations
out('- Setting up database using migrations...', 'info');
// Check if phinx.php exists
if (!is_file(BB_ROOT . 'phinx.php')) {
out('- Migration configuration (phinx.php) not found', 'error');
exit;
}
// Run migrations
$migrationResult = runProcess('php vendor/bin/phinx migrate --configuration=' . BB_ROOT . 'phinx.php');
if ($migrationResult !== 0) {
out('- Database migration failed', 'error');
exit;
}
out("- Database setup completed!\n", 'success');
// Autofill host in robots.txt
$robots_txt_file = BB_ROOT . 'robots.txt';
if (isset($TP_HOST) && is_file($robots_txt_file)) {
$content = file_get_contents($robots_txt_file);
$content = str_replace('example.com', $TP_HOST, $content);
file_put_contents($robots_txt_file, $content);
}
if (isset($APP_ENV) && $APP_ENV === 'local') {
if (!is_file(BB_ROOT . 'library/config.local.php')) {
if (copy(BB_ROOT . 'library/config.php', BB_ROOT . 'library/config.local.php')) {
out('- Local configuration file created!', 'success');
} else {
out('- Cannot create library/config.local.php file. You can create it manually, just copy config.php and rename it to config.local.php', 'warning');
}
}
} else {
if (rename(__FILE__, __FILE__ . '_' . hash('xxh128', time()))) {
out("- Installation file renamed!", 'success');
} else {
out('- Cannot rename installation file (' . __FILE__ . '). Please, rename it manually for security reasons', 'warning');
}
}
// Cleanup...
if (is_file(BB_ROOT . '_cleanup.php')) {
out("\n--- Finishing installation (Cleanup) ---\n", 'info');
out('The cleanup process will remove:');
out('- Development documentation (README, CHANGELOG)', 'debug');
out('- Git configuration files', 'debug');
out('- CI/CD pipelines and code analysis tools', 'debug');
out('- Translation and contribution guidelines', 'debug');
echo 'Do you want to delete these files permanently? [y/N]: ';
if (str_starts_with(mb_strtolower(trim(readline())), 'y')) {
out("\n- Cleanup...", 'info');
require_once BB_ROOT . '_cleanup.php';
unlink(BB_ROOT . '_cleanup.php');
} else {
out('- Skipping...', 'info');
}
}
out("\n- Voila! Good luck & have fun!", 'success');
}

View file

@ -1,27 +0,0 @@
# Example Caddy configuration for TorrentPier
example.com {
root * /path/to/root
encode gzip zstd
php_fastcgi unix//run/php/php-fpm.sock
try_files {path} {path}/ /index.php?{query}
file_server
@blocked {
path /install/* /internal_data/* /library/*
path /.ht* /.en*
path /.git/*
path *.sql *.tpl *.db *.inc *.log *.md
}
respond @blocked 404
redir /sitemap.xml /sitemap/sitemap.xml
@html_css_js {
path *.html *.css *.js *.json *.xml *.txt
}
header @html_css_js Content-Type "{mime}; charset=utf-8"
}
# Refer to the Caddy docs for more information:
# https://caddyserver.com/docs/caddyfile

View file

@ -1,39 +0,0 @@
# Example nginx configuration for TorrentPier
server {
listen 80; # port
server_name example.com; # your domain
root /path/to/root; # folder with TorrentPier installed
index index.php;
charset utf-8;
location / {
try_files \$uri \$uri/ /index.php?\$args;
}
location ~ \/(install|internal_data|library)\/ {
return 404;
}
location ~ /\.(ht|en) {
return 404;
}
location ~ /\.git {
return 404;
}
location ~ \.(.*sql|tpl|db|inc|log|md)$ {
return 404;
}
rewrite ^/sitemap.xml$ /sitemap/sitemap.xml;
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
include fastcgi_params;
}
}

0
public/css/.keep Normal file
View file

View file

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before After
Before After

0
public/fonts/.keep Normal file
View file

0
public/images/.keep Normal file
View file

View file

Before

Width:  |  Height:  |  Size: 331 B

After

Width:  |  Height:  |  Size: 331 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 780 B

After

Width:  |  Height:  |  Size: 780 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 638 B

After

Width:  |  Height:  |  Size: 638 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 621 B

After

Width:  |  Height:  |  Size: 621 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 275 B

After

Width:  |  Height:  |  Size: 275 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 885 B

After

Width:  |  Height:  |  Size: 885 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 729 B

After

Width:  |  Height:  |  Size: 729 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 218 B

After

Width:  |  Height:  |  Size: 218 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 310 B

After

Width:  |  Height:  |  Size: 310 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 266 B

After

Width:  |  Height:  |  Size: 266 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 743 B

After

Width:  |  Height:  |  Size: 743 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 228 B

After

Width:  |  Height:  |  Size: 228 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 237 B

After

Width:  |  Height:  |  Size: 237 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 556 B

After

Width:  |  Height:  |  Size: 556 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 501 B

After

Width:  |  Height:  |  Size: 501 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 628 B

After

Width:  |  Height:  |  Size: 628 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 187 B

After

Width:  |  Height:  |  Size: 187 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 302 B

After

Width:  |  Height:  |  Size: 302 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 353 B

After

Width:  |  Height:  |  Size: 353 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 283 B

After

Width:  |  Height:  |  Size: 283 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 252 B

After

Width:  |  Height:  |  Size: 252 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 499 B

After

Width:  |  Height:  |  Size: 499 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 231 B

After

Width:  |  Height:  |  Size: 231 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 221 B

After

Width:  |  Height:  |  Size: 221 B

Before After
Before After

Some files were not shown because too many files have changed in this diff Show more