3 Commits 27764096fa ... 85def13110

Autor SHA1 Mensaje Fecha
  Alexander Musikhin 85def13110 Http functional moved in abstract class SearchDummyJson hace 2 años
  Alexander Musikhin 85cab1a5c2 command GetProducts hace 2 años
  Alexander Musikhin 4fa2642883 Refactored models and migrations hace 2 años

+ 0 - 77
app/Console/Commands/GetProducts.php

@@ -1,77 +0,0 @@
-<?php
-
-namespace App\Console\Commands;
-
-use App\Models\Product;
-use Illuminate\Console\Command;
-use Illuminate\Support\Facades\Http;
-use Illuminate\Support\Str;
-
-class GetProducts extends Command
-{
-    /**
-     * The name and signature of the console command.
-     *
-     * @var string
-     */
-    protected $signature = 'app:get-products';
-
-    /**
-     * The console command description.
-     *
-     * @var string
-     */
-    protected $description = 'Get products from DummyJSON';
-
-    /**
-     * Execute the console command.
-     */
-    public function handle()
-    {
-        $url = 'https://dummyjson.com/products/search';
-        $limit = 5;
-        $skip = 0;
-        $total = 0;
-
-        do{
-            $response = Http::retry(10, 15)
-                ->get($url, ['q' => 'iphone', 'limit' => $limit, 'skip' => $skip]);
-            if($response->successful()){
-                $json = $response->json();
-                $products = $response->json('products');
-                $skip = $response->json('skip');
-                $total = $response->json('total');
-
-                $this->upsertProducts($products);
-
-
-                $skip += $limit;
-                $this->info('Received ' . count($products) . ' records');
-            } else {
-                $this->error('Error on HTTP request!');
-
-            }
-        } while($skip < $total);
-        $this->info('Total: ' . $total);
-    }
-
-    protected function upsertProducts($products):void
-    {
-        foreach ($products as $product){
-
-            $tags = $product['tags'];
-//            $reviews =
-
-            $product = $this->arrayKeysCamelToSnake($product);
-            Product::query()->updateOrInsert(['id' => $product['id']], $product);
-        }
-    }
-
-    protected function arrayKeysCamelToSnake($arr): array
-    {
-        $ret = [];
-        foreach ($arr as $k => $v)
-            $ret[Str::snake($k)] = $v;
-        return $ret;
-    }
-}

+ 79 - 0
app/Console/Commands/SearchDummyJson.php

@@ -0,0 +1,79 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Models\Tag;
+use Illuminate\Console\Command;
+use Illuminate\Http\Client\ConnectionException;
+use Illuminate\Support\Facades\Http;
+use Illuminate\Support\Str;
+
+abstract class SearchDummyJson extends Command
+{
+    /**
+     * Limit fetching data per iteration
+     *
+     * @var int
+     */
+    protected int $limit = 5;
+
+    /**
+     * Get or create tags ids from Tag model
+     *
+     * @param $tags
+     * @return array
+     */
+    protected function getTagsIds($tags):array
+    {
+        $ids = [];
+        foreach ($tags as $tag){
+            $t = Tag::query()->firstOrCreate(['name' => $tag]);
+            $ids[] = $t->id;
+        }
+        return $ids;
+    }
+
+    /**
+     * Convert array keys from camelCase to snake_case
+     *
+     * @param $arr
+     * @return array
+     */
+    protected function arrayKeysCamelToSnake($arr): array
+    {
+        $ret = [];
+        foreach ($arr as $k => $v)
+            $ret[Str::snake($k)] = $v;
+        return $ret;
+    }
+
+    /**
+     * Get data from DummyJSON by name
+     *
+     * @param $name
+     * @param $q
+     * @return array
+     * @throws ConnectionException
+     */
+    protected function getJson($name, $q): array
+    {
+        $skip = $total = 0;
+        $data = [];
+        do{
+            $response = Http::retry(10, 15)
+                ->get('https://dummyjson.com/' . $name . '/search', ['q' => $q, 'limit' => $this->limit, 'skip' => $skip]);
+
+            if($response->successful()){
+                $data = array_merge($data, $response->json($name));
+                $skip = $response->json('skip');
+                $total = $response->json('total');
+                $skip += $this->limit;
+                $this->info('Received ' . count($data) . ' / ' . $total);
+            } else {
+                $this->error('Error on HTTP request!');
+            }
+        } while($skip < $total);
+
+        return $data;
+    }
+}

+ 90 - 0
app/Console/Commands/SearchProducts.php

