diff --git a/.cursorrules b/.cursorrules index 1dfc100a..23858983 100644 --- a/.cursorrules +++ b/.cursorrules @@ -8,7 +8,6 @@ Project specificities: - Tests are written using Pest. - This is a blog made with Laravel. - It generates money through affiliate links. -- Visits are tracked in the backend to bypass ad blockers using Pirsch Analytics' API. Code style: - Take inspiration from the codebase. diff --git a/.env.example b/.env.example index 9cc7476f..4bae1cb4 100644 --- a/.env.example +++ b/.env.example @@ -71,7 +71,7 @@ CLOUDFLARE_API_TOKEN= GITHUB_CLIENT_ID= GITHUB_CLIENT_SECRET= -GITHUB_REDIRECT=https://benjamincrozat.test/auth/callback +GITHUB_REDIRECT= OPENAI_API_KEY= diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bf17c354..4c74ea87 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,9 +11,7 @@ jobs: CLOUDFLARE_IMAGES_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_IMAGES_ACCOUNT_ID }} CLOUDFLARE_IMAGES_ACCOUNT_HASH: ${{ secrets.CLOUDFLARE_IMAGES_ACCOUNT_HASH }} CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - PIRSCH_ACCESS_KEY: ${{ secrets.PIRSCH_ACCESS_KEY }} - PIRSCH_CLIENT_ID: ${{ secrets.PIRSCH_CLIENT_ID }} - PIRSCH_CLIENT_SECRET: ${{ secrets.PIRSCH_CLIENT_SECRET }} + steps: - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/README.md b/README.md index bca2e131..60c828b5 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,47 @@ - + -# Benjamin Crozat's blog +# Carlos Santos' Blog -This is the source code for my revenue-generating 20K monthly visitors [blog](https://benjamincrozat.com). It's **100% not vibe coded** BTW. +This is the source code for my technical blog at [carlossantosdev.com](https://carlossantosdev.com). Here I share deep insights about **PHP**, **Laravel Ecosystem**, **AI**, and **Software Architecture**, documenting my journey and experiences as a developer. -**Feel free to steal whatever you need.** But first, why don't you follow me on [X](https://x.com/benjamincrozat)? +**Feel free to explore and learn from the code.** Don't forget to follow me on [X](https://x.com/carlossantosdev)! -## What to expect +## What this blog focuses on + +This blog is dedicated to sharing expertise in: + +- **PHP & Laravel Ecosystem:** Deep dives into modern PHP development, Laravel framework features, packages, and best practices +- **AI Integration:** Exploring how to integrate AI technologies into web applications and development workflows +- **Software Architecture:** Patterns, principles, and strategies for building scalable and maintainable applications +- **Workshop documentation:** Content and resources from workshops I conduct on PHP, Laravel, and AI topics +- **Meetup insights:** Sharing knowledge from tech meetups and community events +- **Development journey:** My experiences and learnings as a PHP developer specializing in Laravel and AI + +## What to expect from the codebase -- **Independently built and maintained:** - This codebase evolves organically as time allows and needs arise. - - **Production-ready Laravel code:** - Explore clean, fast, and scalable code with Actions, Jobs, Policies, and more, as well as a thoughtfully organized structure. + Explore clean, fast, and scalable code with Actions, Jobs, Policies, and more, with a thoughtfully organized structure. + +- **Monetization through affiliate links:** + See how affiliate links are implemented for revenue generation. + +- **Modern admin interface:** + Powered by Filament v4 for content management and administration. + +- **Interactive features:** + Livewire-powered components for enhanced user experience. -- **Monetization & analytics:** - See how I implemented affiliate links, ad-blocker-resistant analytics, and outbound tracking for real revenue. +- **Automated workflows:** + Background jobs and scheduled tasks for content automation. -- **Beautiful admin (WIP) & user experience:** - Discover how Filament v4 powers my admin panel and how Livewire brings interactivity to my custom-made comments system and forms. +- **Image management:** + Cloudflare Images integration for efficient image handling. -- **Automation & background jobs:** - See how I automate content, recommendations, and more using AI, queues (managed by Horizon), and scheduled tasks. +- **Comprehensive testing:** + Test suite written using Pest to ensure code reliability and maintainability. -- **Cloudflare Images integration:** - Because I didn't want to develop my own image upload flow from scratch. +--- -- **Comprehensive test suite:** - 130+ tests written using Pest show how to keep features reliable and code maintainable. +## Attribution - +This project is a fork of [Benjamin Crozat's blog](https://github.com/benjamincrozat/blog-v5), but simplified for my own use. diff --git a/_docs/images/wolf_bg.png b/_docs/images/wolf_bg.png new file mode 100644 index 00000000..29d24d4e Binary files /dev/null and b/_docs/images/wolf_bg.png differ diff --git a/app/Actions/CreatePostForLink.php b/app/Actions/CreatePostForLink.php deleted file mode 100644 index fc0f08c5..00000000 --- a/app/Actions/CreatePostForLink.php +++ /dev/null @@ -1,90 +0,0 @@ -url)->throw(); - - app(Readability::class)->parse( - $response->body() - ); - - $response = OpenAI::chat()->create([ - 'model' => 'gpt-4.1', - 'messages' => [ - [ - 'role' => 'system', - 'content' => view('components.prompts.create-post-for-link.system')->render(), - ], - [ - 'role' => 'user', - 'content' => view('components.prompts.create-post-for-link.user', [ - 'url' => $link->url, - 'author' => app(Readability::class)->getAuthor(), - 'title' => app(Readability::class)->getTitle(), - 'content' => app(Readability::class)->getContent(), - 'notes' => $link->notes, - ])->render(), - ], - ], - 'response_format' => [ - 'type' => 'json_schema', - 'json_schema' => [ - 'name' => 'post', - 'schema' => [ - 'type' => 'object', - 'properties' => [ - 'title' => [ - 'type' => 'string', - 'description' => 'The title of the post.', - ], - 'content' => [ - 'type' => 'string', - 'description' => 'The content of the post.', - ], - 'description' => [ - 'type' => 'string', - 'description' => 'The meta description of the post. 160 characters or less.', - ], - ], - 'required' => [ - 'title', - 'content', - 'description', - ], - 'additionalProperties' => false, - ], - 'strict' => true, - ], - ], - ]); - - $json = json_decode($response->choices[0]->message->content); - - $post = Post::query()->create([ - 'user_id' => 1, - 'title' => $json->title, - 'content' => $json->content, - 'description' => $json->description, - 'published_at' => $link->is_approved ?? now(), - ]); - - $link->update([ - 'post_id' => $post->id, - ]); - - RecommendPosts::dispatch($post); - - return $post; - } -} diff --git a/app/Actions/FetchPostSessions.php b/app/Actions/FetchPostSessions.php deleted file mode 100644 index 68867e95..00000000 --- a/app/Actions/FetchPostSessions.php +++ /dev/null @@ -1,60 +0,0 @@ -http - ->post('https://api.pirsch.io/api/v1/token', [ - 'client_id' => $this->pirschClientId, - 'client_secret' => $this->pirschClientSecret, - ]) - ->throw() - ->json('access_token'); - - $from ??= now()->subDays(7); - - $to ??= now(); - - $this->http - ->withToken($pirschAccessToken) - ->get('https://api.pirsch.io/api/v1/statistics/page', [ - 'id' => config('services.pirsch.domain_id'), - 'from' => $from->toDateString(), - 'to' => $to->toDateString(), - 'tz' => 'UTC', - ]) - ->throw() - ->collect() - ->map(function (array $item) { - $item['path'] = explode('#', $item['path'])[0]; - - return $item; - }) - ->groupBy('path') - ->each(function (Collection $items) { - Post::query() - ->where('slug', trim($items[0]['path'], '/')) - ->update(['sessions_count' => $items->sum('sessions')]); - }); - } -} diff --git a/app/Actions/RecommendPosts.php b/app/Actions/RecommendPosts.php index b4d44ef5..aa382f36 100644 --- a/app/Actions/RecommendPosts.php +++ b/app/Actions/RecommendPosts.php @@ -1,5 +1,7 @@ published() @@ -26,7 +28,7 @@ public function recommend(Post $post) : void ], [ 'role' => 'user', - 'content' => view('components.prompts.get-recommended-posts.user', compact('post', 'candidates'))->render(), + 'content' => view('components.prompts.get-recommended-posts.user', ['post' => $post, 'candidates' => $candidates])->render(), ], ], 'response_format' => [ @@ -74,7 +76,7 @@ public function recommend(Post $post) : void ]); $post->update([ - 'recommendations' => json_decode($response->choices[0]->message->content)->recommendations, + 'recommendations' => json_decode((string) $response->choices[0]->message->content)->recommendations, ]); } } diff --git a/app/Actions/RefreshUserData.php b/app/Actions/RefreshUserData.php index d64adef7..3e2732ee 100644 --- a/app/Actions/RefreshUserData.php +++ b/app/Actions/RefreshUserData.php @@ -1,16 +1,18 @@ api('user') diff --git a/app/Actions/TrackEvent.php b/app/Actions/TrackEvent.php deleted file mode 100644 index 70a9dad7..00000000 --- a/app/Actions/TrackEvent.php +++ /dev/null @@ -1,24 +0,0 @@ -retry(3) - ->post('https://api.pirsch.io/api/v1/event', [ - 'event_name' => 'Clicked on short URL', - 'event_meta' => $meta, - 'url' => $url, - 'ip' => $ip, - 'user_agent' => $userAgent, - 'accept_language' => $acceptLanguage, - 'referrer' => $referrer, - ]) - ->throw(); - } -} diff --git a/app/Actions/TrackVisit.php b/app/Actions/TrackVisit.php deleted file mode 100644 index bca1a650..00000000 --- a/app/Actions/TrackVisit.php +++ /dev/null @@ -1,25 +0,0 @@ -retry(3) - ->post('https://api.pirsch.io/api/v1/hit', [ - 'url' => $url, - 'ip' => $ip, - 'user_agent' => $userAgent, - 'accept_language' => $acceptLanguage, - 'referrer' => $referrer, - ]) - ->throw(); - } -} diff --git a/app/Console/Commands/GenerateRecommendationsCommand.php b/app/Console/Commands/GenerateRecommendationsCommand.php index 028579d9..04335696 100644 --- a/app/Console/Commands/GenerateRecommendationsCommand.php +++ b/app/Console/Commands/GenerateRecommendationsCommand.php @@ -1,9 +1,11 @@ argument('slug')) { $post = Post::query()->where('slug', $slug)->firstOrFail(); @@ -27,7 +29,7 @@ public function handle() : void Post::query() ->published() ->cursor() - ->each(function (Post $post) { + ->each(function (Post $post): void { RecommendPosts::dispatch($post); $this->info("Queued recommendation generation for \"$post->title\"…"); diff --git a/app/Console/Commands/GenerateSitemapCommand.php b/app/Console/Commands/GenerateSitemapCommand.php index c2ac183d..0ba718f8 100644 --- a/app/Console/Commands/GenerateSitemapCommand.php +++ b/app/Console/Commands/GenerateSitemapCommand.php @@ -1,12 +1,14 @@ add(route('home')); @@ -26,19 +28,17 @@ public function handle() : void Post::query() ->published() ->cursor() - ->each(fn (Post $post) => $sitemap->add(route('posts.show', $post))); + ->each(fn (Post $post): Sitemap => $sitemap->add(route('posts.show', $post))); User::query() ->cursor() - ->each(fn (User $user) => $sitemap->add(route('authors.show', $user))); + ->each(fn (User $user): Sitemap => $sitemap->add(route('authors.show', $user))); $sitemap->add(route('categories.index')); Category::query() ->cursor() - ->each(fn (Category $category) => $sitemap->add(route('categories.show', $category))); - - $sitemap->add(route('links.index')); + ->each(fn (Category $category): Sitemap => $sitemap->add(route('categories.show', $category))); $sitemap->writeToFile($path = public_path('sitemap.xml')); diff --git a/app/Console/Commands/RefreshUserDataCommand.php b/app/Console/Commands/RefreshUserDataCommand.php index acfdc03c..f6e21477 100644 --- a/app/Console/Commands/RefreshUserDataCommand.php +++ b/app/Console/Commands/RefreshUserDataCommand.php @@ -1,9 +1,11 @@ argument('id')) { RefreshUserData::dispatch( @@ -38,6 +40,6 @@ public function handle() : void ->delay(now()->addSeconds($index * 5)) ); - $this->info($users->count() . ' user(s) have been queued for a refresh.'); + $this->info($users->count().' user(s) have been queued for a refresh.'); } } diff --git a/app/Console/Commands/SyncPostSessionsCommand.php b/app/Console/Commands/SyncPostSessionsCommand.php index 153c39c6..d9b8d860 100644 --- a/app/Console/Commands/SyncPostSessionsCommand.php +++ b/app/Console/Commands/SyncPostSessionsCommand.php @@ -1,19 +1,20 @@ fetch(); diff --git a/app/Console/Commands/SyncVisitorsCommand.php b/app/Console/Commands/SyncVisitorsCommand.php deleted file mode 100644 index 463468e9..00000000 --- a/app/Console/Commands/SyncVisitorsCommand.php +++ /dev/null @@ -1,77 +0,0 @@ -info('Fetching fresh analytics data…'); - - $data = $this->fetch(); - - Metric::query()->create([ - 'key' => 'platform_desktop', - 'value' => ($data['relative_platform_desktop'] ?? 0) * 100, - ]); - - Metric::query()->create([ - 'key' => 'sessions', - 'value' => $data['sessions'] ?? 0, - ]); - - Metric::query()->create([ - 'key' => 'views', - 'value' => $data['views'] ?? 0, - ]); - - Metric::query()->create([ - 'key' => 'visitors', - 'value' => $data['visitors'] ?? 0, - ]); - - $this->info('Fresh analytics data has been fetched.'); - } - - protected function fetch() : array - { - $accessToken = Http::post('https://api.pirsch.io/api/v1/token', [ - 'client_id' => config('services.pirsch.client_id'), - 'client_secret' => config('services.pirsch.client_secret'), - ]) - ->throw() - ->json('access_token'); - - $overview = Http::withToken($accessToken) - ->get('https://api.pirsch.io/api/v1/statistics/total', [ - 'id' => config('services.pirsch.domain_id'), - 'from' => now()->subDays(31)->toDateString(), - 'to' => now()->subDay()->toDateString(), - 'timezone' => 'Europe/Paris', - ]) - ->throw() - ->json(); - - $platform = Http::withToken($accessToken) - ->get('https://api.pirsch.io/api/v1/statistics/platform', [ - 'id' => config('services.pirsch.domain_id'), - 'from' => now()->subDays(31)->toDateString(), - 'to' => now()->subDay()->toDateString(), - 'timezone' => 'Europe/Paris', - ]) - ->throw() - ->json(); - - return array_merge($overview, $platform); - } -} diff --git a/app/Filament/Resources/CategoryResource.php b/app/Filament/Resources/CategoryResource.php index ea61968f..320f9702 100644 --- a/app/Filament/Resources/CategoryResource.php +++ b/app/Filament/Resources/CategoryResource.php @@ -1,39 +1,45 @@ components([ @@ -41,7 +47,7 @@ public static function form(Schema $schema) : Schema ->required() ->maxLength(255) ->live(onBlur: true) - ->afterStateUpdated(fn (Set $set, ?string $state) => $set('slug', Str::slug($state))), + ->afterStateUpdated(fn (Set $set, ?string $state): mixed => $set('slug', Str::slug($state))), TextInput::make('slug') ->required() @@ -52,7 +58,8 @@ public static function form(Schema $schema) : Schema ]); } - public static function table(Table $table) : Table + #[Override] + public static function table(Table $table): Table { return $table ->defaultSort('id', 'desc') @@ -88,7 +95,7 @@ public static function table(Table $table) : Table ]); } - public static function getPages() : array + public static function getPages(): array { return [ 'index' => ListCategories::route('/'), @@ -97,19 +104,21 @@ public static function getPages() : array ]; } - public static function getRelations() : array + #[Override] + public static function getRelations(): array { return [ PostsRelationManager::class, ]; } - public static function getGloballySearchableAttributes() : array + public static function getGloballySearchableAttributes(): array { return ['name', 'slug', 'content']; } - public static function getEloquentQuery() : Builder + #[Override] + public static function getEloquentQuery(): Builder { return parent::getEloquentQuery() ->withCount('posts'); diff --git a/app/Filament/Resources/CategoryResource/Pages/CreateCategory.php b/app/Filament/Resources/CategoryResource/Pages/CreateCategory.php index b5a077a5..fae21e32 100644 --- a/app/Filament/Resources/CategoryResource/Pages/CreateCategory.php +++ b/app/Filament/Resources/CategoryResource/Pages/CreateCategory.php @@ -1,9 +1,11 @@ defaultSort(null); } diff --git a/app/Filament/Resources/CommentResource.php b/app/Filament/Resources/CommentResource.php index 64a88517..1465772d 100644 --- a/app/Filament/Resources/CommentResource.php +++ b/app/Filament/Resources/CommentResource.php @@ -1,37 +1,43 @@ components([ @@ -74,7 +80,8 @@ public static function form(Schema $schema) : Schema ->columns(12); } - public static function table(Table $table) : Table + #[Override] + public static function table(Table $table): Table { return $table ->defaultSort('id', 'desc') @@ -123,14 +130,15 @@ public static function table(Table $table) : Table ]); } - public static function getRelations() : array + #[Override] + public static function getRelations(): array { return [ // ]; } - public static function getPages() : array + public static function getPages(): array { return [ 'index' => ListComments::route('/'), @@ -139,17 +147,17 @@ public static function getPages() : array ]; } - public static function getGloballySearchableAttributes() : array + public static function getGloballySearchableAttributes(): array { return ['user.name', 'content']; } - public static function getGlobalSearchResultTitle(Model $record) : string + public static function getGlobalSearchResultTitle(Model $record): string { - return strlen($record->content) > 50 ? substr($record->content, 0, 50) . '…' : $record->content; + return mb_strlen($record->content) > 50 ? mb_substr($record->content, 0, 50).'…' : $record->content; } - public static function getGlobalSearchResultDetails(Model $record) : array + public static function getGlobalSearchResultDetails(Model $record): array { return ['Author' => $record->user->name]; } diff --git a/app/Filament/Resources/CommentResource/Pages/CreateComment.php b/app/Filament/Resources/CommentResource/Pages/CreateComment.php index 26dd3501..ab92f605 100644 --- a/app/Filament/Resources/CommentResource/Pages/CreateComment.php +++ b/app/Filament/Resources/CommentResource/Pages/CreateComment.php @@ -1,9 +1,11 @@ components([ - Select::make('user_id') - ->relationship('user', 'name') - ->required() - ->searchable() - ->columnSpanFull() - ->label('Sender'), - - Select::make('post_id') - ->relationship('post', 'title') - ->searchable() - ->columnSpanFull() - ->label('Post') - ->helperText("Any link can be associated with a post. Usually, they're AI-generated."), - - TextInput::make('url') - ->required() - ->url() - ->maxLength(255) - ->label('URL') - ->columnSpanFull(), - - TextInput::make('image_url') - ->url() - ->label('Image URL') - ->columnSpanFull(), - - TextInput::make('author') - ->required() - ->maxLength(255) - ->columnSpanFull(), - - TextInput::make('title') - ->required() - ->maxLength(255) - ->columnSpanFull(), - - Textarea::make('description') - ->maxLength(65535) - ->columnSpanFull(), - - Textarea::make('notes') - ->maxLength(65535) - ->columnSpanFull(), - - DateTimePicker::make('is_approved') - ->timezone('Europe/Paris') - ->native(false) - ->label('Approved At'), - - DateTimePicker::make('is_declined') - ->timezone('Europe/Paris') - ->native(false) - ->label('Declined At'), - ]); - } - - public static function table(Table $table) : Table - { - return $table - ->defaultSort('id', 'desc') - ->columns([ - ImageColumn::make('image_url') - ->imageWidth(107) - ->imageHeight(80) - ->label('Image'), - - TextColumn::make('title') - ->searchable() - ->limit(50), - - TextColumn::make('url') - ->url(fn (Link $record) => $record->url, shouldOpenInNewTab: true) - ->searchable() - ->limit(40) - ->label('URL'), - - TextColumn::make('user.name') - ->searchable() - ->label('Sender'), - - TextColumn::make('status') - ->badge() - ->color(fn (string $state) : string => match ($state) { - 'Approved' => 'success', - 'Declined' => 'danger', - default => 'gray', - }) - ->getStateUsing(function (Model $record) { - if ($record->is_approved) { - return 'Approved'; - } - - if ($record->is_declined) { - return 'Declined'; - } - - return 'Pending'; - }) - ->label('Status'), - - TextColumn::make('created_at') - ->dateTime() - ->sortable() - ->label('Submitted Date'), - ]) - ->filters([ - SelectFilter::make('status') - ->options([ - 'approved' => 'Approved', - 'declined' => 'Declined', - 'pending' => 'Pending', - ]) - ->query(fn (Builder $query, array $data) => match ($data['value']) { - 'approved' => $query->approved(), - 'declined' => $query->declined(), - 'pending' => $query->pending(), - default => $query, - }) - ->default('pending'), - ]) - ->recordActions([ - ActionGroup::make([ - Action::make('open') - ->url(fn (Link $record) => $record->url, shouldOpenInNewTab: true) - ->icon('heroicon-o-arrow-top-right-on-square'), - - Action::make('approve') - ->schema([ - Textarea::make('notes') - ->helperText('These notes will help when generating the small companion article designed to entice readers to click.'), - ]) - ->modalHeading('Approve Link') - ->modalFooterActions([ - Action::make('regenerate') - ->action(function (Link $record, array $data) { - $record->approve(); - - Notification::make() - ->title('The link has been approved.') - ->success() - ->send(); - }) - ->color('gray') - ->label('Approve without post'), - - Action::make('approve') - ->action(function (Link $record, array $data) { - $record->approve($data['notes']); - - if ($record->post_id) { - Notification::make() - ->title('The link has been approved.') - ->success() - ->send(); - } else { - CreatePostForLink::dispatch($record); - - Notification::make() - ->title('The link has been approved and a post is being created.') - ->success() - ->send(); - } - }) - ->label('Approve and generate post'), - ]) - ->hidden(fn (Link $record) => $record->isApproved()) - ->color('success') - ->icon('heroicon-o-check') - ->label('Approve'), - - Action::make('decline') - ->action(fn (Link $record) => $record->decline()) - ->hidden(fn (Link $record) => $record->isDeclined()) - ->color('danger') - ->icon('heroicon-o-x-circle'), - - Action::make('generate_post') - ->action(function (Link $record, array $data) { - $record->approve($data['notes']); - - CreatePostForLink::dispatch($record); - - Notification::make() - ->title('A new post is being regenerated.') - ->success() - ->send(); - }) - ->schema([ - Textarea::make('notes') - ->helperText('These notes will help when generating the small companion article designed to entice readers to click.'), - ]) - ->modalHeading('Generate Post') - ->modalSubmitActionLabel('Generate') - ->icon('heroicon-o-arrow-path'), - - Action::make('Put back in pending') - ->action(fn (Link $record) => $record->update([ - 'is_approved' => null, - 'is_declined' => null, - ])) - ->icon('heroicon-o-queue-list') - ->hidden(fn (Link $record) => is_null($record->is_approved) && is_null($record->is_declined)), - - EditAction::make() - ->icon('heroicon-o-pencil'), - - DeleteAction::make() - ->icon('heroicon-o-trash'), - ]), - ]) - ->toolbarActions([ - BulkActionGroup::make([ - BulkAction::make('approve') - ->action(fn (Collection $records) => $records->each->approve()) - ->color('success') - ->icon('heroicon-o-check'), - - BulkAction::make('decline') - ->action(fn (Collection $records) => $records->each->decline()) - ->color('danger') - ->icon('heroicon-o-x-circle'), - - BulkAction::make('Put back in pending') - ->action(fn (Collection $records) => $records->each->update([ - 'is_approved' => null, - 'is_declined' => null, - ])) - ->icon('heroicon-o-queue-list'), - - DeleteBulkAction::make(), - ]), - ]); - } - - public static function getPages() : array - { - return [ - 'index' => ListLinks::route('/'), - 'create' => CreateLink::route('/create'), - 'edit' => EditLink::route('/{record}/edit'), - ]; - } - - public static function getGloballySearchableAttributes() : array - { - return ['user.name', 'url', 'title', 'description']; - } - - public static function getGlobalSearchResultDetails($record) : array - { - return [ - 'From' => $record->user->name, - 'URL' => $record->url, - ]; - } -} diff --git a/app/Filament/Resources/LinkResource/Pages/CreateLink.php b/app/Filament/Resources/LinkResource/Pages/CreateLink.php deleted file mode 100644 index 387d29eb..00000000 --- a/app/Filament/Resources/LinkResource/Pages/CreateLink.php +++ /dev/null @@ -1,11 +0,0 @@ -components([ @@ -52,7 +58,8 @@ public static function form(Schema $schema) : Schema ]); } - public static function table(Table $table) : Table + #[Override] + public static function table(Table $table): Table { return $table ->defaultSort('id', 'desc') @@ -88,7 +95,7 @@ public static function table(Table $table) : Table ]); } - public static function getPages() : array + public static function getPages(): array { return [ 'index' => ListMetrics::route('/'), @@ -96,17 +103,17 @@ public static function getPages() : array ]; } - public static function getGloballySearchableAttributes() : array + public static function getGloballySearchableAttributes(): array { return ['key', 'value']; } - public static function getGlobalSearchResultDetails(Model $record) : array + public static function getGlobalSearchResultDetails(Model $record): array { return ['Value' => $record->value]; } - public static function getRecordTitle(?Model $record) : string|Htmlable|null + public static function getRecordTitle(?Model $record): string|Htmlable|null { return "\"{$record->key}\" metric"; } diff --git a/app/Filament/Resources/MetricResource/Pages/ListMetrics.php b/app/Filament/Resources/MetricResource/Pages/ListMetrics.php index e396cb7a..aeabd4e9 100644 --- a/app/Filament/Resources/MetricResource/Pages/ListMetrics.php +++ b/app/Filament/Resources/MetricResource/Pages/ListMetrics.php @@ -1,16 +1,18 @@ components([ @@ -69,7 +74,7 @@ public static function form(Schema $schema) : Schema ->required() ->maxLength(255) ->live(onBlur: true) - ->afterStateUpdated(function (Get $get, Set $set, ?string $old, ?string $state) { + ->afterStateUpdated(function (Get $get, Set $set, ?string $old, ?string $state): void { // Only update slug if it hasn't been manually customized. if (($get('slug') ?? '') !== Str::slug($old)) { return; @@ -147,7 +152,7 @@ public static function form(Schema $schema) : Schema ->placeholder(now()) ->defaultFocusedDate(now()) ->reactive() - ->afterStateUpdated(function (DateTimePicker $component, $state) { + ->afterStateUpdated(function (DateTimePicker $component, $state): void { if (blank($state)) { return; } @@ -168,7 +173,7 @@ public static function form(Schema $schema) : Schema ->defaultFocusedDate(now()) ->closeOnDateSelection() ->reactive() - ->afterStateUpdated(function (DateTimePicker $component, $state) { + ->afterStateUpdated(function (DateTimePicker $component, $state): void { if (blank($state)) { return; } @@ -189,7 +194,8 @@ public static function form(Schema $schema) : Schema ->columns(12); } - public static function table(Table $table) : Table + #[Override] + public static function table(Table $table): Table { return $table ->defaultSort('id', 'desc') @@ -243,23 +249,11 @@ public static function table(Table $table) : Table ->trueLabel('With image') ->falseLabel('Without image') ->queries( - blank: fn (Builder $query) => $query, true: fn (Builder $query) => $query->whereNotNull('image_path'), false: fn (Builder $query) => $query->whereNull('image_path'), + blank: fn (Builder $query): Builder => $query, ), - SelectFilter::make('link_association') - ->label('Link association') - ->options([ - 'with_link' => 'With link', - 'without_link' => 'Without link', - ]) - ->query(fn (Builder $query, array $data) => match ($data['value']) { - 'with_link' => $query->whereHas('link'), - 'without_link' => $query->whereDoesntHave('link'), - default => $query, - }), - TernaryFilter::make('published_at') ->nullable() ->label('Published status') @@ -267,9 +261,9 @@ public static function table(Table $table) : Table ->trueLabel('Published') ->falseLabel('Draft') ->queries( - blank: fn (Builder $query) => $query, true: fn (Builder $query) => $query->whereNotNull('published_at'), false: fn (Builder $query) => $query->whereNull('published_at'), + blank: fn (Builder $query): Builder => $query, ), TernaryFilter::make('updated_stale') @@ -279,8 +273,7 @@ public static function table(Table $table) : Table ->trueLabel('Yes') ->falseLabel('No') ->queries( - blank: fn (Builder $query) => $query, - true: fn (Builder $query) => $query->where(function (Builder $query) { + true: fn (Builder $query) => $query->where(function (Builder $query): void { $oneYearAgo = now()->subYear(); $query @@ -291,7 +284,7 @@ public static function table(Table $table) : Table ->whereDate('published_at', '<=', $oneYearAgo) ); }), - false: fn (Builder $query) => $query->where(function (Builder $query) { + false: fn (Builder $query) => $query->where(function (Builder $query): void { $oneYearAgo = now()->subYear(); $query->where( @@ -304,6 +297,7 @@ public static function table(Table $table) : Table ->whereDate('published_at', '>', $oneYearAgo) ); }), + blank: fn (Builder $query): Builder => $query, ), TrashedFilter::make(), @@ -318,24 +312,24 @@ public static function table(Table $table) : Table TableAction::make('copy_url') ->label('Copy URL') ->icon('heroicon-o-link') - ->alpineClickHandler(fn (Post $record) => 'window.navigator.clipboard.writeText(' . Js::from(route('posts.show', $record)) . ')'), + ->alpineClickHandler(fn (Post $record): string => 'window.navigator.clipboard.writeText('.Js::from(route('posts.show', $record)).')'), TableAction::make('copy') ->label('Copy as Markdown') ->icon('heroicon-o-clipboard-document') - ->alpineClickHandler(fn (Post $record) => 'window.navigator.clipboard.writeText(' . Js::from($record->toMarkdown()) . ')'), + ->alpineClickHandler(fn (Post $record): string => 'window.navigator.clipboard.writeText('.Js::from($record->toMarkdown()).')'), Action::make('search_console') ->label('Check in GSC') ->icon('heroicon-o-chart-bar') - ->url(function (Post $record) { - $domain = preg_replace('/https?:\/\//', '', config('app.url')); + ->url(function (Post $record): string { + $domain = preg_replace('/https?:\/\//', '', (string) config('app.url')); - return "https://search.google.com/search-console/performance/search-analytics?resource_id=sc-domain%3A$domain&breakdown=query&page=!" . rawurlencode(route('posts.show', $record)); + return "https://search.google.com/search-console/performance/search-analytics?resource_id=sc-domain%3A$domain&breakdown=query&page=!".rawurlencode(route('posts.show', $record)); }, shouldOpenInNewTab: true), Action::make('recommendations') - ->action(function (Post $record) { + ->action(function (Post $record): void { RecommendPosts::dispatch($record); Notification::make() @@ -365,7 +359,7 @@ public static function table(Table $table) : Table ]); } - public static function getPages() : array + public static function getPages(): array { return [ 'index' => ListPosts::route('/'), @@ -375,17 +369,17 @@ public static function getPages() : array ]; } - public static function getGloballySearchableAttributes() : array + public static function getGloballySearchableAttributes(): array { return ['user.name', 'title', 'serp_title', 'slug', 'content', 'description', 'canonical_url']; } - public static function getGlobalSearchResultDetails(Model $record) : array + public static function getGlobalSearchResultDetails(Model $record): array { return ['Author' => $record->user->name]; } - public static function getRecordSubNavigation(Page $page) : array + public static function getRecordSubNavigation(Page $page): array { return $page->generateNavigationItems([ EditPost::class, @@ -393,12 +387,12 @@ public static function getRecordSubNavigation(Page $page) : array ]); } - public static function getSubNavigationPosition() : SubNavigationPosition + public static function getSubNavigationPosition(): SubNavigationPosition { return SubNavigationPosition::Top; } - public static function getRecordRouteBindingEloquentQuery() : Builder + public static function getRecordRouteBindingEloquentQuery(): Builder { return parent::getRecordRouteBindingEloquentQuery() ->withoutGlobalScopes([ diff --git a/app/Filament/Resources/PostResource/Pages/CreatePost.php b/app/Filament/Resources/PostResource/Pages/CreatePost.php index 129d233e..311d838c 100644 --- a/app/Filament/Resources/PostResource/Pages/CreatePost.php +++ b/app/Filament/Resources/PostResource/Pages/CreatePost.php @@ -1,16 +1,18 @@ record); } diff --git a/app/Filament/Resources/PostResource/Pages/EditPost.php b/app/Filament/Resources/PostResource/Pages/EditPost.php index 6d335a25..a9102166 100644 --- a/app/Filament/Resources/PostResource/Pages/EditPost.php +++ b/app/Filament/Resources/PostResource/Pages/EditPost.php @@ -1,24 +1,26 @@ label('Copy URL') ->icon('heroicon-o-link') - ->alpineClickHandler(fn (Post $record) => 'window.navigator.clipboard.writeText(' . Js::from(route('posts.show', $record)) . ')'), + ->alpineClickHandler(fn (Post $record): string => 'window.navigator.clipboard.writeText('.Js::from(route('posts.show', $record)).')'), Action::make('copy') ->label('Copy as Markdown') ->icon('heroicon-o-clipboard-document') - ->alpineClickHandler(fn (Post $record) => 'window.navigator.clipboard.writeText(' . Js::from($record->toMarkdown()) . ')'), + ->alpineClickHandler(fn (Post $record): string => 'window.navigator.clipboard.writeText('.Js::from($record->toMarkdown()).')'), Action::make('search_console') ->label('Check in GSC') ->icon('heroicon-o-chart-bar') - ->url(function (Post $record) { - $domain = preg_replace('/https?:\/\//', '', config('app.url')); + ->url(function (Post $record): string { + $domain = preg_replace('/https?:\/\//', '', (string) config('app.url')); - return "https://search.google.com/search-console/performance/search-analytics?resource_id=sc-domain%3A$domain&breakdown=query&page=!" . rawurlencode(route('posts.show', $record)); + return "https://search.google.com/search-console/performance/search-analytics?resource_id=sc-domain%3A$domain&breakdown=query&page=!".rawurlencode(route('posts.show', $record)); }, shouldOpenInNewTab: true), Action::make('recommendations') - ->action(function (Post $record) { + ->action(function (Post $record): void { RecommendPosts::dispatch($record); Notification::make() @@ -66,7 +68,7 @@ protected function getHeaderActions() : array ]; } - protected function afterSave() : void + protected function afterSave(): void { if (! $this->record->recommendations) { RecommendPosts::dispatch($this->record); diff --git a/app/Filament/Resources/PostResource/Pages/ListPosts.php b/app/Filament/Resources/PostResource/Pages/ListPosts.php index 61fc4b8e..88947572 100644 --- a/app/Filament/Resources/PostResource/Pages/ListPosts.php +++ b/app/Filament/Resources/PostResource/Pages/ListPosts.php @@ -1,16 +1,18 @@ getRecordTitle()}\" comments"; + return CommentResource::table($table); } - public function getBreadcrumb() : string + #[Override] + public function getTitle(): string|Htmlable { - return 'Manage Comments'; + return "Manage \"{$this->getRecordTitle()}\" comments"; } - public static function getNavigationLabel() : string + #[Override] + public function getBreadcrumb(): string { return 'Manage Comments'; } diff --git a/app/Filament/Resources/PostResource/RelationManagers/CategoriesRelationManager.php b/app/Filament/Resources/PostResource/RelationManagers/CategoriesRelationManager.php index dd92f031..4a4c535f 100644 --- a/app/Filament/Resources/PostResource/RelationManagers/CategoriesRelationManager.php +++ b/app/Filament/Resources/PostResource/RelationManagers/CategoriesRelationManager.php @@ -1,22 +1,24 @@ defaultSort(null); } diff --git a/app/Filament/Resources/RedirectResource.php b/app/Filament/Resources/RedirectResource.php index 34ca9582..21e48d7c 100644 --- a/app/Filament/Resources/RedirectResource.php +++ b/app/Filament/Resources/RedirectResource.php @@ -1,37 +1,43 @@ components([ @@ -45,7 +51,8 @@ public static function form(Schema $schema) : Schema ]); } - public static function table(Table $table) : Table + #[Override] + public static function table(Table $table): Table { return $table ->defaultSort('id', 'desc') @@ -69,8 +76,8 @@ public static function table(Table $table) : Table ->outlined() ->size('xs') ->color('gray') - ->extraAttributes(fn (Redirect $record) : array => [ - 'x-on:click' => 'navigator.clipboard.writeText(' . Js::from($record->to) . "); this.innerText='copied'; setTimeout(() => { this.innerText='copy'; }, 2000);", + ->extraAttributes(fn (Redirect $record): array => [ + 'x-on:click' => 'navigator.clipboard.writeText('.Js::from($record->to)."); this.innerText='copied'; setTimeout(() => { this.innerText='copy'; }, 2000);", ]), EditAction::make() ->icon('') @@ -91,7 +98,7 @@ public static function table(Table $table) : Table ]); } - public static function getPages() : array + public static function getPages(): array { return [ 'index' => ListRedirects::route('/'), @@ -100,12 +107,12 @@ public static function getPages() : array ]; } - public static function getGloballySearchableAttributes() : array + public static function getGloballySearchableAttributes(): array { return ['from', 'to']; } - public static function getGlobalSearchResultDetails(Model $record) : array + public static function getGlobalSearchResultDetails(Model $record): array { return ['To' => $record->to]; } diff --git a/app/Filament/Resources/RedirectResource/Pages/CreateRedirect.php b/app/Filament/Resources/RedirectResource/Pages/CreateRedirect.php index 969da2f3..e05bad85 100644 --- a/app/Filament/Resources/RedirectResource/Pages/CreateRedirect.php +++ b/app/Filament/Resources/RedirectResource/Pages/CreateRedirect.php @@ -1,16 +1,18 @@ outlined() ->size('xs') ->color('gray') - ->extraAttributes(fn () : array => [ - 'x-on:click' => 'navigator.clipboard.writeText(' . Js::from($this->getRecord()->to) . "); this.innerText='copied'; setTimeout(() => { this.innerText='copy'; }, 2000);", + ->extraAttributes(fn (): array => [ + 'x-on:click' => 'navigator.clipboard.writeText('.Js::from($this->getRecord()->to)."); this.innerText='copied'; setTimeout(() => { this.innerText='copy'; }, 2000);", ]), DeleteAction::make(), ]; diff --git a/app/Filament/Resources/RedirectResource/Pages/ListRedirects.php b/app/Filament/Resources/RedirectResource/Pages/ListRedirects.php index b84c4740..f07dc000 100644 --- a/app/Filament/Resources/RedirectResource/Pages/ListRedirects.php +++ b/app/Filament/Resources/RedirectResource/Pages/ListRedirects.php @@ -1,16 +1,18 @@ components([ @@ -49,7 +55,8 @@ public static function form(Schema $schema) : Schema ]); } - public static function table(Table $table) : Table + #[Override] + public static function table(Table $table): Table { return $table ->defaultSort('id', 'desc') @@ -93,7 +100,7 @@ public static function table(Table $table) : Table ]); } - public static function getPages() : array + public static function getPages(): array { return [ 'index' => ListShortUrls::route('/'), @@ -102,7 +109,7 @@ public static function getPages() : array ]; } - public static function getGloballySearchableAttributes() : array + public static function getGloballySearchableAttributes(): array { return [ 'url', @@ -110,7 +117,7 @@ public static function getGloballySearchableAttributes() : array ]; } - public static function getGlobalSearchResultDetails($record) : array + public static function getGlobalSearchResultDetails($record): array { return [ 'URL' => $record->url, diff --git a/app/Filament/Resources/ShortUrlResource/Pages/CreateShortUrl.php b/app/Filament/Resources/ShortUrlResource/Pages/CreateShortUrl.php index 21ef6341..38eae3eb 100644 --- a/app/Filament/Resources/ShortUrlResource/Pages/CreateShortUrl.php +++ b/app/Filament/Resources/ShortUrlResource/Pages/CreateShortUrl.php @@ -1,9 +1,11 @@ components([ @@ -57,7 +63,8 @@ public static function form(Schema $schema) : Schema ->columns(1); } - public static function table(Table $table) : Table + #[Override] + public static function table(Table $table): Table { return $table ->defaultSort('id', 'desc') @@ -123,7 +130,7 @@ public static function table(Table $table) : Table ]); } - public static function getPages() : array + public static function getPages(): array { return [ 'index' => ListUsers::route('/'), @@ -132,12 +139,12 @@ public static function getPages() : array ]; } - public static function getGloballySearchableAttributes() : array + public static function getGloballySearchableAttributes(): array { return ['name', 'github_login', 'email']; } - public static function getGlobalSearchResultDetails(Model $record) : array + public static function getGlobalSearchResultDetails(Model $record): array { return [ 'Email' => $record->email, diff --git a/app/Filament/Resources/UserResource/Pages/CreateUser.php b/app/Filament/Resources/UserResource/Pages/CreateUser.php index 78a38949..72b81bb0 100644 --- a/app/Filament/Resources/UserResource/Pages/CreateUser.php +++ b/app/Filament/Resources/UserResource/Pages/CreateUser.php @@ -1,5 +1,7 @@ icon('heroicon-o-user') ->outlined() ->color('gray') - ->visible(fn () => Auth::user()?->canImpersonate() && $this->record->canBeImpersonated()) + ->visible(fn (): bool => Auth::user()?->canImpersonate() && $this->record->canBeImpersonated()) ->action(function () { session([ 'impersonate.return' => request()->headers->get('referer') ?? request()->fullUrl(), diff --git a/app/Filament/Resources/UserResource/Pages/ListUsers.php b/app/Filament/Resources/UserResource/Pages/ListUsers.php index 55b26c45..50c8b602 100644 --- a/app/Filament/Resources/UserResource/Pages/ListUsers.php +++ b/app/Filament/Resources/UserResource/Pages/ListUsers.php @@ -1,16 +1,18 @@ token) ->get($this->api("images/v1/{$path}")); @@ -35,17 +37,17 @@ public function fileExists(string $path) : bool } /* @inheritdoc */ - public function directoryExists(string $path) : bool + public function directoryExists(string $path): bool { return false; // Cloudflare Images has no directories concept. } /* @inheritdoc */ - public function write(string $path, string $contents, Config $config) : void + public function write(string $path, string $contents, Config $config): void { $tmp = tmpfile(); - if (false === $tmp) { + if ($tmp === false) { throw UnableToWriteFile::atLocation($path, 'Unable to create temporary file.'); } @@ -59,29 +61,13 @@ public function write(string $path, string $contents, Config $config) : void } /* @inheritdoc */ - public function writeStream(string $path, $contents, Config $config) : void + public function writeStream(string $path, $contents, Config $config): void { $this->upload($path, $contents); } - protected function upload(string $path, $resource) : void - { - $response = Http::withToken($this->token) - ->asMultipart() - ->attach('file', $resource, basename($path)) - ->post($this->api('images/v1'), [ - // Store the desired path as the custom ID so we can reference it later. - 'id' => $path, - 'requireSignedURLs' => 'false', - ]); - - if (! $response->ok()) { - throw UnableToWriteFile::atLocation($path, $response->body()); - } - } - /* @inheritdoc */ - public function read(string $path) : string + public function read(string $path): string { $response = Http::get($this->getUrl($path)); @@ -106,7 +92,7 @@ public function readStream(string $path) } /* @inheritdoc */ - public function delete(string $path) : void + public function delete(string $path): void { $response = Http::withToken($this->token) ->delete($this->api("images/v1/{$path}")); @@ -117,31 +103,31 @@ public function delete(string $path) : void } /* @inheritdoc */ - public function deleteDirectory(string $path) : void + public function deleteDirectory(string $path): void { throw UnableToDeleteDirectory::atLocation($path, 'Directories are not supported by Cloudflare Images.'); } /* @inheritdoc */ - public function createDirectory(string $path, Config $config) : void + public function createDirectory(string $path, Config $config): void { throw UnableToCreateDirectory::atLocation($path, 'Directories are not supported by Cloudflare Images.'); } /* @inheritdoc */ - public function setVisibility(string $path, string $visibility) : void + public function setVisibility(string $path, string $visibility): void { throw InvalidVisibilityProvided::withVisibility($visibility, 'public'); } /* @inheritdoc */ - public function visibility(string $path) : FileAttributes + public function visibility(string $path): FileAttributes { return new FileAttributes($path, null, 'public'); } /* @inheritdoc */ - public function mimeType(string $path) : FileAttributes + public function mimeType(string $path): FileAttributes { $headers = $this->getHeaders($path); @@ -149,7 +135,7 @@ public function mimeType(string $path) : FileAttributes } /* @inheritdoc */ - public function lastModified(string $path) : FileAttributes + public function lastModified(string $path): FileAttributes { $headers = $this->getHeaders($path); @@ -159,13 +145,13 @@ public function lastModified(string $path) : FileAttributes } /* @inheritdoc */ - public function fileSize(string $path) : FileAttributes + public function fileSize(string $path): FileAttributes { $headers = $this->getHeaders($path); $size = isset($headers['content-length']) ? (int) $headers['content-length'] : null; - if (null === $size) { + if ($size === null) { throw UnableToRetrieveMetadata::fileSize($path, 'Content-Length header missing.'); } @@ -173,37 +159,53 @@ public function fileSize(string $path) : FileAttributes } /* @inheritdoc */ - public function listContents(string $path, bool $deep) : iterable + public function listContents(string $path, bool $deep): iterable { return []; // Listing is not required for now. } /* @inheritdoc */ - public function move(string $source, string $destination, Config $config) : void + public function move(string $source, string $destination, Config $config): void { throw UnableToMoveFile::because($source, $destination, 'Cloudflare Images does not support moving files.'); } /* @inheritdoc */ - public function copy(string $source, string $destination, Config $config) : void + public function copy(string $source, string $destination, Config $config): void { throw UnableToCopyFile::because($source, $destination, 'Cloudflare Images does not support copying files.'); } /** - * Build full API URL for the given path. + * Public URL used by Laravel's `Storage::url()`. */ - protected function api(string $endpoint) : string + public function getUrl(string $path): string + { + return sprintf('https://imagedelivery.net/%s/%s/%s', $this->accountHash, mb_trim($path, '/'), $this->variant); + } + + protected function upload(string $path, $resource): void { - return sprintf('https://api.cloudflare.com/client/v4/accounts/%s/%s', $this->accountId, ltrim($endpoint, '/')); + $response = Http::withToken($this->token) + ->asMultipart() + ->attach('file', $resource, basename($path)) + ->post($this->api('images/v1'), [ + // Store the desired path as the custom ID so we can reference it later. + 'id' => $path, + 'requireSignedURLs' => 'false', + ]); + + if (! $response->ok()) { + throw UnableToWriteFile::atLocation($path, $response->body()); + } } /** - * Public URL used by Laravel's `Storage::url()`. + * Build full API URL for the given path. */ - public function getUrl(string $path) : string + protected function api(string $endpoint): string { - return sprintf('https://imagedelivery.net/%s/%s/%s', $this->accountHash, trim($path, '/'), $this->variant); + return sprintf('https://api.cloudflare.com/client/v4/accounts/%s/%s', $this->accountId, mb_ltrim($endpoint, '/')); } /** @@ -211,7 +213,7 @@ public function getUrl(string $path) : string * * @return array */ - protected function getHeaders(string $path) : array + protected function getHeaders(string $path): array { $response = Http::withoutVerifying()->head($this->getUrl($path)); diff --git a/app/Http/Controllers/AboutController.php b/app/Http/Controllers/AboutController.php new file mode 100644 index 00000000..d8f52593 --- /dev/null +++ b/app/Http/Controllers/AboutController.php @@ -0,0 +1,24 @@ +first(); + + return view('about.show', [ + 'author' => $user, + + 'posts' => $user->posts() + ->latest('published_at') + ->published() + ->paginate(12), + ]); + } +} diff --git a/app/Http/Controllers/Advertising/RedirectToAdvertiserController.php b/app/Http/Controllers/Advertising/RedirectToAdvertiserController.php deleted file mode 100644 index 74bb71d5..00000000 --- a/app/Http/Controllers/Advertising/RedirectToAdvertiserController.php +++ /dev/null @@ -1,42 +0,0 @@ -fullUrl()) && - ($ip = $request->ip()) && - ($userAgent = $request->userAgent())) { - TrackEvent::dispatchAfterResponse( - 'Clicked on ad', - [ - 'slug' => $slug, - 'url' => $adUrl, - ], - $request->fullUrl(), - $ip, - $userAgent, - $request->header('Accept-Language', ''), - $request->header('Referer', ''), - ); - } - - return redirect( - Uri::of($adUrl)->withQuery($request->query() + [ - 'utm_source' => 'benjamin_crozat', - ]) - ); - } -} diff --git a/app/Http/Controllers/Advertising/ShowAdvertisingLandingPageController.php b/app/Http/Controllers/Advertising/ShowAdvertisingLandingPageController.php deleted file mode 100644 index 474afb71..00000000 --- a/app/Http/Controllers/Advertising/ShowAdvertisingLandingPageController.php +++ /dev/null @@ -1,26 +0,0 @@ - Number::format( - Metric::query()->where('key', 'views')->value('value') ?? 0 - ), - 'sessions' => Number::format( - Metric::query()->where('key', 'sessions')->value('value') ?? 0 - ), - 'desktop' => Number::format( - Metric::query()->where('key', 'platform_desktop')->value('value') ?? 0, 0 - ), - ]); - } -} diff --git a/app/Http/Controllers/Auth/GithubAuthCallbackController.php b/app/Http/Controllers/Auth/GithubAuthCallbackController.php index 7896cecf..dbd82ad3 100644 --- a/app/Http/Controllers/Auth/GithubAuthCallbackController.php +++ b/app/Http/Controllers/Auth/GithubAuthCallbackController.php @@ -1,16 +1,18 @@ user(); diff --git a/app/Http/Controllers/Auth/GithubAuthRedirectController.php b/app/Http/Controllers/Auth/GithubAuthRedirectController.php index 39f7870d..750d601b 100644 --- a/app/Http/Controllers/Auth/GithubAuthRedirectController.php +++ b/app/Http/Controllers/Auth/GithubAuthRedirectController.php @@ -1,5 +1,7 @@ session()->invalidate(); diff --git a/app/Http/Controllers/Authors/ShowAuthorController.php b/app/Http/Controllers/Authors/ShowAuthorController.php index 8fe0ea26..585f0457 100644 --- a/app/Http/Controllers/Authors/ShowAuthorController.php +++ b/app/Http/Controllers/Authors/ShowAuthorController.php @@ -1,14 +1,16 @@ $user, @@ -17,11 +19,6 @@ public function __invoke(User $user) : View ->latest('published_at') ->published() ->paginate(12), - - 'links' => $user->links() - ->latest('is_approved') - ->approved() - ->paginate(12), ]); } } diff --git a/app/Http/Controllers/Categories/ListCategoriesController.php b/app/Http/Controllers/Categories/ListCategoriesController.php index d7091d6f..b8606873 100644 --- a/app/Http/Controllers/Categories/ListCategoriesController.php +++ b/app/Http/Controllers/Categories/ListCategoriesController.php @@ -1,14 +1,16 @@ Category::query() diff --git a/app/Http/Controllers/Categories/ShowCategoryController.php b/app/Http/Controllers/Categories/ShowCategoryController.php index 8cbb4961..154a1d48 100644 --- a/app/Http/Controllers/Categories/ShowCategoryController.php +++ b/app/Http/Controllers/Categories/ShowCategoryController.php @@ -1,16 +1,18 @@ $category] + [ 'posts' => $category ->posts() ->latest('published_at') diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 8677cd5c..e2af3d27 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -1,5 +1,7 @@ published() - ->whereDoesntHave('link') ->where('sessions_count', '>', 0) ->orderBy('sessions_count', 'desc') ->limit(12) @@ -23,7 +22,6 @@ public function __invoke() : View $latest = Post::query() ->latest('published_at') ->published() - ->whereDoesntHave('link') ->limit(12) ->get(); @@ -32,16 +30,6 @@ public function __invoke() : View // would have loaded the relationships twice later in the view. $popular->concat($latest)->load('categories', 'user'); - $links = Link::query() - ->latest('is_approved') - ->approved() - ->limit(12) - ->get(); - - $aboutUser = User::query() - ->where('github_login', 'benjamincrozat') - ->first(); - - return view('home', compact('popular', 'latest', 'links', 'aboutUser')); + return view('home', ['popular' => $popular, 'latest' => $latest]); } } diff --git a/app/Http/Controllers/Impersonation/LeaveImpersonationController.php b/app/Http/Controllers/Impersonation/LeaveImpersonationController.php index e0aac744..7910d0bd 100644 --- a/app/Http/Controllers/Impersonation/LeaveImpersonationController.php +++ b/app/Http/Controllers/Impersonation/LeaveImpersonationController.php @@ -1,15 +1,17 @@ isImpersonating()) { $impersonate->leave(); diff --git a/app/Http/Controllers/Links/ListLinksController.php b/app/Http/Controllers/Links/ListLinksController.php deleted file mode 100644 index c9e4976d..00000000 --- a/app/Http/Controllers/Links/ListLinksController.php +++ /dev/null @@ -1,36 +0,0 @@ -select('user_id') - ->distinct('user_id') - ->whereRelation('user', fn (Builder $query) => $query->where('github_login', '!=', 'benjamincrozat')) - ->approved(); - - return view('links.index', [ - 'distinctUserAvatars' => $distinctUsersQuery - ->whereRelation('user', fn (Builder $query) => $query->whereNotNull('avatar')) - ->inRandomOrder() - ->limit(10) - ->get() - ->map(fn (Link $link) => $link->user->avatar), - - 'distinctUsersCount' => $distinctUsersQuery->count(), - - 'links' => Link::query() - ->latest('is_approved') - ->approved() - ->paginate(12), - ]); - } -} diff --git a/app/Http/Controllers/Merchants/ShowMerchantController.php b/app/Http/Controllers/Merchants/ShowMerchantController.php index 0e0f0317..b8d964b3 100644 --- a/app/Http/Controllers/Merchants/ShowMerchantController.php +++ b/app/Http/Controllers/Merchants/ShowMerchantController.php @@ -1,45 +1,27 @@ flatMap(function (array $items) { - return collect($items)->map( - fn (mixed $item) => $item['link'] ?? $item - ); - }) + ->flatMap(fn (array $items) => collect($items)->map( + fn (mixed $item): mixed => $item['link'] ?? $item + )) ->get($slug), 404 ); - if (! empty($request->fullUrl()) && - ($ip = $request->ip()) && - ($userAgent = $request->userAgent())) { - TrackEvent::dispatchAfterResponse( - 'Clicked on merchant', - [ - 'slug' => $slug, - 'url' => $merchantLink, - ], - $request->fullUrl(), - $ip, - $userAgent, - $request->header('Accept-Language', ''), - $request->header('Referer', ''), - ); - } - return redirect()->away( Uri::of($merchantLink) ->withQuery(request()->all()) diff --git a/app/Http/Controllers/Posts/ListLaravelPostsController.php b/app/Http/Controllers/Posts/ListLaravelPostsController.php new file mode 100644 index 00000000..1154608c --- /dev/null +++ b/app/Http/Controllers/Posts/ListLaravelPostsController.php @@ -0,0 +1,23 @@ + Post::query() + ->latest('published_at') + ->published() + ->where('category', 'laravel') + ->paginate(24), + ]); + } +} diff --git a/app/Http/Controllers/Posts/ListPostsController.php b/app/Http/Controllers/Posts/ListPostsController.php index 7fd6f846..bbfce4a6 100644 --- a/app/Http/Controllers/Posts/ListPostsController.php +++ b/app/Http/Controllers/Posts/ListPostsController.php @@ -1,20 +1,21 @@ Post::query() ->latest('published_at') ->published() - ->whereDoesntHave('link') ->paginate(24), ]); } diff --git a/app/Http/Controllers/Posts/ShowPostController.php b/app/Http/Controllers/Posts/ShowPostController.php index 721f61c1..19b0d1bc 100644 --- a/app/Http/Controllers/Posts/ShowPostController.php +++ b/app/Http/Controllers/Posts/ShowPostController.php @@ -1,11 +1,13 @@ where('slug', $slug)->first(); @@ -35,9 +37,9 @@ public function __invoke(Request $request, string $slug) : View } } - return view('posts.show', compact('post') + [ + return view('posts.show', ['post' => $post] + [ 'latestComment' => $post->comments() - ->whereRelation('user', 'github_login', '!=', 'benjamincrozat') + ->whereRelation('user', 'github_login', '!=', 'carlossantosdev') ->latest() ->first(), ]); diff --git a/app/Http/Controllers/ShortUrls/ShowShortUrlController.php b/app/Http/Controllers/ShortUrls/ShowShortUrlController.php index 080ee296..37c36b1a 100644 --- a/app/Http/Controllers/ShortUrls/ShowShortUrlController.php +++ b/app/Http/Controllers/ShortUrls/ShowShortUrlController.php @@ -1,34 +1,22 @@ where('code', $code) ->firstOrFail(); - if (($ip = $request->ip()) && - ($userAgent = $request->userAgent())) { - TrackEvent::dispatchAfterResponse( - 'Clicked on short URL', - ['url' => $shortUrl->url], - $request->fullUrl(), - $ip, - $userAgent, - $request->header('Accept-Language', ''), - $request->header('Referer', ''), - ); - } - return redirect()->away($shortUrl->url); } } diff --git a/app/Http/Controllers/User/ListUserCommentsController.php b/app/Http/Controllers/User/ListUserCommentsController.php index 742565ba..1824aae9 100644 --- a/app/Http/Controllers/User/ListUserCommentsController.php +++ b/app/Http/Controllers/User/ListUserCommentsController.php @@ -1,14 +1,16 @@ $request->user()->comments()->paginate(10), diff --git a/app/Http/Controllers/User/ListUserLinksController.php b/app/Http/Controllers/User/ListUserLinksController.php deleted file mode 100644 index 9d907152..00000000 --- a/app/Http/Controllers/User/ListUserLinksController.php +++ /dev/null @@ -1,17 +0,0 @@ - $request->user()->links()->paginate(10), - ]); - } -} diff --git a/app/Http/Middleware/Admin.php b/app/Http/Middleware/Admin.php index 3e99e2dd..0212ae9c 100644 --- a/app/Http/Middleware/Admin.php +++ b/app/Http/Middleware/Admin.php @@ -1,5 +1,7 @@ user()?->isAdmin()) { return $next($request); diff --git a/app/Http/Middleware/HandleRedirects.php b/app/Http/Middleware/HandleRedirects.php index 95035cdb..ac948fc9 100644 --- a/app/Http/Middleware/HandleRedirects.php +++ b/app/Http/Middleware/HandleRedirects.php @@ -1,33 +1,32 @@ path(), '/'); + $path = mb_trim($request->path(), '/'); // Only handle simple root-level slugs (no slash) to avoid interfering with other routes. - if ('' !== $path && ! str_contains($path, '/')) { - if ($redirect = Redirect::query()->where('from', $path)->first()) { - $target = '/' . ltrim($redirect->to, '/'); - - // Preserve query string if present. - if ($request->getQueryString()) { - $target .= '?' . $request->getQueryString(); - } - - return redirect($target, status: 301); + if ($path !== '' && ! str_contains($path, '/') && $redirect = Redirect::query()->where('from', $path)->first()) { + $target = '/'.mb_ltrim((string) $redirect->to, '/'); + // Preserve query string if present. + if (! in_array($request->getQueryString(), [null, '', '0'], true)) { + $target .= '?'.$request->getQueryString(); } + + return redirect($target, status: 301); } return $next($request); diff --git a/app/Http/Middleware/TrackVisit.php b/app/Http/Middleware/TrackVisit.php deleted file mode 100644 index ff43aa56..00000000 --- a/app/Http/Middleware/TrackVisit.php +++ /dev/null @@ -1,81 +0,0 @@ -shouldTrack($request)) { - // These are needed for Pirsch's API so we need to make sure the values are not empty or null. - if (! empty($url = $request->url()) && - ($ip = $request->ip()) && - ($userAgent = $request->userAgent())) { - app(\App\Actions\TrackVisit::class)->track( - $url, - $ip, - $userAgent, - $request->header('Accept-Language', ''), - $request->header('Referer', ''), - ); - } - } - } - - /** - * Determine if the request should be tracked. - * - * Only track visits that meet all of the following: - * 1. The app is running in production. - * 2. The request is not from Livewire. - * 3. The request does not expect a JSON response. - * 4. The request is not a prefetch request. - * 5. The request uses the GET method. - * 6. The request is not from a crawler. (Pirsch already filters them out, but some may have slipped through.) - * 7. The request is not from an admin. - */ - protected function shouldTrack(Request $request) : bool - { - return 'production' === config('app.env') && - ! $request->hasHeader('X-Livewire') && - ! $request->wantsJson() && - ! $this->isPrefetch($request) && - 'GET' === $request->method() && - ! app(CrawlerDetect::class)->isCrawler($request->userAgent()) && - ! $request->user()?->isAdmin(); - } - - protected function isPrefetch(Request $request) : bool - { - // According to RFC 9218, browsers should send a `Purpose: prefetch` header when prefetching. - // Firefox historically used proprietary headers such as `X-Moz: prefetch`. - // We consider any of these signals as a prefetch request. - - $purpose = strtolower($request->header('Purpose', '')); - if ('prefetch' === $purpose) { - return true; - } - - $xPurpose = strtolower($request->header('X-Purpose', '')); - if ('prefetch' === $xPurpose) { - return true; - } - - $xMoz = strtolower($request->header('X-Moz', '')); - if ('prefetch' === $xMoz) { - return true; - } - - return false; - } -} diff --git a/app/Jobs/CreatePostForLink.php b/app/Jobs/CreatePostForLink.php deleted file mode 100644 index ffdb6eda..00000000 --- a/app/Jobs/CreatePostForLink.php +++ /dev/null @@ -1,21 +0,0 @@ -create($this->link); - } -} diff --git a/app/Jobs/FetchImageForPost.php b/app/Jobs/FetchImageForPost.php index b57ebd35..f21577c7 100644 --- a/app/Jobs/FetchImageForPost.php +++ b/app/Jobs/FetchImageForPost.php @@ -1,13 +1,15 @@ runningUnitTests()) { $image = Http::get('https://picsum.photos/1280/720') ->throw() ->body(); - Storage::put($path = '/images/posts/' . Str::random() . '.jpg', $image); + Storage::put($path = '/images/posts/'.Str::random().'.jpg', $image); } else { $path = null; } $this->post->update([ 'image_path' => $path, - 'image_disk' => $path ? config('filesystems.default') : null, + 'image_disk' => $path !== null && $path !== '' && $path !== '0' ? config('filesystems.default') : null, ]); } } diff --git a/app/Jobs/RecommendPosts.php b/app/Jobs/RecommendPosts.php index bdae4aed..1e3090c4 100644 --- a/app/Jobs/RecommendPosts.php +++ b/app/Jobs/RecommendPosts.php @@ -1,10 +1,12 @@ recommend($this->post); } diff --git a/app/Jobs/RefreshUserData.php b/app/Jobs/RefreshUserData.php index b7f52c26..a3161c47 100644 --- a/app/Jobs/RefreshUserData.php +++ b/app/Jobs/RefreshUserData.php @@ -1,10 +1,12 @@ refresh($this->user); } diff --git a/app/Jobs/TrackEvent.php b/app/Jobs/TrackEvent.php deleted file mode 100644 index 720f6bc1..00000000 --- a/app/Jobs/TrackEvent.php +++ /dev/null @@ -1,34 +0,0 @@ -track( - $this->name, - $this->meta, - $this->url, - $this->ip, - $this->userAgent, - $this->acceptLanguage, - $this->referrer, - ); - } -} diff --git a/app/Livewire/CommentForm.php b/app/Livewire/CommentForm.php index d4c37e5c..7573343e 100644 --- a/app/Livewire/CommentForm.php +++ b/app/Livewire/CommentForm.php @@ -1,9 +1,11 @@ guest()) { abort(401); diff --git a/app/Livewire/Comments.php b/app/Livewire/Comments.php index 315adada..55378c38 100644 --- a/app/Livewire/Comments.php +++ b/app/Livewire/Comments.php @@ -1,16 +1,18 @@ Comment::query() @@ -42,7 +44,7 @@ public function render() : View } #[On('comment.submitted')] - public function store(?int $parentId = null, string $commentContent = '') : void + public function store(?int $parentId = null, string $commentContent = ''): void { if (auth()->guest()) { abort(401); @@ -56,7 +58,7 @@ public function store(?int $parentId = null, string $commentContent = '') : void ]); // If this is a reply, we notify the parent comment's author. - if ($parentId) { + if ($parentId !== null && $parentId !== 0) { Comment::query() ->find($parentId) ?->user @@ -65,9 +67,9 @@ public function store(?int $parentId = null, string $commentContent = '') : void // We notify the admin when a new comment is // posted unless it's the admin himself. - if ('benjamincrozat' !== auth()->user()->github_login) { + if (auth()->user()->github_login !== 'carlossantosdev') { User::query() - ->where('github_login', 'benjamincrozat') + ->where('github_login', 'carlossantosdev') ->first() ?->notify(new NewComment($comment)); } @@ -75,7 +77,7 @@ public function store(?int $parentId = null, string $commentContent = '') : void $this->reset('parentId'); } - public function delete(int $commentId) : void + public function delete(int $commentId): void { if (auth()->guest()) { abort(401); diff --git a/app/Livewire/LinkWizard/FirstStep.php b/app/Livewire/LinkWizard/FirstStep.php deleted file mode 100644 index 4f4e0d3f..00000000 --- a/app/Livewire/LinkWizard/FirstStep.php +++ /dev/null @@ -1,60 +0,0 @@ - 'This URL was already shared.', - ])] - #[Url(history: true)] - public string $url = ''; - - public function stepInfo() : array - { - return [ - 'label' => 'Your link', - ]; - } - - public function mount() : void - { - if ($this->url) { - $this->prepareForNextStep(); - } - } - - public function render() : View - { - return view('livewire.link-wizard.first-step'); - } - - public function submit() : void - { - $this->prepareForNextStep(); - } - - protected function prepareForNextStep() : void - { - try { - $this->validate(); - - Http::head($this->url)->throw(); - - $this->nextStep(); - } catch (ConnectionException|RequestException $e) { - throw ValidationException::withMessages([ - 'url' => 'Your URL is invalid.', - ]); - } - } -} diff --git a/app/Livewire/LinkWizard/LinkWizard.php b/app/Livewire/LinkWizard/LinkWizard.php deleted file mode 100644 index ba609800..00000000 --- a/app/Livewire/LinkWizard/LinkWizard.php +++ /dev/null @@ -1,16 +0,0 @@ - 'Details', - ]; - } - - public function mount() : void - { - if (! $this->url) { - $this->previousStep(); - } - } - - public function render() : View - { - $this->dispatch('fetch')->self(); - - return view('livewire.link-wizard.second-step'); - } - - #[On('fetch')] - public function fetch() : void - { - /** - * @var array{ - * image_url: string, - * title: string, - * description: string, - * } - */ - $embed = cache()->remember( - key: 'embed_' . Str::slug($this->url, '_'), - ttl: now()->addHour(), - callback: function () { - $embed = app(Embed::class)->get($this->url); - - return [ - 'image_url' => $embed->image, - 'title' => $embed->title, - 'description' => $embed->description, - ]; - } - ); - - $this->imageUrl = $embed['image_url']; - $this->title = $embed['title']; - $this->description = $embed['description']; - } - - public function submit() : void - { - $this->validate(); - - $link = Link::query()->create([ - 'user_id' => auth()->id(), - 'url' => $this->url, - 'image_url' => $this->imageUrl, - 'title' => $this->title, - 'description' => $this->description, - ]); - - User::query() - ->where('github_login', 'benjamincrozat') - ->first() - ->notify(new LinkWaitingForValidation($link)); - - $this->redirect(route('links.index', ['submitted' => true]), true); - } -} diff --git a/app/Models/Category.php b/app/Models/Category.php index a689815b..7eed9c35 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -1,10 +1,12 @@ */ use HasFactory; - public function posts() : BelongsToMany + public function posts(): BelongsToMany { return $this->belongsToMany(Post::class); } - public function activity() : BelongsToMany + public function activity(): BelongsToMany { return $this ->belongsToMany(Post::class) diff --git a/app/Models/Comment.php b/app/Models/Comment.php index 224139b4..04ed2176 100644 --- a/app/Models/Comment.php +++ b/app/Models/Comment.php @@ -1,15 +1,17 @@ 'datetime', - ]; - } - - public function user() : BelongsTo + public function user(): BelongsTo { return $this->belongsTo(User::class); } - public function post() : BelongsTo + public function post(): BelongsTo { return $this->belongsTo(Post::class); } - public function parent() : BelongsTo + public function parent(): BelongsTo { - return $this->belongsTo(Comment::class, 'parent_id'); + return $this->belongsTo(self::class, 'parent_id'); } - public function children() : HasMany + public function children(): HasMany { - return $this->hasMany(Comment::class, 'parent_id'); + return $this->hasMany(self::class, 'parent_id'); } - public function stripped() : Attribute + public function stripped(): Attribute { return Attribute::make( - fn () => strip_tags(Str::lightdown($this->content)), + fn (): string => strip_tags(Str::lightdown($this->content)), )->shouldCache(); } - public function truncated() : Attribute + public function truncated(): Attribute { return Attribute::make( - function () { + function (): string { $stripped = strip_tags(Str::lightdown($this->content)); - return trim( - strlen($stripped) > 100 - ? rtrim(substr($stripped, 0, 100), '.') . '…' + return mb_trim( + mb_strlen($stripped) > 100 + ? mb_rtrim(mb_substr($stripped, 0, 100), '.').'…' : $stripped ); }, )->shouldCache(); } - public function deleteWithChildren() : self + public function deleteWithChildren(): self { $this->children->each( - fn (Comment $comment) => $comment->deleteWithChildren() + fn (Comment $comment): Comment => $comment->deleteWithChildren() ); $this->delete(); return $this; } + + protected function casts(): array + { + return [ + 'modified_at' => 'datetime', + ]; + } } diff --git a/app/Models/Link.php b/app/Models/Link.php deleted file mode 100644 index b34b66bc..00000000 --- a/app/Models/Link.php +++ /dev/null @@ -1,100 +0,0 @@ - */ - use HasFactory; - - protected function casts() : array - { - return [ - 'is_approved' => 'datetime', - 'is_declined' => 'datetime', - ]; - } - - #[Scope] - public function pending(Builder $query) : void - { - $query - ->whereNull('is_declined') - ->whereNull('is_approved'); - } - - #[Scope] - public function approved(Builder $query) : void - { - $query - ->whereNotNull('is_approved') - ->whereNull('is_declined'); - } - - #[Scope] - public function declined(Builder $query) : void - { - $query - ->whereNotNull('is_declined') - ->whereNull('is_approved'); - } - - public function user() : BelongsTo - { - return $this->belongsTo(User::class); - } - - public function post() : BelongsTo - { - return $this->belongsTo(Post::class); - } - - public function domain() : Attribute - { - return Attribute::make( - fn () => str_replace('www.', '', parse_url($this->url, PHP_URL_HOST)), - ); - } - - public function approve(?string $notes = null) : self - { - $this->update([ - 'notes' => $notes, - 'is_approved' => now(), - 'is_declined' => null, - ]); - - $this->user->notify(new LinkApproved($this)); - - return $this; - } - - public function decline() : self - { - $this->update([ - 'is_declined' => now(), - 'is_approved' => null, - ]); - - return $this; - } - - public function isApproved() : bool - { - return null !== $this->is_approved && null === $this->is_declined; - } - - public function isDeclined() : bool - { - return null !== $this->is_declined && null === $this->is_approved; - } -} diff --git a/app/Models/Metric.php b/app/Models/Metric.php index f2cc8a70..a6e6ee9f 100644 --- a/app/Models/Metric.php +++ b/app/Models/Metric.php @@ -1,22 +1,26 @@ */ use HasFactory; - protected static function booted() : void + #[Override] + protected static function booted(): void { // We should always return the latest metric. I'm not a // fan of global scopes, but this one is appropriate. - static::addGlobalScope('latest', function (Builder $builder) { + static::addGlobalScope('latest', function (Builder $builder): void { $builder->latest(); }); } diff --git a/app/Models/Post.php b/app/Models/Post.php index 92f2f2ca..7571df94 100644 --- a/app/Models/Post.php +++ b/app/Models/Post.php @@ -1,24 +1,26 @@ slug ??= Str::slug($post->title); } ); - static::updating(function (Post $post) { + static::updating(function (Post $post): void { if (! $post->isDirty('slug')) { return; } @@ -48,7 +51,7 @@ function (Post $post) { return; } - DB::transaction(function () use ($old, $new) { + DB::transaction(function () use ($old, $new): void { // 1. Remove any redirect originating from the new slug. It would // create a loop once the slug becomes its own destination. Redirect::query()->where('from', $new)->delete(); @@ -66,75 +69,58 @@ function (Post $post) { }); } - protected function casts() : array - { - return [ - 'published_at' => 'datetime', - 'modified_at' => 'datetime', - 'recommendations' => 'collection', - ]; - } - - #[Scope] - protected function published(Builder $query) : void - { - $query->whereNotNull('published_at'); - } - - #[Scope] - protected function unpublished(Builder $query) : void + public static function getFeedItems(): Collection { - $query->whereNull('published_at'); + return static::query() + ->published() + ->latest('published_at') + ->limit(50) + ->get(); } - public function user() : BelongsTo + public function user(): BelongsTo { return $this->belongsTo(User::class); } - public function categories() : BelongsToMany + public function categories(): BelongsToMany { return $this->belongsToMany(Category::class); } - public function comments() : HasMany + public function comments(): HasMany { return $this->hasMany(Comment::class); } - public function link() : HasOne - { - return $this->hasOne(Link::class); - } - - public function formattedContent() : Attribute + public function formattedContent(): Attribute { return Attribute::make( - fn () => Str::markdown($this->content), + fn (): string => Str::markdown($this->content), )->shouldCache(); } - public function imageUrl() : Attribute + public function imageUrl(): Attribute { return Attribute::make( fn () => $this->hasImage() ? Storage::disk($this->image_disk)->url($this->image_path) : null, )->shouldCache(); } - public function readTime() : Attribute + public function readTime(): Attribute { return Attribute::make( - fn () => ceil(str_word_count($this->content) / 200), + fn (): float => ceil(str_word_count($this->content) / 200), )->shouldCache(); } - public function recommendedPosts() : Attribute + public function recommendedPosts(): Attribute { return Attribute::make( fn () => empty($this->recommendations) ? null : Post::query() ->whereIn('id', $this->recommendations->pluck('id')) ->get() - ->map(function (self $post) { + ->map(function (self $post): Post { $recommendation = collect($this->recommendations) ->firstWhere('id', $post->id); @@ -147,12 +133,12 @@ public function recommendedPosts() : Attribute )->shouldCache(); } - public function hasImage() : bool + public function hasImage(): bool { return $this->image_path && $this->image_disk; } - public function toMarkdown() : string + public function toMarkdown(): string { // Ensure categories are loaded so we can list them in the front matter. $this->loadMissing('categories'); @@ -170,13 +156,13 @@ public function toMarkdown() : string // Build the YAML-like front matter block. $frontMatterLines = collect($frontMatter) - ->map(fn ($value, string $key) => "$key: $value") + ->map(fn ($value, string $key): string => "$key: $value") ->implode("\n"); return "---\n{$frontMatterLines}\n---\n\n# {$this->title}\n\n{$this->content}\n"; } - public function toPrompt() : string + public function toPrompt(): string { $content = preg_replace(['/\s+/', '/\n+/'], [' ', "\n"], strip_tags($this->formatted_content, allowed_tags: ['a'])); @@ -189,36 +175,48 @@ public function toPrompt() : string MARKDOWN; } - public static function getFeedItems() : Collection - { - return static::query() - ->published() - ->whereDoesntHave('link') - ->latest('published_at') - ->limit(50) - ->get(); - } - - public function toFeedItem() : FeedItem + public function toFeedItem(): FeedItem { $link = route('posts.show', $this); return FeedItem::create() ->id($this->slug) ->title($this->title) - ->summary(Str::markdown($this->description . <<summary(Str::markdown($this->description.<<updated($this->modified_at ?? $this->published_at) ->link($link) ->authorName($this->user->name); } - public function getRouteKeyName() : string + #[Override] + public function getRouteKeyName(): string { return 'slug'; } + + protected function casts(): array + { + return [ + 'published_at' => 'datetime', + 'modified_at' => 'datetime', + 'recommendations' => 'collection', + ]; + } + + #[Scope] + protected function published(Builder $query): void + { + $query->whereNotNull('published_at'); + } + + #[Scope] + protected function unpublished(Builder $query): void + { + $query->whereNull('published_at'); + } } diff --git a/app/Models/Redirect.php b/app/Models/Redirect.php index 7edef2eb..4fb8cd20 100644 --- a/app/Models/Redirect.php +++ b/app/Models/Redirect.php @@ -1,5 +1,7 @@ code ??= Str::random(5); - }); + return Attribute::make( + fn (): string => 'https://'.config('app.url_shortener_domain').'/'.$this->code, + ); } - public function link() : Attribute + #[Override] + protected static function booted(): void { - return Attribute::make( - fn () => 'https://' . config('app.url_shortener_domain') . '/' . $this->code, - ); + static::creating(function (self $model): void { + $model->code ??= Str::random(5); + }); } } diff --git a/app/Models/User.php b/app/Models/User.php index e60dbdaf..06a7c1ad 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -1,18 +1,21 @@ $user->slug = Str::slug($user->name) - ); - } - - /** - * @return array - */ - protected function casts() : array - { - return [ - 'github_data' => 'array', - 'password' => 'hashed', - 'refreshed_at' => 'datetime', - ]; - } - - public function posts() : HasMany + public function posts(): HasMany { return $this->hasMany(Post::class)->published(); } - public function links() : HasMany - { - return $this->hasMany(Link::class); - } - - public function comments() : HasMany + public function comments(): HasMany { return $this->hasMany(Comment::class); } - public function about() : Attribute + public function about(): Attribute { return Attribute::make( fn () => $this->biography ?? $this->github_data['user']['bio'] ?? '', ); } - public function blogUrl() : Attribute + public function blogUrl(): Attribute { return Attribute::make( fn () => $this->github_data['user']['blog'] ?? null, ); } - public function company() : Attribute + public function company(): Attribute { return Attribute::make( fn () => $this->github_data['user']['company'] ?? null, ); } - public function isAdmin() : bool + public function isAdmin(): bool { - return 'benjamincrozat' === $this->github_login; + return $this->github_login === 'carlossantosdev'; } - public function canAccessPanel(Panel $panel) : bool + public function canAccessPanel(Panel $panel): bool { return $this->isAdmin(); } + + #[Override] + protected static function booted(): void + { + static::creating( + fn (User $user) => $user->slug = Str::slug($user->name) + ); + } + + /** + * @return array + */ + protected function casts(): array + { + return [ + 'github_data' => 'array', + 'password' => 'hashed', + 'refreshed_at' => 'datetime', + ]; + } } diff --git a/app/Notifications/LinkApproved.php b/app/Notifications/LinkApproved.php deleted file mode 100644 index 07192039..00000000 --- a/app/Notifications/LinkApproved.php +++ /dev/null @@ -1,31 +0,0 @@ -subject('Your link was approved') - ->greeting('Thank you for submitting!') - ->line("Your link to {$this->link->domain} is live on the blog and ready to be seen by my visitors.") - ->line('By the way, if you want to do me a favor, follow me on [X](https://x.com/benjamincrozat) and [LinkedIn](https://www.linkedin.com/in/benjamincrozat/)!'); - } -} diff --git a/app/Notifications/LinkWaitingForValidation.php b/app/Notifications/LinkWaitingForValidation.php deleted file mode 100644 index e1a2e1ca..00000000 --- a/app/Notifications/LinkWaitingForValidation.php +++ /dev/null @@ -1,31 +0,0 @@ -subject('A link is waiting for validation') - ->greeting('Heads up!') - ->line("A link to {$this->link->domain} from {$this->link->user->name} is waiting for validation.") - ->action('Check', url('/admin/links')); - } -} diff --git a/app/Notifications/NewComment.php b/app/Notifications/NewComment.php index 4f4551df..ad349163 100644 --- a/app/Notifications/NewComment.php +++ b/app/Notifications/NewComment.php @@ -1,13 +1,15 @@ subject('New comment posted') - ->greeting("{$this->comment->user->name} commented on [{$this->comment->post->title}](" . route('posts.show', $this->comment->post) . ')') - ->action('Check Comment', route('posts.show', $this->comment->post) . '#comments'); + ->greeting("{$this->comment->user->name} commented on [{$this->comment->post->title}](".route('posts.show', $this->comment->post).')') + ->action('Check Comment', route('posts.show', $this->comment->post).'#comments'); } } diff --git a/app/Notifications/NewReply.php b/app/Notifications/NewReply.php index a1255794..a5a8ff6a 100644 --- a/app/Notifications/NewReply.php +++ b/app/Notifications/NewReply.php @@ -1,13 +1,15 @@ subject('Someone replied to your comment') - ->greeting("{$this->reply->user->name} replied to your comment on [{$this->reply->post->title}](" . route('posts.show', $this->reply->post) . ')') - ->action('Check Reply', route('posts.show', $this->reply->post) . '#comments'); + ->greeting("{$this->reply->user->name} replied to your comment on [{$this->reply->post->title}](".route('posts.show', $this->reply->post).')') + ->action('Check Reply', route('posts.show', $this->reply->post).'#comments'); } } diff --git a/app/Notifications/Welcome.php b/app/Notifications/Welcome.php index 3cbc051e..35b28c80 100644 --- a/app/Notifications/Welcome.php +++ b/app/Notifications/Welcome.php @@ -1,29 +1,31 @@ subject('Your welcome gifts') ->greeting('Thank you for signing up!') - ->line('You can now **post comments** or [**submit links**](' . route('links.index') . ') to content you find useful or wrote.') + ->line('You can now **post comments** to the content you find useful.') ->line('If you want to keep reading, here are some popular articles:'); Post::query() @@ -34,18 +36,18 @@ public function toMail(User $user) : MailMessage ->limit(5) ->get() ->each(fn (Post $post) => $mailMessage->line( - "- [$post->title](" . route('posts.show', $post) . ')' + "- [$post->title](".route('posts.show', $post).')' )); return $mailMessage - ->line('I also have a selection of [great software deals](' . route('deals') . ') for developers:') - ->line('- [Unlock the power of Git on Mac and Windows](' . route('merchants.show', 'tower') . ')') - ->line('- [Know who visits your site](' . route('merchants.show', 'fathom-analytics') . ')') - ->line('- [Easily deploy PHP web apps](' . route('merchants.show', 'cloudways-php') . ')') - ->line('- [Send emails to your users](' . route('merchants.show', 'mailcoach') . ')') - ->line('- [Rank higher on Google](' . route('merchants.show', 'wincher') . ')') - ->line('- [Monitor your site\'s uptime, speed, and SSL](' . route('merchants.show', 'uptimia') . ')') - ->line('And if you are old school like me, subscribe to the [Atom feed](' . route('feeds.main') . ').') - ->line('Find me on [X](https://x.com/benjamincrozat) and [LinkedIn](https://www.linkedin.com/in/benjamincrozat/).'); + // ->line('I also have a selection of [great software deals](' . route('deals') . ') for developers:') + // ->line('- [Unlock the power of Git on Mac and Windows](' . route('merchants.show', 'tower') . ')') + // ->line('- [Know who visits your site](' . route('merchants.show', 'fathom-analytics') . ')') + // ->line('- [Easily deploy PHP web apps](' . route('merchants.show', 'cloudways-php') . ')') + // ->line('- [Send emails to your users](' . route('merchants.show', 'mailcoach') . ')') + // ->line('- [Rank higher on Google](' . route('merchants.show', 'wincher') . ')') + // ->line('- [Monitor your site\'s uptime, speed, and SSL](' . route('merchants.show', 'uptimia') . ')') + // ->line('If you are old school like me, subscribe to the [Atom feed](' . route('feeds.main') . ').') + ->line('Find me on [X](https://x.com/carlossantosdev) and [LinkedIn](https://www.linkedin.com/in/carlossantosdev/).'); } } diff --git a/app/Policies/CommentPolicy.php b/app/Policies/CommentPolicy.php index 8f584f0a..6babeca2 100644 --- a/app/Policies/CommentPolicy.php +++ b/app/Policies/CommentPolicy.php @@ -1,25 +1,29 @@ isAdmin()) { return true; } + + return null; } - public function delete(User $user, Comment $comment) : bool + public function delete(User $user, Comment $comment): bool { return $comment->user->is($user); } - public function create(User $user) : bool + public function create(User $user): bool { return true; } diff --git a/app/Policies/MetricPolicy.php b/app/Policies/MetricPolicy.php index 7ac974c0..485c0bf5 100644 --- a/app/Policies/MetricPolicy.php +++ b/app/Policies/MetricPolicy.php @@ -1,23 +1,25 @@ $dateTimePicker->defaultDateDisplayFormat('Y/m/d')); + DateTimePicker::configureUsing(fn (DateTimePicker $dateTimePicker): DateTimePicker => $dateTimePicker->defaultDateDisplayFormat('Y/m/d')); - DateTimePicker::configureUsing(fn (DateTimePicker $dateTimePicker) => $dateTimePicker->defaultDateTimeDisplayFormat('Y/m/d H:i')); + DateTimePicker::configureUsing(fn (DateTimePicker $dateTimePicker): DateTimePicker => $dateTimePicker->defaultDateTimeDisplayFormat('Y/m/d H:i')); - DateTimePicker::configureUsing(fn (DateTimePicker $dateTimePicker) => $dateTimePicker->defaultDateTimeWithSecondsDisplayFormat('Y/m/d H:i:s')); + DateTimePicker::configureUsing(fn (DateTimePicker $dateTimePicker): DateTimePicker => $dateTimePicker->defaultDateTimeWithSecondsDisplayFormat('Y/m/d H:i:s')); } - public function panel(Panel $panel) : Panel + public function panel(Panel $panel): Panel { return $panel ->default() diff --git a/app/Providers/HorizonServiceProvider.php b/app/Providers/HorizonServiceProvider.php index 83e16ca2..11b5fbf6 100644 --- a/app/Providers/HorizonServiceProvider.php +++ b/app/Providers/HorizonServiceProvider.php @@ -1,25 +1,28 @@ isAdmin(); - }); + Gate::define('viewHorizon', fn (User $user): bool => $user->isAdmin()); } } diff --git a/app/Str.php b/app/Str.php index d15021fc..d27c98c5 100644 --- a/app/Str.php +++ b/app/Str.php @@ -1,26 +1,31 @@ [ @@ -38,7 +43,7 @@ public static function markdown($string, array $options = [], array $extensions // Open external links in a new window. 'external_link' => [ 'internal_hosts' => [ - preg_replace('/https?:\/\//', '', config('app.url')), + preg_replace('/https?:\/\//', '', (string) config('app.url')), ], 'open_in_new_window' => true, ], @@ -64,7 +69,7 @@ public static function markdown($string, array $options = [], array $extensions return (string) $converter->convert($string); } - public static function lightdown($string, array $options = [], array $extensions = []) : string // @pest-ignore-type + public static function lightdown(string $string, array $options = [], array $extensions = []): string // @pest-ignore-type { $options = array_merge([ 'disallowed_raw_html' => [ @@ -73,7 +78,7 @@ public static function lightdown($string, array $options = [], array $extensions // Open external links in a new window. 'external_link' => [ 'internal_hosts' => [ - preg_replace('/https?:\/\//', '', config('app.url')), + preg_replace('/https?:\/\//', '', (string) config('app.url')), ], 'open_in_new_window' => true, ], @@ -91,10 +96,10 @@ public static function lightdown($string, array $options = [], array $extensions ->addRenderer(Code::class, new InlineCodeBlockRenderer) ->addRenderer(Image::class, new class implements NodeRendererInterface { - public function render(Node $node, ChildNodeRendererInterface $childRenderer) : string + public function render(Node $node, ChildNodeRendererInterface $childRenderer): string { if (! $node instanceof Image) { - throw new \InvalidArgumentException('Incompatible node type: ' . $node::class); + throw new InvalidArgumentException('Incompatible node type: '.$node::class); } // Return only the alt-text (contents of the child nodes), without an tag. @@ -113,9 +118,9 @@ public function render(Node $node, ChildNodeRendererInterface $childRenderer) : * This is a recursive method that will traverse the given * node and all of its children to get the text content. */ - protected static function childrenToText(Node $node) : string + protected static function childrenToText(Node $node): string { - return implode('', array_map(function (Node $child) { + return implode('', array_map(function (Node $child): string { if ($child instanceof AbstractStringContainer) { return $child->getLiteral(); } diff --git a/app/helpers.php b/app/helpers.php index a1c8f044..9786594b 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -1,5 +1,7 @@ * }> */ - function extract_headings_from_markdown(string $markdown) : array + function extract_headings_from_markdown(string $markdown): array { // Split the markdown into lines (supports various newline types). $lines = preg_split('/\R/', $markdown); @@ -31,9 +33,9 @@ function extract_headings_from_markdown(string $markdown) : array foreach ($lines as $line) { // Look for markdown headings (one or more '#' followed by a space and then text). if (preg_match('/^(#+)\s+(.*)$/', $line, $matches)) { - $level = strlen($matches[1]); // The heading level is determined by the number of '#' characters + $level = mb_strlen($matches[1]); // The heading level is determined by the number of '#' characters - $text = trim(strip_tags(Str::markdown($matches[2]))); + $text = mb_trim(strip_tags(Str::markdown($matches[2]))); $node = [ 'level' => $level, @@ -43,11 +45,11 @@ function extract_headings_from_markdown(string $markdown) : array ]; // Pop the stack until we find a heading of a lower level. - while (! empty($stack) && end($stack)['level'] >= $level) { + while ($stack !== [] && end($stack)['level'] >= $level) { array_pop($stack); } - if (empty($stack)) { + if ($stack === []) { // No parent heading found; this is a top-level heading. $headings[] = $node; diff --git a/bootstrap/app.php b/bootstrap/app.php index cabc9944..81d7c08d 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -1,30 +1,30 @@ withRouting( web: [ - __DIR__ . '/../routes/auth.php', - __DIR__ . '/../routes/legacy.php', - __DIR__ . '/../routes/user.php', - __DIR__ . '/../routes/shortener.php', - __DIR__ . '/../routes/guest.php', + __DIR__.'/../routes/auth.php', + __DIR__.'/../routes/legacy.php', + __DIR__.'/../routes/user.php', + __DIR__.'/../routes/shortener.php', + __DIR__.'/../routes/guest.php', ], - commands: __DIR__ . '/../routes/console.php', + commands: __DIR__.'/../routes/console.php', ) - ->withMiddleware(function (Middleware $middleware) { + ->withMiddleware(function (Middleware $middleware): void { $middleware ->redirectGuestsTo('/login') - ->append(HandleRedirects::class) - ->web(TrackVisit::class); + ->append(HandleRedirects::class); }) - ->withExceptions(function (Exceptions $exceptions) { + ->withExceptions(function (Exceptions $exceptions): void { Integration::handles($exceptions); }) ->create(); diff --git a/bootstrap/providers.php b/bootstrap/providers.php index 2a33f62c..00070cc4 100644 --- a/bootstrap/providers.php +++ b/bootstrap/providers.php @@ -1,5 +1,7 @@ =5.5" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35||^5.6.3||^9.5", - "yoast/phpunit-polyfills": "^1.0" - }, - "suggest": { - "ext-awscrt": "Make sure you install awscrt native extension to use any of the functionality." - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "AWS SDK Common Runtime Team", - "email": "aws-sdk-common-runtime@amazon.com" - } - ], - "description": "AWS Common Runtime for PHP", - "homepage": "https://github.com/awslabs/aws-crt-php", - "keywords": [ - "amazon", - "aws", - "crt", - "sdk" - ], - "support": { - "issues": "https://github.com/awslabs/aws-crt-php/issues", - "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.7" - }, - "time": "2024-10-18T22:15:13+00:00" - }, - { - "name": "aws/aws-sdk-php", - "version": "3.351.5", - "source": { - "type": "git", - "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "2f00efa2544d158ea366c1e1174097ef330ec883" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/2f00efa2544d158ea366c1e1174097ef330ec883", - "reference": "2f00efa2544d158ea366c1e1174097ef330ec883", - "shasum": "" - }, - "require": { - "aws/aws-crt-php": "^1.2.3", - "ext-json": "*", - "ext-pcre": "*", - "ext-simplexml": "*", - "guzzlehttp/guzzle": "^7.4.5", - "guzzlehttp/promises": "^2.0", - "guzzlehttp/psr7": "^2.4.5", - "mtdowling/jmespath.php": "^2.8.0", - "php": ">=8.1", - "psr/http-message": "^2.0" - }, - "require-dev": { - "andrewsville/php-token-reflection": "^1.4", - "aws/aws-php-sns-message-validator": "~1.0", - "behat/behat": "~3.0", - "composer/composer": "^2.7.8", - "dms/phpunit-arraysubset-asserts": "^0.4.0", - "doctrine/cache": "~1.4", - "ext-dom": "*", - "ext-openssl": "*", - "ext-pcntl": "*", - "ext-sockets": "*", - "phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5", - "psr/cache": "^2.0 || ^3.0", - "psr/simple-cache": "^2.0 || ^3.0", - "sebastian/comparator": "^1.2.3 || ^4.0 || ^5.0", - "symfony/filesystem": "^v6.4.0 || ^v7.1.0", - "yoast/phpunit-polyfills": "^2.0" - }, - "suggest": { - "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", - "doctrine/cache": "To use the DoctrineCacheAdapter", - "ext-curl": "To send requests using cURL", - "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", - "ext-sockets": "To use client-side monitoring" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "Aws\\": "src/" - }, - "exclude-from-classmap": [ - "src/data/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Amazon Web Services", - "homepage": "http://aws.amazon.com" - } - ], - "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", - "homepage": "http://aws.amazon.com/sdkforphp", - "keywords": [ - "amazon", - "aws", - "cloud", - "dynamodb", - "ec2", - "glacier", - "s3", - "sdk" - ], - "support": { - "forum": "https://github.com/aws/aws-sdk-php/discussions", - "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.351.5" - }, - "time": "2025-07-23T18:04:16+00:00" + "time": "2025-07-30T15:45:57+00:00" }, { "name": "blade-ui-kit/blade-heroicons", @@ -866,39 +715,41 @@ "time": "2025-02-18T11:09:44+00:00" }, { - "name": "composer/ca-bundle", - "version": "1.5.7", + "name": "codeat3/blade-codicons", + "version": "1.34.0", "source": { "type": "git", - "url": "https://github.com/composer/ca-bundle.git", - "reference": "d665d22c417056996c59019579f1967dfe5c1e82" + "url": "https://github.com/codeat3/blade-codicons.git", + "reference": "edda8111ac5f042d0921db531f97317ec6a8de53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/d665d22c417056996c59019579f1967dfe5c1e82", - "reference": "d665d22c417056996c59019579f1967dfe5c1e82", + "url": "https://api.github.com/repos/codeat3/blade-codicons/zipball/edda8111ac5f042d0921db531f97317ec6a8de53", + "reference": "edda8111ac5f042d0921db531f97317ec6a8de53", "shasum": "" }, "require": { - "ext-openssl": "*", - "ext-pcre": "*", - "php": "^7.2 || ^8.0" + "blade-ui-kit/blade-icons": "^1.1", + "illuminate/support": "^8.0|^9.0|^10.0|^11.0|^12.0", + "php": "^7.4|^8.0" }, "require-dev": { - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^8 || ^9", - "psr/log": "^1.0 || ^2.0 || ^3.0", - "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "codeat3/blade-icon-generation-helpers": "^0.10", + "codeat3/phpcs-styles": "^1.0", + "orchestra/testbench": "^6.0|^7.0|^8.0|^9.0|^10.0", + "phpunit/phpunit": "^9.0|^10.5|^11.0" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.x-dev" + "laravel": { + "providers": [ + "Codeat3\\BladeCodicons\\BladeCodiconsServiceProvider" + ] } }, "autoload": { "psr-4": { - "Composer\\CaBundle\\": "src" + "Codeat3\\BladeCodicons\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -907,39 +758,174 @@ ], "authors": [ { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" + "name": "Swapnil Sarwe", + "homepage": "https://swapnilsarwe.com" + }, + { + "name": "Dries Vints", + "homepage": "https://driesvints.com" } ], - "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "description": "A package to easily make use of \"VSCode Codicons\" in your Laravel Blade views.", + "homepage": "https://github.com/codeat3/blade-codicons", "keywords": [ - "cabundle", - "cacert", - "certificate", - "ssl", - "tls" + "blade", + "codicons", + "laravel" ], "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.5.7" + "issues": "https://github.com/codeat3/blade-codicons/issues", + "source": "https://github.com/codeat3/blade-codicons/tree/1.34.0" }, "funding": [ { - "url": "https://packagist.com", - "type": "custom" + "url": "https://github.com/swapnilsarwe", + "type": "github" + } + ], + "time": "2025-02-25T09:37:40+00:00" + }, + { + "name": "codeat3/blade-eos-icons", + "version": "1.15.0", + "source": { + "type": "git", + "url": "https://github.com/codeat3/blade-eos-icons.git", + "reference": "e00a7f6b2a2d72f679c4ec6d9bc80293746da154" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/codeat3/blade-eos-icons/zipball/e00a7f6b2a2d72f679c4ec6d9bc80293746da154", + "reference": "e00a7f6b2a2d72f679c4ec6d9bc80293746da154", + "shasum": "" + }, + "require": { + "blade-ui-kit/blade-icons": "^1.1", + "illuminate/support": "^8.0|^9.0|^10.0|^11.0|^12.0", + "php": "^7.4|^8.0" + }, + "require-dev": { + "codeat3/blade-icon-generation-helpers": "^0.10", + "codeat3/phpcs-styles": "^1.0", + "orchestra/testbench": "^6.0|^7.0|^8.0|^9.0|^10.0", + "phpunit/phpunit": "^9.0|^10.5|^11.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Codeat3\\BladeEosIcons\\BladeEosIconsServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Codeat3\\BladeEosIcons\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Swapnil Sarwe", + "homepage": "https://swapnilsarwe.com" }, { - "url": "https://github.com/composer", + "name": "Dries Vints", + "homepage": "https://driesvints.com" + } + ], + "description": "A package to easily make use of \"EOS Icons\" in your Laravel Blade views.", + "homepage": "https://github.com/codeat3/blade-eos-icons", + "keywords": [ + "blade", + "eos-icons", + "laravel" + ], + "support": { + "issues": "https://github.com/codeat3/blade-eos-icons/issues", + "source": "https://github.com/codeat3/blade-eos-icons/tree/1.15.0" + }, + "funding": [ + { + "url": "https://github.com/swapnilsarwe", "type": "github" + } + ], + "time": "2025-02-25T08:32:48+00:00" + }, + { + "name": "codeat3/blade-simple-icons", + "version": "7.9.0", + "source": { + "type": "git", + "url": "https://github.com/codeat3/blade-simple-icons.git", + "reference": "bc67fef6a05741e0606574219b5942b0e68a0ec3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/codeat3/blade-simple-icons/zipball/bc67fef6a05741e0606574219b5942b0e68a0ec3", + "reference": "bc67fef6a05741e0606574219b5942b0e68a0ec3", + "shasum": "" + }, + "require": { + "blade-ui-kit/blade-icons": "^1.1", + "illuminate/support": "^8.0|^9.0|^10.0|^11.0|^12.0", + "php": "^7.4|^8.0" + }, + "require-dev": { + "codeat3/blade-icon-generation-helpers": "^0.10", + "codeat3/phpcs-styles": "^1.0", + "orchestra/testbench": "^6.0|^7.0|^8.0|^9.0|^10.0", + "phpunit/phpunit": "^9.0|^10.5|^11.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Codeat3\\BladeSimpleIcons\\BladeSimpleIconsServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Codeat3\\BladeSimpleIcons\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Swapnil Sarwe", + "homepage": "https://swapnilsarwe.com" }, { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" + "name": "Dries Vints", + "homepage": "https://driesvints.com" } ], - "time": "2025-05-26T15:08:54+00:00" + "description": "A package to easily make use of \"Simple Icons\" in your Laravel Blade views. ", + "homepage": "https://github.com/codeat3/blade-simple-icons", + "keywords": [ + "blade", + "laravel", + "simpleicons" + ], + "support": { + "issues": "https://github.com/codeat3/blade-simple-icons/issues", + "source": "https://github.com/codeat3/blade-simple-icons/tree/7.9.0" + }, + "funding": [ + { + "url": "https://github.com/swapnilsarwe", + "type": "github" + } + ], + "time": "2025-08-06T09:24:01+00:00" }, { "name": "danharrin/date-format-converter", @@ -1123,33 +1109,32 @@ }, { "name": "doctrine/inflector", - "version": "2.0.10", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc" + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/5817d0659c5b50c9b950feb9af7b9668e2c436bc", - "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^11.0", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/phpstan-strict-rules": "^1.3", - "phpunit/phpunit": "^8.5 || ^9.5", - "vimeo/psalm": "^4.25 || ^5.4" + "doctrine/coding-standard": "^12.0 || ^13.0", + "phpstan/phpstan": "^1.12 || ^2.0", + "phpstan/phpstan-phpunit": "^1.4 || ^2.0", + "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", + "phpunit/phpunit": "^8.5 || ^12.2" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + "Doctrine\\Inflector\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1194,7 +1179,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.10" + "source": "https://github.com/doctrine/inflector/tree/2.1.0" }, "funding": [ { @@ -1210,7 +1195,7 @@ "type": "tidelift" } ], - "time": "2024-02-18T20:23:39+00:00" + "time": "2025-08-10T19:31:58+00:00" }, { "name": "doctrine/lexer", @@ -1421,107 +1406,18 @@ ], "time": "2025-03-06T22:45:56+00:00" }, - { - "name": "embed/embed", - "version": "v4.4.17", - "source": { - "type": "git", - "url": "https://github.com/php-embed/Embed.git", - "reference": "b2ea091a5586c14ea5f2c5bf52fb0ef38e5aef87" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-embed/Embed/zipball/b2ea091a5586c14ea5f2c5bf52fb0ef38e5aef87", - "reference": "b2ea091a5586c14ea5f2c5bf52fb0ef38e5aef87", - "shasum": "" - }, - "require": { - "composer/ca-bundle": "^1.0", - "ext-curl": "*", - "ext-dom": "*", - "ext-json": "*", - "ext-mbstring": "*", - "ml/json-ld": "^1.1", - "oscarotero/html-parser": "^0.1.4", - "php": "^7.4|^8", - "psr/http-client": "^1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0|^2.0" - }, - "require-dev": { - "brick/varexporter": "^0.3.1", - "friendsofphp/php-cs-fixer": "^2.0", - "nyholm/psr7": "^1.2", - "oscarotero/php-cs-fixer-config": "^1.0", - "phpunit/phpunit": "^9.0", - "symfony/css-selector": "^5.0" - }, - "suggest": { - "symfony/css-selector": "If you want to get elements using css selectors" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "Embed\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Oscar Otero", - "email": "oom@oscarotero.com", - "homepage": "http://oscarotero.com", - "role": "Developer" - } - ], - "description": "PHP library to retrieve page info using oembed, opengraph, etc", - "homepage": "https://github.com/oscarotero/Embed", - "keywords": [ - "embed", - "embedly", - "oembed", - "opengraph", - "twitter cards" - ], - "support": { - "email": "oom@oscarotero.com", - "issues": "https://github.com/oscarotero/Embed/issues", - "source": "https://github.com/php-embed/Embed/tree/v4.4.17" - }, - "funding": [ - { - "url": "https://paypal.me/oscarotero", - "type": "custom" - }, - { - "url": "https://github.com/oscarotero", - "type": "github" - }, - { - "url": "https://www.patreon.com/misteroom", - "type": "patreon" - } - ], - "time": "2025-05-13T12:42:29+00:00" - }, { "name": "filament/actions", "version": "4.x-dev", "source": { "type": "git", "url": "https://github.com/filamentphp/actions.git", - "reference": "035b51d43e127b33e7d87d36eba48d9fbc336a06" + "reference": "2dca3c47813ee4528d3929547ca01c0d965c64cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/actions/zipball/035b51d43e127b33e7d87d36eba48d9fbc336a06", - "reference": "035b51d43e127b33e7d87d36eba48d9fbc336a06", + "url": "https://api.github.com/repos/filamentphp/actions/zipball/2dca3c47813ee4528d3929547ca01c0d965c64cf", + "reference": "2dca3c47813ee4528d3929547ca01c0d965c64cf", "shasum": "" }, "require": { @@ -1557,7 +1453,7 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-07-22T09:41:11+00:00" + "time": "2025-08-10T20:27:33+00:00" }, { "name": "filament/filament", @@ -1565,12 +1461,12 @@ "source": { "type": "git", "url": "https://github.com/filamentphp/panels.git", - "reference": "18a865e951ee92abbfb5424a5c20fdbd883256fb" + "reference": "f468aa4d726d51a15963fa52990627823dba4206" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/panels/zipball/18a865e951ee92abbfb5424a5c20fdbd883256fb", - "reference": "18a865e951ee92abbfb5424a5c20fdbd883256fb", + "url": "https://api.github.com/repos/filamentphp/panels/zipball/f468aa4d726d51a15963fa52990627823dba4206", + "reference": "f468aa4d726d51a15963fa52990627823dba4206", "shasum": "" }, "require": { @@ -1614,7 +1510,7 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-07-22T09:40:59+00:00" + "time": "2025-08-10T20:27:36+00:00" }, { "name": "filament/forms", @@ -1622,12 +1518,12 @@ "source": { "type": "git", "url": "https://github.com/filamentphp/forms.git", - "reference": "2b4da6bb71130cc095568b69067a9150e9abc6d7" + "reference": "d7a3133ec51b33b21c4a8c56e9b2bbe9cb42962a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/forms/zipball/2b4da6bb71130cc095568b69067a9150e9abc6d7", - "reference": "2b4da6bb71130cc095568b69067a9150e9abc6d7", + "url": "https://api.github.com/repos/filamentphp/forms/zipball/d7a3133ec51b33b21c4a8c56e9b2bbe9cb42962a", + "reference": "d7a3133ec51b33b21c4a8c56e9b2bbe9cb42962a", "shasum": "" }, "require": { @@ -1664,7 +1560,7 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-07-22T09:41:10+00:00" + "time": "2025-08-10T20:27:38+00:00" }, { "name": "filament/infolists", @@ -1672,19 +1568,18 @@ "source": { "type": "git", "url": "https://github.com/filamentphp/infolists.git", - "reference": "d392d438ec4ede7ed476db155179f6f19b73a371" + "reference": "bc88ac4ee74d069030bfb0d54af8a4d09c5da45c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/infolists/zipball/d392d438ec4ede7ed476db155179f6f19b73a371", - "reference": "d392d438ec4ede7ed476db155179f6f19b73a371", + "url": "https://api.github.com/repos/filamentphp/infolists/zipball/bc88ac4ee74d069030bfb0d54af8a4d09c5da45c", + "reference": "bc88ac4ee74d069030bfb0d54af8a4d09c5da45c", "shasum": "" }, "require": { "filament/actions": "self.version", "filament/schemas": "self.version", "filament/support": "self.version", - "phiki/phiki": "^1.1", "php": "^8.2" }, "type": "library", @@ -1710,7 +1605,7 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-07-22T09:41:18+00:00" + "time": "2025-08-10T20:27:59+00:00" }, { "name": "filament/notifications", @@ -1765,12 +1660,12 @@ "source": { "type": "git", "url": "https://github.com/filamentphp/schemas.git", - "reference": "3b95968d124403b080f50c02d0c595b814b120f5" + "reference": "79b0412e85ff0191d87e537fdfd74f444c1468eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/schemas/zipball/3b95968d124403b080f50c02d0c595b814b120f5", - "reference": "3b95968d124403b080f50c02d0c595b814b120f5", + "url": "https://api.github.com/repos/filamentphp/schemas/zipball/79b0412e85ff0191d87e537fdfd74f444c1468eb", + "reference": "79b0412e85ff0191d87e537fdfd74f444c1468eb", "shasum": "" }, "require": { @@ -1803,7 +1698,7 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-07-16T13:41:24+00:00" + "time": "2025-08-07T14:06:42+00:00" }, { "name": "filament/support", @@ -1811,12 +1706,12 @@ "source": { "type": "git", "url": "https://github.com/filamentphp/support.git", - "reference": "b9fd9b30e29ee4ed4c645bb202dbaa2b5907bd7e" + "reference": "1c6ed2b3697600f5213bee6760e0f709a746a968" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/support/zipball/b9fd9b30e29ee4ed4c645bb202dbaa2b5907bd7e", - "reference": "b9fd9b30e29ee4ed4c645bb202dbaa2b5907bd7e", + "url": "https://api.github.com/repos/filamentphp/support/zipball/1c6ed2b3697600f5213bee6760e0f709a746a968", + "reference": "1c6ed2b3697600f5213bee6760e0f709a746a968", "shasum": "" }, "require": { @@ -1861,7 +1756,7 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-07-22T09:40:51+00:00" + "time": "2025-08-10T20:27:33+00:00" }, { "name": "filament/tables", @@ -1869,12 +1764,12 @@ "source": { "type": "git", "url": "https://github.com/filamentphp/tables.git", - "reference": "b794cbdf79100834d519dc2524f30a345da6ad5d" + "reference": "0ac17c4c831750fe3c6e0b431f414203b57d9697" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/tables/zipball/b794cbdf79100834d519dc2524f30a345da6ad5d", - "reference": "b794cbdf79100834d519dc2524f30a345da6ad5d", + "url": "https://api.github.com/repos/filamentphp/tables/zipball/0ac17c4c831750fe3c6e0b431f414203b57d9697", + "reference": "0ac17c4c831750fe3c6e0b431f414203b57d9697", "shasum": "" }, "require": { @@ -1906,7 +1801,7 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-07-22T09:41:17+00:00" + "time": "2025-08-10T20:27:53+00:00" }, { "name": "filament/widgets", @@ -1914,12 +1809,12 @@ "source": { "type": "git", "url": "https://github.com/filamentphp/widgets.git", - "reference": "144fcc9613e74c40dc7bd517b009754832ea9e83" + "reference": "ec65855e6b572900eefbb514608fb2ce92abb8d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/widgets/zipball/144fcc9613e74c40dc7bd517b009754832ea9e83", - "reference": "144fcc9613e74c40dc7bd517b009754832ea9e83", + "url": "https://api.github.com/repos/filamentphp/widgets/zipball/ec65855e6b572900eefbb514608fb2ce92abb8d0", + "reference": "ec65855e6b572900eefbb514608fb2ce92abb8d0", "shasum": "" }, "require": { @@ -1950,7 +1845,7 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2025-07-21T10:09:49+00:00" + "time": "2025-08-10T20:27:56+00:00" }, { "name": "firebase/php-jwt", @@ -2015,71 +1910,6 @@ }, "time": "2025-04-09T20:32:01+00:00" }, - { - "name": "fivefilters/readability.php", - "version": "v3.3.3", - "source": { - "type": "git", - "url": "https://github.com/fivefilters/readability.php.git", - "reference": "e2ee7b9e49eae89ac7ed2c74b15718100a73b4c8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fivefilters/readability.php/zipball/e2ee7b9e49eae89ac7ed2c74b15718100a73b4c8", - "reference": "e2ee7b9e49eae89ac7ed2c74b15718100a73b4c8", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "league/uri": "^7.0", - "masterminds/html5": "^2.0", - "php": ">=8.1", - "psr/log": "^1.0 || ^2.0 || ^3.0" - }, - "require-dev": { - "monolog/monolog": "^3.0", - "phpunit/phpunit": "^10.0 || ^11.0" - }, - "suggest": { - "monolog/monolog": "Allow logging debug information" - }, - "type": "library", - "autoload": { - "psr-4": { - "fivefilters\\Readability\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Andres Rey", - "email": "andreskrey@gmail.com", - "role": "Original Developer" - }, - { - "name": "Keyvan Minoukadeh", - "email": "keyvan@fivefilters.org", - "homepage": "https://www.fivefilters.org", - "role": "Developer/Maintainer" - } - ], - "description": "A PHP port of Readability.js", - "homepage": "https://github.com/fivefilters/readability.php", - "keywords": [ - "html", - "readability" - ], - "support": { - "issues": "https://github.com/fivefilters/readability.php/issues", - "source": "https://github.com/fivefilters/readability.php/tree/v3.3.3" - }, - "time": "2025-04-26T23:45:37+00:00" - }, { "name": "fruitcake/php-cors", "version": "v1.3.0", @@ -2624,58 +2454,6 @@ ], "time": "2025-02-03T10:55:03+00:00" }, - { - "name": "jaybizzle/crawler-detect", - "version": "v1.3.5", - "source": { - "type": "git", - "url": "https://github.com/JayBizzle/Crawler-Detect.git", - "reference": "fbf1a3e81d61b088e7af723fb3c7a4ee92ac7e34" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/fbf1a3e81d61b088e7af723fb3c7a4ee92ac7e34", - "reference": "fbf1a3e81d61b088e7af723fb3c7a4ee92ac7e34", - "shasum": "" - }, - "require": { - "php": ">=7.1.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8|^5.5|^6.5|^9.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Jaybizzle\\CrawlerDetect\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mark Beech", - "email": "m@rkbee.ch", - "role": "Developer" - } - ], - "description": "CrawlerDetect is a PHP class for detecting bots/crawlers/spiders via the user agent", - "homepage": "https://github.com/JayBizzle/Crawler-Detect/", - "keywords": [ - "crawler", - "crawler detect", - "crawler detector", - "crawlerdetect", - "php crawler detect" - ], - "support": { - "issues": "https://github.com/JayBizzle/Crawler-Detect/issues", - "source": "https://github.com/JayBizzle/Crawler-Detect/tree/v1.3.5" - }, - "time": "2025-06-11T17:58:05+00:00" - }, { "name": "jean85/pretty-package-versions", "version": "2.1.1", @@ -2738,16 +2516,16 @@ }, { "name": "kirschbaum-development/eloquent-power-joins", - "version": "4.2.6", + "version": "4.2.7", "source": { "type": "git", "url": "https://github.com/kirschbaum-development/eloquent-power-joins.git", - "reference": "72cff1e838bb3f826dc09a5566219ad7fa56237f" + "reference": "f2f8d3575a54d91b3e5058d65ac1fccb3ea7dd94" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/kirschbaum-development/eloquent-power-joins/zipball/72cff1e838bb3f826dc09a5566219ad7fa56237f", - "reference": "72cff1e838bb3f826dc09a5566219ad7fa56237f", + "url": "https://api.github.com/repos/kirschbaum-development/eloquent-power-joins/zipball/f2f8d3575a54d91b3e5058d65ac1fccb3ea7dd94", + "reference": "f2f8d3575a54d91b3e5058d65ac1fccb3ea7dd94", "shasum": "" }, "require": { @@ -2795,9 +2573,9 @@ ], "support": { "issues": "https://github.com/kirschbaum-development/eloquent-power-joins/issues", - "source": "https://github.com/kirschbaum-development/eloquent-power-joins/tree/4.2.6" + "source": "https://github.com/kirschbaum-development/eloquent-power-joins/tree/4.2.7" }, - "time": "2025-07-10T16:55:34+00:00" + "time": "2025-08-06T10:46:13+00:00" }, { "name": "knplabs/github-api", @@ -2956,16 +2734,16 @@ }, { "name": "laravel/framework", - "version": "v12.21.0", + "version": "v12.22.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "ac8c4e73bf1b5387b709f7736d41427e6af1c93b" + "reference": "d33ee45184126f32f593d4b809a846ed88a1dc43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/ac8c4e73bf1b5387b709f7736d41427e6af1c93b", - "reference": "ac8c4e73bf1b5387b709f7736d41427e6af1c93b", + "url": "https://api.github.com/repos/laravel/framework/zipball/d33ee45184126f32f593d4b809a846ed88a1dc43", + "reference": "d33ee45184126f32f593d4b809a846ed88a1dc43", "shasum": "" }, "require": { @@ -3167,20 +2945,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-07-22T15:41:55+00:00" + "time": "2025-08-08T13:58:03+00:00" }, { "name": "laravel/horizon", - "version": "v5.33.1", + "version": "v5.33.2", "source": { "type": "git", "url": "https://github.com/laravel/horizon.git", - "reference": "50057bca1f1dcc9fbd5ff6d65143833babd784b3" + "reference": "baa36725ed24dbbcd7ddb4ba3dcfd4c0f22028ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/horizon/zipball/50057bca1f1dcc9fbd5ff6d65143833babd784b3", - "reference": "50057bca1f1dcc9fbd5ff6d65143833babd784b3", + "url": "https://api.github.com/repos/laravel/horizon/zipball/baa36725ed24dbbcd7ddb4ba3dcfd4c0f22028ed", + "reference": "baa36725ed24dbbcd7ddb4ba3dcfd4c0f22028ed", "shasum": "" }, "require": { @@ -3201,13 +2979,13 @@ "require-dev": { "mockery/mockery": "^1.0", "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.0|^10.4|^11.5", - "predis/predis": "^1.1|^2.0" + "phpstan/phpstan": "^1.10|^2.0", + "phpunit/phpunit": "^9.0|^10.4|^11.5|^12.0", + "predis/predis": "^1.1|^2.0|^3.0" }, "suggest": { "ext-redis": "Required to use the Redis PHP driver.", - "predis/predis": "Required when not using the Redis PHP driver (^1.1|^2.0)." + "predis/predis": "Required when not using the Redis PHP driver (^1.1|^2.0|^3.0)." }, "type": "library", "extra": { @@ -3245,9 +3023,9 @@ ], "support": { "issues": "https://github.com/laravel/horizon/issues", - "source": "https://github.com/laravel/horizon/tree/v5.33.1" + "source": "https://github.com/laravel/horizon/tree/v5.33.2" }, - "time": "2025-06-16T13:48:30+00:00" + "time": "2025-08-05T02:30:15+00:00" }, { "name": "laravel/prompts", @@ -3870,61 +3648,6 @@ }, "time": "2025-06-25T13:29:59+00:00" }, - { - "name": "league/flysystem-aws-s3-v3", - "version": "3.29.0", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "c6ff6d4606e48249b63f269eba7fabdb584e76a9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/c6ff6d4606e48249b63f269eba7fabdb584e76a9", - "reference": "c6ff6d4606e48249b63f269eba7fabdb584e76a9", - "shasum": "" - }, - "require": { - "aws/aws-sdk-php": "^3.295.10", - "league/flysystem": "^3.10.0", - "league/mime-type-detection": "^1.0.0", - "php": "^8.0.2" - }, - "conflict": { - "guzzlehttp/guzzle": "<7.0", - "guzzlehttp/ringphp": "<1.1.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "League\\Flysystem\\AwsS3V3\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Frank de Jonge", - "email": "info@frankdejonge.nl" - } - ], - "description": "AWS S3 filesystem adapter for Flysystem.", - "keywords": [ - "Flysystem", - "aws", - "file", - "files", - "filesystem", - "s3", - "storage" - ], - "support": { - "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.29.0" - }, - "time": "2024-08-17T13:10:48+00:00" - }, { "name": "league/flysystem-local", "version": "3.30.0", @@ -4440,16 +4163,16 @@ }, { "name": "masterminds/html5", - "version": "2.9.0", + "version": "2.10.0", "source": { "type": "git", "url": "https://github.com/Masterminds/html5-php.git", - "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6" + "reference": "fcf91eb64359852f00d921887b219479b4f21251" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", - "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251", + "reference": "fcf91eb64359852f00d921887b219479b4f21251", "shasum": "" }, "require": { @@ -4501,113 +4224,9 @@ ], "support": { "issues": "https://github.com/Masterminds/html5-php/issues", - "source": "https://github.com/Masterminds/html5-php/tree/2.9.0" - }, - "time": "2024-03-31T07:05:07+00:00" - }, - { - "name": "ml/iri", - "version": "1.1.4", - "target-dir": "ML/IRI", - "source": { - "type": "git", - "url": "https://github.com/lanthaler/IRI.git", - "reference": "cbd44fa913e00ea624241b38cefaa99da8d71341" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/lanthaler/IRI/zipball/cbd44fa913e00ea624241b38cefaa99da8d71341", - "reference": "cbd44fa913e00ea624241b38cefaa99da8d71341", - "shasum": "" - }, - "require": { - "lib-pcre": ">=4.0", - "php": ">=5.3.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "ML\\IRI": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Markus Lanthaler", - "email": "mail@markus-lanthaler.com", - "homepage": "http://www.markus-lanthaler.com", - "role": "Developer" - } - ], - "description": "IRI handling for PHP", - "homepage": "http://www.markus-lanthaler.com", - "keywords": [ - "URN", - "iri", - "uri", - "url" - ], - "support": { - "issues": "https://github.com/lanthaler/IRI/issues", - "source": "https://github.com/lanthaler/IRI/tree/master" + "source": "https://github.com/Masterminds/html5-php/tree/2.10.0" }, - "time": "2014-01-21T13:43:39+00:00" - }, - { - "name": "ml/json-ld", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/lanthaler/JsonLD.git", - "reference": "537e68e87a6bce23e57c575cd5dcac1f67ce25d8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/lanthaler/JsonLD/zipball/537e68e87a6bce23e57c575cd5dcac1f67ce25d8", - "reference": "537e68e87a6bce23e57c575cd5dcac1f67ce25d8", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ml/iri": "^1.1.1", - "php": ">=5.3.0" - }, - "require-dev": { - "json-ld/tests": "1.0", - "phpunit/phpunit": "^4" - }, - "type": "library", - "autoload": { - "psr-4": { - "ML\\JsonLD\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Markus Lanthaler", - "email": "mail@markus-lanthaler.com", - "homepage": "http://www.markus-lanthaler.com", - "role": "Developer" - } - ], - "description": "JSON-LD Processor for PHP", - "homepage": "http://www.markus-lanthaler.com", - "keywords": [ - "JSON-LD", - "jsonld" - ], - "support": { - "issues": "https://github.com/lanthaler/JsonLD/issues", - "source": "https://github.com/lanthaler/JsonLD/tree/1.2.1" - }, - "time": "2022-09-29T08:45:17+00:00" + "time": "2025-07-25T09:04:22+00:00" }, { "name": "monolog/monolog", @@ -4702,94 +4321,28 @@ }, "funding": [ { - "url": "https://github.com/Seldaek", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", - "type": "tidelift" - } - ], - "time": "2025-03-24T10:02:05+00:00" - }, - { - "name": "mtdowling/jmespath.php", - "version": "2.8.0", - "source": { - "type": "git", - "url": "https://github.com/jmespath/jmespath.php.git", - "reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/a2a865e05d5f420b50cc2f85bb78d565db12a6bc", - "reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc", - "shasum": "" - }, - "require": { - "php": "^7.2.5 || ^8.0", - "symfony/polyfill-mbstring": "^1.17" - }, - "require-dev": { - "composer/xdebug-handler": "^3.0.3", - "phpunit/phpunit": "^8.5.33" - }, - "bin": [ - "bin/jp.php" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.8-dev" - } - }, - "autoload": { - "files": [ - "src/JmesPath.php" - ], - "psr-4": { - "JmesPath\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" + "url": "https://github.com/Seldaek", + "type": "github" }, { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" } ], - "description": "Declaratively specify how to extract elements from a JSON document", - "keywords": [ - "json", - "jsonpath" - ], - "support": { - "issues": "https://github.com/jmespath/jmespath.php/issues", - "source": "https://github.com/jmespath/jmespath.php/tree/2.8.0" - }, - "time": "2024-09-04T18:46:31+00:00" + "time": "2025-03-24T10:02:05+00:00" }, { "name": "nesbot/carbon", - "version": "3.10.1", + "version": "3.10.2", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "1fd1935b2d90aef2f093c5e35f7ae1257c448d00" + "reference": "76b5c07b8a9d2025ed1610e14cef1f3fd6ad2c24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/1fd1935b2d90aef2f093c5e35f7ae1257c448d00", - "reference": "1fd1935b2d90aef2f093c5e35f7ae1257c448d00", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/76b5c07b8a9d2025ed1610e14cef1f3fd6ad2c24", + "reference": "76b5c07b8a9d2025ed1610e14cef1f3fd6ad2c24", "shasum": "" }, "require": { @@ -4881,31 +4434,31 @@ "type": "tidelift" } ], - "time": "2025-06-21T15:19:35+00:00" + "time": "2025-08-02T09:36:06+00:00" }, { "name": "nette/php-generator", - "version": "v4.1.8", + "version": "v4.2.0", "source": { "type": "git", "url": "https://github.com/nette/php-generator.git", - "reference": "42806049a7774a2bd316c958f5dcf01c6b5c56fa" + "reference": "4707546a1f11badd72f5d82af4f8a6bc64bd56ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/php-generator/zipball/42806049a7774a2bd316c958f5dcf01c6b5c56fa", - "reference": "42806049a7774a2bd316c958f5dcf01c6b5c56fa", + "url": "https://api.github.com/repos/nette/php-generator/zipball/4707546a1f11badd72f5d82af4f8a6bc64bd56ac", + "reference": "4707546a1f11badd72f5d82af4f8a6bc64bd56ac", "shasum": "" }, "require": { - "nette/utils": "^3.2.9 || ^4.0", - "php": "8.0 - 8.4" + "nette/utils": "^4.0.6", + "php": "8.1 - 8.5" }, "require-dev": { - "jetbrains/phpstorm-attributes": "dev-master", + "jetbrains/phpstorm-attributes": "^1.2", "nette/tester": "^2.4", - "nikic/php-parser": "^4.18 || ^5.0", - "phpstan/phpstan": "^1.0", + "nikic/php-parser": "^5.0", + "phpstan/phpstan-nette": "^2.0@stable", "tracy/tracy": "^2.8" }, "suggest": { @@ -4914,10 +4467,13 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { + "psr-4": { + "Nette\\": "src" + }, "classmap": [ "src/" ] @@ -4938,7 +4494,7 @@ "homepage": "https://nette.org/contributors" } ], - "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.4 features.", + "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.5 features.", "homepage": "https://nette.org", "keywords": [ "code", @@ -4948,9 +4504,9 @@ ], "support": { "issues": "https://github.com/nette/php-generator/issues", - "source": "https://github.com/nette/php-generator/tree/v4.1.8" + "source": "https://github.com/nette/php-generator/tree/v4.2.0" }, - "time": "2025-03-31T00:29:29+00:00" + "time": "2025-08-06T18:24:31+00:00" }, { "name": "nette/schema", @@ -5016,29 +4572,29 @@ }, { "name": "nette/utils", - "version": "v4.0.7", + "version": "v4.0.8", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2" + "reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/e67c4061eb40b9c113b218214e42cb5a0dda28f2", - "reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2", + "url": "https://api.github.com/repos/nette/utils/zipball/c930ca4e3cf4f17dcfb03037703679d2396d2ede", + "reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede", "shasum": "" }, "require": { - "php": "8.0 - 8.4" + "php": "8.0 - 8.5" }, "conflict": { "nette/finder": "<3", "nette/schema": "<1.2.2" }, "require-dev": { - "jetbrains/phpstorm-attributes": "dev-master", + "jetbrains/phpstorm-attributes": "^1.2", "nette/tester": "^2.5", - "phpstan/phpstan": "^1.0", + "phpstan/phpstan-nette": "^2.0@stable", "tracy/tracy": "^2.9" }, "suggest": { @@ -5056,6 +4612,9 @@ } }, "autoload": { + "psr-4": { + "Nette\\": "src" + }, "classmap": [ "src/" ] @@ -5096,9 +4655,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.7" + "source": "https://github.com/nette/utils/tree/v4.0.8" }, - "time": "2025-06-03T04:55:08+00:00" + "time": "2025-08-06T21:43:34+00:00" }, { "name": "nicmart/tree", @@ -5156,16 +4715,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.5.0", + "version": "v5.6.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "ae59794362fe85e051a58ad36b289443f57be7a9" + "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", - "reference": "ae59794362fe85e051a58ad36b289443f57be7a9", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/221b0d0fdf1369c71047ad1d18bb5880017bbc56", + "reference": "221b0d0fdf1369c71047ad1d18bb5880017bbc56", "shasum": "" }, "require": { @@ -5208,9 +4767,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.0" }, - "time": "2025-05-31T08:24:38+00:00" + "time": "2025-07-27T20:03:57+00:00" }, { "name": "nunomaduro/termwind", @@ -5651,57 +5210,64 @@ "time": "2025-07-07T06:15:55+00:00" }, { - "name": "oscarotero/html-parser", - "version": "v0.1.8", + "name": "owenvoke/blade-fontawesome", + "version": "v2.9.1", "source": { "type": "git", - "url": "https://github.com/oscarotero/html-parser.git", - "reference": "10f3219267a365d9433f2f7d1694209c9d436c8d" + "url": "https://github.com/owenvoke/blade-fontawesome.git", + "reference": "94dcd0c78f43f8234b0d9c76c903ecd288b8b0d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/oscarotero/html-parser/zipball/10f3219267a365d9433f2f7d1694209c9d436c8d", - "reference": "10f3219267a365d9433f2f7d1694209c9d436c8d", + "url": "https://api.github.com/repos/owenvoke/blade-fontawesome/zipball/94dcd0c78f43f8234b0d9c76c903ecd288b8b0d1", + "reference": "94dcd0c78f43f8234b0d9c76c903ecd288b8b0d1", "shasum": "" }, "require": { - "php": "^7.2 || ^8" + "blade-ui-kit/blade-icons": "^1.5", + "illuminate/support": "^10.34|^11.0|^12.0", + "php": "^8.1" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.11", - "phpunit/phpunit": "^8.0" + "laravel/pint": "^1.13", + "orchestra/testbench": "^8.12|^9.0|^10.0", + "pestphp/pest": "^2.26|^3.7", + "phpstan/phpstan": "^1.10|^2.1", + "symfony/var-dumper": "^6.3|^7.2" }, "type": "library", + "extra": { + "laravel": { + "providers": [ + "OwenVoke\\BladeFontAwesome\\BladeFontAwesomeServiceProvider" + ] + } + }, "autoload": { "psr-4": { - "HtmlParser\\": "src" + "OwenVoke\\BladeFontAwesome\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ + "description": "A package to easily make use of Font Awesome in your Laravel Blade views", + "support": { + "issues": "https://github.com/owenvoke/blade-fontawesome/issues", + "source": "https://github.com/owenvoke/blade-fontawesome/tree/v2.9.1" + }, + "funding": [ { - "name": "Oscar Otero", - "email": "oom@oscarotero.com", - "homepage": "http://oscarotero.com", - "role": "Developer" + "url": "https://ecologi.com/owenvoke?gift-trees", + "type": "custom" + }, + { + "url": "https://github.com/owenvoke", + "type": "github" } ], - "description": "Parse html strings to DOMDocument", - "homepage": "https://github.com/oscarotero/html-parser", - "keywords": [ - "dom", - "html", - "parser" - ], - "support": { - "email": "oom@oscarotero.com", - "issues": "https://github.com/oscarotero/html-parser/issues", - "source": "https://github.com/oscarotero/html-parser/tree/v0.1.8" - }, - "time": "2023-11-29T20:28:41+00:00" + "time": "2025-03-28T16:03:42+00:00" }, { "name": "paragonie/constant_time_encoding", @@ -5821,38 +5387,40 @@ "time": "2020-10-15T08:29:30+00:00" }, { - "name": "phiki/phiki", - "version": "v1.1.6", + "name": "peckphp/peck", + "version": "v0.1.3", "source": { "type": "git", - "url": "https://github.com/phikiphp/phiki.git", - "reference": "3174d8cb309bdccc32b7a33500379de76148256b" + "url": "https://github.com/peckphp/peck.git", + "reference": "907d7221b057519627811c7c752d65ffa9163dc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phikiphp/phiki/zipball/3174d8cb309bdccc32b7a33500379de76148256b", - "reference": "3174d8cb309bdccc32b7a33500379de76148256b", + "url": "https://api.github.com/repos/peckphp/peck/zipball/907d7221b057519627811c7c752d65ffa9163dc1", + "reference": "907d7221b057519627811c7c752d65ffa9163dc1", "shasum": "" }, "require": { - "league/commonmark": "^2.5.3", - "php": "^8.2" + "nunomaduro/termwind": "^1.17.0|^2.3.0", + "php": "^8.2", + "symfony/console": "^6.4.17|^7.2.1", + "symfony/finder": "^6.4.17|^7.2.2" }, "require-dev": { - "illuminate/support": "^11.30", - "laravel/pint": "^1.18.1", - "pestphp/pest": "^3.5.1", - "phpstan/extension-installer": "^1.4.3", - "phpstan/phpstan": "^2.0", - "symfony/var-dumper": "^7.1.6" + "laravel/pint": "^1.20.0", + "pestphp/pest": "^2.36|^3.7.4", + "pestphp/pest-plugin-type-coverage": "^2.8.7|^3.2.3", + "phpstan/phpstan": "^1.12.16", + "rector/rector": "^1.2.10", + "symfony/var-dumper": "^7.2.0" }, "bin": [ - "bin/phiki" + "bin/peck" ], "type": "library", "autoload": { "psr-4": { - "Phiki\\": "src/" + "Peck\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -5861,18 +5429,36 @@ ], "authors": [ { - "name": "Ryan Chandler", - "email": "support@ryangjchandler.co.uk", - "homepage": "https://ryangjchandler.co.uk", - "role": "Developer" + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" } ], - "description": "Syntax highlighting using TextMate grammars in PHP.", + "description": "Peck is a powerful CLI tool designed to identify pure wording or spelling (grammar) mistakes in your codebase.", + "keywords": [ + "checker", + "codebase", + "php", + "spelling" + ], "support": { - "issues": "https://github.com/phikiphp/phiki/issues", - "source": "https://github.com/phikiphp/phiki/tree/v1.1.6" + "issues": "https://github.com/peckphp/peck/issues", + "source": "https://github.com/peckphp/peck/tree/v0.1.3" }, - "time": "2025-06-06T20:18:29+00:00" + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2025-03-31T15:16:31+00:00" }, { "name": "php-http/cache-plugin", @@ -7078,16 +6664,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.9", + "version": "v0.12.10", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "1b801844becfe648985372cb4b12ad6840245ace" + "reference": "6e80abe6f2257121f1eb9a4c55bf29d921025b22" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/1b801844becfe648985372cb4b12ad6840245ace", - "reference": "1b801844becfe648985372cb4b12ad6840245ace", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/6e80abe6f2257121f1eb9a4c55bf29d921025b22", + "reference": "6e80abe6f2257121f1eb9a4c55bf29d921025b22", "shasum": "" }, "require": { @@ -7137,12 +6723,11 @@ "authors": [ { "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" + "email": "justin@justinhileman.info" } ], "description": "An interactive shell for modern PHP.", - "homepage": "http://psysh.org", + "homepage": "https://psysh.org", "keywords": [ "REPL", "console", @@ -7151,9 +6736,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.9" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.10" }, - "time": "2025-06-23T02:35:06+00:00" + "time": "2025-08-04T12:39:37+00:00" }, { "name": "ralouphie/getallheaders", @@ -8010,97 +7595,6 @@ ], "time": "2024-05-17T09:06:10+00:00" }, - { - "name": "spatie/laravel-activitylog", - "version": "4.10.2", - "source": { - "type": "git", - "url": "https://github.com/spatie/laravel-activitylog.git", - "reference": "bb879775d487438ed9a99e64f09086b608990c10" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-activitylog/zipball/bb879775d487438ed9a99e64f09086b608990c10", - "reference": "bb879775d487438ed9a99e64f09086b608990c10", - "shasum": "" - }, - "require": { - "illuminate/config": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0", - "illuminate/database": "^8.69 || ^9.27 || ^10.0 || ^11.0 || ^12.0", - "illuminate/support": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0", - "php": "^8.1", - "spatie/laravel-package-tools": "^1.6.3" - }, - "require-dev": { - "ext-json": "*", - "orchestra/testbench": "^6.23 || ^7.0 || ^8.0 || ^9.0 || ^10.0", - "pestphp/pest": "^1.20 || ^2.0 || ^3.0" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Spatie\\Activitylog\\ActivitylogServiceProvider" - ] - } - }, - "autoload": { - "files": [ - "src/helpers.php" - ], - "psr-4": { - "Spatie\\Activitylog\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Freek Van der Herten", - "email": "freek@spatie.be", - "homepage": "https://spatie.be", - "role": "Developer" - }, - { - "name": "Sebastian De Deyne", - "email": "sebastian@spatie.be", - "homepage": "https://spatie.be", - "role": "Developer" - }, - { - "name": "Tom Witkowski", - "email": "dev.gummibeer@gmail.com", - "homepage": "https://gummibeer.de", - "role": "Developer" - } - ], - "description": "A very simple activity logger to monitor the users of your website or application", - "homepage": "https://github.com/spatie/activitylog", - "keywords": [ - "activity", - "laravel", - "log", - "spatie", - "user" - ], - "support": { - "issues": "https://github.com/spatie/laravel-activitylog/issues", - "source": "https://github.com/spatie/laravel-activitylog/tree/4.10.2" - }, - "funding": [ - { - "url": "https://spatie.be/open-source/support-us", - "type": "custom" - }, - { - "url": "https://github.com/spatie", - "type": "github" - } - ], - "time": "2025-06-15T06:59:49+00:00" - }, { "name": "spatie/laravel-collection-macros", "version": "8.0.0", @@ -8740,16 +8234,16 @@ }, { "name": "symfony/console", - "version": "v7.3.1", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "9e27aecde8f506ba0fd1d9989620c04a87697101" + "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/9e27aecde8f506ba0fd1d9989620c04a87697101", - "reference": "9e27aecde8f506ba0fd1d9989620c04a87697101", + "url": "https://api.github.com/repos/symfony/console/zipball/5f360ebc65c55265a74d23d7fe27f957870158a1", + "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1", "shasum": "" }, "require": { @@ -8814,7 +8308,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.1" + "source": "https://github.com/symfony/console/tree/v7.3.2" }, "funding": [ { @@ -8825,12 +8319,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T19:55:54+00:00" + "time": "2025-07-30T17:13:41+00:00" }, { "name": "symfony/css-selector", @@ -9033,16 +8531,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.3.1", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "35b55b166f6752d6aaf21aa042fc5ed280fce235" + "reference": "0b31a944fcd8759ae294da4d2808cbc53aebd0c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/35b55b166f6752d6aaf21aa042fc5ed280fce235", - "reference": "35b55b166f6752d6aaf21aa042fc5ed280fce235", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/0b31a944fcd8759ae294da4d2808cbc53aebd0c3", + "reference": "0b31a944fcd8759ae294da4d2808cbc53aebd0c3", "shasum": "" }, "require": { @@ -9090,7 +8588,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.3.1" + "source": "https://github.com/symfony/error-handler/tree/v7.3.2" }, "funding": [ { @@ -9101,12 +8599,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-13T07:48:40+00:00" + "time": "2025-07-07T08:17:57+00:00" }, { "name": "symfony/event-dispatcher", @@ -9266,16 +8768,16 @@ }, { "name": "symfony/finder", - "version": "v7.3.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d" + "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/ec2344cf77a48253bbca6939aa3d2477773ea63d", - "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d", + "url": "https://api.github.com/repos/symfony/finder/zipball/2a6614966ba1074fa93dae0bc804227422df4dfe", + "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe", "shasum": "" }, "require": { @@ -9310,7 +8812,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.3.0" + "source": "https://github.com/symfony/finder/tree/v7.3.2" }, "funding": [ { @@ -9321,25 +8823,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-12-30T19:00:26+00:00" + "time": "2025-07-15T13:41:35+00:00" }, { "name": "symfony/html-sanitizer", - "version": "v7.3.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/html-sanitizer.git", - "reference": "cf21254e982b12276329940ca4af5e623ee06c58" + "reference": "3388e208450fcac57d24aef4d5ae41037b663630" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/html-sanitizer/zipball/cf21254e982b12276329940ca4af5e623ee06c58", - "reference": "cf21254e982b12276329940ca4af5e623ee06c58", + "url": "https://api.github.com/repos/symfony/html-sanitizer/zipball/3388e208450fcac57d24aef4d5ae41037b663630", + "reference": "3388e208450fcac57d24aef4d5ae41037b663630", "shasum": "" }, "require": { @@ -9379,7 +8885,7 @@ "sanitizer" ], "support": { - "source": "https://github.com/symfony/html-sanitizer/tree/v7.3.0" + "source": "https://github.com/symfony/html-sanitizer/tree/v7.3.2" }, "funding": [ { @@ -9390,25 +8896,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-03-31T08:49:55+00:00" + "time": "2025-07-10T08:29:33+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.3.1", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "23dd60256610c86a3414575b70c596e5deff6ed9" + "reference": "6877c122b3a6cc3695849622720054f6e6fa5fa6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/23dd60256610c86a3414575b70c596e5deff6ed9", - "reference": "23dd60256610c86a3414575b70c596e5deff6ed9", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6877c122b3a6cc3695849622720054f6e6fa5fa6", + "reference": "6877c122b3a6cc3695849622720054f6e6fa5fa6", "shasum": "" }, "require": { @@ -9458,7 +8968,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.3.1" + "source": "https://github.com/symfony/http-foundation/tree/v7.3.2" }, "funding": [ { @@ -9469,25 +8979,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-23T15:07:14+00:00" + "time": "2025-07-10T08:47:49+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.3.1", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "1644879a66e4aa29c36fe33dfa6c54b450ce1831" + "reference": "6ecc895559ec0097e221ed2fd5eb44d5fede083c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/1644879a66e4aa29c36fe33dfa6c54b450ce1831", - "reference": "1644879a66e4aa29c36fe33dfa6c54b450ce1831", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6ecc895559ec0097e221ed2fd5eb44d5fede083c", + "reference": "6ecc895559ec0097e221ed2fd5eb44d5fede083c", "shasum": "" }, "require": { @@ -9572,7 +9086,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.3.1" + "source": "https://github.com/symfony/http-kernel/tree/v7.3.2" }, "funding": [ { @@ -9583,25 +9097,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-28T08:24:55+00:00" + "time": "2025-07-31T10:45:04+00:00" }, { "name": "symfony/mailer", - "version": "v7.3.1", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "b5db5105b290bdbea5ab27b89c69effcf1cb3368" + "reference": "d43e84d9522345f96ad6283d5dfccc8c1cfc299b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/b5db5105b290bdbea5ab27b89c69effcf1cb3368", - "reference": "b5db5105b290bdbea5ab27b89c69effcf1cb3368", + "url": "https://api.github.com/repos/symfony/mailer/zipball/d43e84d9522345f96ad6283d5dfccc8c1cfc299b", + "reference": "d43e84d9522345f96ad6283d5dfccc8c1cfc299b", "shasum": "" }, "require": { @@ -9652,7 +9170,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.3.1" + "source": "https://github.com/symfony/mailer/tree/v7.3.2" }, "funding": [ { @@ -9663,25 +9181,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T19:55:54+00:00" + "time": "2025-07-15T11:36:08+00:00" }, { "name": "symfony/mime", - "version": "v7.3.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9" + "reference": "e0a0f859148daf1edf6c60b398eb40bfc96697d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9", - "reference": "0e7b19b2f399c31df0cdbe5d8cbf53f02f6cfcd9", + "url": "https://api.github.com/repos/symfony/mime/zipball/e0a0f859148daf1edf6c60b398eb40bfc96697d1", + "reference": "e0a0f859148daf1edf6c60b398eb40bfc96697d1", "shasum": "" }, "require": { @@ -9736,7 +9258,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.3.0" + "source": "https://github.com/symfony/mime/tree/v7.3.2" }, "funding": [ { @@ -9747,25 +9269,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-02-19T08:51:26+00:00" + "time": "2025-07-15T13:41:35+00:00" }, { "name": "symfony/options-resolver", - "version": "v7.3.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "afb9a8038025e5dbc657378bfab9198d75f10fca" + "reference": "119bcf13e67dbd188e5dbc74228b1686f66acd37" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/afb9a8038025e5dbc657378bfab9198d75f10fca", - "reference": "afb9a8038025e5dbc657378bfab9198d75f10fca", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/119bcf13e67dbd188e5dbc74228b1686f66acd37", + "reference": "119bcf13e67dbd188e5dbc74228b1686f66acd37", "shasum": "" }, "require": { @@ -9803,7 +9329,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.3.0" + "source": "https://github.com/symfony/options-resolver/tree/v7.3.2" }, "funding": [ { @@ -9814,12 +9340,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-04T13:12:05+00:00" + "time": "2025-07-15T11:36:08+00:00" }, { "name": "symfony/polyfill-ctype", @@ -10604,16 +10134,16 @@ }, { "name": "symfony/routing", - "version": "v7.3.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "8e213820c5fea844ecea29203d2a308019007c15" + "reference": "7614b8ca5fa89b9cd233e21b627bfc5774f586e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/8e213820c5fea844ecea29203d2a308019007c15", - "reference": "8e213820c5fea844ecea29203d2a308019007c15", + "url": "https://api.github.com/repos/symfony/routing/zipball/7614b8ca5fa89b9cd233e21b627bfc5774f586e4", + "reference": "7614b8ca5fa89b9cd233e21b627bfc5774f586e4", "shasum": "" }, "require": { @@ -10665,7 +10195,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.3.0" + "source": "https://github.com/symfony/routing/tree/v7.3.2" }, "funding": [ { @@ -10676,12 +10206,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-05-24T20:43:28+00:00" + "time": "2025-07-15T11:36:08+00:00" }, { "name": "symfony/service-contracts", @@ -10768,16 +10302,16 @@ }, { "name": "symfony/string", - "version": "v7.3.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125" + "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f3570b8c61ca887a9e2938e85cb6458515d2b125", - "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125", + "url": "https://api.github.com/repos/symfony/string/zipball/42f505aff654e62ac7ac2ce21033818297ca89ca", + "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca", "shasum": "" }, "require": { @@ -10835,7 +10369,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.0" + "source": "https://github.com/symfony/string/tree/v7.3.2" }, "funding": [ { @@ -10846,25 +10380,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-20T20:19:01+00:00" + "time": "2025-07-10T08:47:49+00:00" }, { "name": "symfony/translation", - "version": "v7.3.1", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "241d5ac4910d256660238a7ecf250deba4c73063" + "reference": "81b48f4daa96272efcce9c7a6c4b58e629df3c90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/241d5ac4910d256660238a7ecf250deba4c73063", - "reference": "241d5ac4910d256660238a7ecf250deba4c73063", + "url": "https://api.github.com/repos/symfony/translation/zipball/81b48f4daa96272efcce9c7a6c4b58e629df3c90", + "reference": "81b48f4daa96272efcce9c7a6c4b58e629df3c90", "shasum": "" }, "require": { @@ -10931,7 +10469,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.3.1" + "source": "https://github.com/symfony/translation/tree/v7.3.2" }, "funding": [ { @@ -10942,12 +10480,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T19:55:54+00:00" + "time": "2025-07-30T17:31:46+00:00" }, { "name": "symfony/translation-contracts", @@ -11103,16 +10645,16 @@ }, { "name": "symfony/var-dumper", - "version": "v7.3.1", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "6e209fbe5f5a7b6043baba46fe5735a4b85d0d42" + "reference": "53205bea27450dc5c65377518b3275e126d45e75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6e209fbe5f5a7b6043baba46fe5735a4b85d0d42", - "reference": "6e209fbe5f5a7b6043baba46fe5735a4b85d0d42", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/53205bea27450dc5c65377518b3275e126d45e75", + "reference": "53205bea27450dc5c65377518b3275e126d45e75", "shasum": "" }, "require": { @@ -11124,7 +10666,6 @@ "symfony/console": "<6.4" }, "require-dev": { - "ext-iconv": "*", "symfony/console": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", "symfony/process": "^6.4|^7.0", @@ -11167,7 +10708,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.3.1" + "source": "https://github.com/symfony/var-dumper/tree/v7.3.2" }, "funding": [ { @@ -11178,12 +10719,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T19:55:54+00:00" + "time": "2025-07-29T20:02:46+00:00" }, { "name": "tempest/highlight", @@ -11937,16 +11482,16 @@ }, { "name": "filp/whoops", - "version": "2.18.3", + "version": "2.18.4", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "59a123a3d459c5a23055802237cb317f609867e5" + "reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/59a123a3d459c5a23055802237cb317f609867e5", - "reference": "59a123a3d459c5a23055802237cb317f609867e5", + "url": "https://api.github.com/repos/filp/whoops/zipball/d2102955e48b9fd9ab24280a7ad12ed552752c4d", + "reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d", "shasum": "" }, "require": { @@ -11996,7 +11541,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.18.3" + "source": "https://github.com/filp/whoops/tree/2.18.4" }, "funding": [ { @@ -12004,7 +11549,7 @@ "type": "github" } ], - "time": "2025-06-16T00:02:10+00:00" + "time": "2025-08-08T12:00:00+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -12290,16 +11835,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.13.3", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "faed855a7b5f4d4637717c2b3863e277116beb36" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36", - "reference": "faed855a7b5f4d4637717c2b3863e277116beb36", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { @@ -12338,7 +11883,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.3" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, "funding": [ { @@ -12346,7 +11891,7 @@ "type": "tidelift" } ], - "time": "2025-07-05T12:25:42+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { "name": "nunomaduro/collision", @@ -13394,16 +12939,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.19", + "version": "2.1.22", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "473a8c30e450d87099f76313edcbb90852f9afdf" + "reference": "41600c8379eb5aee63e9413fe9e97273e25d57e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/473a8c30e450d87099f76313edcbb90852f9afdf", - "reference": "473a8c30e450d87099f76313edcbb90852f9afdf", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/41600c8379eb5aee63e9413fe9e97273e25d57e4", + "reference": "41600c8379eb5aee63e9413fe9e97273e25d57e4", "shasum": "" }, "require": { @@ -13448,7 +12993,7 @@ "type": "github" } ], - "time": "2025-07-21T19:58:24+00:00" + "time": "2025-08-04T19:17:37+00:00" }, { "name": "phpunit/php-code-coverage", @@ -13886,6 +13431,66 @@ ], "time": "2025-03-23T16:02:11+00:00" }, + { + "name": "rector/rector", + "version": "2.1.2", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "40a71441dd73fa150a66102f5ca1364c44fc8fff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/40a71441dd73fa150a66102f5ca1364c44fc8fff", + "reference": "40a71441dd73fa150a66102f5ca1364c44fc8fff", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0", + "phpstan/phpstan": "^2.1.18" + }, + "conflict": { + "rector/rector-doctrine": "*", + "rector/rector-downgrade-php": "*", + "rector/rector-phpunit": "*", + "rector/rector-symfony": "*" + }, + "suggest": { + "ext-dom": "To manipulate phpunit.xml via the custom-rule command" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "homepage": "https://getrector.com/", + "keywords": [ + "automation", + "dev", + "migration", + "refactoring" + ], + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/2.1.2" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2025-07-17T19:30:06+00:00" + }, { "name": "sebastian/cli-parser", "version": "3.0.2", @@ -14058,16 +13663,16 @@ }, { "name": "sebastian/comparator", - "version": "6.3.1", + "version": "6.3.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959" + "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/24b8fbc2c8e201bb1308e7b05148d6ab393b6959", - "reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/85c77556683e6eee4323e4c5468641ca0237e2e8", + "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8", "shasum": "" }, "require": { @@ -14126,15 +13731,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.1" + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.2" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" } ], - "time": "2025-03-07T06:57:01+00:00" + "time": "2025-08-10T08:07:46+00:00" }, { "name": "sebastian/complexity", @@ -14715,16 +14332,16 @@ }, { "name": "sebastian/type", - "version": "5.1.2", + "version": "5.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e" + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/a8a7e30534b0eb0c77cd9d07e82de1a114389f5e", - "reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449", "shasum": "" }, "require": { @@ -14760,15 +14377,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/type/issues", "security": "https://github.com/sebastianbergmann/type/security/policy", - "source": "https://github.com/sebastianbergmann/type/tree/5.1.2" + "source": "https://github.com/sebastianbergmann/type/tree/5.1.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" } ], - "time": "2025-03-18T13:35:50+00:00" + "time": "2025-08-09T06:55:48+00:00" }, { "name": "sebastian/version", diff --git a/config/advertisers.php b/config/advertisers.php deleted file mode 100644 index 99d460d8..00000000 --- a/config/advertisers.php +++ /dev/null @@ -1,6 +0,0 @@ - 'https://sevalla.com/application-hosting/', - 'vemetric' => 'https://vemetric.com', -]; diff --git a/config/app.php b/config/app.php index ca0b477e..888526b0 100644 --- a/config/app.php +++ b/config/app.php @@ -1,5 +1,7 @@ env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_cache_'), + 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'), ]; diff --git a/config/cors.php b/config/cors.php index cda51994..b9e13903 100644 --- a/config/cors.php +++ b/config/cors.php @@ -1,5 +1,7 @@ [ 'cluster' => env('REDIS_CLUSTER', 'redis'), - 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_database_'), + 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), ], 'default' => [ diff --git a/config/debugbar.php b/config/debugbar.php index a1126cdb..05ac4743 100644 --- a/config/debugbar.php +++ b/config/debugbar.php @@ -1,5 +1,7 @@ [ 'main' => [ @@ -18,8 +20,8 @@ */ 'url' => '/feed', - 'title' => config('app.name') . ' posts', - 'description' => 'Latest articles from ' . config('app.name'), + 'title' => config('app.name').' posts', + 'description' => 'Latest articles from '.config('app.name'), 'language' => 'en-US', /* diff --git a/config/filesystems.php b/config/filesystems.php index 2c3b510e..ed0fc1a7 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -1,5 +1,7 @@ [ 'driver' => 'local', 'root' => storage_path('app/public'), - 'url' => env('APP_URL') . '/storage', + 'url' => env('APP_URL').'/storage', 'visibility' => 'public', 'throw' => false, ], diff --git a/config/horizon.php b/config/horizon.php index 75bec5d0..3a8d9147 100644 --- a/config/horizon.php +++ b/config/horizon.php @@ -1,5 +1,7 @@ env( 'HORIZON_PREFIX', - Str::slug(env('APP_NAME', 'laravel'), '_') . '_horizon:' + Str::slug(env('APP_NAME', 'laravel'), '_').'_horizon:' ), /* diff --git a/config/livewire.php b/config/livewire.php index a592a8fa..28091474 100644 --- a/config/livewire.php +++ b/config/livewire.php @@ -1,5 +1,7 @@ [ 'host' => env('PAPERTRAIL_URL'), 'port' => env('PAPERTRAIL_PORT'), - 'connectionString' => 'tls://' . env('PAPERTRAIL_URL') . ':' . env('PAPERTRAIL_PORT'), + 'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'), ], 'processors' => [PsrLogMessageProcessor::class], ], diff --git a/config/mail.php b/config/mail.php index 403b5a67..5d597612 100644 --- a/config/mail.php +++ b/config/mail.php @@ -1,5 +1,7 @@ [ - 'battle-ready-laravel' => [ - 'name' => 'Battle Ready Laravel', - 'author' => 'Ash Allen', - 'link' => 'https://battle-ready-laravel.com/?aff=WJ9DG', - 'image' => 'resources/img/books/battle-ready-laravel.jpg', - ], - 'consuming-apis-laravel' => [ - 'name' => 'Consuming APIs in Laravel', - 'author' => 'Ash Allen', - 'link' => 'https://consuming-apis-in-laravel.com/?aff=WJ9DG', - 'image' => 'resources/img/books/consuming-apis-laravel.jpg', - ], - 'domain-driven-laravel' => [ - 'name' => 'Domain-Driven Laravel', - 'author' => 'Martin Joo', - 'link' => 'https://martinjoo.gumroad.com/l/ddd-laravel-premium?a=266251475', - 'image' => 'resources/img/books/domain-driven-laravel.jpg', - ], - 'level-up-tailwind-css' => [ - 'name' => 'Level up with Tailwind CSS', - 'author' => 'Shruti Balasa', - 'link' => 'https://shrutibalasa.gumroad.com/l/level-up-with-tailwind-css?a=266251475', - 'image' => 'resources/img/books/level-up-tailwind-css.jpg', - ], - 'mastering-laravel-validation-rules' => [ - 'name' => 'Mastering Laravel Validation Rules', - 'author' => 'Aaron Saray / Joel Clermont', - 'link' => 'https://nocompromises.gumroad.com/l/laravel-validation?a=266251475', - 'image' => 'resources/img/books/mastering-laravel-validation.jpg', - ], - 'microservices-laravel' => [ - 'name' => 'Microservices with Laravel', - 'author' => 'Martin Joo', - 'link' => 'https://martinjoo.gumroad.com/l/microservices-with-laravel-premium?a=266251475', - 'image' => 'resources/img/books/microservices-laravel.jpg', - ], - 'vue-design-patterns' => [ - 'name' => 'Vue.js Design Patterns', - 'author' => 'Lachlan Miller', - 'link' => 'https://lachlanmiller.gumroad.com/l/vuejs-design-patterns?a=266251475', - 'image' => 'resources/img/books/vue-design-patterns.jpg', - ], - ], + 'books' => [], - 'courses' => [ - 'serverless-laravel' => 'https://jackellis.gumroad.com/l/serverlesslaravel?a=266251475', - ], + 'courses' => [], - 'services' => [ - 'cloudways-php' => 'https://www.cloudways.com/en/php-hosting.php?id=1242932', - 'cloudways' => 'https://www.cloudways.com/en/laravel-hosting?id=1242932', - 'codecademy' => 'https://www.pjtra.com/t/2-438168-292025-213588', - 'digitalocean-managed-databases' => 'https://digitalocean.pxf.io/c/3801334/1377288/15890', - 'digitalocean-managed-mysql-database' => 'https://digitalocean.pxf.io/baNjbB', - 'digitalocean' => 'https://digitalocean.pxf.io/c/3801334/1377286/15890', - 'fathom-analytics' => 'https://usefathom.com/ref/JTPOCN', - 'flare' => 'https://flareapp.io/?via=benjamincrozat', - 'headshotpro' => 'https://www.headshotpro.com/?via=benjamincrozat', - 'hostgator' => 'https://partners.hostgator.com/Py6kbX', - 'kinsta' => 'https://kinsta.com/application-hosting/?kaid=AEFAUTRRTINA', - 'mailcoach' => 'https://mailcoach.app/?via=benjamincrozat', - 'namecheap' => 'https://namecheap.pxf.io/c/3801334/1712998/5618', - 'oh-dear' => 'https://ohdear.app/?via=benjamincrozat', - 'pirsch-analytics' => 'https://pirsch.io/ref/Z0E1Nqogb5', - 'ploi' => 'https://ploi.io/register?referrer=eJAat2H4YE45BjmIwzBl', - 'savvycal' => 'https://savvycal.com/?via=benjamincrozat', - 'semrush' => 'https://semrush.sjv.io/benjamincrozat', - 'setapp' => 'https://setapp.sjv.io/c/3801334/344537/5114', - 'simple-analytics' => 'https://simpleanalytics.com/?referral=benjamincrozat', - 'skillshare' => 'https://skillshare.eqcm.net/rQWAbD', - 'tinkerwell' => 'https://tinkerwell.app/?ref=benjamincrozat', - 'tower' => 'https://www.git-tower.com/?via=benjamincrozat', - 'uptimia' => 'https://www.uptimia.com/?via=benjamincrozat', - 'untitled-ui' => 'https://store.untitledui.com?aff=WJ9DG', - 'vultr' => 'https://www.vultr.com/products/cloud-compute/?ref=9270910-8H', - 'webshare' => 'https://www.webshare.io/?referral_code=dt6d6ofk1rzp', - 'wincher' => 'https://www.wincher.com/rank-tracker?via=benjamincrozat', - ], + 'services' => [], ]; diff --git a/config/openai.php b/config/openai.php index ebf66eb4..030176c6 100644 --- a/config/openai.php +++ b/config/openai.php @@ -1,5 +1,7 @@ env('HORIZON_TOKEN'), ], - 'pirsch' => [ - 'access_key' => env('PIRSCH_ACCESS_KEY'), - 'client_id' => env('PIRSCH_CLIENT_ID'), - 'client_secret' => env('PIRSCH_CLIENT_SECRET'), - 'domain_id' => env('PIRSCH_DOMAIN_ID'), - ], - 'postmark' => [ 'token' => env('POSTMARK_TOKEN'), ], diff --git a/config/session.php b/config/session.php index 98661c4f..dc96a25c 100644 --- a/config/session.php +++ b/config/session.php @@ -1,5 +1,7 @@ env( 'SESSION_COOKIE', - Str::slug(env('APP_NAME', 'laravel'), '_') . '_session' + Str::slug(env('APP_NAME', 'laravel'), '_').'_session' ), /* diff --git a/database/factories/CategoryFactory.php b/database/factories/CategoryFactory.php index ef4a32a6..d7c0dfc5 100644 --- a/database/factories/CategoryFactory.php +++ b/database/factories/CategoryFactory.php @@ -1,5 +1,7 @@ ucfirst(fake()->word()), diff --git a/database/factories/CommentFactory.php b/database/factories/CommentFactory.php index c9c9c0e7..bb69354e 100644 --- a/database/factories/CommentFactory.php +++ b/database/factories/CommentFactory.php @@ -1,5 +1,7 @@ User::factory(), diff --git a/database/factories/LinkFactory.php b/database/factories/LinkFactory.php deleted file mode 100644 index cd3df6da..00000000 --- a/database/factories/LinkFactory.php +++ /dev/null @@ -1,41 +0,0 @@ - - */ -class LinkFactory extends Factory -{ - public function definition() : array - { - return [ - 'user_id' => User::factory(), - 'post_id' => Post::factory(), - 'url' => fake()->url(), - 'image_url' => 'https://picsum.photos/' . random_int(1024, 1280) . '/' . random_int(640, 720), - 'title' => fake()->sentence(), - 'description' => fake()->paragraph(), - ]; - } - - public function approved() : static - { - return $this->state([ - 'is_approved' => now(), - 'is_declined' => null, - ]); - } - - public function declined() : static - { - return $this->state([ - 'is_declined' => now(), - 'is_approved' => null, - ]); - } -} diff --git a/database/factories/MetricFactory.php b/database/factories/MetricFactory.php index e5e9657f..dc386b26 100644 --- a/database/factories/MetricFactory.php +++ b/database/factories/MetricFactory.php @@ -1,5 +1,7 @@ fake()->word(), diff --git a/database/factories/PostFactory.php b/database/factories/PostFactory.php index e747e677..b70b7f78 100644 --- a/database/factories/PostFactory.php +++ b/database/factories/PostFactory.php @@ -1,5 +1,7 @@ User::factory(), diff --git a/database/factories/ShortUrlFactory.php b/database/factories/ShortUrlFactory.php index aa928ccd..c69ccef2 100644 --- a/database/factories/ShortUrlFactory.php +++ b/database/factories/ShortUrlFactory.php @@ -1,5 +1,7 @@ fake()->url(), diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index 1ca53469..e5b0fd9e 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -1,5 +1,7 @@ unique()->safeEmail(); return [ 'name' => fake()->name(), 'github_login' => fake()->userName(), - 'avatar' => 'https://i.pravatar.cc/150?u=' . $email, + 'avatar' => 'https://i.pravatar.cc/150?u='.$email, 'github_data' => [ 'id' => fake()->unique()->randomNumber(), 'name' => fake()->name(), diff --git a/database/migrations/0001_01_01_000000_create_users_table.php b/database/migrations/0001_01_01_000000_create_users_table.php index 22a3cf9d..26484a89 100644 --- a/database/migrations/0001_01_01_000000_create_users_table.php +++ b/database/migrations/0001_01_01_000000_create_users_table.php @@ -1,14 +1,16 @@ id(); $table->string('name'); $table->string('slug')->unique(); @@ -22,7 +24,7 @@ public function up() : void $table->timestamps(); }); - Schema::create('sessions', function (Blueprint $table) { + Schema::create('sessions', function (Blueprint $table): void { $table->string('id')->primary(); $table->foreignId('user_id')->nullable()->index(); $table->string('ip_address', 45)->nullable(); @@ -32,7 +34,7 @@ public function up() : void }); } - public function down() : void + public function down(): void { Schema::dropIfExists('users'); Schema::dropIfExists('sessions'); diff --git a/database/migrations/0001_01_01_000001_create_failed_jobs_table.php b/database/migrations/0001_01_01_000001_create_failed_jobs_table.php index e1546ff0..06d93fee 100644 --- a/database/migrations/0001_01_01_000001_create_failed_jobs_table.php +++ b/database/migrations/0001_01_01_000001_create_failed_jobs_table.php @@ -1,14 +1,16 @@ id(); $table->string('uuid')->unique(); $table->text('connection'); @@ -19,7 +21,7 @@ public function up() : void }); } - public function down() : void + public function down(): void { Schema::dropIfExists('failed_jobs'); } diff --git a/database/migrations/2025_03_08_084957_create_links_table.php b/database/migrations/2025_03_08_084957_create_links_table.php deleted file mode 100644 index 7030305e..00000000 --- a/database/migrations/2025_03_08_084957_create_links_table.php +++ /dev/null @@ -1,29 +0,0 @@ -id(); - $table->foreignId('user_id'); - $table->string('url')->unique(); - $table->text('image_url')->nullable(); - $table->string('title'); - $table->text('description')->nullable(); - $table->text('notes')->nullable(); - $table->timestamp('is_approved')->nullable(); - $table->timestamp('is_declined')->nullable(); - $table->timestamps(); - }); - } - - public function down() : void - { - Schema::dropIfExists('links'); - } -}; diff --git a/database/migrations/2025_03_10_105514_create_comments_table.php b/database/migrations/2025_03_10_105514_create_comments_table.php index 0ba08c17..a59e0487 100644 --- a/database/migrations/2025_03_10_105514_create_comments_table.php +++ b/database/migrations/2025_03_10_105514_create_comments_table.php @@ -1,14 +1,16 @@ id(); $table->foreignId('user_id')->index(); $table->foreignId('post_id')->index(); @@ -20,7 +22,7 @@ public function up() : void }); } - public function down() : void + public function down(): void { Schema::dropIfExists('comments'); } diff --git a/database/migrations/2025_03_16_172544_create_categories_table.php b/database/migrations/2025_03_16_172544_create_categories_table.php index 53c44e9b..abdf4258 100644 --- a/database/migrations/2025_03_16_172544_create_categories_table.php +++ b/database/migrations/2025_03_16_172544_create_categories_table.php @@ -1,14 +1,16 @@ id(); $table->string('name'); $table->string('slug')->unique(); @@ -17,7 +19,7 @@ public function up() : void }); } - public function down() : void + public function down(): void { Schema::dropIfExists('categories'); } diff --git a/database/migrations/2025_03_20_073535_create_metrics_table.php b/database/migrations/2025_03_20_073535_create_metrics_table.php index 8afbc0a5..b5e7a104 100644 --- a/database/migrations/2025_03_20_073535_create_metrics_table.php +++ b/database/migrations/2025_03_20_073535_create_metrics_table.php @@ -1,14 +1,16 @@ id(); $table->string('key'); $table->longText('value'); @@ -16,7 +18,7 @@ public function up() : void }); } - public function down() : void + public function down(): void { Schema::dropIfExists('metrics'); } diff --git a/database/migrations/2025_04_18_062920_create_posts_table.php b/database/migrations/2025_04_18_062920_create_posts_table.php index d2f79308..6f96560c 100644 --- a/database/migrations/2025_04_18_062920_create_posts_table.php +++ b/database/migrations/2025_04_18_062920_create_posts_table.php @@ -1,14 +1,16 @@ id(); $table->foreignId('user_id')->index(); $table->string('image_path')->nullable(); @@ -26,7 +28,7 @@ public function up() : void }); } - public function down() : void + public function down(): void { Schema::dropIfExists('posts'); } diff --git a/database/migrations/2025_04_18_064159_create_category_post_table.php b/database/migrations/2025_04_18_064159_create_category_post_table.php index 8c94df16..d6cddc39 100644 --- a/database/migrations/2025_04_18_064159_create_category_post_table.php +++ b/database/migrations/2025_04_18_064159_create_category_post_table.php @@ -1,21 +1,23 @@ id(); $table->foreignId('category_id')->index(); $table->foreignId('post_id')->index(); }); } - public function down() : void + public function down(): void { Schema::dropIfExists('category_post'); } diff --git a/database/migrations/2025_04_22_054313_create_notifications_table.php b/database/migrations/2025_04_22_054313_create_notifications_table.php index b5379724..eeddea0d 100644 --- a/database/migrations/2025_04_22_054313_create_notifications_table.php +++ b/database/migrations/2025_04_22_054313_create_notifications_table.php @@ -1,17 +1,19 @@ uuid('id')->primary(); $table->string('type'); $table->morphs('notifiable'); @@ -24,7 +26,7 @@ public function up() : void /** * Reverse the migrations. */ - public function down() : void + public function down(): void { Schema::dropIfExists('notifications'); } diff --git a/database/migrations/2025_06_29_103428_create_jobs_table.php b/database/migrations/2025_06_29_103428_create_jobs_table.php index 8a875320..f0ab0042 100644 --- a/database/migrations/2025_06_29_103428_create_jobs_table.php +++ b/database/migrations/2025_06_29_103428_create_jobs_table.php @@ -1,14 +1,16 @@ bigIncrements('id'); $table->string('queue')->index(); $table->longText('payload'); @@ -19,7 +21,7 @@ public function up() : void }); } - public function down() : void + public function down(): void { Schema::dropIfExists('jobs'); } diff --git a/database/migrations/2025_06_29_104711_create_cache_table.php b/database/migrations/2025_06_29_104711_create_cache_table.php index 25c32a25..6fbf4c39 100644 --- a/database/migrations/2025_06_29_104711_create_cache_table.php +++ b/database/migrations/2025_06_29_104711_create_cache_table.php @@ -1,27 +1,29 @@ string('key')->primary(); $table->mediumText('value'); $table->integer('expiration'); }); - Schema::create('cache_locks', function (Blueprint $table) { + Schema::create('cache_locks', function (Blueprint $table): void { $table->string('key')->primary(); $table->string('owner'); $table->integer('expiration'); }); } - public function down() : void + public function down(): void { Schema::dropIfExists('cache'); Schema::dropIfExists('cache_locks'); diff --git a/database/migrations/2025_07_01_133025_add_refreshed_at_column_to_users_table.php b/database/migrations/2025_07_01_133025_add_refreshed_at_column_to_users_table.php index b96116d5..0b69f8c4 100644 --- a/database/migrations/2025_07_01_133025_add_refreshed_at_column_to_users_table.php +++ b/database/migrations/2025_07_01_133025_add_refreshed_at_column_to_users_table.php @@ -1,26 +1,28 @@ datetime('refreshed_at')->nullable(); }); - User::query()->cursor()->each(function (User $user) { + User::query()->cursor()->each(function (User $user): void { $user->update(['refreshed_at' => $user->created_at]); }); } - public function down() : void + public function down(): void { - Schema::table('users', function (Blueprint $table) { + Schema::table('users', function (Blueprint $table): void { $table->dropColumn('refreshed_at'); }); } diff --git a/database/migrations/2025_07_03_150924_add_serp_title_to_posts_table.php b/database/migrations/2025_07_03_150924_add_serp_title_to_posts_table.php index 0ae98dda..63b99534 100644 --- a/database/migrations/2025_07_03_150924_add_serp_title_to_posts_table.php +++ b/database/migrations/2025_07_03_150924_add_serp_title_to_posts_table.php @@ -1,21 +1,23 @@ string('serp_title')->nullable(); }); } - public function down() : void + public function down(): void { - Schema::table('posts', function (Blueprint $table) { + Schema::table('posts', function (Blueprint $table): void { $table->dropColumn('serp_title'); }); } diff --git a/database/migrations/2025_07_04_204047_create_redirects_table.php b/database/migrations/2025_07_04_204047_create_redirects_table.php index 74c851f7..59c7c331 100644 --- a/database/migrations/2025_07_04_204047_create_redirects_table.php +++ b/database/migrations/2025_07_04_204047_create_redirects_table.php @@ -1,14 +1,16 @@ id(); $table->string('from')->unique(); $table->string('to'); @@ -16,7 +18,7 @@ public function up() : void }); } - public function down() : void + public function down(): void { Schema::dropIfExists('redirects'); } diff --git a/database/migrations/2025_07_08_102802_add_post_id_to_links_table.php b/database/migrations/2025_07_08_102802_add_post_id_to_links_table.php deleted file mode 100644 index 21121726..00000000 --- a/database/migrations/2025_07_08_102802_add_post_id_to_links_table.php +++ /dev/null @@ -1,22 +0,0 @@ -foreignId('post_id')->nullable()->unique()->after('user_id'); - }); - } - - public function down() : void - { - Schema::table('links', function (Blueprint $table) { - $table->dropColumn('post_id'); - }); - } -}; diff --git a/database/migrations/2025_07_08_121136_add_author_column_to_links_table.php b/database/migrations/2025_07_08_121136_add_author_column_to_links_table.php deleted file mode 100644 index 1193a592..00000000 --- a/database/migrations/2025_07_08_121136_add_author_column_to_links_table.php +++ /dev/null @@ -1,22 +0,0 @@ -string('author')->nullable(); - }); - } - - public function down() : void - { - Schema::table('links', function (Blueprint $table) { - $table->dropColumn('author'); - }); - } -}; diff --git a/database/migrations/2025_07_12_230158_add_is_commercial_column_to_posts_table.php b/database/migrations/2025_07_12_230158_add_is_commercial_column_to_posts_table.php index 234271d9..20b61a05 100644 --- a/database/migrations/2025_07_12_230158_add_is_commercial_column_to_posts_table.php +++ b/database/migrations/2025_07_12_230158_add_is_commercial_column_to_posts_table.php @@ -1,21 +1,23 @@ boolean('is_commercial')->default(false)->after('description'); }); } - public function down() : void + public function down(): void { - Schema::table('posts', function (Blueprint $table) { + Schema::table('posts', function (Blueprint $table): void { $table->dropColumn('is_commercial'); }); } diff --git a/database/migrations/2025_07_16_083543_remove_unique_index_from_post_id_on_links_table.php b/database/migrations/2025_07_16_083543_remove_unique_index_from_post_id_on_links_table.php deleted file mode 100644 index d80736e7..00000000 --- a/database/migrations/2025_07_16_083543_remove_unique_index_from_post_id_on_links_table.php +++ /dev/null @@ -1,22 +0,0 @@ -dropUnique(['post_id']); - }); - } - - public function down() : void - { - Schema::table('links', function (Blueprint $table) { - $table->unique('post_id'); - }); - } -}; diff --git a/database/migrations/2025_07_20_000000_create_short_urls_table.php b/database/migrations/2025_07_20_000000_create_short_urls_table.php index 680e9f4d..04c43200 100644 --- a/database/migrations/2025_07_20_000000_create_short_urls_table.php +++ b/database/migrations/2025_07_20_000000_create_short_urls_table.php @@ -1,14 +1,16 @@ id(); $table->string('code')->unique(); $table->string('url'); @@ -16,7 +18,7 @@ public function up() : void }); } - public function down() : void + public function down(): void { Schema::dropIfExists('short_urls'); } diff --git a/database/migrations/2025_07_20_154044_drop_reactions_table.php b/database/migrations/2025_07_20_154044_drop_reactions_table.php index 3288d5bb..9b5e523e 100644 --- a/database/migrations/2025_07_20_154044_drop_reactions_table.php +++ b/database/migrations/2025_07_20_154044_drop_reactions_table.php @@ -1,19 +1,21 @@ id(); $table->foreignId('user_id')->index(); $table->foreignId('comment_id')->index(); diff --git a/database/migrations/2025_07_26_183654_add_deleted_at_to_posts_table.php b/database/migrations/2025_07_26_183654_add_deleted_at_to_posts_table.php index 14f1b209..eb5601e5 100644 --- a/database/migrations/2025_07_26_183654_add_deleted_at_to_posts_table.php +++ b/database/migrations/2025_07_26_183654_add_deleted_at_to_posts_table.php @@ -1,23 +1,25 @@ softDeletes(); } }); } - public function down() : void + public function down(): void { - Schema::table('posts', function (Blueprint $table) { + Schema::table('posts', function (Blueprint $table): void { if (Schema::hasColumn('posts', 'deleted_at')) { $table->dropSoftDeletes(); } diff --git a/database/migrations/2025_07_29_000000_add_sessions_count_index_to_posts_table.php b/database/migrations/2025_07_29_000000_add_sessions_count_index_to_posts_table.php index 61542173..c99bd750 100644 --- a/database/migrations/2025_07_29_000000_add_sessions_count_index_to_posts_table.php +++ b/database/migrations/2025_07_29_000000_add_sessions_count_index_to_posts_table.php @@ -1,21 +1,23 @@ index('sessions_count'); }); } - public function down() : void + public function down(): void { - Schema::table('posts', function (Blueprint $table) { + Schema::table('posts', function (Blueprint $table): void { $table->dropIndex(['sessions_count']); }); } diff --git a/database/seeders/CategorySeeder.php b/database/seeders/CategorySeeder.php index 576f8c2f..d0b2ba8a 100644 --- a/database/seeders/CategorySeeder.php +++ b/database/seeders/CategorySeeder.php @@ -1,5 +1,7 @@ isProduction()) { + return; + } + Category::factory(30)->create(); } } diff --git a/database/seeders/CommentSeeder.php b/database/seeders/CommentSeeder.php index 48e67c04..94d8e886 100644 --- a/database/seeders/CommentSeeder.php +++ b/database/seeders/CommentSeeder.php @@ -1,16 +1,22 @@ isProduction()) { + return; + } + Comment::factory(100) ->recycle(User::all()) ->recycle(Post::query()->where('is_commercial', false)->get()) diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index ec503179..08e4e513 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -1,28 +1,29 @@ isProduction()) { + return; + } + Storage::disk('public')->deleteDirectory('images/posts'); cache()->flush(); - Artisan::call(SyncVisitorsCommand::class); - $this->call([ UserSeeder::class, CategorySeeder::class, PostSeeder::class, CommentSeeder::class, - LinkSeeder::class, ShortUrlSeeder::class, ]); } diff --git a/database/seeders/LinkSeeder.php b/database/seeders/LinkSeeder.php deleted file mode 100644 index dfcba97a..00000000 --- a/database/seeders/LinkSeeder.php +++ /dev/null @@ -1,18 +0,0 @@ -recycle(User::all()) - ->approved() - ->create(); - } -} diff --git a/database/seeders/PostSeeder.php b/database/seeders/PostSeeder.php index d4d48784..36f2d9b1 100644 --- a/database/seeders/PostSeeder.php +++ b/database/seeders/PostSeeder.php @@ -1,17 +1,23 @@ isProduction()) { + return; + } + Post::factory(50) ->recycle(User::all()) ->recycle(Category::all()) @@ -127,7 +133,7 @@ public function sayHello(string $name): string MARKDOWN ]) - ->each(function (Post $post) { + ->each(function (Post $post): void { FetchImageForPost::dispatch($post); $post->categories()->attach( diff --git a/database/seeders/ShortUrlSeeder.php b/database/seeders/ShortUrlSeeder.php index 0d250712..1b408a7a 100644 --- a/database/seeders/ShortUrlSeeder.php +++ b/database/seeders/ShortUrlSeeder.php @@ -1,5 +1,7 @@ isProduction()) { + return; + } + ShortUrl::factory(10)->create(); } } diff --git a/database/seeders/UserSeeder.php b/database/seeders/UserSeeder.php index 667c8451..93aab2f1 100644 --- a/database/seeders/UserSeeder.php +++ b/database/seeders/UserSeeder.php @@ -1,5 +1,7 @@ isProduction()) { + return; + } + User::factory()->create([ - 'name' => 'Benjamin Crozat', - 'email' => 'benjamincrozat@me.com', - 'github_login' => 'benjamincrozat', + 'name' => 'Carlos Santos', + 'email' => 'carlos.santos.dev@gmail.com', + 'github_login' => 'carlossantosdev', ]); User::factory(10)->create(); diff --git a/package-lock.json b/package-lock.json index 62608c65..1f1ad695 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,25 +1,27 @@ { - "name": "benjamincrozat", + "name": "blog", "lockfileVersion": 3, "requires": true, "packages": { "": { - "devDependencies": { + "dependencies": { "@marcreichel/alpine-autosize": "^1.3.3", "@tailwindcss/forms": "^0.5.10", "@tailwindcss/typography": "^0.5.16", "@tailwindcss/vite": "^4.1.4", - "concurrently": "^9.1.2", + "@tailwindplus/elements": "^1.0.3", "laravel-vite-plugin": "^1.2.0", "tailwindcss": "^4.1.4", "vite": "^6.3.2" + }, + "devDependencies": { + "concurrently": "^9.1.2" } }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -36,7 +38,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -53,7 +54,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -70,7 +70,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -87,7 +86,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -104,7 +102,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -121,7 +118,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -138,7 +134,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -155,7 +150,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -172,7 +166,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -189,7 +182,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -206,7 +198,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -223,7 +214,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -240,7 +230,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -257,7 +246,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -274,7 +262,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -291,7 +278,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -308,7 +294,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -325,7 +310,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -342,7 +326,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -359,7 +342,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -376,7 +358,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -393,7 +374,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -410,7 +390,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -427,7 +406,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -444,7 +422,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -461,7 +438,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -475,7 +451,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "dev": true, "license": "ISC", "dependencies": { "minipass": "^7.0.4" @@ -488,7 +463,6 @@ "version": "0.3.12", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -499,7 +473,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -509,14 +482,12 @@ "version": "1.5.4", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.29", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -527,7 +498,6 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/@marcreichel/alpine-autosize/-/alpine-autosize-1.3.3.tgz", "integrity": "sha512-1F8Wb/b1oVb1eGySpr5InODcHoZGNpWIpYsfHIELvcwqiipBGrGwhYMJVh11skb/dx2JcTqCgheZTyTHJ15oRw==", - "dev": true, "license": "MIT", "dependencies": { "autosize": "^6.0.1" @@ -540,7 +510,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -554,7 +523,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -568,7 +536,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -582,7 +549,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -596,7 +562,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -610,7 +575,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -624,7 +588,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -638,7 +601,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -652,7 +614,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -666,7 +627,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -680,7 +640,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -694,7 +653,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -708,7 +666,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -722,7 +679,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -736,7 +692,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -750,7 +705,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -764,7 +718,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -778,7 +731,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -792,7 +744,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -806,7 +757,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -817,7 +767,6 @@ "version": "0.5.10", "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz", "integrity": "sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==", - "dev": true, "license": "MIT", "dependencies": { "mini-svg-data-uri": "^1.2.3" @@ -830,7 +779,6 @@ "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", - "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", @@ -846,7 +794,6 @@ "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", - "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -878,7 +825,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -895,7 +841,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -912,7 +857,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -929,7 +873,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -946,7 +889,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -963,7 +905,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -980,7 +921,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -997,7 +937,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1014,7 +953,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1039,7 +977,6 @@ "cpu": [ "wasm32" ], - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1061,7 +998,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1078,7 +1014,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1092,7 +1027,6 @@ "version": "0.5.16", "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz", "integrity": "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==", - "dev": true, "license": "MIT", "dependencies": { "lodash.castarray": "^4.4.0", @@ -1108,7 +1042,6 @@ "version": "4.1.11", "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz", "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==", - "dev": true, "license": "MIT", "dependencies": { "@tailwindcss/node": "4.1.11", @@ -1119,11 +1052,16 @@ "vite": "^5.2.0 || ^6 || ^7" } }, + "node_modules/@tailwindplus/elements": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tailwindplus/elements/-/elements-1.0.3.tgz", + "integrity": "sha512-9FqFmOXBm2pY8Y5tNDCDp+H53eH7gT83ghzCFnsYZVbKjXXj3E3MX0zeDG7nVBoG7zE8l1e/gM/ynOSPsDFdxA==", + "license": "SEE LICENSE IN LICENSE.md" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, "license": "MIT" }, "node_modules/ansi-regex": { @@ -1156,7 +1094,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/autosize/-/autosize-6.0.1.tgz", "integrity": "sha512-f86EjiUKE6Xvczc4ioP1JBlWG7FKrE13qe/DxBCpe8GCipCq2nFw73aO8QEBKHfSbYGDN5eB9jXWKen7tspDqQ==", - "dev": true, "license": "MIT" }, "node_modules/chalk": { @@ -1193,7 +1130,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=18" @@ -1264,7 +1200,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, "license": "MIT", "bin": { "cssesc": "bin/cssesc" @@ -1277,7 +1212,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -1294,7 +1228,6 @@ "version": "5.18.2", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==", - "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -1308,7 +1241,6 @@ "version": "0.25.6", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.6.tgz", "integrity": "sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg==", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -1360,7 +1292,6 @@ "version": "6.4.6", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", - "dev": true, "license": "MIT", "peerDependencies": { "picomatch": "^3 || ^4" @@ -1375,7 +1306,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -1400,7 +1330,6 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, "license": "ISC" }, "node_modules/has-flag": { @@ -1427,7 +1356,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", - "dev": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -1437,7 +1365,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.3.0.tgz", "integrity": "sha512-P5qyG56YbYxM8OuYmK2OkhcKe0AksNVJUjq9LUZ5tOekU9fBn9LujYyctI4t9XoLjuMvHJXXpCoPntY1oKltuA==", - "dev": true, "license": "MIT", "dependencies": { "picocolors": "^1.0.0", @@ -1457,7 +1384,6 @@ "version": "1.30.1", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", - "dev": true, "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -1489,7 +1415,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1510,7 +1435,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1531,7 +1455,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1552,7 +1475,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1573,7 +1495,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1594,7 +1515,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1615,7 +1535,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1636,7 +1555,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1657,7 +1575,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1678,7 +1595,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1703,28 +1619,24 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==", - "dev": true, "license": "MIT" }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "dev": true, "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, "license": "MIT" }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" @@ -1734,7 +1646,6 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", - "dev": true, "license": "MIT", "bin": { "mini-svg-data-uri": "cli.js" @@ -1744,7 +1655,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" @@ -1754,7 +1664,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", - "dev": true, "license": "MIT", "dependencies": { "minipass": "^7.1.2" @@ -1767,7 +1676,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "dev": true, "license": "MIT", "bin": { "mkdirp": "dist/cjs/src/bin.js" @@ -1783,7 +1691,6 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, "funding": [ { "type": "github", @@ -1802,14 +1709,12 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -1822,7 +1727,6 @@ "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, "funding": [ { "type": "opencollective", @@ -1851,7 +1755,6 @@ "version": "6.0.10", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", - "dev": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -1875,7 +1778,6 @@ "version": "4.45.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz", "integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -1938,7 +1840,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -1992,14 +1893,12 @@ "version": "4.1.11", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", - "dev": true, "license": "MIT" }, "node_modules/tapable": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2009,7 +1908,6 @@ "version": "7.4.3", "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", - "dev": true, "license": "ISC", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", @@ -2027,7 +1925,6 @@ "version": "0.2.14", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.4.4", @@ -2054,21 +1951,19 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, + "devOptional": true, "license": "0BSD" }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, "license": "MIT" }, "node_modules/vite": { "version": "6.3.5", "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", - "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", @@ -2143,7 +2038,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/vite-plugin-full-reload/-/vite-plugin-full-reload-1.2.0.tgz", "integrity": "sha512-kz18NW79x0IHbxRSHm0jttP4zoO9P9gXh+n6UTwlNKnviTTEpOlum6oS9SmecrTtSr+muHEn5TUuC75UovQzcA==", - "dev": true, "license": "MIT", "dependencies": { "picocolors": "^1.0.0", @@ -2154,7 +2048,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -2195,7 +2088,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=18" diff --git a/package.json b/package.json index cfd5c8e2..561c7fc8 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "@tailwindcss/forms": "^0.5.10", "@tailwindcss/typography": "^0.5.16", "@tailwindcss/vite": "^4.1.4", + "@tailwindplus/elements": "^1.0.3", "laravel-vite-plugin": "^1.2.0", "tailwindcss": "^4.1.4", "vite": "^6.3.2" @@ -17,4 +18,4 @@ "devDependencies": { "concurrently": "^9.1.2" } -} \ No newline at end of file +} diff --git a/peck.json b/peck.json new file mode 100644 index 00000000..732e6f53 --- /dev/null +++ b/peck.json @@ -0,0 +1,59 @@ +{ + "preset": "laravel", + "ignore": { + "words": [ + "apis", + "cloudways", + "codicon", + "compliances", + "config", + "cors", + "debugbar", + "digitalocean", + "dropdown", + "eos", + "fab", + "favicon", + "filament", + "filesystem", + "filesystems", + "geolocation", + "glenth", + "gpt", + "heroicon", + "iconoir", + "igl", + "img", + "js", + "kinsta", + "laravel's", + "laravel", + "lightdown", + "mailcoach", + "markdown", + "meilisearch", + "microservices", + "nav", + "openai", + "php", + "postcss", + "serp", + "sevalla", + "shortener", + "si", + "str", + "svg", + "textarea", + "tmp", + "uptimia", + "vemetric", + "vue", + "waitlist", + "webp", + "wincher" + ], + "paths": [ + "public" + ] + } +} \ No newline at end of file diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 1fe424b3..07519bae 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -23,7 +23,7 @@ - + diff --git a/pint.json b/pint.json index 4b0e606e..4b74e162 100644 --- a/pint.json +++ b/pint.json @@ -1,24 +1,57 @@ { "preset": "laravel", - "exclude": [ - "bootstrap/cache", - "storage" - ], "rules": { - "concat_space": { - "spacing": "one" + "array_push": true, + "backtick_to_shell_exec": true, + "date_time_immutable": true, + "declare_strict_types": true, + "lowercase_keywords": true, + "lowercase_static_reference": true, + "final_internal_class": true, + "final_public_method_for_abstract_class": true, + "fully_qualified_strict_types": true, + "global_namespace_import": { + "import_classes": true, + "import_constants": true, + "import_functions": true }, - "new_with_braces": { - "anonymous_class": false, - "named_class": false - }, - "ordered_imports": { - "sort_algorithm": "length" + "mb_str_functions": true, + "modernize_types_casting": true, + "new_with_parentheses": false, + "no_superfluous_elseif": true, + "no_useless_else": true, + "no_multiple_statements_per_line": true, + "ordered_class_elements": { + "order": [ + "use_trait", + "case", + "constant", + "constant_public", + "constant_protected", + "constant_private", + "property_public", + "property_protected", + "property_private", + "construct", + "destruct", + "magic", + "phpunit", + "method_abstract", + "method_public_static", + "method_public", + "method_protected_static", + "method_protected", + "method_private_static", + "method_private" + ], + "sort_algorithm": "none" }, + "ordered_interfaces": true, "ordered_traits": true, - "return_type_declaration": { - "space_before": "one" - }, - "yoda_style": true + "protected_to_private": true, + "self_accessor": true, + "self_static_accessor": true, + "strict_comparison": true, + "visibility_required": true } } \ No newline at end of file diff --git a/public/images/hero-section/Carlos-Santos.png b/public/images/hero-section/Carlos-Santos.png new file mode 100644 index 00000000..dcc3875e Binary files /dev/null and b/public/images/hero-section/Carlos-Santos.png differ diff --git a/public/images/hero-section/ai-bg.webp b/public/images/hero-section/ai-bg.webp new file mode 100644 index 00000000..c1fb2e69 Binary files /dev/null and b/public/images/hero-section/ai-bg.webp differ diff --git a/public/images/hero-section/glenth-logo.png b/public/images/hero-section/glenth-logo.png new file mode 100644 index 00000000..36c749ad Binary files /dev/null and b/public/images/hero-section/glenth-logo.png differ diff --git a/public/images/hero-section/laravel-bg.png b/public/images/hero-section/laravel-bg.png new file mode 100644 index 00000000..7575c2a9 Binary files /dev/null and b/public/images/hero-section/laravel-bg.png differ diff --git a/public/images/hero-section/php-bg.png b/public/images/hero-section/php-bg.png new file mode 100644 index 00000000..4d68908d Binary files /dev/null and b/public/images/hero-section/php-bg.png differ diff --git a/public/index.php b/public/index.php index 74a0960e..d55a3b2c 100644 --- a/public/index.php +++ b/public/index.php @@ -1,17 +1,19 @@ handleRequest(Request::capture()); diff --git a/rector.php b/rector.php new file mode 100644 index 00000000..85c4b7c8 --- /dev/null +++ b/rector.php @@ -0,0 +1,22 @@ +withPaths([ + __DIR__.'/app', + __DIR__.'/bootstrap/app.php', + __DIR__.'/database', + __DIR__.'/public', + ]) + ->withPreparedSets( + deadCode: true, + codeQuality: true, + typeDeclarations: true, + privatization: true, + earlyReturn: true, + strictBooleans: true, + ) + ->withPhpSets(); diff --git a/resources/css/app.css b/resources/css/app.css index dc223c2d..0d60ab29 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -12,7 +12,7 @@ --breakpoint-2xl: 1440px; --font-sans: - Outfit, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", + "JetBrains Mono", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } @@ -115,4 +115,4 @@ input[type="search"]::-webkit-search-results-decoration { .hl-gutter-deletion { background-color: #EA4334; color: #fff; -} +} \ No newline at end of file diff --git a/resources/views/about/show.blade.php b/resources/views/about/show.blade.php new file mode 100644 index 00000000..55a59948 --- /dev/null +++ b/resources/views/about/show.blade.php @@ -0,0 +1,56 @@ +@php + $description = Str::limit(strip_tags(Str::markdown($author->about)), 160); +@endphp + + +
+
+ {{ $author->name }} + +

+ {{ $author->name }} +

+ + @if ($author->company) +

+ {{ $author->company }} +

+ @endif +
+ + @if ($author->biography) + + {!! Str::markdown($author->about) !!} + + @endif + + +
+ + + @if ($posts->isNotEmpty()) + + @else +

+ So far, {{ $author->name }} didn't write any article. +

+ @endif + + +
+ + +
diff --git a/resources/views/advertise.blade.php b/resources/views/advertise.blade.php deleted file mode 100644 index cb14673c..00000000 --- a/resources/views/advertise.blade.php +++ /dev/null @@ -1,106 +0,0 @@ - -
- - -

- Advertise to {{ Number::format($visitors) }} developers -

- -

- This is the right place to show off your product. -

- - - Get in touch - -
- - -
- -
larajobs
- - -
-
- - -
-
- -
{{ Number::format($visitors) }}
-
visitors
-
- -
- -
{{ $views }}
-
page views
-
- -
- -
{{ $sessions }}
-
sessions
-
- -
- -
{{ $desktop }}%
-
on desktop
-
-
-
- - -

Your options right now are:

- -
    -
  • - A sponsored article that gets you: - -
      -
    • - Featured on top for a week -
    • - -
    • - Access to {{ Number::format($visitors) }} monthly developers -
    • - -
    • - A lifetime backlink on a DR 51 domain -
    • - -
    • - A position on the blog, forever -
    • -
    -
  • -
- -

- Interested? Get in touch with me. -

-
-
diff --git a/resources/views/authors/show.blade.php b/resources/views/authors/show.blade.php index a09080bd..6e00f8e8 100644 --- a/resources/views/authors/show.blade.php +++ b/resources/views/authors/show.blade.php @@ -1,23 +1,12 @@ @php -$description = Str::limit( - strip_tags(Str::markdown($author->about)), - 160 -); + $description = Str::limit(strip_tags(Str::markdown($author->about)), 160); @endphp - +
- {{ $author->name }} + {{ $author->name }}

{{ $author->name }} @@ -46,26 +35,10 @@ class="mx-auto mt-1 rounded-full size-16"

@endif - + - - @if ($links->isNotEmpty()) - - @else -

- So far, {{ $author->name }} didn't send any link. -

- @endif - -
+ + + + + + {{ $title }} + + + + + + + + + + + + + + + + + + + @vite('resources/css/app.css') + + + + + + + + + + + + + + + + + +class('font-light text-gray-600') }}> +
+ @if (app('impersonate')->isImpersonating()) +
+

+ Currently impersonating {{ auth()->user()->name }}. + + Return to account → + +

+
@endif - - - - - - - - - - - - - - class('font-light text-gray-600') }}> -
- @if (app('impersonate')->isImpersonating()) -
-

