Release v1.1.0 #79

Merged
myrmidex merged 12 commits from release/v1.1.0 into main 2026-03-08 11:44:53 +01:00
14 changed files with 239 additions and 176 deletions
Showing only changes of commit 025794c852 - Show all commits

View file

@ -5,6 +5,8 @@
use App\Models\PlatformAccount; use App\Models\PlatformAccount;
use App\Models\PlatformChannel; use App\Models\PlatformChannel;
use App\Models\PlatformInstance; use App\Models\PlatformInstance;
use Illuminate\Support\Facades\DB;
use RuntimeException;
class CreateChannelAction class CreateChannelAction
{ {
@ -17,26 +19,28 @@ public function execute(string $name, int $platformInstanceId, ?int $languageId
->get(); ->get();
if ($activeAccounts->isEmpty()) { if ($activeAccounts->isEmpty()) {
throw new \RuntimeException('No active platform accounts found for this instance. Please create a platform account first.'); throw new RuntimeException('No active platform accounts found for this instance. Please create a platform account first.');
} }
$channel = PlatformChannel::create([ return DB::transaction(function () use ($name, $platformInstanceId, $languageId, $description, $activeAccounts) {
'platform_instance_id' => $platformInstanceId, $channel = PlatformChannel::create([
'channel_id' => $name, 'platform_instance_id' => $platformInstanceId,
'name' => $name, 'channel_id' => $name,
'display_name' => ucfirst($name), 'name' => $name,
'description' => $description, 'display_name' => ucfirst($name),
'language_id' => $languageId, 'description' => $description,
'is_active' => true, 'language_id' => $languageId,
]); 'is_active' => true,
]);
$channel->platformAccounts()->attach($activeAccounts->first()->id, [ $channel->platformAccounts()->attach($activeAccounts->first()->id, [
'is_active' => true, 'is_active' => true,
'priority' => 1, 'priority' => 1,
'created_at' => now(), 'created_at' => now(),
'updated_at' => now(), 'updated_at' => now(),
]); ]);
return $channel->load('platformAccounts'); return $channel->load('platformAccounts');
});
} }
} }

View file

