exists(); $hasFeed = Feed::where('is_active', true)->exists(); $hasChannel = PlatformChannel::where('is_active', true)->exists(); $hasRoute = Route::where('is_active', true)->exists(); // Check if onboarding was explicitly skipped $onboardingSkipped = Setting::where('key', 'onboarding_skipped')->value('value') === 'true'; // User needs onboarding if they don't have the required components AND haven't skipped it $needsOnboarding = (!$hasPlatformAccount || !$hasFeed || !$hasChannel || !$hasRoute) && !$onboardingSkipped; // Determine current step $currentStep = null; if ($needsOnboarding) { if (!$hasPlatformAccount) { $currentStep = 'platform'; } elseif (!$hasFeed) { $currentStep = 'feed'; } elseif (!$hasChannel) { $currentStep = 'channel'; } elseif (!$hasRoute) { $currentStep = 'route'; } } return $this->sendResponse([ 'needs_onboarding' => $needsOnboarding, 'current_step' => $currentStep, 'has_platform_account' => $hasPlatformAccount, 'has_feed' => $hasFeed, 'has_channel' => $hasChannel, 'has_route' => $hasRoute, 'onboarding_skipped' => $onboardingSkipped, ], 'Onboarding status retrieved successfully.'); } /** * Get onboarding options (languages, platform instances) */ public function options(): JsonResponse { $languages = Language::where('is_active', true) ->orderBy('name') ->get(['id', 'short_code', 'name', 'native_name', 'is_active']); $platformInstances = PlatformInstance::where('is_active', true) ->orderBy('name') ->get(['id', 'platform', 'url', 'name', 'description', 'is_active']); // Get existing feeds and channels for route creation $feeds = Feed::where('is_active', true) ->orderBy('name') ->get(['id', 'name', 'url', 'type']); $platformChannels = PlatformChannel::where('is_active', true) ->with(['platformInstance:id,name,url']) ->orderBy('name') ->get(['id', 'platform_instance_id', 'name', 'display_name', 'description']); return $this->sendResponse([ 'languages' => $languages, 'platform_instances' => $platformInstances, 'feeds' => $feeds, 'platform_channels' => $platformChannels, ], 'Onboarding options retrieved successfully.'); } /** * Create platform account for onboarding */ public function createPlatform(Request $request): JsonResponse { $validator = Validator::make($request->all(), [ 'instance_url' => 'required|string|max:255|regex:/^[a-zA-Z0-9]([a-zA-Z0-9\-\.]*[a-zA-Z0-9])?$/', 'username' => 'required|string|max:255', 'password' => 'required|string|min:6', 'platform' => 'required|in:lemmy', ], [ 'instance_url.regex' => 'Please enter a valid domain name (e.g., lemmy.world, belgae.social)' ]); if ($validator->fails()) { throw new ValidationException($validator); } $validated = $validator->validated(); // Normalize the instance URL - prepend https:// if needed $instanceDomain = $validated['instance_url']; $fullInstanceUrl = 'https://' . $instanceDomain; try { // Create or get platform instance $platformInstance = PlatformInstance::firstOrCreate([ 'url' => $fullInstanceUrl, 'platform' => $validated['platform'], ], [ 'name' => ucfirst($instanceDomain), 'is_active' => true, ]); // Authenticate with Lemmy API using the full URL $authResponse = $this->lemmyAuthService->authenticate( $fullInstanceUrl, $validated['username'], $validated['password'] ); // Create platform account with the current schema $platformAccount = PlatformAccount::create([ 'platform' => $validated['platform'], 'instance_url' => $fullInstanceUrl, 'username' => $validated['username'], 'password' => $validated['password'], 'settings' => [ 'display_name' => $authResponse['person_view']['person']['display_name'] ?? null, 'description' => $authResponse['person_view']['person']['bio'] ?? null, 'person_id' => $authResponse['person_view']['person']['id'] ?? null, 'platform_instance_id' => $platformInstance->id, 'api_token' => $authResponse['jwt'] ?? null, // Store JWT in settings for now ], 'is_active' => true, 'status' => 'active', ]); return $this->sendResponse( new PlatformAccountResource($platformAccount), 'Platform account created successfully.' ); } catch (\App\Exceptions\PlatformAuthException $e) { // Check if it's a rate limit error if (str_contains($e->getMessage(), 'Rate limited by')) { return $this->sendError($e->getMessage(), [], 429); } return $this->sendError('Invalid username or password. Please check your credentials and try again.', [], 422); } catch (\Exception $e) { $message = 'Unable to connect to the Lemmy instance. Please check the URL and try again.'; // If it's a network/connection issue, provide a more specific message if (str_contains(strtolower($e->getMessage()), 'connection') || str_contains(strtolower($e->getMessage()), 'network') || str_contains(strtolower($e->getMessage()), 'timeout')) { $message = 'Connection failed. Please check the instance URL and your internet connection.'; } return $this->sendError($message, [], 422); } } /** * Create feed for onboarding */ public function createFeed(Request $request): JsonResponse { $validator = Validator::make($request->all(), [ 'name' => 'required|string|max:255', 'url' => 'required|url|max:500', 'type' => 'required|in:website,rss', 'language_id' => 'required|exists:languages,id', 'description' => 'nullable|string|max:1000', ]); if ($validator->fails()) { throw new ValidationException($validator); } $validated = $validator->validated(); $feed = Feed::create([ 'name' => $validated['name'], 'url' => $validated['url'], 'type' => $validated['type'], 'language_id' => $validated['language_id'], 'description' => $validated['description'] ?? null, 'is_active' => true, ]); return $this->sendResponse( new FeedResource($feed->load('language')), 'Feed created successfully.' ); } /** * Create channel for onboarding */ public function createChannel(Request $request): JsonResponse { $validator = Validator::make($request->all(), [ 'name' => 'required|string|max:255', 'platform_instance_id' => 'required|exists:platform_instances,id', 'language_id' => 'required|exists:languages,id', 'description' => 'nullable|string|max:1000', ]); if ($validator->fails()) { throw new ValidationException($validator); } $validated = $validator->validated(); $channel = PlatformChannel::create([ 'platform_instance_id' => $validated['platform_instance_id'], 'channel_id' => $validated['name'], // For Lemmy, this is the community name 'name' => $validated['name'], 'display_name' => ucfirst($validated['name']), 'description' => $validated['description'] ?? null, 'language_id' => $validated['language_id'], 'is_active' => true, ]); return $this->sendResponse( new PlatformChannelResource($channel->load(['platformInstance', 'language'])), 'Channel created successfully.' ); } /** * Create route for onboarding */ public function createRoute(Request $request): JsonResponse { $validator = Validator::make($request->all(), [ 'feed_id' => 'required|exists:feeds,id', 'platform_channel_id' => 'required|exists:platform_channels,id', 'priority' => 'nullable|integer|min:1|max:100', 'filters' => 'nullable|array', ]); if ($validator->fails()) { throw new ValidationException($validator); } $validated = $validator->validated(); $route = Route::create([ 'feed_id' => $validated['feed_id'], 'platform_channel_id' => $validated['platform_channel_id'], 'priority' => $validated['priority'] ?? 50, 'filters' => $validated['filters'] ?? [], 'is_active' => true, ]); return $this->sendResponse( new RouteResource($route->load(['feed', 'platformChannel'])), 'Route created successfully.' ); } /** * Mark onboarding as complete */ public function complete(): JsonResponse { // In a real implementation, you might want to update a user preference // or create a setting that tracks onboarding completion // For now, we'll just return success since the onboarding status // is determined by the existence of platform accounts, feeds, and channels return $this->sendResponse( ['completed' => true], 'Onboarding completed successfully.' ); } /** * Skip onboarding - user can access the app without completing setup */ public function skip(): JsonResponse { Setting::updateOrCreate( ['key' => 'onboarding_skipped'], ['value' => 'true'] ); return $this->sendResponse( ['skipped' => true], 'Onboarding skipped successfully.' ); } /** * Reset onboarding skip status - force user back to onboarding */ public function resetSkip(): JsonResponse { Setting::where('key', 'onboarding_skipped')->delete(); return $this->sendResponse( ['reset' => true], 'Onboarding skip status reset successfully.' ); } }