Fix languages and feeds
This commit is contained in:
parent
f2947b57c0
commit
1c772e63cb
12 changed files with 171 additions and 20 deletions
|
|
@ -57,9 +57,6 @@ public function store(StoreFeedRequest $request): JsonResponse
|
||||||
$validated['url'] = $adapter->getHomepageUrl();
|
$validated['url'] = $adapter->getHomepageUrl();
|
||||||
$validated['type'] = 'website';
|
$validated['type'] = 'website';
|
||||||
|
|
||||||
// Remove provider from validated data as it's not a database column
|
|
||||||
unset($validated['provider']);
|
|
||||||
|
|
||||||
$feed = Feed::create($validated);
|
$feed = Feed::create($validated);
|
||||||
|
|
||||||
return $this->sendResponse(
|
return $this->sendResponse(
|
||||||
|
|
|
||||||
|
|
@ -90,11 +90,17 @@ public function options(): JsonResponse
|
||||||
->orderBy('name')
|
->orderBy('name')
|
||||||
->get(['id', 'platform_instance_id', 'name', 'display_name', 'description']);
|
->get(['id', 'platform_instance_id', 'name', 'display_name', 'description']);
|
||||||
|
|
||||||
|
// Get feed providers from config
|
||||||
|
$feedProviders = collect(config('feed.providers', []))
|
||||||
|
->filter(fn($provider) => $provider['is_active'])
|
||||||
|
->values();
|
||||||
|
|
||||||
return $this->sendResponse([
|
return $this->sendResponse([
|
||||||
'languages' => $languages,
|
'languages' => $languages,
|
||||||
'platform_instances' => $platformInstances,
|
'platform_instances' => $platformInstances,
|
||||||
'feeds' => $feeds,
|
'feeds' => $feeds,
|
||||||
'platform_channels' => $platformChannels,
|
'platform_channels' => $platformChannels,
|
||||||
|
'feed_providers' => $feedProviders,
|
||||||
], 'Onboarding options retrieved successfully.');
|
], 'Onboarding options retrieved successfully.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,6 +221,7 @@ public function createFeed(Request $request): JsonResponse
|
||||||
'name' => $validated['name'],
|
'name' => $validated['name'],
|
||||||
'url' => $url,
|
'url' => $url,
|
||||||
'type' => $type,
|
'type' => $type,
|
||||||
|
'provider' => $provider,
|
||||||
'language_id' => $validated['language_id'],
|
'language_id' => $validated['language_id'],
|
||||||
'description' => $validated['description'] ?? null,
|
'description' => $validated['description'] ?? null,
|
||||||
'is_active' => true,
|
'is_active' => true,
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
* @property string $name
|
* @property string $name
|
||||||
* @property string $url
|
* @property string $url
|
||||||
* @property string $type
|
* @property string $type
|
||||||
|
* @property string $provider
|
||||||
* @property int $language_id
|
* @property int $language_id
|
||||||
* @property Language|null $language
|
* @property Language|null $language
|
||||||
* @property string $description
|
* @property string $description
|
||||||
|
|
@ -38,6 +39,7 @@ class Feed extends Model
|
||||||
'name',
|
'name',
|
||||||
'url',
|
'url',
|
||||||
'type',
|
'type',
|
||||||
|
'provider',
|
||||||
'language_id',
|
'language_id',
|
||||||
'description',
|
'description',
|
||||||
'settings',
|
'settings',
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
namespace App\Services\Factories;
|
namespace App\Services\Factories;
|
||||||
|
|
||||||
use App\Contracts\ArticleParserInterface;
|
use App\Contracts\ArticleParserInterface;
|
||||||
|
use App\Models\Feed;
|
||||||
use App\Services\Parsers\VrtArticleParser;
|
use App\Services\Parsers\VrtArticleParser;
|
||||||
use App\Services\Parsers\BelgaArticleParser;
|
use App\Services\Parsers\BelgaArticleParser;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
|
@ -33,6 +34,25 @@ public static function getParser(string $url): ArticleParserInterface
|
||||||
throw new Exception("No parser found for URL: {$url}");
|
throw new Exception("No parser found for URL: {$url}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function getParserForFeed(Feed $feed, string $parserType = 'article'): ?ArticleParserInterface
|
||||||
|
{
|
||||||
|
if (!$feed->provider) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$providerConfig = config("feed.providers.{$feed->provider}");
|
||||||
|
if (!$providerConfig || !isset($providerConfig['parsers'][$parserType])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parserClass = $providerConfig['parsers'][$parserType];
|
||||||
|
if (!class_exists($parserClass)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new $parserClass();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<int, string>
|
* @return array<int, string>
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,20 @@ public static function getParser(string $url): HomepageParserInterface
|
||||||
|
|
||||||
public static function getParserForFeed(Feed $feed): ?HomepageParserInterface
|
public static function getParserForFeed(Feed $feed): ?HomepageParserInterface
|
||||||
{
|
{
|
||||||
try {
|
if (!$feed->provider) {
|
||||||
return self::getParser($feed->url);
|
|
||||||
} catch (Exception) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$providerConfig = config("feed.providers.{$feed->provider}");
|
||||||
|
if (!$providerConfig || !isset($providerConfig['parsers']['homepage'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parserClass = $providerConfig['parsers']['homepage'];
|
||||||
|
if (!class_exists($parserClass)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new $parserClass();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
56
backend/config/feed.php
Normal file
56
backend/config/feed.php
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Feed Providers
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This array contains the configuration for available feed providers.
|
||||||
|
| Each provider should have a unique code, display name, description,
|
||||||
|
| type (website or rss), and active status.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'providers' => [
|
||||||
|
'vrt' => [
|
||||||
|
'code' => 'vrt',
|
||||||
|
'name' => 'VRT News',
|
||||||
|
'description' => 'Belgian public broadcaster news',
|
||||||
|
'type' => 'website',
|
||||||
|
'is_active' => true,
|
||||||
|
'parsers' => [
|
||||||
|
'homepage' => \App\Services\Parsers\VrtHomepageParserAdapter::class,
|
||||||
|
'article' => \App\Services\Parsers\VrtArticleParser::class,
|
||||||
|
'article_page' => \App\Services\Parsers\VrtArticlePageParser::class,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'belga' => [
|
||||||
|
'code' => 'belga',
|
||||||
|
'name' => 'Belga News Agency',
|
||||||
|
'description' => 'Belgian national news agency',
|
||||||
|
'type' => 'rss',
|
||||||
|
'is_active' => true,
|
||||||
|
'parsers' => [
|
||||||
|
'homepage' => \App\Services\Parsers\BelgaHomepageParserAdapter::class,
|
||||||
|
'article' => \App\Services\Parsers\BelgaArticleParser::class,
|
||||||
|
'article_page' => \App\Services\Parsers\BelgaArticlePageParser::class,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default Feed Settings
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Default configuration values for feed processing
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'defaults' => [
|
||||||
|
'fetch_interval' => 3600, // 1 hour in seconds
|
||||||
|
'max_articles_per_fetch' => 50,
|
||||||
|
'article_retention_days' => 30,
|
||||||
|
],
|
||||||
|
];
|
||||||
51
backend/config/languages.php
Normal file
51
backend/config/languages.php
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Supported Languages
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This array contains all supported languages in the application.
|
||||||
|
| Each language has a short_code, display name, native name, and active status.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'supported' => [
|
||||||
|
'en' => [
|
||||||
|
'short_code' => 'en',
|
||||||
|
'name' => 'English',
|
||||||
|
'native_name' => 'English',
|
||||||
|
'is_active' => true,
|
||||||
|
],
|
||||||
|
'nl' => [
|
||||||
|
'short_code' => 'nl',
|
||||||
|
'name' => 'Dutch',
|
||||||
|
'native_name' => 'Nederlands',
|
||||||
|
'is_active' => true,
|
||||||
|
],
|
||||||
|
'fr' => [
|
||||||
|
'short_code' => 'fr',
|
||||||
|
'name' => 'French',
|
||||||
|
'native_name' => 'Français',
|
||||||
|
'is_active' => true,
|
||||||
|
],
|
||||||
|
'de' => [
|
||||||
|
'short_code' => 'de',
|
||||||
|
'name' => 'German',
|
||||||
|
'native_name' => 'Deutsch',
|
||||||
|
'is_active' => true,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default Language
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The default language code when no language is specified
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'default' => 'en',
|
||||||
|
];
|
||||||
|
|
@ -19,6 +19,7 @@ public function definition(): array
|
||||||
'name' => $this->faker->words(3, true),
|
'name' => $this->faker->words(3, true),
|
||||||
'url' => $this->faker->url(),
|
'url' => $this->faker->url(),
|
||||||
'type' => $this->faker->randomElement(['website', 'rss']),
|
'type' => $this->faker->randomElement(['website', 'rss']),
|
||||||
|
'provider' => $this->faker->randomElement(['vrt', 'belga']),
|
||||||
'language_id' => null,
|
'language_id' => null,
|
||||||
'description' => $this->faker->optional()->sentence(),
|
'description' => $this->faker->optional()->sentence(),
|
||||||
'settings' => [],
|
'settings' => [],
|
||||||
|
|
@ -61,4 +62,20 @@ public function language(Language $language): static
|
||||||
'language_id' => $language->id,
|
'language_id' => $language->id,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function vrt(): static
|
||||||
|
{
|
||||||
|
return $this->state(fn (array $attributes) => [
|
||||||
|
'provider' => 'vrt',
|
||||||
|
'url' => 'https://www.vrt.be/vrtnws/en/',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function belga(): static
|
||||||
|
{
|
||||||
|
return $this->state(fn (array $attributes) => [
|
||||||
|
'provider' => 'belga',
|
||||||
|
'url' => 'https://www.belganewsagency.eu/',
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -14,6 +14,7 @@ public function up(): void
|
||||||
$table->string('name'); // "VRT News", "Belga News Agency"
|
$table->string('name'); // "VRT News", "Belga News Agency"
|
||||||
$table->string('url'); // "https://vrt.be" or "https://feeds.example.com/rss.xml"
|
$table->string('url'); // "https://vrt.be" or "https://feeds.example.com/rss.xml"
|
||||||
$table->enum('type', ['website', 'rss']); // Feed type
|
$table->enum('type', ['website', 'rss']); // Feed type
|
||||||
|
$table->string('provider'); // Feed provider code (vrt, belga, etc.)
|
||||||
$table->foreignId('language_id')->nullable()->constrained();
|
$table->foreignId('language_id')->nullable()->constrained();
|
||||||
$table->text('description')->nullable();
|
$table->text('description')->nullable();
|
||||||
$table->json('settings')->nullable(); // Custom settings per feed type
|
$table->json('settings')->nullable(); // Custom settings per feed type
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,7 @@ public function run(): void
|
||||||
{
|
{
|
||||||
$this->call([
|
$this->call([
|
||||||
SettingsSeeder::class,
|
SettingsSeeder::class,
|
||||||
]);
|
|
||||||
|
|
||||||
// Seed languages in local/dev environment only to avoid conflicts in tests
|
|
||||||
if (app()->environment('local')) {
|
|
||||||
$this->call([
|
|
||||||
LanguageSeeder::class,
|
LanguageSeeder::class,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,7 @@ class LanguageSeeder extends Seeder
|
||||||
{
|
{
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
$languages = [
|
$languages = config('languages.supported', []);
|
||||||
['short_code' => 'en', 'name' => 'English', 'native_name' => 'English', 'is_active' => true],
|
|
||||||
['short_code' => 'nl', 'name' => 'Dutch', 'native_name' => 'Nederlands', 'is_active' => true],
|
|
||||||
['short_code' => 'fr', 'name' => 'French', 'native_name' => 'Français', 'is_active' => true],
|
|
||||||
['short_code' => 'de', 'name' => 'German', 'native_name' => 'Deutsch', 'is_active' => true],
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($languages as $language) {
|
foreach ($languages as $language) {
|
||||||
DB::table('languages')->updateOrInsert(
|
DB::table('languages')->updateOrInsert(
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ class FeedTest extends TestCase
|
||||||
|
|
||||||
public function test_fillable_fields(): void
|
public function test_fillable_fields(): void
|
||||||
{
|
{
|
||||||
$fillableFields = ['name', 'url', 'type', 'language_id', 'description', 'settings', 'is_active', 'last_fetched_at'];
|
$fillableFields = ['name', 'url', 'type', 'provider', 'language_id', 'description', 'settings', 'is_active', 'last_fetched_at'];
|
||||||
$feed = new Feed();
|
$feed = new Feed();
|
||||||
|
|
||||||
$this->assertEquals($fillableFields, $feed->getFillable());
|
$this->assertEquals($fillableFields, $feed->getFillable());
|
||||||
|
|
@ -240,6 +240,7 @@ public function test_feed_creation_with_explicit_values(): void
|
||||||
'name' => 'Test Feed',
|
'name' => 'Test Feed',
|
||||||
'url' => 'https://example.com/feed',
|
'url' => 'https://example.com/feed',
|
||||||
'type' => 'rss',
|
'type' => 'rss',
|
||||||
|
'provider' => 'vrt',
|
||||||
'language_id' => $language->id,
|
'language_id' => $language->id,
|
||||||
'description' => 'Test description',
|
'description' => 'Test description',
|
||||||
'settings' => $settings,
|
'settings' => $settings,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue