feat: ship creator journey v2 and profile updates

This commit is contained in:
2026-04-12 21:42:07 +02:00
parent a2457f4e49
commit d5cff21ea2
335 changed files with 20147 additions and 1545 deletions

View File

@@ -10,15 +10,26 @@ use Illuminate\Support\ServiceProvider;
use App\Models\Artwork;
use App\Models\ArtworkAward;
use App\Models\ArtworkComment;
use App\Models\ArtworkFeature;
use App\Models\ArtworkFavourite;
use App\Models\ArtworkMedal;
use App\Models\ArtworkReaction;
use App\Models\ContentType;
use App\Models\GroupRelease;
use App\Models\GroupReleaseContributor;
use App\Observers\ArtworkAwardObserver;
use App\Observers\ArtworkCommentObserver;
use App\Observers\ArtworkFeatureObserver;
use App\Observers\ArtworkFavouriteObserver;
use App\Observers\ArtworkObserver;
use App\Observers\ArtworkReactionObserver;
use App\Observers\ContentTypeObserver;
use App\Observers\GroupReleaseContributorObserver;
use App\Observers\GroupReleaseObserver;
use App\Services\Upload\Contracts\UploadDraftServiceInterface;
use App\Services\Upload\UploadDraftService;
use App\Services\ContentTypes\ContentTypeSlugResolver;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Auth;
@@ -82,6 +93,12 @@ class AppServiceProvider extends ServiceProvider
// after the folder was renamed from legacy/ to _legacy/.
View::addNamespace('legacy', resource_path('views/_legacy'));
$exceptionRendererComponentsPath = base_path('vendor/laravel/framework/src/Illuminate/Foundation/resources/exceptions/renderer/components');
if (is_dir($exceptionRendererComponentsPath)) {
Blade::anonymousComponentNamespace($exceptionRendererComponentsPath, 'laravel-exceptions-renderer');
}
$this->configureAuthRateLimiters();
$this->configureUploadRateLimiters();
$this->configureMessagingRateLimiters();
@@ -94,10 +111,15 @@ class AppServiceProvider extends ServiceProvider
$this->configureMailFailureLogging();
ArtworkAward::observe(ArtworkAwardObserver::class);
ArtworkMedal::observe(ArtworkAwardObserver::class);
Artwork::observe(ArtworkObserver::class);
ArtworkFeature::observe(ArtworkFeatureObserver::class);
ArtworkFavourite::observe(ArtworkFavouriteObserver::class);
ArtworkComment::observe(ArtworkCommentObserver::class);
ArtworkReaction::observe(ArtworkReactionObserver::class);
ContentType::observe(ContentTypeObserver::class);
GroupRelease::observe(GroupReleaseObserver::class);
GroupReleaseContributor::observe(GroupReleaseContributorObserver::class);
// ── OAuth / SocialiteProviders ──────────────────────────────────────
Event::listen(
@@ -134,6 +156,17 @@ class AppServiceProvider extends ServiceProvider
$avatarHash = null;
$displayName = null;
$userId = null;
$toolbarContentTypes = collect();
try {
$toolbarContentTypes = $this->app
->make(ContentTypeSlugResolver::class)
->toolbarContentTypes()
->map(fn (ContentType $contentType) => $contentType->only(['id', 'name', 'slug']))
->map(fn (array $attributes) => new ContentType($attributes));
} catch (\Throwable $e) {
$toolbarContentTypes = collect();
}
if (Auth::check()) {
$userId = Auth::id();
@@ -188,7 +221,7 @@ class AppServiceProvider extends ServiceProvider
$displayName = Auth::user()->name ?: (Auth::user()->username ?? '');
}
$view->with(compact('userId','uploadCount', 'favCount', 'msgCount', 'noticeCount', 'receivedCommentsCount', 'avatarHash', 'displayName'));
$view->with(compact('userId','uploadCount', 'favCount', 'msgCount', 'noticeCount', 'receivedCommentsCount', 'avatarHash', 'displayName', 'toolbarContentTypes'));
});
// Replace the framework HandleCors with our ConditionalCors so the
@@ -384,13 +417,14 @@ class AppServiceProvider extends ServiceProvider
RateLimiter::for('artwork-awards', function (Request $request): array {
$userId = $request->user()?->id;
$artworkId = (int) $request->route('id');
$perMinute = max(1, (int) config('artwork_medals.rate_limit_per_minute', 10));
return [
// Prevent burst spam on a single artwork while allowing normal exploration.
Limit::perMinute(20)->by('awards:user:' . ($userId ?? 'guest') . ':art:' . $artworkId),
Limit::perMinute($perMinute)->by('awards:user:' . ($userId ?? 'guest') . ':art:' . $artworkId),
// Global safety net for user/IP across all artworks.
Limit::perMinute(120)->by('awards:user:' . ($userId ?? 'guest')),
Limit::perMinute(180)->by('awards:ip:' . $request->ip()),
Limit::perMinute($perMinute * 6)->by('awards:user:' . ($userId ?? 'guest')),
Limit::perMinute($perMinute * 9)->by('awards:ip:' . $request->ip()),
];
});
}
@@ -472,6 +506,10 @@ class AppServiceProvider extends ServiceProvider
private function registerCpadMenuItems(): void
{
if (! $this->isControlPanelRequest()) {
return;
}
if (! class_exists(Menu::class)) {
return;
}
@@ -483,4 +521,25 @@ class AppServiceProvider extends ServiceProvider
// Control panel menu registration should never block the app boot.
}
}
private function isControlPanelRequest(): bool
{
if ($this->app->runningInConsole()) {
return true;
}
if (! $this->app->bound('request')) {
return false;
}
$prefix = trim((string) config('cpad.webroot', config('cp.webroot', '/cp')), '/');
if ($prefix === '') {
return false;
}
$request = $this->app->make('request');
return $request->is($prefix) || $request->is($prefix . '/*');
}
}