@@ -0,0 +1,90 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Models\Product;
+use App\Models\ProductImage;
+use App\Models\ProductReview;
+use Illuminate\Http\Client\ConnectionException;
+
+class SearchProducts extends SearchDummyJson
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'app:get-products';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Get products from DummyJSON';
+
+    /**
+     * Execute the console command.
+     */
+    public function handle(): void
+    {
+        try {
+            $products = $this->getJson('products', 'iPhone');
+            $this->upsertProducts($products);
+            $this->info('Done!');
+        } catch (ConnectionException $e) {
+            $this->error('Error fetching data!');
+            dump($e);
+        }
+    }
+
+    /**
+     * Prepare data and update or insert into DB
+     * todo: need validation
+     *
+     * @param $products
+     * @return void
+     */
+    protected function upsertProducts($products): void
+    {
+        foreach ($products as $product) {
+
+            // remember foreign tables data
+            $tags = $product['tags'];
+            $reviews = $product['reviews'];
+            $images = $product['images'];
+
+            // move some subarray elements to root
+            $product['width'] = $product['dimensions']['width'];
+            $product['height'] = $product['dimensions']['height'];
+            $product['depth'] = $product['dimensions']['depth'];
+            $product['barcode'] = $product['meta']['barcode'];
+            $product['qr_code'] = $product['meta']['qrCode'];
+
+            // remove extra data
+            unset($product['tags'], $product['reviews'], $product['images'], $product['dimensions'], $product['meta']);
+
+            $product = $this->arrayKeysCamelToSnake($product);
+            Product::query()->updateOrInsert(['id' => $product['id']], $product);
+            $p = Product::find($product['id']);
+
+            // detach all tags and attach received
+            $p->tags()->detach();
+            $p->tags()->attach($this->getTagsIds($tags));
+
+            // delete all reviews and create from received
+            ProductReview::query()->where('product_id', '=', $p->id)->delete();
+            foreach ($reviews as $review) {
+                $review['product_id'] = $p->id;
+                ProductReview::query()->create($this->arrayKeysCamelToSnake($review));
+            }
+
+            // delete all images and create from received
+            ProductImage::query()->where('product_id', '=', $p->id)->delete();
+            foreach ($images as $image) {
+                ProductImage::create(['product_id' => $p->id, 'url' => $image]);
+            }
+        }
+    }
+
+}

+ 1 - 0
app/Models/ProductImage.php

@@ -7,5 +7,6 @@ use Illuminate\Database\Eloquent\Model;
 
 class ProductImage extends Model
 {
+    public $timestamps = false;
     protected $fillable = ['product_id', 'url'];
 }

+ 2 - 1
app/Models/ProductReview.php

@@ -6,5 +6,6 @@ use Illuminate\Database\Eloquent\Model;
 
 class ProductReview extends Model
 {
-    protected $fillable = ['product_id', 'rating', 'comment', 'reviewer_name', 'reviewer_email'];
+    public $timestamps = false;
+    protected $fillable = ['product_id', 'rating', 'comment', 'date', 'reviewer_name', 'reviewer_email'];
 }

+ 1 - 0
app/Models/Tag.php

@@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany;
 
 class Tag extends Model
 {
+    public $timestamps = false;
     protected $fillable = ['name'];
 
     public function products(): BelongsToMany

+ 1 - 1
database/migrations/2024_08_29_102201_create_products_table.php

@@ -27,7 +27,7 @@ return new class extends Migration
             $table->decimal('height');
             $table->decimal('depth');
             $table->string('warranty_information');
-            $table->string('shipment_information');
+            $table->string('shipping_information');
             $table->string('availability_status');
             $table->string('return_policy');
             $table->string('minimum_order_quantity');

+ 1 - 1
database/migrations/2024_08_29_103420_create_tags_table.php

@@ -13,7 +13,7 @@ return new class extends Migration
     {
         Schema::create('tags', function (Blueprint $table) {
             $table->id();
-            $table->string('name');
+            $table->string('name')->unique();
         });
     }
 

+ 2 - 3
database/migrations/2024_08_29_103421_create_product_tags_table.php → database/migrations/2024_08_29_103421_create_product_tag_table.php

@@ -11,10 +11,9 @@ return new class extends Migration
      */
     public function up(): void
     {
-        Schema::create('product_tags', function (Blueprint $table) {
+        Schema::create('product_tag', function (Blueprint $table) {
             $table->unsignedBigInteger('product_id');
             $table->unsignedBigInteger('tag_id');
-            $table->timestamps();
         });
     }
 
@@ -23,6 +22,6 @@ return new class extends Migration
      */
     public function down(): void
     {
-        Schema::dropIfExists('product_tags');
+        Schema::dropIfExists('product_tag');
     }
 };

+ 3 - 3
database/migrations/2024_08_29_104219_create_reviews_table.php → database/migrations/2024_08_29_104219_create_product_reviews_table.php

@@ -11,14 +11,14 @@ return new class extends Migration
      */
     public function up(): void
     {
-        Schema::create('reviews', function (Blueprint $table) {
+        Schema::create('product_reviews', function (Blueprint $table) {
             $table->id();
             $table->foreignId('product_id')->constrained('products')->cascadeOnDelete();
             $table->unsignedInteger('rating');
             $table->text('comment');
+            $table->timestamp('date');
             $table->string('reviewer_name');
             $table->string('reviewer_email');
-            $table->timestamps();
         });
     }
 
@@ -27,6 +27,6 @@ return new class extends Migration
      */
     public function down(): void
     {
-        Schema::dropIfExists('reviews');
+        Schema::dropIfExists('product_reviews');
     }
 };

+ 0 - 1
database/migrations/2024_08_29_104518_create_product_images_table.php

@@ -15,7 +15,6 @@ return new class extends Migration
             $table->id();
             $table->foreignId('product_id')->constrained('products')->cascadeOnDelete();
             $table->text('url');
-            $table->timestamps();
         });
     }