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,280 @@
<?php
declare(strict_types=1);
namespace App\Framework\Admin\Examples;
use App\Framework\Admin\Services\CrudService;
use App\Framework\Admin\ValueObjects\CrudConfig;
use App\Framework\Attributes\Route;
use App\Framework\Http\Method;
use App\Framework\Http\Request;
use App\Framework\Http\Responses\Redirect;
use App\Framework\Http\Responses\ViewResult;
/**
* Campaign CRUD Controller - Example Implementation
*
* Demonstrates composition-based CRUD pattern using CrudService
* No inheritance - pure composition following framework principles
*/
final readonly class CampaignCrudController
{
private CrudConfig $config;
public function __construct(
private CrudService $crudService,
private CampaignRepository $campaignRepository
) {
// Configure CRUD settings for campaigns resource
$this->config = CrudConfig::forResource(
resource: 'campaigns',
resourceName: 'Campaign',
title: 'Campaigns'
)->withColumns([
['field' => 'artist_name', 'label' => 'Artist', 'sortable' => true],
['field' => 'campaign_type', 'label' => 'Type', 'sortable' => true],
['field' => 'status', 'label' => 'Status', 'sortable' => true],
['field' => 'created_at', 'label' => 'Created', 'sortable' => true],
])->withFilters([
['field' => 'status', 'type' => 'select', 'options' => ['active', 'draft', 'archived']],
['field' => 'campaign_type', 'type' => 'select', 'options' => ['pre-save', 'release', 'tour']],
])->withBulkActions([
['name' => 'activate', 'label' => 'Activate Selected'],
['name' => 'archive', 'label' => 'Archive Selected'],
]);
}
/**
* Show campaigns index
*/
#[Route(path: '/admin/campaigns', method: Method::GET)]
public function index(Request $request): ViewResult
{
// Fetch campaigns from repository
$campaigns = $this->campaignRepository->findAll();
// Delegate rendering to CrudService
return $this->crudService->renderIndex(
config: $this->config,
items: $campaigns,
request: $request,
pagination: null // Add pagination if needed
);
}
/**
* Show create campaign form
*/
#[Route(path: '/admin/campaigns/create', method: Method::GET)]
public function create(): ViewResult
{
return $this->crudService->renderCreate(
config: $this->config,
formFields: $this->getCampaignFormFields(),
defaultData: null,
helpText: 'Create a new campaign for an artist. Pre-save campaigns allow fans to save music before release.'
);
}
/**
* Store new campaign
*/
#[Route(path: '/admin/campaigns/store', method: Method::POST)]
public function store(Request $request): Redirect
{
try {
// Validate and create campaign
$data = $request->parsedBody->toArray();
$campaign = $this->campaignRepository->create($data);
// Delegate redirect to CrudService
return $this->crudService->redirectAfterCreate(
config: $this->config,
request: $request,
itemId: $campaign->id
);
} catch (\Exception $e) {
return $this->crudService->redirectWithError(
message: 'Failed to create campaign: ' . $e->getMessage(),
inputData: $request->parsedBody->toArray()
);
}
}
/**
* Show edit campaign form
*/
#[Route(path: '/admin/campaigns/edit/{id}', method: Method::GET)]
public function edit(Request $request): ViewResult
{
$id = $request->routeParams->get('id');
$campaign = $this->campaignRepository->findById($id);
return $this->crudService->renderEdit(
config: $this->config,
id: $id,
formFields: $this->getCampaignFormFields(),
itemData: $campaign->toArray(),
metadata: [
'id' => $campaign->id,
'createdAt' => $campaign->created_at->format('Y-m-d H:i:s'),
'updatedAt' => $campaign->updated_at?->format('Y-m-d H:i:s'),
'status' => ucfirst($campaign->status),
'statusColor' => $this->getStatusColor($campaign->status),
],
helpText: 'Edit campaign details. Changes will be reflected immediately.'
);
}
/**
* Update campaign
*/
#[Route(path: '/admin/campaigns/update/{id}', method: Method::PUT)]
public function update(Request $request): Redirect
{
try {
$id = $request->routeParams->get('id');
$data = $request->parsedBody->toArray();
$this->campaignRepository->update($id, $data);
return $this->crudService->redirectAfterUpdate(
config: $this->config,
request: $request,
itemId: $id
);
} catch (\Exception $e) {
return $this->crudService->redirectWithError(
message: 'Failed to update campaign: ' . $e->getMessage(),
inputData: $request->parsedBody->toArray()
);
}
}
/**
* Show campaign details
*/
#[Route(path: '/admin/campaigns/view/{id}', method: Method::GET)]
public function show(Request $request): ViewResult
{
$id = $request->routeParams->get('id');
$campaign = $this->campaignRepository->findById($id);
return $this->crudService->renderShow(
config: $this->config,
id: $id,
fields: [
['label' => 'Artist Name', 'value' => $campaign->artist_name, 'type' => 'text'],
['label' => 'Campaign Type', 'value' => ucfirst($campaign->campaign_type), 'type' => 'badge', 'color' => 'primary'],
['label' => 'Status', 'value' => ucfirst($campaign->status), 'type' => 'badge', 'color' => $this->getStatusColor($campaign->status)],
['label' => 'Spotify URI', 'value' => $campaign->spotify_uri, 'type' => 'text'],
['label' => 'Release Date', 'value' => $campaign->release_date?->format('Y-m-d'), 'type' => 'text'],
['label' => 'Total Saves', 'value' => $campaign->stats['total_saves'] ?? 0, 'type' => 'text'],
],
metadata: [
'id' => $campaign->id,
'createdAt' => $campaign->created_at->format('Y-m-d H:i:s'),
'updatedAt' => $campaign->updated_at?->format('Y-m-d H:i:s'),
'status' => ucfirst($campaign->status),
'statusColor' => $this->getStatusColor($campaign->status),
],
relatedItems: null, // Add related items if needed
actions: [
['type' => 'link', 'url' => "/admin/campaigns/{$id}/analytics", 'label' => 'View Analytics', 'icon' => 'graph-up', 'variant' => 'primary'],
['type' => 'button', 'name' => 'duplicate', 'label' => 'Duplicate Campaign', 'icon' => 'files', 'variant' => 'secondary'],
]
);
}
/**
* Delete campaign
*/
#[Route(path: '/admin/campaigns/delete/{id}', method: Method::DELETE)]
public function destroy(Request $request): Redirect
{
try {
$id = $request->routeParams->get('id');
$this->campaignRepository->delete($id);
return $this->crudService->redirectAfterDelete($this->config);
} catch (\Exception $e) {
return $this->crudService->redirectWithError(
message: 'Failed to delete campaign: ' . $e->getMessage()
);
}
}
/**
* Get campaign form fields configuration
*/
private function getCampaignFormFields(): array
{
return [
[
'type' => 'text',
'name' => 'artist_name',
'label' => 'Artist Name',
'required' => true,
'placeholder' => 'Enter artist name',
],
[
'type' => 'select',
'name' => 'campaign_type',
'label' => 'Campaign Type',
'required' => true,
'options' => [
['value' => 'pre-save', 'label' => 'Pre-Save Campaign'],
['value' => 'release', 'label' => 'Release Campaign'],
['value' => 'tour', 'label' => 'Tour Campaign'],
],
],
[
'type' => 'text',
'name' => 'spotify_uri',
'label' => 'Spotify URI',
'required' => true,
'placeholder' => 'spotify:album:...',
'help' => 'Full Spotify URI for the album or track',
],
[
'type' => 'date',
'name' => 'release_date',
'label' => 'Release Date',
'required' => false,
],
[
'type' => 'select',
'name' => 'status',
'label' => 'Status',
'required' => true,
'options' => [
['value' => 'draft', 'label' => 'Draft'],
['value' => 'active', 'label' => 'Active'],
['value' => 'archived', 'label' => 'Archived'],
],
],
[
'type' => 'textarea',
'name' => 'description',
'label' => 'Description',
'required' => false,
'placeholder' => 'Campaign description (optional)',
'rows' => 4,
],
];
}
/**
* Get Bootstrap color for campaign status
*/
private function getStatusColor(string $status): string
{
return match ($status) {
'active' => 'success',
'draft' => 'warning',
'archived' => 'secondary',
default => 'info',
};
}
}

View File

@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace App\Framework\Admin\Examples;
/**
* Campaign Repository - Example Stub
*
* Stub implementation for the example CRUD controller
* In a real implementation, this would use the EntityManager
*/
final readonly class CampaignRepository
{
public function findAll(): array
{
// Stub: Return sample data
return [];
}
public function findById(string $id): object
{
// Stub: Return sample campaign object
return (object) [
'id' => $id,
'artist_name' => 'Example Artist',
'campaign_type' => 'pre-save',
'status' => 'active',
'spotify_uri' => 'spotify:album:example',
'release_date' => new \DateTime('2025-01-01'),
'created_at' => new \DateTime(),
'updated_at' => new \DateTime(),
'stats' => ['total_saves' => 0],
];
}
public function create(array $data): object
{
// Stub: Create campaign
return (object) ['id' => 'new-id'];
}
public function update(string $id, array $data): void
{
// Stub: Update campaign
}
public function delete(string $id): void
{
// Stub: Delete campaign
}
}