refactor: reorganize project structure for better maintainability

- Move 45 debug/test files from root to organized scripts/ directories
- Secure public/ directory by removing debug files (security improvement)
- Create structured scripts organization:
  • scripts/debug/      (20 files) - Framework debugging tools
  • scripts/test/       (18 files) - Test and validation scripts
  • scripts/maintenance/ (5 files) - Maintenance utilities
  • scripts/dev/         (2 files) - Development tools

Security improvements:
- Removed all debug/test files from public/ directory
- Only production files remain: index.php, health.php

Root directory cleanup:
- Reduced from 47 to 2 PHP files in root
- Only essential production files: console.php, worker.php

This improves:
 Security (no debug code in public/)
 Organization (clear separation of concerns)
 Maintainability (easy to find and manage scripts)
 Professional structure (clean root directory)
This commit is contained in:
2025-10-05 10:59:15 +02:00
parent 03e5188644
commit 887847dde6
77 changed files with 3902 additions and 787 deletions

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace App\Framework\Smartlinks\Actions;
use App\Framework\Smartlinks\TokenAction;
interface ActionRegistry
{
/**
* Register an action
*/
public function register(SmartlinkAction $action): void;
/**
* Get an action by name
*/
public function get(TokenAction $action): ?SmartlinkAction;
/**
* Check if an action is registered
*/
public function has(TokenAction $action): bool;
/**
* Get all registered actions
*/
public function getAll(): array;
/**
* Unregister an action
*/
public function unregister(TokenAction $action): void;
}

View File

