Files
SkinbaseNova/app/Providers/AppServiceProvider.php
Gregor Klevze d0aefc5ddc feat: Nova homepage, profile redesign, and legacy view system overhaul
Homepage
- Add HomepageService with hero, trending (award-weighted), fresh uploads,
  popular tags, creator spotlight (weekly uploads ranking), and news sections
- Add React components: HomePage, HomeHero, HomeTrending, HomeFresh,
  HomeTags, HomeCreators, HomeNews (lazy-loaded below the fold)
- Wire home.blade.php with JSON props, SEO meta, JSON-LD, and hero preload
- Add HomePage.jsx to vite.config.js inputs

Profile page
- Hero banner with random user artwork as background + dark gradient overlay
- Favourites section uses real Artwork models + <x-artwork-card> for CDN URLs
- Newest artworks grid: gallery-grid → grid grid-cols-2 gap-4

Edit Profile page (user.blade.php)
- Add hero banner (featured wallpaper/photography via artwork_features,
  content_type_id IN [2,3]) sourced in UserController
- Remove bg-deep from outer wrapper; card backgrounds: bg-panel → bg-nova-800
- Remove stray AI-generated tag fragment from template

Author profile links
- Fix all /@username routes in: HomepageService, MonthlyCommentatorsController,
  LatestCommentsController, MyBuddiesController and corresponding blade views

Legacy view namespace
- Register View::addNamespace('legacy', resource_path('views/_legacy'))
  in AppServiceProvider::boot()
- Convert all view('legacy.x') and @include('legacy.x') calls to legacy::x
- Migrate legacy views to resources/views/_legacy/ with namespace support
2026-02-26 10:25:35 +01:00

183 lines
6.6 KiB
PHP

<?php
namespace App\Providers;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;
use App\Models\ArtworkAward;
use App\Observers\ArtworkAwardObserver;
use App\Models\Artwork;
use App\Observers\ArtworkObserver;
use App\Services\Upload\Contracts\UploadDraftServiceInterface;
use App\Services\Upload\UploadDraftService;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Log;
use Illuminate\Queue\Events\JobFailed;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
// Bind UploadDraftService interface to implementation
$this->app->singleton(UploadDraftServiceInterface::class, function ($app) {
return new UploadDraftService($app->make('filesystem'));
});
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
// Map the 'legacy' view namespace to resources/views/_legacy so all
// view('legacy::foo') and @include('legacy::foo') calls resolve correctly
// after the folder was renamed from legacy/ to _legacy/.
View::addNamespace('legacy', resource_path('views/_legacy'));
$this->configureAuthRateLimiters();
$this->configureUploadRateLimiters();
$this->configureMailFailureLogging();
ArtworkAward::observe(ArtworkAwardObserver::class);
Artwork::observe(ArtworkObserver::class);
// Provide toolbar counts and user info to layout views (port of legacy toolbar logic)
View::composer(['layouts.nova', 'layouts.nova.*'], function ($view) {
$uploadCount = $favCount = $msgCount = $noticeCount = 0;
$avatarHash = null;
$displayName = null;
$userId = null;
if (Auth::check()) {
$userId = Auth::id();
try {
$uploadCount = DB::table('artworks')->where('user_id', $userId)->count();
} catch (\Throwable $e) {
$uploadCount = 0;
}
try {
// legacy table name fallback handled elsewhere; here we look for user_favorites or favourites
$favCount = DB::table('user_favorites')->where('user_id', $userId)->count();
} catch (\Throwable $e) {
try {
$favCount = DB::table('favourites')->where('user_id', $userId)->count();
} catch (\Throwable $e) {
$favCount = 0;
}
}
try {
$msgCount = DB::table('messages')->where('reciever_id', $userId)->whereNull('read_at')->count();
} catch (\Throwable $e) {
$msgCount = 0;
}
try {
$noticeCount = DB::table('notification')->where('user_id', $userId)->where('new', 1)->count();
} catch (\Throwable $e) {
$noticeCount = 0;
}
try {
$profile = DB::table('user_profiles')->where('user_id', $userId)->first();
$avatarHash = $profile->avatar_hash ?? null;
} catch (\Throwable $e) {
$avatarHash = null;
}
$displayName = Auth::user()->name ?: (Auth::user()->username ?? '');
}
$view->with(compact('userId','uploadCount', 'favCount', 'msgCount', 'noticeCount', 'avatarHash', 'displayName'));
});
}
private function configureAuthRateLimiters(): void
{
RateLimiter::for('register-ip', function (Request $request): Limit {
$limit = max(1, (int) config('registration.ip_per_minute_limit', 3));
return Limit::perMinute($limit)->by('register:ip:' . $request->ip());
});
RateLimiter::for('register-ip-daily', function (Request $request): Limit {
$limit = max(1, (int) config('registration.ip_per_day_limit', 20));
return Limit::perDay($limit)->by('register:ip:daily:' . $request->ip());
});
RateLimiter::for('register', function (Request $request): array {
$emailKey = strtolower((string) $request->input('email', 'unknown'));
$ipLimit = (int) config('registration.ip_per_minute_limit', 3);
$emailLimit = (int) config('registration.email_per_minute_limit', 6);
return [
Limit::perMinute($ipLimit)->by('register:ip:' . $request->ip()),
Limit::perMinute($emailLimit)->by('register:email:' . $emailKey),
];
});
}
private function configureMailFailureLogging(): void
{
Event::listen(JobFailed::class, function (JobFailed $event): void {
if (! str_contains(strtolower($event->job->resolveName()), 'sendqueuedmailable')) {
return;
}
Log::warning('mail delivery failed', [
'transport' => config('mail.default'),
'job_name' => $event->job->resolveName(),
'queue' => $event->job->getQueue(),
'connection' => $event->connectionName,
'exception' => $event->exception->getMessage(),
]);
});
}
private function configureUploadRateLimiters(): void
{
RateLimiter::for('uploads-init', function (Request $request): array {
return $this->buildUploadLimits($request, 'init');
});
RateLimiter::for('uploads-finish', function (Request $request): array {
return $this->buildUploadLimits($request, 'finish');
});
RateLimiter::for('uploads-status', function (Request $request): array {
return $this->buildUploadLimits($request, 'status');
});
}
private function buildUploadLimits(Request $request, string $key): array
{
$config = (array) config('uploads.rate_limits.' . $key, []);
$decay = (int) config('uploads.rate_limits.decay_minutes', 1);
$perUser = (int) ($config['per_user'] ?? 0);
$perIp = (int) ($config['per_ip'] ?? 0);
$limits = [];
if ($perUser > 0) {
$userId = $request->user()?->id ?? 'guest';
$limits[] = Limit::perMinutes($decay, $perUser)->by('u:' . $userId);
}
if ($perIp > 0) {
$limits[] = Limit::perMinutes($decay, $perIp)->by('ip:' . $request->ip());
}
return $limits;
}
}