campaignRepository->findAll(); return new ViewResult('admin/presave/campaigns/index', [ 'campaigns' => $campaigns, 'stats' => $this->getGlobalStats(), ]); } /** * Show create campaign form */ #[Route(path: '/admin/presave/campaigns/create', method: Method::GET)] public function create(): ViewResult { return new ViewResult('admin/presave/campaigns/create'); } /** * Store new campaign */ #[Route(path: '/admin/presave/campaigns', method: Method::POST)] public function store(HttpRequest $request): Redirect { $data = $request->parsedBody->toArray(); // Validate required fields $this->validateCampaignData($data); // Parse track URLs from form $trackUrls = []; if (! empty($data['spotify_url'])) { $trackUrls['spotify'] = $data['spotify_url']; } if (! empty($data['apple_music_url'])) { $trackUrls['apple_music'] = $data['apple_music_url']; } if (! empty($data['tidal_url'])) { $trackUrls['tidal'] = $data['tidal_url']; } // Create campaign $campaign = PreSaveCampaign::create( title: $data['title'], artistName: $data['artist_name'], coverImageUrl: $data['cover_image_url'], description: $data['description'] ?? null, releaseDate: strtotime($data['release_date']), trackUrls: $trackUrls, startDate: ! empty($data['start_date']) ? strtotime($data['start_date']) : null ); $this->campaignRepository->save($campaign); return Redirect::to('/admin/presave/campaigns') ->withFlashMessage('success', 'Campaign created successfully!'); } /** * Show campaign details with registrations */ #[Route(path: '/admin/presave/campaigns/{id}', method: Method::GET)] public function show(HttpRequest $request): ViewResult { $campaignId = $request->routeParams->get('id'); $campaign = $this->campaignRepository->findById($campaignId); if ($campaign === null) { throw FrameworkException::create( ErrorCode::ENTITY_NOT_FOUND, "Campaign with ID {$campaignId} not found" ); } $registrations = $this->registrationRepository->findByCampaign($campaignId); $stats = $this->getCampaignStats($campaign, $registrations); return new ViewResult('admin/presave/campaigns/show', [ 'campaign' => $campaign, 'registrations' => $registrations, 'stats' => $stats, ]); } /** * Show edit campaign form */ #[Route(path: '/admin/presave/campaigns/{id}/edit', method: Method::GET)] public function edit(HttpRequest $request): ViewResult { $campaignId = $request->routeParams->get('id'); $campaign = $this->campaignRepository->findById($campaignId); if ($campaign === null) { throw FrameworkException::create( ErrorCode::ENTITY_NOT_FOUND, "Campaign with ID {$campaignId} not found" ); } return new ViewResult('admin/presave/campaigns/edit', [ 'campaign' => $campaign, ]); } /** * Update campaign */ #[Route(path: '/admin/presave/campaigns/{id}', method: Method::PUT)] public function update(HttpRequest $request): Redirect { $campaignId = $request->routeParams->get('id'); $campaign = $this->campaignRepository->findById($campaignId); if ($campaign === null) { throw FrameworkException::create( ErrorCode::ENTITY_NOT_FOUND, "Campaign with ID {$campaignId} not found" ); } $data = $request->parsedBody->toArray(); $this->validateCampaignData($data); // Parse track URLs $trackUrls = []; if (! empty($data['spotify_url'])) { $trackUrls['spotify'] = $data['spotify_url']; } if (! empty($data['apple_music_url'])) { $trackUrls['apple_music'] = $data['apple_music_url']; } if (! empty($data['tidal_url'])) { $trackUrls['tidal'] = $data['tidal_url']; } // Update campaign $updatedCampaign = $campaign->updateDetails( title: $data['title'], artistName: $data['artist_name'], coverImageUrl: $data['cover_image_url'], description: $data['description'] ?? null, releaseDate: strtotime($data['release_date']), trackUrls: $trackUrls ); $this->campaignRepository->save($updatedCampaign); return Redirect::to("/admin/presave/campaigns/{$campaignId}") ->withFlashMessage('success', 'Campaign updated successfully!'); } /** * Delete campaign */ #[Route(path: '/admin/presave/campaigns/{id}', method: Method::DELETE)] public function destroy(HttpRequest $request): JsonResult { $campaignId = $request->routeParams->get('id'); $campaign = $this->campaignRepository->findById($campaignId); if ($campaign === null) { return new JsonResult([ 'success' => false, 'message' => 'Campaign not found', ], 404); } $this->campaignRepository->delete($campaignId); return new JsonResult([ 'success' => true, 'message' => 'Campaign deleted successfully', ]); } /** * Activate campaign */ #[Route(path: '/admin/presave/campaigns/{id}/activate', method: Method::POST)] public function activate(HttpRequest $request): Redirect { $campaignId = $request->routeParams->get('id'); $campaign = $this->campaignRepository->findById($campaignId); if ($campaign === null) { throw FrameworkException::create( ErrorCode::ENTITY_NOT_FOUND, "Campaign with ID {$campaignId} not found" ); } $activeCampaign = $campaign->updateStatus(CampaignStatus::ACTIVE); $this->campaignRepository->save($activeCampaign); return Redirect::to("/admin/presave/campaigns/{$campaignId}") ->withFlashMessage('success', 'Campaign activated successfully!'); } /** * Pause campaign */ #[Route(path: '/admin/presave/campaigns/{id}/pause', method: Method::POST)] public function pause(HttpRequest $request): Redirect { $campaignId = $request->routeParams->get('id'); $campaign = $this->campaignRepository->findById($campaignId); if ($campaign === null) { throw FrameworkException::create( ErrorCode::ENTITY_NOT_FOUND, "Campaign with ID {$campaignId} not found" ); } $pausedCampaign = $campaign->updateStatus(CampaignStatus::PAUSED); $this->campaignRepository->save($pausedCampaign); return Redirect::to("/admin/presave/campaigns/{$campaignId}") ->withFlashMessage('success', 'Campaign paused successfully!'); } /** * Complete campaign */ #[Route(path: '/admin/presave/campaigns/{id}/complete', method: Method::POST)] public function complete(HttpRequest $request): Redirect { $campaignId = $request->routeParams->get('id'); $campaign = $this->campaignRepository->findById($campaignId); if ($campaign === null) { throw FrameworkException::create( ErrorCode::ENTITY_NOT_FOUND, "Campaign with ID {$campaignId} not found" ); } $completedCampaign = $campaign->updateStatus(CampaignStatus::COMPLETED); $this->campaignRepository->save($completedCampaign); return Redirect::to("/admin/presave/campaigns/{$campaignId}") ->withFlashMessage('success', 'Campaign marked as completed!'); } /** * Validate campaign data */ private function validateCampaignData(array $data): void { $required = ['title', 'artist_name', 'cover_image_url', 'release_date']; foreach ($required as $field) { if (empty($data[$field])) { throw FrameworkException::create( ErrorCode::VAL_REQUIRED_FIELD_MISSING, "Field '{$field}' is required" )->withData(['field' => $field]); } } // Validate at least one track URL if (empty($data['spotify_url']) && empty($data['apple_music_url']) && empty($data['tidal_url'])) { throw FrameworkException::create( ErrorCode::VAL_BUSINESS_RULE_VIOLATION, 'At least one track URL (Spotify, Apple Music, or Tidal) is required' ); } } /** * Get global campaign statistics */ private function getGlobalStats(): array { $allCampaigns = $this->campaignRepository->findAll(); $stats = [ 'total' => count($allCampaigns), 'draft' => 0, 'active' => 0, 'paused' => 0, 'completed' => 0, 'total_registrations' => 0, ]; foreach ($allCampaigns as $campaign) { $stats[strtolower($campaign->status->value)]++; $registrations = $this->registrationRepository->findByCampaign($campaign->id); $stats['total_registrations'] += count($registrations); } return $stats; } /** * Get campaign-specific statistics */ private function getCampaignStats(PreSaveCampaign $campaign, array $registrations): array { $stats = [ 'total_registrations' => count($registrations), 'pending' => 0, 'processing' => 0, 'completed' => 0, 'failed' => 0, 'by_platform' => [ 'spotify' => 0, 'apple_music' => 0, 'tidal' => 0, ], ]; foreach ($registrations as $registration) { $stats[strtolower($registration->status->value)]++; $stats['by_platform'][strtolower($registration->platform->value)]++; } return $stats; } }