login update
This commit is contained in:
138
app/Services/RSS/RSSFeedBuilder.php
Normal file
138
app/Services/RSS/RSSFeedBuilder.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\RSS;
|
||||
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* RSSFeedBuilder
|
||||
*
|
||||
* Responsible for:
|
||||
* - normalising feed items from Eloquent collections
|
||||
* - enforcing feed limits (max 20 items)
|
||||
* - rendering RSS 2.0 XML via the rss.channel Blade template
|
||||
* - returning a properly typed HTTP Response
|
||||
*/
|
||||
final class RSSFeedBuilder
|
||||
{
|
||||
/** Hard item limit per feed (spec §7). */
|
||||
public const FEED_LIMIT = 20;
|
||||
|
||||
// ── Public builders ───────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Build an RSS 2.0 Response from an Artwork Eloquent collection.
|
||||
* Artworks must have 'user' and 'categories' relations preloaded.
|
||||
*/
|
||||
public function buildFromArtworks(
|
||||
string $channelTitle,
|
||||
string $channelDescription,
|
||||
string $feedUrl,
|
||||
Collection $artworks,
|
||||
): Response {
|
||||
$items = $artworks->take(self::FEED_LIMIT)->map(fn ($a) => $this->artworkToItem($a));
|
||||
|
||||
return $this->buildResponse($channelTitle, $channelDescription, url('/'), $feedUrl, $items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an RSS 2.0 Response from a BlogPost Eloquent collection.
|
||||
* Posts must have 'author' relation preloaded.
|
||||
*/
|
||||
public function buildFromBlogPosts(
|
||||
string $channelTitle,
|
||||
string $channelDescription,
|
||||
string $feedUrl,
|
||||
Collection $posts,
|
||||
): Response {
|
||||
$items = $posts->take(self::FEED_LIMIT)->map(fn ($p) => $this->blogPostToItem($p));
|
||||
|
||||
return $this->buildResponse($channelTitle, $channelDescription, url('/blog'), $feedUrl, $items);
|
||||
}
|
||||
|
||||
// ── Private helpers ───────────────────────────────────────────────────────
|
||||
|
||||
private function buildResponse(
|
||||
string $channelTitle,
|
||||
string $channelDescription,
|
||||
string $channelLink,
|
||||
string $feedUrl,
|
||||
Collection $items,
|
||||
): Response {
|
||||
$xml = view('rss.channel', [
|
||||
'channelTitle' => trim($channelTitle) . ' — Skinbase',
|
||||
'channelDescription' => $channelDescription,
|
||||
'channelLink' => $channelLink,
|
||||
'feedUrl' => $feedUrl,
|
||||
'items' => $items,
|
||||
'buildDate' => now()->toRfc2822String(),
|
||||
])->render();
|
||||
|
||||
return response($xml, 200, [
|
||||
'Content-Type' => 'application/rss+xml; charset=utf-8',
|
||||
'Cache-Control' => 'public, max-age=300',
|
||||
]);
|
||||
}
|
||||
|
||||
/** Convert an Artwork model to an RSS item array. */
|
||||
private function artworkToItem(object $artwork): array
|
||||
{
|
||||
$link = url('/art/' . $artwork->id . '/' . ($artwork->slug ?? ''));
|
||||
$thumb = method_exists($artwork, 'thumbUrl') ? $artwork->thumbUrl('sm') : null;
|
||||
|
||||
// Primary category from eagerly loaded relation (avoid N+1)
|
||||
$primaryCategory = ($artwork->relationLoaded('categories'))
|
||||
? $artwork->categories->first()
|
||||
: null;
|
||||
|
||||
// Build HTML description embedded in CDATA
|
||||
$descParts = [];
|
||||
if ($thumb) {
|
||||
$descParts[] = '<img src="' . htmlspecialchars($thumb, ENT_XML1) . '" '
|
||||
. 'alt="' . htmlspecialchars((string) $artwork->title, ENT_XML1) . '" />';
|
||||
}
|
||||
if (!empty($artwork->description)) {
|
||||
$descParts[] = '<p>' . htmlspecialchars(strip_tags((string) $artwork->description), ENT_XML1) . '</p>';
|
||||
}
|
||||
|
||||
return [
|
||||
'title' => (string) $artwork->title,
|
||||
'link' => $link,
|
||||
'guid' => $link,
|
||||
'description' => implode('', $descParts),
|
||||
'pubDate' => $artwork->published_at?->toRfc2822String(),
|
||||
'author' => $artwork->user?->username ?? 'Unknown',
|
||||
'category' => $primaryCategory?->name,
|
||||
'enclosure' => $thumb ? [
|
||||
'url' => $thumb,
|
||||
'length' => 0,
|
||||
'type' => 'image/jpeg',
|
||||
] : null,
|
||||
];
|
||||
}
|
||||
|
||||
/** Convert a BlogPost model to an RSS item array. */
|
||||
private function blogPostToItem(object $post): array
|
||||
{
|
||||
$link = url('/blog/' . $post->slug);
|
||||
$excerpt = $post->excerpt ?? strip_tags((string) ($post->body ?? ''));
|
||||
|
||||
return [
|
||||
'title' => (string) $post->title,
|
||||
'link' => $link,
|
||||
'guid' => $link,
|
||||
'description' => $excerpt,
|
||||
'pubDate' => $post->published_at?->toRfc2822String(),
|
||||
'author' => $post->author?->username ?? 'Skinbase',
|
||||
'category' => null,
|
||||
'enclosure' => !empty($post->featured_image) ? [
|
||||
'url' => $post->featured_image,
|
||||
'length' => 0,
|
||||
'type' => 'image/jpeg',
|
||||
] : null,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user