- Currently impersonating {{ auth()->user()->name }}. - - Return to account → - -

-
- @endif - - @empty($hideAd) - - @endempty - - @empty($hideNavigation) -
- -
- @endempty - -
empty($hideNavigation), - ])> - {{ $slot }} -
- - @empty($hideFooter) - - @endempty -
- - - - @livewireScriptConfig - - @vite('resources/js/app.js') - + + + @empty($hideNavigation) +
+ +
+ @endempty + +
empty($hideNavigation)])> + {{ $slot }} +
+ + @empty($hideFooter) + + @endempty +
+ + + + @livewireScriptConfig + + @vite('resources/js/app.js') + + diff --git a/resources/views/components/btn.blade.php b/resources/views/components/btn.blade.php index 75fb97b6..2f940c89 100644 --- a/resources/views/components/btn.blade.php +++ b/resources/views/components/btn.blade.php @@ -1,28 +1,20 @@ @if ($attributes->has('href')) -class([ + 'inline-block disabled:bg-gray-100 disabled:hover:bg-gray-100! disabled:text-gray-300! font-medium rounded-xl tracking-tight transition-colors', + 'bg-gray-200 hover:bg-gray-100' => + $attributes->missing('primary') && $attributes->missing('primary-alt') && $attributes->missing('disabled'), + 'bg-blue-600 hover:bg-blue-500 text-white' => $attributes->has('primary'), + 'bg-blue-100 hover:bg-blue-50 text-blue-900' => $attributes->has('primary-alt'), + 'px-[1.3rem] py-[.65rem]' => !$attributes->has('size'), + 'px-6 py-3 text-lg' => 'md' === $attributes->get('size'), + 'px-[.65rem] py-[.35rem] text-sm rounded-md' => 'sm' === $attributes->get('size'), + 'px-[.65rem] py-[.35rem] text-xs rounded-sm' => 'xs' === $attributes->get('size'), + ]) }} + > + {{ $slot }} + @if ($attributes->has('href')) + @else - + @endif diff --git a/resources/views/components/deals/item.blade.php b/resources/views/components/deals/item.blade.php index 33e36473..ae8df24b 100644 --- a/resources/views/components/deals/item.blade.php +++ b/resources/views/components/deals/item.blade.php @@ -1,14 +1,8 @@ class('bg-gray-100/75 flex rounded-xl overflow-hidden transition-opacity hover:opacity-50') - ->merge([ - 'href' => $href, - 'target' => '_blank', - 'data-pirsch-event' => "Clicked on $name", - ]) - }} -> + {{ $attributes->class('bg-gray-100/75 flex rounded-xl overflow-hidden transition-opacity hover:opacity-50')->merge([ + 'href' => $href, + 'target' => '_blank', + ]) }}>

{{ $headline }} @@ -18,20 +12,14 @@ {!! Str::markdown($subheadline) !!} - + {{ $cta }}

- {{ $name }} + {{ $name }}
diff --git a/resources/views/components/footer.blade.php b/resources/views/components/footer.blade.php index 49203c59..691342e5 100644 --- a/resources/views/components/footer.blade.php +++ b/resources/views/components/footer.blade.php @@ -1,19 +1,20 @@
class('bg-gray-100') }}> -
diff --git a/resources/views/components/hero-section/hero-section-old.blade.php b/resources/views/components/hero-section/hero-section-old.blade.php new file mode 100644 index 00000000..d723cbf4 --- /dev/null +++ b/resources/views/components/hero-section/hero-section-old.blade.php @@ -0,0 +1,32 @@ +
+
+ + +
+
+
+ + +
+
+
+
+
diff --git a/resources/views/components/hero-section/hero-section.blade.php b/resources/views/components/hero-section/hero-section.blade.php new file mode 100644 index 00000000..42f0cd39 --- /dev/null +++ b/resources/views/components/hero-section/hero-section.blade.php @@ -0,0 +1,31 @@ +
+
+ + +
+
+
+ + +
+
+
+
+
diff --git a/resources/views/components/hero-section/images.blade.php b/resources/views/components/hero-section/images.blade.php new file mode 100644 index 00000000..bcd7be8a --- /dev/null +++ b/resources/views/components/hero-section/images.blade.php @@ -0,0 +1,39 @@ +
+ {{--
--}} +
+
+ +
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
diff --git a/resources/views/components/hero-section/welcome-text.blade.php b/resources/views/components/hero-section/welcome-text.blade.php new file mode 100644 index 00000000..53e35a12 --- /dev/null +++ b/resources/views/components/hero-section/welcome-text.blade.php @@ -0,0 +1,16 @@ +
+

