This commit is contained in:
ct
2025-06-20 13:03:52 +08:00
parent eef45fdc9d
commit b502120091
22 changed files with 426 additions and 164 deletions

View File

@@ -26,7 +26,6 @@ public function up(): void
});
}
/**
* Reverse the migrations.
*/

View File

@@ -0,0 +1,32 @@
<?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::table('meme_medias', function (Blueprint $table) {
$table->json('action_keywords')->nullable();
$table->json('emotion_keywords')->nullable();
$table->json('misc_keywords')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('meme_medias', function (Blueprint $table) {
$table->dropColumn('action_keywords');
$table->dropColumn('emotion_keywords');
$table->dropColumn('misc_keywords');
});
}
};

View File

@@ -0,0 +1,34 @@
<?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::table('memes', function (Blueprint $table) {
$table->json('action_keywords')->nullable();
$table->json('emotion_keywords')->nullable();
$table->json('misc_keywords')->nullable();
$table->string('primary_keyword_type')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('memes', function (Blueprint $table) {
$table->dropColumn('action_keywords');
$table->dropColumn('emotion_keywords');
$table->dropColumn('misc_keywords');
$table->dropColumn('primary_keyword_type');
});
}
};

View File

@@ -3,9 +3,8 @@
namespace Database\Seeders;
use App\Helpers\FirstParty\AI\CloudflareAI;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\Category;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
@@ -20,19 +19,20 @@ public function run(): void
$jsonPath = database_path('seeders/data/json/category');
// Check if directory exists
if (!File::exists($jsonPath)) {
if (! File::exists($jsonPath)) {
$this->command->error("JSON directory not found: {$jsonPath}");
return;
}
// Get all JSON files except the schema file
$jsonFiles = File::glob($jsonPath . '/*.json');
$jsonFiles = File::glob($jsonPath.'/*.json');
$jsonFiles = array_filter($jsonFiles, function ($file) {
return !str_contains(basename($file), 'schema');
return ! str_contains(basename($file), 'schema');
});
$this->command->info('Starting to seed categories from JSON files...');
$this->command->info('Found ' . count($jsonFiles) . ' JSON files to process.');
$this->command->info('Found '.count($jsonFiles).' JSON files to process.');
foreach ($jsonFiles as $jsonFile) {
$this->processJsonFile($jsonFile);
@@ -55,36 +55,40 @@ private function processJsonFile(string $filePath): void
$data = json_decode($jsonContent, true);
if (json_last_error() !== JSON_ERROR_NONE) {
$this->command->error("Invalid JSON in file: {$fileName} - " . json_last_error_msg());
$this->command->error("Invalid JSON in file: {$fileName} - ".json_last_error_msg());
return;
}
// Validate JSON structure
if (!isset($data['category'])) {
if (! isset($data['category'])) {
$this->command->error("Missing 'category' key in file: {$fileName}");
return;
}
$categoryData = $data['category'];
// Validate required fields
if (!isset($categoryData['name']) || !isset($categoryData['description'])) {
if (! isset($categoryData['name']) || ! isset($categoryData['description'])) {
$this->command->error("Missing required fields (name/description) in file: {$fileName}");
return;
}
// Create main category
$mainCategory = $this->createMainCategory($categoryData, $data);
if (!$mainCategory) {
if (! $mainCategory) {
$this->command->error("Failed to create main category for file: {$fileName}");
return;
}
// Create subcategories
if (isset($categoryData['subcategories']) && is_array($categoryData['subcategories'])) {
foreach ($categoryData['subcategories'] as $index => $subcategoryData) {
if (!$this->createSubcategory($subcategoryData, $mainCategory, $data, $index)) {
if (! $this->createSubcategory($subcategoryData, $mainCategory, $data, $index)) {
$this->command->warn("Failed to create subcategory at index {$index} for file: {$fileName}");
}
}
@@ -92,10 +96,10 @@ private function processJsonFile(string $filePath): void
$this->command->info("✓ Successfully processed: {$fileName}");
} catch (\Exception $e) {
$this->command->error("Error processing {$fileName}: " . $e->getMessage());
$this->command->error("Error processing {$fileName}: ".$e->getMessage());
Log::error("CategorySeeder error for {$fileName}", [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
'trace' => $e->getTraceAsString(),
]);
}
}
@@ -113,6 +117,7 @@ private function createMainCategory(array $categoryData, array $originalData): ?
if ($existingCategory) {
$this->command->warn("Main category '{$categoryData['name']}' already exists. Skipping...");
return $existingCategory;
}
@@ -126,7 +131,7 @@ private function createMainCategory(array $categoryData, array $originalData): ?
'meme_angles' => null, // Main categories don't have meme_angles
'sample_captions' => null, // Main categories don't have sample_captions
'payload' => $originalData,
'embedding' => CloudflareAI::getVectorEmbeddingBgeSmall($categoryData['name'] . " " . $categoryData['description']),
'embedding' => CloudflareAI::getVectorEmbeddingBgeSmall($categoryData['name'].' '.$categoryData['description']),
]);
// Add keywords as tags
@@ -138,11 +143,12 @@ private function createMainCategory(array $categoryData, array $originalData): ?
return $category;
} catch (\Exception $e) {
$this->command->error("Error creating main category: " . $e->getMessage());
Log::error("Error creating main category", [
$this->command->error('Error creating main category: '.$e->getMessage());
Log::error('Error creating main category', [
'category_data' => $categoryData,
'error' => $e->getMessage()
'error' => $e->getMessage(),
]);
return null;
}
}
@@ -154,8 +160,9 @@ private function createSubcategory(array $subcategoryData, Category $parentCateg
{
try {
// Validate required subcategory fields
if (!isset($subcategoryData['name']) || !isset($subcategoryData['description'])) {
if (! isset($subcategoryData['name']) || ! isset($subcategoryData['description'])) {
$this->command->warn("Subcategory at index {$index} missing required fields (name/description). Skipping...");
return null;
}
@@ -166,6 +173,7 @@ private function createSubcategory(array $subcategoryData, Category $parentCateg
if ($existingSubcategory) {
$this->command->warn(" Subcategory '{$subcategoryData['name']}' already exists. Skipping...");
return $existingSubcategory;
}
@@ -174,8 +182,8 @@ private function createSubcategory(array $subcategoryData, Category $parentCateg
'subcategory' => $subcategoryData,
'parent_category' => [
'name' => $parentCategory->name,
'description' => $parentCategory->description
]
'description' => $parentCategory->description,
],
];
// Create the subcategory using the correct nested set method
@@ -189,7 +197,7 @@ private function createSubcategory(array $subcategoryData, Category $parentCateg
'subcategories' => null, // Subcategories don't have subcategories
'payload' => $subcategoryPayload,
'parent_id' => $parentCategory->id, // Set parent_id directly
'embedding' => CloudflareAI::getVectorEmbeddingBgeSmall($subcategoryData['name'] . " " . $subcategoryData['description']),
'embedding' => CloudflareAI::getVectorEmbeddingBgeSmall($subcategoryData['name'].' '.$subcategoryData['description']),
]);
// Add keywords as tags
@@ -201,12 +209,13 @@ private function createSubcategory(array $subcategoryData, Category $parentCateg
return $subcategory;
} catch (\Exception $e) {
$this->command->error("Error creating subcategory at index {$index}: " . $e->getMessage());
Log::error("Error creating subcategory", [
$this->command->error("Error creating subcategory at index {$index}: ".$e->getMessage());
Log::error('Error creating subcategory', [
'subcategory_data' => $subcategoryData,
'parent_id' => $parentCategory->id,
'error' => $e->getMessage()
'error' => $e->getMessage(),
]);
return null;
}
}
@@ -219,11 +228,11 @@ private function attachKeywordsAsTags(Category $category, array $keywords): void
try {
$category->attachTags($keywords, 'category');
} catch (\Exception $e) {
$this->command->warn("Failed to attach tags to category '{$category->name}': " . $e->getMessage());
Log::warning("Failed to attach tags", [
$this->command->warn("Failed to attach tags to category '{$category->name}': ".$e->getMessage());
Log::warning('Failed to attach tags', [
'category_id' => $category->id,
'keywords' => $keywords,
'error' => $e->getMessage()
'error' => $e->getMessage(),
]);
}
}

View File

@@ -52,7 +52,7 @@ public function run(): void
$csv_path = database_path('seeders/data/webm_metadata.csv');
$meme_data = $this->parseCsvFile($csv_path);
$this->command->info('📊 Found ' . count($meme_data) . ' memes to import');
$this->command->info('📊 Found '.count($meme_data).' memes to import');
// Process records individually for PostgreSQL compatibility
$total_processed = 0;
@@ -60,12 +60,10 @@ public function run(): void
$total_failed = 0;
foreach ($meme_data as $index => $meme_record) {
$this->command->info('Processing ' . ($index + 1) . '/' . count($meme_data) . ': ' . $meme_record['filename']);
$this->command->info('Processing '.($index + 1).'/'.count($meme_data).': '.$meme_record['filename']);
$meme_record['keywords'] = $this->stringToCleanArray($meme_record['keywords']);
try {
// Check for duplicates OUTSIDE of transaction
$base_filename = pathinfo($meme_record['filename'], PATHINFO_FILENAME);
@@ -158,6 +156,7 @@ private function stringToCleanArray($string)
return array_filter(array_map(function ($item) {
$item = trim($item); // Remove whitespace
$item = preg_replace('/[^\w\s]/', '', $item); // Remove punctuation
return trim(preg_replace('/\s+/', ' ', $item)); // Clean extra spaces
}, explode(',', $string)), function ($value) {
return $value !== '';
@@ -192,13 +191,13 @@ private function importSingleMeme(array $meme_record): bool
'save_url', // Mode: just save URL reference
null, // Auto-generate filename
'r2', // Disk (not used for URL mode)
trim($meme_record['name']) . " ({$format})", // Name with format
trim($meme_record['name'])." ({$format})", // Name with format
null, // No specific user
$config['mime'] // MIME type
);
$media_uuids[$format . '_uuid'] = $media->uuid;
$media_urls[$format . '_url'] = $url;
$media_uuids[$format.'_uuid'] = $media->uuid;
$media_urls[$format.'_url'] = $url;
} catch (\Exception $e) {
$this->command->error("Failed to create {$format} media for {$meme_record['filename']}: {$e->getMessage()}");
throw $e;
@@ -208,7 +207,7 @@ private function importSingleMeme(array $meme_record): bool
// Generate embedding
try {
$embedding = CloudflareAI::getVectorEmbeddingBgeSmall(
$meme_record['name'] . ' ' . $meme_record['description'] . ' ' . implode(' ', $meme_record['keywords'])
$meme_record['name'].' '.$meme_record['description'].' '.implode(' ', $meme_record['keywords'])
);
} catch (\Exception $e) {
$this->command->warn("Failed to generate embedding for {$meme_record['filename']}: {$e->getMessage()}");
@@ -256,7 +255,7 @@ private function importSingleMeme(array $meme_record): bool
// Add keywords as tags
$this->attachKeywordsAsTags($meme_media, $meme_record['keywords']);
$this->command->info('✅ Imported: ' . trim($meme_record['name']));
$this->command->info('✅ Imported: '.trim($meme_record['name']));
return true;
} catch (\Exception $e) {
@@ -270,11 +269,11 @@ private function attachKeywordsAsTags(MemeMedia $meme_media, array $keywords): v
try {
$meme_media->attachTags($keywords, 'meme_media');
} catch (\Exception $e) {
$this->command->warn("Failed to attach tags to meme media '{$meme_media->name}': " . $e->getMessage());
Log::warning("Failed to attach tags", [
$this->command->warn("Failed to attach tags to meme media '{$meme_media->name}': ".$e->getMessage());
Log::warning('Failed to attach tags', [
'category_id' => $meme_media->id,
'keywords' => $keywords,
'error' => $e->getMessage()
'error' => $e->getMessage(),
]);
}
}
@@ -284,7 +283,7 @@ private function attachKeywordsAsTags(MemeMedia $meme_media, array $keywords): v
*/
private function generateCdnUrl(string $base_filename, string $extension): string
{
return self::CDN_BASE_URL . "/{$extension}/{$base_filename}.{$extension}";
return self::CDN_BASE_URL."/{$extension}/{$base_filename}.{$extension}";
}
/**