This commit is contained in:
2026-02-03 15:31:29 +05:00
commit 326c677e8d
2800 changed files with 1489388 additions and 0 deletions

0
.ai/base.mdx Normal file
View File

18
.editorconfig Normal file
View File

@@ -0,0 +1,18 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[docker-compose.yml]
indent_size = 4

61
.env.example Normal file
View File

@@ -0,0 +1,61 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
API_TOKEN=123
NOVA_LICENSE_KEY=UEkhFwqhhYw2UPqcoVNWWhKrZOOWXujgj7pLPdDzMqflYX4Pwl
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=postshop
DB_USERNAME=root
DB_PASSWORD=
BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
MEMCACHED_HOST=127.0.0.1
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=mailpit
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME=https
PUSHER_APP_CLUSTER=mt1
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_HOST="${PUSHER_HOST}"
VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

28
.eslintrc Normal file
View File

@@ -0,0 +1,28 @@
module.exports = {
"env": {
"browser": true,
"es6": true
},
"extends": "eslint:recommended",
"parserOptions": {
"sourceType": "module"
},
"rules": {
"indent": [
"error",
4
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"never"
]
}
};

11
.gitattributes vendored Normal file
View File

@@ -0,0 +1,11 @@
* text=auto eol=lf
*.blade.php diff=html
*.css diff=css
*.html diff=html
*.md diff=markdown
*.php diff=php
/.github export-ignore
CHANGELOG.md export-ignore
.styleci.yml export-ignore

23
.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
/.phpunit.cache
/node_modules
/public/build
/public/hot
/public/storage
/storage/*.key
/vendor
.env
.env.backup
.env.production
.phpunit.result.cache
Homestead.json
Homestead.yaml
auth.json
npm-debug.log
yarn-error.log
/.fleet
/.idea
/.vscode
/nova
/nova-old
/dump.rdb
**/.DS_Store

51
README.md Normal file
View File

