Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d6ab4424fc | |||
|
a76c84b40f |
|||
|
99f306ede1 |
|||
|
714cd95d79 |
12 changed files with 61 additions and 211 deletions
|
|
@ -7,66 +7,13 @@ namespace App\Http\Controllers;
|
|||
use App\Models\Article;
|
||||
use App\Models\Note;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
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.
|
||||
*/
|
||||
public function blogJson(): array
|
||||
public function blogJson(): JsonResponse
|
||||
{
|
||||
$articles = Article::where('published', '1')->latest('updated_at')->take(20)->get();
|
||||
$data = [
|
||||
|
|
@ -94,13 +41,15 @@ class FeedsController extends Controller
|
|||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
return response()->json($data, 200, [
|
||||
'Content-Type' => 'application/feed+json',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the notes JSON feed.
|
||||
*/
|
||||
public function notesJson(): array
|
||||
public function notesJson(): JsonResponse
|
||||
{
|
||||
$notes = Note::latest()->with('media', 'place', 'tags')->take(20)->get();
|
||||
$data = [
|
||||
|
|
@ -130,7 +79,9 @@ class FeedsController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
return response()->json($data, 200, [
|
||||
'Content-Type' => 'application/feed+json',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class ArticleService
|
|||
return Article::create([
|
||||
'title' => $data['name'],
|
||||
'main' => $data['content'],
|
||||
'published' => true,
|
||||
'published' => ($data['post-status'] ?? null) !== 'draft',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ class EntryData extends MicropubData
|
|||
public readonly mixed $checkin,
|
||||
public readonly mixed $syndication,
|
||||
public readonly ?array $photos,
|
||||
public readonly ?string $postStatus,
|
||||
) {}
|
||||
|
||||
public static function fromRequest(Request $request): static
|
||||
|
|
@ -49,6 +50,7 @@ class EntryData extends MicropubData
|
|||
checkin: Arr::get($data, 'properties.checkin.0'),
|
||||
syndication: Arr::get($data, 'properties.syndication.0'),
|
||||
photos: Arr::get($data, 'properties.photo'),
|
||||
postStatus: Arr::get($data, 'properties.post-status.0'),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -67,6 +69,7 @@ class EntryData extends MicropubData
|
|||
checkin: $request->input('checkin'),
|
||||
syndication: $request->input('syndication'),
|
||||
photos: $request->input('photos'),
|
||||
postStatus: $request->input('post-status'),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -100,6 +103,7 @@ class EntryData extends MicropubData
|
|||
checkin: $data['checkin'] ?? null,
|
||||
syndication: $data['syndication'] ?? null,
|
||||
photos: $data['photos'] ?? null,
|
||||
postStatus: $data['post-status'] ?? null,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -120,6 +124,7 @@ class EntryData extends MicropubData
|
|||
'checkin' => $this->checkin,
|
||||
'syndication' => $this->syndication,
|
||||
'photos' => $this->photos,
|
||||
'post-status' => $this->postStatus,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
<?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>
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
<?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>
|
||||
11
resources/views/icons/json-feed.blade.php
Normal file
11
resources/views/icons/json-feed.blade.php
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
@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,13 +7,9 @@
|
|||
<title>@yield('title'){{ config('app.name') }}</title>
|
||||
<link rel="stylesheet" href="/assets/highlight/nord.css">
|
||||
<link rel="stylesheet" href="/assets/css/app.css">
|
||||
<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/feed+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/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/feed+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="indieauth-metadata" href="{{ route('indieauth.metadata') }}">
|
||||
<link rel="authorization_endpoint" href="{{ route('indieauth.start') }}">
|
||||
|
|
@ -40,7 +36,7 @@
|
|||
<a href="/likes">Likes</a>
|
||||
<a href="/contacts">Contacts</a>
|
||||
<a href="/projects">Projects</a>
|
||||
<a href="{{ route('feed.notes.json') }}" class="rss-icon">@include('icons.rss', ['title' => 'RSS Feed'])</a>
|
||||
<a href="{{ route('feed.notes.json') }}" class="rss-icon">@include('icons.json-feed', ['title' => 'JSON Feed'])</a>
|
||||
</nav>
|
||||
<div id="theme-selector">
|
||||
<button class="toggle" popovertarget="theme-selector-dropdown">
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
<?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>
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
<?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,8 +170,6 @@ Route::middleware(MyAuthMiddleware::class)->prefix('admin')->group(function () {
|
|||
|
||||
// Blog pages using ArticlesController
|
||||
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.jf2', [FeedsController::class, 'blogJf2'])->name('feed.blog.jf2');
|
||||
Route::get('/s/{id}', [ArticlesController::class, 'onlyIdInURL']);
|
||||
|
|
@ -182,8 +180,6 @@ Route::prefix('blog')->group(function () {
|
|||
// Notes pages using NotesController
|
||||
Route::prefix('notes')->group(function () {
|
||||
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.jf2', [FeedsController::class, 'notesJf2'])->name('feed.notes.jf2');
|
||||
Route::get('/new', [NotesController::class, 'create']);
|
||||
|
|
|
|||
|
|
@ -15,42 +15,6 @@ class FeedsTest extends TestCase
|
|||
{
|
||||
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]
|
||||
public function blog_jf2_feed_is_present(): void
|
||||
{
|
||||
|
|
@ -73,18 +37,6 @@ 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.
|
||||
*/
|
||||
|
|
@ -93,7 +45,7 @@ class FeedsTest extends TestCase
|
|||
{
|
||||
Article::factory()->count(3)->create();
|
||||
$response = $this->get('/blog/feed.json');
|
||||
$response->assertHeader('Content-Type', 'application/json');
|
||||
$response->assertHeader('Content-Type', 'application/feed+json');
|
||||
$response->assertOk();
|
||||
}
|
||||
|
||||
|
|
@ -105,7 +57,7 @@ class FeedsTest extends TestCase
|
|||
{
|
||||
Note::factory()->count(3)->create();
|
||||
$response = $this->get('/notes/feed.json');
|
||||
$response->assertHeader('Content-Type', 'application/json');
|
||||
$response->assertHeader('Content-Type', 'application/feed+json');
|
||||
$response->assertOk();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -869,6 +869,37 @@ class MicropubControllerTest extends TestCase
|
|||
$this->assertDatabaseHas('articles', [
|
||||
'title' => $name,
|
||||
'main' => $content,
|
||||
'published' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function micropub_client_api_request_creates_an_unpublished_article_when_post_status_is_draft(): void
|
||||
{
|
||||
$faker = Factory::create();
|
||||
$name = $faker->text(50);
|
||||
$content = $faker->paragraphs(5, true);
|
||||
|
||||
$response = $this->postJson(
|
||||
'/api/post',
|
||||
[
|
||||
'type' => ['h-entry'],
|
||||
'properties' => [
|
||||
'name' => [$name],
|
||||
'content' => [$content],
|
||||
'post-status' => ['draft'],
|
||||
],
|
||||
],
|
||||
['HTTP_Authorization' => 'Bearer '.$this->getToken()]
|
||||
);
|
||||
|
||||
$response
|
||||
->assertJson(['response' => 'created'])
|
||||
->assertStatus(201);
|
||||
$this->assertDatabaseHas('articles', [
|
||||
'title' => $name,
|
||||
'main' => $content,
|
||||
'published' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue