This commit is contained in:
2025-09-25 03:03:31 +05:00
commit ae480cf2f6
2768 changed files with 1485826 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
<?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
*/
public function checkForUpdate(CheckForUpdateRequest $request): JsonResponse
{
$app_version = AppVersion::latest()->where('os', $request->os)->first();
if (! $app_version || $request->version === $app_version->version) {
return $this->latestVersion();
}
if ($request->version < $app_version->version && $app_version->important) {
return $this->requiredToUpdate();
}
if ((int) $request->version < (int) $app_version->version) {
return $this->optionalToUpdate();
}
return $this->versionNotFound();
}
/**
* Latest version
*/
public function latestVersion(): JsonResponse
{
return response()->json(['update' => 'latest']);
}
/**
* Required to update
*/
public function requiredToUpdate(): JsonResponse
{
return response()->json(['update' => 'required']);
}
/**
* Update not required, but should be
*/
public function optionalToUpdate(): JsonResponse
{
return response()->json(['update' => 'optional']);
}
/**
* App version not found
*/
public function versionNotFound(): JsonResponse
{
return response()->json(['update' => 'version-not-found']);
}
}

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,35 @@
<?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 [
'phone_number' => ['required', 'integer', 'between:61000000,71999999', 'unique:users,phone_number'],
'name' => ['required', 'string', 'max:255'],
'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 ? $name[0] : '',
]);
}
}

View File

@@ -0,0 +1,80 @@
<?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;
class AuthController extends Controller
{
/**
* (Auth) Guest token (walk-in-user)
*/
public function guestToken(): JsonResponse
{
return response()->rest(
data: UserRepository::guestUser()->createToken(bin2hex(random_bytes(25)))->plainTextToken,
code: 201
);
}
/**
* Register user
*/
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)
);
}
/**
* (Auth) Login
*/
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)
);
}
/**
* (Auth) Verify the code
*/
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,79 @@
<?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()
->each(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 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,90 @@
<?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 int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
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,59 @@
<?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));
}
$query
->orWhere('products.name', '~*', $searchQuery)
->orWhere('products.sku', '~*', $searchQuery)
->orWhere('products.barcode', '~*', $searchQuery);
})
->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()->update([
'first_name' => $request->name,
'phone_number' => $request->phone_number,
]);
auth()->user()->options->set('address', $request->address);
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,
];
}
}