Compare commits
9 changed files with 210 additions and 24 deletions
|
|
@ -7,13 +7,66 @@ namespace App\Http\Controllers;
|
||||||
use App\Models\Article;
|
use App\Models\Article;
|
||||||
use App\Models\Note;
|
use App\Models\Note;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Response;
|
||||||
|
|
||||||
class FeedsController extends Controller
|
class FeedsController extends Controller
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Returns the blog RSS feed.
|
||||||
|
*/
|
||||||
|
public function blogRss(): Response
|
||||||
|
{
|
||||||
|
$articles = Article::where('published', '1')->latest('updated_at')->take(20)->get();
|
||||||
|
$buildDate = $articles->first()->updated_at->toRssString();
|
||||||
|
|
||||||
|
return response()
|
||||||
|
->view('articles.rss', compact('articles', 'buildDate'))
|
||||||
|
->header('Content-Type', 'application/rss+xml; charset=utf-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the blog Atom feed.
|
||||||
|
*/
|
||||||
|
public function blogAtom(): Response
|
||||||
|
{
|
||||||
|
$articles = Article::where('published', '1')->latest('updated_at')->take(20)->get();
|
||||||
|
|
||||||
|
return response()
|
||||||
|
->view('articles.atom', compact('articles'))
|
||||||
|
->header('Content-Type', 'application/atom+xml; charset=utf-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the notes RSS feed.
|
||||||
|
*/
|
||||||
|
public function notesRss(): Response
|
||||||
|
{
|
||||||
|
$notes = Note::latest()->take(20)->get();
|
||||||
|
$buildDate = $notes->first()->updated_at->toRssString();
|
||||||
|
|
||||||
|
return response()
|
||||||
|
->view('notes.rss', compact('notes', 'buildDate'))
|
||||||
|
->header('Content-Type', 'application/rss+xml; charset=utf-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the notes Atom feed.
|
||||||
|
*/
|
||||||
|
public function notesAtom(): Response
|
||||||
|
{
|
||||||
|
$notes = Note::latest()->take(20)->get();
|
||||||
|
|
||||||
|
return response()
|
||||||
|
->view('notes.atom', compact('notes'))
|
||||||
|
->header('Content-Type', 'application/atom+xml; charset=utf-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @todo sort out return type for json responses */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the blog JSON feed.
|
* Returns the blog JSON feed.
|
||||||
*/
|
*/
|
||||||
public function blogJson(): JsonResponse
|
public function blogJson(): array
|
||||||
{
|
{
|
||||||
$articles = Article::where('published', '1')->latest('updated_at')->take(20)->get();
|
$articles = Article::where('published', '1')->latest('updated_at')->take(20)->get();
|
||||||
$data = [
|
$data = [
|
||||||
|
|
@ -41,15 +94,13 @@ class FeedsController extends Controller
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json($data, 200, [
|
return $data;
|
||||||
'Content-Type' => 'application/feed+json',
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the notes JSON feed.
|
* Returns the notes JSON feed.
|
||||||
*/
|
*/
|
||||||
public function notesJson(): JsonResponse
|
public function notesJson(): array
|
||||||
{
|
{
|
||||||
$notes = Note::latest()->with('media', 'place', 'tags')->take(20)->get();
|
$notes = Note::latest()->with('media', 'place', 'tags')->take(20)->get();
|
||||||
$data = [
|
$data = [
|
||||||
|
|
@ -79,9 +130,7 @@ class FeedsController extends Controller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json($data, 200, [
|
return $data;
|
||||||
'Content-Type' => 'application/feed+json',
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
20
resources/views/articles/atom.blade.php
Normal file
20
resources/views/articles/atom.blade.php
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||||
|
<title>Atom feed for {{ config('user.display_name') }}’s blog</title>
|
||||||
|
<link rel="self" href="{{ config('app.url') }}/blog/feed.atom" />
|
||||||
|
<id>{{ config('app.url')}}/blog</id>
|
||||||
|
<updated>{{ $articles[0]->updated_at->toAtomString() }}</updated>
|
||||||
|
|
||||||
|
@foreach($articles as $article)
|
||||||
|
<entry>
|
||||||
|
<title>{{ $article->title }}</title>
|
||||||
|
<link href="{{ config('app.url') }}{{ $article->link }}" />
|
||||||
|
<id>{{ config('app.url') }}{{ $article->link }}</id>
|
||||||
|
<updated>{{ $article->updated_at->toAtomString() }}</updated>
|
||||||
|
<content>{{ $article->main }}</content>
|
||||||
|
<author>
|
||||||
|
<name>{{ config('user.display_name') }}</name>
|
||||||
|
</author>
|
||||||
|
</entry>
|
||||||
|
@endforeach
|
||||||
|
</feed>
|
||||||
26
resources/views/articles/rss.blade.php
Normal file
26
resources/views/articles/rss.blade.php
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||||
|
<channel>
|
||||||
|
<title>{{ config('user.display_name') }}</title>
|
||||||
|
<atom:link href="{{ config('app.url') }}/blog/feed.rss" rel="self" type="application/rss+xml" />
|
||||||
|
<description>An RSS feed of the blog posts found on {{ config('app.url') }}</description>
|
||||||
|
<link>{{ config('app.url') }}/blog</link>
|
||||||
|
<lastBuildDate>{{ $buildDate }}</lastBuildDate>
|
||||||
|
<ttl>1800</ttl>
|
||||||
|
|
||||||
|
@foreach($articles as $article)
|
||||||
|
<item>
|
||||||
|
<title>{{ strip_tags($article->title) }}</title>
|
||||||
|
<description>
|
||||||
|
<![CDATA[
|
||||||
|
{{ $article->main }}
|
||||||
|
@if($article->url)<p><a href="{{ config('app.url') }}{{ $article->link }}">Permalink</a></p>@endif
|
||||||
|
]]>
|
||||||
|
</description>
|
||||||
|
<link>@if($article->url != ''){{ $article->url }}@else{{ config('app.url') }}{{ $article->link }}@endif</link>
|
||||||
|
<guid>{{ config('app.url') }}{{ $article->link }}</guid>
|
||||||
|
<pubDate>{{ $article->pubdate }}</pubDate>
|
||||||
|
</item>
|
||||||
|
@endforeach
|
||||||
|
</channel>
|
||||||
|
</rss>
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
@php
|
|
||||||
if (isset($title)) {
|
|
||||||
$uniqueId = bin2hex(random_bytes(6));
|
|
||||||
}
|
|
||||||
@endphp
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 200 200" class="feather feather-json-feed"
|
|
||||||
@if($title)aria-labelledby="{{ $uniqueId }}"@endif
|
|
||||||
>
|
|
||||||
@if($title)<title id="{{ $uniqueId }}">{{ $title }}</title>@endif
|
|
||||||
<path fill="#497714" transform="translate(0,200) scale(0.1,-0.1)" d="M373 1547 c-88 -95 -127 -171 -127 -252 -1 -90 28 -140 164 -280 150 -155 160 -168 160 -219 0 -31 -9 -55 -41 -101 -34 -51 -39 -65 -34 -95 3 -20 18 -46 36 -62 44 -41 89 -38 163 12 32 22 67 40 78 40 46 0 99 -38 227 -164 169 -165 209 -190 305 -190 92 -1 158 32 258 125 l73 67 -59 61 -60 60 -39 -36 c-22 -19 -60 -44 -84 -55 -73 -32 -109 -12 -278 152 -77 76 -156 145 -175 155 -47 24 -110 22 -159 -6 -23 -12 -44 -19 -48 -15 -4 4 2 25 15 46 24 42 29 110 12 154 -5 15 -66 84 -135 156 -138 143 -165 180 -165 227 0 47 15 78 61 128 l42 45 -54 59 c-30 32 -57 59 -61 60 -3 0 -37 -32 -75 -72z M1474 1631 c-36 -22 -58 -75 -48 -115 10 -41 59 -76 106 -76 107 0 137 147 41 199 -31 16 -64 14 -99 -8z M1229 1385 c-55 -30 -73 -89 -44 -145 19 -37 43 -50 95 -50 110 0 139 144 40 195 -36 18 -57 18 -91 0z M949 1111 c-86 -87 29 -228 130 -159 40 27 52 62 41 115 -12 51 -41 73 -97 73 -36 0 -50 -6 -74 -29z"/>
|
|
||||||
</svg>
|
|
||||||
|
|
@ -7,9 +7,13 @@
|
||||||
<title>@yield('title'){{ config('app.name') }}</title>
|
<title>@yield('title'){{ config('app.name') }}</title>
|
||||||
<link rel="stylesheet" href="/assets/highlight/nord.css">
|
<link rel="stylesheet" href="/assets/highlight/nord.css">
|
||||||
<link rel="stylesheet" href="/assets/css/app.css">
|
<link rel="stylesheet" href="/assets/css/app.css">
|
||||||
<link rel="alternate" type="application/feed+json" title="Blog JSON Feed" href="{{ route('feed.blog.json') }}">
|
<link rel="alternate" type="application/rss+xml" title="Blog RSS Feed" href="{{ route('feed.blog.rss') }}">
|
||||||
|
<link rel="alternate" type="application/atom+xml" title="Blog Atom Feed" href="{{ route('feed.blog.atom') }}">
|
||||||
|
<link rel="alternate" type="application/json" title="Blog JSON Feed" href="{{ route('feed.blog.json') }}">
|
||||||
<link rel="alternate" type="application/jf2feed+json" title="Blog JF2 Feed" href="{{ route('feed.blog.jf2') }}">
|
<link rel="alternate" type="application/jf2feed+json" title="Blog JF2 Feed" href="{{ route('feed.blog.jf2') }}">
|
||||||
<link rel="alternate" type="application/feed+json" title="Notes JSON Feed" href="{{ route('feed.notes.json') }}">
|
<link rel="alternate" type="application/rss+xml" title="Notes RSS Feed" href="{{ route('feed.notes.rss') }}">
|
||||||
|
<link rel="alternate" type="application/atom+xml" title="Notes Atom Feed" href="{{ route('feed.notes.atom') }}">
|
||||||
|
<link rel="alternate" type="application/json" title="Notes JSON Feed" href="{{ route('feed.notes.json') }}">
|
||||||
<link rel="alternate" type="application/jf2feed+json" title="Notes JF2 Feed" href="{{ route('feed.notes.jf2') }}">
|
<link rel="alternate" type="application/jf2feed+json" title="Notes JF2 Feed" href="{{ route('feed.notes.jf2') }}">
|
||||||
<link rel="indieauth-metadata" href="{{ route('indieauth.metadata') }}">
|
<link rel="indieauth-metadata" href="{{ route('indieauth.metadata') }}">
|
||||||
<link rel="authorization_endpoint" href="{{ route('indieauth.start') }}">
|
<link rel="authorization_endpoint" href="{{ route('indieauth.start') }}">
|
||||||
|
|
@ -36,7 +40,7 @@
|
||||||
<a href="/likes">Likes</a>
|
<a href="/likes">Likes</a>
|
||||||
<a href="/contacts">Contacts</a>
|
<a href="/contacts">Contacts</a>
|
||||||
<a href="/projects">Projects</a>
|
<a href="/projects">Projects</a>
|
||||||
<a href="{{ route('feed.notes.json') }}" class="rss-icon">@include('icons.json-feed', ['title' => 'JSON Feed'])</a>
|
<a href="{{ route('feed.notes.json') }}" class="rss-icon">@include('icons.rss', ['title' => 'RSS Feed'])</a>
|
||||||
</nav>
|
</nav>
|
||||||
<div id="theme-selector">
|
<div id="theme-selector">
|
||||||
<button class="toggle" popovertarget="theme-selector-dropdown">
|
<button class="toggle" popovertarget="theme-selector-dropdown">
|
||||||
|
|
|
||||||
20
resources/views/notes/atom.blade.php
Normal file
20
resources/views/notes/atom.blade.php
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||||
|
<title>Atom feed for {{ config('user.display_name') }}’s notes</title>
|
||||||
|
<link rel="self" href="{{ config('app.url') }}/notes/feed.atom" />
|
||||||
|
<id>{{ config('app.url')}}/notes</id>
|
||||||
|
<updated>{{ $notes[0]->updated_at->toAtomString() }}</updated>
|
||||||
|
|
||||||
|
@foreach($notes as $note)
|
||||||
|
<entry>
|
||||||
|
<title>{{ strip_tags($note->note) }}</title>
|
||||||
|
<link href="{{ $note->uri }}" />
|
||||||
|
<id>{{ $note->uri }}</id>
|
||||||
|
<updated>{{ $note->updated_at->toAtomString() }}</updated>
|
||||||
|
<content type="html">{{ $note->note }}</content>
|
||||||
|
<author>
|
||||||
|
<name>{{ config('user.display_name') }}</name>
|
||||||
|
</author>
|
||||||
|
</entry>
|
||||||
|
@endforeach
|
||||||
|
</feed>
|
||||||
26
resources/views/notes/rss.blade.php
Normal file
26
resources/views/notes/rss.blade.php
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||||
|
<channel>
|
||||||
|
<title>{{ config('user.display_name') }}</title>
|
||||||
|
<atom:link href="{{ config('app.url') }}/notes/feed.rss" rel="self" type="application/rss+xml" />
|
||||||
|
<description>An RSS feed of the notes found on {{ config('app.url') }}</description>
|
||||||
|
<link>{{ config('app.url') }}/notes</link>
|
||||||
|
<lastBuildDate>{{ $buildDate }}</lastBuildDate>
|
||||||
|
<ttl>1800</ttl>
|
||||||
|
|
||||||
|
@foreach($notes as $note)
|
||||||
|
<item>
|
||||||
|
<title>{{ strip_tags($note->note) }}</title>
|
||||||
|
<description>
|
||||||
|
<![CDATA[
|
||||||
|
{!! $note->note !!}
|
||||||
|
]]>
|
||||||
|
</description>
|
||||||
|
<link>{{ $note->uri }}</link>
|
||||||
|
<guid>{{ $note->uri}}</guid>
|
||||||
|
<pubDate>{{ $note->pubdate }}</pubDate>
|
||||||
|
</item>
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
</channel>
|
||||||
|
</rss>
|
||||||
|
|
@ -170,6 +170,8 @@ Route::middleware(MyAuthMiddleware::class)->prefix('admin')->group(function () {
|
||||||
|
|
||||||
// Blog pages using ArticlesController
|
// Blog pages using ArticlesController
|
||||||
Route::prefix('blog')->group(function () {
|
Route::prefix('blog')->group(function () {
|
||||||
|
Route::get('/feed.rss', [FeedsController::class, 'blogRss'])->name('feed.blog.rss');
|
||||||
|
Route::get('/feed.atom', [FeedsController::class, 'blogAtom'])->name('feed.blog.atom');
|
||||||
Route::get('/feed.json', [FeedsController::class, 'blogJson'])->name('feed.blog.json');
|
Route::get('/feed.json', [FeedsController::class, 'blogJson'])->name('feed.blog.json');
|
||||||
Route::get('/feed.jf2', [FeedsController::class, 'blogJf2'])->name('feed.blog.jf2');
|
Route::get('/feed.jf2', [FeedsController::class, 'blogJf2'])->name('feed.blog.jf2');
|
||||||
Route::get('/s/{id}', [ArticlesController::class, 'onlyIdInURL']);
|
Route::get('/s/{id}', [ArticlesController::class, 'onlyIdInURL']);
|
||||||
|
|
@ -180,6 +182,8 @@ Route::prefix('blog')->group(function () {
|
||||||
// Notes pages using NotesController
|
// Notes pages using NotesController
|
||||||
Route::prefix('notes')->group(function () {
|
Route::prefix('notes')->group(function () {
|
||||||
Route::get('/', [NotesController::class, 'index']);
|
Route::get('/', [NotesController::class, 'index']);
|
||||||
|
Route::get('/feed.rss', [FeedsController::class, 'notesRss'])->name('feed.notes.rss');
|
||||||
|
Route::get('/feed.atom', [FeedsController::class, 'notesAtom'])->name('feed.notes.atom');
|
||||||
Route::get('/feed.json', [FeedsController::class, 'notesJson'])->name('feed.notes.json');
|
Route::get('/feed.json', [FeedsController::class, 'notesJson'])->name('feed.notes.json');
|
||||||
Route::get('/feed.jf2', [FeedsController::class, 'notesJf2'])->name('feed.notes.jf2');
|
Route::get('/feed.jf2', [FeedsController::class, 'notesJf2'])->name('feed.notes.jf2');
|
||||||
Route::get('/new', [NotesController::class, 'create']);
|
Route::get('/new', [NotesController::class, 'create']);
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,42 @@ class FeedsTest extends TestCase
|
||||||
{
|
{
|
||||||
use RefreshDatabase;
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the blog RSS feed.
|
||||||
|
*/
|
||||||
|
#[Test]
|
||||||
|
public function blog_rss_feed_is_present(): void
|
||||||
|
{
|
||||||
|
Article::factory()->count(3)->create();
|
||||||
|
$response = $this->get('/blog/feed.rss');
|
||||||
|
$response->assertHeader('Content-Type', 'application/rss+xml; charset=utf-8');
|
||||||
|
$response->assertOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the notes RSS feed.
|
||||||
|
*/
|
||||||
|
#[Test]
|
||||||
|
public function notes_rss_feed_is_present(): void
|
||||||
|
{
|
||||||
|
Note::factory()->count(3)->create();
|
||||||
|
$response = $this->get('/notes/feed.rss');
|
||||||
|
$response->assertHeader('Content-Type', 'application/rss+xml; charset=utf-8');
|
||||||
|
$response->assertOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the blog RSS feed.
|
||||||
|
*/
|
||||||
|
#[Test]
|
||||||
|
public function blog_atom_feed_is_present(): void
|
||||||
|
{
|
||||||
|
Article::factory()->count(3)->create();
|
||||||
|
$response = $this->get('/blog/feed.atom');
|
||||||
|
$response->assertHeader('Content-Type', 'application/atom+xml; charset=utf-8');
|
||||||
|
$response->assertOk();
|
||||||
|
}
|
||||||
|
|
||||||
#[Test]
|
#[Test]
|
||||||
public function blog_jf2_feed_is_present(): void
|
public function blog_jf2_feed_is_present(): void
|
||||||
{
|
{
|
||||||
|
|
@ -37,6 +73,18 @@ class FeedsTest extends TestCase
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the notes RSS feed.
|
||||||
|
*/
|
||||||
|
#[Test]
|
||||||
|
public function notes_atom_feed_is_present(): void
|
||||||
|
{
|
||||||
|
Note::factory()->count(3)->create();
|
||||||
|
$response = $this->get('/notes/feed.atom');
|
||||||
|
$response->assertHeader('Content-Type', 'application/atom+xml; charset=utf-8');
|
||||||
|
$response->assertOk();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test the blog JSON feed.
|
* Test the blog JSON feed.
|
||||||
*/
|
*/
|
||||||
|
|
@ -45,7 +93,7 @@ class FeedsTest extends TestCase
|
||||||
{
|
{
|
||||||
Article::factory()->count(3)->create();
|
Article::factory()->count(3)->create();
|
||||||
$response = $this->get('/blog/feed.json');
|
$response = $this->get('/blog/feed.json');
|
||||||
$response->assertHeader('Content-Type', 'application/feed+json');
|
$response->assertHeader('Content-Type', 'application/json');
|
||||||
$response->assertOk();
|
$response->assertOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,7 +105,7 @@ class FeedsTest extends TestCase
|
||||||
{
|
{
|
||||||
Note::factory()->count(3)->create();
|
Note::factory()->count(3)->create();
|
||||||
$response = $this->get('/notes/feed.json');
|
$response = $this->get('/notes/feed.json');
|
||||||
$response->assertHeader('Content-Type', 'application/feed+json');
|
$response->assertHeader('Content-Type', 'application/json');
|
||||||
$response->assertOk();
|
$response->assertOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue