From c7302092bb8b824564f7f59417a91e2c05b86daa Mon Sep 17 00:00:00 2001 From: myrmidex Date: Sat, 5 Jul 2025 02:37:38 +0200 Subject: [PATCH] Feeds CRUD --- app/Http/Controllers/FeedsController.php | 83 +++++++++++++ app/Models/Feed.php | 71 +++++++++++ .../2025_07_05_003216_create_feeds_table.php | 31 +++++ resources/views/pages/feeds/create.blade.php | 109 +++++++++++++++++ resources/views/pages/feeds/edit.blade.php | 110 +++++++++++++++++ resources/views/pages/feeds/index.blade.php | 87 ++++++++++++++ resources/views/pages/feeds/show.blade.php | 113 ++++++++++++++++++ resources/views/partials/sidebar.blade.php | 4 + routes/web.php | 1 + 9 files changed, 609 insertions(+) create mode 100644 app/Http/Controllers/FeedsController.php create mode 100644 app/Models/Feed.php create mode 100644 database/migrations/2025_07_05_003216_create_feeds_table.php create mode 100644 resources/views/pages/feeds/create.blade.php create mode 100644 resources/views/pages/feeds/edit.blade.php create mode 100644 resources/views/pages/feeds/index.blade.php create mode 100644 resources/views/pages/feeds/show.blade.php diff --git a/app/Http/Controllers/FeedsController.php b/app/Http/Controllers/FeedsController.php new file mode 100644 index 0000000..88599d4 --- /dev/null +++ b/app/Http/Controllers/FeedsController.php @@ -0,0 +1,83 @@ +orderBy('name') + ->get(); + + return view('pages.feeds.index', compact('feeds')); + } + + public function create(): View + { + return view('pages.feeds.create'); + } + + public function store(Request $request): RedirectResponse + { + $validated = $request->validate([ + 'name' => 'required|string|max:255', + 'url' => 'required|url|unique:feeds,url', + 'type' => 'required|in:website,rss', + 'language' => 'required|string|size:2', + 'description' => 'nullable|string', + 'is_active' => 'boolean' + ]); + + // Default is_active to true if not provided + $validated['is_active'] = $validated['is_active'] ?? true; + + Feed::create($validated); + + return redirect()->route('feeds.index') + ->with('success', 'Feed created successfully!'); + } + + public function show(Feed $feed): View + { + return view('pages.feeds.show', compact('feed')); + } + + public function edit(Feed $feed): View + { + return view('pages.feeds.edit', compact('feed')); + } + + public function update(Request $request, Feed $feed): RedirectResponse + { + $validated = $request->validate([ + 'name' => 'required|string|max:255', + 'url' => 'required|url|unique:feeds,url,' . $feed->id, + 'type' => 'required|in:website,rss', + 'language' => 'required|string|size:2', + 'description' => 'nullable|string', + 'is_active' => 'boolean' + ]); + + // Default is_active to current value if not provided + $validated['is_active'] = $validated['is_active'] ?? $feed->is_active; + + $feed->update($validated); + + return redirect()->route('feeds.index') + ->with('success', 'Feed updated successfully!'); + } + + public function destroy(Feed $feed): RedirectResponse + { + $feed->delete(); + + return redirect()->route('feeds.index') + ->with('success', 'Feed deleted successfully!'); + } +} \ No newline at end of file diff --git a/app/Models/Feed.php b/app/Models/Feed.php new file mode 100644 index 0000000..afa55be --- /dev/null +++ b/app/Models/Feed.php @@ -0,0 +1,71 @@ + 'array', + 'is_active' => 'boolean', + 'last_fetched_at' => 'datetime' + ]; + + public function getTypeDisplayAttribute(): string + { + return match ($this->type) { + 'website' => 'Website', + 'rss' => 'RSS Feed', + default => 'Unknown' + }; + } + + public function getStatusAttribute(): string + { + if (!$this->is_active) { + return 'Inactive'; + } + + if (!$this->last_fetched_at) { + return 'Never fetched'; + } + + $hoursAgo = $this->last_fetched_at->diffInHours(now()); + + if ($hoursAgo < 2) { + return 'Recently fetched'; + } elseif ($hoursAgo < 24) { + return "Fetched {$hoursAgo}h ago"; + } else { + return "Fetched " . $this->last_fetched_at->diffForHumans(); + } + } +} diff --git a/database/migrations/2025_07_05_003216_create_feeds_table.php b/database/migrations/2025_07_05_003216_create_feeds_table.php new file mode 100644 index 0000000..4131468 --- /dev/null +++ b/database/migrations/2025_07_05_003216_create_feeds_table.php @@ -0,0 +1,31 @@ +id(); + $table->string('name'); // "VRT News", "Belga News Agency" + $table->string('url'); // "https://vrt.be" or "https://feeds.example.com/rss.xml" + $table->enum('type', ['website', 'rss']); // Feed type + $table->string('language', 5)->default('en'); // Language code (en, nl, etc.) + $table->text('description')->nullable(); + $table->json('settings')->nullable(); // Custom settings per feed type + $table->boolean('is_active')->default(true); + $table->timestamp('last_fetched_at')->nullable(); + $table->timestamps(); + + $table->unique('url'); + }); + } + + public function down(): void + { + Schema::dropIfExists('feeds'); + } +}; \ No newline at end of file diff --git a/resources/views/pages/feeds/create.blade.php b/resources/views/pages/feeds/create.blade.php new file mode 100644 index 0000000..08af8a4 --- /dev/null +++ b/resources/views/pages/feeds/create.blade.php @@ -0,0 +1,109 @@ +@extends('layouts.app') + +@section('content') +
+
+
+

Add New Feed

+

Create a new content feed for articles.

+
+ +
+
+ @csrf + +
+
+ + + @error('name') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('url') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('type') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('language') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('description') +

{{ $message }}

+ @enderror +
+ +
+ + +
+
+ +
+ + Cancel + + +
+
+
+
+
+@endsection \ No newline at end of file diff --git a/resources/views/pages/feeds/edit.blade.php b/resources/views/pages/feeds/edit.blade.php new file mode 100644 index 0000000..d761591 --- /dev/null +++ b/resources/views/pages/feeds/edit.blade.php @@ -0,0 +1,110 @@ +@extends('layouts.app') + +@section('content') +
+
+
+

Edit Feed

+

Update the details for {{ $feed->name }}.

+
+ +
+
+ @csrf + @method('PUT') + +
+
+ + + @error('name') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('url') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('type') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('language') +

{{ $message }}

+ @enderror +
+ +
+ + + @error('description') +

{{ $message }}

+ @enderror +
+ +
+ is_active) ? 'checked' : '' }} + class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded"> + +
+
+ +
+ + Cancel + + +
+
+
+
+
+@endsection \ No newline at end of file diff --git a/resources/views/pages/feeds/index.blade.php b/resources/views/pages/feeds/index.blade.php new file mode 100644 index 0000000..34645ff --- /dev/null +++ b/resources/views/pages/feeds/index.blade.php @@ -0,0 +1,87 @@ +@extends('layouts.app') + +@section('content') +
+
+
+

