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
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Libraries\Mongo;
|
||||
use App\Models\GameModel;
|
||||
use CodeIgniter\Controller;
|
||||
|
||||
class Api extends Controller
|
||||
{
|
||||
public function suggest(): \CodeIgniter\HTTP\ResponseInterface
|
||||
{
|
||||
$q = trim((string) service('request')->getGet('q'));
|
||||
if (mb_strlen($q) < 2) {
|
||||
return service('response')->setJSON([]);
|
||||
}
|
||||
|
||||
return service('response')->setJSON((new GameModel())->suggest($q));
|
||||
}
|
||||
|
||||
/**
|
||||
* Game Prices API: lowest price by Steam App ID.
|
||||
* GET /api/prices?appid=1245620
|
||||
*/
|
||||
public function prices(): \CodeIgniter\HTTP\ResponseInterface
|
||||
{
|
||||
$appid = (int) service('request')->getGet('appid');
|
||||
if ($appid <= 0) {
|
||||
return service('response')->setStatusCode(400)->setJSON([
|
||||
'error' => 'bad_request',
|
||||
'message' => 'Missing or invalid "appid" query parameter.',
|
||||
]);
|
||||
}
|
||||
|
||||
// appid map is in the CrawlCovers command; reuse via a public map.
|
||||
$game = (new GameModel())->findByAppId($appid);
|
||||
if ($game === null) {
|
||||
return service('response')->setStatusCode(404)->setJSON([
|
||||
'error' => 'not_found',
|
||||
'message' => "No game found for Steam App ID {$appid}.",
|
||||
]);
|
||||
}
|
||||
|
||||
return service('response')->setJSON([
|
||||
'game' => [
|
||||
'title' => $game['title'],
|
||||
'slug' => $game['slug'],
|
||||
'url' => '/game/' . $game['slug'],
|
||||
'steam_appid' => $appid,
|
||||
'regular_price' => (float) $game['base_price'],
|
||||
'best_official' => [
|
||||
'price' => (float) $game['best_official']['price'],
|
||||
'store' => $game['best_official']['store'],
|
||||
'cut' => (int) $game['best_official']['cut'],
|
||||
],
|
||||
'best_keyshop' => [
|
||||
'price' => (float) $game['best_keyshop']['price'],
|
||||
'store' => $game['best_keyshop']['store'],
|
||||
'cut' => (int) $game['best_keyshop']['cut'],
|
||||
],
|
||||
'all_time_low' => [
|
||||
'price' => (float) $game['all_time_low']['price'],
|
||||
'store' => $game['all_time_low']['store'],
|
||||
'date' => $game['all_time_low']['date'] instanceof \MongoDB\BSON\UTCDateTime
|
||||
? $game['all_time_low']['date']->toDateTime()->format('Y-m-d')
|
||||
: null,
|
||||
],
|
||||
'active_deals' => (int) $game['active_deals'],
|
||||
'metacritic' => (int) $game['score'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* API documentation page data (regions, endpoints).
|
||||
*/
|
||||
public function docs(): string
|
||||
{
|
||||
$model = new GameModel();
|
||||
|
||||
$data = [
|
||||
'pageTitle' => 'GG.deals API — Game Deals, Bundles and Prices API',
|
||||
'stats' => $model->stats(),
|
||||
];
|
||||
|
||||
return view('api', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use CodeIgniter\Controller;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* BaseController provides a convenient place for loading components
|
||||
* and performing functions that are needed by all your controllers.
|
||||
*
|
||||
* Extend this class in any new controllers:
|
||||
* ```
|
||||
* class Home extends BaseController
|
||||
* ```
|
||||
*
|
||||
* For security, be sure to declare any new methods as protected or private.
|
||||
*/
|
||||
abstract class BaseController extends Controller
|
||||
{
|
||||
/**
|
||||
* Be sure to declare properties for any property fetch you initialized.
|
||||
* The creation of dynamic property is deprecated in PHP 8.2.
|
||||
*/
|
||||
|
||||
// protected $session;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
|
||||
{
|
||||
// Load here all helpers you want to be available in your controllers that extend BaseController.
|
||||
// Caution: Do not put the this below the parent::initController() call below.
|
||||
// $this->helpers = ['form', 'url'];
|
||||
|
||||
// Caution: Do not edit this line.
|
||||
parent::initController($request, $response, $logger);
|
||||
|
||||
// Preload any models, libraries, etc, here.
|
||||
// $this->session = service('session');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Libraries\Mongo;
|
||||
use App\Models\GameModel;
|
||||
use App\Models\StoreModel;
|
||||
use CodeIgniter\Controller;
|
||||
|
||||
class Bundle extends Controller
|
||||
{
|
||||
public function index(string $slug): string
|
||||
{
|
||||
$b = Mongo::collection('bundles')->findOne(['slug' => $slug]);
|
||||
if ($b === null) {
|
||||
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
$games = [];
|
||||
foreach ($b['games'] as $gslug) {
|
||||
$g = (new GameModel())->findBySlug($gslug);
|
||||
if ($g !== null) {
|
||||
$games[] = $g;
|
||||
}
|
||||
}
|
||||
|
||||
$data = [
|
||||
'pageTitle' => $b['title'] . ' Bundle — GG.deals',
|
||||
'bundle' => $b,
|
||||
'games' => $games,
|
||||
'stores' => (new StoreModel())->map(),
|
||||
];
|
||||
|
||||
return view('bundle', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Libraries\Mongo;
|
||||
use App\Models\GameModel;
|
||||
use CodeIgniter\Controller;
|
||||
|
||||
class Franchise extends Controller
|
||||
{
|
||||
public function index(string $slug): string
|
||||
{
|
||||
$fr = Mongo::collection('franchises')->findOne(['slug' => $slug]);
|
||||
if ($fr === null) {
|
||||
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
$games = [];
|
||||
foreach ($fr['games'] as $gslug) {
|
||||
$g = (new GameModel())->findBySlug($gslug);
|
||||
if ($g !== null) {
|
||||
$games[] = $g;
|
||||
}
|
||||
}
|
||||
|
||||
$data = [
|
||||
'pageTitle' => $fr['name'] . ' Franchise — GG.deals',
|
||||
'franchise' => $fr,
|
||||
'games' => $games,
|
||||
'allFranchises' => Mongo::collection('franchises')->find([], ['sort' => ['name' => 1]])->toArray(),
|
||||
];
|
||||
|
||||
return view('franchise', $data);
|
||||
}
|
||||
|
||||
public function all(): string
|
||||
{
|
||||
$list = Mongo::collection('franchises')->find([], ['sort' => ['name' => 1]])->toArray();
|
||||
|
||||
$data = [
|
||||
'pageTitle' => 'Franchises — GG.deals',
|
||||
'franchises' => $list,
|
||||
];
|
||||
|
||||
return view('franchises', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Libraries\Mongo;
|
||||
use App\Models\GameModel;
|
||||
use App\Models\StoreModel;
|
||||
use CodeIgniter\Controller;
|
||||
|
||||
class Game extends Controller
|
||||
{
|
||||
public function show(string $slug): string
|
||||
{
|
||||
$model = new GameModel();
|
||||
|
||||
$game = $model->findBySlug($slug);
|
||||
if ($game === null) {
|
||||
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
$deals = $model->dealsFor($slug);
|
||||
$history = $model->historyFor($slug);
|
||||
$similar = $model->similar($game, 8);
|
||||
$bundles = $model->bundlesFor($slug);
|
||||
$franchises = $model->franchisesFor($slug);
|
||||
$subscriptions = Mongo::collection('subscriptions')->find([], ['sort' => ['price' => 1]])->toArray();
|
||||
|
||||
$data = [
|
||||
'pageTitle' => $game['title'] . ' — best deals — GG.deals',
|
||||
'game' => $game,
|
||||
'deals' => $deals,
|
||||
'history' => $history,
|
||||
'stores' => (new StoreModel())->map(),
|
||||
'similar' => $similar,
|
||||
'bundles' => $bundles,
|
||||
'franchises' => $franchises,
|
||||
'subscriptions' => $subscriptions,
|
||||
];
|
||||
|
||||
return view('game', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\GameModel;
|
||||
use App\Models\StoreModel;
|
||||
use CodeIgniter\Controller;
|
||||
|
||||
class Games extends Controller
|
||||
{
|
||||
public function index(string $genre = ''): string
|
||||
{
|
||||
$model = new GameModel();
|
||||
$request = service('request');
|
||||
|
||||
// genre map (slug => name)
|
||||
$genres = [];
|
||||
foreach ($model->genres() as $g) {
|
||||
$slug = url_title((string) $g['_id'], '-', true);
|
||||
$genres[$slug] = ['name' => $g['_id'], 'count' => $g['count']];
|
||||
}
|
||||
ksort($genres);
|
||||
|
||||
$genreName = '';
|
||||
if ($genre !== '') {
|
||||
if (! isset($genres[$genre])) {
|
||||
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
$genreName = $genres[$genre]['name'];
|
||||
}
|
||||
|
||||
// ---- filters (same shape as the deal feed) ----
|
||||
$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') ?? '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') ?? ''),
|
||||
'genre' => $genreName,
|
||||
'genres' => array_values(array_filter(array_map('strval', (array) $request->getGet('genres')))),
|
||||
];
|
||||
|
||||
$page = max(1, (int) $request->getGet('page'));
|
||||
$perPage = 20;
|
||||
|
||||
$feed = $model->feed($filters, $page, $perPage);
|
||||
|
||||
$data = [
|
||||
'pageTitle' => ($genreName !== '' ? ucwords($genreName) . ' games — ' : '') . 'GG.deals',
|
||||
'filters' => $filters,
|
||||
'feed' => $feed['rows'],
|
||||
'total' => $feed['total'],
|
||||
'pages' => $feed['pages'],
|
||||
'page' => $page,
|
||||
'perPage' => $perPage,
|
||||
'genres' => $genres,
|
||||
'activeGenre' => $genre,
|
||||
'activeGenreName' => $genreName,
|
||||
'stores' => (new StoreModel())->all(),
|
||||
'stats' => $model->stats(),
|
||||
];
|
||||
|
||||
return view('games', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?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 & 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Libraries\Mongo;
|
||||
use CodeIgniter\Controller;
|
||||
|
||||
class News extends Controller
|
||||
{
|
||||
private const CATEGORIES = [
|
||||
'' => 'All news',
|
||||
'deals' => 'Deals',
|
||||
'bundles' => 'Bundles',
|
||||
'freebies' => 'Freebies',
|
||||
'giveaways' => 'Giveaways',
|
||||
'subscriptions' => 'Subscriptions',
|
||||
];
|
||||
|
||||
public function index(string $cat = ''): string
|
||||
{
|
||||
if (! isset(self::CATEGORIES[$cat])) {
|
||||
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
$filter = $cat !== '' ? ['category' => $cat] : [];
|
||||
|
||||
$posts = Mongo::collection('news')
|
||||
->find($filter, ['sort' => ['date' => -1], 'limit' => 40])
|
||||
->toArray();
|
||||
|
||||
$data = [
|
||||
'pageTitle' => 'Video game news — GG.deals',
|
||||
'posts' => $posts,
|
||||
'cats' => self::CATEGORIES,
|
||||
'active' => $cat,
|
||||
];
|
||||
|
||||
return view('news', $data);
|
||||
}
|
||||
|
||||
public function article(string $slug): string
|
||||
{
|
||||
$post = Mongo::collection('news')->findOne(['slug' => $slug]);
|
||||
if ($post === null) {
|
||||
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
$related = Mongo::collection('news')
|
||||
->find(
|
||||
['slug' => ['$ne' => $slug], 'category' => $post['category']],
|
||||
['sort' => ['date' => -1], 'limit' => 4]
|
||||
)
|
||||
->toArray();
|
||||
|
||||
$data = [
|
||||
'pageTitle' => $post['title'] . ' — GG.deals',
|
||||
'post' => $post,
|
||||
'related' => $related,
|
||||
'cats' => self::CATEGORIES,
|
||||
'active' => $post['category'],
|
||||
];
|
||||
|
||||
return view('news_article', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use CodeIgniter\Controller;
|
||||
|
||||
class Pages extends Controller
|
||||
{
|
||||
public function faq(): string
|
||||
{
|
||||
$data = [
|
||||
'pageTitle' => 'FAQ — GG.deals',
|
||||
];
|
||||
|
||||
return view('faq', $data);
|
||||
}
|
||||
|
||||
public function tutorial(string $topic): string
|
||||
{
|
||||
$tutorials = [
|
||||
'activate-steam' => ['How to activate Steam CD key', 'steam'],
|
||||
'activate-gog' => ['How to activate GOG CD key', 'gog'],
|
||||
'activate-epic' => ['How to activate Epic Games CD key', 'epic'],
|
||||
'activate-origin' => ['How to activate Origin CD key', 'origin'],
|
||||
'activate-uplay' => ['How to activate Uplay CD key', 'uplay'],
|
||||
'keyshops-risks' => ['Keyshops — what to know', 'keyshops'],
|
||||
];
|
||||
|
||||
if (! isset($tutorials[$topic])) {
|
||||
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
$data = [
|
||||
'pageTitle' => $tutorials[$topic][0] . ' — GG.deals',
|
||||
'tutorial' => $tutorials[$topic],
|
||||
'all' => $tutorials,
|
||||
];
|
||||
|
||||
return view('tutorial', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Libraries\Mongo;
|
||||
use CodeIgniter\Controller;
|
||||
|
||||
class Prepaids extends Controller
|
||||
{
|
||||
public function index(): string
|
||||
{
|
||||
$groups = Mongo::collection('prepaids')->find([], ['sort' => ['name' => 1]])->toArray();
|
||||
|
||||
$data = [
|
||||
'pageTitle' => 'Gift Cards & Prepaids — GG.deals',
|
||||
'groups' => $groups,
|
||||
];
|
||||
|
||||
return view('prepaids', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\GameModel;
|
||||
use CodeIgniter\Controller;
|
||||
|
||||
class Rankings extends Controller
|
||||
{
|
||||
public function index(): string
|
||||
{
|
||||
$model = new GameModel();
|
||||
|
||||
$data = [
|
||||
'pageTitle' => 'Rankings — GG.deals',
|
||||
'wishlisted' => $model->ranked('trending', 20),
|
||||
'collected' => $model->ranked('rating_pct', 20),
|
||||
'metacritic' => $model->ranked('score', 20),
|
||||
'reviews' => $model->ranked('rating_pct', 20),
|
||||
];
|
||||
|
||||
return view('rankings', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\GameModel;
|
||||
use CodeIgniter\Controller;
|
||||
|
||||
class Stores extends Controller
|
||||
{
|
||||
public function index(): string
|
||||
{
|
||||
$model = new GameModel();
|
||||
|
||||
$data = [
|
||||
'pageTitle' => 'Stores — GG.deals',
|
||||
'stores' => $model->storeStats(),
|
||||
];
|
||||
|
||||
return view('stores', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Libraries\Mongo;
|
||||
use CodeIgniter\Controller;
|
||||
|
||||
class Subscriptions extends Controller
|
||||
{
|
||||
public function index(): string
|
||||
{
|
||||
$subs = Mongo::collection('subscriptions')
|
||||
->find([], ['sort' => ['price' => 1]])
|
||||
->toArray();
|
||||
|
||||
$data = [
|
||||
'pageTitle' => 'Subscriptions — GG.deals',
|
||||
'subs' => $subs,
|
||||
'stores' => (new \App\Models\StoreModel())->map(),
|
||||
];
|
||||
|
||||
return view('subscriptions', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\StoreModel;
|
||||
use App\Libraries\Mongo;
|
||||
use CodeIgniter\Controller;
|
||||
|
||||
class Vouchers extends Controller
|
||||
{
|
||||
public function index(): string
|
||||
{
|
||||
$vouchers = Mongo::collection('vouchers')->find([], ['sort' => ['store' => 1]])->toArray();
|
||||
|
||||
$data = [
|
||||
'pageTitle' => 'Promo Codes & Coupons — GG.deals',
|
||||
'vouchers' => $vouchers,
|
||||
'stores' => (new StoreModel())->map(),
|
||||
];
|
||||
|
||||
return view('vouchers', $data);
|
||||
}
|
||||
|
||||
public function store(string $code): string
|
||||
{
|
||||
$vouchers = Mongo::collection('vouchers')->find(['store' => $code], ['sort' => ['created' => -1]])->toArray();
|
||||
|
||||
$data = [
|
||||
'pageTitle' => ucfirst($code) . ' promo codes — GG.deals',
|
||||
'vouchers' => $vouchers,
|
||||
'stores' => (new StoreModel())->map(),
|
||||
'storeCode' => $code,
|
||||
];
|
||||
|
||||
return view('vouchers', $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\GameModel;
|
||||
use CodeIgniter\Controller;
|
||||
|
||||
class Wishlist extends Controller
|
||||
{
|
||||
public function index(): string
|
||||
{
|
||||
// wishlist slugs come from localStorage client-side; the page shows
|
||||
// the JS-powered collection with an optional server fetch fallback.
|
||||
$data = [
|
||||
'pageTitle' => 'Wishlist — GG.deals',
|
||||
];
|
||||
|
||||
return view('wishlist', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON: fetch full game data for a list of slugs (used by the wishlist page).
|
||||
*/
|
||||
public function data(): \CodeIgniter\HTTP\ResponseInterface
|
||||
{
|
||||
$slugs = (array) service('request')->getGet('slugs');
|
||||
|
||||
$games = [];
|
||||
foreach (array_unique(array_filter($slugs)) as $slug) {
|
||||
$g = (new GameModel())->findBySlug($slug);
|
||||
if ($g !== null) {
|
||||
$games[] = $g;
|
||||
}
|
||||
}
|
||||
|
||||
return service('response')->setJSON($games);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user