name('cli-script.show'); Route::middleware('guest')->group(function (): void { // Guests land straight on the Trending log... Route::get('/', [LogController::class, 'index'])->name('home'); // Session... if (app()->isLocal()) { // Locally, signing in asks for any handle in a modal, without GitHub... Route::get('login', [LocalSessionController::class, 'create'])->name('login'); Route::post('login/local', [LocalSessionController::class, 'store'])->name('local-login.store'); } else { Route::get('login', [SessionController::class, 'create']) ->middleware('throttle:10,1') ->name('login'); } Route::get('auth/github/callback', [SessionController::class, 'store']) ->middleware('throttle:10,1') ->name('login.store'); }); // Log, where guests read Trending and Latest... Route::get('log/everyone', fn () => to_route('log.show', Log::Trending, 301)); Route::get('log/{log}', [LogController::class, 'show'])->name('log.show'); // Terms and privacy, readable by everyone... Route::get('terms', [TermsController::class, 'show'])->name('terms.show'); Route::get('privacy', [PrivacyController::class, 'show'])->name('privacy.show'); // Unsubscribing from the admins' emails, from the signed link in each one... Route::get('emails/unsubscribe/{user}', [UnsubscriptionController::class, 'show'])->whereUuid('user')->name('unsubscription.show'); Route::post('emails/unsubscribe/{user}', [UnsubscriptionController::class, 'store']) ->whereUuid('user') ->withoutMiddleware(PreventRequestForgery::class) ->name('unsubscription.store'); // How /dev/null was built, readable by everyone... Route::get('about', [AboutController::class, 'show'])->name('about.show'); // Logos, colors, and backgrounds, downloadable by everyone... Route::get('brand', [BrandController::class, 'show'])->name('brand.show'); // The apps, downloadable by everyone, where the Mac app's first page used to be too... Route::get('apps', [AppController::class, 'index'])->name('app.index'); Route::get('mac', fn () => to_route('app.index', status: 301)); // What was merged into production, readable by everyone... Route::get('changelog', [ChangelogController::class, 'index'])->name('changelog.index'); // Public pages, readable by guests and crawlers... Route::middleware(RememberIntendedUrl::class)->group(function (): void { Route::get('@{user:handle}', [UserController::class, 'show'])->name('user.show'); Route::get('@{user:handle}/following', [UserFollowingController::class, 'index']) ->name('user-following.index'); Route::get('@{user:handle}/followers', [UserFollowersController::class, 'index']) ->name('user-followers.index'); Route::get('@{user:handle}/mutuals', [UserMutualsController::class, 'index']) ->name('user-mutuals.index'); Route::get('@{user:handle}/commits/{commit}', [CommitController::class, 'show']) ->scopeBindings() ->name('commit.show'); Route::get('@{user:handle}/commits/{commit}/photos/{photo}', [CommitPhotoController::class, 'show']) ->scopeBindings() ->whereNumber('photo') ->name('commit-photo.show'); }); // Sitemaps... Route::middleware('cache.headers:public;max_age=3600;etag')->group(function (): void { Route::get('sitemap.xml', [SitemapController::class, 'index'])->name('sitemap.index'); Route::get('sitemaps/users-{page}.xml', [UserSitemapController::class, 'show']) ->where('page', '[1-9][0-9]*') ->name('user-sitemap.show'); Route::get('sitemaps/commits-{page}.xml', [CommitSitemapController::class, 'show']) ->where('page', '[1-9][0-9]*') ->name('commit-sitemap.show'); }); // User... Route::get('mentions/{user}', [MentionController::class, 'show']) ->whereUuid('user') ->name('mention.show'); Route::get('mentions/{user}/preview', [UserPreviewController::class, 'show']) ->middleware('throttle:people') ->whereUuid('user') ->name('mention.preview'); Route::get('@{user:handle}/preview', [UserPreviewController::class, 'show']) ->middleware('throttle:people') ->name('user-preview.show'); Route::get('@{user:handle}/og.png', [UserOpenGraphImageController::class, 'show']) ->name('user-open-graph-image.show'); Route::get('@{user:handle}/feed.atom', [UserFeedController::class, 'show']) ->middleware('cache.headers:public;max_age=300;etag') ->name('user-feed.show'); // User activity graph, embedded in READMEs... Route::get('@{user:handle}/graph.svg', [UserActivityGraphController::class, 'show']) ->middleware(['throttle:activity-graph', 'cache.headers:public;max_age=3600;etag']) ->name('user-activity-graph.show'); // Commit... Route::get('@{user:handle}/commits/{commit}/og.png', [CommitOpenGraphImageController::class, 'show']) ->scopeBindings() ->name('commit-open-graph-image.show'); Route::get('commit-images/{commitImage}', [CommitImageController::class, 'show']) ->whereUuid('commitImage') ->name('commit-image.show'); Route::get('commits/{commit}/link-preview', [CommitLinkPreviewController::class, 'show']) ->name('commit-link-preview.show'); Route::get('commits/{commit}/status', [CommitStatusController::class, 'show']) ->name('commit-status.show'); Route::get('commits/{commit}/replies', [CommitReplyController::class, 'index']) ->withTrashed() ->name('commit-reply.index'); Route::get('commits/{commit}/continuation', [CommitContinuationController::class, 'index']) ->name('commit-continuation.index'); Route::get('commits/{commit}/related', [RelatedCommitController::class, 'index']) ->middleware('throttle:related-commits') ->name('related-commit.index'); // Search... Route::get('search', [SearchController::class, 'index']) ->name('search.index'); // User Search... Route::get('search/users', [UserSearchController::class, 'index']) ->middleware('throttle:people') ->name('user-search.index'); // Commit Search... Route::get('search/commits', [CommitSearchController::class, 'index']) ->middleware('throttle:search') ->name('commit-search.index'); Route::middleware('auth')->group(function (): void { Route::post('issues', [IssueController::class, 'store']) ->middleware([PreventPostingInTimeout::class, 'throttle:10,1']) ->name('issue.store'); // Session... Route::get('push-subscriptions', [PushSubscriptionController::class, 'index'])->name('push-subscription.index'); Route::post('push-subscriptions', [PushSubscriptionController::class, 'store'])->middleware(['throttle:30,1', SerializePushSessions::class, SerializePushSubscriptions::class])->name('push-subscription.store'); Route::delete('push-subscriptions', [PushSubscriptionController::class, 'destroy'])->middleware(SerializePushSessions::class)->name('push-subscription.destroy'); Route::post('logout', [SessionController::class, 'destroy']) ->middleware(SerializePushSessions::class) ->name('logout'); // Banned, where a banned user is sent from everywhere else... Route::get('banned', [BannedController::class, 'show'])->name('banned.show'); // Followers the viewer knows... Route::get('@{user:handle}/followers-you-know', [UserFollowersYouKnowController::class, 'index']) ->name('user-followers-you-know.index'); // Onboarding... Route::get('onboarding', [OnboardingController::class, 'show'])->name('onboarding.show'); Route::put('onboarding', [OnboardingController::class, 'update'])->name('onboarding.update'); // Private starred commits... Route::get('starred', [StarredCommitController::class, 'index'])->name('starred.index'); // Every commit about a link, a prototype only reachable by its URL... Route::get('links', [LinkController::class, 'index'])->name('link.index'); // Commit... Route::get('scheduled-commits', [ScheduledCommitController::class, 'index'])->name('scheduled-commit.index'); Route::post('scheduled-commits', [ScheduledCommitController::class, 'store']) ->middleware([PreventPostingInTimeout::class, RequireCommitRules::class, ThrottleCommits::class])->name('scheduled-commit.store'); Route::delete('scheduled-commits/{scheduledCommit}', [ScheduledCommitController::class, 'destroy'])->name('scheduled-commit.destroy'); Route::post('scheduled-commits/{scheduledCommit}/submission', [ScheduledCommitSubmissionController::class, 'store']) ->middleware([PreventPostingInTimeout::class, ThrottleCommits::class])->name('scheduled-commit-submission.store'); Route::get('commit-stars', [CommitStarCountController::class, 'index'])->name('commit-star-count.index'); Route::post('commits', [CommitController::class, 'store']) ->middleware([PreventPostingInTimeout::class, RequireCommitRules::class, ThrottleCommits::class]) ->name('commit.store'); Route::post('commits/preview', [CommitPreviewController::class, 'store']) ->name('commit-preview.store'); Route::post('commits/{commit}/translation', [CommitTranslationController::class, 'store']) ->middleware('throttle:10,1,translations:') ->name('commit-translation.store'); Route::delete('commits/{commit}', [CommitController::class, 'destroy']) ->name('commit.destroy'); Route::post('commits/{commit}/restoration', [CommitRestorationController::class, 'store']) ->withTrashed() ->name('commit-restoration.store'); Route::put('commits/{commit}/pin', [CommitPinController::class, 'update']) ->middleware('throttle:pins') ->name('commit-pin.update'); Route::delete('commits/{commit}/pin', [CommitPinController::class, 'destroy']) ->middleware('throttle:pins') ->name('commit-pin.destroy'); Route::get('commits/{commit}/stars', [CommitStarController::class, 'index']) ->name('commit-star.index'); Route::post('commits/{commit}/star', [CommitStarController::class, 'store']) ->middleware('throttle:stars') ->name('commit-star.store'); Route::post('commits/{commit}/pasteis', [CommitPastelController::class, 'store']) ->middleware('throttle:60,1') ->name('commit-pastel.store'); Route::post('commits/{commit}/reports', [CommitReportController::class, 'store']) ->middleware('throttle:commit-reports') ->name('commit-report.store'); Route::delete('commits/{commit}/star', [CommitStarController::class, 'destroy']) ->middleware('throttle:stars') ->name('commit-star.destroy'); Route::post('commits/{commit}/replies', [CommitReplyController::class, 'store']) ->middleware([PreventPostingInTimeout::class, RequireCommitRules::class, ThrottleCommits::class]) ->name('commit-reply.store'); Route::delete('commits/{commit}/replies/{reply}', [CommitReplyController::class, 'destroy']) ->withTrashed() ->name('commit-reply.destroy'); Route::post('commits/{commit}/poll/votes', [CommitPollVoteController::class, 'store']) ->name('commit-poll-vote.store'); // Notifications... Route::get('notifications', [NotificationController::class, 'index'])->name('notification.index'); Route::get('notifications/{notification}/group', [NotificationGroupController::class, 'show'])->name('notification-group.show'); // Messages... Route::get('remotes', [ConversationController::class, 'index'])->name('conversation.index'); Route::get('remotes/@{partner:handle}', [ConversationController::class, 'show'])->name('conversation.show'); Route::get('remotes/orgs/{org}', [OrgController::class, 'show'])->whereUuid('org')->name('org.show'); Route::get('remotes/images/{messageImage}', [MessageImageController::class, 'show'])->whereUuid('messageImage')->name('message-image.show'); Route::get('remotes/voices/{messageVoice}', [MessageVoiceController::class, 'show'])->whereUuid('messageVoice')->name('message-voice.show'); Route::middleware('throttle:conversations')->group(function (): void { Route::get('remotes/inbox', [InboxController::class, 'index'])->name('inbox.index'); Route::get('remotes/@{partner:handle}/messages', [ConversationMessageController::class, 'index']) ->name('conversation-message.index'); Route::put('remotes/@{partner:handle}/read', [ConversationReadController::class, 'update']) ->name('conversation-read.update'); Route::put('remotes/@{partner:handle}/typing', [ConversationTypingController::class, 'update']) ->name('conversation-typing.update'); Route::post('remotes/@{partner:handle}/merge', [ConversationMergeController::class, 'store']) ->name('conversation-merge.store'); Route::post('remotes/@{partner:handle}/drop', [ConversationDropController::class, 'store']) ->name('conversation-drop.store'); Route::put('remotes/preferences', [UserRemotePreferencesController::class, 'update']) ->name('user-remote-preferences.update'); Route::get('remotes/candidates', [RemoteCandidateController::class, 'index'])->name('remote-candidate.index'); Route::post('remotes/orgs', [OrgController::class, 'store'])->middleware([PreventPostingInTimeout::class, 'throttle:messages'])->name('org.store'); Route::put('remotes/orgs/{org}', [OrgController::class, 'update'])->whereUuid('org')->middleware(PreventPostingInTimeout::class)->name('org.update'); Route::get('remotes/orgs/{org}/messages', [OrgMessageController::class, 'index'])->whereUuid('org')->name('org-message.index'); Route::put('remotes/orgs/{org}/read', [OrgReadController::class, 'update'])->whereUuid('org')->name('org-read.update'); Route::put('remotes/orgs/{org}/typing', [OrgTypingController::class, 'update'])->whereUuid('org')->name('org-typing.update'); Route::post('remotes/orgs/{org}/archive', [OrgArchiveController::class, 'store'])->whereUuid('org')->name('org-archive.store'); Route::delete('remotes/orgs/{org}/archive', [OrgArchiveController::class, 'destroy'])->whereUuid('org')->name('org-archive.destroy'); Route::put('remotes/orgs/{org}/mute', [OrgMuteController::class, 'update'])->whereUuid('org')->name('org-mute.update'); Route::put('remotes/orgs/{org}/pin', [OrgPinController::class, 'update'])->whereUuid('org')->name('org-pin.update'); Route::post('remotes/orgs/{org}/members', [OrgMemberController::class, 'store'])->whereUuid('org')->middleware([PreventPostingInTimeout::class, 'throttle:messages'])->name('org-member.store'); Route::put('remotes/orgs/{org}/members/{member:handle}', [OrgMemberController::class, 'update'])->whereUuid('org')->withoutScopedBindings()->name('org-member.update'); Route::delete('remotes/orgs/{org}/members/{member:handle}', [OrgMemberController::class, 'destroy'])->whereUuid('org')->withoutScopedBindings()->name('org-member.destroy'); Route::post('remotes/@{partner:handle}/archive', [ConversationArchiveController::class, 'store']) ->name('conversation-archive.store'); Route::delete('remotes/@{partner:handle}/archive', [ConversationArchiveController::class, 'destroy']) ->name('conversation-archive.destroy'); Route::put('remotes/@{partner:handle}/mute', [ConversationMuteController::class, 'update']) ->name('conversation-mute.update'); Route::put('remotes/@{partner:handle}/pin', [ConversationPinController::class, 'update']) ->name('conversation-pin.update'); Route::get('remotes/messages/{message}/link-preview', [MessageLinkPreviewController::class, 'show']) ->whereNumber('message') ->name('message-link-preview.show'); Route::get('remotes/reactions', [MessageReactionController::class, 'index'])->name('message-reaction.index'); Route::post('remotes/messages/{message}/reactions', [MessageReactionController::class, 'store']) ->whereNumber('message') ->name('message-reaction.store'); Route::delete('remotes/messages/{message}/reactions', [MessageReactionController::class, 'destroy']) ->whereNumber('message') ->name('message-reaction.destroy'); }); Route::put('remotes/messages/{message}/pin', [MessagePinController::class, 'update']) ->whereNumber('message') ->middleware([PreventPostingInTimeout::class, 'throttle:pins']) ->name('message-pin.update'); Route::delete('remotes/messages/{message}/pin', [MessagePinController::class, 'destroy']) ->whereNumber('message') ->middleware('throttle:pins') ->name('message-pin.destroy'); Route::post('remotes/@{partner:handle}/messages', [ConversationMessageController::class, 'store']) ->middleware([PreventPostingInTimeout::class, 'throttle:messages']) ->name('conversation-message.store'); Route::post('remotes/@{partner:handle}/ping', [ConversationPingController::class, 'store']) ->middleware([PreventPostingInTimeout::class, 'throttle:pings']) ->name('conversation-ping.store'); Route::post('remotes/orgs/{org}/ping', [OrgPingController::class, 'store']) ->whereUuid('org') ->middleware([PreventPostingInTimeout::class, 'throttle:pings']) ->name('org-ping.store'); Route::post('remotes/orgs/{org}/messages', [OrgMessageController::class, 'store']) ->whereUuid('org') ->middleware([PreventPostingInTimeout::class, 'throttle:messages']) ->name('org-message.store'); // Moderation... Route::get('moderation', [ModerationController::class, 'show'])->name('moderation.show'); Route::get('moderation/reports', [ReportedCommitController::class, 'index'])->name('reported-commit.index'); Route::delete('moderation/reports/{report}', [CommitReportController::class, 'destroy'])->name('commit-report.destroy'); Route::post('moderation/{commit}/approval', [CommitApprovalController::class, 'store']) ->name('commit-approval.store'); Route::post('moderation/{commit}/rejection', [CommitRejectionController::class, 'store']) ->name('commit-rejection.store'); Route::put('moderation', [ModerationController::class, 'update'])->name('moderation.update'); Route::get('moderation/people', [ModeratedUserController::class, 'index'])->name('moderated-user.index'); Route::get('moderation/bulk', [BulkActivityController::class, 'index'])->name('bulk-activity.index'); Route::delete('moderation/bans/{ban}', [BanController::class, 'destroy'])->whereUuid('ban')->name('ban.destroy'); // Operations... Route::get('operations', [OperationsController::class, 'show'])->name('operations.show'); Route::get('operations/health', [SystemHealthController::class, 'show'])->name('system-health.show'); Route::get('operations/events', [OperationsEventController::class, 'index'])->name('operations-event.index'); Route::get('operations/forecasts', [ForecastController::class, 'show'])->name('forecast.show'); Route::get('operations/features', [FeatureController::class, 'index'])->name('feature.index'); Route::get('operations/activity', [SiteActivityController::class, 'show'])->name('site-activity.show'); Route::get('operations/traffic', [TrafficController::class, 'show'])->name('traffic.show'); Route::get('operations/metrics', [ProductMetricsController::class, 'show'])->name('product-metrics.show'); Route::post('operations/failed-jobs/{failedJob}/retry', [FailedJobRetryController::class, 'store']) ->whereUuid('failedJob') ->name('failed-job-retry.store'); Route::delete('operations/failed-jobs', [FailedJobsController::class, 'destroy'])->name('failed-jobs.destroy'); Route::delete('operations/failed-jobs/{failedJob}', [FailedJobController::class, 'destroy']) ->whereUuid('failedJob') ->name('failed-job.destroy'); Route::put('operations/features/{feature}', [FeatureController::class, 'update'])->name('feature.update'); Route::get('operations/emails', [EmailController::class, 'create'])->name('email.create'); Route::post('operations/emails', [EmailController::class, 'store'])->name('email.store'); Route::post('operations/emails/test', [TestEmailController::class, 'store'])->name('test-email.store'); Route::post('operations/emails/preview', [EmailPreviewController::class, 'store'])->name('email-preview.store'); Route::post('operations/emails/draft', [EmailDraftController::class, 'store']) ->middleware('throttle:10,1') ->name('email-draft.store'); // Mention Suggestions... Route::get('search/mentions', [MentionSuggestionController::class, 'index']) ->middleware('throttle:people') ->name('mention-suggestion.index'); Route::get('search/issues', [IssueSuggestionController::class, 'index']) ->middleware('throttle:issue-suggestions') ->name('issue-suggestion.index'); // User Profile... Route::get('settings', fn () => to_route('user-profile.edit')); Route::get('settings/profile', [UserProfileController::class, 'edit'])->name('user-profile.edit'); Route::put('settings/profile', [UserProfileController::class, 'update']) ->middleware('throttle:profile-syncs') ->name('user-profile.update'); Route::get('settings/export', ExportController::class) ->middleware(['throttle:exports', 'throttle:5,1']) ->name('export'); // User Lane... Route::put('settings/lane', [UserLaneController::class, 'update'])->name('user-lane.update'); // User Timezone... Route::put('settings/timezone', [UserTimezoneController::class, 'update'])->name('user-timezone.update'); Route::put('settings/automatic-translation', [UserAutomaticTranslationController::class, 'update'])->name('user-automatic-translation.update'); Route::put('settings/translation-language', [UserTranslationLanguageController::class, 'update'])->name('user-translation-language.update'); Route::put('settings/pasteis-mode', [UserPasteisModeController::class, 'update'])->name('user-pasteis-mode.update'); Route::put('settings/remotes', [UserRemotesController::class, 'update'])->name('user-remotes.update'); // User Terms, accepted from the dialog every page shows until the current version is... Route::put('settings/terms', [UserTermsController::class, 'update'])->name('user-terms.update'); // User Live... Route::put('settings/live', [UserLiveController::class, 'update']) ->middleware(PreventPostingInTimeout::class) ->name('user-live.update'); Route::delete('settings/live', [UserLiveController::class, 'destroy'])->name('user-live.destroy'); // Appearance... Route::get('settings/appearance', fn () => Inertia::render('appearance/update'))->name('appearance.edit'); // Terminal... Route::get('settings/terminal', fn () => Inertia::render('terminal/show', ['url' => url('/')]))->name('terminal.show'); // Safety... Route::get('settings/safety', [SafetyController::class, 'edit'])->name('safety.edit'); // Devices... Route::get('settings/devices', [DeviceController::class, 'index'])->name('device.index'); Route::delete('settings/devices/{device}', [DeviceController::class, 'destroy'])->name('device.destroy'); // Mobile Authorization... Route::get('mobile/authorize', [MobileAuthorizationController::class, 'show'])->name('mobile-authorization.show'); Route::post('mobile/authorize', [MobileAuthorizationController::class, 'store']) ->middleware('throttle:mobile-authorizations') ->name('mobile-authorization.store'); // User... Route::delete('user', [UserController::class, 'destroy'])->name('user.destroy'); // User Follow... Route::post('@{user:handle}/follow', [UserFollowController::class, 'store']) ->middleware('throttle:follows') ->name('user-follow.store'); Route::delete('@{user:handle}/follow', [UserFollowController::class, 'destroy']) ->middleware('throttle:follows') ->name('user-follow.destroy'); // User Mute... Route::post('@{user:handle}/mute', [UserMuteController::class, 'store']) ->name('user-mute.store'); Route::delete('@{user:handle}/mute', [UserMuteController::class, 'destroy']) ->name('user-mute.destroy'); // User Block... Route::post('@{user:handle}/block', [UserBlockController::class, 'store']) ->name('user-block.store'); Route::delete('@{user:handle}/block', [UserBlockController::class, 'destroy']) ->name('user-block.destroy'); // Suggestion Dismissal... Route::post('@{user:handle}/dismiss', [SuggestionDismissalController::class, 'store']) ->name('suggestion-dismissal.store'); // User Timeout, given by moderators... Route::post('@{user:handle}/timeout', [UserTimeoutController::class, 'store']) ->name('user-timeout.store'); Route::delete('@{user:handle}/timeout', [UserTimeoutController::class, 'destroy']) ->name('user-timeout.destroy'); // User Ban, given by moderators... Route::post('@{user:handle}/ban', [UserBanController::class, 'store']) ->name('user-ban.store'); Route::delete('@{user:handle}/ban', [UserBanController::class, 'destroy']) ->name('user-ban.destroy'); }); Route::fallback(fn () => abort(404));