349 lines
11 KiB
PHP
349 lines
11 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire;
|
|
|
|
use App\Jobs\ArticleDiscoveryJob;
|
|
use App\Models\Feed;
|
|
use App\Models\Language;
|
|
use App\Models\PlatformAccount;
|
|
use App\Models\PlatformChannel;
|
|
use App\Models\PlatformInstance;
|
|
use App\Models\Route;
|
|
use App\Models\Setting;
|
|
use App\Services\Auth\LemmyAuthService;
|
|
use App\Services\OnboardingService;
|
|
use Illuminate\Support\Facades\Crypt;
|
|
use Livewire\Component;
|
|
|
|
class Onboarding extends Component
|
|
{
|
|
// Step tracking (1-6: welcome, platform, feed, channel, route, complete)
|
|
public int $step = 1;
|
|
|
|
// Platform form
|
|
public string $instanceUrl = '';
|
|
public string $username = '';
|
|
public string $password = '';
|
|
public ?array $existingAccount = null;
|
|
|
|
// Feed form
|
|
public string $feedName = '';
|
|
public string $feedProvider = 'vrt';
|
|
public ?int $feedLanguageId = null;
|
|
public string $feedDescription = '';
|
|
|
|
// Channel form
|
|
public string $channelName = '';
|
|
public ?int $platformInstanceId = null;
|
|
public ?int $channelLanguageId = null;
|
|
public string $channelDescription = '';
|
|
|
|
// Route form
|
|
public ?int $routeFeedId = null;
|
|
public ?int $routeChannelId = null;
|
|
public int $routePriority = 50;
|
|
|
|
// State
|
|
public array $errors = [];
|
|
public bool $isLoading = false;
|
|
|
|
protected LemmyAuthService $lemmyAuthService;
|
|
|
|
public function boot(LemmyAuthService $lemmyAuthService): void
|
|
{
|
|
$this->lemmyAuthService = $lemmyAuthService;
|
|
}
|
|
|
|
public function mount(): void
|
|
{
|
|
// Check for existing platform account
|
|
$account = PlatformAccount::where('is_active', true)->first();
|
|
if ($account) {
|
|
$this->existingAccount = [
|
|
'id' => $account->id,
|
|
'username' => $account->username,
|
|
'instance_url' => $account->instance_url,
|
|
];
|
|
}
|
|
|
|
// Pre-fill feed form if exists
|
|
$feed = Feed::where('is_active', true)->first();
|
|
if ($feed) {
|
|
$this->feedName = $feed->name;
|
|
$this->feedProvider = $feed->provider ?? 'vrt';
|
|
$this->feedLanguageId = $feed->language_id;
|
|
$this->feedDescription = $feed->description ?? '';
|
|
}
|
|
|
|
// Pre-fill channel form if exists
|
|
$channel = PlatformChannel::where('is_active', true)->first();
|
|
if ($channel) {
|
|
$this->channelName = $channel->name;
|
|
$this->platformInstanceId = $channel->platform_instance_id;
|
|
$this->channelLanguageId = $channel->language_id;
|
|
$this->channelDescription = $channel->description ?? '';
|
|
}
|
|
|
|
// Pre-fill route form if exists
|
|
$route = Route::where('is_active', true)->first();
|
|
if ($route) {
|
|
$this->routeFeedId = $route->feed_id;
|
|
$this->routeChannelId = $route->platform_channel_id;
|
|
$this->routePriority = $route->priority;
|
|
}
|
|
}
|
|
|
|
public function goToStep(int $step): void
|
|
{
|
|
$this->step = $step;
|
|
$this->errors = [];
|
|
}
|
|
|
|
public function nextStep(): void
|
|
{
|
|
$this->step++;
|
|
$this->errors = [];
|
|
}
|
|
|
|
public function previousStep(): void
|
|
{
|
|
if ($this->step > 1) {
|
|
$this->step--;
|
|
$this->errors = [];
|
|
}
|
|
}
|
|
|
|
public function continueWithExistingAccount(): void
|
|
{
|
|
$this->nextStep();
|
|
}
|
|
|
|
public function deleteAccount(): void
|
|
{
|
|
if ($this->existingAccount) {
|
|
PlatformAccount::destroy($this->existingAccount['id']);
|
|
$this->existingAccount = null;
|
|
}
|
|
}
|
|
|
|
public function createPlatformAccount(): void
|
|
{
|
|
$this->errors = [];
|
|
$this->isLoading = true;
|
|
|
|
$this->validate([
|
|
'instanceUrl' => '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',
|
|
], [
|
|
'instanceUrl.regex' => 'Please enter a valid domain name (e.g., lemmy.world, belgae.social)',
|
|
]);
|
|
|
|
$fullInstanceUrl = 'https://' . $this->instanceUrl;
|
|
|
|
try {
|
|
// Create or get platform instance
|
|
$platformInstance = PlatformInstance::firstOrCreate([
|
|
'url' => $fullInstanceUrl,
|
|
'platform' => 'lemmy',
|
|
], [
|
|
'name' => ucfirst($this->instanceUrl),
|
|
'is_active' => true,
|
|
]);
|
|
|
|
// Authenticate with Lemmy API
|
|
$authResponse = $this->lemmyAuthService->authenticate(
|
|
$fullInstanceUrl,
|
|
$this->username,
|
|
$this->password
|
|
);
|
|
|
|
// 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 = [
|
|
'id' => $platformAccount->id,
|
|
'username' => $platformAccount->username,
|
|
'instance_url' => $platformAccount->instance_url,
|
|
];
|
|
|
|
$this->nextStep();
|
|
} catch (\App\Exceptions\PlatformAuthException $e) {
|
|
if (str_contains($e->getMessage(), 'Rate limited by')) {
|
|
$this->errors['general'] = $e->getMessage();
|
|
} else {
|
|
$this->errors['general'] = 'Invalid username or password. Please check your credentials and try again.';
|
|
}
|
|
} catch (\Exception $e) {
|
|
$this->errors['general'] = 'Unable to connect to the Lemmy instance. Please check the URL and try again.';
|
|
} finally {
|
|
$this->isLoading = false;
|
|
}
|
|
}
|
|
|
|
public function createFeed(): void
|
|
{
|
|
$this->errors = [];
|
|
$this->isLoading = true;
|
|
|
|
$this->validate([
|
|
'feedName' => 'required|string|max:255',
|
|
'feedProvider' => 'required|in:belga,vrt',
|
|
'feedLanguageId' => 'required|exists:languages,id',
|
|
'feedDescription' => 'nullable|string|max:1000',
|
|
]);
|
|
|
|
try {
|
|
// Map provider to URL
|
|
$url = $this->feedProvider === 'vrt'
|
|
? 'https://www.vrt.be/vrtnws/en/'
|
|
: 'https://www.belganewsagency.eu/';
|
|
|
|
Feed::firstOrCreate(
|
|
['url' => $url],
|
|
[
|
|
'name' => $this->feedName,
|
|
'type' => 'website',
|
|
'provider' => $this->feedProvider,
|
|
'language_id' => $this->feedLanguageId,
|
|
'description' => $this->feedDescription ?: null,
|
|
'is_active' => true,
|
|
]
|
|
);
|
|
|
|
$this->nextStep();
|
|
} catch (\Exception $e) {
|
|
$this->errors['general'] = 'Failed to create feed. Please try again.';
|
|
} finally {
|
|
$this->isLoading = false;
|
|
}
|
|
}
|
|
|
|
public function createChannel(): void
|
|
{
|
|
$this->errors = [];
|
|
$this->isLoading = true;
|
|
|
|
$this->validate([
|
|
'channelName' => 'required|string|max:255',
|
|
'platformInstanceId' => 'required|exists:platform_instances,id',
|
|
'channelLanguageId' => 'required|exists:languages,id',
|
|
'channelDescription' => 'nullable|string|max:1000',
|
|
]);
|
|
|
|
try {
|
|
$platformInstance = PlatformInstance::findOrFail($this->platformInstanceId);
|
|
|
|
// Check for active platform accounts
|
|
$activeAccounts = PlatformAccount::where('instance_url', $platformInstance->url)
|
|
->where('is_active', true)
|
|
->get();
|
|
|
|
if ($activeAccounts->isEmpty()) {
|
|
$this->errors['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(),
|
|
]);
|
|
|
|
$this->nextStep();
|
|
} catch (\Exception $e) {
|
|
$this->errors['general'] = 'Failed to create channel. Please try again.';
|
|
} finally {
|
|
$this->isLoading = false;
|
|
}
|
|
}
|
|
|
|
public function createRoute(): void
|
|
{
|
|
$this->errors = [];
|
|
$this->isLoading = true;
|
|
|
|
$this->validate([
|
|
'routeFeedId' => 'required|exists:feeds,id',
|
|
'routeChannelId' => 'required|exists:platform_channels,id',
|
|
'routePriority' => 'nullable|integer|min:1|max:100',
|
|
]);
|
|
|
|
try {
|
|
Route::create([
|
|
'feed_id' => $this->routeFeedId,
|
|
'platform_channel_id' => $this->routeChannelId,
|
|
'priority' => $this->routePriority,
|
|
'is_active' => true,
|
|
]);
|
|
|
|
// Trigger article discovery
|
|
ArticleDiscoveryJob::dispatch();
|
|
|
|
$this->nextStep();
|
|
} catch (\Exception $e) {
|
|
$this->errors['general'] = 'Failed to create route. Please try again.';
|
|
} finally {
|
|
$this->isLoading = false;
|
|
}
|
|
}
|
|
|
|
public function completeOnboarding(): void
|
|
{
|
|
Setting::updateOrCreate(
|
|
['key' => 'onboarding_completed'],
|
|
['value' => now()->toIso8601String()]
|
|
);
|
|
|
|
app(OnboardingService::class)->clearCache();
|
|
|
|
$this->redirect(route('dashboard'));
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
$languages = Language::where('is_active', true)->orderBy('name')->get();
|
|
$platformInstances = PlatformInstance::where('is_active', true)->orderBy('name')->get();
|
|
$feeds = Feed::where('is_active', true)->orderBy('name')->get();
|
|
$channels = PlatformChannel::where('is_active', true)->orderBy('name')->get();
|
|
|
|
$feedProviders = collect(config('feed.providers', []))
|
|
->filter(fn($provider) => $provider['is_active'] ?? false)
|
|
->values();
|
|
|
|
return view('livewire.onboarding', [
|
|
'languages' => $languages,
|
|
'platformInstances' => $platformInstances,
|
|
'feeds' => $feeds,
|
|
'channels' => $channels,
|
|
'feedProviders' => $feedProviders,
|
|
])->layout('layouts.guest');
|
|
}
|
|
}
|