@ -4,6 +4,7 @@
use App\Models\Feed; use App\Models\Feed;
use App\Models\Language; use App\Models\Language;
use InvalidArgumentException;
class CreateFeedAction class CreateFeedAction
{ {
@ -15,7 +16,7 @@ public function execute(string $name, string $provider, int $languageId, ?string
$url = config("feed.providers.{$provider}.languages.{$langCode}.url"); $url = config("feed.providers.{$provider}.languages.{$langCode}.url");
if (!$url) { if (!$url) {
throw new \InvalidArgumentException("Invalid provider and language combination: {$provider}/{$langCode}"); throw new InvalidArgumentException("Invalid provider and language combination: {$provider}/{$langCode}");
} }
$providerConfig = config("feed.providers.{$provider}"); $providerConfig = config("feed.providers.{$provider}");

View file

@ -6,6 +6,7 @@
use App\Models\PlatformAccount; use App\Models\PlatformAccount;
use App\Models\PlatformInstance; use App\Models\PlatformInstance;
use App\Services\Auth\LemmyAuthService; use App\Services\Auth\LemmyAuthService;
use Illuminate\Support\Facades\DB;
class CreatePlatformAccountAction class CreatePlatformAccountAction
{ {
@ -23,28 +24,30 @@ public function execute(string $instanceDomain, string $username, string $passwo
// Authenticate first — if this fails, no records are created // Authenticate first — if this fails, no records are created
$authResponse = $this->lemmyAuthService->authenticate($fullInstanceUrl, $username, $password); $authResponse = $this->lemmyAuthService->authenticate($fullInstanceUrl, $username, $password);
$platformInstance = PlatformInstance::firstOrCreate([ return DB::transaction(function () use ($fullInstanceUrl, $instanceDomain, $username, $password, $platform, $authResponse) {
'url' => $fullInstanceUrl, $platformInstance = PlatformInstance::firstOrCreate([
'platform' => $platform, 'url' => $fullInstanceUrl,
], [ 'platform' => $platform,
'name' => ucfirst($instanceDomain), ], [
'is_active' => true, 'name' => ucfirst($instanceDomain),
]); 'is_active' => true,
]);
return PlatformAccount::create([ return PlatformAccount::create([
'platform' => $platform, 'platform' => $platform,
'instance_url' => $fullInstanceUrl, 'instance_url' => $fullInstanceUrl,
'username' => $username, 'username' => $username,
'password' => $password, 'password' => $password,
'settings' => [ 'settings' => [
'display_name' => $authResponse['person_view']['person']['display_name'] ?? null, 'display_name' => $authResponse['person_view']['person']['display_name'] ?? null,
'description' => $authResponse['person_view']['person']['bio'] ?? null, 'description' => $authResponse['person_view']['person']['bio'] ?? null,
'person_id' => $authResponse['person_view']['person']['id'] ?? null, 'person_id' => $authResponse['person_view']['person']['id'] ?? null,
'platform_instance_id' => $platformInstance->id, 'platform_instance_id' => $platformInstance->id,
'api_token' => $authResponse['jwt'] ?? null, 'api_token' => $authResponse['jwt'] ?? null,
], ],
'is_active' => true, 'is_active' => true,
'status' => 'active', 'status' => 'active',
]); ]);
});
} }
} }

View file

@ -2,12 +2,15 @@
namespace App\Http\Controllers\Api\V1; namespace App\Http\Controllers\Api\V1;
use App\Actions\CreateFeedAction;
use App\Http\Requests\StoreFeedRequest; use App\Http\Requests\StoreFeedRequest;
use App\Http\Requests\UpdateFeedRequest; use App\Http\Requests\UpdateFeedRequest;
use App\Http\Resources\FeedResource; use App\Http\Resources\FeedResource;
use App\Models\Feed; use App\Models\Feed;
use Exception;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use InvalidArgumentException;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
class FeedsController extends BaseController class FeedsController extends BaseController
@ -41,32 +44,26 @@ public function index(Request $request): JsonResponse
/** /**
* Store a newly created feed * Store a newly created feed
*/ */
public function store(StoreFeedRequest $request): JsonResponse public function store(StoreFeedRequest $request, CreateFeedAction $createFeedAction): JsonResponse
{ {
try { try {
$validated = $request->validated(); $validated = $request->validated();
$validated['is_active'] = $validated['is_active'] ?? true;
// Map provider to URL and set type $feed = $createFeedAction->execute(
$providers = [ $validated['name'],
'vrt' => new \App\Services\Parsers\VrtHomepageParserAdapter(), $validated['provider'],
'belga' => new \App\Services\Parsers\BelgaHomepageParserAdapter(), $validated['language_id'],
]; $validated['description'] ?? null,
);
$adapter = $providers[$validated['provider']];
$validated['url'] = $adapter->getHomepageUrl();
$validated['type'] = 'website';
$feed = Feed::create($validated);
return $this->sendResponse( return $this->sendResponse(
new FeedResource($feed), new FeedResource($feed),
'Feed created successfully!', 'Feed created successfully!',
201 201
); );
} catch (ValidationException $e) { } catch (InvalidArgumentException $e) {
return $this->sendValidationError($e->errors()); return $this->sendError($e->getMessage(), [], 422);
} catch (\Exception $e) { } catch (Exception $e) {
return $this->sendError('Failed to create feed: ' . $e->getMessage(), [], 500); return $this->sendError('Failed to create feed: ' . $e->getMessage(), [], 500);
} }
} }
@ -99,7 +96,7 @@ public function update(UpdateFeedRequest $request, Feed $feed): JsonResponse
); );
} catch (ValidationException $e) { } catch (ValidationException $e) {
return $this->sendValidationError($e->errors()); return $this->sendValidationError($e->errors());
} catch (\Exception $e) { } catch (Exception $e) {
return $this->sendError('Failed to update feed: ' . $e->getMessage(), [], 500); return $this->sendError('Failed to update feed: ' . $e->getMessage(), [], 500);
} }
} }
@ -116,7 +113,7 @@ public function destroy(Feed $feed): JsonResponse
null, null,
'Feed deleted successfully!' 'Feed deleted successfully!'
); );
} catch (\Exception $e) { } catch (Exception $e) {
return $this->sendError('Failed to delete feed: ' . $e->getMessage(), [], 500); return $this->sendError('Failed to delete feed: ' . $e->getMessage(), [], 500);
} }
} }
@ -136,7 +133,7 @@ public function toggle(Feed $feed): JsonResponse
new FeedResource($feed->fresh()), new FeedResource($feed->fresh()),
"Feed {$status} successfully!" "Feed {$status} successfully!"
); );
} catch (\Exception $e) { } catch (Exception $e) {
return $this->sendError('Failed to toggle feed status: ' . $e->getMessage(), [], 500); return $this->sendError('Failed to toggle feed status: ' . $e->getMessage(), [], 500);
} }
} }