@@ -0,0 +1,51 @@
# Postshop
## Installation
```bash
# Install posgres and configure
sudo su - postgres
psql
CREATE USER postshop WITH PASSWORD 'ACvL2(F@H^F)D7gs';
CREATE DATABASE postshopdb WITH OWNER = postshop LC_COLLATE = 'en_US.UTF-8' TEMPLATE template0;
# Clone
git clone --depth 1 https://github.com/nurmuhammet-ali/postshop.git
# Install packages and configure
cd postshop
cp .env.example .env
composer install --prefer-dist
vim .env
# setup db
# ...
# Link the storage
php artisan storage:link
# Migrate and seed
php artisan migrate --seed
# Setup permessions (nginx)
chown -R www-data:www-data ./
sudo find -type f -exec chmod 644 {} \;
sudo find -type d -exec chmod 755 {} \;
sudo chgrp -R www-data storage bootstrap/cache
sudo chmod -R ug+rwx storage bootstrap/cache
# Setup supervisor
apt-get install supervisor
vim /etc/supervisor/conf.d/laravel-default-worker.conf
[program:laravel-default-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
user=www-data
numprocs=8
redirect_stderr=true
stdout_logfile=/var/www/postshop/storage/logs/worker.log
stopwaitsecs=3600
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-default-worker:*

32
app/Console/Kernel.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use Laravel\Nova\Fields\Attachments\PruneStaleAttachments;
class Kernel extends ConsoleKernel
{
/**
* Define the application's command schedule.
*/
protected function schedule(Schedule $schedule): void
{
// Remove unnecessary documents...
$schedule->exec('rm -rf '.public_path('app-docs/*'))->everyMinute();
// Remove non saved attachments...
$schedule->call(new PruneStaleAttachments)->daily();
}
/**
* Register the commands for the application.
*/
protected function commands(): void
{
$this->load(__DIR__.'/Commands');
require base_path('routes/console.php');
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Events\Ecommerce\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ChannelActiveStateChanged implements ShouldQueue
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*/
public function __construct(
public $channel
) {
//
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Events\Ecommerce\Product\Category;
use App\Models\Ecommerce\Product\Category\Category;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class CategoryTaxChanged
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*/
public function __construct(
public Category $category
) {}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Events\Ecommerce\Product\Order;
use App\Models\Ecommerce\Product\Order\Order;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class OrderCreated
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*/
public function __construct(
public Order $order
) {
//
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class Handler extends ExceptionHandler
{
/**
* The list of the inputs that are never flashed to the session on validation exceptions.
*
* @var array<int, string>
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
/**
* Register the exception handling callbacks for the application.
*/
public function register(): void
{
// 404..
$this->renderable(function (NotFoundHttpException $e, $request) {
if ($request->is('api/*')) {
return response()->noContent(404);
}
});
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace App\Exports\Ecommerce\Product\Order;
use App\Models\Ecommerce\Product\Order\OrderItem;
use App\Models\Ecommerce\Product\Order\Status\OrderStatus;
use Maatwebsite\Excel\Concerns\Exportable;
use Maatwebsite\Excel\Concerns\FromQuery;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
class ExportOrderReport implements FromQuery, ShouldAutoSize, WithHeadings, WithMapping
{
use Exportable;
/**
* Get the start & end date to filter
*/
public function __construct(
public string $start_date,
public string $end_date
) {}
/**
* @return \Illuminate\Support\Collection
*/
public function query()
{
return OrderItem::join(
table: 'orders',
first: fn ($join) => $join->on('order_items.order_id', '=', 'orders.id')
->where('orders.status', '=', OrderStatus::COMPLETED)
->whereNull('deleted_at')
)->whereRaw('(orders.created_at >= ? AND orders.created_at <= ?)', [
$this->start_date.' 00:00:00',
$this->end_date.' 23:59:59',
]);
}
/**
* @var Order
*/
public function map($order): array
{
$orderDeliveryTime = match ($order->shipping_method) {
'express' => 'Ekspres',
default => $order->time,
};
return [
$order->order_id,
$order->name,
$orderDeliveryTime,
$order->quantity,
$order->unit_price_amount,
$order->unit_price_amount * $order->quantity,
];
}
/**
* Headings for stylesheet
*/
public function headings(): array
{
return [
'Sargyt ID',
'Ady',
'Sene',
'Sany',
'Satylan Bahasy',
'Satylan umumy bahasy',
];
}
}

View File

@@ -0,0 +1,107 @@
<?php
namespace App\Helpers\Ecommerce\Product\Filter;
use App\Rules\CommaSeparatedIntegers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class ProductFilterer
{
/*
* Query builder
*/
protected $queryBuilder;
/**
* Request
*/
protected Request $request;
/**
* Product filterer
*/
public function __construct($queryBuilder, $request)
{
$this->queryBuilder = $queryBuilder;
$this->request = $request;
}
/**
* Create "new" class via static method
*/
public static function make($queryBuilder, $request): self
{
return new self($queryBuilder, $request);
}
/**
* Validate the request
*/
public function validateRequest(): \Illuminate\Contracts\Validation\Validator
{
return Validator::make($this->request->all(), [
'ids' => ['nullable', 'string', new CommaSeparatedIntegers],
'brands' => ['nullable', 'string', new CommaSeparatedIntegers],
'categories' => ['nullable', 'string', new CommaSeparatedIntegers],
'name' => ['nullable', 'string', 'max:255'],
'min_price' => ['nullable', 'numeric'],
'max_price' => ['nullable', 'numeric'],
'backorder' => ['nullable', 'in:0,1'],
]);
}
/**
* Sort
*/
public function filter()
{
if ($this->validateRequest()->fails()) {
return $this->queryBuilder;
}
if ($this->request->filled('brands')) {
$this->queryBuilder->whereIntegerInRaw('products.brand_id', explode(',', $this->request->brands));
}
if ($this->request->filled('categories')) {
$this->queryBuilder->whereIn(
column: 'id',
values: (
fn ($query) => $query->from('product_has_relations')
->select('product_id')
->distinct('product_id')
->where('productable_type', '=', 'category')
->whereIn('productable_id', explode(',', $this->request->categories))
)
);
}
if ($this->request->filled('name')) {
$operator = $this->queryBuilder->getConnection()->getDriverName() === 'pgsql' ? 'ilike' : 'like';
$this->queryBuilder->where('products.name', $operator, $this->request->name.'%');
}
if ($this->request->filled('min_price')) {
if ($this->queryBuilder->getConnection()->getDriverName() === 'pgsql') {
$this->queryBuilder->whereRaw('products.price_amount::NUMERIC >= ?', [$this->request->float('min_price')]);
} else {
$this->queryBuilder->where('products.price_amount', '>=', $this->request->float('min_price'));
}
}
if ($this->request->filled('max_price')) {
if ($this->queryBuilder->getConnection()->getDriverName() === 'pgsql') {
$this->queryBuilder->whereRaw('products.price_amount::NUMERIC <= ?', [$this->request->float('max_price')]);
} else {
$this->queryBuilder->where('products.price_amount', '<=', $this->request->float('max_price'));
}
}
if ($this->request->filled('backorder')) {
$this->queryBuilder->where('products.backorder', $this->request->backorder);
}
return $this->queryBuilder;
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace App\Helpers\Ecommerce\Product\Sort;
use App\Rules\SortableColumnRule;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class ProductSorter
{
/*
* Query builder
*/
protected mixed $queryBuilder;
/**
* Request
*/
protected Request $request;
/**
* Allowed columns for sorting
*
* @var array<string>
*/
protected array $allowedColumns = [
'price_amount',
'created_at',
'name',
];
/**
* Constructor
*/
public function __construct($queryBuilder, $request)
{
$this->queryBuilder = $queryBuilder;
$this->request = $request;
}
/**
* Construct statically
*/
public static function make($queryBuilder, $request): self
{
return new self($queryBuilder, $request);
}
/**
* Validate the request
*/
public function validateRequest(): \Illuminate\Contracts\Validation\Validator
{
return Validator::make($this->request->all(), [
'sorting' => ['nullable', 'string', new SortableColumnRule($this->allowedColumns)],
]);
}
/**
* Sort from request
*/
public function sort()
{
if ($this->request->isNotFilled('sorting') || $this->validateRequest()->fails()) {
return $this->orderByLatest();
}
return $this->queryBuilder->orderBy($this->getColumn(), $this->getSortType());
}
/**
* Get sort column
*/
public function getColumn(): string
{
return explode('-', $this->request->sorting)[0];
}
/**
* Get sort type
*/
public function getSortType(): string
{
$sortType = explode('-', $this->request->sorting)[1];
return $sortType === 'descending' ? 'DESC' : 'ASC';
}
/**
* Order by latest (created_at)
*/
public function orderByLatest(): mixed
{
return $this->queryBuilder->latest();
}
}

396
app/Helpers/helpers.php Normal file
View File

@@ -0,0 +1,396 @@
<?php
use App\Models\Auth\Verification;
use App\Models\Ecommerce\Channel\Channel;
use App\Models\Ecommerce\Product\Inventory\Inventory;
use App\Repositories\Ecommerce\Product\Barcode\BarcodeRepository;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Route;
if (! function_exists('modules_path')) {
/**
* Single translation for all locales
*/
function modules_path(string $path = ''): string
{
return app_path('Modules/'.$path);
}
}
if (! function_exists('translatable')) {
/**
* Single translation for all locales
*/
function translatable(string $text): array
{
return collect(config('app.locales'))->map(fn ($locale) => $text)->toArray();
}
}
if (! function_exists('removeWhiteSpace')) {
/**
* Remove white sapce from string
*/
function removeWhiteSpace(string $text): string
{
return preg_replace('/\s+/', '', $text);
}
}
if (! function_exists('temp_cache')) {
/**
* Temprory cache via array driver
*/
function temp_cache(string|array|null $key): mixed
{
if (is_string($key)) {
return cache()->get($key);
}
if (is_array($key)) {
return cache()->put(
key: $key[0],
value: $key[1]
);
}
return cache();
}
}
if (! function_exists('sendSMS')) {
/**
* Send a sms
*/
function sendSMS(string|int $phone, string|int $message): mixed
{
if (! app()->isProduction()) {
return [];
}
return [];
// $response = Http::withToken('WsTEJTBGlQiJ')
// ->retry(
// times: 3,
// sleepMilliseconds: 50,
// throw: false,
// when: function (Exception $exception, PendingRequest $request) {
// Log::channel('sms_api_error')
// ->error('Exception: ', [
// 'message' => $exception->getMessage(),
// 'line' => $exception->getLine(),
// ]);
// return true;
// })
// ->post('https://sms.post.gov.tm/api/clients/sms/create', [
// 'phone' => '993'.$phone,
// 'content' => $message,
// ]);
// return $response->json();
}
}
if (! function_exists('sendSMSVerification')) {
/**
* Send a sms verification code
*
* @return \App\Models\Verification | null
*/
function sendSMSVerification(string|int $phone_number): ?Verification
{
/* for apple testing */
$phone_code = ($phone_number == '61126667') ? 77777 : rand(10000, 99999);
$verification = Verification::where(['username' => $phone_number])->first();
$verification
? $verification->update(['code' => $phone_code])
: Verification::create(['username' => $phone_number, 'code' => $phone_code]);
sendSMS($phone_number, 'Tassyklaýyş belgi: '.$phone_code);
return $verification;
}
}
if (! function_exists('randomHex')) {
/**
* Generate random hex
*/
function randomHex(): string
{
$chars = 'ABCDEF0123456789';
$color = '#';
for ($i = 0; $i < 6; $i++) {
$color .= $chars[rand(0, strlen($chars) - 1)];
}
return $color;
}
}
if (! function_exists('phoneMaskFormat')) {
/**
* Phone number mask
*/
function phoneMaskFormat(): string
{
return '+(\\9\\93)-99-99-99-99';
}
}
if (! function_exists('unmask_phone')) {
/**
* Unmask phone from TM code +(993)-6x-xx-xx-xx to 6xxxxxxx
*/
function unmask_phone(string|int|float $phone): string
{
return str_replace(['+(993)', '-'], '', $phone);
}
}
if (! function_exists('mask_phone')) {
/**
* Mask phone for TM code from 6xxxxxxx to +(993)-6x-xx-xx-xx
*/
function mask_phone(string|int|float $phone): string
{
// Split the number into an array of every two digits
$chunks = array_chunk(str_split($phone, 2), 1);
// Collapse the array chunks into a string with dashes
return '+(993)-'.implode('-', array_map('implode', $chunks));
}
}
if (! function_exists('tmpostChannel')) {
/**
* Default channel
*
* @return \App\Models\Shop\Channel
*/
function tmpostChannel(): Channel
{
Cache::rememberForever('tmpostChannel', fn () => Channel::tmpostDefault());
return Cache::get('tmpostChannel');
}
}
if (! function_exists('tmpostDefaultInventory')) {
/**
* Default inventory id
*/
function tmpostDefaultInventory(): mixed
{
Cache::rememberForever('tmpostDefaultInventory', fn () => Inventory::tmpostDefault());
return Cache::get('tmpostDefaultInventory');
}
}
if (! function_exists('round_up')) {
/**
* Round up the float
*/
function round_up(string|int|float $number, int $precision = 2): float
{
$fig = (int) str_pad('1', $precision, '0');
return ceil($number * $fig) / $fig;
}
}
if (! function_exists('routeName')) {
/**
* Current route name
*/
function routeName(): ?string
{
return Route::currentRouteName();
}
}
if (! function_exists('routeIs')) {
/**
* Check if current route matches with given datas
*/
function routeIs(string|array $name): bool
{
return is_string($name)
? $name === routeName()
: in_array(routeName(), $name);
}
}
/**
* Check if route is product related
*/
function routeIsProductRelated(): bool
{
return routeIs([
'web.categories.products',
'web.brands.products',
'web.collections.products',
'web.entrepreneurs.show',
'web.search.index',
]);
}
if (! function_exists('keyValueExistsInArray')) {
/**
* Check "key-value" exists in array
*
* @param int $key
* @param int $value
*/
function keyValueExistsInArray(array $datas, string|int $key, string|int $value): bool
{
foreach ($datas as $data) {
if (isset($data[$key]) && $data[$key] === $value) {
return true;
}
}
return false;
}
}
if (! function_exists('productTaxForCategory')) {
/**
* Product tax for categories it has been related
* As of now, the highest category tax is taken
*/
function productTaxForCategory(array|Collection $categories): int
{
return DB::table('categories')
->whereIntegerInRaw('id', $categories)
->select(DB::raw('MAX(tax_percentage::integer) as tax'))
->pluck('tax')[0] ?? 1;
}
}
if (! function_exists('validateCommaSeperated')) {
/**
* Validates a comma-separated string of fields.
*
* - Validates the format of each field (alphanumeric or underscore).
* - Optionally checks fields against a model's fillable property.
*
* @param string|null $value The comma-separated string to validate.
* @param string|null $model The model class to check fields against (optional).
* @return string[] An array of validated fields.
*/
function validateCommaSeperated(?string $value, mixed $model = null): array
{
if (is_null($value)) {
return [];
}
$values = explode(',', removeWhiteSpace($value));
$validated = [];
$fields = [];
if (! is_null($model)) {
$fields = (new $model)->getFillable();
}
foreach ($values as $value) {
if (empty($value) || is_numeric($value)) {
continue;
}
if (! validator(data: ['key' => $value], rules: ['key' => ['alpha_dash:ascii']])->fails()) {
if (! empty($fields)) {
if (in_array($value, $fields)) {
$validated[] = $value;
}
continue;
}
$validated[] = $value;
}
}
return $validated;
}
}
if (! function_exists('orderAdminNumber')) {
/**
* Order admin phone number
*/
function orderAdminNumber(): int
{
return 65728952;
}
}
if (! function_exists('barcodeGeneratorRoute')) {
/**
* Barcode generator route
*/
function barcodeGeneratorRoute(): string
{
return BarcodeRepository::imageGeneratorRoute();
}
}
if (! function_exists('calculateProductPriceAmount')) {
/**
* Calculate product price amount
*/
function calculateProductPriceAmount(int|float $price, int $tax): float
{
return round_up(($price / (100 - $tax)) * 100);
}
}
/**
* Create halkbank order
*
* @param string $price
* @return array{status: string, url: string|null}
*/
function createHalkbankOrder($price = 123): array
{
$orderNumber = date('dmyHis');
$paymentResponse = Http::get('https://mpi.gov.tm/payment/rest/register.do', [
'orderNumber' => $orderNumber,
'amount' => $price,
'currency' => 934,
'language' => 'ru',
'userName' => 516122500260,
'password' => 'MrZsO9wfgWOBjf4',
'returnUrl' => route('online-payment-store'),
'pageView' => 'DESKTOP',
'description' => 'Kart tölegi',
])->onError(function ($response) {
Log::error('Payment error', [
'response' => [
'body' => $response->body(),
],
]);
});
if ($paymentResponse->failed()) {
return [
'status' => 'failed',
'url' => '',
];
}
return [
'status' => 'success',
'url' => $paymentResponse['formUrl'],
];
}

View File

@@ -0,0 +1,88 @@
<?php
namespace App\Http\Controllers\Api\System\VersionManagement;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\System\VersionManagement\CheckForUpdateRequest;
use App\Models\System\VersionManagement\AppVersion;
use Illuminate\Http\JsonResponse;
class AppVersionController extends Controller
{
/**
* Check for app updates
*
* This api should be triggered when the app is **launched**, and **current version** and **operating system** should be sent in body. It should check if there is any update available for the app.\
* **Handle Response**:\
* * **Update Required**: If the update is critical/mandatory, show a **blocking modal**. The user cannot dismiss it and must click a button to go to the App Store or Google Play Store.\
* * **Update Optional**: If a new version is available but not mandatory, show a **dismissible notification** or modal suggesting the user to update for a better experience.\
* * **Latest Version**: If the app is up-to-date, proceed to the app home screen silently.\
* * **Version Not Found**: If the version is not recognized (e.g., development build), do not show any update prompts.\
*/
public function checkForUpdate(CheckForUpdateRequest $request): JsonResponse
{
$app_version = AppVersion::latest()->where('os', $request->os)->first();
if (! $app_version) {
return $this->versionNotFound();
}
if ($request->version === $app_version->version) {
return $this->latestVersion($app_version);
}
if ($request->version < $app_version->version && $app_version->important) {
return $this->requiredToUpdate($app_version);
}
if ((int) $request->version < (int) $app_version->version) {
return $this->optionalToUpdate($app_version);
}
return $this->versionNotFound();
}
/**
* Latest version
*/
public function latestVersion(AppVersion $app_version): JsonResponse
{
return response()->json([
'update' => 'latest',
'notes' => sprintf('You are using the latest version of the app (%s).', $app_version->version),
]);
}
/**
* Required to update
*/
public function requiredToUpdate(AppVersion $app_version): JsonResponse
{
return response()->json([
'update' => 'required',
'notes' => sprintf('A critical update is available. Please update to the latest version (%s).', $app_version->version),
]);
}
/**
* Update not required, but should be
*/
public function optionalToUpdate(AppVersion $app_version): JsonResponse
{
return response()->json([
'update' => 'optional',
'notes' => sprintf('A new version is available. Please update to the latest version (%s).', $app_version->version),
]);
}
/**
* App version not found
*/
public function versionNotFound(): JsonResponse
{
return response()->json([
'update' => 'version-not-found',
'notes' => 'The version is not recognized (e.g., development build).',
]);
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Models\Shop\Product\Attribute;
class AttributeController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
return response()->rest(
Attribute::with('values:id,key,attribute_id,value,position')
->where('is_enabled', true)
->get(['id', 'slug', 'name', 'type', 'category_id', 'is_enabled'])
);
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace App\Http\Controllers\Api\V1\Auth\Register;
use Illuminate\Foundation\Http\FormRequest;
class AuthRegisterRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
*/
public function rules(): array
{
return [
/**
* @example 61929248
*/
'phone_number' => ['required', 'integer', 'between:61000000,71999999', 'unique:users,phone_number'],
/**
* @example Nurmuhammet Allanov
*/
'name' => ['required', 'string', 'max:255'],
/**
* @example 75 3rd Ave, New York, NY 10003, USA
*/
'address' => ['required', 'string', 'max:255'],
];
}
/**
* Handle a passed validation attempt.
*/
protected function passedValidation(): void
{
$name = explode(' ', $this->name);
$this->merge([
'first_name' => $name[0],
'last_name' => count($name) > 1 ? implode(' ', array_slice($name, 1)) : '',
]);
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Api\V1\Auth\Register\AuthRegisterRequest;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\V1\Auth\AuthLoginRequest;
use App\Http\Requests\Api\V1\Auth\AuthVerifyRequest;
use App\Models\User;
use App\Repositories\UserRepository;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Str;
class AuthController extends Controller
{
/**
* Guest token (walk-in-user)
*
* Use when user visits website/app for the first time, and save the token in cache.
*/
public function guestToken(): JsonResponse
{
return response()->rest(
data: UserRepository::guestUser()->createToken(Str::random(20))->plainTextToken,
code: 201
);
}
/**
* Register user
*
* Register a new user and send a verification code to their phone number. Then make another request to verification route.
*/
public function register(AuthRegisterRequest $request): JsonResponse
{
UserRepository::registerUser($request)();
sendSMSVerification($request->phone_number);
return response()->rest(
data: [],
code: 201,
message: sprintf('%s: %s', __('Verification code sent to'), $request->phone_number)
);
}
/**
* Login
*
* Send a verification code to the phone number. Then make another request to verify route.
*/
public function login(AuthLoginRequest $request): JsonResponse
{
sendSMSVerification($request->phone_number);
return response()->rest(
data: [],
code: 201,
message: sprintf('%s: %s', __('Verification code sent to'), $request->phone_number)
);
}
/**
* Verify the code
*
* After verification, bearer token will be returned.
*/
public function verify(AuthVerifyRequest $request): JsonResponse
{
$user = User::where('phone_number', $request->phone_number)->firstOr(UserRepository::registerUser($request));
return response()->rest(
data: $user->createToken(bin2hex(random_bytes(20)))->plainTextToken,
code: 201
);
}
/**
* (Auth)* Delete user
*
* @authenticated
*/
public function delete(): JsonResponse
{
auth()->user()->delete();
return response()->rest();
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Http\Controllers\Api\V1\Banner;
use App\Http\Controllers\Api\V1\Banner\Requests\BannerIndexRequest;
use App\Http\Controllers\Api\V1\Banner\Resources\BannerResource;
use App\Http\Controllers\Controller;
use App\Models\CMS\Media\Banner;
use Illuminate\Http\JsonResponse;
class BannerController extends Controller
{
/**
* (Media) Banners (index)
*/
public function index(BannerIndexRequest $request): JsonResponse
{
return response()->rest_paginate(
BannerResource::collection(
Banner::with('media')
->where('is_visible', true)
->when($request->place, fn ($query, $place) => $query->where('place', $place))
->when($request->app, fn ($query, $app) => $query->where('app', $app))
->ordered()
->simplePaginate(
perPage: $request->perPage,
columns: $request->fields,
)
)
);
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Http\Controllers\Api\V1\Banner\Requests;
use App\Models\CMS\Media\Banner;
use App\Models\System\Settings\OS;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class BannerIndexRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
*/
public function rules(): array
{
return [
'place' => ['nullable', 'string', Rule::in(array_keys(Banner::places()))],
'app' => ['nullable', 'string', Rule::in(array_keys(OS::apps()))],
'perPage' => ['nullable', 'integer'],
'fields' => ['nullable', 'string'],
];
}
/**
* Handle a passed validation attempt.
*/
protected function passedValidation(): void
{
$fields = [];
if ($this->fields) {
$sanitezedFields = validateCommaSeperated($this->fields, Banner::class);
$fields['fields'] = ! empty($sanitezedFields) ? $sanitezedFields : ['*'];
} else {
$fields['fields'] = ['*'];
}
$this->merge([
'perPage' => $this->perPage ?: 6,
...$fields,
]);
}
/**
* Get the error messages for the defined validation rules.
*
* @return array<string, string>
*/
public function messages(): array
{
return [
'place.in' => sprintf('Valid sources: %s', implode(', ', array_keys(OS::apps()))),
'app.in' => sprintf('Valid sources: %s', implode(', ', array_keys(OS::apps()))),
];
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace App\Http\Controllers\Api\V1\Banner\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class BannerResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->whenHas('id'),
'app' => $this->whenHas('app'),
'title' => $this->whenHas('title'),
'place' => $this->whenHas('place'),
'link' => $this->whenHas('link'),
'resource_type' => $this->whenHas('resource_type'),
'resource_id' => $this->whenHas('resource_id'),
'thumbnail' => $this->thumbnail('350x350'),
'related_resources' => $this->when(
condition: $this->shouldIncludeRelatedResources($request),
value: fn () => $this->relatedResources(),
),
];
}
/**
* Check if should include related resources
*/
protected function shouldIncludeRelatedResources(Request $request): bool
{
if (! $request->fields) {
$request->merge(['fields' => ['*']]);
}
if ($request->fields[0] === '*') {
return true;
}
return in_array(['resource_type', 'resource_id', 'place'], $request->fields);
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace App\Http\Controllers\Api\V1\Brand;
use App\Http\Controllers\Api\V1\Brand\Resources\BrandResource;
use App\Http\Controllers\Api\V1\Product\Resources\ProductResource;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\V1\Brand\BrandIndexRequest;
use App\Http\Requests\Api\V1\Brand\BrandProductsRequest;
use App\Models\Ecommerce\Product\Brand\Brand;
use App\Repositories\Ecommerce\Product\ProductRepository;
use Illuminate\Http\JsonResponse;
class BrandController extends Controller
{
/**
* Brands (index)
*/
public function index(BrandIndexRequest $request): JsonResponse
{
return response()->rest(
BrandResource::collection(
Brand::query()
->with('media')
->enabled()
->ordered()
->when($request->input('type'), fn ($query, $type) => $query->where('type', $type))
->get()
)
);
}
/**
* Brands (show)
*
* @param App\Models\Ecommerce\Product\Brand\Brand $brand
*/
public function show(Brand $brand): JsonResponse
{
$brand->load('media');
return response()->rest(new BrandResource($brand));
}
/**
* Brands (products)
*/
public function products(BrandProductsRequest $request, Brand $brand): JsonResponse
{
return response()->rest_paginate(
ProductResource::collection(
ProductRepository::make($request)
->queryAsFromResource($brand)
->applyBasicQueries()
->applyFilters()
->applySorting()
->simplePaginate()
)
);
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Http\Controllers\Api\V1\Brand\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class BrandMediaResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'thumbnail' => $this->getUrl('thumb200x200'),
'images_400x400' => $this->getUrl('thumb400x400'),
'images_720x720' => $this->getUrl('thumb720x720'),
'images_800x800' => $this->getUrl('thumb800x800'),
'images_1200x1200' => $this->getUrl('thumb1200x1200'),
];
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Http\Controllers\Api\V1\Brand\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class BrandResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'slug' => $this->slug,
'type' => $this->type,
'description' => $this->description,
'media' => BrandMediaResource::collection($this->whenLoaded('media')),
];
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Http\Controllers\Api\V1\Carousel;
use App\Http\Controllers\Api\V1\Carousel\Requests\CarouselIndexRequest;
use App\Http\Controllers\Api\V1\Carousel\Resources\CarouselResource;
use App\Http\Controllers\Controller;
use App\Models\CMS\Media\Carousel;
use Illuminate\Http\JsonResponse;
class CarouselController extends Controller
{
/**
* (Media) Carousels (index)
*/
public function index(CarouselIndexRequest $request): JsonResponse
{
return response()->rest_paginate(
CarouselResource::collection(
Carousel::with('media')
->where('is_visible', true)
->when($request->place, fn ($query, $place) => $query->where('place', $place))
->when($request->app, fn ($query, $app) => $query->where('app', $app))
->ordered()
->simplePaginate(
perPage: $request->perPage,
columns: $request->fields,
)
)
);
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace App\Http\Controllers\Api\V1\Carousel\Requests;
use App\Models\CMS\Media\Carousel;
use App\Models\System\Settings\OS;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class CarouselIndexRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
*/
public function rules(): array
{
return [
'place' => ['nullable', 'string', Rule::in(array_keys(
Carousel::places()
))],
'perPage' => ['nullable', 'integer'],
'fields' => ['nullable', 'string'],
];
}
/**
* Handle a passed validation attempt.
*/
protected function passedValidation(): void
{
$fields = [];
if ($this->fields) {
$sanitezedFields = validateCommaSeperated($this->fields, Carousel::class);
$fields['fields'] = ! empty($sanitezedFields) ? $sanitezedFields : ['*'];
} else {
$fields['fields'] = ['*'];
}
$this->merge([
'perPage' => $this->perPage ?: 6,
...$fields,
]);
}
/**
* Get the error messages for the defined validation rules.
*
* @return array<string, string>
*/
public function messages(): array
{
return [
'place.in' => sprintf('Valid sources: %s', implode(', ', array_keys(OS::apps()))),
'app.in' => sprintf('Valid sources: %s', implode(', ', array_keys(OS::apps()))),
];
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Http\Controllers\Api\V1\Carousel\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class CarouselResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->whenHas('id'),
'title' => $this->whenHas('title'),
'place' => $this->whenHas('place'),
'app' => $this->whenHas('app'),
'link' => $this->whenHas('link'),
'resource_id' => $this->whenHas('resource_id'),
'resource_type' => $this->whenHas('resource_type'),
'thumbnail' => $this->thumbnail('650x650'),
'image' => $this->getFirstMediaUrl('main'),
];
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Api\V1\Product\Resources\ProductResource;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\V1\Cart\CartRemoveRequest;
use App\Http\Requests\Api\V1\Cart\CartStoreRequest;
use Illuminate\Http\JsonResponse;
class CartController extends Controller
{
/**
* (*) Users carts (index)
*/
public function index(): JsonResponse
{
$cartItems = auth()->user()->carts()
->with(['product' => ['media', 'brand']])
->orderBy('cart_items.id', 'desc')
->get()
->filter(function ($cartItem) {
if ($cartItem->product->stock < $cartItem->product_quantity) {
if ($cartItem->product->stock >= 1) {
$cartItem->update([
'product_quantity' => $cartItem->product->stock,
]);
} else {
$cartItem->delete();
return false;
}
}
return true;
});
return response()->rest(
$cartItems->map(fn ($cartItem) => [
'id' => $cartItem->id,
'user_id' => $cartItem->user_id,
'product_id' => $cartItem->product_id,
'product_quantity' => $cartItem->product_quantity,
'created_at' => $cartItem->created_at,
'updated_at' => $cartItem->updated_at,
'product' => new ProductResource($cartItem->product),
])
);
}
/**
* (*) Store new cart (store)
*/
public function store(CartStoreRequest $request): JsonResponse
{
auth()->user()->carts()->updateOrInsert(
['user_id' => auth()->id(), 'product_id' => $request->product_id],
['product_quantity' => $request->product_quantity]
);
return response()->rest(message: 'Cart item added or updated successfully', code: 201);
}
/**
* (*) Remove item from cart (patch)
*/
public function remove(CartRemoveRequest $request): JsonResponse
{
auth()->user()->carts()->where('product_id', $request->product_id)->delete();
return response()->rest(message: 'Cart item removed successfully', code: 200);
}
/**
* (*) Delete all user's carts (delete)
*/
public function destroy(): JsonResponse
{
auth()->user()->carts()->delete();
return response()->rest(message: "All user's carts deleted successfully", code: 200);
}
}

View File

@@ -0,0 +1,73 @@
<?php
namespace App\Http\Controllers\Api\V1\Category;
use App\Http\Controllers\Api\V1\Category\Resources\CategoryResource;
use App\Http\Controllers\Api\V1\Product\Resources\ProductResource;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\V1\Category\CategoryIndexRequest;
use App\Http\Requests\Api\V1\Product\BasicProductIndexRequest;
use App\Models\Ecommerce\Product\Category\Category;
use App\Repositories\Ecommerce\Product\Category\CategoryRepository;
use App\Repositories\Ecommerce\Product\ProductRepository;
use Illuminate\Http\JsonResponse;
class CategoryController extends Controller
{
/**
* Categories (index)
*/
public function index(CategoryIndexRequest $request): JsonResponse
{
return response()->rest(
CategoryResource::collection(
CategoryRepository::make($request)
->applyBasicQueries()
->applyFilters()
->get()
)
);
}
/**
* Categories (show)
*/
public function show(Category $category): JsonResponse
{
$category->load(['media', 'children']);
return response()->rest(new CategoryResource($category));
}
/**
* Categories (children)
*/
public function children(Category $category): JsonResponse
{
return response()->rest(
CategoryResource::collection(
$category->children()
->enabled()
->ordered()
->get()
)
);
}
/**
* Brands (products)
*/
public function products(BasicProductIndexRequest $request, Category $category): JsonResponse
{
return response()->rest_paginate(
ProductResource::collection(
ProductRepository::make($request)
->queryAsFromResource($category)
->applyBasicQueries()
->applyFilters()
->applySorting()
->simplePaginate()
)
);
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Http\Controllers\Api\V1\Category\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class CategoryMediaResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'thumbnail' => $this->getUrl(),
'images_400x400' => $this->getUrl('thumb400x400'),
'images_720x720' => $this->getUrl('thumb720x720'),
'images_800x800' => $this->getUrl('thumb800x800'),
'images_1200x1200' => $this->getUrl('thumb1200x1200'),
];
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Http\Controllers\Api\V1\Category\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class CategoryResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'parent_id' => $this->parent_id,
'name' => $this->name,
'slug' => $this->slug,
'group' => $this->type,
'description' => $this->description,
'color' => $this->color,
'media' => CategoryMediaResource::collection($this->whenLoaded('media')),
'children' => static::collection($this->whenLoaded('children')),
];
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace App\Http\Controllers\Api\V1\Channel;
use App\Http\Controllers\Api\V1\Channel\Requests\ChannelIndexRequest;
use App\Http\Controllers\Api\V1\Product\Resources\ProductResource;
use App\Http\Controllers\Controller;
use App\Http\Resources\Api\V1\Channel\ChannelResource;
use App\Models\Ecommerce\Channel\Channel;
use App\Repositories\Ecommerce\Product\ProductRepository;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ChannelController extends Controller
{
/**
* Channels (index)
*/
public function index(ChannelIndexRequest $request): JsonResponse
{
return response()->rest_paginate(
ChannelResource::collection(
Channel::query()
->with('media')
->where('is_visible', true)
->ordered()
->simplePaginate(
perPage: $request->perPage,
columns: $request->fields,
)
)
);
}
/**
* Channels (show)
*/
public function show(Channel $channel): JsonResponse
{
$channel->load('media');
return response()->rest(new ChannelResource($channel));
}
/**
* Channels (products)
*/
public function products(Request $request, Channel $channel): JsonResponse
{
return response()->rest_paginate(
ProductResource::collection(
ProductRepository::make($request)
->queryAsFromResource($channel)
->applyBasicQueries()
->applyFilters()
->applySorting()
->simplePaginate()
)
);
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Http\Controllers\Api\V1\Channel\Requests;
use App\Models\Ecommerce\Channel\Channel;
use Illuminate\Foundation\Http\FormRequest;
class ChannelIndexRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
*/
public function rules(): array
{
return [
'perPage' => ['nullable', 'integer'],
'fields' => ['nullable', 'string'],
];
}
/**
* Handle a passed validation attempt.
*/
protected function passedValidation(): void
{
$fields = [];
if ($this->fields) {
$sanitezedFields = validateCommaSeperated($this->fields, Channel::class);
$fields['fields'] = ! empty($sanitezedFields) ? $sanitezedFields : ['*'];
} else {
$fields['fields'] = ['*'];
}
$this->merge([
'perPage' => $this->perPage ?: 6,
...$fields,
]);
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace App\Http\Controllers\Api\V1\Collection;
use App\Http\Controllers\Api\V1\Collection\Resources\CollectionResource;
use App\Http\Controllers\Api\V1\Product\Resources\ProductResource;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\V1\Product\BasicProductIndexRequest;
use App\Models\Ecommerce\Product\Collection\Collection;
use App\Repositories\Ecommerce\Product\ProductRepository;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class CollectionController extends Controller
{
/**
* Collections (index).
*/
public function index(): JsonResponse
{
return response()->rest(
CollectionResource::collection(
Collection::with('media')->where('is_visible', true)->ordered()->get()
)
);
}
/**
* Paginated
*/
public function paginated(Request $request)
{
$perPage = $request->perPage ?: 6;
return response()->rest_paginate(
CollectionResource::collection(
Collection::with('media')->where('is_visible', true)->ordered()->simplePaginate($perPage)
)
);
}
/**
* Collections (show)
*/
public function show(Collection $collection): JsonResponse
{
$collection->load('media');
return response()->rest(new CollectionResource($collection));
}
/**
* Collections (products)
*/
public function products(BasicProductIndexRequest $request, Collection $collection)
{
return response()->rest_paginate(
ProductResource::collection(
ProductRepository::make($request)
->queryAsFromResource($collection)
->applyBasicQueries()
->applyFilters()
->applySorting()
->simplePaginate()
)
);
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Http\Controllers\Api\V1\Collection\Resources;
use App\Http\Resources\MediaResource;
use Illuminate\Http\Resources\Json\JsonResource;
class CollectionResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'slug' => $this->slug,
'description' => $this->description,
'sort_order' => $this->sort_order,
'seo_title' => $this->seo_title,
'seo_description' => $this->seo_description,
'media' => MediaResource::collection($this->whenLoaded('media')),
];
}
}

View File

@@ -0,0 +1,102 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Models\Shop\Product\Product;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
class CommentController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
return response()->rest(
DB::table('comments')->where('user_id', auth()->id())->get()
);
}
/**
* Store a newly created resource in storage.
*
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'product_id' => ['required', 'integer', 'exists:products,id'],
'comment' => ['required', 'string'],
]);
if ($validator->fails()) {
return response()->rest($validator->messages()->get('*'), 400, 'Validation failed');
}
$user = auth()->user();
$product = Product::find($request->product_id);
$product->commentAsUser($user, strip_tags($request->comment));
return response()->rest();
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
//
}
/**
* Update the specified resource in storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request)
{
$validator = Validator::make($request->all(), [
'comment_id' => ['required', 'integer', 'exists:comments,id'],
'comment' => ['required', 'string'],
]);
if ($validator->fails()) {
return response()->rest($validator->messages()->get('*'), 400, []);
}
DB::table('comments')->where('id', $request->comment_id)->update([
'comment' => strip_tags($request->comment),
]);
return response()->rest();
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy(Request $request)
{
$validator = Validator::make($request->all(), ['comment_id' => ['required', 'integer']]);
if ($validator->fails()) {
return response()->rest($validator->messages()->get('*'), 400, []);
}
DB::table('comments')->where('id', $request->comment_id)->delete();
return response()->rest();
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\V1\Forms\ContactUS\ContactUSStoreRequest;
use App\Models\CMS\Forms\ContactUS;
use Illuminate\Http\JsonResponse;
class ContactMessageController extends Controller
{
/**
* (Marketing)* Contact us
*/
public function store(ContactUSStoreRequest $request): JsonResponse
{
ContactUS::create([
'phone' => $request->phone,
'title' => $request->title,
'content' => $request->content,
'type' => $request->type,
'user_id' => auth()->id(),
]);
return response()->rest([], 201);
}
}

View File

@@ -0,0 +1,168 @@
<?php
namespace App\Http\Controllers\Api\V1\Entrepreneur;
use App\Http\Controllers\Controller;
use App\Models\Auth\Verification;
use App\Models\Ecommerce\Channel\Channel;
use App\Models\User;
use Illuminate\Auth\Events\Verified;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
class EntrepreneurAuthController extends Controller
{
public function register(Request $request)
{
$validator = Validator::make($request->all(), [
'first_name' => ['required', 'string'],
'phone_number' => ['required', 'integer', 'between:61000000,71999999', 'unique:users,phone_number'],
'email' => ['required', 'email', 'unique:users,email'],
'password' => ['required'],
'region' => ['required', 'string', 'in:mr,ag,ah,dz,lb,bn'],
'patent_data' => ['nullable'],
]);
if ($validator->fails()) {
return response()->rest($validator->messages()->get('*'), 400, 'Wrong credentials');
}
DB::transaction(function () use ($request) {
$user = User::create([
'first_name' => $request->first_name,
'last_name' => ' ',
'email' => $request->email,
'password' => bcrypt($request->password),
'phone_number' => $request->phone_number,
]);
$user->documents()->create([
'patent_data' => str_replace('public/', '', $request->file('patent_data')?->store('public/entrepreneur/patent_data') ?? 'public/'),
]);
sendSMSVerification($request->phone_number);
// Verification::updateOrCreate(['username' => $request->email, 'code' => 12345]);
});
return response()->rest();
}
public function verifyPhoneNumber(Request $request)
{
$validator = Validator::make($request->all(), ['phone_number' => 'required|integer|between:61000000,65999999', 'code' => 'required|string']);
if ($validator->fails()) {
return response()->rest($validator->messages()->get('*'), 400, 'Wrong credentials');
}
$verification = Verification::where('username', $request->phone_number)->where('code', $request->code)->first();
if (! $verification) {
return response()->rest([], 400, 'Wrong credentials');
}
User::where('phone_number', $request->phone_number)->update(['phone_number_verified_at' => now()]);
return response()->rest();
}
public function verifyEmail(Request $request)
{
// email should be validated for deleted_at to
$validator = Validator::make($request->all(), ['email' => ['required', 'string', 'email', 'exists:users,email'], 'code' => ['required', 'string']]);
if ($validator->fails()) {
return response()->rest($validator->messages()->get('*'), 400, 'Wrong credentials');
}
$verfication = Verification::where('username', $request->email)->where('code', $request->code)->first();
if (! $verfication) {
return response()->rest([], 400, 'Wrong credentials');
}
$user = User::where('email', $request->email)->first();
$user->email_verified_at = now();
$user->save();
event(new Verified($user));
return response()->rest();
}
public function finalize(Request $request)
{
$validator = Validator::make($request->all(), [
'email' => ['required', 'string', 'email', 'exists:users,email'],
'password' => ['required', 'string'],
]);
if ($validator->fails()) {
return response()->rest($validator->messages()->get('*'), 400, 'Wrong credentials');
}
if (! auth()->attempt(['email' => $request->email, 'password' => $request->password])) {
return response()->rest([], 400, 'Wrong credentials');
}
$user = User::where('email', $request->email)->first();
if (! $user || ! Hash::check($request->password, $user->password)) {
return response()->rest([], 400, 'Failed');
}
if (! $user->hasRole('vendor')) {
$user->assignRole('vendor');
// User
$name = $user->first_name;
$channel = Channel::create([
'name' => $name,
'slug' => Str::slug($name).'_'.random_int(10000, 9999999),
'description' => '',
'timezone' => 'asia/ashgabat',
'url' => url('/'),
'is_default' => true,
'channelables_type' => 'App\Models\User',
'channelables_id' => $user->id,
'is_visible' => true,
]);
$channel->inventories()->create([
'name' => $name,
'code' => Str::slug($name).'_'.random_int(10000, 9999999),
'region' => 'ag',
'shareable' => false,
'is_default' => true,
]);
}
$bearerToken = $user->createToken(bin2hex(random_bytes(20)))->plainTextToken;
return response()->rest($bearerToken, 201);
}
public function login(Request $request)
{
$validator = Validator::make($request->all(), ['email' => 'required|string|email', 'password' => 'required|string']);
if ($validator->fails()) {
return response()->rest($validator->messages()->get('*'), 400, 'Absolutely wrong credentials.');
}
$user = User::where('email', $request->email)->first();
if (! $user || ! Hash::check($request->password, $user->password)) {
return response()->rest([], 400, 'Failed');
}
$bearerToken = $user->createToken(bin2hex(random_bytes(20)))->plainTextToken;
return response()->rest($bearerToken, 201);
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace App\Http\Controllers\Api\V1\Entrepreneur;
use App\Http\Controllers\Controller;
use App\Http\Resources\Api\V1\Vendor\Order\VendorOrderIndexResource;
use App\Http\Resources\Api\V1\Vendor\Order\VendorOrderShowResource;
use App\Models\Ecommerce\Product\Order\Order;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class EntrepreneurOrderController extends Controller
{
public function index(Request $request)
{
$order_ids = DB::table('order_items')
->where('channel_id', auth()->user()->channel()->id)
->distinct()
->pluck('order_id');
$perPage = $request->input('perPage') ?? 20;
$page = $request->input('page') ?? 1;
return response()->rest_paginate(
VendorOrderIndexResource::collection(
Order::query()
->whereIntegerInRaw('orders.id', $order_ids)
->with('paymentType')
->latest()
->paginate($perPage, ['*'], 'page', $page)
)
);
}
public function show(Order $order)
{
$order->load(['items' => ['product' => ['media']]]);
return new VendorOrderShowResource($order);
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Http\Controllers\Api\V1\Entrepreneur\Requests;
use Illuminate\Foundation\Http\FormRequest;
class VendorProductStoreRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
// essentials...
'name' => ['required', 'string', 'max:255'],
'description' => ['nullable', 'string', 'max:255'],
'files' => ['nullable'],
// prices...
'cost_amount' => ['required', 'numeric'],
'old_price_amount' => ['nullable', 'numeric', 'gt:cost_amount'],
// relationships...
// 'integer', 'exists:brands,id'
'brand_id' => ['nullable'],
'category_ids' => ['required', 'array'],
'collection_ids' => ['nullable', 'array'],
// inventories...
'stock' => ['required', 'integer', 'min:1'],
'sku' => ['nullable', 'string', 'max:255'],
'barcode' => ['nullable', 'string', 'max:255', 'unique:products,barcode'],
];
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Http\Controllers\Api\V1\Entrepreneur\Requests;
use Illuminate\Foundation\Http\FormRequest;
class VendorProductUpdateRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
// essentials...
'name' => ['required', 'string', 'max:255'],
'description' => ['nullable', 'string', 'max:255'],
'new_imgs' => ['nullable'],
'deleted_images' => ['nullable'],
// prices...
'cost_amount' => ['required', 'numeric'],
'old_price_amount' => ['nullable', 'numeric', 'gt:cost_amount'],
// relationships...
'brand_id' => ['nullable', 'integer', 'exists:brands,id'],
'category_ids' => ['required', 'array'],
'collection_ids' => ['nullable', 'array'],
// inventories...
'stock' => ['required', 'integer', 'min:1'],
'sku' => ['nullable', 'string', 'max:255'],
'barcode' => ['nullable', 'string', 'max:255'],
];
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Http\Controllers\Api\V1\Entrepreneur;
use App\Http\Controllers\Controller;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
class VendorMetricsController extends Controller
{
public function index()
{
$channel = auth()->user()->channel();
$products_count = $channel->products()->count();
$order_items = DB::table('order_items')->where('channel_id', $channel->id)->count();
$order_items_today = DB::table('order_items')->where('channel_id', $channel->id)->whereDate('created_at', Carbon::today())->count();
return response()->rest([
'products_count' => $products_count,
'order_items_count' => $order_items,
'order_items_today_count' => $order_items_today,
]);
}
}

View File

@@ -0,0 +1,207 @@
<?php
namespace App\Http\Controllers\Api\V1\Entrepreneur;
use App\Http\Controllers\Api\V1\Entrepreneur\Requests\VendorProductStoreRequest;
use App\Http\Controllers\Api\V1\Entrepreneur\Requests\VendorProductUpdateRequest;
use App\Http\Controllers\Api\V1\Product\Resources\Review\ProductReviewResource;
use App\Http\Controllers\Controller;
use App\Http\Resources\Api\V1\Vendor\Product\VendorProductIndexResource;
use App\Models\Ecommerce\Product\Product\Product;
use App\Models\User;
use App\Repositories\Ecommerce\Product\ProductRepository;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class VendorProductController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
$user = auth()->user();
$channel = $user->channel();
if (! $channel) {
return response()->rest([], 400, 'User doest not have a channel');
}
return response()->rest_paginate(
VendorProductIndexResource::collection(
ProductRepository::make($request)
->queryAsFromResource($channel)
->attachEagerLoadingRelationship([
'brand',
'media' => function ($query) {
$query->orderBy('order_column', 'asc');
},
])
->applyFilters()
->applySorting()
->simplePaginate()
)
);
}
/**
* Store a newly created resource in storage.
*
* @return \Illuminate\Http\Response
*/
public function store(VendorProductStoreRequest $request)
{
$user = auth()->user();
$channel = $user->channel();
$tax = productTaxForCategory($request->category_ids ?? []);
if ($request->brand_id == 0) {
$request->merge(['brand_id' => null]);
}
$this->addProduct($request, $user, $channel, $tax);
return response()->rest([], 201, 'Created');
}
/**
* Add product
*/
public function addProduct(Request $request, User $user, mixed $channel, int $tax)
{
$product = Product::create([
'name' => $request->name,
'description' => $request->description,
'cost_amount' => $request->cost_amount,
'price_amount' => ProductRepository::calculatePrice($request->cost_amount, $tax),
'old_price_amount' => $request->old_price_amount ? ProductRepository::calculatePrice($request->old_price_amount, $tax) : null,
'sku' => $request->sku,
'barcode' => $request->barcode,
'brand_id' => $request->brand_id,
'stock' => $request->stock,
'is_visible' => false,
'options' => json_encode(ProductRepository::shippingAttributes()),
]);
$product->categories()->attach($request->category_ids);
$product->collections()->attach($request->collection_ids);
$product->channels()->attach($channel->id);
$inventory = $channel->inventories()->first() ?: tmpostDefaultInventory();
$product->inventories()->attach($inventory->id, ['stock' => $request->stock]);
if ($request->hasFile('files')) {
$product->addMultipleMediaFromRequest(['files'])
->each(function ($fileAdder) {
$fileAdder->preservingOriginal()->toMediaCollection('uploads');
});
}
}
/**
* Display the specified resource.
*
* @param App\Models\Ecommerce\Product\Product\Product $product
* @return \Illuminate\Http\Response
*/
public function show(Product $product)
{
$product->load('media');
return response()->rest(new VendorProductIndexResource($product));
}
/**
* Update the specified resource in storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(VendorProductUpdateRequest $request, Product $product)
{
$barcodeCheck = DB::table('products')
->where('barcode', $request->barcode)
->whereNot('id', $product->id)
->exists();
if ($barcodeCheck) {
return response()->rest([
'message' => 'Used barcode',
'errors' => ['barcode' => ['already taken']],
]);
}
$user = auth()->user();
$channel = $user->channel();
$tax = productTaxForCategory($request->category_ids ?? []);
$product->update([
'name' => $request->name,
'description' => $request->description,
'cost_amount' => $request->cost_amount,
'price_amount' => ProductRepository::calculatePrice($request->cost_amount, $tax),
'old_price_amount' => $request->old_price_amount
? ProductRepository::calculatePrice($request->old_price_amount, $tax)
: null,
'sku' => $request->sku,
'barcode' => $request->barcode,
'brand_id' => $request->brand_id,
'stock' => $request->stock,
]);
$product->categories()->attach($request->category_ids);
$product->collections()->attach($request->collection_ids);
$inventory = $channel->inventories()->first() ?: tmpostDefaultInventory();
$product->inventories()->sync($inventory->id, ['stock' => $request->stock]);
if ($request->hasFile('files')) {
$product->addMultipleMediaFromRequest(['files'])
->each(function ($fileAdder) {
$fileAdder->preservingOriginal()->toMediaCollection('uploads');
});
}
if ($request->filled('deleted_images')) {
$product->getMedia('uploads')->map(function ($media) use ($request) {
if (in_array($media->getUrl('thumb'), $request->deleted_images)) {
$media->delete();
}
});
}
if ($request->hasFile('new_imgs')) {
$product->addMultipleMediaFromRequest(['new_imgs'])
->each(function ($fileAdder) {
$fileAdder->toMediaCollection('uploads');
});
}
return response()->rest([], 201, 'Updated');
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy(Product $product)
{
$product->delete();
return response()->rest();
}
/**
* Get product reviews
*/
public function reviews(Product $product)
{
return ProductReviewResource::collection($product->reviews()->get());
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Http\Controllers\Api\V1\Entrepreneur;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class VendorProfileController extends Controller
{
public function index(Request $request)
{
$user = auth()->user();
return response()->rest([
'id' => $user->id,
'first_name' => $user->first_name,
'last_name' => $user->last_name,
'email' => $user->email,
'phone_number' => $user->phone_number,
'email_verified_at' => $user->email_verified_at,
'verified' => $user->verified,
'options' => $user->options,
'created_at' => $user->created_at,
'updated_at' => $user->updated_at,
'phone_number_verified_at' => $user->phone_number_verified_at,
]);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Http\Controllers\Api\V1\Entrepreneur\Resources\Order;
use App\Models\Ecommerce\Product\Order\Shipping\OrderShipping;
use App\Models\Ecommerce\Product\Order\Status\OrderStatus;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class OrderIndexResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'status' => OrderStatus::formattedStatusFor($this->status),
'shipping_method' => OrderShipping::formattedShippingMethod($this->shipping_method),
'notes' => $this->notes,
'delivery_time' => $this->delivery_time,
'delivery_at' => $this->delivery_at,
'region' => $this->region,
'payment_type' => $this->paymentType?->name,
];
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace App\Http\Controllers\Api\V1\Entrepreneur\Resources\Order;
use App\Models\Ecommerce\Product\Order\Shipping\OrderShipping;
use App\Models\Ecommerce\Product\Order\Status\OrderStatus;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class OrderShowResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'status' => OrderStatus::formattedStatusFor($this->status),
'shipping_method' => OrderShipping::formattedShippingMethod($this->shipping_method),
'notes' => $this->notes,
'delivery_time' => $this->delivery_time,
'delivery_at' => $this->delivery_at,
'region' => $this->region,
'payment_type' => $this->paymentType?->name,
'products' => $this->items->map(fn ($item) => [
'quantity' => $item->quantity,
'product' => [
'id' => $item->product->id,
'name' => $item->product->name,
'price_amount' => $item->product->price_amount,
'thumbnail' => $item->product->thumbnail('thumb150x150'),
],
]),
];
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Http\Controllers\Api\V1\Favorite;
use App\Http\Controllers\Api\V1\Favorite\Requests\FavoriteStoreRequest;
use App\Http\Controllers\Api\V1\Favorite\Resources\FavoriteResource;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
class FavoriteController extends Controller
{
/**
* (*) User's favorite products (index)
*/
public function index(): JsonResponse
{
return response()->rest(
FavoriteResource::collection(
auth()->user()->favorites()->with(['product' => ['media', 'brand']])->get()
)
);
}
/**
* (*) Store resource
*/
public function store(FavoriteStoreRequest $request): JsonResponse
{
$data = auth()->user()->favorites()->where('product_id', $request->product_id)->first();
$status = $data
? $data->delete()
: auth()->user()->favorites()->create([
'user_id' => auth()->id(),
'product_id' => $request->product_id,
]);
return response()->rest(
message: is_bool($status) ? 'Removed' : 'Added'
);
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\Api\V1\Favorite\Requests;
use Illuminate\Foundation\Http\FormRequest;
class FavoriteStoreRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string, mixed>
*/
public function rules(): array
{
return [
'product_id' => ['required', 'integer', 'exists:products,id'],
];
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace App\Http\Controllers\Api\V1\Favorite\Resources;
use App\Http\Controllers\Api\V1\Product\Resources\ProductResource;
use Illuminate\Http\Resources\Json\JsonResource;
class FavoriteResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request): array
{
return [
'created_at' => $this->created_at,
'product' => new ProductResource($this->whenLoaded('product')),
];
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Models\Shop\Product\Attribute;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class FilterParamsController extends Controller
{
public function index(Request $request)
{
$category_id = (int) $request->category_id;
$attributes = Attribute::with('values:id,key,attribute_id,value')
->where('is_enabled', true)
->when($request->filled('category_id'), fn ($query) => $query->where('category_id', $category_id))
->get(['id', 'slug', 'name', 'type', 'category_id', 'is_enabled']);
return [
'brands' => $this->getBrands($request),
'attributes' => $attributes->whereNotIn('slug', ['colour', 'size'])->flatten(1),
'sizes' => $attributes->where('slug', 'size')->map(fn ($size_attribute) => [
'attribute_id' => $size_attribute->id,
'values' => $size_attribute->values,
])->first(),
'colours' => $attributes->where('slug', 'colour')->map(fn ($colour_attribute) => [
'attribute_id' => $colour_attribute->id,
'values' => $colour_attribute->values,
])->first(),
];
}
public function getBrands($request)
{
if ($request->filled('category_id')) {
$category_id = (int) $request->category_id;
$brand_ids = DB::table('products')
->join('product_has_relations', function ($join) use ($category_id) {
$join->on('products.id', '=', 'product_has_relations.product_id')
->where('product_has_relations.productable_id', '=', $category_id)
->where('product_has_relations.productable_type', '=', 'category');
})
->select(['id', 'brand_id'])
->pluck('brand_id');
return DB::table('brands')
->where('is_enabled', true)
->whereIn('id', $brand_ids)
->get(['id', 'name']);
}
return DB::table('brands')->where('is_enabled', true)->get(['id', 'name']);
}
}

View File

@@ -0,0 +1,130 @@
<?php
namespace App\Http\Controllers\Api\V1\Filters;
use App\Http\Controllers\Api\V1\Filters\Requests\FilterIndexRequest;
use App\Http\Controllers\Controller;
use App\Models\Ecommerce\Product\Brand\Brand;
use App\Models\Ecommerce\Product\Category\Category;
use App\Models\Ecommerce\Product\Collection\Collection;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class FilterController extends Controller
{
/**
* Filter Controller
*/
public function __construct(
protected Request $request
) {}
/**
* Filters
*/
public function index(FilterIndexRequest $request): JsonResponse
{
return response()->rest([
'categories' => $this->categories()->map(fn ($category) => [
'id' => $category->id,
'parent_id' => $category->parent_id,
'name' => $category->name,
]),
'brands' => $this->brands(),
]);
}
/**
* Categories
*/
private function categories()
{
if ($this->shouldFilterByCategory()) {
return Category::find($this->request->category_id, ['id', 'parent_id'])->children()->get(['id', 'parent_id', 'name']);
}
if ($this->shouldFilterByCollection()) {
return $this->filterByCategoryResource(Collection::find($this->request->collection_id));
}
if ($this->shouldFilterByBrand()) {
return $this->filterByCategoryResource(Brand::find($this->request->brand_id));
}
return Category::query()->where('is_visible', true)->ordered()->get(['id', 'parent_id', 'name']);
}
private function brands()
{
if ($this->shouldFilterByBrand()) {
return Brand::where('id', $this->request->brand_id)->get(['id', 'name']);
}
if ($this->shouldFilterByCategory()) {
$brands = Category::find($this->request->category_id)->products()
->where('products.is_visible', true)
->where('products.parent_id', null)
->where('products.stock', '>', 0)
->distinct('products.brand_id')
->pluck('products.brand_id');
return Brand::whereIntegerInRaw('id', $brands)->get(['id', 'name']);
}
if ($this->shouldFilterByCollection()) {
$brands = Collection::find($this->request->collection_id)->products()
->where('products.is_visible', true)
->where('products.parent_id', null)
->where('products.stock', '>', 0)
->distinct('products.brand_id')
->pluck('products.brand_id');
return Brand::whereIntegerInRaw('id', $brands)->get(['id', 'name']);
}
return Brand::query()->where('is_visible', true)->ordered()->get(['id', 'name']);
}
/**
* Filter by category
*/
private function filterByCategoryResource($resource)
{
$products = $resource->products()
->where('products.is_visible', true)
->where('products.parent_id', null)
->where('products.stock', '>', 0)
->distinct('products.id')
->pluck('products.id');
return Category::where('is_visible', true)->ordered()->join('product_has_relations', 'categories.id', '=', 'product_has_relations.productable_id')
->where('product_has_relations.productable_type', '=', 'category')
->whereIntegerInRaw('product_has_relations.product_id', $products)
->get(['id', 'parent_id', 'name'])
->unique('categories.id');
}
/**
* Check if request should be filtered by category
*/
private function shouldFilterByCategory(): bool
{
return $this->request->filled('category_id');
}
/**
* Check if request should be filtered by collection
*/
private function shouldFilterByCollection(): bool
{
return $this->request->filled('collection_id');
}
/**
* Check if request should be filtered by brand
*/
private function shouldFilterByBrand(): bool
{
return $this->request->filled('brand_id');
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Http\Controllers\Api\V1\Filters\Helpers;
use Laravel\Nova\Makeable;
class BrandsFilter
{
use Makeable;
public function __construct(
protected Builder $queryBuilder,
protected Request $request,
) {}
/**
* Basic queries
*/
public function applyBaseQueries(): self
{
$this->queryBuilder->where('is_visible', true)->ordered();
return $this;
}
/**
* Get brands
*/
public function get()
{
return $this->queryBuilder->whereIntegerInRaw(
column: 'id',
values: $resource->products()->distinct('brand_id')->pluck('brand_id')
)->get(['id', 'name']);
return $this->queryBuilder->get(['id', 'name']);
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace App\Http\Controllers\Api\V1\Filters\Helpers;
use App\Models\Ecommerce\Product\Brand\Brand;
use App\Models\Ecommerce\Product\Category\Category;
use App\Models\Ecommerce\Product\Collection\Collection;
use App\Repositories\Ecommerce\Filter\FilterParamRepository;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Laravel\Nova\Makeable;
class CategoriesFilter
{
use Makeable;
/**
* Request object to work with
*/
protected Request $request;
/**
* Request object to work with
*/
protected Builder $queryBuilder;
/**
* Filter repo
*/
protected FilterParamRepository $filterRepository;
/**
* Table columns to get
*/
protected array $columns = [
'id',
'parent_id',
'name',
];
/**
* Categories for filtering
*/
public function __construct(Request $request)
{
$this->request = $request;
$this->queryBuilder = config('ecommerce.models.category')::query();
$this->filterRepository = FilterParamRepository::make($this->request);
}
/**
* Basic queries
*/
public function applyBaseQueries(): self
{
$this->queryBuilder->enabled()->ordered();
return $this;
}
/**
* Get all categories
*/
public function get()
{
if ($this->filterRepository->shouldFilterByCategory()) {
return Category::find($this->request->category_id, ['id', 'parent_id'])->children()->get($this->columns);
}
if ($this->filterRepository->shouldFilterByCollection()) {
return $this->byResource(Collection::find($this->request->collection_id));
}
if ($this->filterRepository->shouldFilterByBrand()) {
return $this->byResource(Brand::find($this->request->brand_id));
}
return $this->queryBuilder->get($this->columns);
}
public function byResource($resource)
{
$products = $resource->products()
->where('products.is_visible', true)
->where('products.parent_id', null)
->where('products.stock', '>', 0)
->distinct('products.id')
->pluck('products.id');
return $this->queryBuilder->join('product_has_relations', 'categories.id', '=', 'product_has_relations.productable_id')
->where('product_has_relations.productable_type', '=', 'category')
->whereIntegerInRaw('product_has_relations.product_id', $products)
->get($this->columns)
->unique('categories.id');
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers\Api\V1\Filters\Requests;
use Illuminate\Foundation\Http\FormRequest;
class FilterIndexRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'collection_id' => ['bail', 'nullable', 'int', 'exists:collections,id'],
'category_id' => ['bail', 'nullable', 'int', 'exists:categories,id'],
'brand_id' => ['bail', 'nullable', 'int', 'exists:brands,id'],
];
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Http\Controllers\Api\V1\Legal;
use App\Http\Controllers\Controller;
use App\Http\Resources\Api\V1\Legal\LegalPageResource;
use App\Models\Legal\LegalPage;
use Illuminate\Http\JsonResponse;
class LegalPageController extends Controller
{
/**
* (Legal) Legal pages
*/
public function index(): JsonResponse
{
return response()->rest(
LegalPage::where('is_active', true)
->get(['slug', 'title'])
->map(fn ($legalPage) => [
'slug' => $legalPage->slug,
'title' => $legalPage->title,
])
);
}
/**
* (Legal) Legal pages
*/
public function show(LegalPage $legalPage): JsonResponse
{
return response()->rest(new LegalPageResource($legalPage));
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Models\Legal\LegalPage;
class LegalPagesController extends Controller
{
public function refundPolicy()
{
return response()->rest(
LegalPage::where('slug', 'refund-policy')->first(['title', 'content'])
);
}
public function privacyPolicy()
{
return response()->rest(
LegalPage::where('slug', 'privacy-policy')->first(['title', 'content'])
);
}
public function termsOfUse()
{
return response()->rest(
LegalPage::where('slug', 'terms-of-use')->first(['title', 'content'])
);
}
public function shippingPolicy()
{
return response()->rest(
LegalPage::where('slug', 'shipping-policy')->first(['title', 'content'])
);
}
public function paymentTypeNotAvailable()
{
return 'Töleg görnüşi elýeter däl';
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Http\Resources\Products\ProductResource;
use App\Models\Shop\Product\Product;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Maize\Markable\Models\Like;
class LikeController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
return response()->rest_paginate(ProductResource::collection(
Product::with(['media', 'brand'])->whereHasLike(auth()->user())->paginate(15)
));
}
/**
* Store a newly created resource in storage.
*
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'product_id' => ['required', 'integer', 'exists:products,id'],
]);
if ($validator->fails()) {
return response()->rest($validator->messages()->get('*'), 400, 'Wrong credentials');
}
$product = Product::find($request->product_id);
Like::add($product, auth()->user());
return response()->rest([], 201);
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy(Request $request)
{
$validator = Validator::make($request->all(), [
'product_id' => ['required', 'integer', 'exists:products,id'],
]);
if ($validator->fails()) {
return response()->rest([], 400, 'Wrong credentials');
}
$product = Product::find($request->product_id);
Like::remove($product, auth()->user());
return response()->rest();
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
class LinkController extends Controller
{
public function product(Request $request)
{
$validator = Validator::make($request->all(), [
'id' => ['required', 'integer'],
]);
if ($validator->fails()) {
return response()->rest($validator->messages()->get('*'), 400, 'Validator fail');
}
$product = DB::table('products')->where('id', $request->id)->pluck('slug', 'id');
if ($product->isEmpty()) {
return to_route('web.products.show', ['product' => 1]);
}
return to_route('web.products.show', ['product' => $product->first()]);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\V1\Forms\Newsletter\NewsletterSubscriptionStoreRequest;
use App\Models\CMS\Marketing\NewsletterUser;
use Illuminate\Http\JsonResponse;
class NewsletterSubscriptionController extends Controller
{
/**
* (Marketing) Subscribe to newsletter
*/
public function store(NewsletterSubscriptionStoreRequest $request): JsonResponse
{
NewsletterUser::insertOrIgnore($request->validated());
return response()->rest();
}
}

View File

@@ -0,0 +1,132 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Api\Services\Payment\HalkbankPaymentService;
use App\Http\Controllers\Controller;
use App\Models\Shop\Order\Order;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class OnlinePaymentController extends Controller
{
public function halkbank(Request $request)
{
$formData = $request->validate([
'pan' => ['required', 'integer', 'digits:16'],
'month' => ['required', 'integer', 'between:1,12'],
'year' => ['required', 'integer', 'min:2022', 'max:2066'],
'name' => ['required', 'string'],
'cvc' => ['required'],
'order_id' => ['required', 'integer', 'exists:orders,id'],
]);
return route('payment-not-available');
$order = Order::find($request->order_id);
$halkbank = new HalkbankPaymentService(
amount: (string) $order->fullPriceWithShipping().'00',
returnURL: route('web.order.check.halkbank', ['id' => $order->id])
);
$halkbank->orderTicket();
$app_name = 'POSTSHOP';
$app_id = Str::random(16);
// Wagtyny kesgitlemek
$response_start_hack = Http::asForm()->post('http://localhost:9090/api/v1/start-hack', [
'app' => $app_name,
'id' => $app_id,
'url' => $halkbank->paymentPageUrl(),
]);
Log::info(['hack' => $response_start_hack->json()]);
if (! ($response_start_hack && array_key_exists('status', $response_start_hack->json()) && $response_start_hack['status'] == 'ok')) {
return response()->rest([$response_start_hack->body()]);
}
$ok = (string) $request->cvc;
$cvc = match (strlen($ok)) {
1 => '00'.$ok,
2 => '0'.$ok,
default => $ok
};
$response_submit_card = Http::asForm()->post('http://localhost:9090/api/v1/submit-card', [
'app' => $app_name,
'id' => $app_id,
'md-order' => $halkbank->ticketOrderId(),
'card-number' => $request->pan,
'card-expiry' => $request->year.$request->month,
'name-on-card' => $request->name,
'card-cvc' => $cvc,
]);
Log::info(['submit_card' => $response_submit_card->json()]);
if (! ($response_submit_card && array_key_exists('status', $response_submit_card->json()) && $response_submit_card['status'] == 'ok')) {
return response()->rest([$response_submit_card->body()]);
}
return response()->rest([
'order_id' => $order->id,
'ticket_order_id' => $halkbank->ticketOrderId(),
'url' => $response_submit_card['acs-session-url'],
]);
}
public function checkPayment(Request $request)
{
$request->validate(['order_id' => 'required|integer|exists:orders,id', 'ticket_order_id' => 'required|string']);
$payment_status = HalkbankPaymentService::checkPayment($request->ticket_order_id);
if ($payment_status) {
Order::find($request->order_id)->markAsPaid();
}
return response()->rest([
'payment_status' => $payment_status,
]);
}
public function halkbankVerifyOTP(Request $request)
{
$request->validate([
'order_id' => ['required', 'integer', 'exists:orders,id'],
'request_id' => ['required'],
'sms_code' => ['required', 'integer'],
]);
$response = HalkbankPaymentService::sendSMSVerificationCode(
request_id: $request->request_id,
sms_code: $request->sms_code
);
$doc = new \DOMDocument;
$doc->loadHTML($response->body());
$inputs = $doc->getElementsByTagName('input');
if (count($inputs) > 0) {
$md = $inputs[0]->getAttribute('value');
$paRes = $inputs[1]->getAttribute('value');
}
if ($md && $paRes) {
$response_3d = Http::asForm()->post('https://mpi.gov.tm:443/payment/rest/finish3ds.do', [
'MD' => $md,
'PaRes' => $paRes,
]);
Log::info('3d', [$response_3d->body()]);
}
return response()->rest();
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Http\Controllers\Api\V1\Order;
use App\Http\Controllers\Controller;
use App\Models\System\Settings\Payments\PaymentType;
use Illuminate\Http\JsonResponse;
class OrderPaymentController extends Controller
{
/**
* Order payment types
*/
public function index(): JsonResponse
{
return response()->rest(
PaymentType::all(['id', 'name'])
->map(fn ($paymentType) => [
'id' => $paymentType->id,
'name' => $paymentType->name,
])
);
}
}

View File

@@ -0,0 +1,92 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Http\Requests\CheckoutOrderRequest;
use App\Http\Resources\Api\V1\Order\OrderIndexResource;
use App\Models\Ecommerce\Product\Order\Order;
use App\Repositories\Ecommerce\Order\OrderRepository;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class OrderController extends Controller
{
/**
* (*) User's orders
*/
public function index()
{
return response()->rest_paginate(
OrderIndexResource::collection(
auth()->user()->orders()->with(['items', 'items.product', 'paymentType'])->latest()->paginate(20)
)
);
}
/**
* (*) Return available times to make an order
*/
public function time(): JsonResponse
{
return response()->rest(OrderRepository::availableTimes());
}
/**
* (*) Store order
*/
public function store(CheckoutOrderRequest $request): JsonResponse
{
$order = (new OrderRepository($request->all()))->create();
$url = null;
if ($request->payment_type_id == 3) {
$response = createHalkbankOrder();
if ($response['status'] == 'success') {
$url = $response['url'];
}
}
return response()->rest([
'order_id' => $order->id,
'payment_url' => $url,
], 201);
}
/**
* (*) Show order
*/
public function show(Order $order): JsonResponse
{
$order->load(['items', 'items.product', 'paymentType']);
return response()->rest(new OrderIndexResource($order));
}
/**
* Update the specified resource in storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Order $order): JsonResponse
{
// $order-.
return response()->rest();
}
/**
* Remove the specified resource from storage.
*
* @param Order $order
* @return \Illuminate\Http\Response
*/
public function destroy(Order $order)
{
$order->delete();
return response()->rest();
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Http\Controllers\Api\V1\PostBranch;
use App\Http\Controllers\Controller;
use App\Models\Post\PostBranch;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class PostBranchController extends Controller
{
/**
* All post branches
*/
public function index(Request $request): JsonResponse
{
return response()->rest(
PostBranch::query()
->get()
->map(fn ($postBranch) => [
'id' => $postBranch->id,
'province_id' => $postBranch->province_id,
'name' => $postBranch->name,
'address' => $postBranch->address,
'description' => $postBranch->description,
])
);
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Http\Controllers\Api\V1\Product\Barcode;
use App\Http\Controllers\Api\V1\Product\Barcode\Requests\ProductBarcodeSearchIndexRequest;
use App\Http\Controllers\Controller;
use App\Models\Ecommerce\Product\Product\Product;
use Illuminate\Http\JsonResponse;
class ProductBarcodeSearchController extends Controller
{
/**
* Search product via barcode
*/
public function index(ProductBarcodeSearchIndexRequest $request): JsonResponse
{
$product = Product::where('barcode', $request->barcode)->with('media')->first();
return response()->rest([
'id' => $product->id,
'name' => $product->name,
'stock' => $product->stock,
'price_amount' => $product->price_amount,
'image' => [
'thumbnail' => $product->thumbnail(),
'images_400x400' => $product->getMedia('uploads')->map(fn ($media) => $media->getUrl('thumb400x400')),
'images_720x720' => $product->getMedia('uploads')->map(fn ($media) => $media->getUrl('thumb720x720')),
'images_800x800' => $product->getMedia('uploads')->map(fn ($media) => $media->getUrl('thumb800x800')),
'images_1200x1200' => $product->getMedia('uploads')->map(fn ($media) => $media->getUrl('thumb1200x1200')),
],
]);
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\Api\V1\Product\Barcode\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ProductBarcodeSearchIndexRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'barcode' => ['required', 'string', 'max:255', 'exists:products,barcode'],
];
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace App\Http\Controllers\Api\V1\Product;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\V1\Product\ProductCommentStore;
use App\Http\Resources\Api\V1\Common\CommentResource;
use App\Models\Common\Comment;
use App\Models\Ecommerce\Product\Product\Product;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ProductCommentController extends Controller
{
/**
* Product's comments
*/
public function index(Request $request, Product $product): JsonResponse
{
return response()->rest(
CommentResource::collection(
$product->comments()->with('user')->where('active', true)->get()
)
);
}
/**
* Store
*/
public function store(ProductCommentStore $request, Product $product): JsonResponse
{
$product->comments()->create([
'comment' => $request->comment,
'user_id' => auth()->id(),
]);
return response()->rest(message: 'Comment added successfully', code: 201);
}
/**
* Destroy the comment from product
*/
public function destroy(Product $product, Comment $comment): JsonResponse
{
$product->comments()->where('comments.id', $comment->id)->delete();
return response()->rest(message: 'Comment removed successfully from product', code: 204);
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace App\Http\Controllers\Api\V1\Product;
use App\Http\Controllers\Api\V1\Product\Resources\ProductIndexResource;
use App\Http\Controllers\Api\V1\Product\Resources\ProductShowResource;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\V1\Product\ProductIndexRequest;
use App\Models\Ecommerce\Product\Product\Product;
use App\Repositories\Ecommerce\Product\ProductRepository;
use Illuminate\Http\JsonResponse;
class ProductController extends Controller
{
/**
* Products (index)
*/
public function index(ProductIndexRequest $request): JsonResponse
{
return response()->rest_paginate(
ProductIndexResource::collection(
ProductRepository::make($request)
->applyBasicQueries()
->applyFilters()
->applySorting()
->simplePaginate()
)
);
}
/**
* Products (show)
*/
public function show(Product $product): JsonResponse
{
$product->load([
'channels:id,name',
'properties',
'media' => function ($query) {
$query->orderBy('order_column', 'asc');
},
'variations' => [
'media',
'properties',
],
'reviews',
'categories:id,name',
]);
return response()->rest(new ProductShowResource($product));
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Http\Controllers\Api\V1\Product;
use App\Http\Controllers\Api\V1\Product\Resources\ProductResource;
use App\Http\Controllers\Controller;
use App\Models\Ecommerce\Product\Product\Product;
use App\Repositories\Ecommerce\Product\ProductRepository;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
class ProductRelatedController extends Controller
{
/**
* Related products (index)
*/
public function index(Product $product): JsonResponse
{
$products = DB::table('product_has_relations')
->select('product_has_relations.product_id')
->whereIn('product_has_relations.productable_id', (function ($query) use ($product) {
$query->from('product_has_relations')
->select('productable_id')
->distinct('productable_id')
->where('productable_type', '=', 'category')
->where('product_id', '=', $product->id);
}))
->limit(12)
->orderByRaw('RANDOM()')
->pluck('product_has_relations.product_id')
->unique();
return response()->rest(
ProductResource::collection(
ProductRepository::make(request())
->applyBasicQueries()
->whereIntegerInRaw('id', $products->toArray())
->get()
)
);
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Http\Controllers\Api\V1\Product;
use App\Http\Controllers\Api\V1\Product\Resources\Review\ProductReviewResource;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\V1\Product\Review\ProductReviewStore;
use App\Models\Ecommerce\Product\Product\Product;
use Illuminate\Http\JsonResponse;
class ProductReviewController extends Controller
{
/**
* Product reviews (index)
*/
public function index(Product $product): JsonResponse
{
return response()->rest(
ProductReviewResource::collection(
$product->reviews()
->where('is_visible', true)
->orderBy('is_recommended')
->get()
)
);
}
/**
* (*) Product reviews (store)
*/
public function store(ProductReviewStore $request, Product $product): JsonResponse
{
$product->reviews()->create([
'user_id' => auth()->id(),
...$request->validated(),
]);
return response()->rest(message: 'Review added successfully', code: 201);
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace App\Http\Controllers\Api\V1\Product\Resources\Attribute;
use Illuminate\Http\Resources\Json\JsonResource;
class ProductAttributeProductShowResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request): array
{
return [
'attribute_id' => $this->attribute->id,
'name' => $this->attribute->name,
'value' => $this->values->map(fn ($val) => $val->real_value)->first(),
];
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace App\Http\Controllers\Api\V1\Product\Resources\Attribute;
use Illuminate\Http\Resources\Json\JsonResource;
class ProductAttributeResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)
{
return [
'attribute_id' => $this->attribute->id,
'name' => $this->attribute->name,
'value' => $this->values->map(fn ($val) => $val->real_value)->first(),
];
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace App\Http\Controllers\Api\V1\Product\Resources;
use App\Http\Resources\MediaResource;
use App\Repositories\Ecommerce\Product\Property\PropertyRepository;
use Illuminate\Http\Resources\Json\JsonResource;
class ProductIndexResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request): array
{
$reviews = $this->whenLoaded('reviews');
return [
'id' => $this->id,
'parent_id' => $this->parent_id,
'name' => $this->name,
'slug' => $this->slug,
'description' => '',
'sku' => $this->sku,
'barcode' => $this->barcode,
'stock' => $this->stock,
'price_amount' => $this->price_amount,
'old_price_amount' => $this->old_price_amount,
'backorder' => $this->backorder,
'weight_value' => $this->weight_value,
'weight_unit' => $this->weight_unit,
'height_value' => $this->height_value,
'height_unit' => $this->height_unit,
'media' => MediaResource::collection($this->whenLoaded('media')),
'created_at' => $this->created_at,
'seo_title' => $this->seo_title,
'seo_description' => $this->seo_description,
'colour' => PropertyRepository::getRealValue('colour', $this->colour),
'size' => PropertyRepository::getRealValue('size', $this->size),
'brand' => [
'id' => $this->brand_id,
'name' => $this->brand?->name,
],
'reviews' => [
'count' => $reviews ? $reviews->count() : 0,
'rating' => $reviews ? number_format($reviews->avg('rating'), 2) : 0,
],
];
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Http\Controllers\Api\V1\Product\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class ProductMediaResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'thumbnail' => $this->getUrl('thumb288x431'),
'images_400x400' => $this->getUrl('thumb400x400'),
'images_720x720' => $this->getUrl('thumb720x720'),
'images_800x800' => $this->getUrl('thumb800x800'),
'images_1200x1200' => $this->getUrl('thumb1200x1200'),
];
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace App\Http\Controllers\Api\V1\Product\Resources;
use App\Http\Controllers\Api\V1\Product\Resources\Attribute\ProductAttributeProductShowResource;
use App\Http\Controllers\Api\V1\Product\Resources\Variant\ProductVariantResource;
use App\Http\Resources\Api\V1\Channel\ChannelResource;
use App\Repositories\Ecommerce\Product\Property\PropertyRepository;
use Illuminate\Http\Resources\Json\JsonResource;
class ProductResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request): array
{
$media = $this->whenLoaded('media');
$reviews = $this->whenLoaded('reviews');
$reviewsLoaded = get_class($reviews) !== 'Illuminate\\Http\\Resources\\MissingValue';
return [
'id' => $this->id,
'parent_id' => $this->parent_id,
'name' => $this->name,
'slug' => $this->slug,
'description' => $this->description,
'sku' => $this->sku,
'barcode' => $this->barcode,
'stock' => $this->stock,
'price_amount' => $this->price_amount,
'old_price_amount' => $this->old_price_amount,
'backorder' => $this->backorder,
'weight_value' => $this->weight_value,
'weight_unit' => $this->weight_unit,
'height_value' => $this->height_value,
'height_unit' => $this->height_unit,
'media' => ProductMediaResource::collection($media),
'created_at' => $this->created_at,
'seo_title' => $this->seo_title,
'seo_description' => $this->seo_description,
'is_visible' => $this->is_visible,
'colour' => PropertyRepository::getRealValue('colour', $this->colour),
'size' => PropertyRepository::getRealValue('size', $this->size),
'brand' => [
'id' => $this->brand_id,
'name' => $this->brand?->name,
],
'channel' => ChannelResource::collection($this->whenLoaded('channels')),
'properties' => ProductAttributeProductShowResource::collection($this->whenLoaded('properties')),
'variations' => ProductVariantResource::collection($this->whenLoaded('variations')),
'reviews' => [
'count' => $reviewsLoaded ? $reviews->count() : 0,
'rating' => $reviewsLoaded ? number_format($reviews->avg('rating'), 2) : '',
],
];
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace App\Http\Controllers\Api\V1\Product\Resources;
use App\Http\Controllers\Api\V1\Product\Resources\Attribute\ProductAttributeProductShowResource;
use App\Http\Controllers\Api\V1\Product\Resources\Review\ProductReviewResource;
use App\Http\Controllers\Api\V1\Product\Resources\Variant\ProductVariantResource;
use App\Http\Resources\Api\V1\Channel\ChannelResource;
use App\Http\Resources\MediaResource;
use App\Repositories\Ecommerce\Product\Property\PropertyRepository;
use Illuminate\Http\Resources\Json\JsonResource;
class ProductShowResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request): array
{
$reviews = $this->whenLoaded('reviews');
$categories = $this->whenLoaded('categories');
return [
'id' => $this->id,
'parent_id' => $this->parent_id,
'name' => $this->name,
'slug' => $this->slug,
'description' => $this->description,
'sku' => $this->sku,
'barcode' => $this->barcode,
'stock' => $this->stock,
'price_amount' => $this->price_amount,
'old_price_amount' => $this->old_price_amount,
'backorder' => $this->backorder ? 'true' : 'false',
'weight_value' => $this->weight_value,
'weight_unit' => $this->weight_unit,
'height_value' => $this->height_value,
'height_unit' => $this->height_unit,
'media' => MediaResource::collection($this->whenLoaded('media')),
'created_at' => $this->created_at,
'seo_title' => $this->seo_title,
'seo_description' => $this->seo_description,
'colour' => PropertyRepository::getRealValue('colour', $this->colour),
'size' => PropertyRepository::getRealValue('size', $this->size),
'available_colors' => PropertyRepository::getAvailableColors($this),
'available_sizes' => PropertyRepository::getAvailableSizes($this),
'brand' => [
'id' => $this->brand_id,
'name' => $this->brand?->name,
],
'channel' => ChannelResource::collection($this->whenLoaded('channels')),
'properties' => ProductAttributeProductShowResource::collection($this->whenLoaded('properties')),
'variations' => ProductVariantResource::collection($this->whenLoaded('variations')),
'reviews' => [
'count' => $reviews ? $reviews->count() : 0,
'rating' => $reviews ? number_format($reviews->avg('rating'), 2) : 0,
],
'reviews_resources' => ProductReviewResource::collection($this->whenLoaded('reviews')),
$this->mergeWhen($categories, [
'categories' => $categories->map(fn ($category) => [
'id' => $category->id,
'name' => $category->name,
]),
]),
];
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Http\Controllers\Api\V1\Product\Resources\Review;
use Illuminate\Http\Resources\Json\JsonResource;
class ProductReviewResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request): array
{
return [
'id' => $this->id,
'user_id' => $this->user_id,
'product_id' => $this->product_id,
'rating' => $this->rating,
'title' => $this->title,
'content' => $this->content,
'is_recommended' => $this->is_recommended,
'source' => $this->source,
];
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace App\Http\Controllers\Api\V1\Product\Resources\Variant;
use App\Http\Controllers\Api\V1\Product\Resources\Attribute\ProductAttributeResource;
use App\Repositories\Ecommerce\Product\Property\PropertyRepository;
use Illuminate\Http\Resources\Json\JsonResource;
class ProductVariantResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)
{
return [
'id' => $this->id,
'parent_id' => $this->parent_id,
'name' => $this->name,
'slug' => $this->slug,
'description' => $this->description,
'sku' => $this->sku,
'barcode' => $this->barcode,
'stock' => $this->stock,
'cost_amount' => $this->cost_amount,
'price_amount' => $this->price_amount,
'old_price_amount' => $this->old_price_amount,
'backorder' => $this->backorder,
'weight_value' => $this->weight_value,
'weight_unit' => $this->weight_unit,
'height_value' => $this->height_value,
'height_unit' => $this->height_unit,
'colour' => PropertyRepository::getRealValue('colour', $this->colour),
'size' => PropertyRepository::getRealValue('size', $this->size),
'thumbnail' => $this->thumbnail(),
'images_400x400' => $this->getMedia('uploads')->map(fn ($media) => $media->getUrl('thumb400x400')),
'images_800x800' => $this->getMedia('uploads')->map(fn ($media) => $media->getUrl('thumb800x800')),
'images_1200x1200' => $this->getMedia('uploads')->map(fn ($media) => $media->getUrl('thumb1200x1200')),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'properties' => ProductAttributeResource::collection($this->whenLoaded('properties')),
];
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace App\Http\Controllers\Api\V1\Product\Search;
use App\Http\Controllers\Api\V1\Product\Resources\ProductMediaResource;
use App\Http\Controllers\Controller;
use App\Models\Ecommerce\Product\Product\Product;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ProductSearchController extends Controller
{
/**
* Search product via barcode
*/
public function index(Request $request): JsonResponse
{
$request->validate([
'q' => ['required', 'string', 'max:255'],
]);
$searchQuery = $request->input('q');
$products = Product::with(['brand', 'media'])
->where(function ($query) use ($searchQuery) {
if (is_numeric($searchQuery)) {
$query->where('products.id', intval($searchQuery));
}
$operator = $query->getConnection()->getDriverName() === 'pgsql' ? '~*' : 'like';
$value = $query->getConnection()->getDriverName() === 'pgsql' ? $searchQuery : "%{$searchQuery}%";
$query
->orWhere('products.name', $operator, $value)
->orWhere('products.sku', $operator, $value)
->orWhere('products.barcode', $operator, $value);
})
->when($request->isNotFilled('internal'), function ($query) {
$query->where('products.is_visible', true)
->where('products.parent_id', null)
->where('products.stock', '>', 0);
})
->limit(40)
->get();
return response()->rest(
$products->map(fn ($product) => [
'id' => $product->id,
'name' => $product->fullName,
'stock' => $product->stock,
'cost_amount' => $product->cost_amount,
'price_amount' => $product->price_amount,
'brand' => [
'id' => $product->brand?->id,
'name' => $product->brand?->name,
],
'thumbnail' => $product->thumbnail(),
'media' => ProductMediaResource::collection($product->media),
])
);
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace App\Http\Controllers\Api\V1\Profile;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
class ProfileController extends Controller
{
/**
* Users profile
*/
public function index(): JsonResponse
{
$user = auth()->user();
return response()->rest([
'first_name' => $user->first_name,
'last_name' => $user->last_name,
'phone_number' => $user->phone_number,
'address' => $user->options->get('address'),
]);
}
/**
* Store
*/
public function store(Request $request)
{
$request->validate([
'name' => ['required', 'string', 'max:255'],
'phone_number' => ['required', 'int', 'between:61000000,71999999', Rule::unique('users', 'phone_number')->ignore(auth()->id())],
'address' => ['required', 'string', 'max:255'],
]);
auth()->user()->options->set('address', $request->address);
auth()->user()->update([
'first_name' => $request->name,
'phone_number' => $request->phone_number,
]);
return response()->rest();
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Http\Controllers\Api\V1\Province;
use App\Http\Controllers\Controller;
use App\Models\System\Settings\Location\Province;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ProvinceController extends Controller
{
/**
* Get provinces
*/
public function index(Request $request): JsonResponse
{
return response()->rest(
Province::query()
->get()
->map(fn ($province) => [
'id' => $province->id,
'region' => $province->region,
'name' => $province->name,
]));
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Api\V1\Reviews\Resources\ReviewResource;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\V1\Product\Review\ProductReviewUpdate;
use App\Models\Ecommerce\Product\Review\Review;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ReviewController extends Controller
{
/**
* (*) User's reviews
*/
public function index(Request $request): JsonResponse
{
return response()->rest_paginate(
ReviewResource::collection(
auth()->user()->reviews()->with(['product', 'product.brand'])->simplePaginate($request->perPage ?: 10)
)
);
}
/**
* (*) Update review
*/
public function update(ProductReviewUpdate $request, Review $review): JsonResponse
{
$review->update($request->validated());
return response()->rest(message: 'Review updated successfully');
}
/**
* (*) Destroy the comment from product
*/
public function destroy(Review $review): JsonResponse
{
$review->delete();
return response()->rest(message: 'Review deleted successfully');
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Http\Controllers\Api\V1\Reviews\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class ReviewResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'product' => $this->when($this->relationLoaded('product'), [
'id' => $this->product->id,
'name' => $this->product->name,
'barcode' => $this->product->barcode,
'price_amount' => $this->product->price_amount,
'brand' => [
'id' => $this->product->brand_id,
'name' => $this->product->brand?->name,
],
]),
'rating' => $this->rating,
'title' => $this->title,
'content' => $this->content,
'is_recommended' => $this->is_recommended,
'updated_at' => $this->updated_at,
];
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Milon\Barcode\Facades\DNS1DFacade;
class BarcodeGeneratorController extends Controller
{
/**
* Barcode image generator
*/
public function index(Request $request)
{
$request->validate([
'barcode' => ['required'],
]);
return Storage::download(
Str::after(
DNS1DFacade::getBarcodePNGPath(
code: $request->barcode,
type: config('barcode.default_type'),
showCode: true,
),
'storage/app/'
)
);
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController
{
use AuthorizesRequests, ValidatesRequests;
}

View File

@@ -0,0 +1,94 @@
<?php
namespace App\Http\Controllers;
use App\Models\CMS\Forms\ContactUS;
use App\Models\CMS\Marketing\Newsletter;
use App\Models\CMS\Marketing\NewsletterUser;
use App\Models\CMS\Media\Banner;
use App\Models\CMS\Media\Carousel;
use App\Models\CMS\Media\Gallery;
use App\Models\Ecommerce\Channel\Channel;
use App\Models\Ecommerce\Product\Brand\Brand;
use App\Models\Ecommerce\Product\Category\Category;
use App\Models\Ecommerce\Product\Collection\Collection;
use App\Models\Ecommerce\Product\Coupon\Coupon;
use App\Models\Ecommerce\Product\Inventory\Inventory;
use App\Models\Ecommerce\Product\Order\Order;
use App\Models\Ecommerce\Product\Product\Product;
use App\Models\Ecommerce\Product\Property\Attribute;
use App\Models\Ecommerce\Product\Review\Review;
use App\Models\Legal\LegalPage;
use App\Models\Post\PostBranch;
use App\Models\System\Roles\Permission;
use App\Models\System\Roles\Role;
use App\Models\System\Settings\Location\Province;
use App\Models\System\Settings\Payments\PaymentType;
use App\Models\System\VersionManagement\AppVersion;
use App\Models\User;
use Illuminate\Support\Facades\Artisan;
class TestController extends Controller
{
public function ok()
{
if (! app()->isProduction()) {
auth()->login(User::where('email', 'nurmuhammet@mail.com')->first());
return back();
}
abort(404);
}
public function index()
{
$models = [
Order::class,
Product::class,
Attribute::class,
Inventory::class,
Channel::class,
Collection::class,
Category::class,
Brand::class,
User::class,
// Payout::class,
Banner::class,
Carousel::class,
Gallery::class,
LegalPage::class,
NewsletterUser::class,
Newsletter::class,
Coupon::class,
Review::class,
ContactUS::class,
Role::class,
Permission::class,
Province::class,
PostBranch::class,
PaymentType::class,
AppVersion::class,
];
$data = [];
collect($models)->each(function (string $model) use (&$data) {
$modelNamespace = str_replace(
'\\',
'/',
str_replace('App\\Models\\', '', $model)
);
$modelName = $modelNamespace.'Policy';
$data[] = [
'key' => $modelName,
'value' => $modelNamespace,
];
Artisan::call("make:policy {$modelName} --model={$modelNamespace}");
});
return $data;
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Blade;
class UserDocController extends Controller
{
public function index(Request $request)
{
$files = scandir(public_path($request->folder));
return Blade::render('
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Files</title>
</head>
<body>
<ul>
@foreach($lists as $list)
<li><a href="{{ url($link_folder) }}/{{ $list }}">{{ $list }}</a></li>
@endforeach
</ul>
</body>
</html>
', [
'link_folder' => $request->folder,
'lists' => array_filter($files, fn ($data) => ! ($data == '.' || $data == '..')),
]);
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace App\Http\Controllers\Web\Auth;
use App\Http\Controllers\Controller;
use App\Models\Auth\Verification;
use App\Models\User;
use Illuminate\Http\Request;
class LoginController extends Controller
{
public function __construct()
{
$this->middleware('guest');
}
public function twoFactorLogin(Request $request)
{
$request->merge(['phone_number' => substr(str_replace(['+', '(', ')', '-'], '', $request->phone_number), 3)]);
$request->validate([
'phone_number' => ['required', 'integer', 'between:61000000,65999999'],
'verification_code' => ['nullable', 'integer'],
]);
if ($request->filled('verification_code')) {
return $this->login($request);
}
sendSMSVerification($request->phone_number);
return response()->rest();
}
public function login(Request $request)
{
$verification = Verification::where('username', $request->phone_number)
->where('code', $request->verification_code)
->first();
if (! $verification) {
return response()->rest([], 401, 'Verification not found');
}
$user = User::firstOrCreate(
['phone_number' => $request->phone_number],
[
'first_name' => 'Ulanyjy',
'last_name' => 'Ulanyjy',
'email' => $request->phone_number.'user@fakemail.com',
]
);
auth()->login($user);
return response()->rest([], 200, 'Login');
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Http\Controllers\Web\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class LogoutController extends Controller
{
public function __invoke(Request $request)
{
Auth::guard()->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect()->route('web.home');
}
}

View File

@@ -0,0 +1,97 @@
<?php
namespace App\Http\Controllers\Web\BecomeSeller;
use App\Http\Controllers\Controller;
use App\Http\Requests\Web\BecomeSellerPageStoreRequest;
use App\Models\EntrepreneursDocs;
use App\Models\User;
use App\Models\Verification;
use Illuminate\Auth\Events\Verified;
use Illuminate\Http\Request;
class BecomeSellerPageController extends Controller
{
public function index()
{
return view('web.themes.shella.pages.becomeseller.index');
}
public function store(BecomeSellerPageStoreRequest $request)
{
$user = User::create([
'first_name' => $request->first_name,
'last_name' => $request->last_name,
'email' => $request->email,
'password' => bcrypt($request->password),
'phone_number' => $request->phone_number,
]);
$user->addresses()->forceCreate([
'first_name' => $request->first_name,
'last_name' => $request->last_name,
'company_name' => $request->shop_name,
'street_address' => $request->street_address,
'zipcode' => 744000,
'city' => $request->city,
'phone_number' => $request->phone_number,
'is_default' => true,
'type' => 'work',
]);
EntrepreneursDocs::create([
'corporation_type' => $request->corporation_type,
'corporation_name' => $request->corporation_name,
'patent_data' => str_replace('public/', '', $request->file('patent_data')?->store('public/entrepreneur/patent_data') ?? 'public/'),
'user_id' => $user->id,
]);
sendSMSVerification($request->phone_number);
$verification = Verification::where(['username' => $request->email])->first();
$verification ? $verification->update(['code' => 12345]) : Verification::create(['username' => $request->email, 'code' => 12345]);
return to_route('web.become-seller.verification', ['phone' => $request->phone_number, 'email' => $request->email]);
}
public function verification()
{
return view('web.themes.shella.pages.becomeseller.verification');
}
public function verificationStore(Request $request)
{
$request->validate([
'phone_number' => ['required', 'integer', 'between:61000000,65999999'],
'phone_verification_code' => ['required', 'string'],
'email' => ['required', 'string', 'email', 'exists:users,email'],
'email_verification_code' => ['required', 'string'],
]);
if (Verification::where('username', $request->phone_number)->where('code', $request->phone_verification_code)->exists()
&& Verification::where('username', $request->email)->where('code', $request->email_verification_code)->exists()
) {
$user = User::where([
'phone_number' => $request->phone_number,
'email' => $request->email,
])->update([
'phone_number_verified_at' => now(),
'email_verified_at' => now(),
]);
event(new Verified($user));
session()->flash('sweet_alert_message', true);
session()->flash('sweet_alert_message_title', 'Üstünlikli ýerine ýetirildi');
session()->flash('sweet_alert_message_content', '');
session()->flash('sweet_alert_message_status', 'success');
return to_route('web.home');
}
return back()->withErrors([
'Verification' => 'could not pass validation',
]);
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace App\Http\Controllers\Web\Brand;
use App\Http\Controllers\Controller;
use App\Models\Ecommerce\Product\Brand\Brand;
use App\Repositories\Ecommerce\Product\ProductRepository;
use Illuminate\Http\Request;
class BrandController extends Controller
{
/**
* Display the specified resource.
*
* @return Application|Factory|View
*/
public function show(Brand $brand)
{
return to_route('web.brands.products', ['brand' => $brand->slug]);
}
/**
* Brand products
*/
public function products(Brand $brand, Request $request)
{
$products = ProductRepository::make($request)
->queryAsFromResource($brand)
->applyBasicQueries()
->applyFilters()
->applySorting()
->simplePaginate();
return $request->ajax()
? ProductRepository::ajaxPaginate($products)
: view('web.themes.shella.pages.brands.products.index', [
'brand' => $brand,
'products' => $products,
]);
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Http\Controllers\Web\Favorites;
use App\Http\Controllers\Controller;
class FavoritesPageController extends Controller
{
public function index()
{
$favs = auth()->user()->favorites()->with(['product' => ['media', 'brand']])->get();
$products = $favs->map(fn ($fav) => $fav->product);
return view('web.themes.shella.pages.favorites.index', [
'products' => $products,
'user_likes' => $products,
]);
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Http\Controllers\Web\Gallery;
use App\Http\Controllers\Controller;
use App\Models\Site\Gallery;
class GalleryController extends Controller
{
public function index()
{
return view('web.themes.shella.pages.galleries.index', [
'galleries' => Gallery::with('media')->enabled()->latest()->get(),
]);
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Http\Controllers\Web\Home;
use App\Http\Controllers\Controller;
use App\Models\Ecommerce\Product\Collection\Collection;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class HomeController extends Controller
{
/**
* Display a listing of the resource.
*
* @return Application|Factory|View
*/
public function index(Request $request)
{
$collections = Collection::query()
->ordered()
->where('is_visible', true)
->simplePaginate(
perPage: 6,
page: request('page') ?: 1
);
return $request->ajax()
? $this->ajaxPaginate($collections)
: view('web.themes.shella.pages.home.index', [
'collections' => $collections,
]);
}
/**
* Ajax paginate
*
* @param Collection $collections
*/
public function ajaxPaginate($collections): JsonResponse
{
return response()->json([
'pagination' => $collections,
'collections' => view('web.themes.trendyol.components.products.collection.collections', [
'collections' => $collections,
])->render(),
]);
}
public function userLikes()
{
$user_likes = auth()->check()
? auth()->user()->favorites()->with(['media', 'brand'])->get()
: collect();
cache()->put('user_favorites_likes_count', $user_likes->count());
return $user_likes;
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace App\Http\Controllers\Web;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class LikeController extends Controller
{
public function count()
{
return response()->json([
'count' => auth()->user()->favorites()->count(),
]);
}
public function userLikes()
{
return response()->rest(
auth()->user()->favorites()->with(['product' => ['media', 'brand']])->limit(4)->get()
->map(fn ($cartItem) => [
'id' => $cartItem->product->id,
'slug' => $cartItem->product->slug,
'name' => $cartItem->product->name,
'price_amount' => $cartItem->product->price_amount,
'brand_id' => $cartItem->product->brand_id,
'created_at' => $cartItem->product->created_at,
'brand' => $cartItem->product->brand,
'image' => $cartItem->product->thumbnail('200x200'),
]));
}
public function store(Request $request)
{
$request->validate([
'id' => ['required', 'integer', 'exists:products,id'],
]);
$data = auth()->user()->favorites()->where('product_id', $request->id)->first();
$status = $data
? $data->delete()
: auth()->user()->favorites()->create([
'user_id' => auth()->id(),
'product_id' => $request->id,
]);
return response()->json([
'attached' => is_bool($status) ? false : true,
], 200);
}
public function clear()
{
DB::table('favorites')->where('user_id', auth()->id())->delete();
return response()->rest();
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Http\Controllers\Web\Locale;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
class LocaleController extends Controller
{
/**
* @return RedirectResponse
*/
public function __invoke($locale)
{
if (array_key_exists($locale, config('app.locales'))) {
session()->put('locale', $locale);
}
return back();
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Http\Controllers\Web\Marketing;
use App\Http\Controllers\Controller;
use App\Models\CMS\Marketing\NewsletterUser;
use Illuminate\Http\Request;
class NewsletterSubscriptionController extends Controller
{
public function store(Request $request)
{
$this->validate($request, [
'email' => ['required', 'string', 'email', 'unique:newsletter_users'],
]);
NewsletterUser::create(['email' => $request->email]);
return $request->ajax() ? ['success' => true] : back()->with('message', 'Successfully subscribed');
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace App\Http\Controllers\Web;
use App\Http\Controllers\Controller;
class PaymentController extends Controller
{
//
public function index()
{
return view('web.payment');
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace App\Http\Controllers\Web\PostBranch;
use App\Http\Controllers\Controller;
use App\Models\Post\PostBranch;
class BranchController extends Controller
{
public function index()
{
return PostBranch::get(['id', 'province_id', 'name']);
}
}

Some files were not shown because too many files have changed in this diff Show More