Move model observers onto attributes and tidy provider bindings.

Register observers with ObservedBy, bind the comment spam classifier, and keep AppServiceProvider focused on application wiring.
This commit is contained in:
2026-09-20 14:49:00 +02:00
parent 586c44ba74
commit 31c87e4977
9 changed files with 336 additions and 250 deletions
+3
View File
@@ -4,9 +4,12 @@ declare(strict_types=1);
namespace App\Models; namespace App\Models;
use App\Observers\ArtworkAwardObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
#[ObservedBy([ArtworkAwardObserver::class])]
class ArtworkAward extends Model class ArtworkAward extends Model
{ {
protected $table = 'artwork_medals'; protected $table = 'artwork_medals';
+6 -3
View File
@@ -2,6 +2,9 @@
namespace App\Models; namespace App\Models;
use App\Observers\ArtworkFavouriteObserver;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@ -12,12 +15,12 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
* @property int $user_id * @property int $user_id
* @property int $artwork_id * @property int $artwork_id
* @property int|null $legacy_id Original favourite_id from the old site * @property int|null $legacy_id Original favourite_id from the old site
* @property \Carbon\Carbon $created_at * @property Carbon $created_at
* @property \Carbon\Carbon $updated_at * @property Carbon $updated_at
*
* @property-read User $user * @property-read User $user
* @property-read Artwork $artwork * @property-read Artwork $artwork
*/ */
#[ObservedBy([ArtworkFavouriteObserver::class])]
class ArtworkFavourite extends Model class ArtworkFavourite extends Model
{ {
protected $table = 'artwork_favourites'; protected $table = 'artwork_favourites';
+4
View File
@@ -1,10 +1,14 @@
<?php <?php
namespace App\Models; namespace App\Models;
use App\Observers\ArtworkFeatureObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
#[ObservedBy([ArtworkFeatureObserver::class])]
class ArtworkFeature extends Model class ArtworkFeature extends Model
{ {
use SoftDeletes; use SoftDeletes;
+3
View File
@@ -2,6 +2,8 @@
namespace App\Models; namespace App\Models;
use App\Observers\ArtworkReactionObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@ -11,6 +13,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
* @property int $user_id * @property int $user_id
* @property string $reaction ReactionType slug (e.g. thumbs_up, heart, fire…) * @property string $reaction ReactionType slug (e.g. thumbs_up, heart, fire…)
*/ */
#[ObservedBy([ArtworkReactionObserver::class])]
class ArtworkReaction extends Model class ArtworkReaction extends Model
{ {
public $timestamps = false; public $timestamps = false;
+4 -3
View File
@@ -2,12 +2,13 @@
namespace App\Models; namespace App\Models;
use App\Observers\ContentTypeObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use App\Models\Artwork;
#[ObservedBy([ContentTypeObserver::class])]
class ContentType extends Model class ContentType extends Model
{ {
protected $fillable = ['name', 'slug', 'description', 'order', 'hide_from_menu', 'mascot_path', 'cover_art_path']; protected $fillable = ['name', 'slug', 'description', 'order', 'hide_from_menu', 'mascot_path', 'cover_art_path'];
+17
View File
@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace App\Models; namespace App\Models;
use App\Observers\GroupReleaseObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@ -11,29 +13,44 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
#[ObservedBy([GroupReleaseObserver::class])]
class GroupRelease extends Model class GroupRelease extends Model
{ {
use HasFactory; use HasFactory;
use SoftDeletes; use SoftDeletes;
public const STATUS_PLANNED = 'planned'; public const STATUS_PLANNED = 'planned';
public const STATUS_IN_PROGRESS = 'in_progress'; public const STATUS_IN_PROGRESS = 'in_progress';
public const STATUS_INTERNAL_REVIEW = 'internal_review'; public const STATUS_INTERNAL_REVIEW = 'internal_review';
public const STATUS_SCHEDULED = 'scheduled'; public const STATUS_SCHEDULED = 'scheduled';
public const STATUS_RELEASED = 'released'; public const STATUS_RELEASED = 'released';
public const STATUS_ARCHIVED = 'archived'; public const STATUS_ARCHIVED = 'archived';
public const STATUS_CANCELLED = 'cancelled'; public const STATUS_CANCELLED = 'cancelled';
public const STAGE_CONCEPT = 'concept'; public const STAGE_CONCEPT = 'concept';
public const STAGE_PRODUCTION = 'production'; public const STAGE_PRODUCTION = 'production';
public const STAGE_REVIEW = 'review'; public const STAGE_REVIEW = 'review';
public const STAGE_PACKAGING = 'packaging'; public const STAGE_PACKAGING = 'packaging';
public const STAGE_APPROVAL = 'approval'; public const STAGE_APPROVAL = 'approval';
public const STAGE_PUBLISHING = 'publishing'; public const STAGE_PUBLISHING = 'publishing';
public const STAGE_RELEASED = 'released'; public const STAGE_RELEASED = 'released';
public const VISIBILITY_PUBLIC = 'public'; public const VISIBILITY_PUBLIC = 'public';
public const VISIBILITY_UNLISTED = 'unlisted'; public const VISIBILITY_UNLISTED = 'unlisted';
public const VISIBILITY_PRIVATE = 'private'; public const VISIBILITY_PRIVATE = 'private';
protected $fillable = [ protected $fillable = [
+3
View File
@@ -4,10 +4,13 @@ declare(strict_types=1);
namespace App\Models; namespace App\Models;
use App\Observers\GroupReleaseContributorObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
#[ObservedBy([GroupReleaseContributorObserver::class])]
class GroupReleaseContributor extends Model class GroupReleaseContributor extends Model
{ {
use HasFactory; use HasFactory;
+27
View File
@@ -4,48 +4,75 @@ declare(strict_types=1);
namespace App\Models; namespace App\Models;
use App\Observers\HomepageAnnouncementObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
#[ObservedBy([HomepageAnnouncementObserver::class])]
class HomepageAnnouncement extends Model class HomepageAnnouncement extends Model
{ {
use SoftDeletes; use SoftDeletes;
public const STATUS_DRAFT = 'draft'; public const STATUS_DRAFT = 'draft';
public const STATUS_PUBLISHED = 'published'; public const STATUS_PUBLISHED = 'published';
public const STATUS_ARCHIVED = 'archived'; public const STATUS_ARCHIVED = 'archived';
public const TYPE_ANNOUNCEMENT = 'announcement'; public const TYPE_ANNOUNCEMENT = 'announcement';
public const TYPE_LAUNCH = 'launch'; public const TYPE_LAUNCH = 'launch';
public const TYPE_NEWS = 'news'; public const TYPE_NEWS = 'news';
public const TYPE_WORLD = 'world'; public const TYPE_WORLD = 'world';
public const TYPE_EVENT = 'event'; public const TYPE_EVENT = 'event';
public const TYPE_NOTICE = 'notice'; public const TYPE_NOTICE = 'notice';
public const TYPE_MAINTENANCE = 'maintenance'; public const TYPE_MAINTENANCE = 'maintenance';
public const PLACEMENT_HOMEPAGE_AFTER_FEATURED = 'homepage_after_featured'; public const PLACEMENT_HOMEPAGE_AFTER_FEATURED = 'homepage_after_featured';
public const LINK_TYPE_NONE = 'none'; public const LINK_TYPE_NONE = 'none';
public const LINK_TYPE_CUSTOM_URL = 'custom_url'; public const LINK_TYPE_CUSTOM_URL = 'custom_url';
public const LINK_TYPE_NEWS = 'news'; public const LINK_TYPE_NEWS = 'news';
public const LINK_TYPE_WORLD = 'world'; public const LINK_TYPE_WORLD = 'world';
public const LINK_TYPE_ARTWORK = 'artwork'; public const LINK_TYPE_ARTWORK = 'artwork';
public const LINK_TYPE_COLLECTION = 'collection'; public const LINK_TYPE_COLLECTION = 'collection';
public const LINK_TYPE_GROUP = 'group'; public const LINK_TYPE_GROUP = 'group';
public const LINK_TYPE_PROFILE = 'profile'; public const LINK_TYPE_PROFILE = 'profile';
public const LINK_TYPE_EXPLORE = 'explore'; public const LINK_TYPE_EXPLORE = 'explore';
public const LINK_TYPE_UPLOAD = 'upload'; public const LINK_TYPE_UPLOAD = 'upload';
public const GRADIENT_NOVA_AURORA = 'nova_aurora'; public const GRADIENT_NOVA_AURORA = 'nova_aurora';
public const GRADIENT_DEEP_SPACE = 'deep_space'; public const GRADIENT_DEEP_SPACE = 'deep_space';
public const GRADIENT_SUNRISE = 'sunrise'; public const GRADIENT_SUNRISE = 'sunrise';
public const GRADIENT_OCEAN_GLOW = 'ocean_glow'; public const GRADIENT_OCEAN_GLOW = 'ocean_glow';
public const GRADIENT_SPRING_VIBES = 'spring_vibes'; public const GRADIENT_SPRING_VIBES = 'spring_vibes';
public const GRADIENT_FANTASY_REALMS = 'fantasy_realms'; public const GRADIENT_FANTASY_REALMS = 'fantasy_realms';
public const GRADIENT_MINIMAL_LIGHT = 'minimal_light'; public const GRADIENT_MINIMAL_LIGHT = 'minimal_light';
public const GRADIENT_DARK_GLASS = 'dark_glass'; public const GRADIENT_DARK_GLASS = 'dark_glass';
protected $table = 'homepage_announcements'; protected $table = 'homepage_announcements';
+102 -77
View File
@@ -3,53 +3,59 @@
namespace App\Providers; namespace App\Providers;
use App\Contracts\Images\SubjectDetectorInterface; use App\Contracts\Images\SubjectDetectorInterface;
use Illuminate\Cache\RateLimiting\Limit; use App\Contracts\Moderation\CommentSpamClassifier;
use Illuminate\Http\Request; use App\Events\Achievements\AchievementCheckRequested;
use Illuminate\Support\Facades\RateLimiter; use App\Events\Achievements\UserXpUpdated;
use Illuminate\Support\ServiceProvider; use App\Events\Posts\ArtworkShared;
use App\Events\Posts\PostCommented;
use App\Http\Middleware\ConditionalCors;
use App\Listeners\Academy\HandleAcademyStripeWebhook;
use App\Listeners\Academy\HandleAcademyStripeWebhookHandled;
use App\Listeners\Achievements\CheckUserAchievements;
use App\Listeners\Posts\AwardXpForPostCommented;
use App\Listeners\Posts\SendArtworkSharedNotification;
use App\Listeners\Posts\SendPostCommentedNotification;
use App\Models\Artwork; 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\ContentType;
use App\Models\GroupRelease;
use App\Models\GroupReleaseContributor;
use App\Models\HomepageAnnouncement;
use App\Observers\ArtworkAwardObserver;
use App\Observers\ArtworkCommentObserver;
use App\Observers\ArtworkFeatureObserver;
use App\Observers\ArtworkFavouriteObserver;
use App\Observers\ArtworkObserver; use App\Observers\ArtworkObserver;
use App\Observers\ArtworkReactionObserver;
use App\Observers\ContentTypeObserver;
use App\Observers\GroupReleaseContributorObserver;
use App\Observers\GroupReleaseObserver;
use App\Observers\HomepageAnnouncementObserver;
use App\Services\Upload\Contracts\UploadDraftServiceInterface;
use App\Services\Upload\UploadDraftService;
use App\Services\ContentTypes\ContentTypeSlugResolver; use App\Services\ContentTypes\ContentTypeSlugResolver;
use App\Services\Worlds\WorldService; use App\Services\Countries\CountryRemoteProvider;
use Illuminate\Support\Facades\Blade; use App\Services\Countries\CountryRemoteProviderInterface;
use Illuminate\Support\Facades\View; use App\Services\EarlyGrowth\SpotlightEngine;
use Illuminate\Support\Facades\DB; use App\Services\EarlyGrowth\SpotlightEngineInterface;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Log;
use Illuminate\Queue\Events\JobFailed;
use App\Services\ReceivedCommentsInboxService;
use App\Services\Images\Detectors\ChainedSubjectDetector; use App\Services\Images\Detectors\ChainedSubjectDetector;
use App\Services\Images\Detectors\HeuristicSubjectDetector; use App\Services\Images\Detectors\HeuristicSubjectDetector;
use App\Services\Images\Detectors\NullSubjectDetector; use App\Services\Images\Detectors\NullSubjectDetector;
use App\Services\Images\Detectors\VisionSubjectDetector; use App\Services\Images\Detectors\VisionSubjectDetector;
use App\Services\Moderation\TogetherCommentSpamClassifier;
use App\Services\ReceivedCommentsInboxService;
use App\Services\Recommendations\VectorSimilarity\VectorAdapterFactory;
use App\Services\Recommendations\VectorSimilarity\VectorAdapterInterface;
use App\Services\Upload\Contracts\UploadDraftServiceInterface;
use App\Services\Upload\UploadDraftService;
use App\Services\Worlds\WorldService;
use App\Support\Http\TimedInertiaSsrGateway;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Http\Middleware\HandleCors;
use Illuminate\Http\Request;
use Illuminate\Queue\Events\JobFailed;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;
use Illuminate\View\Factory;
use Inertia\Ssr\Gateway;
use Klevze\ControlPanel\Framework\Core\Menu; use Klevze\ControlPanel\Framework\Core\Menu;
use Laravel\Cashier\Events\WebhookHandled; use Laravel\Cashier\Events\WebhookHandled;
use Laravel\Cashier\Events\WebhookReceived; use Laravel\Cashier\Events\WebhookReceived;
use App\Listeners\Academy\HandleAcademyStripeWebhook; use Meilisearch\Client;
use App\Listeners\Academy\HandleAcademyStripeWebhookHandled; use SocialiteProviders\Discord\DiscordExtendSocialite;
use SocialiteProviders\Manager\SocialiteWasCalled;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
{ {
@@ -58,14 +64,18 @@ class AppServiceProvider extends ServiceProvider
*/ */
public function register(): void public function register(): void
{ {
$this->app->bind(CommentSpamClassifier::class, function ($app) {
return $app->make(TogetherCommentSpamClassifier::class);
});
$this->app->bind( $this->app->bind(
\Inertia\Ssr\Gateway::class, Gateway::class,
\App\Support\Http\TimedInertiaSsrGateway::class, TimedInertiaSsrGateway::class,
); );
$this->app->singleton( $this->app->singleton(
\App\Services\Countries\CountryRemoteProviderInterface::class, CountryRemoteProviderInterface::class,
\App\Services\Countries\CountryRemoteProvider::class, CountryRemoteProvider::class,
); );
// Bind UploadDraftService interface to implementation // Bind UploadDraftService interface to implementation
@@ -75,14 +85,14 @@ class AppServiceProvider extends ServiceProvider
// Bind vector adapter interface for similarity system (resolves via factory) // Bind vector adapter interface for similarity system (resolves via factory)
$this->app->bind( $this->app->bind(
\App\Services\Recommendations\VectorSimilarity\VectorAdapterInterface::class, VectorAdapterInterface::class,
fn () => \App\Services\Recommendations\VectorSimilarity\VectorAdapterFactory::make(), fn () => VectorAdapterFactory::make(),
); );
// EGS: bind SpotlightEngineInterface to the concrete SpotlightEngine // EGS: bind SpotlightEngineInterface to the concrete SpotlightEngine
$this->app->singleton( $this->app->singleton(
\App\Services\EarlyGrowth\SpotlightEngineInterface::class, SpotlightEngineInterface::class,
\App\Services\EarlyGrowth\SpotlightEngine::class, SpotlightEngine::class,
); );
$this->app->singleton(SubjectDetectorInterface::class, function ($app) { $this->app->singleton(SubjectDetectorInterface::class, function ($app) {
@@ -97,7 +107,7 @@ class AppServiceProvider extends ServiceProvider
// with no HTTP timeout, so a slow/overloaded Meilisearch leaves PHP-FPM // with no HTTP timeout, so a slow/overloaded Meilisearch leaves PHP-FPM
// workers blocked forever in curl_exec(), draining the whole pool under // workers blocked forever in curl_exec(), draining the whole pool under
// a search traffic spike. Fail fast instead. // a search traffic spike. Fail fast instead.
$this->app->singleton(\Meilisearch\Client::class, function ($app) { $this->app->singleton(Client::class, function ($app) {
$config = $app['config']->get('scout.meilisearch'); $config = $app['config']->get('scout.meilisearch');
$httpClient = new \GuzzleHttp\Client([ $httpClient = new \GuzzleHttp\Client([
@@ -105,7 +115,7 @@ class AppServiceProvider extends ServiceProvider
'timeout' => 5, 'timeout' => 5,
]); ]);
return new \Meilisearch\Client($config['host'], $config['key'], $httpClient); return new Client($config['host'], $config['key'], $httpClient);
}); });
} }
@@ -119,12 +129,29 @@ class AppServiceProvider extends ServiceProvider
// Map the 'legacy' view namespace to resources/views/_legacy so all // Map the 'legacy' view namespace to resources/views/_legacy so all
// view('legacy::foo') and @include('legacy::foo') calls resolve correctly // view('legacy::foo') and @include('legacy::foo') calls resolve correctly
// after the folder was renamed from legacy/ to _legacy/. // after the folder was renamed from legacy/ to _legacy/.
View::addNamespace('legacy', resource_path('views/_legacy')); $legacyViewPath = resource_path('views/_legacy');
$registerLegacyViewNamespace = static function (Factory $view) use ($legacyViewPath): void {
$view->addNamespace('legacy', $legacyViewPath);
};
if ($this->app->resolved('view')) {
$registerLegacyViewNamespace($this->app->make('view'));
} else {
$this->app->afterResolving('view', $registerLegacyViewNamespace);
}
$exceptionRendererComponentsPath = base_path('vendor/laravel/framework/src/Illuminate/Foundation/resources/exceptions/renderer/components'); $exceptionRendererComponentsPath = base_path('vendor/laravel/framework/src/Illuminate/Foundation/resources/exceptions/renderer/components');
$registerExceptionRendererComponents = static function ($compiler) use ($exceptionRendererComponentsPath): void {
if (is_dir($exceptionRendererComponentsPath)) { if (is_dir($exceptionRendererComponentsPath)) {
Blade::anonymousComponentNamespace($exceptionRendererComponentsPath, 'laravel-exceptions-renderer'); $compiler->anonymousComponentNamespace($exceptionRendererComponentsPath, 'laravel-exceptions-renderer');
}
};
if ($this->app->resolved('blade.compiler')) {
$registerExceptionRendererComponents($this->app->make('blade.compiler'));
} else {
$this->app->afterResolving('blade.compiler', $registerExceptionRendererComponents);
} }
$this->configureAuthRateLimiters(); $this->configureAuthRateLimiters();
@@ -140,45 +167,35 @@ class AppServiceProvider extends ServiceProvider
$this->configureSettingsRateLimiters(); $this->configureSettingsRateLimiters();
$this->configureMailFailureLogging(); $this->configureMailFailureLogging();
ArtworkAward::observe(ArtworkAwardObserver::class);
ArtworkMedal::observe(ArtworkAwardObserver::class);
Artwork::observe(ArtworkObserver::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);
HomepageAnnouncement::observe(HomepageAnnouncementObserver::class);
// ── OAuth / SocialiteProviders ────────────────────────────────────── // ── OAuth / SocialiteProviders ──────────────────────────────────────
Event::listen( Event::listen(
\SocialiteProviders\Manager\SocialiteWasCalled::class, SocialiteWasCalled::class,
\SocialiteProviders\Discord\DiscordExtendSocialite::class, DiscordExtendSocialite::class,
); );
// Apple provider removed — no listener registered // Apple provider removed — no listener registered
// ── Posts / Feed System Events ────────────────────────────────────── // ── Posts / Feed System Events ──────────────────────────────────────
Event::listen( Event::listen(
\App\Events\Posts\ArtworkShared::class, ArtworkShared::class,
\App\Listeners\Posts\SendArtworkSharedNotification::class, SendArtworkSharedNotification::class,
); );
Event::listen( Event::listen(
\App\Events\Posts\PostCommented::class, PostCommented::class,
\App\Listeners\Posts\SendPostCommentedNotification::class, SendPostCommentedNotification::class,
); );
Event::listen( Event::listen(
\App\Events\Posts\PostCommented::class, PostCommented::class,
\App\Listeners\Posts\AwardXpForPostCommented::class, AwardXpForPostCommented::class,
); );
Event::listen( Event::listen(
\App\Events\Achievements\AchievementCheckRequested::class, AchievementCheckRequested::class,
\App\Listeners\Achievements\CheckUserAchievements::class, CheckUserAchievements::class,
); );
Event::listen( Event::listen(
\App\Events\Achievements\UserXpUpdated::class, UserXpUpdated::class,
\App\Listeners\Achievements\CheckUserAchievements::class, CheckUserAchievements::class,
); );
Event::listen( Event::listen(
WebhookReceived::class, WebhookReceived::class,
@@ -190,7 +207,8 @@ class AppServiceProvider extends ServiceProvider
); );
// Provide toolbar counts and user info to layout views (port of legacy toolbar logic) // Provide toolbar counts and user info to layout views (port of legacy toolbar logic)
View::composer(['layouts.nova', 'layouts.nova.*'], function ($view) { $registerNovaLayoutComposer = function (Factory $factory): void {
$factory->composer(['layouts.nova', 'layouts.nova.*'], function ($view) {
$uploadCount = $favCount = $msgCount = $noticeCount = $receivedCommentsCount = 0; $uploadCount = $favCount = $msgCount = $noticeCount = $receivedCommentsCount = 0;
$avatarHash = null; $avatarHash = null;
$displayName = null; $displayName = null;
@@ -198,7 +216,7 @@ class AppServiceProvider extends ServiceProvider
$toolbarContentTypes = collect(); $toolbarContentTypes = collect();
$toolbarActiveCampaign = null; $toolbarActiveCampaign = null;
$request = request(); $request = request();
$canReadSessionAuth = $request instanceof \Illuminate\Http\Request $canReadSessionAuth = $request instanceof Request
&& $request->hasSession() && $request->hasSession()
&& $request->attributes->get('skinbase.session_skipped') !== true; && $request->attributes->get('skinbase.session_skipped') !== true;
$authUser = $canReadSessionAuth ? Auth::user() : null; $authUser = $canReadSessionAuth ? Auth::user() : null;
@@ -301,19 +319,26 @@ class AppServiceProvider extends ServiceProvider
$view->with(compact('userId', 'uploadCount', 'favCount', 'msgCount', 'noticeCount', 'receivedCommentsCount', 'avatarHash', 'displayName', 'toolbarContentTypes', 'toolbarActiveCampaign')); $view->with(compact('userId', 'uploadCount', 'favCount', 'msgCount', 'noticeCount', 'receivedCommentsCount', 'avatarHash', 'displayName', 'toolbarContentTypes', 'toolbarActiveCampaign'));
}); });
};
if ($this->app->resolved('view')) {
$registerNovaLayoutComposer($this->app->make('view'));
} else {
$this->app->afterResolving('view', $registerNovaLayoutComposer);
}
// Replace the framework HandleCors with our ConditionalCors so the // Replace the framework HandleCors with our ConditionalCors so the
// CP_ENABLE_CORS / config('cors.paths') toggle takes effect. // CP_ENABLE_CORS / config('cors.paths') toggle takes effect.
try { try {
$middlewareConfig = $this->app->make(\Illuminate\Foundation\Configuration\Middleware::class); $middlewareConfig = $this->app->make(Middleware::class);
$middlewareConfig->replace( $middlewareConfig->replace(
\Illuminate\Http\Middleware\HandleCors::class, HandleCors::class,
\App\Http\Middleware\ConditionalCors::class ConditionalCors::class
); );
} catch (\Throwable $_) { } catch (\Throwable $_) {
// Fallback: push to kernel if replace isn't available in this app instance // Fallback: push to kernel if replace isn't available in this app instance
$this->app->make(\Illuminate\Contracts\Http\Kernel::class) $this->app->make(Kernel::class)
->pushMiddleware(\App\Http\Middleware\ConditionalCors::class); ->pushMiddleware(ConditionalCors::class);
} }
} }