View file

@ -2,16 +2,21 @@
namespace App\Http\Controllers\Api\V1; namespace App\Http\Controllers\Api\V1;
use App\Enums\PlatformEnum; use App\Actions\CreatePlatformAccountAction;
use App\Exceptions\PlatformAuthException;
use App\Http\Requests\StorePlatformAccountRequest;
use App\Http\Resources\PlatformAccountResource; use App\Http\Resources\PlatformAccountResource;
use App\Models\PlatformAccount; use App\Models\PlatformAccount;
use App\Models\PlatformInstance; use Exception;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
class PlatformAccountsController extends BaseController class PlatformAccountsController extends BaseController
{ {
public function __construct(
private readonly CreatePlatformAccountAction $createPlatformAccountAction,
) {}
/** /**
* Display a listing of platform accounts * Display a listing of platform accounts
*/ */
@ -30,46 +35,30 @@ public function index(): JsonResponse
/** /**
* Store a newly created platform account * Store a newly created platform account
*/ */
public function store(Request $request): JsonResponse public function store(StorePlatformAccountRequest $request): JsonResponse
{ {
try { try {
$validated = $request->validate([ $validated = $request->validated();
'platform' => 'required|in:lemmy,mastodon,reddit',
'instance_url' => 'required|url',
'username' => 'required|string|max:255',
'password' => 'required|string',
'settings' => 'nullable|array',
]);
// Create or find platform instance $account = $this->createPlatformAccountAction->execute(
$platformEnum = PlatformEnum::from($validated['platform']); $validated['instance_url'],
$instance = PlatformInstance::firstOrCreate([ $validated['username'],
'platform' => $platformEnum, $validated['password'],
'url' => $validated['instance_url'], $validated['platform'],
], [ );
'name' => parse_url($validated['instance_url'], PHP_URL_HOST),
'description' => ucfirst($validated['platform']) . ' instance',
'is_active' => true,
]);
$account = PlatformAccount::create($validated);
// If this is the first account for this platform, make it active
if (!PlatformAccount::where('platform', $validated['platform'])
->where('is_active', true)
->exists()) {
$account->setAsActive();
}
return $this->sendResponse( return $this->sendResponse(
new PlatformAccountResource($account), new PlatformAccountResource($account),
'Platform account created successfully!', 'Platform account created successfully!',
201 201
); );
} catch (ValidationException $e) { } catch (PlatformAuthException $e) {
return $this->sendValidationError($e->errors()); if (str_contains($e->getMessage(), 'Rate limited by')) {
} catch (\Exception $e) { return $this->sendError($e->getMessage(), [], 429);
return $this->sendError('Failed to create platform account: ' . $e->getMessage(), [], 500); }
return $this->sendError('Invalid username or password. Please check your credentials and try again.', [], 422);
} catch (Exception $e) {
return $this->sendError('Unable to connect to the Lemmy instance. Please check the URL and try again.', [], 422);
} }
} }
@ -110,7 +99,7 @@ public function update(Request $request, PlatformAccount $platformAccount): Json
); );
} catch (ValidationException $e) { } catch (ValidationException $e) {
return $this->sendValidationError($e->errors()); return $this->sendValidationError($e->errors());
} catch (\Exception $e) { } catch (Exception $e) {
return $this->sendError('Failed to update platform account: ' . $e->getMessage(), [], 500); return $this->sendError('Failed to update platform account: ' . $e->getMessage(), [], 500);
} }
} }
@ -127,7 +116,7 @@ public function destroy(PlatformAccount $platformAccount): JsonResponse
null, null,
'Platform account deleted successfully!' 'Platform account deleted successfully!'
); );
} catch (\Exception $e) { } catch (Exception $e) {
return $this->sendError('Failed to delete platform account: ' . $e->getMessage(), [], 500); return $this->sendError('Failed to delete platform account: ' . $e->getMessage(), [], 500);
} }
} }
@ -144,7 +133,7 @@ public function setActive(PlatformAccount $platformAccount): JsonResponse
new PlatformAccountResource($platformAccount->fresh()), new PlatformAccountResource($platformAccount->fresh()),
"Set {$platformAccount->username}@{$platformAccount->instance_url} as active for {$platformAccount->platform->value}!" "Set {$platformAccount->username}@{$platformAccount->instance_url} as active for {$platformAccount->platform->value}!"
); );
} catch (\Exception $e) { } catch (Exception $e) {
return $this->sendError('Failed to set platform account as active: ' . $e->getMessage(), [], 500); return $this->sendError('Failed to set platform account as active: ' . $e->getMessage(), [], 500);
} }
} }

