docs: consolidate documentation into organized structure

- Move 12 markdown files from root to docs/ subdirectories
- Organize documentation by category:
  • docs/troubleshooting/ (1 file)  - Technical troubleshooting guides
  • docs/deployment/      (4 files) - Deployment and security documentation
  • docs/guides/          (3 files) - Feature-specific guides
  • docs/planning/        (4 files) - Planning and improvement proposals

Root directory cleanup:
- Reduced from 16 to 4 markdown files in root
- Only essential project files remain:
  • CLAUDE.md (AI instructions)
  • README.md (Main project readme)
  • CLEANUP_PLAN.md (Current cleanup plan)
  • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements)

This improves:
 Documentation discoverability
 Logical organization by purpose
 Clean root directory
 Better maintainability
This commit is contained in:
2025-10-05 11:05:04 +02:00
parent 887847dde6
commit 5050c7d73a
36686 changed files with 196456 additions and 12398919 deletions

View File

@@ -6,13 +6,15 @@ namespace App\Infrastructure\GeoIp;
use PDO;
use RuntimeException;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\ValueObjects\SqlQuery;
final class CountryDataService
{
private const string RESTCOUNTRIES_API_URL = 'https://restcountries.com/v3.1/all?fields=cca2,name,translations';
public function __construct(
private readonly PDO $database
private readonly ConnectionInterface $connection
) {
}
@@ -30,27 +32,27 @@ final class CountryDataService
throw new RuntimeException('Ungültige JSON-Daten erhalten');
}
$this->database->beginTransaction();
$this->database->exec('DELETE FROM countries');
$this->connection->beginTransaction();
$this->connection->execute(SqlQuery::create('DELETE FROM countries'));
$stmt = $this->database->prepare('INSERT INTO countries (code, name_en, name_de, name_native, updated_at) VALUES (?, ?, ?, ?, ?)');
$insertSql = 'INSERT INTO countries (code, name_en, name_de, name_native, updated_at) VALUES (?, ?, ?, ?, ?)';
$processed = 0;
foreach ($countries as $countryData) {
$country = $this->parseCountryData($countryData);
if ($country) {
$stmt->execute([
$this->connection->execute(SqlQuery::create($insertSql, [
$country->code,
$country->nameEn,
$country->nameDe,
$country->nameNative,
$country->updatedAt,
]);
]));
$processed++;
}
}
$this->database->commit();
$this->connection->commit();
echo "Länderdatenbank aktualisiert: {$processed} Länder geladen\n";
return $processed;
@@ -58,9 +60,8 @@ final class CountryDataService
public function getCountryByCode(string $code): ?Country
{
$stmt = $this->database->prepare('SELECT code, name_en, name_de, name_native, updated_at FROM countries WHERE code = ?');
$stmt->execute([$code]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$sql = 'SELECT code, name_en, name_de, name_native, updated_at FROM countries WHERE code = ?';
$result = $this->connection->queryOne(SqlQuery::create($sql, [$code]));
if (! $result) {
return null;

View File

@@ -5,6 +5,8 @@ declare(strict_types=1);
namespace App\Infrastructure\GeoIp;
use App\Framework\Http\IpAddress;
use App\Framework\Database\PdoConnection;
use App\Framework\Core\ValueObjects\CountryCode;
use PDO;
final class GeoIp
@@ -19,8 +21,9 @@ final class GeoIp
{
$databasePath = $databasePath ?? __DIR__ . '/data/ip_country.sqlite';
$this->database = $this->initializeDatabase($databasePath);
$this->ipRangeService = new IpRangeService($this->database);
$this->countryDataService = new CountryDataService($this->database);
$connection = new PdoConnection($this->database);
$this->ipRangeService = new IpRangeService($connection);
$this->countryDataService = new CountryDataService($connection);
}
/**
@@ -46,6 +49,22 @@ final class GeoIp
return $this->getCountryForString($ipAddress->value);
}
public function getCountryCode(IpAddress $ipAddress): CountryCode
{
if ($ipAddress->isPrivate()) {
// Return a default country code for private IPs (e.g., local development)
return CountryCode::fromString('XX'); // XX = Unknown/Private
}
$countryCodeString = $this->getCountryForString($ipAddress->value);
if ($countryCodeString === null) {
return CountryCode::fromString('XX'); // Unknown
}
return CountryCode::fromString($countryCodeString);
}
public function getCountryInfo(string $ip): CountryInfo
{
$countryCode = $this->getCountryForString($ip);

View File

@@ -7,6 +7,8 @@ namespace App\Infrastructure\GeoIp;
use Generator;
use PDO;
use RuntimeException;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\ValueObjects\SqlQuery;
final class IpRangeService
{
@@ -21,16 +23,16 @@ final class IpRangeService
private const int BATCH_SIZE = 1000;
public function __construct(
private readonly PDO $database
private readonly ConnectionInterface $connection
) {
}
public function loadIpRanges(): int
{
$this->database->exec('DELETE FROM ip_ranges');
$this->connection->execute(SqlQuery::create('DELETE FROM ip_ranges'));
$this->database->beginTransaction();
$stmt = $this->database->prepare('INSERT INTO ip_ranges (ip_start, ip_end, country) VALUES (?, ?, ?)');
$this->connection->beginTransaction();
$insertSql = 'INSERT INTO ip_ranges (ip_start, ip_end, country) VALUES (?, ?, ?)';
$totalProcessed = 0;
@@ -40,13 +42,13 @@ final class IpRangeService
try {
$processed = 0;
foreach ($this->parseIpRanges($url) as $ipRange) {
$stmt->execute([$ipRange['ip_start'], $ipRange['ip_end'], $ipRange['country']]);
$this->connection->execute(SqlQuery::create($insertSql, [$ipRange['ip_start'], $ipRange['ip_end'], $ipRange['country']]));
$processed++;
$totalProcessed++;
if ($totalProcessed % self::BATCH_SIZE === 0) {
$this->database->commit();
$this->database->beginTransaction();
$this->connection->commit();
$this->connection->beginTransaction();
echo "Verarbeitet: {$totalProcessed} Einträge insgesamt\n";
}
}
@@ -58,7 +60,7 @@ final class IpRangeService
}
}
$this->database->commit();
$this->connection->commit();
echo "Datenbank-Aufbau abgeschlossen. Insgesamt {$totalProcessed} Einträge aus allen RIRs.\n";
return $totalProcessed;
@@ -71,9 +73,8 @@ final class IpRangeService
return null;
}
$stmt = $this->database->prepare('SELECT country FROM ip_ranges WHERE ip_start <= ? AND ip_end >= ?');
$stmt->execute([$ipLong, $ipLong]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$sql = 'SELECT country FROM ip_ranges WHERE ip_start <= ? AND ip_end >= ?';
$result = $this->connection->queryOne(SqlQuery::create($sql, [$ipLong, $ipLong]));
return $result ? $result['country'] : null;
}

View File

@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace App\Infrastructure\Music\ValueObjects;
/**
* Spotify API Credentials Value Object
*
* Immutable credentials for Spotify API authentication
*/
final readonly class SpotifyCredentials
{
public function __construct(
public string $clientId,
public string $clientSecret,
public string $redirectUri
) {
if (empty($this->clientId)) {
throw new \InvalidArgumentException('Spotify Client ID cannot be empty');
}
if (empty($this->clientSecret)) {
throw new \InvalidArgumentException('Spotify Client Secret cannot be empty');
}
if (empty($this->redirectUri) || !filter_var($this->redirectUri, FILTER_VALIDATE_URL)) {
throw new \InvalidArgumentException('Spotify Redirect URI must be a valid URL');
}
}
public static function fromEnvironment(array $env): self
{
return new self(
clientId: $env['SPOTIFY_CLIENT_ID'] ?? '',
clientSecret: $env['SPOTIFY_CLIENT_SECRET'] ?? '',
redirectUri: $env['SPOTIFY_REDIRECT_URI'] ?? ''
);
}
public function toArray(): array
{
return [
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret,
'redirect_uri' => $this->redirectUri,
];
}
}