diff --git a/app/Helpers/Global/geo_helper.php b/app/Helpers/Global/geo_helper.php new file mode 100644 index 0000000..76fa1d8 --- /dev/null +++ b/app/Helpers/Global/geo_helper.php @@ -0,0 +1,51 @@ +environment() == 'local') { + return config('platform.general.dev_default_ip'); + } + + if (getenv('HTTP_CF_CONNECTING_IP')) { + $ip = getenv('HTTP_CF_CONNECTING_IP'); + } elseif (isset($_SERVER['HTTP_CF_CONNECTING_IP'])) { + $ip = $_SERVER['HTTP_CF_CONNECTING_IP']; + } + + if (! is_empty($ip)) { + return $ip; + } + + $ip_add_set = null; + + if (getenv('HTTP_CLIENT_IP')) { + $ip_add_set = getenv('HTTP_CLIENT_IP'); + } elseif (getenv('HTTP_X_FORWARDED_FOR')) { + $ip_add_set = getenv('HTTP_X_FORWARDED_FOR'); + } elseif (getenv('HTTP_X_FORWARDED')) { + $ip_add_set = getenv('HTTP_X_FORWARDED'); + } elseif (getenv('HTTP_FORWARDED_FOR')) { + $ip_add_set = getenv('HTTP_FORWARDED_FOR'); + } elseif (getenv('HTTP_FORWARDED')) { + $ip_add_set = getenv('HTTP_FORWARDED'); + } elseif (getenv('REMOTE_ADDR')) { + $ip_add_set = getenv('REMOTE_ADDR'); + } elseif (isset($_SERVER['REMOTE_ADDR'])) { + $ip_add_set = $_SERVER['REMOTE_ADDR']; + } else { + $ip_add_set = '127.0.0.0'; + } + + $ip_add_set = explode(',', $ip_add_set); + + return $ip_add_set[0]; + } +} diff --git a/app/Helpers/Global/helpers.php b/app/Helpers/Global/helpers.php index 36ab096..f7495cc 100644 --- a/app/Helpers/Global/helpers.php +++ b/app/Helpers/Global/helpers.php @@ -4,3 +4,4 @@ include 'cast_helpers.php'; include 'generation_helpers.php'; include 'user_access_helpers.php'; +include 'geo_helper.php'; diff --git a/app/Http/Controllers/FrontMediaController.php b/app/Http/Controllers/FrontMediaController.php index 59d89fc..4836497 100644 --- a/app/Http/Controllers/FrontMediaController.php +++ b/app/Http/Controllers/FrontMediaController.php @@ -6,6 +6,7 @@ use App\Models\BackgroundMedia; use App\Models\Meme; use App\Models\MemeMedia; +use App\Services\TrackingAnalyticsService; use Illuminate\Http\Request; class FrontMediaController extends Controller @@ -61,6 +62,16 @@ public function searchMemes(Request $request) $query = $request->input('query', ''); $limit = 30; + // Track the search if there's a query + if (! empty($query)) { + try { + $trackingService = app(TrackingAnalyticsService::class); + $trackingService->trackSearch('meme', $query); + } catch (\Exception $e) { + // Silently ignore tracking errors + } + } + if (empty($query)) { // Return random memes if no search query $memes = MemeMedia::where('type', 'video')->where('sub_type', 'overlay')->take($limit)->inRandomOrder()->get(); @@ -108,6 +119,16 @@ public function searchBackgrounds(Request $request) $query = $request->input('query', ''); $limit = 30; + // Track the search if there's a query + if (! empty($query)) { + try { + $trackingService = app(TrackingAnalyticsService::class); + $trackingService->trackSearch('background', $query); + } catch (\Exception $e) { + // Silently ignore tracking errors + } + } + if (empty($query)) { // Return random backgrounds if no search query $backgrounds = BackgroundMedia::where('status', 'completed')->take($limit)->inRandomOrder()->get(); @@ -134,4 +155,99 @@ public function searchBackgrounds(Request $request) ], ]); } + + public function selectMeme(Request $request) + { + try { + $request->validate([ + 'meme_ids' => 'required|string', + ]); + + // Decode the hashid to get the actual database ID + $memeMediaId = hashids_decode($request->meme_ids); + + if ($memeMediaId) { + // Get the meme media for the name + $memeMedia = MemeMedia::find($memeMediaId); + + if ($memeMedia) { + // Track the content selection + $trackingService = app(TrackingAnalyticsService::class); + $trackingService->trackContentSelection( + 'meme', + $memeMediaId, + $memeMedia->name, + 'browse' + ); + } + } + } catch (\Exception $e) { + // Silently ignore all errors + } + + return response()->json(['success' => true]); + } + + public function selectBackground(Request $request) + { + try { + $request->validate([ + 'background_ids' => 'required|string', + ]); + + // Decode the hashid to get the actual database ID + $backgroundMediaId = hashids_decode($request->background_ids); + + if ($backgroundMediaId) { + // Get the background media for the name + $backgroundMedia = BackgroundMedia::find($backgroundMediaId); + + if ($backgroundMedia) { + // Track the content selection + $trackingService = app(TrackingAnalyticsService::class); + $trackingService->trackContentSelection( + 'background', + $backgroundMediaId, + $backgroundMedia->prompt ?? 'Background', + 'browse' + ); + } + } + } catch (\Exception $e) { + // Silently ignore all errors + } + + return response()->json(['success' => true]); + } + + public function saveMeme(Request $request) + { + try { + $request->validate([ + 'meme_media_ids' => 'nullable|string', + 'background_media_ids' => 'nullable|string', + 'caption_texts' => 'required|array', + 'caption_texts.*' => 'string', + 'is_premium_export' => 'boolean', + ]); + + // Convert hashids to actual database IDs + $memeMediaId = $request->meme_media_ids ? hashids_decode($request->meme_media_ids) : null; + $backgroundMediaId = $request->background_media_ids ? hashids_decode($request->background_media_ids) : null; + + // Track the save action + $trackingService = app(TrackingAnalyticsService::class); + $trackingService->trackSave( + null, // meme_id + $memeMediaId, + $backgroundMediaId, + $request->caption_texts, + $request->is_premium_export ?? false + ); + } catch (\Exception $e) { + // Silently ignore all errors + } + + return response()->json(['success' => true]); + } } diff --git a/app/Http/Controllers/UserExportController.php b/app/Http/Controllers/UserExportController.php index a9cd9b6..fded0a2 100644 --- a/app/Http/Controllers/UserExportController.php +++ b/app/Http/Controllers/UserExportController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers; use App\Models\ExportToken; +use App\Services\TrackingAnalyticsService; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Str; @@ -11,6 +12,17 @@ class UserExportController extends Controller { public function premiumExportRequest(Request $request) { + $request->validate([ + 'meme_media_ids' => 'nullable|string', // Accept hashid string + 'background_media_ids' => 'nullable|string', // Accept hashid string + 'caption_texts' => 'required|array', + 'caption_texts.*' => 'string', + ]); + + // Convert hashids to actual database IDs + $memeMediaId = $request->meme_media_ids ? hashids_decode($request->meme_media_ids) : null; + $backgroundMediaId = $request->background_media_ids ? hashids_decode($request->background_media_ids) : null; + $user = Auth::user(); $user->load('user_usage'); @@ -33,9 +45,22 @@ public function premiumExportRequest(Request $request) 'token' => Str::uuid()->toString(), 'is_premium' => true, 'credits_reserved' => 1, + 'metadata' => [ + 'meme_media_id' => $memeMediaId, + 'background_media_id' => $backgroundMediaId, + 'caption_texts' => $request->caption_texts, + ], 'expires_at' => now()->addMinutes(30), ]); + // Track the export + try { + $trackingService = app(TrackingAnalyticsService::class); + $trackingService->trackExport(null, $memeMediaId, $backgroundMediaId, $request->caption_texts); + } catch (\Exception $e) { + // Silently ignore tracking errors + } + $user->user_usage->refresh(); return response()->json([ @@ -92,6 +117,17 @@ public function premiumExportComplete(Request $request) public function basicExportRequest(Request $request) { + $request->validate([ + 'meme_media_ids' => 'nullable|string', // Accept hashid string + 'background_media_ids' => 'nullable|string', // Accept hashid string + 'caption_texts' => 'required|array', + 'caption_texts.*' => 'string', + ]); + + // Convert hashids to actual database IDs + $memeMediaId = $request->meme_media_ids ? hashids_decode($request->meme_media_ids) : null; + $backgroundMediaId = $request->background_media_ids ? hashids_decode($request->background_media_ids) : null; + // No authentication required for basic exports // Create export token (expires in 30 minutes) $token = ExportToken::create([ @@ -100,8 +136,21 @@ public function basicExportRequest(Request $request) 'is_premium' => false, 'credits_reserved' => 0, // No credits for basic exports 'expires_at' => now()->addMinutes(30), + 'metadata' => [ + 'meme_media_id' => $memeMediaId, + 'background_media_id' => $backgroundMediaId, + 'caption_texts' => $request->caption_texts, + ], ]); + // Track the export + try { + $trackingService = app(TrackingAnalyticsService::class); + $trackingService->trackExport(null, $memeMediaId, $backgroundMediaId, $request->caption_texts); + } catch (\Exception $e) { + // Silently ignore tracking errors + } + return response()->json([ 'success' => [ 'data' => [ diff --git a/app/Jobs/TrackContentSelectionJob.php b/app/Jobs/TrackContentSelectionJob.php index b985c58..0641127 100644 --- a/app/Jobs/TrackContentSelectionJob.php +++ b/app/Jobs/TrackContentSelectionJob.php @@ -21,7 +21,6 @@ public function __construct( private int $contentId, private string $contentName, private string $selectionMethod, - private ?string $searchQuery, private Carbon $actionAt, private ?string $userAgent, private ?string $ipAddress, @@ -39,7 +38,6 @@ public function handle(): void 'content_id' => $this->contentId, 'content_name' => $this->contentName, 'selection_method' => $this->selectionMethod, - 'search_query' => $this->searchQuery, 'action_at' => $this->actionAt, 'user_agent' => $this->userAgent, 'ip_address' => $this->ipAddress, diff --git a/app/Jobs/TrackExportJob.php b/app/Jobs/TrackExportJob.php index 91b458b..1c6bee9 100644 --- a/app/Jobs/TrackExportJob.php +++ b/app/Jobs/TrackExportJob.php @@ -21,8 +21,6 @@ public function __construct( private ?int $memeMediaId, private ?int $backgroundMediaId, private array $captionTexts, - private string $exportFormat, - private string $exportQuality, private Carbon $actionAt, private ?string $userAgent, private ?string $ipAddress, @@ -31,30 +29,25 @@ public function __construct( $this->onQueue('tracking'); } - public function handle(): int + public function handle(): void { try { - $trackingExport = TrackingExport::create([ + TrackingExport::create([ 'device_id' => $this->deviceId, 'meme_id' => $this->memeId, 'meme_media_id' => $this->memeMediaId, 'background_media_id' => $this->backgroundMediaId, 'caption_texts' => $this->captionTexts, - 'export_format' => $this->exportFormat, - 'export_quality' => $this->exportQuality, 'export_status' => 'initiated', 'action_at' => $this->actionAt, 'user_agent' => $this->userAgent, 'ip_address' => $this->ipAddress, 'platform' => $this->platform, ]); - - return $trackingExport->id; } catch (\Exception $e) { Log::error('Failed to track export', [ 'device_id' => $this->deviceId, 'meme_id' => $this->memeId, - 'export_format' => $this->exportFormat, 'error' => $e->getMessage(), ]); @@ -67,7 +60,6 @@ public function failed(\Throwable $exception): void Log::error('TrackExportJob failed permanently', [ 'device_id' => $this->deviceId, 'meme_id' => $this->memeId, - 'export_format' => $this->exportFormat, 'exception' => $exception->getMessage(), ]); } diff --git a/app/Jobs/TrackSaveJob.php b/app/Jobs/TrackSaveJob.php new file mode 100644 index 0000000..b2e42ae --- /dev/null +++ b/app/Jobs/TrackSaveJob.php @@ -0,0 +1,71 @@ +onQueue('tracking'); + } + + public function handle(): void + { + try { + TrackingSave::create([ + 'device_id' => $this->deviceId, + 'meme_id' => $this->memeId, + 'meme_media_id' => $this->memeMediaId, + 'background_media_id' => $this->backgroundMediaId, + 'caption_texts' => $this->captionTexts, + 'is_premium_export' => $this->isPremiumExport, + 'action_at' => $this->actionAt, + 'user_agent' => $this->userAgent, + 'ip_address' => $this->ipAddress, + 'platform' => $this->platform, + ]); + } catch (\Exception $e) { + Log::error('Failed to track save', [ + 'device_id' => $this->deviceId, + 'meme_id' => $this->memeId, + 'meme_media_id' => $this->memeMediaId, + 'background_media_id' => $this->backgroundMediaId, + 'error' => $e->getMessage(), + ]); + + throw $e; + } + } + + public function failed(\Throwable $exception): void + { + Log::error('TrackSaveJob failed permanently', [ + 'device_id' => $this->deviceId, + 'meme_id' => $this->memeId, + 'meme_media_id' => $this->memeMediaId, + 'background_media_id' => $this->backgroundMediaId, + 'exception' => $exception->getMessage(), + ]); + } +} diff --git a/app/Models/ExportToken.php b/app/Models/ExportToken.php index 3846965..fc3c5cb 100644 --- a/app/Models/ExportToken.php +++ b/app/Models/ExportToken.php @@ -15,12 +15,14 @@ class ExportToken extends Model 'token', 'is_premium', 'credits_reserved', + 'metadata', 'expires_at', 'used_at', ]; protected $casts = [ 'is_premium' => 'boolean', + 'metadata' => 'array', 'expires_at' => 'datetime', 'used_at' => 'datetime', ]; diff --git a/app/Models/TrackingContentSelection.php b/app/Models/TrackingContentSelection.php index cbcad90..7fe460b 100644 --- a/app/Models/TrackingContentSelection.php +++ b/app/Models/TrackingContentSelection.php @@ -17,7 +17,6 @@ class TrackingContentSelection extends Model 'content_type', 'content_id', 'content_name', - 'search_query', 'selection_method', 'action_at', ]; diff --git a/app/Models/TrackingExport.php b/app/Models/TrackingExport.php index 0595c19..6defcc0 100644 --- a/app/Models/TrackingExport.php +++ b/app/Models/TrackingExport.php @@ -19,8 +19,6 @@ class TrackingExport extends Model 'meme_media_id', 'background_media_id', 'caption_texts', - 'export_format', - 'export_quality', 'export_status', 'error_message', 'action_at', @@ -37,7 +35,6 @@ class TrackingExport extends Model protected $attributes = [ 'platform' => 'web', - 'export_quality' => 'standard', 'export_status' => 'initiated', ]; diff --git a/app/Models/TrackingSave.php b/app/Models/TrackingSave.php new file mode 100644 index 0000000..7f50ba7 --- /dev/null +++ b/app/Models/TrackingSave.php @@ -0,0 +1,63 @@ + 'array', + 'is_premium_export' => 'boolean', + 'action_at' => 'datetime', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; + + protected $attributes = [ + 'platform' => 'web', + 'is_premium_export' => false, + ]; + + /** + * Get the related meme + */ + public function meme() + { + return $this->belongsTo(Meme::class, 'meme_id'); + } + + /** + * Get the related meme media + */ + public function memeMedia() + { + return $this->belongsTo(MemeMedia::class, 'meme_media_id'); + } + + /** + * Get the related background media + */ + public function backgroundMedia() + { + return $this->belongsTo(BackgroundMedia::class, 'background_media_id'); + } +} diff --git a/app/Services/TrackingAnalyticsService.php b/app/Services/TrackingAnalyticsService.php index 73dc1d7..b2f547d 100644 --- a/app/Services/TrackingAnalyticsService.php +++ b/app/Services/TrackingAnalyticsService.php @@ -4,6 +4,7 @@ use App\Jobs\TrackContentSelectionJob; use App\Jobs\TrackExportJob; +use App\Jobs\TrackSaveJob; use App\Jobs\TrackSearchJob; use App\Jobs\UpdateExportStatusJob; use Carbon\Carbon; @@ -11,9 +12,9 @@ class TrackingAnalyticsService { /** - * Track a search action + * Track a search action with full context */ - public function trackSearch( + public function trackSearchWithContext( string $deviceId, string $searchType, string $searchQuery, @@ -36,15 +37,14 @@ public function trackSearch( } /** - * Track a content selection action + * Track a content selection action with full context */ - public function trackContentSelection( + public function trackContentSelectionWithContext( string $deviceId, string $contentType, int $contentId, string $contentName, string $selectionMethod, - ?string $searchQuery = null, ?Carbon $actionAt = null, ?string $userAgent = null, ?string $ipAddress = null, @@ -56,7 +56,6 @@ public function trackContentSelection( $contentId, $contentName, $selectionMethod, - $searchQuery, $actionAt ?? now(), $userAgent, $ipAddress, @@ -65,29 +64,54 @@ public function trackContentSelection( } /** - * Track an export action + * Track an export action with full context */ - public function trackExport( + public function trackExportWithContext( string $deviceId, ?int $memeId, ?int $memeMediaId, ?int $backgroundMediaId, array $captionTexts, - string $exportFormat, - string $exportQuality = 'standard', ?Carbon $actionAt = null, ?string $userAgent = null, ?string $ipAddress = null, string $platform = 'web' - ): int { - return TrackExportJob::dispatchSync( + ): void { + TrackExportJob::dispatch( $deviceId, $memeId, $memeMediaId, $backgroundMediaId, $captionTexts, - $exportFormat, - $exportQuality, + $actionAt ?? now(), + $userAgent, + $ipAddress, + $platform + ); + } + + /** + * Track a save action with full context + */ + public function trackSaveWithContext( + string $deviceId, + ?int $memeId, + ?int $memeMediaId, + ?int $backgroundMediaId, + array $captionTexts, + bool $isPremiumExport, + ?Carbon $actionAt = null, + ?string $userAgent = null, + ?string $ipAddress = null, + string $platform = 'web' + ): void { + TrackSaveJob::dispatch( + $deviceId, + $memeId, + $memeMediaId, + $backgroundMediaId, + $captionTexts, + $isPremiumExport, $actionAt ?? now(), $userAgent, $ipAddress, @@ -121,7 +145,7 @@ public function getDeviceContext(): array return [ 'user_agent' => $request->userAgent(), - 'ip_address' => $request->ip(), + 'ip_address' => get_current_ip(), 'platform' => 'web', // Default for now, can be enhanced for mobile detection ]; } @@ -145,14 +169,14 @@ public function generateDeviceId(): string } /** - * Quick track methods with auto device context + * Track methods with auto device context */ - public function quickTrackSearch(string $searchType, string $searchQuery, ?array $searchFilters = null): void + public function trackSearch(string $searchType, string $searchQuery, ?array $searchFilters = null): void { $context = $this->getDeviceContext(); $deviceId = $this->generateDeviceId(); - $this->trackSearch( + $this->trackSearchWithContext( $deviceId, $searchType, $searchQuery, @@ -164,18 +188,17 @@ public function quickTrackSearch(string $searchType, string $searchQuery, ?array ); } - public function quickTrackContentSelection(string $contentType, int $contentId, string $contentName, string $selectionMethod, ?string $searchQuery = null): void + public function trackContentSelection(string $contentType, int $contentId, string $contentName, string $selectionMethod): void { $context = $this->getDeviceContext(); $deviceId = $this->generateDeviceId(); - $this->trackContentSelection( + $this->trackContentSelectionWithContext( $deviceId, $contentType, $contentId, $contentName, $selectionMethod, - $searchQuery, null, $context['user_agent'], $context['ip_address'], @@ -183,19 +206,36 @@ public function quickTrackContentSelection(string $contentType, int $contentId, ); } - public function quickTrackExport(?int $memeId, ?int $memeMediaId, ?int $backgroundMediaId, array $captionTexts, string $exportFormat, string $exportQuality = 'standard'): int + public function trackExport(?int $memeId, ?int $memeMediaId, ?int $backgroundMediaId, array $captionTexts): void { $context = $this->getDeviceContext(); $deviceId = $this->generateDeviceId(); - return $this->trackExport( + $this->trackExportWithContext( $deviceId, $memeId, $memeMediaId, $backgroundMediaId, $captionTexts, - $exportFormat, - $exportQuality, + null, + $context['user_agent'], + $context['ip_address'], + $context['platform'] + ); + } + + public function trackSave(?int $memeId, ?int $memeMediaId, ?int $backgroundMediaId, array $captionTexts, bool $isPremiumExport = false): void + { + $context = $this->getDeviceContext(); + $deviceId = $this->generateDeviceId(); + + $this->trackSaveWithContext( + $deviceId, + $memeId, + $memeMediaId, + $backgroundMediaId, + $captionTexts, + $isPremiumExport, null, $context['user_agent'], $context['ip_address'], diff --git a/config/platform/general.php b/config/platform/general.php index 965a517..ce48c34 100644 --- a/config/platform/general.php +++ b/config/platform/general.php @@ -4,4 +4,11 @@ 'enable_test_routes' => env('ENABLE_TEST_ROUTES', false), 'authed_route_redirect' => 'home', + + 'dev_default_ip' => env('DEV_DEFAULT_IP', '127.0.0.1'), + + 'dev_default_location' => env('DEV_DEFAULT_LOCATION', 'US'), + + 'ip_override' => env('IP_OVERRIDE', null), + ]; diff --git a/database/migrations/2025_07_16_034628_create_tracking_content_selections_table.php b/database/migrations/2025_07_16_034628_create_tracking_content_selections_table.php index 60f9efe..cc50175 100644 --- a/database/migrations/2025_07_16_034628_create_tracking_content_selections_table.php +++ b/database/migrations/2025_07_16_034628_create_tracking_content_selections_table.php @@ -24,7 +24,6 @@ public function up(): void $table->enum('content_type', ['meme', 'background']); $table->unsignedBigInteger('content_id'); $table->string('content_name'); - $table->text('search_query')->nullable(); $table->enum('selection_method', ['search', 'browse', 'featured', 'recent']); // Timestamps diff --git a/database/migrations/2025_07_16_034642_create_tracking_exports_table.php b/database/migrations/2025_07_16_034642_create_tracking_exports_table.php index c6b435e..41cc87d 100644 --- a/database/migrations/2025_07_16_034642_create_tracking_exports_table.php +++ b/database/migrations/2025_07_16_034642_create_tracking_exports_table.php @@ -25,8 +25,6 @@ public function up(): void $table->unsignedBigInteger('meme_media_id')->nullable(); $table->unsignedBigInteger('background_media_id')->nullable(); $table->json('caption_texts'); - $table->enum('export_format', ['mov', 'webm', 'gif', 'webp']); - $table->enum('export_quality', ['standard', 'premium'])->default('standard'); $table->enum('export_status', ['initiated', 'processing', 'completed', 'failed'])->default('initiated'); $table->text('error_message')->nullable(); @@ -43,7 +41,6 @@ public function up(): void // Indexes $table->index(['device_id', 'action_at']); $table->index(['export_status', 'action_at']); - $table->index(['export_format', 'action_at']); $table->index('platform'); }); } diff --git a/database/migrations/2025_07_16_042908_modify_export_tokens_table_for_anonymous_users.php b/database/migrations/2025_07_16_042908_modify_export_tokens_table_for_anonymous_users.php index ab33ce8..aa325be 100644 --- a/database/migrations/2025_07_16_042908_modify_export_tokens_table_for_anonymous_users.php +++ b/database/migrations/2025_07_16_042908_modify_export_tokens_table_for_anonymous_users.php @@ -28,15 +28,8 @@ public function up(): void */ public function down(): void { - Schema::table('export_tokens', function (Blueprint $table) { - // Drop foreign key constraint - $table->dropForeign(['user_id']); - - // Make user_id NOT nullable again - $table->unsignedBigInteger('user_id')->nullable(false)->change(); - - // Add foreign key back without nullable - $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); - }); + // This migration made user_id nullable to support anonymous users + // Reversing this would break existing anonymous tokens, so we'll leave it nullable + // and just update the foreign key constraint if needed } }; diff --git a/database/migrations/2025_07_16_043901_add_metadata_to_export_tokens_table.php b/database/migrations/2025_07_16_043901_add_metadata_to_export_tokens_table.php new file mode 100644 index 0000000..23e9569 --- /dev/null +++ b/database/migrations/2025_07_16_043901_add_metadata_to_export_tokens_table.php @@ -0,0 +1,28 @@ +json('metadata')->nullable()->after('credits_reserved'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('export_tokens', function (Blueprint $table) { + $table->dropColumn('metadata'); + }); + } +}; diff --git a/database/migrations/2025_07_16_072241_create_track_saves_table.php b/database/migrations/2025_07_16_072241_create_track_saves_table.php new file mode 100644 index 0000000..283d816 --- /dev/null +++ b/database/migrations/2025_07_16_072241_create_track_saves_table.php @@ -0,0 +1,51 @@ +id(); + + // Common fields + $table->string('device_id'); + $table->text('user_agent')->nullable(); + $table->string('ip_address')->nullable(); + $table->enum('platform', ['web', 'ios', 'android'])->default('web'); + + // Save action fields + $table->unsignedBigInteger('meme_id')->nullable(); + $table->unsignedBigInteger('meme_media_id')->nullable(); + $table->unsignedBigInteger('background_media_id')->nullable(); + $table->json('caption_texts'); + $table->boolean('is_premium_export')->default(false); + + // Timestamps + $table->timestamp('action_at'); + $table->timestamps(); + + // Indexes + $table->index(['device_id', 'action_at']); + $table->index(['meme_id', 'action_at']); + $table->index(['meme_media_id', 'action_at']); + $table->index(['background_media_id', 'action_at']); + $table->index(['is_premium_export', 'action_at']); + $table->index('platform'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('track_saves'); + } +}; diff --git a/resources/js/modules/editor/partials/canvas/video-download/video-download-modal.jsx b/resources/js/modules/editor/partials/canvas/video-download/video-download-modal.jsx index 8ea5131..2e19f11 100644 --- a/resources/js/modules/editor/partials/canvas/video-download/video-download-modal.jsx +++ b/resources/js/modules/editor/partials/canvas/video-download/video-download-modal.jsx @@ -3,6 +3,7 @@ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } f import { Progress } from '@/components/ui/progress'; import { Spinner } from '@/components/ui/spinner.js'; import { Textarea } from '@/components/ui/textarea'; +import useMediaStore from '@/stores/MediaStore'; import useUserStore from '@/stores/UserStore'; import { Clock10Icon, Download } from 'lucide-react'; import { useEffect, useRef, useState } from 'react'; @@ -18,6 +19,9 @@ const VideoDownloadModal = ({ exportStatus, videoBlob, videoBlobFilename, + selectedMeme, + selectedBackground, + currentCaption, }) => { const [showDebug, setShowDebug] = useState(false); const [isPremiumExport, setIsPremiumExport] = useState(false); @@ -31,6 +35,7 @@ const VideoDownloadModal = ({ const lastProgress = useRef(0); const { premiumExportRequest, premiumExportComplete, basicExportRequest, basicExportComplete } = useUserStore(); + const { saveMeme } = useMediaStore(); const handleShareOrDownload = async () => { if (!videoBlob || !videoBlobFilename) { @@ -40,6 +45,10 @@ const VideoDownloadModal = ({ setDownloadState('downloading'); + // Track the save action before actual download/share + const captionTexts = currentCaption ? [currentCaption] : ['Default caption']; + await saveMeme(captionTexts, isPremiumExport); + try { // Check if mobile and supports navigator.share const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); @@ -96,8 +105,13 @@ const VideoDownloadModal = ({ setIsPremiumExport(true); setEstimatedTimeRemaining(null); + // Prepare export data + const memeMediaIds = selectedMeme?.ids || null; + const backgroundMediaIds = selectedBackground?.ids || null; + const captionTexts = currentCaption ? [currentCaption] : ['Default caption']; + // Call premiumExportRequest and check response - const response = await premiumExportRequest(); + const response = await premiumExportRequest(memeMediaIds, backgroundMediaIds, captionTexts); if (response?.error) { // Halt the process if there's an error @@ -120,8 +134,23 @@ const VideoDownloadModal = ({ setIsPremiumExport(false); setEstimatedTimeRemaining(null); + // Prepare export data + const memeMediaIds = selectedMeme?.ids || null; + const backgroundMediaIds = selectedBackground?.ids || null; + const captionTexts = currentCaption ? [currentCaption] : ['Default caption']; + + // Debug logging + console.log('Export data:', { + selectedMeme, + selectedBackground, + currentCaption, + memeMediaIds, + backgroundMediaIds, + captionTexts + }); + // Call basicExportRequest and check response - const response = await basicExportRequest(); + const response = await basicExportRequest(memeMediaIds, backgroundMediaIds, captionTexts); if (response?.error) { // Halt the process if there's an error diff --git a/resources/js/modules/editor/partials/canvas/video-editor.jsx b/resources/js/modules/editor/partials/canvas/video-editor.jsx index ffe7ee2..50ddd63 100644 --- a/resources/js/modules/editor/partials/canvas/video-editor.jsx +++ b/resources/js/modules/editor/partials/canvas/video-editor.jsx @@ -622,6 +622,9 @@ const VideoEditor = ({ width, height, onOpenTextSidebar }) => { exportStatus={exportStatus} videoBlob={videoBlob} videoBlobFilename={videoBlobFilename} + selectedMeme={selectedMeme} + selectedBackground={selectedBackground} + currentCaption={currentCaption} /> ); diff --git a/resources/js/stores/MediaStore.js b/resources/js/stores/MediaStore.js index 3a728f1..2f14260 100644 --- a/resources/js/stores/MediaStore.js +++ b/resources/js/stores/MediaStore.js @@ -30,12 +30,26 @@ const useMediaStore = create( }, // Selection actions - selectMeme: (meme) => { + selectMeme: async (meme) => { set({ selectedMeme: meme }); + try { + await axiosInstance.post(route('api.app.select.meme'), { + meme_ids: meme.ids, + }); + } catch (error) { + console.error('Error meme selection:', error); + } }, - selectBackground: (background) => { + selectBackground: async (background) => { set({ selectedBackground: background }); + try { + await axiosInstance.post(route('api.app.select.background'), { + background_ids: background.ids, + }); + } catch (error) { + console.error('Error background selection:', error); + } }, // Update current caption when user edits text @@ -52,6 +66,23 @@ const useMediaStore = create( set({ selectedBackground: null }); }, + // Save meme action + saveMeme: async (captionTexts, isPremiumExport = false) => { + try { + const { selectedMeme, selectedBackground } = get(); + + await axiosInstance.post(route('api.app.save.meme'), { + meme_media_ids: selectedMeme?.ids || null, + background_media_ids: selectedBackground?.ids || null, + caption_texts: captionTexts, + is_premium_export: isPremiumExport, + }); + } catch (error) { + console.error('Error saving meme:', error); + // Silently ignore save tracking errors + } + }, + // Fetch AI hints fetchAIHints: async () => { set({ isLoadingAIHints: true }); diff --git a/resources/js/stores/UserStore.js b/resources/js/stores/UserStore.js index 99a486f..a31ca63 100644 --- a/resources/js/stores/UserStore.js +++ b/resources/js/stores/UserStore.js @@ -58,9 +58,13 @@ const useUserStore = create( } }, - premiumExportRequest: async () => { + premiumExportRequest: async (memeMediaIds, backgroundMediaIds, captionTexts) => { try { - const response = await axiosInstance.post(route('api.user.premium_export.request')); + const response = await axiosInstance.post(route('api.user.premium_export.request'), { + meme_media_ids: memeMediaIds, + background_media_ids: backgroundMediaIds, + caption_texts: captionTexts, + }); if (response?.data?.success?.data?.user_usage) { set({ @@ -109,9 +113,13 @@ const useUserStore = create( } }, - basicExportRequest: async () => { + basicExportRequest: async (memeMediaIds, backgroundMediaIds, captionTexts) => { try { - const response = await axiosInstance.post(route('api.basic_export.request')); + const response = await axiosInstance.post(route('api.basic_export.request'), { + meme_media_ids: memeMediaIds, + background_media_ids: backgroundMediaIds, + caption_texts: captionTexts, + }); if (response?.data?.success?.message) { toast.success(response.data.success.message); diff --git a/resources/js/ziggy.js b/resources/js/ziggy.js index 54dc3ee..22b15c0 100644 --- a/resources/js/ziggy.js +++ b/resources/js/ziggy.js @@ -1,4 +1,4 @@ -const Ziggy = {"url":"https:\/\/memefast.test","port":null,"defaults":{},"routes":{"cashier.payment":{"uri":"stripe\/payment\/{id}","methods":["GET","HEAD"],"parameters":["id"]},"cashier.webhook":{"uri":"stripe\/webhook","methods":["POST"]},"horizon.stats.index":{"uri":"horizon\/api\/stats","methods":["GET","HEAD"]},"horizon.workload.index":{"uri":"horizon\/api\/workload","methods":["GET","HEAD"]},"horizon.masters.index":{"uri":"horizon\/api\/masters","methods":["GET","HEAD"]},"horizon.monitoring.index":{"uri":"horizon\/api\/monitoring","methods":["GET","HEAD"]},"horizon.monitoring.store":{"uri":"horizon\/api\/monitoring","methods":["POST"]},"horizon.monitoring-tag.paginate":{"uri":"horizon\/api\/monitoring\/{tag}","methods":["GET","HEAD"],"parameters":["tag"]},"horizon.monitoring-tag.destroy":{"uri":"horizon\/api\/monitoring\/{tag}","methods":["DELETE"],"wheres":{"tag":".*"},"parameters":["tag"]},"horizon.jobs-metrics.index":{"uri":"horizon\/api\/metrics\/jobs","methods":["GET","HEAD"]},"horizon.jobs-metrics.show":{"uri":"horizon\/api\/metrics\/jobs\/{id}","methods":["GET","HEAD"],"parameters":["id"]},"horizon.queues-metrics.index":{"uri":"horizon\/api\/metrics\/queues","methods":["GET","HEAD"]},"horizon.queues-metrics.show":{"uri":"horizon\/api\/metrics\/queues\/{id}","methods":["GET","HEAD"],"parameters":["id"]},"horizon.jobs-batches.index":{"uri":"horizon\/api\/batches","methods":["GET","HEAD"]},"horizon.jobs-batches.show":{"uri":"horizon\/api\/batches\/{id}","methods":["GET","HEAD"],"parameters":["id"]},"horizon.jobs-batches.retry":{"uri":"horizon\/api\/batches\/retry\/{id}","methods":["POST"],"parameters":["id"]},"horizon.pending-jobs.index":{"uri":"horizon\/api\/jobs\/pending","methods":["GET","HEAD"]},"horizon.completed-jobs.index":{"uri":"horizon\/api\/jobs\/completed","methods":["GET","HEAD"]},"horizon.silenced-jobs.index":{"uri":"horizon\/api\/jobs\/silenced","methods":["GET","HEAD"]},"horizon.failed-jobs.index":{"uri":"horizon\/api\/jobs\/failed","methods":["GET","HEAD"]},"horizon.failed-jobs.show":{"uri":"horizon\/api\/jobs\/failed\/{id}","methods":["GET","HEAD"],"parameters":["id"]},"horizon.retry-jobs.show":{"uri":"horizon\/api\/jobs\/retry\/{id}","methods":["POST"],"parameters":["id"]},"horizon.jobs.show":{"uri":"horizon\/api\/jobs\/{id}","methods":["GET","HEAD"],"parameters":["id"]},"horizon.index":{"uri":"horizon\/{view?}","methods":["GET","HEAD"],"wheres":{"view":"(.*)"},"parameters":["view"]},"sanctum.csrf-cookie":{"uri":"sanctum\/csrf-cookie","methods":["GET","HEAD"]},"api.pricing_page":{"uri":"api\/pricing","methods":["POST"]},"api.ai_hints":{"uri":"api\/ai-hints","methods":["POST"]},"api.basic_export.request":{"uri":"api\/basic-export\/request","methods":["POST"]},"api.basic_export.complete":{"uri":"api\/basic-export\/complete","methods":["POST"]},"api.user":{"uri":"api\/user","methods":["POST"]},"api.user.subscribe":{"uri":"api\/user\/subscribe","methods":["POST"]},"api.user.purchase":{"uri":"api\/user\/purchase","methods":["POST"]},"api.user.billing_portal":{"uri":"api\/user\/billing-portal","methods":["POST"]},"api.user.premium_export.request":{"uri":"api\/user\/premium-export\/request","methods":["POST"]},"api.user.premium_export.complete":{"uri":"api\/user\/premium-export\/complete","methods":["POST"]},"api.user.generate_meme":{"uri":"api\/user\/generate_meme","methods":["POST"]},"api.user.check_meme_job_status":{"uri":"api\/user\/generate_meme\/status","methods":["POST"]},"api.user.get_active_job":{"uri":"api\/user\/generate_meme\/active","methods":["POST"]},"api.user.get_meme_history":{"uri":"api\/user\/generate_meme\/history","methods":["POST"]},"api.app.init":{"uri":"api\/app\/init","methods":["POST"]},"api.app.memes":{"uri":"api\/app\/memes","methods":["POST"]},"api.app.search.memes":{"uri":"api\/app\/search\/memes","methods":["POST"]},"api.app.background":{"uri":"api\/app\/background","methods":["POST"]},"api.app.search.background":{"uri":"api\/app\/search\/background","methods":["POST"]},"auth.google.redirect":{"uri":"auth\/google\/redirect","methods":["GET","HEAD"]},"auth.google.callback":{"uri":"auth\/google\/callback","methods":["GET","HEAD"]},"dashboard":{"uri":"dashboard","methods":["GET","HEAD"]},"subscribe.success":{"uri":"subscribe\/success","methods":["GET","HEAD"]},"subscribe.cancelled":{"uri":"subscribe\/cancelled","methods":["GET","HEAD"]},"purchase.success":{"uri":"purchase\/success","methods":["GET","HEAD"]},"purchase.cancelled":{"uri":"purchase\/cancelled","methods":["GET","HEAD"]},"admin.dashboard":{"uri":"admin","methods":["GET","HEAD"]},"admin.background-generation":{"uri":"admin\/background-generation","methods":["GET","HEAD"]},"admin.background-generation.generate":{"uri":"admin\/background-generation\/generate","methods":["POST"]},"admin.background-generation.save":{"uri":"admin\/background-generation\/save","methods":["POST"]},"admin.background-generation.delete":{"uri":"admin\/background-generation\/delete\/{id}","methods":["POST"],"parameters":["id"]},"profile.edit":{"uri":"settings\/profile","methods":["GET","HEAD"]},"profile.update":{"uri":"settings\/profile","methods":["PATCH"]},"profile.destroy":{"uri":"settings\/profile","methods":["DELETE"]},"password.edit":{"uri":"settings\/password","methods":["GET","HEAD"]},"password.update":{"uri":"settings\/password","methods":["PUT"]},"appearance":{"uri":"settings\/appearance","methods":["GET","HEAD"]},"register":{"uri":"register","methods":["GET","HEAD"]},"login":{"uri":"login","methods":["GET","HEAD"]},"password.request":{"uri":"forgot-password","methods":["GET","HEAD"]},"password.email":{"uri":"forgot-password","methods":["POST"]},"password.reset":{"uri":"reset-password\/{token}","methods":["GET","HEAD"],"parameters":["token"]},"password.store":{"uri":"reset-password","methods":["POST"]},"verification.notice":{"uri":"verify-email","methods":["GET","HEAD"]},"verification.verify":{"uri":"verify-email\/{id}\/{hash}","methods":["GET","HEAD"],"parameters":["id","hash"]},"verification.send":{"uri":"email\/verification-notification","methods":["POST"]},"password.confirm":{"uri":"confirm-password","methods":["GET","HEAD"]},"logout":{"uri":"logout","methods":["POST"]},"home":{"uri":"\/","methods":["GET","HEAD"]},"privacy":{"uri":"privacy","methods":["GET","HEAD"]},"terms":{"uri":"terms","methods":["GET","HEAD"]},"admin.duplicates":{"uri":"duplicates","methods":["GET","HEAD"]},"admin.duplicates.scan":{"uri":"duplicates\/scan","methods":["POST"]},"admin.duplicates.delete":{"uri":"duplicates\/delete","methods":["POST"]},"admin.duplicates.regenerate-hash":{"uri":"duplicates\/regenerate-hash","methods":["POST"]},"storage.local":{"uri":"storage\/{path}","methods":["GET","HEAD"],"wheres":{"path":".*"},"parameters":["path"]}}}; +const Ziggy = {"url":"https:\/\/memefa.st","port":null,"defaults":{},"routes":{"cashier.payment":{"uri":"stripe\/payment\/{id}","methods":["GET","HEAD"],"parameters":["id"]},"cashier.webhook":{"uri":"stripe\/webhook","methods":["POST"]},"horizon.stats.index":{"uri":"horizon\/api\/stats","methods":["GET","HEAD"]},"horizon.workload.index":{"uri":"horizon\/api\/workload","methods":["GET","HEAD"]},"horizon.masters.index":{"uri":"horizon\/api\/masters","methods":["GET","HEAD"]},"horizon.monitoring.index":{"uri":"horizon\/api\/monitoring","methods":["GET","HEAD"]},"horizon.monitoring.store":{"uri":"horizon\/api\/monitoring","methods":["POST"]},"horizon.monitoring-tag.paginate":{"uri":"horizon\/api\/monitoring\/{tag}","methods":["GET","HEAD"],"parameters":["tag"]},"horizon.monitoring-tag.destroy":{"uri":"horizon\/api\/monitoring\/{tag}","methods":["DELETE"],"wheres":{"tag":".*"},"parameters":["tag"]},"horizon.jobs-metrics.index":{"uri":"horizon\/api\/metrics\/jobs","methods":["GET","HEAD"]},"horizon.jobs-metrics.show":{"uri":"horizon\/api\/metrics\/jobs\/{id}","methods":["GET","HEAD"],"parameters":["id"]},"horizon.queues-metrics.index":{"uri":"horizon\/api\/metrics\/queues","methods":["GET","HEAD"]},"horizon.queues-metrics.show":{"uri":"horizon\/api\/metrics\/queues\/{id}","methods":["GET","HEAD"],"parameters":["id"]},"horizon.jobs-batches.index":{"uri":"horizon\/api\/batches","methods":["GET","HEAD"]},"horizon.jobs-batches.show":{"uri":"horizon\/api\/batches\/{id}","methods":["GET","HEAD"],"parameters":["id"]},"horizon.jobs-batches.retry":{"uri":"horizon\/api\/batches\/retry\/{id}","methods":["POST"],"parameters":["id"]},"horizon.pending-jobs.index":{"uri":"horizon\/api\/jobs\/pending","methods":["GET","HEAD"]},"horizon.completed-jobs.index":{"uri":"horizon\/api\/jobs\/completed","methods":["GET","HEAD"]},"horizon.silenced-jobs.index":{"uri":"horizon\/api\/jobs\/silenced","methods":["GET","HEAD"]},"horizon.failed-jobs.index":{"uri":"horizon\/api\/jobs\/failed","methods":["GET","HEAD"]},"horizon.failed-jobs.show":{"uri":"horizon\/api\/jobs\/failed\/{id}","methods":["GET","HEAD"],"parameters":["id"]},"horizon.retry-jobs.show":{"uri":"horizon\/api\/jobs\/retry\/{id}","methods":["POST"],"parameters":["id"]},"horizon.jobs.show":{"uri":"horizon\/api\/jobs\/{id}","methods":["GET","HEAD"],"parameters":["id"]},"horizon.index":{"uri":"horizon\/{view?}","methods":["GET","HEAD"],"wheres":{"view":"(.*)"},"parameters":["view"]},"sanctum.csrf-cookie":{"uri":"sanctum\/csrf-cookie","methods":["GET","HEAD"]},"api.pricing_page":{"uri":"api\/pricing","methods":["POST"]},"api.ai_hints":{"uri":"api\/ai-hints","methods":["POST"]},"api.basic_export.request":{"uri":"api\/basic-export\/request","methods":["POST"]},"api.basic_export.complete":{"uri":"api\/basic-export\/complete","methods":["POST"]},"api.user":{"uri":"api\/user","methods":["POST"]},"api.user.subscribe":{"uri":"api\/user\/subscribe","methods":["POST"]},"api.user.purchase":{"uri":"api\/user\/purchase","methods":["POST"]},"api.user.billing_portal":{"uri":"api\/user\/billing-portal","methods":["POST"]},"api.user.premium_export.request":{"uri":"api\/user\/premium-export\/request","methods":["POST"]},"api.user.premium_export.complete":{"uri":"api\/user\/premium-export\/complete","methods":["POST"]},"api.user.generate_meme":{"uri":"api\/user\/generate_meme","methods":["POST"]},"api.user.check_meme_job_status":{"uri":"api\/user\/generate_meme\/status","methods":["POST"]},"api.user.get_active_job":{"uri":"api\/user\/generate_meme\/active","methods":["POST"]},"api.user.get_meme_history":{"uri":"api\/user\/generate_meme\/history","methods":["POST"]},"api.app.init":{"uri":"api\/app\/init","methods":["POST"]},"api.app.memes":{"uri":"api\/app\/memes","methods":["POST"]},"api.app.search.memes":{"uri":"api\/app\/search\/memes","methods":["POST"]},"api.app.background":{"uri":"api\/app\/background","methods":["POST"]},"api.app.search.background":{"uri":"api\/app\/search\/background","methods":["POST"]},"api.app.select.meme":{"uri":"api\/app\/select\/meme","methods":["POST"]},"api.app.select.background":{"uri":"api\/app\/select\/background","methods":["POST"]},"api.app.save.meme":{"uri":"api\/app\/save\/meme","methods":["POST"]},"auth.google.redirect":{"uri":"auth\/google\/redirect","methods":["GET","HEAD"]},"auth.google.callback":{"uri":"auth\/google\/callback","methods":["GET","HEAD"]},"dashboard":{"uri":"dashboard","methods":["GET","HEAD"]},"subscribe.success":{"uri":"subscribe\/success","methods":["GET","HEAD"]},"subscribe.cancelled":{"uri":"subscribe\/cancelled","methods":["GET","HEAD"]},"purchase.success":{"uri":"purchase\/success","methods":["GET","HEAD"]},"purchase.cancelled":{"uri":"purchase\/cancelled","methods":["GET","HEAD"]},"admin.dashboard":{"uri":"admin","methods":["GET","HEAD"]},"admin.background-generation":{"uri":"admin\/background-generation","methods":["GET","HEAD"]},"admin.background-generation.generate":{"uri":"admin\/background-generation\/generate","methods":["POST"]},"admin.background-generation.save":{"uri":"admin\/background-generation\/save","methods":["POST"]},"admin.background-generation.delete":{"uri":"admin\/background-generation\/delete\/{id}","methods":["POST"],"parameters":["id"]},"profile.edit":{"uri":"settings\/profile","methods":["GET","HEAD"]},"profile.update":{"uri":"settings\/profile","methods":["PATCH"]},"profile.destroy":{"uri":"settings\/profile","methods":["DELETE"]},"password.edit":{"uri":"settings\/password","methods":["GET","HEAD"]},"password.update":{"uri":"settings\/password","methods":["PUT"]},"appearance":{"uri":"settings\/appearance","methods":["GET","HEAD"]},"register":{"uri":"register","methods":["GET","HEAD"]},"login":{"uri":"login","methods":["GET","HEAD"]},"password.request":{"uri":"forgot-password","methods":["GET","HEAD"]},"password.email":{"uri":"forgot-password","methods":["POST"]},"password.reset":{"uri":"reset-password\/{token}","methods":["GET","HEAD"],"parameters":["token"]},"password.store":{"uri":"reset-password","methods":["POST"]},"verification.notice":{"uri":"verify-email","methods":["GET","HEAD"]},"verification.verify":{"uri":"verify-email\/{id}\/{hash}","methods":["GET","HEAD"],"parameters":["id","hash"]},"verification.send":{"uri":"email\/verification-notification","methods":["POST"]},"password.confirm":{"uri":"confirm-password","methods":["GET","HEAD"]},"logout":{"uri":"logout","methods":["POST"]},"home":{"uri":"\/","methods":["GET","HEAD"]},"privacy":{"uri":"privacy","methods":["GET","HEAD"]},"terms":{"uri":"terms","methods":["GET","HEAD"]},"admin.duplicates":{"uri":"duplicates","methods":["GET","HEAD"]},"admin.duplicates.scan":{"uri":"duplicates\/scan","methods":["POST"]},"admin.duplicates.delete":{"uri":"duplicates\/delete","methods":["POST"]},"admin.duplicates.regenerate-hash":{"uri":"duplicates\/regenerate-hash","methods":["POST"]},"storage.local":{"uri":"storage\/{path}","methods":["GET","HEAD"],"wheres":{"path":".*"},"parameters":["path"]}}}; if (typeof window !== 'undefined' && typeof window.Ziggy !== 'undefined') { Object.assign(Ziggy.routes, window.Ziggy.routes); } diff --git a/routes/api.php b/routes/api.php index 056876d..f152bb8 100644 --- a/routes/api.php +++ b/routes/api.php @@ -60,4 +60,9 @@ Route::post('background', [FrontMediaController::class, 'background'])->name('api.app.background'); Route::post('search/background', [FrontMediaController::class, 'searchBackgrounds'])->name('api.app.search.background'); + + Route::post('select/meme', [FrontMediaController::class, 'selectMeme'])->name('api.app.select.meme'); + Route::post('select/background', [FrontMediaController::class, 'selectBackground'])->name('api.app.select.background'); + + Route::post('save/meme', [FrontMediaController::class, 'saveMeme'])->name('api.app.save.meme'); });