View file

@ -2,12 +2,16 @@
namespace App\Http\Controllers\Api\V1; namespace App\Http\Controllers\Api\V1;
use App\Actions\CreateChannelAction;
use App\Http\Requests\StorePlatformChannelRequest;
use App\Http\Resources\PlatformChannelResource; use App\Http\Resources\PlatformChannelResource;
use App\Models\PlatformChannel; use App\Models\PlatformChannel;
use App\Models\PlatformAccount; use App\Models\PlatformAccount;
use Exception;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
use RuntimeException;
class PlatformChannelsController extends BaseController class PlatformChannelsController extends BaseController
{ {
@ -30,55 +34,26 @@ public function index(): JsonResponse
/** /**
* Store a newly created platform channel * Store a newly created platform channel
*/ */
public function store(Request $request): JsonResponse public function store(StorePlatformChannelRequest $request, CreateChannelAction $createChannelAction): JsonResponse
{ {
try { try {
$validated = $request->validate([ $validated = $request->validated();
'platform_instance_id' => 'required|exists:platform_instances,id',
'channel_id' => 'required|string|max:255',
'name' => 'required|string|max:255',
'display_name' => 'nullable|string|max:255',
'description' => 'nullable|string',
'is_active' => 'boolean',
]);
$validated['is_active'] = $validated['is_active'] ?? true; $channel = $createChannelAction->execute(
$validated['name'],
// Get the platform instance to check for active accounts $validated['platform_instance_id'],
$platformInstance = \App\Models\PlatformInstance::findOrFail($validated['platform_instance_id']); $validated['language_id'] ?? null,
$validated['description'] ?? null,
// Check if there are active platform accounts for this instance );
$activeAccounts = PlatformAccount::where('instance_url', $platformInstance->url)
->where('is_active', true)
->get();
if ($activeAccounts->isEmpty()) {
return $this->sendError(
'Cannot create channel: No active platform accounts found for this instance. Please create a platform account first.',
[],
422
);
}
$channel = PlatformChannel::create($validated);
// Automatically attach the first active account to the channel
$firstAccount = $activeAccounts->first();
$channel->platformAccounts()->attach($firstAccount->id, [
'is_active' => true,
'priority' => 1,
'created_at' => now(),
'updated_at' => now(),
]);
return $this->sendResponse( return $this->sendResponse(
new PlatformChannelResource($channel->load(['platformInstance', 'platformAccounts'])), new PlatformChannelResource($channel->load(['platformInstance', 'platformAccounts'])),
'Platform channel created successfully and linked to platform account!', 'Platform channel created successfully and linked to platform account!',
201 201
); );
} catch (ValidationException $e) { } catch (RuntimeException $e) {
return $this->sendValidationError($e->errors()); return $this->sendError($e->getMessage(), [], 422);
} catch (\Exception $e) { } catch (Exception $e) {
return $this->sendError('Failed to create platform channel: ' . $e->getMessage(), [], 500); return $this->sendError('Failed to create platform channel: ' . $e->getMessage(), [], 500);
} }
} }
@ -115,7 +90,7 @@ public function update(Request $request, PlatformChannel $platformChannel): Json
); );
} catch (ValidationException $e) { } catch (ValidationException $e) {
return $this->sendValidationError($e->errors()); return $this->sendValidationError($e->errors());
} catch (\Exception $e) { } catch (Exception $e) {
return $this->sendError('Failed to update platform channel: ' . $e->getMessage(), [], 500); return $this->sendError('Failed to update platform channel: ' . $e->getMessage(), [], 500);
} }
} }
@ -132,7 +107,7 @@ public function destroy(PlatformChannel $platformChannel): JsonResponse
null, null,
'Platform channel deleted successfully!' 'Platform channel deleted successfully!'
); );
} catch (\Exception $e) { } catch (Exception $e) {
return $this->sendError('Failed to delete platform channel: ' . $e->getMessage(), [], 500); return $this->sendError('Failed to delete platform channel: ' . $e->getMessage(), [], 500);
} }
} }
@ -152,7 +127,7 @@ public function toggle(PlatformChannel $channel): JsonResponse
new PlatformChannelResource($channel->fresh(['platformInstance', 'platformAccounts'])), new PlatformChannelResource($channel->fresh(['platformInstance', 'platformAccounts'])),
"Platform channel {$status} successfully!" "Platform channel {$status} successfully!"
); );
} catch (\Exception $e) { } catch (Exception $e) {
return $this->sendError('Failed to toggle platform channel status: ' . $e->getMessage(), [], 500); return $this->sendError('Failed to toggle platform channel status: ' . $e->getMessage(), [], 500);
} }
} }
@ -189,7 +164,7 @@ public function attachAccount(PlatformChannel $channel, Request $request): JsonR
); );
} catch (ValidationException $e) { } catch (ValidationException $e) {
return $this->sendValidationError($e->errors()); return $this->sendValidationError($e->errors());
} catch (\Exception $e) { } catch (Exception $e) {
return $this->sendError('Failed to attach platform account: ' . $e->getMessage(), [], 500); return $this->sendError('Failed to attach platform account: ' . $e->getMessage(), [], 500);
} }
} }
@ -210,7 +185,7 @@ public function detachAccount(PlatformChannel $channel, PlatformAccount $account
new PlatformChannelResource($channel->fresh(['platformInstance', 'platformAccounts'])), new PlatformChannelResource($channel->fresh(['platformInstance', 'platformAccounts'])),
'Platform account detached from channel successfully!' 'Platform account detached from channel successfully!'
); );
} catch (\Exception $e) { } catch (Exception $e) {
return $this->sendError('Failed to detach platform account: ' . $e->getMessage(), [], 500); return $this->sendError('Failed to detach platform account: ' . $e->getMessage(), [], 500);
} }
} }
@ -242,7 +217,7 @@ public function updateAccountRelation(PlatformChannel $channel, PlatformAccount
); );
} catch (ValidationException $e) { } catch (ValidationException $e) {
return $this->sendValidationError($e->errors()); return $this->sendValidationError($e->errors());
} catch (\Exception $e) { } catch (Exception $e) {
return $this->sendError('Failed to update platform account relationship: ' . $e->getMessage(), [], 500); return $this->sendError('Failed to update platform account relationship: ' . $e->getMessage(), [], 500);
} }
} }