@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace App\Framework\Smartlinks\Actions;
final readonly class ActionResult
{
public function __construct(
public bool $success,
public string $message = '',
public array $data = [],
public ?string $redirectUrl = null,
public array $errors = []
) {
}
public static function success(string $message = '', array $data = [], ?string $redirectUrl = null): self
{
return new self(
success: true,
message: $message,
data: $data,
redirectUrl: $redirectUrl
);
}
public static function failure(string $message, array $errors = []): self
{
return new self(
success: false,
message: $message,
errors: $errors
);
}
public function isSuccess(): bool
{
return $this->success;
}
public function hasRedirect(): bool
{
return $this->redirectUrl !== null;
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace App\Framework\Smartlinks\Actions;
use App\Framework\Smartlinks\TokenAction;
final class DefaultActionRegistry implements ActionRegistry
{
/** @var SmartlinkAction[] */
private array $actions = [];
public function register(SmartlinkAction $action): void
{
$this->actions[$action->getName()] = $action;
}
public function get(TokenAction $action): ?SmartlinkAction
{
return $this->actions[$action->name] ?? null;
}
public function has(TokenAction $action): bool
{
return isset($this->actions[$action->name]);
}
public function getAll(): array
{
return $this->actions;
}
public function unregister(TokenAction $action): void
{
unset($this->actions[$action->name]);
}
}

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace App\Framework\Smartlinks\Actions;
use App\Framework\Smartlinks\SmartlinkData;
use App\Framework\Smartlinks\TokenConfig;
final readonly class DocumentAccessAction implements SmartlinkAction
{
public function getName(): string
{
return 'document_access';
}
public function getDefaultConfig(): TokenConfig
{
return new TokenConfig(
expiryHours: 72, // 3 days
oneTimeUse: false, // Can be accessed multiple times
maxUses: 10,
requireSecureContext: false
);
}
public function validatePayload(array $payload): bool
{
return ! empty($payload['document_id']) &&
in_array($payload['access_level'], ['read', 'download', 'edit']);
}
public function execute(SmartlinkData $smartlinkData, array $context = []): ActionResult
{
$payload = $smartlinkData->payload;
// Document access logic
return ActionResult::success(
message: "Document access granted",
data: [
'document_id' => $payload['document_id'],
'access_level' => $payload['access_level'],
'download_url' => "/documents/{$payload['document_id']}/download",
'expires_at' => $smartlinkData->expiresAt->format('Y-m-d H:i:s'),
]
);
}
public function getRequiredPermissions(): array
{
return ['documents.access'];
}
}

View File

@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace App\Framework\Smartlinks\Actions;
use App\Framework\Smartlinks\SmartlinkData;
use App\Framework\Smartlinks\TokenConfig;
use DateTimeImmutable;
final readonly class EmailVerificationAction implements SmartlinkAction
{
public function getName(): string
{
return 'email_verification';
}
public function getDefaultConfig(): TokenConfig
{
return new TokenConfig(
expiryHours: 24,
oneTimeUse: true,
maxUses: 1,
requireSecureContext: true
);
}
public function validatePayload(array $payload): bool
{
return ! empty($payload['email']) &&
! empty($payload['user_id']) &&
filter_var($payload['email'], FILTER_VALIDATE_EMAIL);
}
public function execute(SmartlinkData $smartlinkData, array $context = []): ActionResult
{
$payload = $smartlinkData->payload;
// Email verification logic would go here
// For now, just a simple success response
return ActionResult::success(
message: "Email {$payload['email']} successfully verified",
data: [
'user_id' => $payload['user_id'],
'email' => $payload['email'],
'verified_at' => new DateTimeImmutable(),
],
redirectUrl: '/dashboard'
);
}
public function getRequiredPermissions(): array
{
return []; // No special permissions needed for email verification
}
}

View File

@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace App\Framework\Smartlinks\Actions;
use App\Framework\Smartlinks\SmartlinkData;
use App\Framework\Smartlinks\TokenConfig;
final readonly class GenericDataAccessAction implements SmartlinkAction
{
public function __construct(
private string $actionName,
private TokenConfig $config,
private array $requiredPermissions = []
) {
}
public function getName(): string
{
return $this->actionName;
}
public function getDefaultConfig(): TokenConfig
{
return $this->config;
}
public function validatePayload(array $payload): bool
{
// Generic validation - just check that payload is not empty
return ! empty($payload);
}
public function execute(SmartlinkData $smartlinkData, array $context = []): ActionResult
{
// Generic execution - just return the payload data
return ActionResult::success(
message: "Action '{$this->actionName}' executed successfully",
data: $smartlinkData->payload
);
}
public function getRequiredPermissions(): array
{
return $this->requiredPermissions;
}
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace App\Framework\Smartlinks\Actions;
use App\Framework\Smartlinks\SmartlinkData;
use App\Framework\Smartlinks\TokenConfig;
interface SmartlinkAction
{
/**
* Get the action name
*/
public function getName(): string;
/**
* Get default configuration for this action
*/
public function getDefaultConfig(): TokenConfig;
/**
* Validate the payload for this action
*/
public function validatePayload(array $payload): bool;
/**
* Execute the action
*/
public function execute(SmartlinkData $smartlinkData, array $context = []): ActionResult;
/**
* Get required permissions/roles for this action
*/
public function getRequiredPermissions(): array;
}

View File

@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace App\Framework\Smartlinks\Actions;
use App\Framework\Smartlinks\SmartlinkData;
use App\Framework\Smartlinks\TokenConfig;
final readonly class PasswordResetAction implements SmartlinkAction
{
public function getName(): string
{
return 'password_reset';
}
public function getDefaultConfig(): TokenConfig
{
return new TokenConfig(
expiryHours: 1, // Short expiry for security
oneTimeUse: true,
maxUses: 1,
requireSecureContext: true
);
}
public function validatePayload(array $payload): bool
{
return ! empty($payload['user_id']) &&
! empty($payload['email']) &&
filter_var($payload['email'], FILTER_VALIDATE_EMAIL);
}
public function execute(SmartlinkData $smartlinkData, array $context = []): ActionResult
{
$payload = $smartlinkData->payload;
// Password reset form would be shown here
// Return data needed for the password reset form
return ActionResult::success(
message: "Password reset form ready",
data: [
'user_id' => $payload['user_id'],
'email' => $payload['email'],
'form_action' => '/password/reset/submit',
'csrf_token' => $context['csrf_token'] ?? null,
]
);
}
public function getRequiredPermissions(): array
{
return [];
}
}