feature: product creation and image upload
create image upload endpoint create product creation endpoint create get product categories endpoint
This commit is contained in:
parent
30bc4a0cf3
commit
684b7585bb
16
backend/app/Actions/CreateProductAction.php
Normal file
16
backend/app/Actions/CreateProductAction.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions;
|
||||
|
||||
use App\Data\UploadImageDTO;
|
||||
|
||||
final readonly class CreateProductAction
|
||||
{
|
||||
public function execute(UploadImageDTO $uploadImageDTO): void
|
||||
{
|
||||
$path = $uploadImageDTO->image->store('public/images');
|
||||
$uploadImageDTO->product->images()->create([
|
||||
'path' => $path,
|
||||
]);
|
||||
}
|
||||
}
|
||||
19
backend/app/Actions/GetAllProductCategory.php
Normal file
19
backend/app/Actions/GetAllProductCategory.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions;
|
||||
|
||||
use App\Data\ProductCategoryDTO;
|
||||
use App\Models\ProductCategory;
|
||||
|
||||
final readonly class GetAllProductCategory
|
||||
{
|
||||
/**
|
||||
* Execute the action.
|
||||
*/
|
||||
public function execute(): array
|
||||
{
|
||||
return ProductCategory::all(columns: ['id', 'name', 'slug'])
|
||||
->map(fn ($category) => ProductCategoryDTO::fromModel($category))
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
16
backend/app/Actions/UploadImageAction.php
Normal file
16
backend/app/Actions/UploadImageAction.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions;
|
||||
|
||||
use App\Data\UploadImageDTO;
|
||||
|
||||
final readonly class UploadImageAction
|
||||
{
|
||||
public function execute(UploadImageDTO $uploadImageDTO): void
|
||||
{
|
||||
$path = $uploadImageDTO->image->store('public/images');
|
||||
$uploadImageDTO->product->images()->create([
|
||||
'path' => $path,
|
||||
]);
|
||||
}
|
||||
}
|
||||
36
backend/app/Data/ProductCategoryDTO.php
Normal file
36
backend/app/Data/ProductCategoryDTO.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Data;
|
||||
|
||||
use App\Contracts\OutputDataTransferObject;
|
||||
use App\Models\ProductCategory;
|
||||
|
||||
final readonly class ProductCategoryDTO implements OutputDataTransferObject
|
||||
{
|
||||
public function __construct(
|
||||
public int $id,
|
||||
public string $name,
|
||||
public string $slug,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'slug' => $this->slug,
|
||||
];
|
||||
}
|
||||
|
||||
public static function fromModel(ProductCategory $category): self
|
||||
{
|
||||
return new self(
|
||||
id: $category->id,
|
||||
name: $category->name,
|
||||
slug: $category->slug,
|
||||
);
|
||||
}
|
||||
}
|
||||
26
backend/app/Data/ProductImageDTO.php
Normal file
26
backend/app/Data/ProductImageDTO.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Data;
|
||||
|
||||
use App\Contracts\OutputDataTransferObject;
|
||||
|
||||
final readonly class ProductImageDTO implements OutputDataTransferObject
|
||||
{
|
||||
public function __construct(
|
||||
public ?int $id = null,
|
||||
public ?string $path = null,
|
||||
public ?int $productId = null,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'path' => $this->path,
|
||||
'productId' => $this->productId,
|
||||
];
|
||||
}
|
||||
}
|
||||
22
backend/app/Data/UploadImageDTO.php
Normal file
22
backend/app/Data/UploadImageDTO.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Data;
|
||||
|
||||
use App\Models\Product;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
|
||||
readonly class UploadImageDTO
|
||||
{
|
||||
public function __construct(
|
||||
public UploadedFile $image,
|
||||
public Product $product,
|
||||
) {}
|
||||
|
||||
public static function fromRequest(array $data): self
|
||||
{
|
||||
return new self(
|
||||
image: $data['image'],
|
||||
product: Product::find($data['product_id']),
|
||||
);
|
||||
}
|
||||
}
|
||||
13
backend/app/Http/Controllers/ProductCategoryController.php
Normal file
13
backend/app/Http/Controllers/ProductCategoryController.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\GetAllProductCategory;
|
||||
|
||||
class ProductCategoryController extends Controller
|
||||
{
|
||||
public function index(GetAllProductCategory $action)
|
||||
{
|
||||
return $action->execute();
|
||||
}
|
||||
}
|
||||
23
backend/app/Http/Controllers/ProductController.php
Normal file
23
backend/app/Http/Controllers/ProductController.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\CreateProductRequest;
|
||||
use App\Models\Product;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ProductController extends Controller
|
||||
{
|
||||
public function index() {}
|
||||
|
||||
public function store(CreateProductRequest $request)
|
||||
{
|
||||
return Product::create($request->validated());
|
||||
}
|
||||
|
||||
public function show(Product $product) {}
|
||||
|
||||
public function update(Request $request, Product $product) {}
|
||||
|
||||
public function destroy(Product $product) {}
|
||||
}
|
||||
30
backend/app/Http/Controllers/ProductImagesController.php
Normal file
30
backend/app/Http/Controllers/ProductImagesController.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\UploadImageAction;
|
||||
use App\Data\UploadImageDTO;
|
||||
use App\Http\Requests\UploadImageRequest;
|
||||
use App\Models\ProductImages;
|
||||
|
||||
class ProductImagesController extends Controller
|
||||
{
|
||||
public function store(UploadImageRequest $request, UploadImageAction $action)
|
||||
{
|
||||
$action->execute(UploadImageDTO::fromRequest($request->validated()));
|
||||
|
||||
return response()->json(['message' => 'Image uploaded successfully']);
|
||||
}
|
||||
|
||||
public function show(ProductImages $productImages)
|
||||
{
|
||||
return $productImages;
|
||||
}
|
||||
|
||||
public function destroy(ProductImages $productImages)
|
||||
{
|
||||
$productImages->delete();
|
||||
|
||||
return response()->json();
|
||||
}
|
||||
}
|
||||
24
backend/app/Http/Requests/CreateProductRequest.php
Normal file
24
backend/app/Http/Requests/CreateProductRequest.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class CreateProductRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'title' => 'required|string|max:255',
|
||||
'description' => 'required|string',
|
||||
'product_category_id' => 'required|exists:product_categories,id',
|
||||
'actual_price' => 'required|numeric|min:0',
|
||||
'list_price' => 'required|numeric|min:0',
|
||||
];
|
||||
}
|
||||
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
29
backend/app/Http/Requests/UploadImageRequest.php
Normal file
29
backend/app/Http/Requests/UploadImageRequest.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UploadImageRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'image' => 'required|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
|
||||
'product_id' => 'required|exists:products,id',
|
||||
];
|
||||
}
|
||||
}
|
||||
28
backend/app/Models/Product.php
Normal file
28
backend/app/Models/Product.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Product extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'title',
|
||||
'actual_price',
|
||||
'list_price',
|
||||
'description',
|
||||
'product_category_id',
|
||||
];
|
||||
|
||||
public function productCategory(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ProductCategory::class);
|
||||
}
|
||||
|
||||
public function images(): HasMany
|
||||
{
|
||||
return $this->hasMany(ProductImage::class, 'product_id', 'id');
|
||||
}
|
||||
}
|
||||
10
backend/app/Models/ProductCategory.php
Normal file
10
backend/app/Models/ProductCategory.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ProductCategory extends Model
|
||||
{
|
||||
//
|
||||
}
|
||||
19
backend/app/Models/ProductImage.php
Normal file
19
backend/app/Models/ProductImage.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class ProductImage extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'path',
|
||||
'product_id',
|
||||
];
|
||||
|
||||
public function product(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Product::class);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('product_categories', function (Blueprint $table) {
|
||||
$table->id()->index();
|
||||
$table->string('name')->unique();
|
||||
$table->string('slug')->unique();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('product_categories');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use App\Models\ProductCategory;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('products', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('title');
|
||||
$table->text('description');
|
||||
$table->decimal('actual_price', 10, 2);
|
||||
$table->decimal('list_price', 10, 2);
|
||||
$table->foreignIdFor(ProductCategory::class)->index();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('products');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Product;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('product_images', function (Blueprint $table) {
|
||||
$table->id()->index();
|
||||
$table->string('path');
|
||||
$table->foreignIdFor(Product::class)->index();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('product_images');
|
||||
}
|
||||
};
|
||||
28
backend/database/seeders/ProductCategorySeeder.php
Normal file
28
backend/database/seeders/ProductCategorySeeder.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\ProductCategory;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ProductCategorySeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$names = [
|
||||
'Electronics', 'Clothing', 'Home', 'Books',
|
||||
'Sports', 'Toys', 'Health', 'Beauty', 'Automotive',
|
||||
];
|
||||
|
||||
// Transform the flat names into an array of associative arrays
|
||||
$categories = collect($names)->map(function ($name) {
|
||||
return [
|
||||
'name' => $name,
|
||||
'slug' => Str::slug($name),
|
||||
];
|
||||
})->toArray();
|
||||
|
||||
ProductCategory::upsert($categories, ['name'], ['slug']);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,9 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\AuthenticatedUserController;
|
||||
use App\Http\Controllers\ProductCategoryController;
|
||||
use App\Http\Controllers\ProductController;
|
||||
use App\Http\Controllers\ProductImagesController;
|
||||
use App\Http\Controllers\RegisteredUserController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
@ -11,4 +14,7 @@
|
||||
Route::middleware('auth:sanctum')->group(function () {
|
||||
Route::get('/user', [AuthenticatedUserController::class, 'show']);
|
||||
Route::post('/logout', [AuthenticatedUserController::class, 'destroy']);
|
||||
Route::post('/upload/images', action: [ProductImagesController::class, 'store']);
|
||||
Route::apiResource('products', ProductController::class);
|
||||
});
|
||||
Route::get('/categories', [ProductCategoryController::class, 'index']);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user