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

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace App\Application\SmartLink;
use App\Domain\SmartLink\Entities\LinkDestination;
use App\Domain\SmartLink\Enums\ServiceType;
use App\Domain\SmartLink\Services\SmartLinkService;
use App\Domain\SmartLink\ValueObjects\DestinationUrl;
use App\Domain\SmartLink\ValueObjects\SmartLinkId;
use App\Framework\Attributes\Route;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\Method;
use App\Framework\Router\Result\JsonResult;
final readonly class AddLinkDestination
{
public function __construct(
private SmartLinkService $linkService
) {}
#[Route(path: '/api/smart-links/{id}/destinations', method: Method::POST)]
public function __invoke(HttpRequest $request): JsonResult
{
$linkId = SmartLinkId::fromString($request->routeParameters->get('id'));
$data = $request->parsedBody->toArray();
$destination = LinkDestination::create(
linkId: $linkId,
serviceType: ServiceType::from($data['service_type']),
url: DestinationUrl::fromString($data['url']),
priority: (int) ($data['priority'] ?? 0),
isDefault: (bool) ($data['is_default'] ?? false)
);
$this->linkService->addDestination($linkId, $destination);
return new JsonResult([
'message' => 'Destination added successfully',
'destination' => $destination->toArray(),
], 201);
}
}

View File

@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace App\Application\SmartLink\Api;
use App\Domain\SmartLink\Enums\ServiceType;
use App\Domain\SmartLink\Services\SmartLinkService;
use App\Domain\SmartLink\ValueObjects\DestinationUrl;
use App\Domain\SmartLink\ValueObjects\SmartLinkId;
use App\Framework\Attributes\Route;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\Method;
use App\Framework\Http\Status;
use App\Framework\Router\Result\JsonResult;
final readonly class AddLinkDestination
{
public function __construct(
private SmartLinkService $smartLinkService
) {}
#[Route(path: '/api/smart-links/{id}/destinations', method: Method::POST)]
public function __invoke(HttpRequest $request, string $id): JsonResult
{
$data = $request->parsedBody->toArray();
// Workaround: If parsedBody is empty, try parsing JSON directly
if (empty($data)) {
$rawBody = file_get_contents('php://input');
$data = json_decode($rawBody, true) ?? [];
}
$destination = $this->smartLinkService->addDestination(
linkId: SmartLinkId::fromString($id),
serviceType: ServiceType::from($data['service_type'] ?? ''),
url: DestinationUrl::fromString($data['url'] ?? ''),
priority: (int) ($data['priority'] ?? 0),
isDefault: (bool) ($data['is_default'] ?? false)
);
return new JsonResult([
'id' => $destination->id,
'link_id' => $destination->linkId->toString(),
'service_type' => $destination->serviceType->value,
'url' => $destination->url->toString(),
'priority' => $destination->priority,
'is_default' => $destination->isDefault,
'created_at' => $destination->createdAt->format('Y-m-d H:i:s')
], Status::CREATED);
}
}

View File

@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace App\Application\SmartLink\Api;
use App\Domain\SmartLink\Entities\GeoRoutingRule;
use App\Domain\SmartLink\Repositories\GeoRoutingRuleRepository;
use App\Domain\SmartLink\ValueObjects\SmartLinkId;
use App\Framework\Attributes\Route;
use App\Framework\Core\ValueObjects\CountryCode;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\Method;
use App\Framework\Http\Status;
use App\Framework\Router\Result\JsonResult;
final readonly class CreateGeoRoutingRule
{
public function __construct(
private GeoRoutingRuleRepository $geoRoutingRuleRepository
) {}
#[Route(path: '/api/smartlinks/{linkId}/geo-rules', method: Method::POST)]
public function __invoke(HttpRequest $request, string $linkId): JsonResult
{
$data = $request->parsedBody->toArray();
$geoRule = GeoRoutingRule::create(
linkId: SmartLinkId::fromString($linkId),
countryCode: CountryCode::fromString($data['country_code']),
destinationId: $data['destination_id'],
priority: $data['priority'] ?? 0
);
$this->geoRoutingRuleRepository->save($geoRule);
return new JsonResult([
'id' => $geoRule->id->toString(),
'link_id' => $geoRule->linkId->toString(),
'country_code' => $geoRule->countryCode->toString(),
'destination_id' => $geoRule->destinationId,
'priority' => $geoRule->priority,
'created_at' => $geoRule->createdAt->format('Y-m-d H:i:s')
], Status::CREATED);
}
}

