login update
This commit is contained in:
40
app/Http/Controllers/RSS/BlogFeedController.php
Normal file
40
app/Http/Controllers/RSS/BlogFeedController.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\RSS;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\BlogPost;
|
||||
use App\Services\RSS\RSSFeedBuilder;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
/**
|
||||
* BlogFeedController
|
||||
*
|
||||
* GET /rss/blog → latest blog posts feed (spec §3.6)
|
||||
*/
|
||||
final class BlogFeedController extends Controller
|
||||
{
|
||||
public function __construct(private readonly RSSFeedBuilder $builder) {}
|
||||
|
||||
public function __invoke(): Response
|
||||
{
|
||||
$feedUrl = url('/rss/blog');
|
||||
$posts = Cache::remember('rss:blog', 600, fn () =>
|
||||
BlogPost::published()
|
||||
->with('author:id,username')
|
||||
->latest('published_at')
|
||||
->limit(RSSFeedBuilder::FEED_LIMIT)
|
||||
->get()
|
||||
);
|
||||
|
||||
return $this->builder->buildFromBlogPosts(
|
||||
'Blog',
|
||||
'Latest posts from the Skinbase blog.',
|
||||
$feedUrl,
|
||||
$posts,
|
||||
);
|
||||
}
|
||||
}
|
||||
49
app/Http/Controllers/RSS/CreatorFeedController.php
Normal file
49
app/Http/Controllers/RSS/CreatorFeedController.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\RSS;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Artwork;
|
||||
use App\Models\User;
|
||||
use App\Services\RSS\RSSFeedBuilder;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* CreatorFeedController
|
||||
*
|
||||
* GET /rss/creator/{username} → latest artworks by a given creator (spec §3.5)
|
||||
*/
|
||||
final class CreatorFeedController extends Controller
|
||||
{
|
||||
public function __construct(private readonly RSSFeedBuilder $builder) {}
|
||||
|
||||
public function __invoke(string $username): Response
|
||||
{
|
||||
$user = User::where('username', $username)->first();
|
||||
|
||||
if (! $user) {
|
||||
throw new NotFoundHttpException("Creator [{$username}] not found.");
|
||||
}
|
||||
|
||||
$feedUrl = url('/rss/creator/' . $username);
|
||||
$artworks = Cache::remember('rss:creator:' . strtolower($username), 300, fn () =>
|
||||
Artwork::public()->published()
|
||||
->with(['user:id,username', 'categories:id,name,slug,content_type_id'])
|
||||
->where('artworks.user_id', $user->id)
|
||||
->latest('artworks.published_at')
|
||||
->limit(RSSFeedBuilder::FEED_LIMIT)
|
||||
->get()
|
||||
);
|
||||
|
||||
return $this->builder->buildFromArtworks(
|
||||
$user->username . '\'s Artworks',
|
||||
'Latest artworks by ' . $user->username . ' on Skinbase.',
|
||||
$feedUrl,
|
||||
$artworks,
|
||||
);
|
||||
}
|
||||
}
|
||||
98
app/Http/Controllers/RSS/DiscoverFeedController.php
Normal file
98
app/Http/Controllers/RSS/DiscoverFeedController.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\RSS;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Artwork;
|
||||
use App\Services\RSS\RSSFeedBuilder;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
/**
|
||||
* DiscoverFeedController
|
||||
*
|
||||
* Powers the /rss/discover/* feeds (spec §3.2).
|
||||
*
|
||||
* GET /rss/discover → fresh/latest (default)
|
||||
* GET /rss/discover/trending → trending by trending_score_7d
|
||||
* GET /rss/discover/fresh → latest published
|
||||
* GET /rss/discover/rising → rising by heat_score
|
||||
*/
|
||||
final class DiscoverFeedController extends Controller
|
||||
{
|
||||
public function __construct(private readonly RSSFeedBuilder $builder) {}
|
||||
|
||||
/** /rss/discover → redirect to fresh */
|
||||
public function index(): Response
|
||||
{
|
||||
return $this->fresh();
|
||||
}
|
||||
|
||||
/** /rss/discover/trending */
|
||||
public function trending(): Response
|
||||
{
|
||||
$feedUrl = url('/rss/discover/trending');
|
||||
$artworks = Cache::remember('rss:discover:trending', 600, fn () =>
|
||||
Artwork::public()->published()
|
||||
->with(['user:id,username', 'categories:id,name,slug,content_type_id'])
|
||||
->leftJoin('artwork_stats', 'artwork_stats.artwork_id', '=', 'artworks.id')
|
||||
->orderByDesc('artwork_stats.trending_score_7d')
|
||||
->orderByDesc('artworks.published_at')
|
||||
->select('artworks.*')
|
||||
->limit(RSSFeedBuilder::FEED_LIMIT)
|
||||
->get()
|
||||
);
|
||||
|
||||
return $this->builder->buildFromArtworks(
|
||||
'Trending Artworks',
|
||||
'The most-viewed and trending artworks on Skinbase over the past 7 days.',
|
||||
$feedUrl,
|
||||
$artworks,
|
||||
);
|
||||
}
|
||||
|
||||
/** /rss/discover/fresh */
|
||||
public function fresh(): Response
|
||||
{
|
||||
$feedUrl = url('/rss/discover/fresh');
|
||||
$artworks = Cache::remember('rss:discover:fresh', 300, fn () =>
|
||||
Artwork::public()->published()
|
||||
->with(['user:id,username', 'categories:id,name,slug,content_type_id'])
|
||||
->latest('published_at')
|
||||
->limit(RSSFeedBuilder::FEED_LIMIT)
|
||||
->get()
|
||||
);
|
||||
|
||||
return $this->builder->buildFromArtworks(
|
||||
'Fresh Uploads',
|
||||
'The latest artworks just published on Skinbase.',
|
||||
$feedUrl,
|
||||
$artworks,
|
||||
);
|
||||
}
|
||||
|
||||
/** /rss/discover/rising */
|
||||
public function rising(): Response
|
||||
{
|
||||
$feedUrl = url('/rss/discover/rising');
|
||||
$artworks = Cache::remember('rss:discover:rising', 600, fn () =>
|
||||
Artwork::public()->published()
|
||||
->with(['user:id,username', 'categories:id,name,slug,content_type_id'])
|
||||
->leftJoin('artwork_stats', 'artwork_stats.artwork_id', '=', 'artworks.id')
|
||||
->orderByDesc('artwork_stats.heat_score')
|
||||
->orderByDesc('artworks.published_at')
|
||||
->select('artworks.*')
|
||||
->limit(RSSFeedBuilder::FEED_LIMIT)
|
||||
->get()
|
||||
);
|
||||
|
||||
return $this->builder->buildFromArtworks(
|
||||
'Rising Artworks',
|
||||
'Fastest-growing artworks gaining momentum on Skinbase right now.',
|
||||
$feedUrl,
|
||||
$artworks,
|
||||
);
|
||||
}
|
||||
}
|
||||
105
app/Http/Controllers/RSS/ExploreFeedController.php
Normal file
105
app/Http/Controllers/RSS/ExploreFeedController.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\RSS;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Artwork;
|
||||
use App\Models\ContentType;
|
||||
use App\Services\RSS\RSSFeedBuilder;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
/**
|
||||
* ExploreFeedController
|
||||
*
|
||||
* Powers the /rss/explore/* feeds (spec §3.3).
|
||||
*
|
||||
* GET /rss/explore/{type} → latest by content type
|
||||
* GET /rss/explore/{type}/{mode} → sorted by mode (trending|latest|best)
|
||||
*
|
||||
* Valid types: artworks | wallpapers | skins | photography | other
|
||||
* Valid modes: trending | latest | best
|
||||
*/
|
||||
final class ExploreFeedController extends Controller
|
||||
{
|
||||
private const SORT_TTL = [
|
||||
'trending' => 600,
|
||||
'best' => 600,
|
||||
'latest' => 300,
|
||||
];
|
||||
|
||||
public function __construct(private readonly RSSFeedBuilder $builder) {}
|
||||
|
||||
/** /rss/explore/{type} — defaults to latest */
|
||||
public function byType(string $type): Response
|
||||
{
|
||||
return $this->feed($type, 'latest');
|
||||
}
|
||||
|
||||
/** /rss/explore/{type}/{mode} */
|
||||
public function byTypeMode(string $type, string $mode): Response
|
||||
{
|
||||
return $this->feed($type, $mode);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private function feed(string $type, string $mode): Response
|
||||
{
|
||||
$mode = in_array($mode, ['trending', 'latest', 'best'], true) ? $mode : 'latest';
|
||||
$ttl = self::SORT_TTL[$mode] ?? 300;
|
||||
$feedUrl = url('/rss/explore/' . $type . ($mode !== 'latest' ? '/' . $mode : ''));
|
||||
$label = ucfirst(str_replace('-', ' ', $type));
|
||||
|
||||
$artworks = Cache::remember("rss:explore:{$type}:{$mode}", $ttl, function () use ($type, $mode) {
|
||||
$contentType = ContentType::where('slug', $type)->first();
|
||||
|
||||
$query = Artwork::public()->published()
|
||||
->with(['user:id,username', 'categories:id,name,slug,content_type_id']);
|
||||
|
||||
if ($contentType) {
|
||||
$query->whereHas('categories', fn ($q) =>
|
||||
$q->where('content_type_id', $contentType->id)
|
||||
);
|
||||
}
|
||||
|
||||
return match ($mode) {
|
||||
'trending' => $query
|
||||
->leftJoin('artwork_stats', 'artwork_stats.artwork_id', '=', 'artworks.id')
|
||||
->orderByDesc('artwork_stats.trending_score_7d')
|
||||
->orderByDesc('artworks.published_at')
|
||||
->select('artworks.*')
|
||||
->limit(RSSFeedBuilder::FEED_LIMIT)
|
||||
->get(),
|
||||
|
||||
'best' => $query
|
||||
->leftJoin('artwork_stats', 'artwork_stats.artwork_id', '=', 'artworks.id')
|
||||
->orderByDesc('artwork_stats.favorites')
|
||||
->orderByDesc('artwork_stats.downloads')
|
||||
->select('artworks.*')
|
||||
->limit(RSSFeedBuilder::FEED_LIMIT)
|
||||
->get(),
|
||||
|
||||
default => $query
|
||||
->latest('artworks.published_at')
|
||||
->limit(RSSFeedBuilder::FEED_LIMIT)
|
||||
->get(),
|
||||
};
|
||||
});
|
||||
|
||||
$modeLabel = match ($mode) {
|
||||
'trending' => 'Trending',
|
||||
'best' => 'Best',
|
||||
default => 'Latest',
|
||||
};
|
||||
|
||||
return $this->builder->buildFromArtworks(
|
||||
"{$modeLabel} {$label}",
|
||||
"{$modeLabel} {$label} artworks on Skinbase.",
|
||||
$feedUrl,
|
||||
$artworks,
|
||||
);
|
||||
}
|
||||
}
|
||||
40
app/Http/Controllers/RSS/GlobalFeedController.php
Normal file
40
app/Http/Controllers/RSS/GlobalFeedController.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\RSS;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Artwork;
|
||||
use App\Services\RSS\RSSFeedBuilder;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
/**
|
||||
* GlobalFeedController
|
||||
*
|
||||
* GET /rss → global latest-artworks feed (spec §3.1)
|
||||
*/
|
||||
final class GlobalFeedController extends Controller
|
||||
{
|
||||
public function __construct(private readonly RSSFeedBuilder $builder) {}
|
||||
|
||||
public function __invoke(): Response
|
||||
{
|
||||
$feedUrl = url('/rss');
|
||||
$artworks = Cache::remember('rss:global', 300, fn () =>
|
||||
Artwork::public()->published()
|
||||
->with(['user:id,username', 'categories:id,name,slug,content_type_id'])
|
||||
->latest('published_at')
|
||||
->limit(RSSFeedBuilder::FEED_LIMIT)
|
||||
->get()
|
||||
);
|
||||
|
||||
return $this->builder->buildFromArtworks(
|
||||
'Latest Artworks',
|
||||
'The newest artworks published on Skinbase.',
|
||||
$feedUrl,
|
||||
$artworks,
|
||||
);
|
||||
}
|
||||
}
|
||||
49
app/Http/Controllers/RSS/TagFeedController.php
Normal file
49
app/Http/Controllers/RSS/TagFeedController.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\RSS;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Artwork;
|
||||
use App\Models\Tag;
|
||||
use App\Services\RSS\RSSFeedBuilder;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* TagFeedController
|
||||
*
|
||||
* GET /rss/tag/{slug} → artworks tagged with given slug (spec §3.4)
|
||||
*/
|
||||
final class TagFeedController extends Controller
|
||||
{
|
||||
public function __construct(private readonly RSSFeedBuilder $builder) {}
|
||||
|
||||
public function __invoke(string $slug): Response
|
||||
{
|
||||
$tag = Tag::where('slug', $slug)->first();
|
||||
|
||||
if (! $tag) {
|
||||
throw new NotFoundHttpException("Tag [{$slug}] not found.");
|
||||
}
|
||||
|
||||
$feedUrl = url('/rss/tag/' . $slug);
|
||||
$artworks = Cache::remember('rss:tag:' . $slug, 600, fn () =>
|
||||
Artwork::public()->published()
|
||||
->with(['user:id,username', 'categories:id,name,slug,content_type_id'])
|
||||
->whereHas('tags', fn ($q) => $q->where('tags.id', $tag->id))
|
||||
->latest('artworks.published_at')
|
||||
->limit(RSSFeedBuilder::FEED_LIMIT)
|
||||
->get()
|
||||
);
|
||||
|
||||
return $this->builder->buildFromArtworks(
|
||||
ucwords(str_replace('-', ' ', $slug)) . ' Artworks',
|
||||
'Latest Skinbase artworks tagged "' . $tag->name . '".',
|
||||
$feedUrl,
|
||||
$artworks,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user