Feeds

+ + Add New Feed + +
+ + @if(session('success')) +
+ {{ session('success') }} +
+ @endif + +
+ @if($feeds->count() > 0) +
    + @foreach($feeds as $feed) +
  • +
    +
    +
    + @if($feed->type === 'rss') + + @else + + @endif +
    +
    +
    +
    {{ $feed->name }}
    + + {{ $feed->is_active ? 'Active' : 'Inactive' }} + + + {{ $feed->type_display }} + + + {{ strtoupper($feed->language) }} + +
    +
    {{ $feed->url }}
    + @if($feed->description) +
    {{ Str::limit($feed->description, 100) }}
    + @endif +
    {{ $feed->status }}
    +
    +
    +
    + + View + + + Edit + +
    + @csrf + @method('DELETE') + +
    +
    +
    +
  • + @endforeach +
+ @else +
+ +

No feeds yet

+

Get started by adding your first content feed.

+ + Add New Feed + +
+ @endif +
+
+
+@endsection \ No newline at end of file diff --git a/resources/views/pages/feeds/show.blade.php b/resources/views/pages/feeds/show.blade.php new file mode 100644 index 0000000..774e8da --- /dev/null +++ b/resources/views/pages/feeds/show.blade.php @@ -0,0 +1,113 @@ +@extends('layouts.app') + +@section('content') +
+
+
+
+
+

{{ $feed->name }}

+

{{ $feed->type_display }} • {{ strtoupper($feed->language) }}

+
+
+ + {{ $feed->is_active ? 'Active' : 'Inactive' }} + + + Edit Feed + +
+
+
+ +
+
+

Feed Details

+

Information about this content feed.

+
+
+
+
+
Name
+
{{ $feed->name }}
+
+ +
+
Type
+
+
+ @if($feed->type === 'rss') + + @else + + @endif + {{ $feed->type_display }} +
+
+
+ + + +
+
Language
+
{{ strtoupper($feed->language) }}
+
+ +
+
Status
+
{{ $feed->status }}
+
+ +
+
Created
+
{{ $feed->created_at->format('M j, Y g:i A') }}
+
+ + @if($feed->last_fetched_at) +
+
Last Fetched
+
{{ $feed->last_fetched_at->format('M j, Y g:i A') }}
+
+ @endif + + @if($feed->description) +
+
Description
+
{{ $feed->description }}
+
+ @endif +
+
+
+ +
+ + ← Back to Feeds + + +
+ + Edit + +
+ @csrf + @method('DELETE') + +
+
+
+
+
+@endsection \ No newline at end of file diff --git a/resources/views/partials/sidebar.blade.php b/resources/views/partials/sidebar.blade.php index d48b406..e5d1c7e 100644 --- a/resources/views/partials/sidebar.blade.php +++ b/resources/views/partials/sidebar.blade.php @@ -17,6 +17,10 @@ Channels + + + Feeds + Logs diff --git a/routes/web.php b/routes/web.php index fbd0989..3f9d9a1 100644 --- a/routes/web.php +++ b/routes/web.php @@ -25,3 +25,4 @@ Route::post('/platforms/{platformAccount}/set-active', [App\Http\Controllers\PlatformAccountsController::class, 'setActive'])->name('platforms.set-active'); Route::resource('channels', App\Http\Controllers\PlatformChannelsController::class)->names('channels'); +Route::resource('feeds', App\Http\Controllers\FeedsController::class)->names('feeds');