+ Hello!

+

+ I'm Carlos Santos!

+ +

+ I'm a software engineer with a passion for building great products, following good practices and code quality. +

+

And really, I love programming and create great solutions.

+
+ + More about me + +
+
diff --git a/resources/views/components/link-wizard/steps.blade.php b/resources/views/components/link-wizard/steps.blade.php deleted file mode 100644 index 861b5156..00000000 --- a/resources/views/components/link-wizard/steps.blade.php +++ /dev/null @@ -1,12 +0,0 @@ -@props(['steps']) - -
- @foreach ($steps as $step) -
$step->isCurrent(), - ])> - {{ $loop->iteration }}. {{ $step->label }} -
- @endforeach -
\ No newline at end of file diff --git a/resources/views/components/link.blade.php b/resources/views/components/link.blade.php deleted file mode 100644 index 1efc580d..00000000 --- a/resources/views/components/link.blade.php +++ /dev/null @@ -1,75 +0,0 @@ -@props(['link']) - - diff --git a/resources/views/components/links-grid.blade.php b/resources/views/components/links-grid.blade.php deleted file mode 100644 index 6cb9f7ad..00000000 --- a/resources/views/components/links-grid.blade.php +++ /dev/null @@ -1,9 +0,0 @@ -@props(['links']) - -
    class('grid gap-10 gap-y-16 xl:gap-x-16 md:grid-cols-2 xl:grid-cols-3') }}> - @foreach ($links as $link) -
  • - -
  • - @endforeach -
diff --git a/resources/views/components/nav/auth-admin.blade.php b/resources/views/components/nav/auth-admin.blade.php new file mode 100644 index 00000000..5900158c --- /dev/null +++ b/resources/views/components/nav/auth-admin.blade.php @@ -0,0 +1,11 @@ +@if (auth()->user()->isAdmin()) + + + + Admin + + + + Horizon + +@endif diff --git a/resources/views/components/nav/auth.blade.php b/resources/views/components/nav/auth.blade.php new file mode 100644 index 00000000..e3434b49 --- /dev/null +++ b/resources/views/components/nav/auth.blade.php @@ -0,0 +1,25 @@ +@auth +
+ You: {{ auth()->user()->name }} +
+ + + + + + + Your comments + + + + Log out + + + +@else + + Sign in + +@endauth diff --git a/resources/views/components/nav/carlos-santos.blade.php b/resources/views/components/nav/carlos-santos.blade.php new file mode 100644 index 00000000..21bebcd3 --- /dev/null +++ b/resources/views/components/nav/carlos-santos.blade.php @@ -0,0 +1,15 @@ + + +
+ Carlos Santos +
+ + + About + + + + Contact + + + diff --git a/resources/views/components/nav/follow-me.blade.php b/resources/views/components/nav/follow-me.blade.php new file mode 100644 index 00000000..65415df1 --- /dev/null +++ b/resources/views/components/nav/follow-me.blade.php @@ -0,0 +1,15 @@ + + Follow me + + + + GitHub + + + + LinkedIn + + + + X + diff --git a/resources/views/components/nav/fork.blade.php b/resources/views/components/nav/fork.blade.php new file mode 100644 index 00000000..3c828ff2 --- /dev/null +++ b/resources/views/components/nav/fork.blade.php @@ -0,0 +1,7 @@ + + + + Fork the source code + diff --git a/resources/views/components/nav/index.blade.php b/resources/views/components/nav/index.blade.php index 2c6e563b..0946b6e9 100644 --- a/resources/views/components/nav/index.blade.php +++ b/resources/views/components/nav/index.blade.php @@ -1,245 +1,54 @@ diff --git a/resources/views/components/nav/item.blade.php b/resources/views/components/nav/item.blade.php index b6c768ad..8e2f688c 100644 --- a/resources/views/components/nav/item.blade.php +++ b/resources/views/components/nav/item.blade.php @@ -5,18 +5,16 @@ <{{ $attributes->has('href') ? 'a' : 'button' }} {{ $attributes->class([ - 'transition-colors hover:text-blue-600 line-clamp-1', - 'text-blue-600' => request()->fullUrlIs($attributes->get('href')), - ])->merge([ - 'wire:navigate' => ! $attributes->has('no-wire-navigate') && $attributes->has('href'), - 'data-pirsch-event' => "Clicked \“$slot\“", - ]) }} -> - @if (! empty($activeIcon) && request()->fullUrlIs($attributes->get('href'))) + 'transition-colors hover:text-blue-600 line-clamp-1 flex flex-col items-center', + 'text-blue-600' => request()->fullUrlIs($attributes->get('href')), + ])->merge([ + 'wire:navigate' => !$attributes->has('no-wire-navigate') && $attributes->has('href'), + ]) }}> + @if (!empty($activeIcon) && request()->fullUrlIs($attributes->get('href'))) - @elseif (! empty($icon)) + @elseif (!empty($icon)) @endif {{ $slot }} -has('href') ? 'a' : 'button' }}> + has('href') ? 'a' : 'button' }}> diff --git a/resources/views/components/nav/this-website.blade.php b/resources/views/components/nav/this-website.blade.php new file mode 100644 index 00000000..e8a451df --- /dev/null +++ b/resources/views/components/nav/this-website.blade.php @@ -0,0 +1,11 @@ + + This Website + + + + Blog Categories + + +{{-- + Events + --}} diff --git a/resources/views/components/post.blade.php b/resources/views/components/post.blade.php index 831e3144..837ade1c 100644 --- a/resources/views/components/post.blade.php +++ b/resources/views/components/post.blade.php @@ -3,17 +3,26 @@
class('flex flex-col h-full') }}> @if ($post->hasImage()) - {{ $post->title  }} + {{ $post->title }} @else @php - $bgColors = collect([ - 'bg-amber-600', 'bg-blue-600', 'bg-cyan-600', 'bg-emerald-600', 'bg-gray-600', 'bg-green-600', 'bg-indigo-600', 'bg-lime-600', 'bg-pink-600', 'bg-purple-600', 'bg-red-600', 'bg-sky-600', 'bg-teal-600', 'bg-yellow-600', - ])->random(); + $bgColors = collect([ + 'bg-amber-600', + 'bg-blue-600', + 'bg-cyan-600', + 'bg-emerald-600', + 'bg-gray-600', + 'bg-green-600', + 'bg-indigo-600', + 'bg-lime-600', + 'bg-pink-600', + 'bg-purple-600', + 'bg-red-600', + 'bg-sky-600', + 'bg-teal-600', + 'bg-yellow-600', + ])->random(); @endphp
@@ -23,7 +32,8 @@ class="object-cover rounded-xl ring-1 shadow-md transition-opacity shadow-black/ @if ($post->categories->isNotEmpty())
@foreach ($post->categories as $category) - + {{ $category->name }} @endforeach @@ -31,20 +41,14 @@ class="object-cover rounded-xl ring-1 shadow-md transition-opacity shadow-black/ @endif @@ -58,9 +62,10 @@ class="rounded-full ring-1 ring-black/5 size-10" {{ ($post->modified_at ?? $post->published_at)->isoFormat('ll') }}
- @if (! $post->is_commercial) + @if (!$post->is_commercial) -
+
{{ $post->comments_count }} {{ trans_choice('comment|comments', $post->comments_count) }}
diff --git a/resources/views/components/social-network.blade.php b/resources/views/components/social-network.blade.php new file mode 100644 index 00000000..97733718 --- /dev/null +++ b/resources/views/components/social-network.blade.php @@ -0,0 +1,37 @@ +
diff --git a/resources/views/components/status.blade.php b/resources/views/components/status.blade.php index 06295065..976553ed 100644 --- a/resources/views/components/status.blade.php +++ b/resources/views/components/status.blade.php @@ -1,31 +1,17 @@ -@if (session('status') || ! empty(request()->submitted)) -
- @if (! empty(request()->submitted)) + }, 100)" x-show="show" + x-transition:enter-end="opacity-100 translate-y-0" x-transition:enter-start="opacity-0 translate-y-4" + x-transition:enter="transition ease-out duration-300" x-transition:leave-end="opacity-0 translate-y-4" + x-transition:leave="transition ease-in duration-300" @click="() => { show = false }"> + @if (!empty(request()->submitted)) Your link has been submitted for validation. @else {{ session('status') }} @endif
-@endsession + @endsession diff --git a/resources/views/deals.blade.php b/resources/views/deals.blade.php index da8c15bc..e281aa28 100644 --- a/resources/views/deals.blade.php +++ b/resources/views/deals.blade.php @@ -1,33 +1,12 @@ - -

+ +

Unlock the best software deals for developers

-
- - Featured deals - -

These companies are sponsoring my blog. Big thanks to them and make sure to check them out!

- -
- @php - $components = collect([ - 'ads.deals.sevalla', - 'ads.deals.vemetric', - ])->shuffle()->toArray(); - @endphp - - @foreach ($components as $component) - - @endforeach -
-

diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index ebe737f4..386c1760 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -1,104 +1,26 @@

-
- {{ Number::format($visitors) }} monthly visitors read my blog -
- -
- No matter how senior,
- I have something for you. Ready? -
- -
- - Who the F are you? - - - - Start reading - -
+
- @if ($popular->isNotEmpty()) - - - - - Browse all articles - - - @endif - - -
- - - - - - -
-
- @if ($latest->isNotEmpty()) @endif - + Browse all articles - - @if ($links->isNotEmpty()) - - @endif - - - Browse all links - - - - @if ($aboutUser) - - - {{ $aboutUser->name }} + @if ($popular->isNotEmpty()) + + - {!! Str::markdown($aboutUser->biography) !!} - + + Browse all articles + @endif +
diff --git a/resources/views/links/index.blade.php b/resources/views/links/index.blade.php deleted file mode 100644 index c552c7d2..00000000 --- a/resources/views/links/index.blade.php +++ /dev/null @@ -1,53 +0,0 @@ - - @if ($links->currentPage() === 1) -
-

- Keep learning with the community -

- -
- Find tons of resources written and shared by {{ $distinctUsersCount }} web developers. -
- -
- @foreach ($distinctUserAvatars as $avatar) -
- -
- @endforeach -
- -
- - Browse - - - - Submit a link - -
-
- @endif - - $links->currentPage() === 1, - ])> - @if ($links->isNotEmpty()) - - @endif - - - -
diff --git a/resources/views/livewire/link-wizard/first-step.blade.php b/resources/views/livewire/link-wizard/first-step.blade.php deleted file mode 100644 index ac1dbf93..00000000 --- a/resources/views/livewire/link-wizard/first-step.blade.php +++ /dev/null @@ -1,20 +0,0 @@ -
- - - - - - - - Next - - - -
diff --git a/resources/views/livewire/link-wizard/second-step.blade.php b/resources/views/livewire/link-wizard/second-step.blade.php deleted file mode 100644 index bcc63995..00000000 --- a/resources/views/livewire/link-wizard/second-step.blade.php +++ /dev/null @@ -1,43 +0,0 @@ -
- - - - @if ($title) - - @if ($imageUrl) - - @endif - - - - - - - - - Submit - - - @else - - @endif - -
diff --git a/resources/views/login.blade.php b/resources/views/login.blade.php index 807683b2..880256f5 100644 --- a/resources/views/login.blade.php +++ b/resources/views/login.blade.php @@ -1,24 +1,16 @@ - +
- + Back to the home page →
@@ -30,7 +22,8 @@ class="tracking-tight underline underline-offset-4 decoration-gray-600/30"
- By signing in, you will be able to use the comments section and share links with my {{ Number::format($visitors) }} monthly visitors. + By signing in, you will be able to use the comments section and share links with my + {{ Number::format($visitors) }} monthly visitors.
diff --git a/resources/views/posts/show.blade.php b/resources/views/posts/show.blade.php index d7c93d19..8a7ce35d 100644 --- a/resources/views/posts/show.blade.php +++ b/resources/views/posts/show.blade.php @@ -1,26 +1,16 @@ - +
! $post->is_commercial, + '2xl:max-w-(--breakpoint-xl) grid lg:grid-cols-12 gap-16 lg:gap-12' => !$post->is_commercial, 'lg:max-w-(--breakpoint-md)' => $post->is_commercial, ])>
! $post->is_commercial, + 'lg:col-span-8 xl:col-span-9' => !$post->is_commercial, ])>
@if ($post->hasImage()) - {{ $post->title }} + {{ $post->title }} @endif

@@ -28,11 +18,13 @@ class="object-cover mb-12 w-full rounded-xl ring-1 shadow-xl md:mb-16 ring-black read

-

+

{{ $post->title }}

-
+
@@ -46,27 +38,21 @@ class="object-cover mb-12 w-full rounded-xl ring-1 shadow-xl md:mb-16 ring-black
- {{ ($post->modified_at ?? $post->published_at ?? $post->created_at)->isoFormat('ll') }} + {{ ($post->modified_at ?? ($post->published_at ?? $post->created_at))->isoFormat('ll') }}
- -
- {{ $post->user->name }} + +
+ {{ $post->user->name }} Written by
{{ $post->user->name }}
- @if (! $post->is_commercial) + @if (!$post->is_commercial)
- + class="p-3 w-full h-full text-center bg-gray-50 rounded-lg transition-colors hover:bg-blue-50 hover:text-blue-900"> + Actions @@ -97,10 +79,8 @@ class="mx-auto transition-transform size-6 md:size-7" Admin - + Edit article @@ -110,20 +90,13 @@ class="mx-auto transition-transform size-6 md:size-7" Chat - + Ask ChatGPT - + Ask Claude @@ -132,29 +105,27 @@ class="mx-auto transition-transform size-6 md:size-7" Share - + Share on Facebook - + > Share on LinkedIn - + > Share on X @@ -162,25 +133,16 @@ class="mx-auto transition-transform size-6 md:size-7"
- @if (! empty($headings = extract_headings_from_markdown($post->content))) - + @if (!empty(($headings = extract_headings_from_markdown($post->content)))) + @endif {!! $post->formatted_content !!} - @if ($post->link) -

- - Read more on {{ $post->link->domain }} → - -

- @endif - @if (! empty($post->recommendedPosts) && ! $post->is_commercial) + + @if (!empty($post->recommendedPosts) && !$post->is_commercial)

Did you like this article? Then, keep learning:

@@ -188,11 +150,8 @@ class="mt-4 ml-0"
- @if (! $post->is_commercial) + @if (!$post->is_commercial)
@@ -267,18 +217,15 @@ class="grid place-items-center text-white bg-gray-900 rounded-md size-12" @endif
- @if (! $post->is_commercial) + @if (!$post->is_commercial)
- {{-- Just in case I don't think about switching the ads. --}} - @if (now()->isAfter('2025-08-03')) - - @else - - @endif + @@ -289,21 +236,15 @@ class="grid place-items-center text-white bg-gray-900 rounded-md size-12"

- {{ $latestComment->user->name }} + class="flex-none mt-1 rounded-full ring-1 shadow-sm shadow-black/5 ring-black/10 size-7 md:size-8" />

- + {{ $latestComment->user->name }} @@ -317,11 +258,8 @@ class="font-medium"

- + Check comments →

@@ -337,11 +275,8 @@ class="font-medium underline"
  • - +

    Atom feed

    @@ -350,12 +285,8 @@ class="group"
  • - +

    LinkedIn

    @@ -364,13 +295,10 @@ class="group"
  • - -
  • - +

    X

    diff --git a/resources/views/user/links.blade.php b/resources/views/user/links.blade.php deleted file mode 100644 index 2c7e92cb..00000000 --- a/resources/views/user/links.blade.php +++ /dev/null @@ -1,58 +0,0 @@ - - -
    - - Your links - - - - Submit a new link - -
    - -
    - @foreach ($links as $link) -
    - {{ $link->title  }} - -
    -

    - - {{ $link->url }} - -

    - -

    - {{ $link->title }} -

    - -

    - {{ $link->description }} -

    - -

    - Status: - - @if ($link->isApproved()) - Approved - @else - Pending - @endif -

    -
    -
    - @endforeach -
    - - - - diff --git a/routes/auth.php b/routes/auth.php index 2793299b..7c07c4f0 100644 --- a/routes/auth.php +++ b/routes/auth.php @@ -1,21 +1,27 @@ group(function () { Route::view('/login', 'login') ->name('login'); - Route::get('/auth/redirect', GithubAuthRedirectController::class) - ->name('auth.redirect'); + Route::prefix('/auth')->name('auth.')->group(function () { + + Route::get('/gh/redirect', GithubAuthRedirectController::class) + ->name('redirect'); + + Route::get('/gh/callback', GithubAuthCallbackController::class) + ->name('callback'); - Route::get('/auth/callback', GithubAuthCallbackController::class) - ->name('auth.callback'); + }); }); Route::middleware('auth') diff --git a/routes/console.php b/routes/console.php index 77b66876..485f0b06 100644 --- a/routes/console.php +++ b/routes/console.php @@ -1,15 +1,13 @@ daily(); Schedule::command(RefreshUserDataCommand::class) ->hourly(); - -Schedule::command(SyncVisitorsCommand::class) - ->daily(); diff --git a/routes/guest.php b/routes/guest.php index 348f3626..9a12caf1 100644 --- a/routes/guest.php +++ b/routes/guest.php @@ -1,48 +1,40 @@ name('home'); -Route::get('/blog', ListPostsController::class) - ->name('posts.index'); +Route::get('/about', [AboutController::class, 'show']) + ->name('about'); + +Route::prefix('blog')->name('blog.')->group(function () { + Route::get('', ListPostsController::class) + ->name('posts.index'); -Route::get('/authors/{user:slug}', ShowAuthorController::class) - ->name('authors.show'); + Route::get('/authors/{user:slug}', ShowAuthorController::class) + ->name('authors.show'); -Route::get('/categories', ListCategoriesController::class) - ->name('categories.index'); + Route::get('/categories', ListCategoriesController::class) + ->name('categories.index'); -Route::get('/categories/{category:slug}', ShowCategoryController::class) - ->name('categories.show'); + Route::get('/categories/{category:slug}', ShowCategoryController::class) + ->name('categories.show'); +}); Route::view('/deals', 'deals') ->name('deals'); -Route::get('/links/create', LinkWizard::class) - ->middleware('auth') - ->name('links.create'); - -Route::get('/links', ListLinksController::class) - ->name('links.index'); - -Route::get('/advertise', App\Http\Controllers\Advertising\ShowAdvertisingLandingPageController::class) - ->name('advertise'); - -Route::get('/redirect/{slug}', RedirectToAdvertiserController::class) - ->name('redirect-to-advertiser'); - Route::get('/recommends/{slug}', ShowMerchantController::class) ->name('merchants.show'); diff --git a/routes/legacy.php b/routes/legacy.php index 23eab60b..6bc10e52 100644 --- a/routes/legacy.php +++ b/routes/legacy.php @@ -1,5 +1,7 @@ group(function () { diff --git a/routes/user.php b/routes/user.php index 5d361cfb..4146bece 100644 --- a/routes/user.php +++ b/routes/user.php @@ -1,8 +1,9 @@ prefix('/user') @@ -11,6 +12,4 @@ Route::get('/comments', ListUserCommentsController::class) ->name('comments'); - Route::get('/links', ListUserLinksController::class) - ->name('links'); }); diff --git a/tests/Feature/App/Actions/CreatePostForLinkTest.php b/tests/Feature/App/Actions/CreatePostForLinkTest.php deleted file mode 100644 index 6c4e8bee..00000000 --- a/tests/Feature/App/Actions/CreatePostForLinkTest.php +++ /dev/null @@ -1,51 +0,0 @@ - [ - [ - 'message' => [ - 'content' => json_encode([ - 'title' => 'Generated post title', - 'content' => 'Generated post content.', - 'description' => 'Generated meta description.', - ]), - ], - ], - ], - ]), - ]); - - Readability::shouldReceive('parse')->once(); - Readability::shouldReceive('getAuthor')->andReturn('Test Author'); - Readability::shouldReceive('getTitle')->andReturn('Test Title'); - Readability::shouldReceive('getContent')->andReturn('Test Content'); - - Http::fake([ - '*' => Http::response('', 200), - ]); - - $link = Link::factory()->create(); - - $post = app(CreatePostForLink::class)->create($link); - - expect($post->title)->toBe('Generated post title'); - expect($post->content)->toBe('Generated post content.'); - expect($post->description)->toBe('Generated meta description.'); - - Bus::assertDispatchedTimes(RecommendPosts::class, 1); -}); diff --git a/tests/Feature/App/Actions/FetchPostSessionsTest.php b/tests/Feature/App/Actions/FetchPostSessionsTest.php deleted file mode 100644 index fef2cd44..00000000 --- a/tests/Feature/App/Actions/FetchPostSessionsTest.php +++ /dev/null @@ -1,68 +0,0 @@ - Http::allowStrayRequests()); - -it("successfully fetches sessions from Pirsch's API and updates the related posts", function () { - $first = Post::factory()->create([ - 'slug' => 'foo', - 'sessions_count' => 0, - ]); - - $second = Post::factory()->create([ - 'slug' => 'bar', - 'sessions_count' => 0, - ]); - - Http::fake([ - 'api.pirsch.io/api/v1/statistics/page*' => Http::response([ - ['path' => '/foo', 'sessions' => 10], - ['path' => '/foo#section', 'sessions' => 5], - ['path' => '/bar', 'sessions' => 20], - ['path' => '/bar#comment', 'sessions' => 4], - ], 200), - ]); - - app(FetchPostSessions::class)->fetch(); - - expect($first->refresh()->sessions_count)->toBe(15) - ->and($second->refresh()->sessions_count)->toBe(24); -}); - -it('throws when Pirsch refuses credentials', function () { - config([ - 'services.pirsch.client_id' => 'wrong_id', - 'services.pirsch.client_secret' => 'wrong_secret', - ]); - - app(FetchPostSessions::class)->fetch(); -})->throws(RequestException::class); - -it('sends the provided date range to Pirsch', function () { - $from = now()->subDays(14)->startOfDay()->toImmutable(); - $to = now()->subDays(7)->endOfDay()->toImmutable(); - - Http::fake([ - 'api.pirsch.io/api/v1/statistics/page*' => Http::response([], 200), - ]); - - app(FetchPostSessions::class)->fetch($from, $to); - - Http::assertSent(function (Request $request) use ($from, $to) { - if (! str_contains($request->url(), '/statistics/page')) { - return false; - } - - parse_str(parse_url($request->url(), PHP_URL_QUERY), $query); - - expect($query['from'] ?? null)->toBe($from->toDateString()); - expect($query['to'] ?? null)->toBe($to->toDateString()); - - return true; - }); -}); diff --git a/tests/Feature/App/Actions/RefreshUserDataTest.php b/tests/Feature/App/Actions/RefreshUserDataTest.php index 30b3cec4..128d51eb 100644 --- a/tests/Feature/App/Actions/RefreshUserDataTest.php +++ b/tests/Feature/App/Actions/RefreshUserDataTest.php @@ -1,8 +1,10 @@ Http::allowStrayRequests()); - -it("successfully makes a call to Pirsch's API with valid parameters", function () { - app(TrackEvent::class)->track(...trackEventParameters()); -})->throwsNoExceptions(); - -it('handles an invalid token appropriately', function () { - config(['services.pirsch.access_key' => 'invalid_token']); - - app(TrackEvent::class)->track(...trackEventParameters()); -})->throws(RequestException::class); - -it('retries on network failure and does not throw if it succeeds', function () { - Http::fakeSequence('api.pirsch.io/api/v1/hit') - ->pushStatus(503) - ->pushStatus(503) - ->pushStatus(200); - - app(TrackEvent::class)->track(...trackEventParameters()); -})->throwsNoExceptions(); - -it('properly handles request timeouts', function () { - Http::fakeSequence() - ->pushStatus(408) - ->pushStatus(408) - ->pushStatus(200); - - app(TrackEvent::class)->track(...trackEventParameters()); -})->throwsNoExceptions(); - -function trackEventParameters() : array -{ - return [ - 'name' => 'Foo', - 'meta' => ['foo' => 'bar'], - 'url' => fake()->url(), - 'ip' => fake()->ipv4(), - 'userAgent' => fake()->userAgent(), - 'acceptLanguage' => fake()->languageCode(), - 'referrer' => fake()->url(), - ]; -} diff --git a/tests/Feature/App/Actions/TrackVisitTest.php b/tests/Feature/App/Actions/TrackVisitTest.php deleted file mode 100644 index b965291d..00000000 --- a/tests/Feature/App/Actions/TrackVisitTest.php +++ /dev/null @@ -1,46 +0,0 @@ - Http::allowStrayRequests()); - -it("successfully makes a call to Pirsch's API with valid parameters", function () { - app(TrackVisit::class)->track(...trackVisitParameters()); -})->throwsNoExceptions(); - -it('handles an invalid token appropriately', function () { - config(['services.pirsch.access_key' => 'invalid_token']); - - app(TrackVisit::class)->track(...trackVisitParameters()); -})->throws(RequestException::class); - -it('retries on network failure and does not throw if it succeeds', function () { - Http::fakeSequence('api.pirsch.io/api/v1/hit') - ->pushStatus(503) - ->pushStatus(503) - ->pushStatus(200); - - app(TrackVisit::class)->track(...trackVisitParameters()); -})->throwsNoExceptions(); - -it('properly handles request timeouts', function () { - Http::fakeSequence() - ->pushStatus(408) - ->pushStatus(408) - ->pushStatus(200); - - app(TrackVisit::class)->track(...trackVisitParameters()); -})->throwsNoExceptions(); - -function trackVisitParameters() : array -{ - return [ - 'url' => fake()->url(), - 'ip' => fake()->ipv4(), - 'userAgent' => fake()->userAgent(), - 'acceptLanguage' => fake()->languageCode(), - 'referrer' => fake()->url(), - ]; -} diff --git a/tests/Feature/App/Console/Commands/GenerateRecommendationsCommandTest.php b/tests/Feature/App/Console/Commands/GenerateRecommendationsCommandTest.php index e8fcf351..41a8ce1d 100644 --- a/tests/Feature/App/Console/Commands/GenerateRecommendationsCommandTest.php +++ b/tests/Feature/App/Console/Commands/GenerateRecommendationsCommandTest.php @@ -1,13 +1,14 @@ create(['slug' => 'my-slug']); diff --git a/tests/Feature/App/Console/Commands/GenerateSitemapCommandTest.php b/tests/Feature/App/Console/Commands/GenerateSitemapCommandTest.php index 11eeb53c..e6321a77 100644 --- a/tests/Feature/App/Console/Commands/GenerateSitemapCommandTest.php +++ b/tests/Feature/App/Console/Commands/GenerateSitemapCommandTest.php @@ -1,14 +1,15 @@ create(); @@ -39,5 +40,4 @@ ->cursor() ->each(fn (Category $category) => expect($content)->toContain(route('categories.show', $category))); - expect($content)->toContain(route('links.index')); }); diff --git a/tests/Feature/App/Console/Commands/RefreshUserDataCommandTest.php b/tests/Feature/App/Console/Commands/RefreshUserDataCommandTest.php index 6c5df83d..8ad08a82 100644 --- a/tests/Feature/App/Console/Commands/RefreshUserDataCommandTest.php +++ b/tests/Feature/App/Console/Commands/RefreshUserDataCommandTest.php @@ -1,13 +1,14 @@ Http::allowStrayRequests()); it('fetches analytics data for each post and updates the sessions_count', function () { diff --git a/tests/Feature/App/Console/Commands/SyncVisitorsCommandTest.php b/tests/Feature/App/Console/Commands/SyncVisitorsCommandTest.php deleted file mode 100644 index cbe31efd..00000000 --- a/tests/Feature/App/Console/Commands/SyncVisitorsCommandTest.php +++ /dev/null @@ -1,33 +0,0 @@ - Http::response(['access_token' => 'some-access-token']), - 'api.pirsch.io/api/v1/statistics/total*' => Http::response([ - 'visitors' => 1234, - 'views' => 1234, - 'sessions' => 1234, - ]), - 'api.pirsch.io/api/v1/statistics/platform*' => Http::response([ - 'relative_platform_desktop' => 0.1234, - ]), - ]); - - artisan(SyncVisitorsCommand::class) - ->assertSuccessful(); - - assertDatabaseHas(Metric::class, ['key' => 'platform_desktop']); - assertDatabaseHas(Metric::class, ['key' => 'sessions']); - assertDatabaseHas(Metric::class, ['key' => 'views']); - assertDatabaseHas(Metric::class, ['key' => 'visitors']); -}); diff --git a/tests/Feature/App/Filesystem/CloudflareImagesAdapterTest.php b/tests/Feature/App/Filesystem/CloudflareImagesAdapterTest.php index ef0fa6d3..5036717d 100644 --- a/tests/Feature/App/Filesystem/CloudflareImagesAdapterTest.php +++ b/tests/Feature/App/Filesystem/CloudflareImagesAdapterTest.php @@ -1,14 +1,16 @@ Http::allowStrayRequests()); it('uploads an image, returns a public URL, and deletes it using the Cloudflare Images adapter', function () { - /** @var \Illuminate\Filesystem\FilesystemAdapter $disk */ + /** @var Illuminate\Filesystem\FilesystemAdapter $disk */ $disk = Storage::disk('cloudflare-images'); $id = Str::random(20); @@ -23,7 +25,7 @@ }); it('reads an uploaded image both as a string and as a stream using the Cloudflare Images adapter', function () { - /** @var \Illuminate\Filesystem\FilesystemAdapter $disk */ + /** @var Illuminate\Filesystem\FilesystemAdapter $disk */ $disk = Storage::disk('cloudflare-images'); $id = Str::random(20); @@ -44,21 +46,21 @@ }); it('throws the proper exceptions for unsupported operations on the Cloudflare Images adapter', function () { - /** @var \Illuminate\Filesystem\FilesystemAdapter $disk */ + /** @var Illuminate\Filesystem\FilesystemAdapter $disk */ $disk = Storage::disk('cloudflare-images'); - /** @var \App\Filesystem\CloudflareImagesAdapter $adapter */ + /** @var App\Filesystem\CloudflareImagesAdapter $adapter */ $adapter = $disk->getAdapter(); - expect(fn () => $adapter->move('source', 'destination', new \League\Flysystem\Config)) - ->toThrow(\League\Flysystem\UnableToMoveFile::class); + expect(fn () => $adapter->move('source', 'destination', new League\Flysystem\Config)) + ->toThrow(League\Flysystem\UnableToMoveFile::class); - expect(fn () => $adapter->copy('source', 'destination', new \League\Flysystem\Config)) - ->toThrow(\League\Flysystem\UnableToCopyFile::class); + expect(fn () => $adapter->copy('source', 'destination', new League\Flysystem\Config)) + ->toThrow(League\Flysystem\UnableToCopyFile::class); expect(fn () => $adapter->setVisibility('source', 'private')) - ->toThrow(\League\Flysystem\InvalidVisibilityProvided::class); + ->toThrow(League\Flysystem\InvalidVisibilityProvided::class); - expect(fn () => $adapter->createDirectory('foo', new \League\Flysystem\Config)) - ->toThrow(\League\Flysystem\UnableToCreateDirectory::class); + expect(fn () => $adapter->createDirectory('foo', new League\Flysystem\Config)) + ->toThrow(League\Flysystem\UnableToCreateDirectory::class); }); diff --git a/tests/Feature/App/ForYouTest.php b/tests/Feature/App/ForYouTest.php index 659d1cd5..0d7361ba 100644 --- a/tests/Feature/App/ForYouTest.php +++ b/tests/Feature/App/ForYouTest.php @@ -1,5 +1,7 @@ Http::allowStrayRequests()); - -it('redirects to the advertiser and appends additional query string parameters', function () { - Bus::fake(); - - get(route('redirect-to-advertiser', [ - 'slug' => 'sevalla', - 'foo' => 'bar', - 'baz' => 'qux', - ])) - ->assertRedirect(config('advertisers.sevalla') . '?foo=bar&baz=qux&utm_source=benjamin_crozat'); - - Bus::assertDispatchedAfterResponse(TrackEvent::class, function (TrackEvent $job) { - expect($job->name)->toBe('Clicked on ad'); - - expect($job->meta)->toBe([ - 'slug' => 'sevalla', - 'url' => config('advertisers.sevalla'), - ]); - - return true; - }); -}); - -it('throws a 404 if the advertiser is not found', function () { - get(route('redirect-to-advertiser', 'foo')) - ->assertNotFound(); -}); diff --git a/tests/Feature/App/Http/Controllers/Advertising/ShowAdvertisingLandingPageControllerTest.php b/tests/Feature/App/Http/Controllers/Advertising/ShowAdvertisingLandingPageControllerTest.php deleted file mode 100644 index e937425a..00000000 --- a/tests/Feature/App/Http/Controllers/Advertising/ShowAdvertisingLandingPageControllerTest.php +++ /dev/null @@ -1,12 +0,0 @@ -assertOk() - ->assertViewIs('advertise') - ->assertViewHas('views') - ->assertViewHas('sessions') - ->assertViewHas('desktop'); -}); diff --git a/tests/Feature/App/Http/Controllers/Auth/GithubAuthCallbackControllerTest.php b/tests/Feature/App/Http/Controllers/Auth/GithubAuthCallbackControllerTest.php index d27c2f64..19f10d88 100644 --- a/tests/Feature/App/Http/Controllers/Auth/GithubAuthCallbackControllerTest.php +++ b/tests/Feature/App/Http/Controllers/Auth/GithubAuthCallbackControllerTest.php @@ -1,19 +1,17 @@ create(); diff --git a/tests/Feature/App/Http/Controllers/Authors/ShowAuthorControllerTest.php b/tests/Feature/App/Http/Controllers/Authors/ShowAuthorControllerTest.php index 6801eabd..0fa6feb4 100644 --- a/tests/Feature/App/Http/Controllers/Authors/ShowAuthorControllerTest.php +++ b/tests/Feature/App/Http/Controllers/Authors/ShowAuthorControllerTest.php @@ -1,21 +1,18 @@ create(); - Link::factory(3)->create(); - $user = User::factory() ->hasPosts(3, ['published_at' => now()]) - ->hasLinks(3, ['is_approved' => now()]) ->create(); get(route('authors.show', $user)) @@ -25,11 +22,6 @@ ->assertViewHas('posts', function (LengthAwarePaginator $posts) { expect($posts->count())->toBe(3); - return true; - }) - ->assertViewHas('links', function (LengthAwarePaginator $links) { - expect($links->count())->toBe(3); - return true; }); }); diff --git a/tests/Feature/App/Http/Controllers/Categories/ListCategoriesControllerTest.php b/tests/Feature/App/Http/Controllers/Categories/ListCategoriesControllerTest.php index 715a26bd..98e73e09 100644 --- a/tests/Feature/App/Http/Controllers/Categories/ListCategoriesControllerTest.php +++ b/tests/Feature/App/Http/Controllers/Categories/ListCategoriesControllerTest.php @@ -1,9 +1,11 @@ assertOk() diff --git a/tests/Feature/App/Http/Controllers/Categories/ShowCategoryControllerTest.php b/tests/Feature/App/Http/Controllers/Categories/ShowCategoryControllerTest.php index 6a298db7..fef82e5a 100644 --- a/tests/Feature/App/Http/Controllers/Categories/ShowCategoryControllerTest.php +++ b/tests/Feature/App/Http/Controllers/Categories/ShowCategoryControllerTest.php @@ -1,5 +1,7 @@ create(['sessions_count' => 0]); Post::factory(15)->create(['sessions_count' => random_int(1, 1000)]); - Link::factory(15)->approved()->create(); - User::factory()->create([ - 'github_login' => 'benjamincrozat', + 'github_login' => 'carlossantosdev', ]); get(route('home')) ->assertOk() ->assertViewIs('home') - ->assertViewHas('popular', fn (Collection $popular) => 12 === $popular->count()) - ->assertViewHas('latest', fn (Collection $latest) => 12 === $latest->count()) - ->assertViewHas('links', fn (Collection $links) => 12 === $links->count()) - ->assertViewHas('aboutUser', fn (User $aboutUser) => 'benjamincrozat' === $aboutUser->github_login); + ->assertViewHas('popular', fn (Collection $popular) => $popular->count() === 12) + ->assertViewHas('latest', fn (Collection $latest) => $latest->count() === 12) + ->assertViewHas('aboutUser', fn (User $aboutUser) => $aboutUser->github_login === 'carlossantosdev'); }); it('does not show popular posts if there are no sessions', function () { Post::factory(15)->create(['sessions_count' => 0]); User::factory()->create([ - 'github_login' => 'benjamincrozat', + 'github_login' => 'carlossantosdev', ]); get(route('home')) diff --git a/tests/Feature/App/Http/Controllers/Impersonation/LeaveImpersonationControllerTest.php b/tests/Feature/App/Http/Controllers/Impersonation/LeaveImpersonationControllerTest.php index ee3019d2..b7334002 100644 --- a/tests/Feature/App/Http/Controllers/Impersonation/LeaveImpersonationControllerTest.php +++ b/tests/Feature/App/Http/Controllers/Impersonation/LeaveImpersonationControllerTest.php @@ -1,12 +1,14 @@ create([ 'github_login' => 'benjamincrozat', ]); @@ -28,7 +30,7 @@ }); it('redirects even when not impersonating', function () { - /** @var \App\Models\User $admin */ + /** @var User $admin */ $admin = User::factory()->create([ 'github_login' => 'benjamincrozat', ]); diff --git a/tests/Feature/App/Http/Controllers/Links/ListLinksControllerTest.php b/tests/Feature/App/Http/Controllers/Links/ListLinksControllerTest.php deleted file mode 100644 index 610546bc..00000000 --- a/tests/Feature/App/Http/Controllers/Links/ListLinksControllerTest.php +++ /dev/null @@ -1,119 +0,0 @@ -approved()->create(); - - Link::factory(3)->declined()->create(); - - Link::factory(2)->create(); - - get(route('links.index')) - ->assertOk() - ->assertViewHas('links', function (LengthAwarePaginator $links) use ($approved) { - return $links->count() === $approved->count() && - $links->every( - fn (Link $link) => null !== $link->is_approved && null === $link->is_declined - ); - }); -}); - -it('orders links by is_approved in descending order', function () { - $old = Link::factory()->approved()->create(['is_approved' => now()->subDays(2)]); - $new = Link::factory()->approved()->create(['is_approved' => now()->subDay()]); - $newest = Link::factory()->approved()->create(['is_approved' => now()]); - - get(route('links.index')) - ->assertOk() - ->assertViewHas('links', function (LengthAwarePaginator $links) use ($newest, $new, $old) { - return $links->first()->id === $newest->id && - $links->get(1)->id === $new->id && - $links->get(2)->id === $old->id; - }); -}); - -it('passes distinct user avatars to the view', function () { - $usersWithAvatars = User::factory(5) - ->create(['avatar' => 'https://example.com/avatar.png']); - - $usersWithoutAvatars = User::factory(3) - ->create(['avatar' => null]); - - foreach ($usersWithAvatars as $user) { - Link::factory()->approved()->create(['user_id' => $user->id]); - } - - foreach ($usersWithoutAvatars as $user) { - Link::factory()->approved()->create(['user_id' => $user->id]); - } - - get(route('links.index')) - ->assertOk() - ->assertViewHas('distinctUserAvatars', function (Collection $avatars) { - return $avatars->count() <= 10 && - $avatars->every(fn (string $avatar) => null !== $avatar); - }); -}); - -it('excludes specific users from distinct user avatars', function () { - User::factory()->sequence( - ['github_login' => 'benjamincrozat'], - )->create([ - 'avatar' => 'https://example.com/excluded-avatar.png', - ])->each(function (User $user) { - Link::factory()->approved()->create([ - 'user_id' => $user->id, - ]); - }); - - User::factory(5) - ->create(['avatar' => 'https://example.com/avatar.png']) - ->each(function (User $user) { - Link::factory()->approved()->create([ - 'user_id' => $user->id, - ]); - }); - - get(route('links.index')) - ->assertOk() - ->assertViewHas('distinctUserAvatars', fn (Collection $avatars) => ! $avatars->contains('https://example.com/excluded-avatar.png')); -}); - -it('passes distinct users count to the view', function () { - // Create some users with avatars. - User::factory(5) - ->create(['avatar' => 'https://example.com/avatar.png']) - ->each(fn (User $user) => Link::factory()->approved()->create(['user_id' => $user->id])); - - // Create users without avatars (they shouldn't be counted). - User::factory(3) - ->create(['avatar' => null]) - ->each(fn (User $user) => Link::factory()->approved()->create(['user_id' => $user->id])); - - // Create excluded users (they shouldn't be counted as well). - $excludedUser = User::factory()->create([ - 'github_login' => 'benjamincrozat', - 'avatar' => 'https://example.com/avatar.png', - ]); - - Link::factory()->approved()->create(['user_id' => $excludedUser->id]); - - get(route('links.index')) - ->assertOk() - ->assertViewHas('distinctUsersCount', 5); // Only count non-excluded users with avatars. -}); - -it('paginates the links collection', function () { - Link::factory(15)->approved()->create(); - - get(route('links.index')) - ->assertOk() - ->assertViewHas('links', fn (LengthAwarePaginator $links) => 12 === $links->count()); -}); diff --git a/tests/Feature/App/Http/Controllers/Merchants/ShowMerchantControllerTest.php b/tests/Feature/App/Http/Controllers/Merchants/ShowMerchantControllerTest.php index 9321f1d4..2aebfb2d 100644 --- a/tests/Feature/App/Http/Controllers/Merchants/ShowMerchantControllerTest.php +++ b/tests/Feature/App/Http/Controllers/Merchants/ShowMerchantControllerTest.php @@ -1,27 +1,12 @@ 'bar'])) - ->assertRedirectContains(config('merchants.services.ploi') . '&foo=bar'); - - Bus::assertDispatchedAfterResponse(TrackEvent::class, function (TrackEvent $job) { - expect($job->name)->toBe('Clicked on merchant'); - - expect($job->meta)->toBe([ - 'slug' => 'ploi', - 'url' => config('merchants.services.ploi'), - ]); - - return true; - }); + ->assertRedirectContains(config('merchants.services.ploi').'&foo=bar'); }); test('it throws 404 when merchant does not exist', function () { diff --git a/tests/Feature/App/Http/Controllers/Posts/ListPostsControllerTest.php b/tests/Feature/App/Http/Controllers/Posts/ListPostsControllerTest.php index bddca68a..2802b194 100644 --- a/tests/Feature/App/Http/Controllers/Posts/ListPostsControllerTest.php +++ b/tests/Feature/App/Http/Controllers/Posts/ListPostsControllerTest.php @@ -1,9 +1,11 @@ assertOk() diff --git a/tests/Feature/App/Http/Controllers/Posts/ShowPostControllerTest.php b/tests/Feature/App/Http/Controllers/Posts/ShowPostControllerTest.php index a1f54f37..21cb0e6c 100644 --- a/tests/Feature/App/Http/Controllers/Posts/ShowPostControllerTest.php +++ b/tests/Feature/App/Http/Controllers/Posts/ShowPostControllerTest.php @@ -1,10 +1,12 @@ hasComments(3)->create(); @@ -39,7 +41,7 @@ it('shows unpublished posts if the user is admin', function () { $user = User::factory()->create([ - 'github_login' => 'benjamincrozat', + 'github_login' => 'carlossantosdev', ]); $post = Post::factory()->create([ diff --git a/tests/Feature/App/Http/Controllers/ShortUrls/ShowShortUrlControllerTest.php b/tests/Feature/App/Http/Controllers/ShortUrls/ShowShortUrlControllerTest.php index 9a27e7d4..878464d2 100644 --- a/tests/Feature/App/Http/Controllers/ShortUrls/ShowShortUrlControllerTest.php +++ b/tests/Feature/App/Http/Controllers/ShortUrls/ShowShortUrlControllerTest.php @@ -1,33 +1,20 @@ Http::allowStrayRequests()); it('redirects to the short URL and tracks the event', function () { - Bus::fake(); - $shortUrl = ShortUrl::factory()->create(); get(route('shortUrl.show', $shortUrl)) ->assertStatus(302) ->assertRedirect($shortUrl->url); - - Bus::assertDispatchedAfterResponse(TrackEvent::class, function (TrackEvent $job) use ($shortUrl) { - expect($job->name)->toBe('Clicked on short URL'); - - expect($job->meta)->toBe([ - 'url' => $shortUrl->url, - ]); - - return true; - }); }); it('throws a 404 if the short URL does not exist', function () { diff --git a/tests/Feature/App/Http/Controllers/User/ListUserCommentsControllerTest.php b/tests/Feature/App/Http/Controllers/User/ListUserCommentsControllerTest.php index b3aee33a..e3fd4113 100644 --- a/tests/Feature/App/Http/Controllers/User/ListUserCommentsControllerTest.php +++ b/tests/Feature/App/Http/Controllers/User/ListUserCommentsControllerTest.php @@ -1,11 +1,12 @@ create(); - - Link::factory(3)->create([ - 'user_id' => $user->id, - ]); - - Link::factory(3)->approved()->create([ - 'user_id' => $user->id, - ]); - - Link::factory(3)->declined()->create([ - 'user_id' => $user->id, - ]); - - actingAs($user) - ->get(route('user.links')) - ->assertOk() - ->assertViewIs('user.links') - ->assertViewHas('links', fn (LengthAwarePaginator $links) => 9 === $links->count()); -}); - -it("doesn't allow guests", function () { - getJson(route('user.comments')) - ->assertUnauthorized(); -}); diff --git a/tests/Feature/App/Http/Middleware/HandleRedirectsTest.php b/tests/Feature/App/Http/Middleware/HandleRedirectsTest.php index 5594440e..11137aac 100644 --- a/tests/Feature/App/Http/Middleware/HandleRedirectsTest.php +++ b/tests/Feature/App/Http/Middleware/HandleRedirectsTest.php @@ -1,5 +1,7 @@ 'production']); -}); - -it('tracks visits in production', function () { - TrackVisit::expects('track'); - - get('/'); -}); - -it('does not track visits in non-production environments', function () { - TrackVisit::shouldReceive('track')->never(); - - config(['app.env' => 'testing']); - - get('/'); -}); - -it('only tracks GET requests', function () { - TrackVisit::shouldReceive('track')->never(); - - Route::post('/foo', fn () => '') - ->middleware(\App\Http\Middleware\TrackVisit::class); - - post('/foo') - ->assertOk(); -}); - -it('does not track Livewire requests', function () { - TrackVisit::shouldReceive('track')->never(); - - get('/', ['X-Livewire' => 'true']); -}); - -it('does not track requests that want JSON', function () { - TrackVisit::shouldReceive('track')->never(); - - get('/', ['Accept' => 'application/json']); -}); - -it('only tracks if all required parameters are available', function () { - TrackVisit::shouldReceive('track')->never(); - - withServerVariables(['REMOTE_ADDR' => null]); - - get('/'); -}); - -it('does not track requests from crawlers', function () { - TrackVisit::shouldReceive('track')->never(); - - get('/', ['User-Agent' => 'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Googlebot/2.1; +http://www.google.com/bot.html) Chrome/W.X.Y.Z Safari/537.36']); -}); - -it('does not track prefetch requests', function () { - TrackVisit::shouldReceive('track')->never(); - - get('/', ['Purpose' => 'prefetch']); -}); - -it('does not track requests from admins', function () { - TrackVisit::shouldReceive('track')->never(); - - $user = User::factory()->create(['github_login' => 'benjamincrozat']); - - actingAs($user) - ->get('/'); -}); diff --git a/tests/Feature/App/Livewire/CommentFormTest.php b/tests/Feature/App/Livewire/CommentFormTest.php index d5663cbd..cd31fb1c 100644 --- a/tests/Feature/App/Livewire/CommentFormTest.php +++ b/tests/Feature/App/Livewire/CommentFormTest.php @@ -1,7 +1,9 @@ create(); $admin = User::factory()->create([ - 'github_login' => 'benjamincrozat', + 'github_login' => 'carlossantosdev', ]); actingAs($user); @@ -55,7 +56,7 @@ $post = Post::factory()->create(); $admin = User::factory()->create([ - 'github_login' => 'benjamincrozat', + 'github_login' => 'carlossantosdev', ]); actingAs($admin); @@ -74,7 +75,7 @@ $user = User::factory()->create(); $admin = User::factory()->create([ - 'github_login' => 'benjamincrozat', + 'github_login' => 'carlossantosdev', ]); $existingComment = Comment::factory()->create(); diff --git a/tests/Feature/App/Livewire/LinkWizard/FirstStep.php b/tests/Feature/App/Livewire/LinkWizard/FirstStep.php deleted file mode 100644 index 79e5a2d5..00000000 --- a/tests/Feature/App/Livewire/LinkWizard/FirstStep.php +++ /dev/null @@ -1,44 +0,0 @@ -set('url', 'https://example.com') - ->call('submit') - ->assertDispatched('nextStep'); - - Http::assertSent(function (Request $request) { - return 'https://example.com' === $request->url(); - }); -}); - -it('requires a URL', function () { - livewire(FirstStep::class) - ->call('submit') - ->assertHasErrors(['url' => 'required']); -}); - -it('requires a valid URL', function () { - livewire(FirstStep::class) - ->set('url', 'example') - ->call('submit') - ->assertHasErrors(['url' => 'url']); -}); - -it('ensures the URL is unique', function () { - Link::factory()->create(['url' => 'https://example.com']); - - livewire(FirstStep::class) - ->set('url', 'https://example.com') - ->call('submit') - ->assertHasErrors(['url' => 'unique']); -}); diff --git a/tests/Feature/App/Livewire/LinkWizard/LinkWizard.php b/tests/Feature/App/Livewire/LinkWizard/LinkWizard.php deleted file mode 100644 index 030c53b0..00000000 --- a/tests/Feature/App/Livewire/LinkWizard/LinkWizard.php +++ /dev/null @@ -1,22 +0,0 @@ -create(); - - actingAs($user); - - livewire(LinkWizard::class) - ->assertOk(); -}); - -it('disallows guests', function () { - livewire(LinkWizard::class) - ->assertRedirect(route('login')); -}); diff --git a/tests/Feature/App/Livewire/LinkWizard/SecondStepTest.php b/tests/Feature/App/Livewire/LinkWizard/SecondStepTest.php deleted file mode 100644 index d6cef0a5..00000000 --- a/tests/Feature/App/Livewire/LinkWizard/SecondStepTest.php +++ /dev/null @@ -1,61 +0,0 @@ -forever('embed_' . Str::slug($url, '_'), [ - 'image_url' => 'https://example.com/image.png', - 'title' => 'Example title', - 'description' => 'Example description', - ]); - - $user = User::factory()->create(); - - $admin = User::factory()->create(['github_login' => 'benjamincrozat']); - - actingAs($user) - ->get(route('links.create')) - ->assertOk(); - - livewire(SecondStep::class, [ - 'url' => $url, - 'imageUrl' => 'https://example.com/image.png', - 'title' => 'Example title', - 'description' => 'Example description', - ]) - ->assertDispatched('fetch') - ->call('fetch') - ->call('submit') - ->assertRedirect(route('links.index', ['submitted' => true])); - - assertDatabaseHas(Link::class, [ - 'url' => $url, - 'image_url' => 'https://example.com/image.png', - 'title' => 'Example title', - 'description' => 'Example description', - ]); - - Notification::assertSentToTimes($admin, LinkWaitingForValidation::class, 1); -}); - -it("doesn't allow guests", function () { - getJson(route('links.create')) - ->assertUnauthorized(); -}); diff --git a/tests/Feature/App/Models/CommentTest.php b/tests/Feature/App/Models/CommentTest.php index c3b80240..6e043dae 100644 --- a/tests/Feature/App/Models/CommentTest.php +++ b/tests/Feature/App/Models/CommentTest.php @@ -1,8 +1,10 @@ create([ - 'is_approved' => now(), - 'is_declined' => now(), - ]); - - expect($link->is_approved)->toBeInstanceOf(CarbonImmutable::class); - expect($link->is_declined)->toBeInstanceOf(CarbonImmutable::class); -}); - -it('scopes pending links', function () { - Link::factory()->create([ - 'is_approved' => null, - 'is_declined' => null, - ]); - - Link::factory()->create([ - 'is_approved' => now(), - 'is_declined' => null, - ]); - - expect(Link::query()->pending()->get())->toHaveCount(1); -}); - -it('scopes approved links', function () { - Link::factory()->create([ - 'is_approved' => now(), - 'is_declined' => null, - ]); - - Link::factory()->create([ - 'is_approved' => null, - 'is_declined' => now(), - ]); - - expect(Link::query()->approved()->get())->toHaveCount(1); -}); - -it('scopes declined links', function () { - Link::factory()->create([ - 'is_approved' => null, - 'is_declined' => now(), - ]); - - Link::factory()->create([ - 'is_approved' => now(), - 'is_declined' => null, - ]); - - expect(Link::query()->declined()->get())->toHaveCount(1); -}); - -it('belongs to a user', function () { - $user = User::factory()->create(); - - $link = Link::factory()->create([ - 'user_id' => $user->id, - ]); - - expect($link->user->is($user))->toBeTrue(); -}); - -it('belongs to a post', function () { - $post = Post::factory()->create(); - - $link = Link::factory()->create([ - 'post_id' => $post->id, - ]); - - expect($link->post->is($post))->toBeTrue(); -}); - -it('has a domain attribute', function () { - $link = Link::factory()->create([ - 'url' => 'https://www.google.com', - ]); - - expect($link->domain)->toBe('google.com'); -}); - -it('can change to approved and notify the user', function () { - Notification::fake(); - - $link = Link::factory()->create([ - 'is_approved' => null, - 'is_declined' => null, - ]); - - expect($link->is_approved)->toBeNull(); - - $link->approve(); - - expect($link->is_approved)->toBeInstanceOf(CarbonImmutable::class); - - Notification::assertSentToTimes($link->user, LinkApproved::class, 1); -}); - -it('can change to declined', function () { - $link = Link::factory()->create([ - 'is_approved' => null, - 'is_declined' => null, - ]); - - expect($link->is_declined)->toBeNull(); - - $link->decline(); - - expect($link->is_declined)->toBeInstanceOf(CarbonImmutable::class); -}); - -it('checks if it is approved', function () { - $link = Link::factory()->create([ - 'is_approved' => now(), - 'is_declined' => null, - ]); - - expect($link->isApproved())->toBeTrue(); -}); - -it('checks if it is declined', function () { - $link = Link::factory()->create([ - 'is_approved' => null, - 'is_declined' => now(), - ]); - - expect($link->isDeclined())->toBeTrue(); -}); diff --git a/tests/Feature/App/Models/PostTest.php b/tests/Feature/App/Models/PostTest.php index 80fb49a6..983d1b0d 100644 --- a/tests/Feature/App/Models/PostTest.php +++ b/tests/Feature/App/Models/PostTest.php @@ -1,14 +1,16 @@ and($markdown)->toContain('# Foo Bar'); }); -it('getFeedItems only returns the 50 most recent published posts without links', function () { +it('getFeedItems only returns the 50 most recent published posts', function () { // 60 published posts without links. Post::factory(60)->create(['published_at' => now()]); - // One published post with a link – should be excluded. - $withLink = Post::factory()->create(['published_at' => now()]); - \App\Models\Link::factory()->create(['post_id' => $withLink->id]); - // One unpublished post – should be excluded. Post::factory()->create(['published_at' => null]); @@ -298,8 +296,6 @@ // Ensure ordering (latest first) expect($feedItems->first()->published_at->greaterThanOrEqualTo($feedItems->last()->published_at))->toBeTrue(); - // Ensure excluded post with link is not present - expect($feedItems->pluck('id'))->not->toContain($withLink->id); }); it('converts a post to a valid FeedItem via toFeedItem()', function () { @@ -314,7 +310,7 @@ $feedItem = $post->toFeedItem(); - expect($feedItem)->toBeInstanceOf(\Spatie\Feed\FeedItem::class) + expect($feedItem)->toBeInstanceOf(Spatie\Feed\FeedItem::class) ->and($feedItem->id)->toBe('foo') ->and($feedItem->title)->toBe('Foo') ->and($feedItem->link)->toBe(route('posts.show', $post)) @@ -337,11 +333,3 @@ expect($post->comments)->toHaveCount(3) ->and($post->comments_count)->toBe(3); }); - -it('has one link', function () { - $post = Post::factory()->create(); - $link = \App\Models\Link::factory()->create(['post_id' => $post->id]); - - expect($post->link)->toBeInstanceOf(\App\Models\Link::class) - ->and($post->link->is($link))->toBeTrue(); -}); diff --git a/tests/Feature/App/Models/UserTest.php b/tests/Feature/App/Models/UserTest.php index 6669c9ab..388ef70c 100644 --- a/tests/Feature/App/Models/UserTest.php +++ b/tests/Feature/App/Models/UserTest.php @@ -1,5 +1,7 @@ create(); - - $result = new LinkApproved($link) - ->toMail(User::factory()->create()) - ->render(); - - expect($result)->toBeInstanceOf(HtmlString::class); -}); diff --git a/tests/Feature/App/Notifications/LinkWaitingForValidationTest.php b/tests/Feature/App/Notifications/LinkWaitingForValidationTest.php deleted file mode 100644 index 27cc682b..00000000 --- a/tests/Feature/App/Notifications/LinkWaitingForValidationTest.php +++ /dev/null @@ -1,16 +0,0 @@ -create(); - - $result = new LinkWaitingForValidation($link) - ->toMail(User::factory()->create()) - ->render(); - - expect($result)->toBeInstanceOf(HtmlString::class); -}); diff --git a/tests/Feature/App/Notifications/NewCommentTest.php b/tests/Feature/App/Notifications/NewCommentTest.php index b1fbc70a..089e0db3 100644 --- a/tests/Feature/App/Notifications/NewCommentTest.php +++ b/tests/Feature/App/Notifications/NewCommentTest.php @@ -1,7 +1,9 @@ create([ - 'github_login' => 'benjamincrozat', + 'github_login' => 'carlossantosdev', ]); actingAs($user) diff --git a/tests/Feature/App/StrTest.php b/tests/Feature/App/StrTest.php index 7d5d2b0c..9fd9a643 100644 --- a/tests/Feature/App/StrTest.php +++ b/tests/Feature/App/StrTest.php @@ -1,5 +1,7 @@ preset() ->laravel() diff --git a/tests/Feature/FeedTest.php b/tests/Feature/FeedTest.php index 9fbc3201..21fba409 100644 --- a/tests/Feature/FeedTest.php +++ b/tests/Feature/FeedTest.php @@ -1,19 +1,18 @@ create(); - Link::factory(10)->create(); - $response = get(route('feeds.main')) ->assertOk(); - expect(Post::count())->toBe(40); + expect(Post::count())->toBe(30); expect($posts)->toHaveCount(30); diff --git a/tests/Feature/Views/AppTest.php b/tests/Feature/Views/AppTest.php index 868f1253..84ab332a 100644 --- a/tests/Feature/Views/AppTest.php +++ b/tests/Feature/Views/AppTest.php @@ -1,10 +1,8 @@ assertSee('description', 'The best blog about PHP, Laravel, AI, and every other topics involved in building software.'); }); -it("does not include Pirsch's script outside production", function () { - get('/') - ->assertDontSee('src="https://api.pirsch.io/pa.js"', escape: false); -}); - -it("includes Pirsch's script in production", function () { - config(['app.env' => 'production']); - - withoutMiddleware(TrackVisit::class); - - get('/') - ->assertSee('https://api.pirsch.io/pa.js', escape: false); -}); - it('signals the Atom feed', function () { get('/') ->assertSee('application/atom+xml', escape: false); diff --git a/tests/Pest.php b/tests/Pest.php index 416d370a..79caa559 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -1,12 +1,13 @@ extend(TestCase::class) diff --git a/tests/TestCase.php b/tests/TestCase.php index fe1ffc2f..6ac0072e 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -1,5 +1,7 @@