Optimizations
This commit is contained in:
parent
2087ca389e
commit
58848c934e
11 changed files with 1143 additions and 51 deletions
|
|
@ -3,19 +3,23 @@
|
|||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use Domains\Settings\Models\Language;
|
||||
use Domains\Settings\Services\LanguageService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class LanguagesController extends BaseController
|
||||
{
|
||||
public function __construct(
|
||||
private LanguageService $languageService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get languages available for route creation
|
||||
* Returns languages that have both active feeds and active channels
|
||||
*/
|
||||
public function availableForRoutes(): JsonResponse
|
||||
{
|
||||
$languages = Language::availableForRoutes()
|
||||
->orderBy('name')
|
||||
->get(['id', 'short_code', 'name', 'native_name']);
|
||||
$languages = $this->languageService->getAvailableForRoutes();
|
||||
|
||||
return $this->sendResponse(
|
||||
$languages,
|
||||
|
|
@ -26,15 +30,20 @@ public function availableForRoutes(): JsonResponse
|
|||
/**
|
||||
* Get feeds filtered by language
|
||||
*/
|
||||
public function feedsByLanguage(int $languageId): JsonResponse
|
||||
public function feedsByLanguage(Request $request, int $languageId): JsonResponse
|
||||
{
|
||||
$language = Language::findOrFail($languageId);
|
||||
$fields = $this->parseFields($request->get('fields'), [
|
||||
'feeds.id', 'feeds.name', 'feeds.url', 'feeds.type', 'feeds.provider'
|
||||
]);
|
||||
|
||||
$feeds = $language->feeds()
|
||||
->where('feeds.is_active', true)
|
||||
->where('feed_languages.is_active', true)
|
||||
->with('languages')
|
||||
->get(['feeds.id', 'feeds.name', 'feeds.url', 'feeds.type', 'feeds.provider']);
|
||||
$feeds = $this->languageService->getFeedsByLanguage(
|
||||
$languageId,
|
||||
$request->get('search'),
|
||||
(int) $request->get('per_page', 15),
|
||||
$fields
|
||||
);
|
||||
|
||||
$language = Language::select(['name'])->findOrFail($languageId);
|
||||
|
||||
return $this->sendResponse(
|
||||
$feeds,
|
||||
|
|
@ -45,18 +54,93 @@ public function feedsByLanguage(int $languageId): JsonResponse
|
|||
/**
|
||||
* Get channels filtered by language
|
||||
*/
|
||||
public function channelsByLanguage(int $languageId): JsonResponse
|
||||
public function channelsByLanguage(Request $request, int $languageId): JsonResponse
|
||||
{
|
||||
$language = Language::findOrFail($languageId);
|
||||
$fields = $this->parseFields($request->get('fields'), [
|
||||
'id', 'platform_instance_id', 'name', 'display_name', 'description', 'language_id'
|
||||
]);
|
||||
|
||||
$channels = $language->platformChannels()
|
||||
->where('platform_channels.is_active', true)
|
||||
->with(['platformInstance:id,name,url', 'language:id,name,short_code'])
|
||||
->get(['id', 'platform_instance_id', 'name', 'display_name', 'description', 'language_id']);
|
||||
$channels = $this->languageService->getChannelsByLanguage(
|
||||
$languageId,
|
||||
$request->get('search'),
|
||||
(int) $request->get('per_page', 15),
|
||||
$fields
|
||||
);
|
||||
|
||||
$language = Language::select(['name'])->findOrFail($languageId);
|
||||
|
||||
return $this->sendResponse(
|
||||
$channels,
|
||||
"Channels for language '{$language->name}' retrieved successfully."
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get language statistics
|
||||
*/
|
||||
public function statistics(int $languageId): JsonResponse
|
||||
{
|
||||
$stats = $this->languageService->getLanguageStatistics($languageId);
|
||||
$language = Language::select(['name'])->findOrFail($languageId);
|
||||
|
||||
return $this->sendResponse(
|
||||
$stats,
|
||||
"Statistics for language '{$language->name}' retrieved successfully."
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get usage summary for all languages
|
||||
*/
|
||||
public function usageSummary(): JsonResponse
|
||||
{
|
||||
$summary = $this->languageService->getLanguageUsageSummary();
|
||||
|
||||
return $this->sendResponse(
|
||||
$summary,
|
||||
'Language usage summary retrieved successfully.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get common languages between feed and channel
|
||||
*/
|
||||
public function commonLanguages(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'feed_id' => 'required|exists:feeds,id',
|
||||
'channel_id' => 'required|exists:platform_channels,id'
|
||||
]);
|
||||
|
||||
$commonLanguages = $this->languageService->getCommonLanguages(
|
||||
$request->get('feed_id'),
|
||||
$request->get('channel_id')
|
||||
);
|
||||
|
||||
return $this->sendResponse(
|
||||
$commonLanguages,
|
||||
'Common languages retrieved successfully.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse field selection from request
|
||||
*/
|
||||
private function parseFields(?string $fieldsParam, array $defaultFields): array
|
||||
{
|
||||
if (!$fieldsParam) {
|
||||
return $defaultFields;
|
||||
}
|
||||
|
||||
$requestedFields = array_map('trim', explode(',', $fieldsParam));
|
||||
$validFields = [];
|
||||
|
||||
foreach ($requestedFields as $field) {
|
||||
if (in_array($field, $defaultFields) || str_contains($field, '.')) {
|
||||
$validFields[] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
return empty($validFields) ? $defaultFields : $validFields;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,18 +8,22 @@
|
|||
use Domains\Feed\Models\Route;
|
||||
use Domains\Feed\Requests\StoreRouteRequest;
|
||||
use Domains\Feed\Requests\UpdateRouteRequest;
|
||||
use Domains\Settings\Services\LanguageService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class RoutingController extends BaseController
|
||||
{
|
||||
public function __construct(
|
||||
private LanguageService $languageService
|
||||
) {}
|
||||
/**
|
||||
* Display a listing of routing configurations
|
||||
*/
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
$routes = Route::with(['feed', 'platformChannel', 'language', 'keywords'])
|
||||
$routes = Route::withAllRelationships()
|
||||
->orderBy('is_active', 'desc')
|
||||
->orderBy('priority', 'asc')
|
||||
->get();
|
||||
|
|
@ -43,8 +47,17 @@ public function store(StoreRouteRequest $request): JsonResponse
|
|||
|
||||
$route = Route::create($validated);
|
||||
|
||||
// Load relationships efficiently
|
||||
$route->load([
|
||||
'feed:id,name,url,type,provider,is_active',
|
||||
'platformChannel:id,platform_instance_id,name,display_name,description,language_id,is_active',
|
||||
'platformChannel.platformInstance:id,name,url',
|
||||
'language:id,short_code,name,native_name,is_active',
|
||||
'keywords:id,feed_id,platform_channel_id,keyword,is_active'
|
||||
]);
|
||||
|
||||
return $this->sendResponse(
|
||||
new RouteResource($route->load(['feed', 'platformChannel', 'language', 'keywords'])),
|
||||
new RouteResource($route),
|
||||
'Routing configuration created successfully!',
|
||||
201
|
||||
);
|
||||
|
|
@ -66,7 +79,13 @@ public function show(Feed $feed, PlatformChannel $channel): JsonResponse
|
|||
return $this->sendNotFound('Routing configuration not found.');
|
||||
}
|
||||
|
||||
$route->load(['feed', 'platformChannel', 'language', 'keywords']);
|
||||
$route->load([
|
||||
'feed:id,name,url,type,provider,is_active',
|
||||
'platformChannel:id,platform_instance_id,name,display_name,description,language_id,is_active',
|
||||
'platformChannel.platformInstance:id,name,url',
|
||||
'language:id,short_code,name,native_name,is_active',
|
||||
'keywords:id,feed_id,platform_channel_id,keyword,is_active'
|
||||
]);
|
||||
|
||||
return $this->sendResponse(
|
||||
new RouteResource($route),
|
||||
|
|
@ -156,6 +175,19 @@ public function toggle(Feed $feed, PlatformChannel $channel): JsonResponse
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get common languages for feed and channel
|
||||
*/
|
||||
public function commonLanguages(Feed $feed, PlatformChannel $channel): JsonResponse
|
||||
{
|
||||
$commonLanguages = $this->languageService->getCommonLanguages($feed->id, $channel->id);
|
||||
|
||||
return $this->sendResponse(
|
||||
$commonLanguages,
|
||||
'Common languages for feed and channel retrieved successfully.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a route by feed and channel
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Add composite index on feed_languages for language filtering queries
|
||||
Schema::table('feed_languages', function (Blueprint $table) {
|
||||
$table->index(['language_id', 'is_active', 'is_primary'], 'idx_feed_languages_filtering');
|
||||
});
|
||||
|
||||
// Add index on platform_channels for language-based channel lookups
|
||||
Schema::table('platform_channels', function (Blueprint $table) {
|
||||
$table->index(['language_id', 'is_active'], 'idx_platform_channels_language_active');
|
||||
});
|
||||
|
||||
// Add composite index on routes for efficient language-based queries
|
||||
Schema::table('routes', function (Blueprint $table) {
|
||||
$table->index(['language_id', 'is_active', 'priority'], 'idx_routes_language_active_priority');
|
||||
});
|
||||
|
||||
// Add index for language consistency validation queries
|
||||
Schema::table('feed_languages', function (Blueprint $table) {
|
||||
$table->index(['feed_id', 'language_id', 'is_active'], 'idx_feed_languages_validation');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('feed_languages', function (Blueprint $table) {
|
||||
$table->dropIndex('idx_feed_languages_filtering');
|
||||
$table->dropIndex('idx_feed_languages_validation');
|
||||
});
|
||||
|
||||
Schema::table('platform_channels', function (Blueprint $table) {
|
||||
$table->dropIndex('idx_platform_channels_language_active');
|
||||
});
|
||||
|
||||
Schema::table('routes', function (Blueprint $table) {
|
||||
$table->dropIndex('idx_routes_language_active_priority');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
|
|
@ -129,4 +130,42 @@ public function getCommonLanguages(): array
|
|||
|
||||
return in_array($channelLanguageId, $feedLanguageIds) ? [$channelLanguageId] : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope for eager loading all route relationships
|
||||
*/
|
||||
public function scopeWithAllRelationships(Builder $query): Builder
|
||||
{
|
||||
return $query->with([
|
||||
'feed:id,name,url,type,provider,is_active',
|
||||
'platformChannel:id,platform_instance_id,name,display_name,description,language_id,is_active',
|
||||
'platformChannel.platformInstance:id,name,url',
|
||||
'language:id,short_code,name,native_name,is_active',
|
||||
'keywords:id,feed_id,platform_channel_id,keyword,is_active'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope for minimal route data
|
||||
*/
|
||||
public function scopeMinimal(Builder $query): Builder
|
||||
{
|
||||
return $query->select(['feed_id', 'platform_channel_id', 'language_id', 'is_active', 'priority']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope for active routes only
|
||||
*/
|
||||
public function scopeActive(Builder $query): Builder
|
||||
{
|
||||
return $query->where('is_active', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope for routes by language
|
||||
*/
|
||||
public function scopeByLanguage(Builder $query, int $languageId): Builder
|
||||
{
|
||||
return $query->where('language_id', $languageId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace Domains\Feed\Requests;
|
||||
|
||||
use Domains\Feed\Models\Feed;
|
||||
use Domains\Feed\Services\LanguageConsistencyValidator;
|
||||
use Domains\Platform\Models\PlatformChannel;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
|
@ -56,18 +57,12 @@ protected function validateLanguageConsistency(Validator $validator): void
|
|||
return;
|
||||
}
|
||||
|
||||
// Check if feed has the selected language
|
||||
$feed = Feed::find($feedId);
|
||||
if (!$feed || !$feed->languages()->where('languages.id', $languageId)->exists()) {
|
||||
$validator->errors()->add('language_id', 'The selected feed does not support this language.');
|
||||
return;
|
||||
}
|
||||
$consistencyValidator = app(LanguageConsistencyValidator::class);
|
||||
$result = $consistencyValidator->validateConsistency($feedId, $channelId, $languageId);
|
||||
|
||||
// Check if channel has the selected language
|
||||
$channel = PlatformChannel::find($channelId);
|
||||
if (!$channel || $channel->language_id !== (int)$languageId) {
|
||||
$validator->errors()->add('language_id', 'The selected channel does not support this language.');
|
||||
return;
|
||||
if (!$result['is_valid']) {
|
||||
$message = $consistencyValidator->getValidationMessage($result);
|
||||
$validator->errors()->add('language_id', $message);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace Domains\Feed\Requests;
|
||||
|
||||
use Domains\Feed\Models\Feed;
|
||||
use Domains\Feed\Services\LanguageConsistencyValidator;
|
||||
use Domains\Platform\Models\PlatformChannel;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
|
@ -61,16 +62,12 @@ protected function validateLanguageConsistency(Validator $validator): void
|
|||
return;
|
||||
}
|
||||
|
||||
// Check if feed has the selected language
|
||||
if (!$feed->languages()->where('languages.id', $languageId)->exists()) {
|
||||
$validator->errors()->add('language_id', 'The selected feed does not support this language.');
|
||||
return;
|
||||
}
|
||||
$consistencyValidator = app(LanguageConsistencyValidator::class);
|
||||
$result = $consistencyValidator->validateConsistency($feed->id, $channel->id, $languageId);
|
||||
|
||||
// Check if channel has the selected language
|
||||
if ($channel->language_id !== (int)$languageId) {
|
||||
$validator->errors()->add('language_id', 'The selected channel does not support this language.');
|
||||
return;
|
||||
if (!$result['is_valid']) {
|
||||
$message = $consistencyValidator->getValidationMessage($result);
|
||||
$validator->errors()->add('language_id', $message);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,148 @@
|
|||
<?php
|
||||
|
||||
namespace Domains\Feed\Services;
|
||||
|
||||
use Domains\Feed\Models\Feed;
|
||||
use Domains\Platform\Models\PlatformChannel;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class LanguageConsistencyValidator
|
||||
{
|
||||
/**
|
||||
* Validate that a language is consistent between feed and channel in a single optimized query
|
||||
*/
|
||||
public function validateConsistency(int $feedId, int $channelId, int $languageId): array
|
||||
{
|
||||
// Single query to check both feed and channel language consistency
|
||||
$result = DB::selectOne("
|
||||
SELECT
|
||||
EXISTS(
|
||||
SELECT 1
|
||||
FROM feed_languages fl
|
||||
JOIN feeds f ON f.id = fl.feed_id
|
||||
WHERE fl.feed_id = ?
|
||||
AND fl.language_id = ?
|
||||
AND fl.is_active = 1
|
||||
AND f.is_active = 1
|
||||
) as feed_has_language,
|
||||
EXISTS(
|
||||
SELECT 1
|
||||
FROM platform_channels pc
|
||||
WHERE pc.id = ?
|
||||
AND pc.language_id = ?
|
||||
AND pc.is_active = 1
|
||||
) as channel_has_language
|
||||
", [$feedId, $languageId, $channelId, $languageId]);
|
||||
|
||||
return [
|
||||
'is_valid' => $result->feed_has_language && $result->channel_has_language,
|
||||
'feed_supports_language' => (bool) $result->feed_has_language,
|
||||
'channel_supports_language' => (bool) $result->channel_has_language,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get validation error message based on the validation result
|
||||
*/
|
||||
public function getValidationMessage(array $validationResult): ?string
|
||||
{
|
||||
if ($validationResult['is_valid']) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$validationResult['feed_supports_language']) {
|
||||
return 'The selected feed does not support this language.';
|
||||
}
|
||||
|
||||
if (!$validationResult['channel_supports_language']) {
|
||||
return 'The selected channel does not support this language.';
|
||||
}
|
||||
|
||||
return 'Language consistency validation failed.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch validate multiple feed+channel+language combinations
|
||||
*/
|
||||
public function batchValidate(array $combinations): array
|
||||
{
|
||||
if (empty($combinations)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$conditions = [];
|
||||
$params = [];
|
||||
|
||||
foreach ($combinations as $index => $combo) {
|
||||
$feedId = $combo['feed_id'];
|
||||
$channelId = $combo['platform_channel_id'];
|
||||
$languageId = $combo['language_id'];
|
||||
|
||||
$conditions[] = "(fl.feed_id = ? AND fl.language_id = ? AND pc.id = ? AND pc.language_id = ?)";
|
||||
$params = array_merge($params, [$feedId, $languageId, $channelId, $languageId]);
|
||||
}
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
fl.feed_id,
|
||||
pc.id as channel_id,
|
||||
fl.language_id,
|
||||
COUNT(*) as is_valid
|
||||
FROM feed_languages fl
|
||||
JOIN feeds f ON f.id = fl.feed_id AND f.is_active = 1
|
||||
JOIN platform_channels pc ON pc.language_id = fl.language_id AND pc.is_active = 1
|
||||
WHERE fl.is_active = 1
|
||||
AND (" . implode(' OR ', $conditions) . ")
|
||||
GROUP BY fl.feed_id, pc.id, fl.language_id
|
||||
";
|
||||
|
||||
$results = DB::select($sql, $params);
|
||||
|
||||
// Convert results to associative array for easy lookup
|
||||
$validCombinations = [];
|
||||
foreach ($results as $result) {
|
||||
$key = "{$result->feed_id}_{$result->channel_id}_{$result->language_id}";
|
||||
$validCombinations[$key] = $result->is_valid > 0;
|
||||
}
|
||||
|
||||
// Map back to original combinations
|
||||
$output = [];
|
||||
foreach ($combinations as $index => $combo) {
|
||||
$key = "{$combo['feed_id']}_{$combo['platform_channel_id']}_{$combo['language_id']}";
|
||||
$output[$index] = [
|
||||
'is_valid' => $validCombinations[$key] ?? false,
|
||||
'feed_id' => $combo['feed_id'],
|
||||
'channel_id' => $combo['platform_channel_id'],
|
||||
'language_id' => $combo['language_id'],
|
||||
];
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get common languages between a feed and channel efficiently
|
||||
*/
|
||||
public function getCommonLanguages(int $feedId, int $channelId): array
|
||||
{
|
||||
$results = DB::select("
|
||||
SELECT DISTINCT fl.language_id, l.short_code, l.name
|
||||
FROM feed_languages fl
|
||||
JOIN feeds f ON f.id = fl.feed_id AND f.is_active = 1
|
||||
JOIN platform_channels pc ON pc.language_id = fl.language_id AND pc.is_active = 1
|
||||
JOIN languages l ON l.id = fl.language_id AND l.is_active = 1
|
||||
WHERE fl.feed_id = ?
|
||||
AND pc.id = ?
|
||||
AND fl.is_active = 1
|
||||
ORDER BY l.name
|
||||
", [$feedId, $channelId]);
|
||||
|
||||
return array_map(function ($result) {
|
||||
return [
|
||||
'id' => $result->language_id,
|
||||
'short_code' => $result->short_code,
|
||||
'name' => $result->name,
|
||||
];
|
||||
}, $results);
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class Language extends Model
|
||||
{
|
||||
|
|
@ -78,13 +79,20 @@ public function routes(): HasMany
|
|||
*/
|
||||
public function scopeAvailableForRoutes(Builder $query): Builder
|
||||
{
|
||||
return $query->where('is_active', true)
|
||||
->whereHas('feeds', function (Builder $feedQuery) {
|
||||
$feedQuery->where('feeds.is_active', true)
|
||||
return $query->where('languages.is_active', true)
|
||||
->whereExists(function ($subQuery) {
|
||||
$subQuery->select(DB::raw(1))
|
||||
->from('feed_languages')
|
||||
->join('feeds', 'feeds.id', '=', 'feed_languages.feed_id')
|
||||
->whereColumn('feed_languages.language_id', 'languages.id')
|
||||
->where('feeds.is_active', true)
|
||||
->where('feed_languages.is_active', true);
|
||||
})
|
||||
->whereHas('platformChannels', function (Builder $channelQuery) {
|
||||
$channelQuery->where('platform_channels.is_active', true);
|
||||
->whereExists(function ($subQuery) {
|
||||
$subQuery->select(DB::raw(1))
|
||||
->from('platform_channels')
|
||||
->whereColumn('platform_channels.language_id', 'languages.id')
|
||||
->where('platform_channels.is_active', true);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -96,8 +104,12 @@ public function scopeAvailableForRoutes(Builder $query): Builder
|
|||
*/
|
||||
public function scopeWithActiveFeeds(Builder $query): Builder
|
||||
{
|
||||
return $query->whereHas('feeds', function (Builder $feedQuery) {
|
||||
$feedQuery->where('feeds.is_active', true)
|
||||
return $query->whereExists(function ($subQuery) {
|
||||
$subQuery->select(\DB::raw(1))
|
||||
->from('feed_languages')
|
||||
->join('feeds', 'feeds.id', '=', 'feed_languages.feed_id')
|
||||
->whereColumn('feed_languages.language_id', 'languages.id')
|
||||
->where('feeds.is_active', true)
|
||||
->where('feed_languages.is_active', true);
|
||||
});
|
||||
}
|
||||
|
|
@ -110,8 +122,11 @@ public function scopeWithActiveFeeds(Builder $query): Builder
|
|||
*/
|
||||
public function scopeWithActiveChannels(Builder $query): Builder
|
||||
{
|
||||
return $query->whereHas('platformChannels', function (Builder $channelQuery) {
|
||||
$channelQuery->where('platform_channels.is_active', true);
|
||||
return $query->whereExists(function ($subQuery) {
|
||||
$subQuery->select(\DB::raw(1))
|
||||
->from('platform_channels')
|
||||
->whereColumn('platform_channels.language_id', 'languages.id')
|
||||
->where('platform_channels.is_active', true);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -120,8 +135,75 @@ public function scopeWithActiveChannels(Builder $query): Builder
|
|||
*/
|
||||
public function canBeUsedForRoutes(): bool
|
||||
{
|
||||
return $this->is_active &&
|
||||
$this->feeds()->where('feeds.is_active', true)->where('feed_languages.is_active', true)->exists() &&
|
||||
$this->platformChannels()->where('platform_channels.is_active', true)->exists();
|
||||
if (!$this->is_active) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use a single query to check both conditions efficiently
|
||||
$hasActiveFeeds = DB::table('feed_languages')
|
||||
->join('feeds', 'feeds.id', '=', 'feed_languages.feed_id')
|
||||
->where('feed_languages.language_id', $this->id)
|
||||
->where('feeds.is_active', true)
|
||||
->where('feed_languages.is_active', true)
|
||||
->exists();
|
||||
|
||||
if (!$hasActiveFeeds) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return DB::table('platform_channels')
|
||||
->where('language_id', $this->id)
|
||||
->where('is_active', true)
|
||||
->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope for eager loading language data with feeds and channels
|
||||
*/
|
||||
public function scopeWithLanguageData(Builder $query): Builder
|
||||
{
|
||||
return $query->with([
|
||||
'feeds' => function ($query) {
|
||||
$query->where('feeds.is_active', true)
|
||||
->where('feed_languages.is_active', true)
|
||||
->select(['feeds.id', 'feeds.name', 'feeds.url', 'feeds.type', 'feeds.provider']);
|
||||
},
|
||||
'platformChannels' => function ($query) {
|
||||
$query->where('platform_channels.is_active', true)
|
||||
->with(['platformInstance:id,name,url'])
|
||||
->select(['id', 'platform_instance_id', 'name', 'display_name', 'description', 'language_id']);
|
||||
},
|
||||
'routes' => function ($query) {
|
||||
$query->where('routes.is_active', true)
|
||||
->select(['id', 'feed_id', 'platform_channel_id', 'language_id', 'is_active', 'priority']);
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope for minimal language data (for dropdowns and lists)
|
||||
*/
|
||||
public function scopeMinimal(Builder $query): Builder
|
||||
{
|
||||
return $query->select(['id', 'short_code', 'name', 'native_name', 'is_active']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope for language data with counts
|
||||
*/
|
||||
public function scopeWithCounts(Builder $query): Builder
|
||||
{
|
||||
return $query->withCount([
|
||||
'feeds as active_feeds_count' => function ($query) {
|
||||
$query->where('feeds.is_active', true)
|
||||
->where('feed_languages.is_active', true);
|
||||
},
|
||||
'platformChannels as active_channels_count' => function ($query) {
|
||||
$query->where('platform_channels.is_active', true);
|
||||
},
|
||||
'routes as active_routes_count' => function ($query) {
|
||||
$query->where('routes.is_active', true);
|
||||
}
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
289
backend/src/Domains/Settings/Repositories/LanguageRepository.php
Normal file
289
backend/src/Domains/Settings/Repositories/LanguageRepository.php
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
<?php
|
||||
|
||||
namespace Domains\Settings\Repositories;
|
||||
|
||||
use Domains\Settings\Models\Language;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
||||
class LanguageRepository
|
||||
{
|
||||
public function __construct(
|
||||
private Language $model
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Find languages available for route creation with optimized query
|
||||
*/
|
||||
public function findAvailableForRoutes(array $select = ['*']): Collection
|
||||
{
|
||||
return $this->model
|
||||
->select($select)
|
||||
->where('languages.is_active', true)
|
||||
->whereExists(function ($query) {
|
||||
$query->select(DB::raw(1))
|
||||
->from('feed_languages')
|
||||
->join('feeds', 'feeds.id', '=', 'feed_languages.feed_id')
|
||||
->whereColumn('feed_languages.language_id', 'languages.id')
|
||||
->where('feeds.is_active', true)
|
||||
->where('feed_languages.is_active', true);
|
||||
})
|
||||
->whereExists(function ($query) {
|
||||
$query->select(DB::raw(1))
|
||||
->from('platform_channels')
|
||||
->whereColumn('platform_channels.language_id', 'languages.id')
|
||||
->where('platform_channels.is_active', true);
|
||||
})
|
||||
->orderBy('name')
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find feeds by language with efficient joins
|
||||
*/
|
||||
public function findFeedsByLanguage(
|
||||
int $languageId,
|
||||
?string $search = null,
|
||||
array $select = ['feeds.*'],
|
||||
int $perPage = 15
|
||||
): LengthAwarePaginator {
|
||||
$query = DB::table('feeds')
|
||||
->join('feed_languages', 'feeds.id', '=', 'feed_languages.feed_id')
|
||||
->where('feed_languages.language_id', $languageId)
|
||||
->where('feeds.is_active', true)
|
||||
->where('feed_languages.is_active', true)
|
||||
->select($select);
|
||||
|
||||
if ($search) {
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('feeds.name', 'like', "%{$search}%")
|
||||
->orWhere('feeds.url', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
|
||||
return $query->orderBy('feeds.name')
|
||||
->paginate(min($perPage, 100));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find channels by language with efficient joins
|
||||
*/
|
||||
public function findChannelsByLanguage(
|
||||
int $languageId,
|
||||
?string $search = null,
|
||||
array $select = ['platform_channels.*'],
|
||||
int $perPage = 15,
|
||||
bool $withPlatformInstance = true
|
||||
): LengthAwarePaginator {
|
||||
$query = DB::table('platform_channels');
|
||||
|
||||
if ($withPlatformInstance) {
|
||||
$query->join('platform_instances', 'platform_channels.platform_instance_id', '=', 'platform_instances.id')
|
||||
->addSelect([
|
||||
'platform_instances.id as platform_instance_id',
|
||||
'platform_instances.name as platform_instance_name',
|
||||
'platform_instances.url as platform_instance_url'
|
||||
]);
|
||||
}
|
||||
|
||||
$query->where('platform_channels.language_id', $languageId)
|
||||
->where('platform_channels.is_active', true)
|
||||
->select(array_merge($select, $withPlatformInstance ? [] : []));
|
||||
|
||||
if ($search) {
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('platform_channels.name', 'like', "%{$search}%")
|
||||
->orWhere('platform_channels.display_name', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
|
||||
return $query->orderBy('platform_channels.name')
|
||||
->paginate(min($perPage, 100));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get language statistics with single optimized query
|
||||
*/
|
||||
public function getLanguageStatistics(int $languageId): object
|
||||
{
|
||||
return DB::selectOne("
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM feed_languages fl
|
||||
JOIN feeds f ON f.id = fl.feed_id
|
||||
WHERE fl.language_id = ? AND fl.is_active = 1 AND f.is_active = 1) as active_feeds_count,
|
||||
(SELECT COUNT(*) FROM platform_channels pc
|
||||
WHERE pc.language_id = ? AND pc.is_active = 1) as active_channels_count,
|
||||
(SELECT COUNT(*) FROM routes r
|
||||
WHERE r.language_id = ? AND r.is_active = 1) as active_routes_count
|
||||
", [$languageId, $languageId, $languageId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get comprehensive language usage summary
|
||||
*/
|
||||
public function getLanguageUsageSummary(): array
|
||||
{
|
||||
$results = DB::select("
|
||||
SELECT
|
||||
l.id,
|
||||
l.short_code,
|
||||
l.name,
|
||||
l.native_name,
|
||||
l.is_active,
|
||||
COALESCE(feed_stats.active_feeds, 0) as active_feeds_count,
|
||||
COALESCE(channel_stats.active_channels, 0) as active_channels_count,
|
||||
COALESCE(route_stats.active_routes, 0) as active_routes_count,
|
||||
CASE WHEN COALESCE(feed_stats.active_feeds, 0) > 0
|
||||
AND COALESCE(channel_stats.active_channels, 0) > 0
|
||||
THEN 1 ELSE 0 END as can_create_routes
|
||||
FROM languages l
|
||||
LEFT JOIN (
|
||||
SELECT fl.language_id, COUNT(*) as active_feeds
|
||||
FROM feed_languages fl
|
||||
INNER JOIN feeds f ON f.id = fl.feed_id
|
||||
WHERE fl.is_active = 1 AND f.is_active = 1
|
||||
GROUP BY fl.language_id
|
||||
) feed_stats ON feed_stats.language_id = l.id
|
||||
LEFT JOIN (
|
||||
SELECT pc.language_id, COUNT(*) as active_channels
|
||||
FROM platform_channels pc
|
||||
WHERE pc.is_active = 1
|
||||
GROUP BY pc.language_id
|
||||
) channel_stats ON channel_stats.language_id = l.id
|
||||
LEFT JOIN (
|
||||
SELECT r.language_id, COUNT(*) as active_routes
|
||||
FROM routes r
|
||||
WHERE r.is_active = 1
|
||||
GROUP BY r.language_id
|
||||
) route_stats ON route_stats.language_id = l.id
|
||||
ORDER BY l.name
|
||||
");
|
||||
|
||||
return array_map(fn($row) => (array)$row, $results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find common languages between feed and channel with optimized query
|
||||
*/
|
||||
public function findCommonLanguages(int $feedId, int $channelId): array
|
||||
{
|
||||
$results = DB::select("
|
||||
SELECT DISTINCT l.id, l.short_code, l.name, l.native_name
|
||||
FROM languages l
|
||||
INNER JOIN feed_languages fl ON fl.language_id = l.id
|
||||
INNER JOIN feeds f ON f.id = fl.feed_id
|
||||
INNER JOIN platform_channels pc ON pc.language_id = l.id
|
||||
WHERE f.id = ?
|
||||
AND pc.id = ?
|
||||
AND l.is_active = 1
|
||||
AND f.is_active = 1
|
||||
AND fl.is_active = 1
|
||||
AND pc.is_active = 1
|
||||
ORDER BY l.name
|
||||
", [$feedId, $channelId]);
|
||||
|
||||
return array_map(fn($row) => (array)$row, $results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if language can be used for routes with optimized query
|
||||
*/
|
||||
public function canLanguageBeUsedForRoutes(int $languageId): bool
|
||||
{
|
||||
$result = DB::selectOne("
|
||||
SELECT
|
||||
EXISTS(SELECT 1 FROM feed_languages fl
|
||||
JOIN feeds f ON f.id = fl.feed_id
|
||||
WHERE fl.language_id = ? AND fl.is_active = 1 AND f.is_active = 1) as has_feeds,
|
||||
EXISTS(SELECT 1 FROM platform_channels pc
|
||||
WHERE pc.language_id = ? AND pc.is_active = 1) as has_channels
|
||||
", [$languageId, $languageId]);
|
||||
|
||||
return $result->has_feeds && $result->has_channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active languages with optional counts
|
||||
*/
|
||||
public function findActiveLanguages(
|
||||
bool $withCounts = false,
|
||||
array $select = ['*']
|
||||
): Collection {
|
||||
$query = $this->model->where('is_active', true);
|
||||
|
||||
if ($withCounts) {
|
||||
$query->withCounts();
|
||||
}
|
||||
|
||||
if ($select !== ['*'] && !empty($select)) {
|
||||
$query->select($select);
|
||||
}
|
||||
|
||||
return $query->orderBy('name')->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch check language availability for multiple language IDs
|
||||
*/
|
||||
public function batchCheckLanguageAvailability(array $languageIds): array
|
||||
{
|
||||
if (empty($languageIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$placeholders = str_repeat('?,', count($languageIds) - 1) . '?';
|
||||
|
||||
$results = DB::select("
|
||||
SELECT
|
||||
l.id,
|
||||
EXISTS(SELECT 1 FROM feed_languages fl
|
||||
JOIN feeds f ON f.id = fl.feed_id
|
||||
WHERE fl.language_id = l.id AND fl.is_active = 1 AND f.is_active = 1) as has_feeds,
|
||||
EXISTS(SELECT 1 FROM platform_channels pc
|
||||
WHERE pc.language_id = l.id AND pc.is_active = 1) as has_channels
|
||||
FROM languages l
|
||||
WHERE l.id IN ({$placeholders}) AND l.is_active = 1
|
||||
", $languageIds);
|
||||
|
||||
$availability = [];
|
||||
foreach ($results as $result) {
|
||||
$availability[$result->id] = $result->has_feeds && $result->has_channels;
|
||||
}
|
||||
|
||||
return $availability;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get language with relationship counts for specific language
|
||||
*/
|
||||
public function findWithCounts(int $languageId): ?Language
|
||||
{
|
||||
return $this->model
|
||||
->withCounts()
|
||||
->find($languageId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search languages by name or code
|
||||
*/
|
||||
public function search(
|
||||
string $term,
|
||||
bool $activeOnly = true,
|
||||
array $select = ['*']
|
||||
): Collection {
|
||||
$query = $this->model->select($select)
|
||||
->where(function ($q) use ($term) {
|
||||
$q->where('name', 'like', "%{$term}%")
|
||||
->orWhere('native_name', 'like', "%{$term}%")
|
||||
->orWhere('short_code', 'like', "%{$term}%");
|
||||
});
|
||||
|
||||
if ($activeOnly) {
|
||||
$query->where('is_active', true);
|
||||
}
|
||||
|
||||
return $query->orderBy('name')->get();
|
||||
}
|
||||
}
|
||||
147
backend/src/Domains/Settings/Services/LanguageService.php
Normal file
147
backend/src/Domains/Settings/Services/LanguageService.php
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
<?php
|
||||
|
||||
namespace Domains\Settings\Services;
|
||||
|
||||
use Domains\Settings\Models\Language;
|
||||
use Domains\Settings\Repositories\LanguageRepository;
|
||||
use Domains\Feed\Services\LanguageConsistencyValidator;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class LanguageService
|
||||
{
|
||||
public function __construct(
|
||||
private LanguageRepository $languageRepository,
|
||||
private LanguageConsistencyValidator $consistencyValidator
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get languages available for route creation
|
||||
*/
|
||||
public function getAvailableForRoutes(array $fields = []): Collection
|
||||
{
|
||||
$select = !empty($fields) ? $fields : ['id', 'short_code', 'name', 'native_name', 'is_active'];
|
||||
return $this->languageRepository->findAvailableForRoutes($select);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get feeds by language with search and pagination
|
||||
*/
|
||||
public function getFeedsByLanguage(
|
||||
int $languageId,
|
||||
?string $search = null,
|
||||
int $perPage = 15,
|
||||
array $fields = []
|
||||
): LengthAwarePaginator {
|
||||
$defaultFields = [
|
||||
'feeds.id', 'feeds.name', 'feeds.url', 'feeds.type', 'feeds.provider'
|
||||
];
|
||||
|
||||
$selectedFields = !empty($fields) ? $fields : $defaultFields;
|
||||
|
||||
return $this->languageRepository->findFeedsByLanguage(
|
||||
$languageId,
|
||||
$search,
|
||||
$selectedFields,
|
||||
$perPage
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get channels by language with search and pagination
|
||||
*/
|
||||
public function getChannelsByLanguage(
|
||||
int $languageId,
|
||||
?string $search = null,
|
||||
int $perPage = 15,
|
||||
array $fields = []
|
||||
): LengthAwarePaginator {
|
||||
$defaultFields = [
|
||||
'platform_channels.id', 'platform_channels.platform_instance_id',
|
||||
'platform_channels.name', 'platform_channels.display_name',
|
||||
'platform_channels.description', 'platform_channels.language_id'
|
||||
];
|
||||
|
||||
$selectedFields = !empty($fields) ? $fields : $defaultFields;
|
||||
|
||||
return $this->languageRepository->findChannelsByLanguage(
|
||||
$languageId,
|
||||
$search,
|
||||
$selectedFields,
|
||||
$perPage,
|
||||
true // withPlatformInstance
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get language statistics
|
||||
*/
|
||||
public function getLanguageStatistics(int $languageId): object
|
||||
{
|
||||
return $this->languageRepository->getLanguageStatistics($languageId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate route language consistency
|
||||
*/
|
||||
public function validateRouteLanguage(int $feedId, int $channelId, int $languageId): array
|
||||
{
|
||||
return $this->consistencyValidator->validateConsistency($feedId, $channelId, $languageId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch validate multiple route combinations
|
||||
*/
|
||||
public function batchValidateRoutes(array $routeData): array
|
||||
{
|
||||
return $this->consistencyValidator->batchValidate($routeData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get common languages between a feed and channel
|
||||
*/
|
||||
public function getCommonLanguages(int $feedId, int $channelId): array
|
||||
{
|
||||
return $this->languageRepository->findCommonLanguages($feedId, $channelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a language can be used for routes
|
||||
*/
|
||||
public function canLanguageBeUsedForRoutes(int $languageId): bool
|
||||
{
|
||||
return $this->languageRepository->canLanguageBeUsedForRoutes($languageId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get language usage summary
|
||||
*/
|
||||
public function getLanguageUsageSummary(): array
|
||||
{
|
||||
return $this->languageRepository->getLanguageUsageSummary();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get languages with detailed relationship counts
|
||||
*/
|
||||
public function getLanguagesWithCounts(array $fields = []): Collection
|
||||
{
|
||||
return $this->languageRepository->findActiveLanguages(true, $fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find optimal language for feed-channel pair
|
||||
*/
|
||||
public function findOptimalLanguage(int $feedId, int $channelId): ?array
|
||||
{
|
||||
$commonLanguages = $this->getCommonLanguages($feedId, $channelId);
|
||||
|
||||
if (empty($commonLanguages)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Return the first available language (could be enhanced with priority logic)
|
||||
return $commonLanguages[0];
|
||||
}
|
||||
}
|
||||
226
backend/tests/Unit/Services/LanguageServiceTest.php
Normal file
226
backend/tests/Unit/Services/LanguageServiceTest.php
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Services;
|
||||
|
||||
use Domains\Settings\Services\LanguageService;
|
||||
use Domains\Settings\Models\Language;
|
||||
use Domains\Feed\Models\Feed;
|
||||
use Domains\Platform\Models\PlatformChannel;
|
||||
use Domains\Platform\Models\PlatformInstance;
|
||||
use Domains\Feed\Services\LanguageConsistencyValidator;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class LanguageServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private LanguageService $languageService;
|
||||
private Language $language;
|
||||
private Feed $feed;
|
||||
private PlatformChannel $channel;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->languageService = app(LanguageService::class);
|
||||
|
||||
// Create test data
|
||||
$this->language = Language::factory()->create(['is_active' => true]);
|
||||
$this->feed = Feed::factory()->create(['is_active' => true]);
|
||||
|
||||
$platformInstance = PlatformInstance::factory()->create();
|
||||
$this->channel = PlatformChannel::factory()->create([
|
||||
'platform_instance_id' => $platformInstance->id,
|
||||
'language_id' => $this->language->id,
|
||||
'is_active' => true
|
||||
]);
|
||||
|
||||
// Create feed-language relationship
|
||||
$this->feed->languages()->attach($this->language->id, [
|
||||
'url' => $this->feed->url,
|
||||
'is_active' => true,
|
||||
'is_primary' => true
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_get_available_for_routes_returns_languages_with_feeds_and_channels(): void
|
||||
{
|
||||
$languages = $this->languageService->getAvailableForRoutes();
|
||||
|
||||
$this->assertCount(1, $languages);
|
||||
$this->assertEquals($this->language->id, $languages->first()->id);
|
||||
}
|
||||
|
||||
public function test_get_feeds_by_language_returns_paginated_results(): void
|
||||
{
|
||||
$result = $this->languageService->getFeedsByLanguage($this->language->id);
|
||||
|
||||
$this->assertInstanceOf(\Illuminate\Pagination\LengthAwarePaginator::class, $result);
|
||||
$this->assertCount(1, $result->items());
|
||||
$this->assertEquals($this->feed->id, $result->items()[0]->id);
|
||||
}
|
||||
|
||||
public function test_get_feeds_by_language_with_search(): void
|
||||
{
|
||||
$result = $this->languageService->getFeedsByLanguage(
|
||||
$this->language->id,
|
||||
$this->feed->name
|
||||
);
|
||||
|
||||
$this->assertCount(1, $result->items());
|
||||
|
||||
// Test search that shouldn't match
|
||||
$result = $this->languageService->getFeedsByLanguage(
|
||||
$this->language->id,
|
||||
'nonexistent'
|
||||
);
|
||||
|
||||
$this->assertCount(0, $result->items());
|
||||
}
|
||||
|
||||
public function test_get_channels_by_language_returns_paginated_results(): void
|
||||
{
|
||||
$result = $this->languageService->getChannelsByLanguage($this->language->id);
|
||||
|
||||
$this->assertInstanceOf(\Illuminate\Pagination\LengthAwarePaginator::class, $result);
|
||||
$this->assertCount(1, $result->items());
|
||||
$this->assertEquals($this->channel->id, $result->items()[0]->id);
|
||||
}
|
||||
|
||||
public function test_get_language_statistics(): void
|
||||
{
|
||||
$stats = $this->languageService->getLanguageStatistics($this->language->id);
|
||||
|
||||
$this->assertEquals(1, $stats->active_feeds_count);
|
||||
$this->assertEquals(1, $stats->active_channels_count);
|
||||
$this->assertEquals(0, $stats->active_routes_count);
|
||||
}
|
||||
|
||||
public function test_get_common_languages_between_feed_and_channel(): void
|
||||
{
|
||||
$commonLanguages = $this->languageService->getCommonLanguages(
|
||||
$this->feed->id,
|
||||
$this->channel->id
|
||||
);
|
||||
|
||||
$this->assertCount(1, $commonLanguages);
|
||||
$this->assertEquals($this->language->id, $commonLanguages[0]['id']);
|
||||
}
|
||||
|
||||
public function test_get_common_languages_with_no_match(): void
|
||||
{
|
||||
// Create another language and channel that doesn't match the feed
|
||||
$otherLanguage = Language::factory()->create(['is_active' => true]);
|
||||
$platformInstance = PlatformInstance::factory()->create();
|
||||
$otherChannel = PlatformChannel::factory()->create([
|
||||
'platform_instance_id' => $platformInstance->id,
|
||||
'language_id' => $otherLanguage->id,
|
||||
'is_active' => true
|
||||
]);
|
||||
|
||||
$commonLanguages = $this->languageService->getCommonLanguages(
|
||||
$this->feed->id,
|
||||
$otherChannel->id
|
||||
);
|
||||
|
||||
$this->assertCount(0, $commonLanguages);
|
||||
}
|
||||
|
||||
public function test_can_language_be_used_for_routes(): void
|
||||
{
|
||||
$canBeUsed = $this->languageService->canLanguageBeUsedForRoutes($this->language->id);
|
||||
$this->assertTrue($canBeUsed);
|
||||
|
||||
// Test with language that has no feeds or channels
|
||||
$unusableLanguage = Language::factory()->create(['is_active' => true]);
|
||||
$canBeUsed = $this->languageService->canLanguageBeUsedForRoutes($unusableLanguage->id);
|
||||
$this->assertFalse($canBeUsed);
|
||||
}
|
||||
|
||||
public function test_get_language_usage_summary(): void
|
||||
{
|
||||
$summary = $this->languageService->getLanguageUsageSummary();
|
||||
|
||||
$this->assertIsArray($summary);
|
||||
$this->assertGreaterThanOrEqual(1, count($summary));
|
||||
|
||||
$languageData = collect($summary)->where('id', $this->language->id)->first();
|
||||
$this->assertNotNull($languageData);
|
||||
$this->assertEquals(1, $languageData['active_feeds_count']);
|
||||
$this->assertEquals(1, $languageData['active_channels_count']);
|
||||
$this->assertEquals(1, $languageData['can_create_routes']);
|
||||
}
|
||||
|
||||
public function test_find_optimal_language(): void
|
||||
{
|
||||
$optimalLanguage = $this->languageService->findOptimalLanguage(
|
||||
$this->feed->id,
|
||||
$this->channel->id
|
||||
);
|
||||
|
||||
$this->assertNotNull($optimalLanguage);
|
||||
$this->assertEquals($this->language->id, $optimalLanguage['id']);
|
||||
}
|
||||
|
||||
public function test_find_optimal_language_with_no_common_languages(): void
|
||||
{
|
||||
// Create another language and channel that doesn't match the feed
|
||||
$otherLanguage = Language::factory()->create(['is_active' => true]);
|
||||
$platformInstance = PlatformInstance::factory()->create();
|
||||
$otherChannel = PlatformChannel::factory()->create([
|
||||
'platform_instance_id' => $platformInstance->id,
|
||||
'language_id' => $otherLanguage->id,
|
||||
'is_active' => true
|
||||
]);
|
||||
|
||||
$optimalLanguage = $this->languageService->findOptimalLanguage(
|
||||
$this->feed->id,
|
||||
$otherChannel->id
|
||||
);
|
||||
|
||||
$this->assertNull($optimalLanguage);
|
||||
}
|
||||
|
||||
public function test_validate_route_language(): void
|
||||
{
|
||||
$result = $this->languageService->validateRouteLanguage(
|
||||
$this->feed->id,
|
||||
$this->channel->id,
|
||||
$this->language->id
|
||||
);
|
||||
|
||||
$this->assertTrue($result['is_valid']);
|
||||
$this->assertTrue($result['feed_supports_language']);
|
||||
$this->assertTrue($result['channel_supports_language']);
|
||||
}
|
||||
|
||||
public function test_batch_validate_routes(): void
|
||||
{
|
||||
$routeData = [
|
||||
[
|
||||
'feed_id' => $this->feed->id,
|
||||
'platform_channel_id' => $this->channel->id,
|
||||
'language_id' => $this->language->id
|
||||
]
|
||||
];
|
||||
|
||||
$results = $this->languageService->batchValidateRoutes($routeData);
|
||||
|
||||
$this->assertCount(1, $results);
|
||||
$this->assertTrue($results[0]['is_valid']);
|
||||
}
|
||||
|
||||
public function test_get_languages_with_counts(): void
|
||||
{
|
||||
$languages = $this->languageService->getLanguagesWithCounts();
|
||||
|
||||
$this->assertGreaterThanOrEqual(1, $languages->count());
|
||||
|
||||
$language = $languages->where('id', $this->language->id)->first();
|
||||
$this->assertNotNull($language);
|
||||
$this->assertEquals(1, $language->active_feeds_count);
|
||||
$this->assertEquals(1, $language->active_channels_count);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue