61 - Refactor Livewire Onboarding to use Actions, fix double-encryption bug

This commit is contained in:
myrmidex 2026-03-08 01:48:09 +01:00
parent 025794c852
commit c28ac317a5

View file

@ -2,6 +2,11 @@
namespace App\Livewire; namespace App\Livewire;
use App\Actions\CreateChannelAction;
use App\Actions\CreateFeedAction;
use App\Actions\CreatePlatformAccountAction;
use App\Actions\CreateRouteAction;
use App\Exceptions\PlatformAuthException;
use App\Jobs\ArticleDiscoveryJob; use App\Jobs\ArticleDiscoveryJob;
use App\Jobs\SyncChannelPostsJob; use App\Jobs\SyncChannelPostsJob;
use App\Models\Feed; use App\Models\Feed;
@ -11,10 +16,11 @@
use App\Models\PlatformInstance; use App\Models\PlatformInstance;
use App\Models\Route; use App\Models\Route;
use App\Models\Setting; use App\Models\Setting;
use App\Services\Auth\LemmyAuthService;
use App\Services\OnboardingService; use App\Services\OnboardingService;
use Illuminate\Support\Facades\Crypt; use Exception;
use InvalidArgumentException;
use Livewire\Component; use Livewire\Component;
use RuntimeException;
class Onboarding extends Component class Onboarding extends Component
{ {
@ -49,11 +55,21 @@ class Onboarding extends Component
public bool $isLoading = false; public bool $isLoading = false;
private ?int $previousChannelLanguageId = null; private ?int $previousChannelLanguageId = null;
protected LemmyAuthService $lemmyAuthService; protected CreatePlatformAccountAction $createPlatformAccountAction;
protected CreateFeedAction $createFeedAction;
protected CreateChannelAction $createChannelAction;
protected CreateRouteAction $createRouteAction;
public function boot(LemmyAuthService $lemmyAuthService): void public function boot(
{ CreatePlatformAccountAction $createPlatformAccountAction,
$this->lemmyAuthService = $lemmyAuthService; CreateFeedAction $createFeedAction,
CreateChannelAction $createChannelAction,
CreateRouteAction $createRouteAction,
): void {
$this->createPlatformAccountAction = $createPlatformAccountAction;
$this->createFeedAction = $createFeedAction;
$this->createChannelAction = $createChannelAction;
$this->createRouteAction = $createRouteAction;
} }
public function mount(): void public function mount(): void
@ -146,42 +162,13 @@ public function createPlatformAccount(): void
'instanceUrl.regex' => 'Please enter a valid domain name (e.g., lemmy.world, belgae.social)', 'instanceUrl.regex' => 'Please enter a valid domain name (e.g., lemmy.world, belgae.social)',
]); ]);
$fullInstanceUrl = 'https://' . $this->instanceUrl;
try { try {
// Authenticate with Lemmy API first (before creating any records) $platformAccount = $this->createPlatformAccountAction->execute(
$authResponse = $this->lemmyAuthService->authenticate( $this->instanceUrl,
$fullInstanceUrl,
$this->username, $this->username,
$this->password $this->password,
); );
// Only create platform instance after successful authentication
$platformInstance = PlatformInstance::firstOrCreate([
'url' => $fullInstanceUrl,
'platform' => 'lemmy',
], [
'name' => ucfirst($this->instanceUrl),
'is_active' => true,
]);
// Create platform account
$platformAccount = PlatformAccount::create([
'platform' => 'lemmy',
'instance_url' => $fullInstanceUrl,
'username' => $this->username,
'password' => Crypt::encryptString($this->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,
],
'is_active' => true,
'status' => 'active',
]);
$this->existingAccount = [ $this->existingAccount = [
'id' => $platformAccount->id, 'id' => $platformAccount->id,
'username' => $platformAccount->username, 'username' => $platformAccount->username,
@ -189,7 +176,7 @@ public function createPlatformAccount(): void
]; ];
$this->nextStep(); $this->nextStep();
} catch (\App\Exceptions\PlatformAuthException $e) { } catch (PlatformAuthException $e) {
$message = $e->getMessage(); $message = $e->getMessage();
if (str_contains($message, 'Rate limited by')) { if (str_contains($message, 'Rate limited by')) {
$this->formErrors['general'] = $message; $this->formErrors['general'] = $message;
@ -198,9 +185,9 @@ public function createPlatformAccount(): void
} else { } else {
$this->formErrors['general'] = 'Invalid username or password. Please check your credentials and try again.'; $this->formErrors['general'] = 'Invalid username or password. Please check your credentials and try again.';
} }
} catch (\Exception $e) { } catch (Exception $e) {
logger()->error('Lemmy platform account creation failed', [ logger()->error('Lemmy platform account creation failed', [
'instance_url' => $fullInstanceUrl, 'instance_url' => 'https://' . $this->instanceUrl,
'username' => $this->username, 'username' => $this->username,
'error' => $e->getMessage(), 'error' => $e->getMessage(),
'class' => get_class($e), 'class' => get_class($e),
@ -227,35 +214,17 @@ public function createFeed(): void
]); ]);
try { try {
// Get language short code $this->createFeedAction->execute(
$language = Language::find($this->feedLanguageId); $this->feedName,
$langCode = $language->short_code; $this->feedProvider,
$this->feedLanguageId,
// Look up URL from config $this->feedDescription ?: null,
$url = config("feed.providers.{$this->feedProvider}.languages.{$langCode}.url");
if (!$url) {
$this->formErrors['general'] = 'Invalid provider and language combination.';
$this->isLoading = false;
return;
}
$providerConfig = config("feed.providers.{$this->feedProvider}");
Feed::firstOrCreate(
['url' => $url],
[
'name' => $this->feedName,
'type' => $providerConfig['type'] ?? 'website',
'provider' => $this->feedProvider,
'language_id' => $this->feedLanguageId,
'description' => $this->feedDescription ?: null,
'is_active' => true,
]
); );
$this->nextStep(); $this->nextStep();
} catch (\Exception $e) { } catch (InvalidArgumentException $e) {
$this->formErrors['general'] = 'Invalid provider and language combination.';
} catch (Exception $e) {
$this->formErrors['general'] = 'Failed to create feed. Please try again.'; $this->formErrors['general'] = 'Failed to create feed. Please try again.';
} finally { } finally {
$this->isLoading = false; $this->isLoading = false;
@ -285,42 +254,20 @@ public function createChannel(): void
$this->previousChannelLanguageId = $this->channelLanguageId; $this->previousChannelLanguageId = $this->channelLanguageId;
try { try {
$platformInstance = PlatformInstance::findOrFail($this->platformInstanceId); $channel = $this->createChannelAction->execute(
$this->channelName,
// Check for active platform accounts $this->platformInstanceId,
$activeAccounts = PlatformAccount::where('instance_url', $platformInstance->url) $this->channelLanguageId,
->where('is_active', true) $this->channelDescription ?: null,
->get(); );
if ($activeAccounts->isEmpty()) {
$this->formErrors['general'] = 'No active platform accounts found for this instance. Please create a platform account first.';
$this->isLoading = false;
return;
}
$channel = PlatformChannel::create([
'platform_instance_id' => $this->platformInstanceId,
'channel_id' => $this->channelName,
'name' => $this->channelName,
'display_name' => ucfirst($this->channelName),
'description' => $this->channelDescription ?: null,
'language_id' => $this->channelLanguageId,
'is_active' => true,
]);
// Attach first active account
$channel->platformAccounts()->attach($activeAccounts->first()->id, [
'is_active' => true,
'priority' => 1,
'created_at' => now(),
'updated_at' => now(),
]);
// Sync existing posts from this channel for duplicate detection // Sync existing posts from this channel for duplicate detection
SyncChannelPostsJob::dispatch($channel); SyncChannelPostsJob::dispatch($channel);
$this->nextStep(); $this->nextStep();
} catch (\Exception $e) { } catch (RuntimeException $e) {
$this->formErrors['general'] = $e->getMessage();
} catch (Exception $e) {
$this->formErrors['general'] = 'Failed to create channel. Please try again.'; $this->formErrors['general'] = 'Failed to create channel. Please try again.';
} finally { } finally {
$this->isLoading = false; $this->isLoading = false;
@ -339,18 +286,17 @@ public function createRoute(): void
]); ]);
try { try {
Route::create([ $this->createRouteAction->execute(
'feed_id' => $this->routeFeedId, $this->routeFeedId,
'platform_channel_id' => $this->routeChannelId, $this->routeChannelId,
'priority' => $this->routePriority, $this->routePriority,
'is_active' => true, );
]);
// Trigger article discovery // Trigger article discovery
ArticleDiscoveryJob::dispatch(); ArticleDiscoveryJob::dispatch();
$this->nextStep(); $this->nextStep();
} catch (\Exception $e) { } catch (Exception $e) {
$this->formErrors['general'] = 'Failed to create route. Please try again.'; $this->formErrors['general'] = 'Failed to create route. Please try again.';
} finally { } finally {
$this->isLoading = false; $this->isLoading = false;