View File

@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace App\Application\SmartLink\Api;
use App\Domain\SmartLink\Enums\LinkType;
use App\Domain\SmartLink\Services\SmartLinkService;
use App\Domain\SmartLink\ValueObjects\LinkTitle;
use App\Framework\Attributes\Route;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\Method;
use App\Framework\Http\Status;
use App\Framework\Router\Result\JsonResult;
final readonly class CreateSmartLink
{
public function __construct(
private SmartLinkService $smartLinkService
) {}
#[Route(path: '/api/smart-links', method: Method::POST)]
public function __invoke(HttpRequest $request): JsonResult
{
$data = $request->parsedBody->toArray();
// Workaround: If parsedBody is empty, try parsing JSON directly
if (empty($data)) {
$rawBody = file_get_contents('php://input');
$data = json_decode($rawBody, true) ?? [];
}
if (!isset($data['type']) || !isset($data['title'])) {
return new JsonResult([
'error' => 'Missing required fields: type and title'
], Status::BAD_REQUEST);
}
$type = LinkType::from($data['type'] ?? '');
$title = LinkTitle::fromString($data['title'] ?? '');
$smartLink = $this->smartLinkService->createLink(
type: $type,
title: $title,
userId: $data['user_id'] ?? null,
coverImageUrl: $data['cover_image_url'] ?? null
);
return new JsonResult([
'id' => $smartLink->id->toString(),
'short_code' => $smartLink->shortCode->toString(),
'type' => $smartLink->type->value,
'title' => $smartLink->title->toString(),
'status' => $smartLink->status->value,
'created_at' => $smartLink->createdAt->format('Y-m-d H:i:s')
], Status::CREATED);
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace App\Application\SmartLink\Api;
use App\Domain\SmartLink\Repositories\GeoRoutingRuleRepository;
use App\Domain\SmartLink\ValueObjects\GeoRuleId;
use App\Framework\Attributes\Route;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\Method;
use App\Framework\Router\Result\JsonResult;
final readonly class DeleteGeoRoutingRule
{
public function __construct(
private GeoRoutingRuleRepository $geoRoutingRuleRepository
) {}
#[Route(path: '/api/geo-rules/{ruleId}', method: Method::DELETE)]
public function __invoke(HttpRequest $request, string $ruleId): JsonResult
{
$this->geoRoutingRuleRepository->delete(
GeoRuleId::fromString($ruleId)
);
return new JsonResult(['message' => 'Geo-routing rule deleted successfully']);
}
}

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace App\Application\SmartLink\Api;
use App\Domain\SmartLink\Repositories\GeoRoutingRuleRepository;
use App\Domain\SmartLink\ValueObjects\SmartLinkId;
use App\Framework\Attributes\Route;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\Method;
use App\Framework\Router\Result\JsonResult;
final readonly class GetGeoRoutingRules
{
public function __construct(
private GeoRoutingRuleRepository $geoRoutingRuleRepository
) {}
#[Route(path: '/api/smartlinks/{linkId}/geo-rules', method: Method::GET)]
public function __invoke(HttpRequest $request, string $linkId): JsonResult
{
$rules = $this->geoRoutingRuleRepository->findByLinkId(
SmartLinkId::fromString($linkId)
);
$data = array_map(function ($rule) {
return [
'id' => $rule->id->toString(),
'link_id' => $rule->linkId->toString(),
'country_code' => $rule->countryCode->toString(),
'destination_id' => $rule->destinationId,
'priority' => $rule->priority,
'created_at' => $rule->createdAt->format('Y-m-d H:i:s')
];
}, $rules);
return new JsonResult(['rules' => $data]);
}
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace App\Application\SmartLink\Api;
use App\Domain\SmartLink\Services\SmartLinkService;
use App\Domain\SmartLink\ValueObjects\SmartLinkId;
use App\Framework\Attributes\Route;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\Method;
use App\Framework\Router\Result\JsonResult;
final readonly class GetSmartLink
{
public function __construct(
private SmartLinkService $smartLinkService
) {}
#[Route(path: '/api/smart-links/{id}', method: Method::GET)]
public function __invoke(HttpRequest $request, string $id): JsonResult
{
$smartLink = $this->smartLinkService->getLink(new SmartLinkId($id));
return new JsonResult([
'id' => $smartLink->id->toString(),
'short_code' => $smartLink->shortCode->toString(),
'type' => $smartLink->type->value,
'title' => $smartLink->title->toString(),
'status' => $smartLink->status->value,
'cover_image_url' => $smartLink->coverImageUrl,
'user_id' => $smartLink->userId,
'settings' => [
'password_protected' => $smartLink->settings->isPasswordProtected(),
'click_limit' => $smartLink->settings->clickLimit,
'custom_domain' => $smartLink->settings->customDomain,
'show_branding' => $smartLink->settings->showBranding
],
'created_at' => $smartLink->createdAt->format('Y-m-d H:i:s'),
'updated_at' => $smartLink->updatedAt->format('Y-m-d H:i:s')
]);
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace App\Application\SmartLink\Api;
use App\Domain\SmartLink\Services\SmartLinkService;
use App\Domain\SmartLink\ValueObjects\SmartLinkId;
use App\Framework\Attributes\Route;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\Method;
use App\Framework\Router\Result\JsonResult;
final readonly class PublishSmartLink
{
public function __construct(
private SmartLinkService $smartLinkService
) {}
#[Route(path: '/api/smart-links/{id}/publish', method: Method::POST)]
public function __invoke(HttpRequest $request, string $id): JsonResult
{
$smartLink = $this->smartLinkService->publishLink(new SmartLinkId($id));
return new JsonResult([
'id' => $smartLink->id->toString(),
'status' => $smartLink->status->value,
'published_at' => $smartLink->updatedAt->format('Y-m-d H:i:s')
]);
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace App\Application\SmartLink\Api;
use App\Domain\SmartLink\Services\ClickTrackingService;
use App\Domain\SmartLink\Services\LinkRoutingEngine;
use App\Domain\SmartLink\Services\SmartLinkService;
use App\Domain\SmartLink\ValueObjects\ShortCode;
use App\Framework\Attributes\Route;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\Method;
use App\Framework\Router\Result\Redirect;
final readonly class RedirectToDestination
{
public function __construct(
private SmartLinkService $smartLinkService,
private LinkRoutingEngine $routingEngine,
private ClickTrackingService $trackingService
) {}
#[Route(path: '/l/{shortCode}', method: Method::GET)]
public function __invoke(HttpRequest $request, string $shortCode): Redirect
{
$smartLink = $this->smartLinkService->getLinkByShortCode(
ShortCode::fromString($shortCode)
);
$destination = $this->routingEngine->resolveDestination(
smartLink: $smartLink,
request: $request
);
$this->trackingService->trackClick(
smartLink: $smartLink,
request: $request,
destinationService: $destination->serviceType
);
return new Redirect($destination->url->toString());
}
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace App\Application\SmartLink;
use App\Domain\SmartLink\Services\ClickTrackingService;
use App\Domain\SmartLink\Services\LinkRoutingEngine;
use App\Domain\SmartLink\ValueObjects\ShortCode;
use App\Framework\Attributes\Route;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\IpAddress;
use App\Framework\Http\Method;
use App\Framework\Router\Result\Redirect;
final readonly class RedirectToDestination
{
public function __construct(
private LinkRoutingEngine $routingEngine,
private ClickTrackingService $trackingService
) {}
#[Route(path: '/{shortCode}', method: Method::GET)]
public function __invoke(HttpRequest $request): Redirect
{
$shortCode = ShortCode::fromString($request->routeParameters->get('shortCode'));
$ip = IpAddress::fromString($request->server->getClientIp());
$userAgent = $request->server->getUserAgent();
// Resolve destination URL
$destinationUrl = $this->routingEngine->resolveDestination(
shortCode: $shortCode,
ip: $ip,
userAgent: $userAgent
);
// Track click
$link = $this->routingEngine->linkService->findByShortCode($shortCode);
if ($link->settings->trackClicks) {
$this->trackingService->trackClick(
linkId: $link->id,
ip: $ip,
userAgent: $userAgent,
referer: $request->server->getSafeRefererUrl(),
destinationService: null // TODO: Extract from destinationUrl
);
}
return new Redirect($destinationUrl->toString());
}
}