Promo endpoint that display only products on sale. #1
@@ -5,6 +5,7 @@ include_once 'vendor/autoload.php';
|
|||||||
use Krzysiej\RyobiCrawler\Controller\CategoryController;
|
use Krzysiej\RyobiCrawler\Controller\CategoryController;
|
||||||
use Krzysiej\RyobiCrawler\Controller\IndexController;
|
use Krzysiej\RyobiCrawler\Controller\IndexController;
|
||||||
use Krzysiej\RyobiCrawler\Controller\ProductController;
|
use Krzysiej\RyobiCrawler\Controller\ProductController;
|
||||||
|
use Krzysiej\RyobiCrawler\Controller\PromosController;
|
||||||
use Krzysiej\RyobiCrawler\Controller\SearchController;
|
use Krzysiej\RyobiCrawler\Controller\SearchController;
|
||||||
use Krzysiej\RyobiCrawler\Controller\StarController;
|
use Krzysiej\RyobiCrawler\Controller\StarController;
|
||||||
use Symfony\Component\Routing\Matcher\UrlMatcher;
|
use Symfony\Component\Routing\Matcher\UrlMatcher;
|
||||||
@@ -15,10 +16,11 @@ use Symfony\Component\Routing\RouteCollection;
|
|||||||
if (!file_exists('database.sqlite')) {
|
if (!file_exists('database.sqlite')) {
|
||||||
exit('Database file <code>database.sqlite</code> missing. Run docker compose <blockquote>docker compose exec php-app php index.php app:migrate</blockquote> to create it.');
|
exit('Database file <code>database.sqlite</code> missing. Run docker compose <blockquote>docker compose exec php-app php index.php app:migrate</blockquote> to create it.');
|
||||||
}
|
}
|
||||||
$productRoute = new Route('/product/{product_id}', ['_controller' => ProductController::class], ['product_id' => '\d+']);
|
$productRoute = new Route('/product/{product_id<\d+>}', ['_controller' => ProductController::class]);
|
||||||
$searchRoute = new Route('/?search={search_term}', ['_controller' => SearchController::class]);
|
$searchRoute = new Route('/?search={search_term}', ['_controller' => SearchController::class]);
|
||||||
$categoryRoute = new Route('/category/{category_name}', ['_controller' => CategoryController::class]);
|
$categoryRoute = new Route('/category/{category_name}', ['_controller' => CategoryController::class]);
|
||||||
$starRoute = new Route('/star/{product_id}', ['_controller' => StarController::class]);
|
$starRoute = new Route('/star/{product_id}', ['_controller' => StarController::class]);
|
||||||
|
$promosRoute = new Route('/promos', ['_controller' => PromosController::class]);
|
||||||
$indexRoute = new Route('/', ['_controller' => IndexController::class]);
|
$indexRoute = new Route('/', ['_controller' => IndexController::class]);
|
||||||
$routes = new RouteCollection();
|
$routes = new RouteCollection();
|
||||||
|
|
||||||
@@ -26,6 +28,7 @@ $routes->add('product_show', $productRoute);
|
|||||||
$routes->add('search_show', $searchRoute);
|
$routes->add('search_show', $searchRoute);
|
||||||
$routes->add('category_show', $categoryRoute);
|
$routes->add('category_show', $categoryRoute);
|
||||||
$routes->add('start_show', $starRoute);
|
$routes->add('start_show', $starRoute);
|
||||||
|
$routes->add('promos_show', $promosRoute);
|
||||||
$routes->add('index_show', $indexRoute);
|
$routes->add('index_show', $indexRoute);
|
||||||
|
|
||||||
$context = new RequestContext();
|
$context = new RequestContext();
|
||||||
@@ -40,6 +43,7 @@ match ($parameters['_controller']) {
|
|||||||
SearchController::class => (new $parameters['_controller']())($parameters['search_term']),
|
SearchController::class => (new $parameters['_controller']())($parameters['search_term']),
|
||||||
CategoryController::class => (new $parameters['_controller']())($parameters['category_name']),
|
CategoryController::class => (new $parameters['_controller']())($parameters['category_name']),
|
||||||
ProductController::class, StarController::class => (new $parameters['_controller']())($parameters['product_id']),
|
ProductController::class, StarController::class => (new $parameters['_controller']())($parameters['product_id']),
|
||||||
|
PromosController::class => (new $parameters['_controller']())(),
|
||||||
IndexController::class => (new $parameters['_controller']())(),
|
IndexController::class => (new $parameters['_controller']())(),
|
||||||
default => throw new Exception('Route not found')
|
default => throw new Exception('Route not found')
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,17 +2,31 @@
|
|||||||
|
|
||||||
namespace Krzysiej\RyobiCrawler\Controller;
|
namespace Krzysiej\RyobiCrawler\Controller;
|
||||||
|
|
||||||
|
use Illuminate\Support\ItemNotFoundException;
|
||||||
use Krzysiej\RyobiCrawler\Models\Product;
|
use Krzysiej\RyobiCrawler\Models\Product;
|
||||||
|
use Twig\Error\LoaderError;
|
||||||
|
use Twig\Error\RuntimeError;
|
||||||
|
use Twig\Error\SyntaxError;
|
||||||
|
|
||||||
final class ProductController extends BaseController
|
final class ProductController extends BaseController
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @throws SyntaxError
|
||||||
|
* @throws RuntimeError
|
||||||
|
* @throws LoaderError
|
||||||
|
*/
|
||||||
public function __invoke(int $productId): void
|
public function __invoke(int $productId): void
|
||||||
{
|
{
|
||||||
$product = Product::with([
|
$product = Product::with([
|
||||||
'price' => fn($query) => $query->orderBy('created_at', 'desc')
|
'price' => fn($query) => $query->orderBy('created_at', 'desc')
|
||||||
])->find($productId);
|
])->find($productId);
|
||||||
|
|
||||||
|
if(null === $product) {
|
||||||
|
throw new ItemNotFoundException('Product not found');
|
||||||
|
}
|
||||||
|
|
||||||
$priceList = $product->price()->pluck('price')->implode(',');
|
$priceList = $product->price()->pluck('price')->implode(',');
|
||||||
$priceDates = $product->price()->pluck('created_at')->map(fn($date) => $date->format('Y-m-d'))->implode("','");
|
$priceDates = $product->price()->pluck('created_at')->map(fn($date) => $date->format('Y-m-d'))->implode("','");
|
||||||
$this->twig->display('product.html.twig', ['product' => $product ?? [], 'price_list' => $priceList, 'price_dates' => $priceDates]);
|
$this->twig->display('product.html.twig', ['product' => $product, 'price_list' => $priceList, 'price_dates' => $priceDates]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
15
src/Controller/PromosController.php
Normal file
15
src/Controller/PromosController.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Krzysiej\RyobiCrawler\Controller;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Krzysiej\RyobiCrawler\Models\Product;
|
||||||
|
|
||||||
|
final class PromosController extends BaseController
|
||||||
|
{
|
||||||
|
public function __invoke(): void
|
||||||
|
{
|
||||||
|
$products = Product::whereHas('currentPrice', fn(Builder $query) => $query->whereColumn('price', '<', 'productStandardPrice'))->with(['currentPrice'])->get();
|
||||||
|
$this->twig->display('productList.html.twig', ['products' => $products]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ namespace Krzysiej\RyobiCrawler\Models;
|
|||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property integer $skuID
|
* @property integer $skuID
|
||||||
@@ -28,6 +29,11 @@ class Product extends Model
|
|||||||
{
|
{
|
||||||
return $this->hasMany(Price::class);
|
return $this->hasMany(Price::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function currentPrice(): HasOne
|
||||||
|
{
|
||||||
|
return $this->hasOne(Price::class)->latestOfMany('created_at');
|
||||||
|
}
|
||||||
public function stock(): HasMany
|
public function stock(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(Stock::class);
|
return $this->hasMany(Stock::class);
|
||||||
|
|||||||
@@ -2,6 +2,17 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<table class='table table-hover'>
|
<table class='table table-hover'>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Code</th>
|
||||||
|
<th>Categories</th>
|
||||||
|
<th></th>
|
||||||
|
<th>Price</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
{% for product in products %}
|
{% for product in products %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="align-middle font-weight-bold h3"><a class="text-warning text-decoration-none" href="/star/{{ product.id }}">{% if product.starred %}★{% else %} ☆ {% endif %}</a></td>
|
<td class="align-middle font-weight-bold h3"><a class="text-warning text-decoration-none" href="/star/{{ product.id }}">{% if product.starred %}★{% else %} ☆ {% endif %}</a></td>
|
||||||
|
|||||||
@@ -9,14 +9,26 @@
|
|||||||
crossorigin="anonymous"/>
|
crossorigin="anonymous"/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav class="navbar sticky-top bg-body-tertiary border-bottom border-secondary border-1">
|
<nav class="navbar navbar-expand-lg sticky-top bg-body-tertiary border-bottom border-secondary border-1">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="/">Crawler</a>
|
<a class="navbar-brand" href="/">Crawler</a>
|
||||||
<form class="d-flex w-50" role="search" action="/">
|
|
||||||
<input class="form-control me-2" type="search" name="search" placeholder="Search term eg. 36v or RCS18X" value="{{ search }}" aria-label="Search">
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<button class="btn btn-outline-success" type="submit">Search</button>
|
<span class="navbar-toggler-icon"></span>
|
||||||
</form>
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul class="navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" aria-current="page" href="/promos">Promos</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<form class="d-flex w-50" role="search" action="/">
|
||||||
|
<input class="form-control me-2" type="search" name="search" placeholder="Search term eg. 36v or RCS18X" value="{{ search }}" aria-label="Search">
|
||||||
|
<button class="btn btn-outline-success" type="submit">Search</button>
|
||||||
|
</form>
|
||||||
</nav>
|
</nav>
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user