Files
deepseek fff7a4b9b9 GG.deals clone: CodeIgniter 4 + MongoDB game price tracker
- Full gg.deals-style UI: deal feed with filters, game pages with price
  history charts, rankings, vouchers, news, prepaids, subscriptions,
  bundles, franchises, wishlist, API docs
- Real data crawlers:
  * gg:crawl (Steam Store API + GOG Catalog + Epic free games)
  * gg:catalog (252K game slugs from gg.deals sitemaps)
  * gg:ggdeals (per-game prices via FlareSolverr session, JSON-LD parsing)
  * gg:seed / gg:crawl-covers (demo seed + Steam CDN cover art)
- 24/7 self-updating scheduler (scripts/scheduler_loop.php, lockfile-protected)
- Tailwind v4 dark/light theme, Alpine.js, Chart.js
2026-08-23 07:06:02 +00:00

137 lines
5.7 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Controllers;
use App\Models\GameModel;
use App\Models\StoreModel;
use App\Libraries\Mongo;
class Home extends BaseController
{
/**
* Deal presets (mirrors gg.deals /deals/{preset}/ URLs).
*/
private const PRESETS = [
'hot-new-deals' => ['title' => 'Hot New Deals', 'sort' => 'hot', 'preset' => 'hot_new'],
'new-deals' => ['title' => 'New Deals', 'sort' => 'recent', 'preset' => 'new'],
'historical-lows' => ['title' => 'Historical Lows', 'sort' => 'rating', 'preset' => 'hl'],
'ending-soon' => ['title' => 'Ending Soon', 'sort' => 'ending', 'preset' => 'ending'],
'at-least-75-off' => ['title' => 'At least 75% off', 'sort' => 'cut', 'preset' => 'cut75'],
'at-least-85-off' => ['title' => 'At least 85% off', 'sort' => 'cut', 'preset' => 'cut85'],
];
public function index(): string
{
$request = service('request');
// If any filter/search is active → regular deal feed. Otherwise → homepage dashboard.
$hasFilters = $request->getGet('stores') !== null || $request->getGet('kind') !== null
|| $request->getGet('min_price') !== null || $request->getGet('max_price') !== null
|| $request->getGet('min_cut') !== null || $request->getGet('hl_only') !== null
|| $request->getGet('sort') !== null || $request->getGet('price_tier') !== null
|| $request->getGet('discount_tier') !== null || $request->getGet('deal_rating') !== null
|| $request->getGet('release_range') !== null || $request->getGet('q') !== null;
if (! $hasFilters) {
return $this->dashboard();
}
return $this->renderFeed([]);
}
/**
* Plain deal feed (used by /deals) regardless of filters.
*/
public function feedOnly(): string
{
return $this->renderFeed([]);
}
/**
* Homepage dashboard mirroring gg.deals: multiple preset sections.
*/
private function dashboard(): string
{
$model = new GameModel();
$data = [
'pageTitle' => 'GG.deals — Best game deals, prices &amp; discounts',
'popular' => $model->dashboardDeals('best', 8),
'newDeals' => $model->dashboardDeals('new', 8),
'bestDeals' => $model->dashboardDeals('hot', 8),
'histLows' => $model->dashboardDeals('hl', 8),
'endingSoon' => $model->dashboardDeals('ending', 8),
'mostWanted' => $model->dashboardGames('wanted', 8),
'newReleases' => $model->dashboardGames('releases', 8),
'upcoming' => $model->dashboardGames('upcoming', 8),
'news' => $model->recentNews(6),
'prepaids' => Mongo::collection('prepaids')->find([], ['sort' => ['name' => 1], 'limit' => 8])->toArray(),
'stats' => $model->stats(),
'stores' => (new StoreModel())->map(),
'trending' => $model->trending(12),
];
return view('homepage', $data);
}
public function preset(string $name): string
{
if (! isset(self::PRESETS[$name])) {
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
}
return $this->renderFeed(self::PRESETS[$name]);
}
private function renderFeed(array $preset): string
{
$request = service('request');
$filters = [
'stores' => array_values(array_filter(array_map('strval', (array) $request->getGet('stores')))),
'kind' => (string) ($request->getGet('kind') ?? ''),
'min_price' => $request->getGet('min_price') !== null ? (string) $request->getGet('min_price') : '',
'max_price' => $request->getGet('max_price') !== null ? (string) $request->getGet('max_price') : '',
'min_cut' => (int) ($request->getGet('min_cut') ?? 0),
'hl_only' => (bool) $request->getGet('hl_only'),
'sort' => (string) ($request->getGet('sort') ?? ($preset['sort'] ?? 'trending')),
'q' => trim((string) ($request->getGet('q') ?? '')),
'price_tier'=> (string) ($request->getGet('price_tier') ?? ''),
'discount_tier' => (string) ($request->getGet('discount_tier') ?? ''),
'deal_rating' => (string) ($request->getGet('deal_rating') ?? ''),
'release_range' => (string) ($request->getGet('release_range') ?? ''),
'time_range' => (string) ($request->getGet('time_range') ?? ''),
'view' => (string) ($request->getGet('view') ?? 'sidebar'),
'wishlist' => array_values(array_filter(array_map('strval', (array) $request->getGet('wishlist')))),
];
$filters['preset'] = $preset['preset'] ?? '';
$filters['preset_name'] = $preset['title'] ?? '';
$page = max(1, (int) $request->getGet('page'));
$perPage = 20;
$model = new GameModel();
$feed = $model->feed($filters, $page, $perPage);
$data = [
'pageTitle' => ($filters['preset_name'] !== '' ? $filters['preset_name'] . ' — ' : '') . 'Best PC game deals — GG.deals',
'filters' => $filters,
'feed' => $feed['rows'],
'total' => $feed['total'],
'pages' => $feed['pages'],
'page' => $page,
'perPage' => $perPage,
'trending' => $model->trending(12),
'stores' => (new StoreModel())->all(),
'stats' => $model->stats(),
'presets' => self::PRESETS,
'activePreset' => $filters['preset_name'],
];
return view('home', $data);
}
}