- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
86 lines
2.8 KiB
PHP
Executable File
86 lines
2.8 KiB
PHP
Executable File
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Application\Campaign;
|
|
|
|
use App\Framework\Attributes\Route;
|
|
use App\Framework\Http\Method;
|
|
use App\Framework\Http\Session\Session;
|
|
use App\Framework\Meta\MetaData;
|
|
use App\Framework\Router\Result\ViewResult;
|
|
|
|
/**
|
|
* Show Campaign Landing Page
|
|
*
|
|
* Displays public campaign landing page with pre-save functionality
|
|
*/
|
|
final readonly class ShowCampaign
|
|
{
|
|
public function __construct(
|
|
private CampaignService $campaignService
|
|
) {
|
|
}
|
|
|
|
#[Route(path: '/campaign/{slug}', method: Method::GET)]
|
|
public function __invoke(string $slug, Session $session): ViewResult
|
|
{
|
|
// Fetch campaign data
|
|
$campaign = $this->campaignService->findBySlug($slug);
|
|
|
|
if (! $campaign) {
|
|
throw new \RuntimeException("Campaign not found: {$slug}");
|
|
}
|
|
|
|
// Check if user has already saved
|
|
$userHasSaved = $this->campaignService->hasUserSaved(
|
|
$campaign->id,
|
|
$session->getId()
|
|
);
|
|
|
|
// Prepare campaign data for view
|
|
$campaignData = [
|
|
'slug' => $campaign->slug,
|
|
'artist_name' => $campaign->artist_name,
|
|
'album_title' => $campaign->album_title,
|
|
'description' => $campaign->description,
|
|
'artwork_url' => $campaign->artwork_url,
|
|
'release_date' => $campaign->release_date?->format('F j, Y'),
|
|
'total_saves' => $campaign->total_saves,
|
|
'track_count' => $campaign->track_count,
|
|
'spotify_enabled' => (bool) $campaign->spotify_enabled,
|
|
'apple_music_enabled' => (bool) $campaign->apple_music_enabled,
|
|
'tracks' => $campaign->tracks ? array_map(
|
|
fn ($track) => [
|
|
'position' => $track->position,
|
|
'title' => $track->title,
|
|
'duration' => $track->duration ? $this->formatDuration($track->duration) : null,
|
|
'preview_url' => $track->preview_url,
|
|
],
|
|
$campaign->tracks
|
|
) : null,
|
|
];
|
|
|
|
return new ViewResult(
|
|
'campaign-landing',
|
|
new MetaData(
|
|
title: $campaign->artist_name . ' - ' . $campaign->album_title,
|
|
description: $campaign->description ?? 'Pre-save this album on Spotify',
|
|
),
|
|
data: [
|
|
'campaign' => $campaignData,
|
|
'user_has_saved' => $userHasSaved,
|
|
'csrf_token' => $session->csrf->generateToken('campaign-landing')->toString(),
|
|
]
|
|
);
|
|
}
|
|
|
|
private function formatDuration(int $seconds): string
|
|
{
|
|
$minutes = floor($seconds / 60);
|
|
$remainingSeconds = $seconds % 60;
|
|
|
|
return sprintf('%d:%02d', $minutes, $remainingSeconds);
|
|
}
|
|
}
|