View file

@ -2,10 +2,13 @@
namespace App\Http\Controllers\Api\V1; namespace App\Http\Controllers\Api\V1;
use App\Actions\CreateRouteAction;
use App\Http\Requests\StoreRouteRequest;
use App\Http\Resources\RouteResource; use App\Http\Resources\RouteResource;
use App\Models\Feed; use App\Models\Feed;
use App\Models\PlatformChannel; use App\Models\PlatformChannel;
use App\Models\Route; use App\Models\Route;
use Exception;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
@ -31,29 +34,24 @@ public function index(): JsonResponse
/** /**
* Store a newly created routing configuration * Store a newly created routing configuration
*/ */
public function store(Request $request): JsonResponse public function store(StoreRouteRequest $request, CreateRouteAction $createRouteAction): JsonResponse
{ {
try { try {
$validated = $request->validate([ $validated = $request->validated();
'feed_id' => 'required|exists:feeds,id',
'platform_channel_id' => 'required|exists:platform_channels,id',
'is_active' => 'boolean',
'priority' => 'nullable|integer|min:0',
]);
$validated['is_active'] = $validated['is_active'] ?? true; $route = $createRouteAction->execute(
$validated['priority'] = $validated['priority'] ?? 0; $validated['feed_id'],
$validated['platform_channel_id'],
$route = Route::create($validated); $validated['priority'] ?? 0,
$validated['is_active'] ?? true,
);
return $this->sendResponse( return $this->sendResponse(
new RouteResource($route->load(['feed', 'platformChannel', 'keywords'])), new RouteResource($route->load(['feed', 'platformChannel', 'keywords'])),
'Routing configuration created successfully!', 'Routing configuration created successfully!',
201 201
); );
} catch (ValidationException $e) { } catch (Exception $e) {
return $this->sendValidationError($e->errors());
} catch (\Exception $e) {
return $this->sendError('Failed to create routing configuration: ' . $e->getMessage(), [], 500); return $this->sendError('Failed to create routing configuration: ' . $e->getMessage(), [], 500);
} }
} }
@ -104,7 +102,7 @@ public function update(Request $request, Feed $feed, PlatformChannel $channel):
); );
} catch (ValidationException $e) { } catch (ValidationException $e) {
return $this->sendValidationError($e->errors()); return $this->sendValidationError($e->errors());
} catch (\Exception $e) { } catch (Exception $e) {
return $this->sendError('Failed to update routing configuration: ' . $e->getMessage(), [], 500); return $this->sendError('Failed to update routing configuration: ' . $e->getMessage(), [], 500);
} }
} }
@ -129,7 +127,7 @@ public function destroy(Feed $feed, PlatformChannel $channel): JsonResponse
null, null,
'Routing configuration deleted successfully!' 'Routing configuration deleted successfully!'
); );
} catch (\Exception $e) { } catch (Exception $e) {
return $this->sendError('Failed to delete routing configuration: ' . $e->getMessage(), [], 500); return $this->sendError('Failed to delete routing configuration: ' . $e->getMessage(), [], 500);
} }
} }
@ -157,7 +155,7 @@ public function toggle(Feed $feed, PlatformChannel $channel): JsonResponse
new RouteResource($route->fresh(['feed', 'platformChannel', 'keywords'])), new RouteResource($route->fresh(['feed', 'platformChannel', 'keywords'])),
"Routing configuration {$status} successfully!" "Routing configuration {$status} successfully!"
); );
} catch (\Exception $e) { } catch (Exception $e) {
return $this->sendError('Failed to toggle routing configuration status: ' . $e->getMessage(), [], 500); return $this->sendError('Failed to toggle routing configuration status: ' . $e->getMessage(), [], 500);
} }
} }

