From 4412974cfb6f73305d41d5e1dfe6b0d4bfccc076 Mon Sep 17 00:00:00 2001 From: myrmidex Date: Wed, 6 Aug 2025 20:01:28 +0200 Subject: [PATCH] Optimizations + fix failing tests --- .../Controllers/Api/V1/FeedsController.php | 23 ++++++++---- .../Controllers/Api/V1/RoutingController.php | 31 ++++++++-------- backend/app/Http/Resources/FeedResource.php | 7 ++-- .../app/Services/DashboardStatsService.php | 35 +++++++++++++++---- ...025_06_29_072202_create_articles_table.php | 6 ++++ ..._add_approval_status_to_articles_table.php | 3 ++ .../Api/V1/FeedsControllerTest.php | 7 ++-- 7 files changed, 81 insertions(+), 31 deletions(-) diff --git a/backend/app/Http/Controllers/Api/V1/FeedsController.php b/backend/app/Http/Controllers/Api/V1/FeedsController.php index 17831c8..2ff0ad1 100644 --- a/backend/app/Http/Controllers/Api/V1/FeedsController.php +++ b/backend/app/Http/Controllers/Api/V1/FeedsController.php @@ -17,14 +17,25 @@ class FeedsController extends BaseController */ public function index(Request $request): JsonResponse { - $feeds = Feed::orderBy('is_active', 'desc') + $perPage = min($request->get('per_page', 15), 100); + + $feeds = Feed::with(['language']) + ->withCount('articles') + ->orderBy('is_active', 'desc') ->orderBy('name') - ->get(); + ->paginate($perPage); - return $this->sendResponse( - FeedResource::collection($feeds), - 'Feeds retrieved successfully.' - ); + return $this->sendResponse([ + 'feeds' => FeedResource::collection($feeds->items()), + 'pagination' => [ + 'current_page' => $feeds->currentPage(), + 'last_page' => $feeds->lastPage(), + 'per_page' => $feeds->perPage(), + 'total' => $feeds->total(), + 'from' => $feeds->firstItem(), + 'to' => $feeds->lastItem(), + ] + ], 'Feeds retrieved successfully.'); } /** diff --git a/backend/app/Http/Controllers/Api/V1/RoutingController.php b/backend/app/Http/Controllers/Api/V1/RoutingController.php index 8f3ddb3..05555ac 100644 --- a/backend/app/Http/Controllers/Api/V1/RoutingController.php +++ b/backend/app/Http/Controllers/Api/V1/RoutingController.php @@ -63,14 +63,13 @@ public function store(Request $request): JsonResponse */ public function show(Feed $feed, PlatformChannel $channel): JsonResponse { - $route = Route::where('feed_id', $feed->id) - ->where('platform_channel_id', $channel->id) - ->with(['feed', 'platformChannel']) - ->first(); - + $route = $this->findRoute($feed, $channel); + if (!$route) { return $this->sendNotFound('Routing configuration not found.'); } + + $route->load(['feed', 'platformChannel']); return $this->sendResponse( new RouteResource($route), @@ -84,9 +83,7 @@ public function show(Feed $feed, PlatformChannel $channel): JsonResponse public function update(Request $request, Feed $feed, PlatformChannel $channel): JsonResponse { try { - $route = Route::where('feed_id', $feed->id) - ->where('platform_channel_id', $channel->id) - ->first(); + $route = $this->findRoute($feed, $channel); if (!$route) { return $this->sendNotFound('Routing configuration not found.'); @@ -118,9 +115,7 @@ public function update(Request $request, Feed $feed, PlatformChannel $channel): public function destroy(Feed $feed, PlatformChannel $channel): JsonResponse { try { - $route = Route::where('feed_id', $feed->id) - ->where('platform_channel_id', $channel->id) - ->first(); + $route = $this->findRoute($feed, $channel); if (!$route) { return $this->sendNotFound('Routing configuration not found.'); @@ -145,9 +140,7 @@ public function destroy(Feed $feed, PlatformChannel $channel): JsonResponse public function toggle(Feed $feed, PlatformChannel $channel): JsonResponse { try { - $route = Route::where('feed_id', $feed->id) - ->where('platform_channel_id', $channel->id) - ->first(); + $route = $this->findRoute($feed, $channel); if (!$route) { return $this->sendNotFound('Routing configuration not found.'); @@ -168,4 +161,14 @@ public function toggle(Feed $feed, PlatformChannel $channel): JsonResponse return $this->sendError('Failed to toggle routing configuration status: ' . $e->getMessage(), [], 500); } } + + /** + * Find a route by feed and channel + */ + private function findRoute(Feed $feed, PlatformChannel $channel): ?Route + { + return Route::where('feed_id', $feed->id) + ->where('platform_channel_id', $channel->id) + ->first(); + } } \ No newline at end of file diff --git a/backend/app/Http/Resources/FeedResource.php b/backend/app/Http/Resources/FeedResource.php index 24ad40c..ac5641b 100644 --- a/backend/app/Http/Resources/FeedResource.php +++ b/backend/app/Http/Resources/FeedResource.php @@ -23,9 +23,10 @@ public function toArray(Request $request): array 'description' => $this->description, 'created_at' => $this->created_at->toISOString(), 'updated_at' => $this->updated_at->toISOString(), - 'articles_count' => $this->when($request->routeIs('api.feeds.*'), function () { - return $this->articles()->count(); - }), + 'articles_count' => $this->when( + $request->routeIs('api.feeds.*') && isset($this->articles_count), + $this->articles_count + ), ]; } } \ No newline at end of file diff --git a/backend/app/Services/DashboardStatsService.php b/backend/app/Services/DashboardStatsService.php index 25268e6..02b095f 100644 --- a/backend/app/Services/DashboardStatsService.php +++ b/backend/app/Services/DashboardStatsService.php @@ -5,6 +5,7 @@ use App\Models\Article; use App\Models\ArticlePublication; use Carbon\Carbon; +use Illuminate\Support\Facades\DB; class DashboardStatsService { @@ -76,13 +77,35 @@ private function getDateRange(string $period): ?array */ public function getSystemStats(): array { + // Optimize with single queries using conditional aggregation + $feedStats = DB::table('feeds') + ->selectRaw(' + COUNT(*) as total_feeds, + SUM(CASE WHEN is_active = 1 THEN 1 ELSE 0 END) as active_feeds + ') + ->first(); + + $channelStats = DB::table('platform_channels') + ->selectRaw(' + COUNT(*) as total_channels, + SUM(CASE WHEN is_active = 1 THEN 1 ELSE 0 END) as active_channels + ') + ->first(); + + $routeStats = DB::table('routes') + ->selectRaw(' + COUNT(*) as total_routes, + SUM(CASE WHEN is_active = 1 THEN 1 ELSE 0 END) as active_routes + ') + ->first(); + return [ - 'total_feeds' => \App\Models\Feed::count(), - 'active_feeds' => \App\Models\Feed::where('is_active', true)->count(), - 'total_channels' => \App\Models\PlatformChannel::count(), - 'active_channels' => \App\Models\PlatformChannel::where('is_active', true)->count(), - 'total_routes' => \App\Models\Route::count(), - 'active_routes' => \App\Models\Route::where('is_active', true)->count(), + 'total_feeds' => $feedStats->total_feeds, + 'active_feeds' => $feedStats->active_feeds, + 'total_channels' => $channelStats->total_channels, + 'active_channels' => $channelStats->active_channels, + 'total_routes' => $routeStats->total_routes, + 'active_routes' => $routeStats->active_routes, ]; } } \ No newline at end of file diff --git a/backend/database/migrations/2025_06_29_072202_create_articles_table.php b/backend/database/migrations/2025_06_29_072202_create_articles_table.php index 889bd74..a196d53 100644 --- a/backend/database/migrations/2025_06_29_072202_create_articles_table.php +++ b/backend/database/migrations/2025_06_29_072202_create_articles_table.php @@ -17,6 +17,12 @@ public function up(): void $table->boolean('is_duplicate')->default(false); $table->timestamp('validated_at')->nullable(); $table->timestamps(); + + $table->index('url'); + $table->index('is_valid'); + $table->index('validated_at'); + $table->index('created_at'); + $table->index(['is_valid', 'created_at']); }); } diff --git a/backend/database/migrations/2025_07_10_102123_add_approval_status_to_articles_table.php b/backend/database/migrations/2025_07_10_102123_add_approval_status_to_articles_table.php index da6adaf..af85c0c 100644 --- a/backend/database/migrations/2025_07_10_102123_add_approval_status_to_articles_table.php +++ b/backend/database/migrations/2025_07_10_102123_add_approval_status_to_articles_table.php @@ -17,6 +17,9 @@ public function up(): void ->after('is_duplicate'); $table->timestamp('approved_at')->nullable()->after('approval_status'); $table->string('approved_by')->nullable()->after('approved_at'); + + $table->index('approval_status'); + $table->index(['is_valid', 'approval_status']); }); } diff --git a/backend/tests/Feature/Http/Controllers/Api/V1/FeedsControllerTest.php b/backend/tests/Feature/Http/Controllers/Api/V1/FeedsControllerTest.php index 030da4f..a1a9d14 100644 --- a/backend/tests/Feature/Http/Controllers/Api/V1/FeedsControllerTest.php +++ b/backend/tests/Feature/Http/Controllers/Api/V1/FeedsControllerTest.php @@ -18,7 +18,10 @@ public function test_index_returns_successful_response(): void $response->assertStatus(200) ->assertJsonStructure([ 'success', - 'data', + 'data' => [ + 'feeds', + 'pagination' + ], 'message' ]); } @@ -32,7 +35,7 @@ public function test_index_returns_feeds_ordered_by_active_status_then_name(): v $response = $this->getJson('/api/v1/feeds'); $response->assertStatus(200); - $feeds = $response->json('data'); + $feeds = $response->json('data.feeds'); // First should be active feeds in alphabetical order $this->assertEquals('A Feed', $feeds[0]['name']);