View file

@ -0,0 +1,36 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StorePlatformAccountRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* @return array<string, string>
*/
public function rules(): array
{
return [
'platform' => 'required|in:lemmy',
'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',
];
}
/**
* @return array<string, string>
*/
public function messages(): array
{
return [
'instance_url.regex' => 'Please enter a valid domain name (e.g., lemmy.world, belgae.social)',
];
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StorePlatformChannelRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* @return array<string, string>
*/
public function rules(): array
{
return [
'platform_instance_id' => 'required|exists:platform_instances,id',
'name' => 'required|string|max:255',
'language_id' => 'nullable|exists:languages,id',
'description' => 'nullable|string',
];
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreRouteRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/**
* @return array<string, string>
*/
public function rules(): array
{
return [
'feed_id' => 'required|exists:feeds,id',
'platform_channel_id' => 'required|exists:platform_channels,id',
'is_active' => 'boolean',
'priority' => 'nullable|integer|min:0',
];
}
}

View file

@ -49,8 +49,8 @@ public function test_index_returns_feeds_ordered_by_active_status_then_name(): v
public function test_store_creates_vrt_feed_successfully(): void public function test_store_creates_vrt_feed_successfully(): void
{ {
$language = Language::factory()->create(); $language = Language::factory()->english()->create();
$feedData = [ $feedData = [
'name' => 'VRT Test Feed', 'name' => 'VRT Test Feed',
'provider' => 'vrt', 'provider' => 'vrt',
@ -81,8 +81,8 @@ public function test_store_creates_vrt_feed_successfully(): void
public function test_store_creates_belga_feed_successfully(): void public function test_store_creates_belga_feed_successfully(): void
{ {
$language = Language::factory()->create(); $language = Language::factory()->english()->create();
$feedData = [ $feedData = [
'name' => 'Belga Test Feed', 'name' => 'Belga Test Feed',
'provider' => 'belga', 'provider' => 'belga',
@ -99,7 +99,7 @@ public function test_store_creates_belga_feed_successfully(): void
'data' => [ 'data' => [
'name' => 'Belga Test Feed', 'name' => 'Belga Test Feed',
'url' => 'https://www.belganewsagency.eu/', 'url' => 'https://www.belganewsagency.eu/',
'type' => 'website', 'type' => 'rss',
'is_active' => true, 'is_active' => true,
] ]
]); ]);
@ -107,14 +107,14 @@ public function test_store_creates_belga_feed_successfully(): void
$this->assertDatabaseHas('feeds', [ $this->assertDatabaseHas('feeds', [
'name' => 'Belga Test Feed', 'name' => 'Belga Test Feed',
'url' => 'https://www.belganewsagency.eu/', 'url' => 'https://www.belganewsagency.eu/',
'type' => 'website', 'type' => 'rss',
]); ]);
} }
public function test_store_sets_default_active_status(): void public function test_store_sets_default_active_status(): void
{ {
$language = Language::factory()->create(); $language = Language::factory()->english()->create();
$feedData = [ $feedData = [
'name' => 'Test Feed', 'name' => 'Test Feed',
'provider' => 'vrt', 'provider' => 'vrt',

View file

@ -4,7 +4,9 @@
use App\Models\PlatformAccount; use App\Models\PlatformAccount;
use App\Models\PlatformInstance; use App\Models\PlatformInstance;
use App\Services\Auth\LemmyAuthService;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Mockery;
use Tests\TestCase; use Tests\TestCase;
class PlatformAccountsControllerTest extends TestCase class PlatformAccountsControllerTest extends TestCase
@ -42,12 +44,21 @@ public function test_index_returns_successful_response(): void
public function test_store_creates_platform_account_successfully(): void public function test_store_creates_platform_account_successfully(): void
{ {
$mockAuth = Mockery::mock(LemmyAuthService::class);
$mockAuth->shouldReceive('authenticate')
->once()
->with('https://lemmy.example.com', 'testuser', 'testpass123')
->andReturn([
'jwt' => 'test-token',
'person_view' => ['person' => ['id' => 1, 'display_name' => null, 'bio' => null]],
]);
$this->app->instance(LemmyAuthService::class, $mockAuth);
$data = [ $data = [
'platform' => 'lemmy', 'platform' => 'lemmy',
'instance_url' => 'https://lemmy.example.com', 'instance_url' => 'lemmy.example.com',
'username' => 'testuser', 'username' => 'testuser',
'password' => 'testpass123', 'password' => 'testpass123',
'settings' => ['key' => 'value']
]; ];
$response = $this->postJson('/api/v1/platform-accounts', $data); $response = $this->postJson('/api/v1/platform-accounts', $data);

View file

@ -56,11 +56,8 @@ public function test_store_creates_platform_channel_successfully(): void
$data = [ $data = [
'platform_instance_id' => $instance->id, 'platform_instance_id' => $instance->id,
'channel_id' => 'test_channel', 'name' => 'test_channel',
'name' => 'Test Channel',
'display_name' => 'Test Channel Display',
'description' => 'A test channel', 'description' => 'A test channel',
'is_active' => true
]; ];
$response = $this->postJson('/api/v1/platform-channels', $data); $response = $this->postJson('/api/v1/platform-channels', $data);
@ -89,7 +86,7 @@ public function test_store_creates_platform_channel_successfully(): void
$this->assertDatabaseHas('platform_channels', [ $this->assertDatabaseHas('platform_channels', [
'platform_instance_id' => $instance->id, 'platform_instance_id' => $instance->id,
'channel_id' => 'test_channel', 'channel_id' => 'test_channel',
'name' => 'Test Channel', 'name' => 'test_channel',
]); ]);
} }
@ -98,14 +95,13 @@ public function test_store_validates_required_fields(): void
$response = $this->postJson('/api/v1/platform-channels', []); $response = $this->postJson('/api/v1/platform-channels', []);
$response->assertStatus(422) $response->assertStatus(422)
->assertJsonValidationErrors(['platform_instance_id', 'channel_id', 'name']); ->assertJsonValidationErrors(['platform_instance_id', 'name']);
} }
public function test_store_validates_platform_instance_exists(): void public function test_store_validates_platform_instance_exists(): void
{ {
$data = [ $data = [
'platform_instance_id' => 999, 'platform_instance_id' => 999,
'channel_id' => 'test_channel',
'name' => 'Test Channel' 'name' => 'Test Channel'
]; ];

View file

@ -3,6 +3,7 @@
namespace Tests\Unit\Actions; namespace Tests\Unit\Actions;
use App\Actions\CreatePlatformAccountAction; use App\Actions\CreatePlatformAccountAction;
use App\Enums\PlatformEnum;
use App\Exceptions\PlatformAuthException; use App\Exceptions\PlatformAuthException;
use App\Models\PlatformAccount; use App\Models\PlatformAccount;
use App\Models\PlatformInstance; use App\Models\PlatformInstance;
@ -89,7 +90,7 @@ public function test_propagates_auth_exception(): void
$this->lemmyAuthService $this->lemmyAuthService
->shouldReceive('authenticate') ->shouldReceive('authenticate')
->once() ->once()
->andThrow(new PlatformAuthException(\App\Enums\PlatformEnum::LEMMY, 'Invalid credentials')); ->andThrow(new PlatformAuthException(PlatformEnum::LEMMY, 'Invalid credentials'));
try { try {
$this->action->execute('lemmy.world', 'baduser', 'badpass'); $this->action->execute('lemmy.world', 'baduser', 'badpass');