├── .editorconfig ├── .env.example ├── .gitattributes ├── .gitignore ├── Dockerfile ├── LICENSE.md ├── README.md ├── SECURITY.md ├── app ├── Actions │ ├── Fortify │ │ ├── CreateNewUser.php │ │ ├── PasswordValidationRules.php │ │ ├── ResetUserPassword.php │ │ ├── UpdateUserPassword.php │ │ └── UpdateUserProfileInformation.php │ ├── Jetstream │ │ ├── AddTeamMember.php │ │ ├── CreateTeam.php │ │ ├── DeleteTeam.php │ │ ├── DeleteUser.php │ │ ├── InviteTeamMember.php │ │ ├── RemoveTeamMember.php │ │ └── UpdateTeamName.php │ └── User │ │ ├── ActiveOauthProviderAction.php │ │ └── HandleOauthCallbackAction.php ├── Console │ └── Commands │ │ └── GenerateSitemap.php ├── Enums │ └── AppEnvironment.php ├── Exceptions │ └── OAuthAccountLinkingException.php ├── Filament │ └── Resources │ │ ├── UserResource.php │ │ └── UserResource │ │ └── Pages │ │ └── ListUsers.php ├── Http │ ├── Controllers │ │ ├── ApiUserController.php │ │ ├── ChatController.php │ │ ├── Controller.php │ │ ├── DashboardController.php │ │ ├── SubscriptionController.php │ │ ├── User │ │ │ ├── LoginLinkController.php │ │ │ └── OauthController.php │ │ └── WelcomeController.php │ └── Middleware │ │ └── HandleInertiaRequests.php ├── Jobs │ └── User │ │ └── UpdateUserProfileInformationJob.php ├── Models │ ├── LoginLink.php │ ├── Membership.php │ ├── OauthConnection.php │ ├── Team.php │ ├── TeamInvitation.php │ └── User.php ├── Notifications │ └── LoginLinkMail.php ├── Policies │ ├── TeamPolicy.php │ └── UserPolicy.php ├── Providers │ ├── AppServiceProvider.php │ ├── Filament │ │ └── AdminPanelProvider.php │ ├── FortifyServiceProvider.php │ ├── JetstreamServiceProvider.php │ └── TelescopeServiceProvider.php └── Traits │ └── AsFakeAction.php ├── artisan ├── bootstrap ├── app.php ├── cache │ └── .gitignore └── providers.php ├── bun.lockb ├── components.json ├── composer.json ├── composer.lock ├── config ├── app.php ├── auth.php ├── blasp.php ├── cache.php ├── cashier.php ├── database.php ├── filament.php ├── filesystems.php ├── fortify.php ├── ide-helper.php ├── jetstream.php ├── logging.php ├── mail.php ├── oauth.php ├── octane.php ├── prism.php ├── queue.php ├── sanctum.php ├── scribe.php ├── sentry.php ├── services.php ├── session.php ├── sitemap.php ├── subscriptions.php └── telescope.php ├── database ├── .gitignore ├── factories │ ├── LoginLinkFactory.php │ ├── OauthConnectionFactory.php │ ├── TeamFactory.php │ └── UserFactory.php ├── migrations │ ├── 0001_01_01_000000_create_users_table.php │ ├── 0001_01_01_000001_create_cache_table.php │ ├── 0001_01_01_000002_create_jobs_table.php │ ├── 0001_01_01_000003_add_two_factor_columns_to_users_table.php │ ├── 0001_01_01_000004_create_personal_access_tokens_table.php │ ├── 0001_01_01_000005_create_teams_table.php │ ├── 0001_01_01_000006_create_team_user_table.php │ ├── 0001_01_01_000007_create_team_invitations_table.php │ ├── 0001_01_01_000008_create_oauth_connections_table.php │ ├── 0001_01_01_000009_create_telescope_entries_table.php │ ├── 0001_01_01_000010_create_customer_columns.php │ ├── 0001_01_01_000011_create_subscriptions_table.php │ ├── 0001_01_01_000012_create_subscription_items_table.php │ └── 0001_01_01_000013_create_login_links_table.php └── seeders │ └── DatabaseSeeder.php ├── docker-compose.yml ├── docker ├── 8.3 │ ├── Dockerfile │ ├── php.ini │ ├── start-container │ └── supervisord.conf ├── pgsql │ └── create-testing-database.sql └── php │ └── production.ini ├── eslint.config.js ├── jsconfig.json ├── package.json ├── phpstan.neon ├── phpunit.xml ├── pint.json ├── postcss.config.js ├── public ├── .htaccess ├── .user.ini ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-touch-icon.png ├── css │ └── filament │ │ ├── filament │ │ └── app.css │ │ ├── forms │ │ └── forms.css │ │ └── support │ │ └── support.css ├── favicon copy.ico ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── images │ ├── dashboard-dark.webp │ ├── dashboard-light.webp │ ├── og.webp │ └── rocket-dark.png ├── index.php ├── js │ └── filament │ │ ├── filament │ │ ├── app.js │ │ └── echo.js │ │ ├── forms │ │ └── components │ │ │ ├── color-picker.js │ │ │ ├── date-time-picker.js │ │ │ ├── file-upload.js │ │ │ ├── key-value.js │ │ │ ├── markdown-editor.js │ │ │ ├── rich-editor.js │ │ │ ├── select.js │ │ │ ├── tags-input.js │ │ │ └── textarea.js │ │ ├── notifications │ │ └── notifications.js │ │ ├── support │ │ ├── async-alpine.js │ │ └── support.js │ │ ├── tables │ │ ├── components │ │ │ └── table.js │ │ └── tables.js │ │ └── widgets │ │ └── components │ │ ├── chart.js │ │ └── stats-overview │ │ └── stat │ │ └── chart.js ├── llms.txt ├── robots.txt ├── site.webmanifest └── vendor │ └── telescope │ ├── app-dark.css │ ├── app.css │ ├── app.js │ ├── favicon.ico │ └── mix-manifest.json ├── rector.php ├── resources ├── css │ ├── app.css │ └── fonts.css ├── js │ ├── Components │ │ ├── ActionSection.jsx │ │ ├── AppSidebarContent.jsx │ │ ├── AppTeamManager.jsx │ │ ├── AppUserManager.jsx │ │ ├── ApplicationLogo.jsx │ │ ├── ApplicationShortMark.jsx │ │ ├── ConfirmationModal.jsx │ │ ├── ConfirmsPassword.jsx │ │ ├── FeaturesCard.jsx │ │ ├── FormSection.jsx │ │ ├── InputError.jsx │ │ ├── LogoRedirect.jsx │ │ ├── PricingCard.jsx │ │ ├── SocialLoginButton.jsx │ │ ├── StatsCard.jsx │ │ ├── Terminal.jsx │ │ ├── hooks │ │ │ ├── use-mobile.jsx │ │ │ └── use-toast.js │ │ ├── lib │ │ │ └── utils.js │ │ └── shadcn │ │ │ └── ui │ │ │ ├── accordion.jsx │ │ │ ├── alert-dialog.jsx │ │ │ ├── alert.jsx │ │ │ ├── aspect-ratio.jsx │ │ │ ├── avatar.jsx │ │ │ ├── badge.jsx │ │ │ ├── breadcrumb.jsx │ │ │ ├── button.jsx │ │ │ ├── calendar.jsx │ │ │ ├── card.jsx │ │ │ ├── carousel.jsx │ │ │ ├── chart.jsx │ │ │ ├── checkbox.jsx │ │ │ ├── collapsible.jsx │ │ │ ├── command.jsx │ │ │ ├── context-menu.jsx │ │ │ ├── dialog.jsx │ │ │ ├── drawer.jsx │ │ │ ├── dropdown-menu.jsx │ │ │ ├── form.jsx │ │ │ ├── hover-card.jsx │ │ │ ├── input-otp.jsx │ │ │ ├── input.jsx │ │ │ ├── label.jsx │ │ │ ├── menubar.jsx │ │ │ ├── navigation-menu.jsx │ │ │ ├── pagination.jsx │ │ │ ├── popover.jsx │ │ │ ├── progress.jsx │ │ │ ├── radio-group.jsx │ │ │ ├── resizable.jsx │ │ │ ├── scroll-area.jsx │ │ │ ├── select.jsx │ │ │ ├── separator.jsx │ │ │ ├── sheet.jsx │ │ │ ├── sidebar.jsx │ │ │ ├── skeleton.jsx │ │ │ ├── slider.jsx │ │ │ ├── sonner.jsx │ │ │ ├── switch.jsx │ │ │ ├── table.jsx │ │ │ ├── tabs.jsx │ │ │ ├── textarea.jsx │ │ │ ├── toast.jsx │ │ │ ├── toaster.jsx │ │ │ ├── toggle-group.jsx │ │ │ ├── toggle.jsx │ │ │ └── tooltip.jsx │ ├── Composables │ │ └── useSeoMetaTags.js │ ├── Layouts │ │ ├── AppLayout.jsx │ │ └── WebLayout.jsx │ ├── Pages │ │ ├── API │ │ │ ├── Index.jsx │ │ │ └── Partials │ │ │ │ └── ApiTokenManager.jsx │ │ ├── Auth │ │ │ ├── ConfirmPassword.jsx │ │ │ ├── ForgotPassword.jsx │ │ │ ├── Login.jsx │ │ │ ├── Register.jsx │ │ │ ├── ResetPassword.jsx │ │ │ ├── TwoFactorChallenge.jsx │ │ │ └── VerifyEmail.jsx │ │ ├── Chat │ │ │ ├── Components │ │ │ │ ├── ModelSelector.jsx │ │ │ │ └── TemperatureSelector.jsx │ │ │ └── Index.jsx │ │ ├── Dashboard.jsx │ │ ├── PrivacyPolicy.jsx │ │ ├── Profile │ │ │ ├── Partials │ │ │ │ ├── DeleteUserForm.jsx │ │ │ │ ├── LinkedAccountsForm.jsx │ │ │ │ ├── LogoutOtherBrowserSessionsForm.jsx │ │ │ │ ├── TwoFactorAuthenticationForm.jsx │ │ │ │ ├── UpdatePasswordForm.jsx │ │ │ │ └── UpdateProfileInformationForm.jsx │ │ │ └── Show.jsx │ │ ├── Subscriptions │ │ │ ├── Index.jsx │ │ │ └── Partials │ │ │ │ ├── InvoiceManager.jsx │ │ │ │ └── SubscriptionManager.jsx │ │ ├── Teams │ │ │ ├── Create.jsx │ │ │ ├── Partials │ │ │ │ ├── CreateTeamForm.jsx │ │ │ │ ├── DeleteTeamForm.jsx │ │ │ │ ├── TeamMemberManager.jsx │ │ │ │ └── UpdateTeamNameForm.jsx │ │ │ └── Show.jsx │ │ ├── TermsOfService.jsx │ │ └── Welcome.jsx │ ├── app.jsx │ └── bootstrap.js ├── markdown │ ├── policy.md │ └── terms.md └── views │ ├── app.blade.php │ ├── emails │ └── team-invitation.blade.php │ ├── prompts │ └── system.blade.php │ ├── scribe │ └── index.blade.php │ └── vendor │ └── mail │ └── html │ └── themes │ └── default.css ├── routes ├── api.php ├── console.php └── web.php ├── storage ├── app │ ├── .gitignore │ ├── private │ │ └── .gitignore │ └── public │ │ └── .gitignore ├── framework │ ├── .gitignore │ ├── cache │ │ ├── .gitignore │ │ └── data │ │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ ├── testing │ │ └── .gitignore │ └── views │ │ └── .gitignore └── logs │ └── .gitignore ├── tailwind.config.js ├── tests ├── Feature │ ├── Actions │ │ ├── Jetstream │ │ │ └── AddTeamMemberTest.php │ │ └── User │ │ │ └── HandleOauthCallbackActionTest.php │ ├── ApiTokenPermissionsTest.php │ ├── AuthenticationTest.php │ ├── BrowserSessionsTest.php │ ├── Controllers │ │ ├── ApiUserControllerTest.php │ │ ├── DashboardControllerTest.php │ │ └── User │ │ │ ├── LoginLinkControllerTest.php │ │ │ └── OauthControllerTest.php │ ├── CreateApiTokenTest.php │ ├── CreateTeamTest.php │ ├── DeleteAccountTest.php │ ├── DeleteApiTokenTest.php │ ├── DeleteTeamTest.php │ ├── EmailVerificationTest.php │ ├── ExampleTest.php │ ├── InviteTeamMemberTest.php │ ├── Jobs │ │ └── User │ │ │ └── UpdateUserProfileInformationJobTest.php │ ├── LeaveTeamTest.php │ ├── Models │ │ ├── LoginLinkTest.php │ │ └── OauthConnectionTest.php │ ├── PasswordConfirmationTest.php │ ├── PasswordResetTest.php │ ├── Policies │ │ └── UserPolicyTest.php │ ├── ProfileInformationTest.php │ ├── RegistrationTest.php │ ├── RemoveTeamMemberTest.php │ ├── TwoFactorAuthenticationSettingsTest.php │ ├── UpdatePasswordTest.php │ ├── UpdateTeamMemberRoleTest.php │ └── UpdateTeamNameTest.php ├── Pest.php ├── TestCase.php └── Unit │ └── ExampleTest.php └── vite.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | 17 | [docker-compose.yml] 18 | indent_size = 4 19 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=Larasonic 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_TIMEZONE=UTC 6 | APP_URL=http://localhost:8010 7 | APP_PORT=8010 8 | 9 | APP_LOCALE=en 10 | APP_FALLBACK_LOCALE=en 11 | APP_FAKER_LOCALE=en_US 12 | 13 | APP_MAINTENANCE_DRIVER=file 14 | # APP_MAINTENANCE_STORE=database 15 | 16 | PHP_CLI_SERVER_WORKERS=4 17 | 18 | BCRYPT_ROUNDS=12 19 | 20 | LOG_CHANNEL=stack 21 | LOG_STACK=single 22 | LOG_DEPRECATIONS_CHANNEL=null 23 | LOG_LEVEL=debug 24 | 25 | DB_CONNECTION=sqlite 26 | DB_HOST=127.0.0.1 27 | DB_PORT=3306 28 | DB_DATABASE=database/database.sqlite 29 | DB_USERNAME=sail 30 | DB_PASSWORD=password 31 | FORWARD_DB_PORT="${DB_PORT}" 32 | 33 | SESSION_DRIVER=database 34 | SESSION_LIFETIME=120 35 | SESSION_ENCRYPT=false 36 | SESSION_PATH=/ 37 | SESSION_DOMAIN=null 38 | 39 | BROADCAST_CONNECTION=log 40 | FILESYSTEM_DISK=local 41 | QUEUE_CONNECTION=database 42 | 43 | CACHE_STORE=database 44 | CACHE_PREFIX= 45 | 46 | MEMCACHED_HOST=127.0.0.1 47 | 48 | REDIS_CLIENT=phpredis 49 | REDIS_HOST=redis 50 | REDIS_PASSWORD=null 51 | REDIS_PORT=6379 52 | 53 | MAIL_MAILER=smtp 54 | MAIL_SCHEME=null 55 | MAIL_HOST=mailpit 56 | MAIL_PORT=1025 57 | MAIL_USERNAME=null 58 | MAIL_PASSWORD=null 59 | MAIL_FROM_ADDRESS="hello@example.com" 60 | MAIL_FROM_NAME="${APP_NAME}" 61 | 62 | AWS_ACCESS_KEY_ID= 63 | AWS_SECRET_ACCESS_KEY= 64 | AWS_DEFAULT_REGION=us-east-1 65 | AWS_BUCKET= 66 | AWS_USE_PATH_STYLE_ENDPOINT=false 67 | 68 | VITE_APP_NAME="${APP_NAME}" 69 | 70 | OCTANE_SERVER=frankenphp 71 | 72 | GITHUB_CLIENT_ID= 73 | GITHUB_CLIENT_SECRET= 74 | GOOGLE_CLIENT_ID= 75 | GOOGLE_CLIENT_SECRET= 76 | GITLAB_CLIENT_ID= 77 | GITLAB_CLIENT_SECRET= 78 | 79 | STRIPE_KEY= 80 | STRIPE_SECRET= 81 | STRIPE_WEBHOOK_SECRET= 82 | 83 | SENTRY_LARAVEL_DSN= 84 | 85 | GEMINI_API_KEY= 86 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | *.blade.php diff=html 4 | *.css diff=css 5 | *.html diff=html 6 | *.md diff=markdown 7 | *.php diff=php 8 | 9 | /.github export-ignore 10 | CHANGELOG.md export-ignore 11 | .styleci.yml export-ignore 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.phpunit.cache 2 | /node_modules 3 | /public/build 4 | /public/hot 5 | /public/storage 6 | /storage/*.key 7 | /storage/pail 8 | /vendor 9 | .env 10 | .env.backup 11 | .env.production 12 | .phpactor.json 13 | .phpunit.result.cache 14 | Homestead.json 15 | Homestead.yaml 16 | npm-debug.log 17 | yarn-error.log 18 | /auth.json 19 | /.fleet 20 | /.idea 21 | /.vscode 22 | /.zed 23 | 24 | **/caddy 25 | frankenphp 26 | frankenphp-worker.php 27 | **/composer 28 | 29 | .DS_Store 30 | 31 | _ide_helper.php 32 | _ide_helper_models.php 33 | .phpstorm.meta.php 34 | 35 | .scribe/ 36 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Stage 1: Frontend Assets build 2 | FROM oven/bun:1-slim AS node-builder 3 | WORKDIR /app 4 | COPY . . 5 | RUN bun install --frozen-lockfile 6 | RUN bun run build 7 | 8 | # Stage 2: Final image 9 | FROM dunglas/frankenphp:1.4.0-php8.3-alpine AS base 10 | 11 | # Create required directories with proper permissions 12 | RUN mkdir -p /data/caddy /config/caddy /home/.local/share/caddy && \ 13 | chmod -R 755 /data /config /home/.local && \ 14 | # Add non-root user 15 | addgroup -g 1000 appgroup && \ 16 | adduser -u 1000 -G appgroup -h /app -s /bin/sh -D appuser && \ 17 | # Give ownership of Caddy directories 18 | chown -R appuser:appgroup /data /config /home/.local 19 | 20 | # Set Caddy environment variables 21 | ENV XDG_CONFIG_HOME=/config \ 22 | XDG_DATA_HOME=/data 23 | 24 | # Install composer and PHP extensions 25 | COPY --from=composer:2 /usr/bin/composer /usr/bin/composer 26 | RUN install-php-extensions \ 27 | pcntl \ 28 | intl \ 29 | pdo_mysql \ 30 | zip \ 31 | bcmath && \ 32 | # Cleanup 33 | rm -rf /tmp/* /var/cache/apk/* 34 | 35 | # Environment configuration 36 | ENV APP_ENV=production \ 37 | APP_DEBUG=false \ 38 | OCTANE_SERVER=frankenphp 39 | 40 | # Configure PHP for production 41 | COPY docker/php/production.ini $PHP_INI_DIR/conf.d/ 42 | RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" 43 | 44 | # Set up application 45 | WORKDIR /app 46 | COPY --chown=appuser:appgroup . . 47 | COPY --from=node-builder --chown=appuser:appgroup /app/public/build/ ./public/build/ 48 | 49 | # Install dependencies and optimize 50 | RUN composer install --prefer-dist --optimize-autoloader && \ 51 | php artisan optimize && \ 52 | php artisan view:cache && \ 53 | php artisan config:cache && \ 54 | php artisan route:cache && \ 55 | php artisan event:cache && \ 56 | # Set proper permissions 57 | chown -R appuser:appgroup /app && \ 58 | chmod -R 755 storage bootstrap/cache && \ 59 | rm -rf tests node_modules docker .git* && \ 60 | composer clear-cache 61 | 62 | USER appuser 63 | 64 | # Expose port 65 | EXPOSE 8000 66 | 67 | ENTRYPOINT ["php", "artisan", "octane:start", "--host=0.0.0.0"] 68 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Pushpak Chhajed 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | PLEASE DON'T DISCLOSE SECURITY-RELATED ISSUES PUBLICLY. See Below. 4 | 5 | ## Reporting a Vulnerability 6 | 7 | If you discover a security vulnerability within Larasonic, please send an email to Pushpak at hey@pushpak1300.me All security vulnerabilities will be promptly addressed. 8 | -------------------------------------------------------------------------------- /app/Actions/Fortify/CreateNewUser.php: -------------------------------------------------------------------------------- 1 | $input 27 | */ 28 | public function create(array $input): User 29 | { 30 | Validator::make($input, [ 31 | 'name' => ['required', 'string', 'max:255'], 32 | 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 33 | 'password' => Arr::get($input, 'password') ? $this->passwordRules() : 'sometimes', 34 | 'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature() ? ['accepted', 'required'] : '', 35 | ])->validate(); 36 | 37 | return DB::transaction(fn () => tap(User::query()->create([ 38 | 'name' => $input['name'], 39 | 'email' => $input['email'], 40 | 'password' => Arr::get($input, 'password') ? Hash::make($input['password']) : Str::random(12), 41 | ]), function (User $user): void { 42 | $this->createTeam($user); 43 | $this->createCustomer($user); 44 | })); 45 | } 46 | 47 | /** 48 | * Create a personal team for the user. 49 | */ 50 | private function createTeam(User $user): void 51 | { 52 | $user->ownedTeams()->save(Team::query()->forceCreate([ 53 | 'user_id' => $user->id, 54 | 'name' => explode(' ', $user->name, 2)[0]."'s Team", 55 | 'personal_team' => true, 56 | ])); 57 | } 58 | 59 | /** 60 | * Create a billing customer for the user. 61 | */ 62 | private function createCustomer(User $user): void 63 | { 64 | if (! Config::get('cashier.billing_enabled')) { 65 | return; 66 | } 67 | 68 | /** @var Customer $stripeCustomer */ 69 | $stripeCustomer = $user->createOrGetStripeCustomer(); 70 | 71 | $user->update([ 72 | 'stripe_id' => $stripeCustomer->id, 73 | ]); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/Actions/Fortify/PasswordValidationRules.php: -------------------------------------------------------------------------------- 1 | |string> 16 | */ 17 | protected function passwordRules(): array 18 | { 19 | return ['required', 'string', Password::default(), 'confirmed']; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Actions/Fortify/ResetUserPassword.php: -------------------------------------------------------------------------------- 1 | $input 20 | */ 21 | public function reset(User $user, array $input): void 22 | { 23 | Validator::make($input, [ 24 | 'password' => $this->passwordRules(), 25 | ])->validate(); 26 | 27 | $user->forceFill([ 28 | 'password' => Hash::make($input['password']), 29 | ])->save(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Actions/Fortify/UpdateUserPassword.php: -------------------------------------------------------------------------------- 1 | $input 20 | */ 21 | public function update(User $user, array $input): void 22 | { 23 | Validator::make($input, [ 24 | 'password' => $this->passwordRules(), 25 | ])->validateWithBag('updatePassword'); 26 | 27 | $user->forceFill([ 28 | 'password' => Hash::make($input['password']), 29 | ])->save(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Actions/Fortify/UpdateUserProfileInformation.php: -------------------------------------------------------------------------------- 1 | $input 19 | */ 20 | public function update(User $user, array $input): void 21 | { 22 | Validator::make($input, [ 23 | 'name' => ['required', 'string', 'max:255'], 24 | 'email' => ['required', 'email', 'max:255', Rule::unique('users')->ignore($user->id)], 25 | 'photo' => ['nullable', 'mimes:jpg,jpeg,png', 'max:1024'], 26 | ])->validateWithBag('updateProfileInformation'); 27 | 28 | if (isset($input['photo']) && $input['photo'] instanceof UploadedFile) { 29 | $user->updateProfilePhoto($input['photo']); 30 | } 31 | 32 | if ($input['email'] !== $user->email && $user->hasVerifiedEmail()) { 33 | $validated = Validator::make($input, [ 34 | 'name' => ['required', 'string'], 35 | 'email' => ['required', 'string'], 36 | ])->validate(); 37 | 38 | /** @var array{name: string, email: string} $data */ 39 | $data = [ 40 | 'name' => $validated['name'], 41 | 'email' => $validated['email'], 42 | ]; 43 | 44 | $this->updateVerifiedUser($user, $data); 45 | } else { 46 | $user->forceFill([ 47 | 'name' => $input['name'], 48 | 'email' => $input['email'], 49 | ])->save(); 50 | } 51 | } 52 | 53 | /** 54 | * Update the given verified user's profile information. 55 | * 56 | * @param array $input 57 | */ 58 | private function updateVerifiedUser(User $user, array $input): void 59 | { 60 | $user->forceFill([ 61 | 'name' => $input['name'], 62 | 'email' => $input['email'], 63 | 'email_verified_at' => null, 64 | ])->save(); 65 | 66 | $user->sendEmailVerificationNotification(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/Actions/Jetstream/CreateTeam.php: -------------------------------------------------------------------------------- 1 | authorize('create', Jetstream::newTeamModel()); 25 | 26 | Validator::make($input, [ 27 | 'name' => ['required', 'string', 'max:255'], 28 | ])->validateWithBag('createTeam'); 29 | 30 | AddingTeam::dispatch($user); 31 | 32 | /** @var Team $team */ 33 | $team = $user->ownedTeams()->create([ 34 | 'name' => $input['name'], 35 | 'personal_team' => false, 36 | ]); 37 | $user->switchTeam($team); 38 | 39 | return $team; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Actions/Jetstream/DeleteTeam.php: -------------------------------------------------------------------------------- 1 | purge(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Actions/Jetstream/DeleteUser.php: -------------------------------------------------------------------------------- 1 | deleteTeams($user); 27 | $user->deleteProfilePhoto(); 28 | $user->tokens->each->delete(); 29 | $user->delete(); 30 | }); 31 | } 32 | 33 | /** 34 | * Delete the teams and team associations attached to the user. 35 | */ 36 | private function deleteTeams(User $user): void 37 | { 38 | $user->teams()->detach(); 39 | 40 | $user->ownedTeams->each(function (Team $team): void { 41 | $this->deletesTeams->delete($team); 42 | }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Actions/Jetstream/RemoveTeamMember.php: -------------------------------------------------------------------------------- 1 | authorize($user, $team, $teamMember); 23 | 24 | $this->ensureUserDoesNotOwnTeam($teamMember, $team); 25 | 26 | $team->removeUser($teamMember); 27 | 28 | TeamMemberRemoved::dispatch($team, $teamMember); 29 | } 30 | 31 | /** 32 | * Authorize that the user can remove the team member. 33 | */ 34 | private function authorize(User $user, Team $team, User $teamMember): void 35 | { 36 | throw_if(! Gate::forUser($user)->check('removeTeamMember', $team) && 37 | $user->id !== $teamMember->id, new AuthorizationException); 38 | } 39 | 40 | /** 41 | * Ensure that the currently authenticated user does not own the team. 42 | */ 43 | private function ensureUserDoesNotOwnTeam(User $teamMember, Team $team): void 44 | { 45 | /** @var User $owner */ 46 | $owner = $team->owner; 47 | throw_if($teamMember->id === $owner->id, ValidationException::withMessages([ 48 | 'team' => [__('You may not leave a team that you created.')], 49 | ])->errorBag('removeTeamMember')); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/Actions/Jetstream/UpdateTeamName.php: -------------------------------------------------------------------------------- 1 | $input 19 | */ 20 | public function update(User $user, Team $team, array $input): void 21 | { 22 | Gate::forUser($user)->authorize('update', $team); 23 | 24 | Validator::make($input, [ 25 | 'name' => ['required', 'string', 'max:255'], 26 | ])->validateWithBag('updateTeamName'); 27 | 28 | $team->forceFill([ 29 | 'name' => $input['name'], 30 | ])->save(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Actions/User/ActiveOauthProviderAction.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | public function handle(): array 17 | { 18 | /** @var array */ 19 | $providers = Config::array('oauth.providers'); 20 | 21 | return array_filter($providers, fn (array $provider): bool => $provider['active']); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Console/Commands/GenerateSitemap.php: -------------------------------------------------------------------------------- 1 | writeToFile(public_path('sitemap.xml')); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/Enums/AppEnvironment.php: -------------------------------------------------------------------------------- 1 | $provider])); 16 | } 17 | 18 | public static function existingConnection(): self 19 | { 20 | return new self(self::EXISTING_CONNECTION_ERROR_MESSAGE); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Filament/Resources/UserResource/Pages/ListUsers.php: -------------------------------------------------------------------------------- 1 | |null 28 | * 29 | * @authenticated 30 | */ 31 | public function index(): ?Collection 32 | { 33 | Gate::authorize('viewAny', User::class); 34 | 35 | return type(Auth::user())->as(User::class)->currentTeam?->users; 36 | } 37 | 38 | /** 39 | * Create a new user. 40 | * 41 | * @authenticated 42 | */ 43 | public function store(Request $request): User 44 | { 45 | Gate::authorize('create', User::class); 46 | 47 | return app(CreateNewUser::class)->create([ 48 | 'name' => (string) $request->string('name'), 49 | 'email' => (string) $request->string('email'), 50 | 'password' => (string) $request->string('password'), 51 | 'password_confirmation' => (string) $request->string('password_confirmation'), 52 | 'terms' => 'true', 53 | ]); 54 | } 55 | 56 | /** 57 | * Get a specific user by ID. 58 | */ 59 | public function show(User $user): User 60 | { 61 | Gate::authorize('view', $user); 62 | 63 | return $user; 64 | } 65 | 66 | /** 67 | * Update user profile information. 68 | * 69 | * @authenticated 70 | */ 71 | public function update(Request $request, User $user): User 72 | { 73 | Gate::authorize('update', $user); 74 | 75 | app(UpdateUserProfileInformation::class)->update($user, [ 76 | 'name' => $request->name, 77 | 'email' => $request->email, 78 | ]); 79 | 80 | return $user; 81 | } 82 | 83 | /** 84 | * Delete a user. 85 | * 86 | * @authenticated 87 | */ 88 | public function destroy(User $user): Response 89 | { 90 | Gate::authorize('delete', $user); 91 | app(DeleteUser::class)->delete($user); 92 | 93 | return response()->noContent(); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/Http/Controllers/ChatController.php: -------------------------------------------------------------------------------- 1 | $user->subscribed('Larasonic Pro ✨'), 22 | 'systemPrompt' => view('prompts.system')->render(), 23 | 'models' => PrismServer::prisms()->pluck('name'), 24 | ]); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | Route::has('login'), 17 | 'canRegister' => Route::has('register'), 18 | 'seo' => [ 19 | 'title' => 'Home', 20 | ], 21 | ]); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Http/Middleware/HandleInertiaRequests.php: -------------------------------------------------------------------------------- 1 | 38 | */ 39 | public function share(Request $request): array 40 | { 41 | /** @var array */ 42 | return array_merge(parent::share($request), [ 43 | 'name' => Config::get('app.name', 'Larasonic'), 44 | 'flash' => [ 45 | 'message' => fn () => $request->session()->get('message'), 46 | 'success' => fn () => $request->session()->get('success'), 47 | 'error' => fn () => $request->session()->get('error'), 48 | ], 49 | ]); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/Jobs/User/UpdateUserProfileInformationJob.php: -------------------------------------------------------------------------------- 1 | user; 31 | $socialiteUser = $this->socialiteUser; 32 | $user->oauthConnections()->updateOrCreate([ 33 | 'provider' => $this->provider, 34 | ], [ 35 | 'provider_id' => $socialiteUser->getId(), 36 | 'data' => $socialiteUser->getRaw(), 37 | 'token' => $socialiteUser->token, 38 | 'refresh_token' => $socialiteUser->refreshToken, 39 | 'expires_at' => $socialiteUser->expiresIn ? now()->addSeconds($socialiteUser->expiresIn) : null, 40 | ]); 41 | 42 | $user->profile_photo_path = $socialiteUser->getAvatar(); 43 | $user->email_verified_at ??= now(); 44 | $user->save(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/Models/Membership.php: -------------------------------------------------------------------------------- 1 | |Membership newModelQuery() 19 | * @method static \Illuminate\Database\Eloquent\Builder|Membership newQuery() 20 | * @method static \Illuminate\Database\Eloquent\Builder|Membership query() 21 | * @method static \Illuminate\Database\Eloquent\Builder|Membership whereCreatedAt($value) 22 | * @method static \Illuminate\Database\Eloquent\Builder|Membership whereId($value) 23 | * @method static \Illuminate\Database\Eloquent\Builder|Membership whereRole($value) 24 | * @method static \Illuminate\Database\Eloquent\Builder|Membership whereTeamId($value) 25 | * @method static \Illuminate\Database\Eloquent\Builder|Membership whereUpdatedAt($value) 26 | * @method static \Illuminate\Database\Eloquent\Builder|Membership whereUserId($value) 27 | * 28 | * @mixin \Eloquent 29 | */ 30 | final class Membership extends JetstreamMembership 31 | { 32 | /** 33 | * Indicates if the IDs are auto-incrementing. 34 | * 35 | * @var bool 36 | */ 37 | public $incrementing = true; 38 | } 39 | -------------------------------------------------------------------------------- /app/Models/TeamInvitation.php: -------------------------------------------------------------------------------- 1 | |TeamInvitation newModelQuery() 21 | * @method static \Illuminate\Database\Eloquent\Builder|TeamInvitation newQuery() 22 | * @method static \Illuminate\Database\Eloquent\Builder|TeamInvitation query() 23 | * @method static \Illuminate\Database\Eloquent\Builder|TeamInvitation whereCreatedAt($value) 24 | * @method static \Illuminate\Database\Eloquent\Builder|TeamInvitation whereEmail($value) 25 | * @method static \Illuminate\Database\Eloquent\Builder|TeamInvitation whereId($value) 26 | * @method static \Illuminate\Database\Eloquent\Builder|TeamInvitation whereRole($value) 27 | * @method static \Illuminate\Database\Eloquent\Builder|TeamInvitation whereTeamId($value) 28 | * @method static \Illuminate\Database\Eloquent\Builder|TeamInvitation whereUpdatedAt($value) 29 | * 30 | * @mixin \Eloquent 31 | */ 32 | final class TeamInvitation extends JetstreamTeamInvitation 33 | { 34 | /** 35 | * The attributes that are mass assignable. 36 | * 37 | * @var list 38 | */ 39 | protected $fillable = [ 40 | 'email', 41 | 'role', 42 | ]; 43 | 44 | /** 45 | * Get the team that the invitation belongs to. 46 | * 47 | * @return BelongsTo 48 | */ 49 | public function team(): BelongsTo 50 | { 51 | return $this->belongsTo(Team::class); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/Notifications/LoginLinkMail.php: -------------------------------------------------------------------------------- 1 | magicLinkUrl = URL::signedRoute('login-link.login', ['token' => $this->magicLink->token]); 22 | } 23 | 24 | /** 25 | * @return array 26 | */ 27 | public function via(): array 28 | { 29 | return ['mail']; 30 | } 31 | 32 | public function toMail(): MailMessage 33 | { 34 | return (new MailMessage) 35 | ->subject('Your Magic Login Link') 36 | ->line('Click the button below to log in.') 37 | ->action('Log In', $this->magicLinkUrl) 38 | ->line('This link will expire in 15 minutes.') 39 | ->line('If you did not request this link, no action is needed.'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Policies/TeamPolicy.php: -------------------------------------------------------------------------------- 1 | belongsToTeam($team); 29 | } 30 | 31 | /** 32 | * Determine whether the user can create models. 33 | */ 34 | public function create(): bool 35 | { 36 | return true; 37 | } 38 | 39 | /** 40 | * Determine whether the user can update the model. 41 | */ 42 | public function update(User $user, Team $team): bool 43 | { 44 | return $user->ownsTeam($team); 45 | } 46 | 47 | /** 48 | * Determine whether the user can add team members. 49 | */ 50 | public function addTeamMember(User $user, Team $team): bool 51 | { 52 | return $user->ownsTeam($team); 53 | } 54 | 55 | /** 56 | * Determine whether the user can update team member permissions. 57 | */ 58 | public function updateTeamMember(User $user, Team $team): bool 59 | { 60 | return $user->ownsTeam($team); 61 | } 62 | 63 | /** 64 | * Determine whether the user can remove team members. 65 | */ 66 | public function removeTeamMember(User $user, Team $team): bool 67 | { 68 | return $user->ownsTeam($team); 69 | } 70 | 71 | /** 72 | * Determine whether the user can delete the model. 73 | */ 74 | public function delete(User $user, Team $team): bool 75 | { 76 | return $user->ownsTeam($team); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/Providers/Filament/AdminPanelProvider.php: -------------------------------------------------------------------------------- 1 | default() 29 | ->id('admin') 30 | ->path('admin') 31 | ->login() 32 | ->colors([ 33 | 'primary' => Color::Amber, 34 | ]) 35 | ->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources') 36 | ->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages') 37 | ->pages([ 38 | Pages\Dashboard::class, 39 | ]) 40 | ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets') 41 | ->widgets([ 42 | Widgets\AccountWidget::class, 43 | Widgets\FilamentInfoWidget::class, 44 | ]) 45 | ->middleware([ 46 | EncryptCookies::class, 47 | AddQueuedCookiesToResponse::class, 48 | StartSession::class, 49 | AuthenticateSession::class, 50 | ShareErrorsFromSession::class, 51 | VerifyCsrfToken::class, 52 | SubstituteBindings::class, 53 | DisableBladeIconComponents::class, 54 | DispatchServingFilamentEvent::class, 55 | ]) 56 | ->authMiddleware([ 57 | Authenticate::class, 58 | ]); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/Providers/FortifyServiceProvider.php: -------------------------------------------------------------------------------- 1 | string(Fortify::username())).'|'.$request->ip()); 43 | 44 | return Limit::perMinute(5)->by($throttleKey); 45 | }); 46 | 47 | RateLimiter::for('two-factor', fn (Request $request) => Limit::perMinute(5)->by($request->session()->get('login.id'))); 48 | 49 | $this->configureLoginView(); 50 | } 51 | 52 | private function configureLoginView(): void 53 | { 54 | Fortify::loginView(fn () => Inertia::render('Auth/Login', [ 55 | 'availableOauthProviders' => (new ActiveOauthProviderAction())->handle(), 56 | 'canResetPassword' => Route::has('password.request'), 57 | 'status' => session('status'), 58 | ])); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/Providers/TelescopeServiceProvider.php: -------------------------------------------------------------------------------- 1 | hideSensitiveRequestDetails(); 23 | 24 | $isLocal = $this->app->environment('local'); 25 | 26 | Telescope::filter(function (IncomingEntry $entry) use ($isLocal): bool { 27 | if ($isLocal) { 28 | return true; 29 | } 30 | 31 | if ($entry->isReportableException()) { 32 | return true; 33 | } 34 | 35 | if ($entry->isFailedRequest()) { 36 | return true; 37 | } 38 | 39 | if ($entry->isFailedJob()) { 40 | return true; 41 | } 42 | 43 | if ($entry->isScheduledTask()) { 44 | return true; 45 | } 46 | 47 | return $entry->hasMonitoredTag(); 48 | }); 49 | } 50 | 51 | /** 52 | * Register the Telescope gate. 53 | * 54 | * This gate determines who can access Telescope in non-local environments. 55 | */ 56 | protected function gate(): void 57 | { 58 | Gate::define('viewTelescope', fn (User $user): bool => in_array($user->email, [ 59 | // 60 | ])); 61 | } 62 | 63 | /** 64 | * Prevent sensitive request details from being logged by Telescope. 65 | */ 66 | private function hideSensitiveRequestDetails(): void 67 | { 68 | if ($this->app->environment('local')) { 69 | return; 70 | } 71 | 72 | Telescope::hideRequestParameters(['_token']); 73 | 74 | Telescope::hideRequestHeaders([ 75 | 'cookie', 76 | 'x-csrf-token', 77 | 'x-xsrf-token', 78 | ]); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | handleCommand(new ArgvInput); 14 | 15 | exit($status); 16 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | withRouting( 14 | web: __DIR__.'/../routes/web.php', 15 | api: __DIR__.'/../routes/api.php', 16 | health: '/up', 17 | ) 18 | ->withMiddleware(function (Middleware $middleware): void { 19 | $middleware->web(append: [ 20 | HandleInertiaRequests::class, 21 | AddLinkHeadersForPreloadedAssets::class, 22 | ]); 23 | $middleware->validateCsrfTokens(except: [ 24 | 'stripe/*', 25 | 'prism/*', 26 | ]); 27 | $middleware->trustProxies(at: '*'); 28 | }) 29 | ->withExceptions(function (Exceptions $exceptions): void { 30 | Integration::handles($exceptions); // Intgrate Sentry with Application 31 | })->create(); 32 | -------------------------------------------------------------------------------- /bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /bootstrap/providers.php: -------------------------------------------------------------------------------- 1 | [ 7 | [ 8 | 'slug' => 'github', 9 | 'active' => true, 10 | 'icon' => 'mdi:github', // Icons from https://iconify.design/ 11 | ], 12 | [ 13 | 'slug' => 'google', 14 | 'active' => false, 15 | 'icon' => 'mdi:google', // Icons from https://iconify.design/ 16 | ], 17 | [ 18 | 'slug' => 'x', 19 | 'active' => false, 20 | 'icon' => 'ri:twitter-x-line', // Icons from https://iconify.design/ 21 | ], 22 | [ 23 | 'slug' => 'gitlab', 24 | 'active' => true, 25 | 'icon' => 'mdi:gitlab', // Icons from https://iconify.design/ 26 | ], 27 | [ 28 | 'slug' => 'bitbucket', 29 | 'active' => false, 30 | 'icon' => 'mdi:bitbucket', // Icons from https://iconify.design/ 31 | ], 32 | [ 33 | 'slug' => 'discord', 34 | 'active' => false, 35 | 'icon' => 'mdi:discord', // Icons from https://iconify.design/ 36 | ], 37 | ], 38 | ]; 39 | -------------------------------------------------------------------------------- /config/prism.php: -------------------------------------------------------------------------------- 1 | [ 7 | // The middleware that will be applied to the Prism Server routes. 8 | 'middleware' => ['web', 'verified', 'auth'], 9 | 'enabled' => env('PRISM_SERVER_ENABLED', true), 10 | ], 11 | 'providers' => [ 12 | 'openai' => [ 13 | 'url' => env('OPENAI_URL', 'https://api.openai.com/v1'), 14 | 'api_key' => env('OPENAI_API_KEY', ''), 15 | 'organization' => env('OPENAI_ORGANIZATION', null), 16 | ], 17 | 'anthropic' => [ 18 | 'api_key' => env('ANTHROPIC_API_KEY', ''), 19 | 'version' => env('ANTHROPIC_API_VERSION', '2023-06-01'), 20 | ], 21 | 'ollama' => [ 22 | 'url' => env('OLLAMA_URL', 'http://localhost:11434/v1'), 23 | ], 24 | 'mistral' => [ 25 | 'api_key' => env('MISTRAL_API_KEY', ''), 26 | 'url' => env('MISTRAL_URL', 'https://api.mistral.ai/v1'), 27 | ], 28 | 'groq' => [ 29 | 'api_key' => env('GROQ_API_KEY', ''), 30 | 'url' => env('GROQ_URL', 'https://api.groq.com/openai/v1'), 31 | ], 32 | 'xai' => [ 33 | 'api_key' => env('XAI_API_KEY', ''), 34 | 'url' => env('XAI_URL', 'https://api.x.ai/v1'), 35 | ], 36 | 'gemini' => [ 37 | 'api_key' => env('GEMINI_API_KEY', ''), 38 | 'url' => env('GEMINI_URL', 'https://generativelanguage.googleapis.com/v1beta/models'), 39 | ], 40 | ], 41 | ]; 42 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 19 | 'key' => env('AWS_ACCESS_KEY_ID'), 20 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 21 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 22 | ], 23 | 24 | 'resend' => [ 25 | 'key' => env('RESEND_KEY'), 26 | ], 27 | 28 | 'slack' => [ 29 | 'notifications' => [ 30 | 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'), 31 | 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'), 32 | ], 33 | ], 34 | 35 | 'github' => [ 36 | 'client_id' => env('GITHUB_CLIENT_ID'), 37 | 'client_secret' => env('GITHUB_CLIENT_SECRET'), 38 | 'redirect' => env('GITHUB_REDIRECT_URI', '/auth/callback/github'), 39 | ], 40 | 41 | 'google' => [ 42 | 'client_id' => env('GOOGLE_CLIENT_ID'), 43 | 'client_secret' => env('GOOGLE_CLIENT_SECRET'), 44 | 'redirect' => env('GOOGLE_REDIRECT_URI', '/auth/callback/google'), 45 | ], 46 | 47 | 'x' => [ 48 | 'client_id' => env('X_CLIENT_ID'), 49 | 'client_secret' => env('X_CLIENT_SECRET'), 50 | 'redirect' => env('X_REDIRECT_URI', '/auth/callback/x'), 51 | ], 52 | 53 | 'gitlab' => [ 54 | 'client_id' => env('GITLAB_CLIENT_ID'), 55 | 'client_secret' => env('GITLAB_CLIENT_SECRET'), 56 | 'redirect' => env('GITLAB_REDIRECT_URI', '/auth/callback/gitlab'), 57 | ], 58 | 59 | ]; 60 | -------------------------------------------------------------------------------- /config/sitemap.php: -------------------------------------------------------------------------------- 1 | [ 17 | 18 | /* 19 | * Whether or not cookies are used in a request. 20 | */ 21 | RequestOptions::COOKIES => true, 22 | 23 | /* 24 | * The number of seconds to wait while trying to connect to a server. 25 | * Use 0 to wait indefinitely. 26 | */ 27 | RequestOptions::CONNECT_TIMEOUT => 10, 28 | 29 | /* 30 | * The timeout of the request in seconds. Use 0 to wait indefinitely. 31 | */ 32 | RequestOptions::TIMEOUT => 10, 33 | 34 | /* 35 | * Describes the redirect behavior of a request. 36 | */ 37 | RequestOptions::ALLOW_REDIRECTS => false, 38 | ], 39 | 40 | /* 41 | * The sitemap generator can execute JavaScript on each page so it will 42 | * discover links that are generated by your JS scripts. This feature 43 | * is powered by headless Chrome. 44 | */ 45 | 'execute_javascript' => true, 46 | 47 | /* 48 | * The package will make an educated guess as to where Google Chrome is installed. 49 | * You can also manually pass its location here. 50 | */ 51 | 'chrome_binary_path' => null, 52 | 53 | /* 54 | * The sitemap generator uses a CrawlProfile implementation to determine 55 | * which urls should be crawled for the sitemap. 56 | */ 57 | 'crawl_profile' => Profile::class, 58 | 59 | ]; 60 | -------------------------------------------------------------------------------- /config/subscriptions.php: -------------------------------------------------------------------------------- 1 | [ 7 | [ 8 | 'plan' => 'Larasonic Basic 🚀', 9 | 'description' => 'The best plan for individuals who wants to explore demo subscription.', 10 | 'price_id' => 'price_1QTQldHNOPjoTPj1vVHORPs9', 11 | 'price' => 5, 12 | 'features' => [ 13 | 'Example Basic Feature 1', 14 | 'Example Basic Feature 2', 15 | 'Example Basic Feature 3', 16 | 'Example Basic Feature 4', 17 | 'Example Basic Feature 5', 18 | 'Example Basic Feature 6', 19 | ], 20 | ], 21 | [ 22 | 'plan' => 'Larasonic Pro ✨', 23 | 'description' => 'The best plan for individuals who wants to use AI features.', 24 | 'price_id' => 'price_1QSEgRHNOPjoTPj15AjCR23u', 25 | 'price' => 15, 26 | 'features' => [ 27 | 'All Basic Features', 28 | 'Example Pro Feature 2', 29 | 'Example Pro Feature 3', 30 | 'Example Pro Feature 4', 31 | 'Example Pro Feature 5', 32 | 'Example Pro Feature 6', 33 | ], 34 | ], 35 | ], 36 | ]; 37 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite* 2 | -------------------------------------------------------------------------------- /database/factories/LoginLinkFactory.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | final class LoginLinkFactory extends Factory 15 | { 16 | /** 17 | * Define the model's default state. 18 | * 19 | * @return array, mixed> 20 | */ 21 | public function definition(): array 22 | { 23 | return [ 24 | 'user_id' => User::factory()->create()->id, 25 | 'token' => fake()->uuid(), 26 | 'expires_at' => fake()->dateTimeBetween('5 minutes', '15 minutes'), 27 | 'used_at' => null, 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /database/factories/TeamFactory.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | final class TeamFactory extends Factory 15 | { 16 | /** 17 | * Define the model's default state. 18 | * 19 | * @return array, mixed> 20 | */ 21 | public function definition(): array 22 | { 23 | return [ 24 | 'name' => $this->faker->unique()->company(), 25 | 'user_id' => User::factory(), 26 | 'personal_team' => true, 27 | ]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | final class UserFactory extends Factory 18 | { 19 | /** 20 | * The current password being used by the factory. 21 | */ 22 | private static ?string $password = null; 23 | 24 | /** 25 | * Define the model's default state. 26 | * 27 | * @return array, mixed> 28 | */ 29 | public function definition(): array 30 | { 31 | return [ 32 | 'name' => fake()->name(), 33 | 'email' => fake()->unique()->safeEmail(), 34 | 'email_verified_at' => now(), 35 | 'password' => self::$password ??= Hash::make('password'), 36 | 'two_factor_secret' => null, 37 | 'two_factor_recovery_codes' => null, 38 | 'remember_token' => Str::random(10), 39 | 'profile_photo_path' => null, 40 | 'current_team_id' => null, 41 | 'stripe_id' => null, 42 | 'pm_type' => null, 43 | 'pm_last_four' => null, 44 | 'trial_ends_at' => null, 45 | ]; 46 | } 47 | 48 | /** 49 | * Indicate that the model's email address should be unverified. 50 | */ 51 | public function unverified(): self 52 | { 53 | return $this->state(fn (array $attributes): array => [ 54 | 'email_verified_at' => null, 55 | ]); 56 | } 57 | 58 | /** 59 | * Indicate that the user should have a personal team. 60 | */ 61 | public function withPersonalTeam(?callable $callback = null): self 62 | { 63 | if (! Features::hasTeamFeatures()) { 64 | return $this->state([]); 65 | } 66 | 67 | return $this->has( 68 | Team::factory() 69 | /* @phpstan-ignore-next-line */ 70 | ->state(fn (array $attributes, User $user): array => [ 71 | 'name' => $user->name."'s Team", 72 | 'user_id' => $user->id, 73 | 'personal_team' => true, 74 | ]) 75 | ->when(is_callable($callback), $callback), 76 | 'ownedTeams' 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('name'); 19 | $table->string('email')->unique(); 20 | $table->timestamp('email_verified_at')->nullable(); 21 | $table->string('password'); 22 | $table->rememberToken(); 23 | $table->foreignId('current_team_id')->nullable(); 24 | $table->string('profile_photo_path', 2048)->nullable(); 25 | $table->timestamps(); 26 | $table->softDeletes(); 27 | }); 28 | 29 | Schema::create('password_reset_tokens', function (Blueprint $table): void { 30 | $table->string('email')->primary(); 31 | $table->string('token'); 32 | $table->timestamp('created_at')->nullable(); 33 | }); 34 | 35 | Schema::create('sessions', function (Blueprint $table): void { 36 | $table->string('id')->primary(); 37 | $table->foreignId('user_id')->nullable()->index(); 38 | $table->string('ip_address', 45)->nullable(); 39 | $table->text('user_agent')->nullable(); 40 | $table->longText('payload'); 41 | $table->integer('last_activity')->index(); 42 | }); 43 | } 44 | 45 | /** 46 | * Reverse the migrations. 47 | */ 48 | public function down(): void 49 | { 50 | Schema::dropIfExists('users'); 51 | Schema::dropIfExists('password_reset_tokens'); 52 | Schema::dropIfExists('sessions'); 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000001_create_cache_table.php: -------------------------------------------------------------------------------- 1 | string('key')->primary(); 18 | $table->mediumText('value'); 19 | $table->integer('expiration'); 20 | }); 21 | 22 | Schema::create('cache_locks', function (Blueprint $table): void { 23 | $table->string('key')->primary(); 24 | $table->string('owner'); 25 | $table->integer('expiration'); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | */ 32 | public function down(): void 33 | { 34 | Schema::dropIfExists('cache'); 35 | Schema::dropIfExists('cache_locks'); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000002_create_jobs_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('queue')->index(); 19 | $table->longText('payload'); 20 | $table->unsignedTinyInteger('attempts'); 21 | $table->unsignedInteger('reserved_at')->nullable(); 22 | $table->unsignedInteger('available_at'); 23 | $table->unsignedInteger('created_at'); 24 | }); 25 | 26 | Schema::create('job_batches', function (Blueprint $table): void { 27 | $table->string('id')->primary(); 28 | $table->string('name'); 29 | $table->integer('total_jobs'); 30 | $table->integer('pending_jobs'); 31 | $table->integer('failed_jobs'); 32 | $table->longText('failed_job_ids'); 33 | $table->mediumText('options')->nullable(); 34 | $table->integer('cancelled_at')->nullable(); 35 | $table->integer('created_at'); 36 | $table->integer('finished_at')->nullable(); 37 | }); 38 | 39 | Schema::create('failed_jobs', function (Blueprint $table): void { 40 | $table->id(); 41 | $table->string('uuid')->unique(); 42 | $table->text('connection'); 43 | $table->text('queue'); 44 | $table->longText('payload'); 45 | $table->longText('exception'); 46 | $table->timestamp('failed_at')->useCurrent(); 47 | }); 48 | } 49 | 50 | /** 51 | * Reverse the migrations. 52 | */ 53 | public function down(): void 54 | { 55 | Schema::dropIfExists('jobs'); 56 | Schema::dropIfExists('job_batches'); 57 | Schema::dropIfExists('failed_jobs'); 58 | } 59 | }; 60 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000003_add_two_factor_columns_to_users_table.php: -------------------------------------------------------------------------------- 1 | text('two_factor_secret') 19 | ->after('password') 20 | ->nullable(); 21 | 22 | $table->text('two_factor_recovery_codes') 23 | ->after('two_factor_secret') 24 | ->nullable(); 25 | 26 | if (Fortify::confirmsTwoFactorAuthentication()) { 27 | $table->timestamp('two_factor_confirmed_at') 28 | ->after('two_factor_recovery_codes') 29 | ->nullable(); 30 | } 31 | }); 32 | } 33 | 34 | /** 35 | * Reverse the migrations. 36 | */ 37 | public function down(): void 38 | { 39 | Schema::table('users', function (Blueprint $table): void { 40 | $table->dropColumn(array_merge([ 41 | 'two_factor_secret', 42 | 'two_factor_recovery_codes', 43 | ], Fortify::confirmsTwoFactorAuthentication() ? [ 44 | 'two_factor_confirmed_at', 45 | ] : [])); 46 | }); 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000004_create_personal_access_tokens_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->morphs('tokenable'); 19 | $table->string('name'); 20 | $table->string('token', 64)->unique(); 21 | $table->text('abilities')->nullable(); 22 | $table->timestamp('last_used_at')->nullable(); 23 | $table->timestamp('expires_at')->nullable(); 24 | $table->timestamps(); 25 | }); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | */ 31 | public function down(): void 32 | { 33 | Schema::dropIfExists('personal_access_tokens'); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000005_create_teams_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->foreignId('user_id')->index(); 19 | $table->string('name'); 20 | $table->boolean('personal_team'); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | */ 28 | public function down(): void 29 | { 30 | Schema::dropIfExists('teams'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000006_create_team_user_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->foreignId('team_id'); 19 | $table->foreignId('user_id'); 20 | $table->string('role')->nullable(); 21 | $table->timestamps(); 22 | 23 | $table->unique(['team_id', 'user_id']); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | */ 30 | public function down(): void 31 | { 32 | Schema::dropIfExists('team_user'); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000007_create_team_invitations_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->foreignId('team_id')->constrained()->cascadeOnDelete(); 19 | $table->string('email'); 20 | $table->string('role')->nullable(); 21 | $table->timestamps(); 22 | 23 | $table->unique(['team_id', 'email']); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | */ 30 | public function down(): void 31 | { 32 | Schema::dropIfExists('team_invitations'); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000008_create_oauth_connections_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->foreignId('user_id')->constrained('users')->cascadeOnDelete(); 19 | $table->string('provider'); 20 | $table->string('provider_id'); 21 | $table->json('data')->nullable(); 22 | $table->string('token')->nullable(); 23 | $table->string('refresh_token')->nullable(); 24 | $table->timestamp('expires_at')->nullable(); 25 | $table->timestamps(); 26 | 27 | $table->unique(['user_id', 'provider']); 28 | }); 29 | } 30 | 31 | /** 32 | * Reverse the migrations. 33 | */ 34 | public function down(): void 35 | { 36 | Schema::dropIfExists('oauth_connections'); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000009_create_telescope_entries_table.php: -------------------------------------------------------------------------------- 1 | getConnection()); 27 | 28 | $schema->create('telescope_entries', function (Blueprint $table): void { 29 | $table->bigIncrements('sequence'); 30 | $table->uuid('uuid'); 31 | $table->uuid('batch_id'); 32 | $table->string('family_hash')->nullable(); 33 | $table->boolean('should_display_on_index')->default(true); 34 | $table->string('type', 20); 35 | $table->longText('content'); 36 | $table->dateTime('created_at')->nullable(); 37 | 38 | $table->unique('uuid'); 39 | $table->index('batch_id'); 40 | $table->index('family_hash'); 41 | $table->index('created_at'); 42 | $table->index(['type', 'should_display_on_index']); 43 | }); 44 | 45 | $schema->create('telescope_entries_tags', function (Blueprint $table): void { 46 | $table->uuid('entry_uuid'); 47 | $table->string('tag'); 48 | 49 | $table->primary(['entry_uuid', 'tag']); 50 | $table->index('tag'); 51 | 52 | $table->foreign('entry_uuid') 53 | ->references('uuid') 54 | ->on('telescope_entries') 55 | ->onDelete('cascade'); 56 | }); 57 | 58 | $schema->create('telescope_monitoring', function (Blueprint $table): void { 59 | $table->string('tag')->primary(); 60 | }); 61 | } 62 | } 63 | 64 | /** 65 | * Reverse the migrations. 66 | */ 67 | public function down(): void 68 | { 69 | $schema = Schema::connection($this->getConnection()); 70 | 71 | $schema->dropIfExists('telescope_entries_tags'); 72 | $schema->dropIfExists('telescope_entries'); 73 | $schema->dropIfExists('telescope_monitoring'); 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000010_create_customer_columns.php: -------------------------------------------------------------------------------- 1 | string('stripe_id')->nullable()->index(); 18 | $table->string('pm_type')->nullable(); 19 | $table->string('pm_last_four', 4)->nullable(); 20 | $table->timestamp('trial_ends_at')->nullable(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | */ 27 | public function down(): void 28 | { 29 | Schema::table('users', function (Blueprint $table): void { 30 | $table->dropIndex([ 31 | 'stripe_id', 32 | ]); 33 | 34 | $table->dropColumn([ 35 | 'stripe_id', 36 | 'pm_type', 37 | 'pm_last_four', 38 | 'trial_ends_at', 39 | ]); 40 | }); 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000011_create_subscriptions_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->foreignId('user_id'); 19 | $table->string('type'); 20 | $table->string('stripe_id')->unique(); 21 | $table->string('stripe_status'); 22 | $table->string('stripe_price')->nullable(); 23 | $table->integer('quantity')->nullable(); 24 | $table->timestamp('trial_ends_at')->nullable(); 25 | $table->timestamp('ends_at')->nullable(); 26 | 27 | $table->timestamps(); 28 | 29 | $table->index(['user_id', 'stripe_status']); 30 | }); 31 | } 32 | 33 | /** 34 | * Reverse the migrations. 35 | */ 36 | public function down(): void 37 | { 38 | Schema::dropIfExists('subscriptions'); 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000012_create_subscription_items_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->foreignId('subscription_id'); 19 | $table->string('stripe_id')->unique(); 20 | $table->string('stripe_product'); 21 | $table->string('stripe_price'); 22 | $table->integer('quantity')->nullable(); 23 | $table->timestamps(); 24 | 25 | $table->index(['subscription_id', 'stripe_price']); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | */ 32 | public function down(): void 33 | { 34 | Schema::dropIfExists('subscription_items'); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000013_create_login_links_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->foreignId('user_id')->constrained()->cascadeOnDelete(); 19 | $table->string('token', 64)->unique(); 20 | $table->timestamp('expires_at'); 21 | $table->timestamp('used_at')->nullable(); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | */ 29 | public function down(): void 30 | { 31 | Schema::dropIfExists('login_links'); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /database/seeders/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | withPersonalTeam()->create([ 20 | 'name' => 'Test User', 21 | 'email' => 'test@example.com', 22 | 'password' => Hash::make('password'), 23 | ]); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | laravel.test: 3 | build: 4 | context: './docker/8.3' 5 | dockerfile: Dockerfile 6 | args: 7 | WWWGROUP: '${WWWGROUP}' 8 | image: 'sail-8.3/app' 9 | extra_hosts: 10 | - 'host.docker.internal:host-gateway' 11 | ports: 12 | - '${APP_PORT:-80}:${APP_PORT:-80}' 13 | - '${VITE_PORT:-5173}:${VITE_PORT:-5173}' 14 | environment: 15 | WWWUSER: '${WWWUSER}' 16 | LARAVEL_SAIL: 1 17 | XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}' 18 | XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}' 19 | IGNITION_LOCAL_SITES_PATH: '${PWD}' 20 | SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --watch --poll --server=frankenphp --admin-port=2019 --port='${APP_PORT:-80}'" 21 | XDG_CONFIG_HOME: /var/www/html/config 22 | XDG_DATA_HOME: /var/www/html/data 23 | volumes: 24 | - '.:/var/www/html' 25 | networks: 26 | - sail 27 | depends_on: 28 | - mailpit 29 | mailpit: 30 | image: 'axllent/mailpit:latest' 31 | ports: 32 | - '${FORWARD_MAILPIT_PORT:-1025}:1025' 33 | - '${FORWARD_MAILPIT_DASHBOARD_PORT:-8025}:8025' 34 | networks: 35 | - sail 36 | networks: 37 | sail: 38 | driver: bridge 39 | -------------------------------------------------------------------------------- /docker/8.3/php.ini: -------------------------------------------------------------------------------- 1 | [PHP] 2 | post_max_size = 100M 3 | upload_max_filesize = 100M 4 | variables_order = EGPCS 5 | pcov.directory = . 6 | -------------------------------------------------------------------------------- /docker/8.3/start-container: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ "$SUPERVISOR_PHP_USER" != "root" ] && [ "$SUPERVISOR_PHP_USER" != "sail" ]; then 4 | echo "You should set SUPERVISOR_PHP_USER to either 'sail' or 'root'." 5 | exit 1 6 | fi 7 | 8 | if [ ! -z "$WWWUSER" ]; then 9 | usermod -u $WWWUSER sail 10 | fi 11 | 12 | if [ ! -d /.composer ]; then 13 | mkdir /.composer 14 | fi 15 | 16 | chmod -R ugo+rw /.composer 17 | 18 | if [ $# -gt 0 ]; then 19 | if [ "$SUPERVISOR_PHP_USER" = "root" ]; then 20 | exec "$@" 21 | else 22 | exec gosu $WWWUSER "$@" 23 | fi 24 | else 25 | exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf 26 | fi 27 | -------------------------------------------------------------------------------- /docker/8.3/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | user=root 4 | logfile=/var/log/supervisor/supervisord.log 5 | pidfile=/var/run/supervisord.pid 6 | 7 | [program:php] 8 | command=%(ENV_SUPERVISOR_PHP_COMMAND)s 9 | user=%(ENV_SUPERVISOR_PHP_USER)s 10 | environment=LARAVEL_SAIL="1" 11 | stdout_logfile=/dev/stdout 12 | stdout_logfile_maxbytes=0 13 | stderr_logfile=/dev/stderr 14 | stderr_logfile_maxbytes=0 15 | -------------------------------------------------------------------------------- /docker/pgsql/create-testing-database.sql: -------------------------------------------------------------------------------- 1 | SELECT 'CREATE DATABASE testing' 2 | WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'testing')\gexec 3 | -------------------------------------------------------------------------------- /docker/php/production.ini: -------------------------------------------------------------------------------- 1 | ; Production PHP configuration 2 | memory_limit = 256M 3 | max_execution_time = 60 4 | opcache.enable=0 5 | opcache.enable_cli=0 6 | realpath_cache_size=4096K 7 | realpath_cache_ttl=600 8 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import antfu from '@antfu/eslint-config' 2 | 3 | export default antfu({ 4 | react: true, 5 | typescript: false, 6 | formatters: { 7 | css: true, 8 | html: true, 9 | markdown: 'prettier', 10 | }, 11 | rules: { 12 | 'react-refresh/only-export-components': 'off', 13 | }, 14 | ignores: ['storage/**/*', '**/*.{yaml,yml,php}', 'resources/js/Components/shadcn/**/*', 'public/**/*'], 15 | }) 16 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@/*": ["resources/js/*"] 6 | } 7 | }, 8 | "exclude": ["node_modules", "public"] 9 | } 10 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | includes: 2 | - vendor/larastan/larastan/extension.neon 3 | - vendor/nesbot/carbon/extension.neon 4 | 5 | parameters: 6 | paths: 7 | - app 8 | - bootstrap 9 | - database/factories 10 | - routes 11 | - config 12 | level: max 13 | checkOctaneCompatibility: true 14 | checkModelProperties: true 15 | excludePaths: 16 | - '**/ide-helper.php' 17 | - '**/ide_helper_models.php' 18 | - '**/phpstorm.meta.php' 19 | # ignoreErrors: 20 | # - '#PHPDoc tag @var#' 21 | # 22 | # excludePaths: 23 | # - ./*/*/FileToBeExcluded.php 24 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | tests/Unit 10 | 11 | 12 | tests/Feature 13 | 14 | 15 | 16 | 17 | app 18 | 19 | 20 | app/Providers 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | '@tailwindcss/postcss': {}, 4 | }, 5 | } 6 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews -Indexes 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Handle Authorization Header 9 | RewriteCond %{HTTP:Authorization} . 10 | RewriteRule .* - [E=HTTP_A 11 | 12 | # Handle X-XSRF-Token Header 13 | RewriteCond %{HTTP:x-xsrf-token} . 14 | RewriteRule .* - [E=HTTP_X_XSRF_TOKEN:%{HTTP:X-XSRF-Token}] 15 | 16 | # Redirect Trailing Slashes If Not A Folder... 17 | RewriteCond %{REQUEST_FILENAME} !-d 18 | RewriteCond %{REQUEST_URI} (.+)/$ 19 | RewriteRule ^ %1 [L,R=301] 20 | 21 | # Send Requests To Front Controller... 22 | RewriteCond %{REQUEST_FILENAME} !-d 23 | RewriteCond %{REQUEST_FILENAME} !-f 24 | RewriteRule ^ index.php [L] 25 | 26 | -------------------------------------------------------------------------------- /public/.user.ini: -------------------------------------------------------------------------------- 1 | [opcache] 2 | ; Determines if Zend OPCache is enabled 3 | opcache.enable = 1 4 | 5 | ; Determines if Zend OPCache is enabled for the CLI version of PHP 6 | ;opcache.enable_cli=0 7 | 8 | ; The OPcache shared memory storage size. 9 | opcache.memory_consumption = 256 10 | 11 | ; The amount of memory for interned strings in Mbytes. 12 | opcache.interned_strings_buffer = 64 13 | 14 | ; The maximum number of keys (scripts) in the OPcache hash table. 15 | ; Only numbers between 200 and 1000000 are allowed. 16 | opcache.max_accelerated_files = 40000 17 | 18 | ; The maximum percentage of "wasted" memory until a restart is scheduled. 19 | ;opcache.max_wasted_percentage=5 20 | 21 | ; When this directive is enabled, the OPcache appends the current working 22 | ; directory to the script key, thus eliminating possible collisions between 23 | ; files with the same name (basename). Disabling the directive improves 24 | ; performance, but may break existing applications. 25 | ;opcache.use_cwd=1 26 | 27 | ; When disabled, you must reset the OPcache manually or restart the 28 | ; webserver for changes to the filesystem to take effect. 29 | opcache.validate_timestamps = 1 30 | 31 | ; How often (in seconds) to check file timestamps for changes to the shared 32 | ; memory storage allocation. ("1" means validate once per second, but only 33 | ; once per request. "0" means always validate) 34 | opcache.revalidate_freq=60 35 | 36 | ; If disabled, all PHPDoc comments are dropped from the code to reduce the 37 | ; size of the optimized code. 38 | opcache.save_comments = 0 39 | -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shipfastlabs/larasonic-react/d6d77aa64d5b98b989fe98e170ea8bcd38193df6/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shipfastlabs/larasonic-react/d6d77aa64d5b98b989fe98e170ea8bcd38193df6/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shipfastlabs/larasonic-react/d6d77aa64d5b98b989fe98e170ea8bcd38193df6/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon copy.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shipfastlabs/larasonic-react/d6d77aa64d5b98b989fe98e170ea8bcd38193df6/public/favicon copy.ico -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shipfastlabs/larasonic-react/d6d77aa64d5b98b989fe98e170ea8bcd38193df6/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shipfastlabs/larasonic-react/d6d77aa64d5b98b989fe98e170ea8bcd38193df6/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shipfastlabs/larasonic-react/d6d77aa64d5b98b989fe98e170ea8bcd38193df6/public/favicon.ico -------------------------------------------------------------------------------- /public/images/dashboard-dark.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shipfastlabs/larasonic-react/d6d77aa64d5b98b989fe98e170ea8bcd38193df6/public/images/dashboard-dark.webp -------------------------------------------------------------------------------- /public/images/dashboard-light.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shipfastlabs/larasonic-react/d6d77aa64d5b98b989fe98e170ea8bcd38193df6/public/images/dashboard-light.webp -------------------------------------------------------------------------------- /public/images/og.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shipfastlabs/larasonic-react/d6d77aa64d5b98b989fe98e170ea8bcd38193df6/public/images/og.webp -------------------------------------------------------------------------------- /public/images/rocket-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shipfastlabs/larasonic-react/d6d77aa64d5b98b989fe98e170ea8bcd38193df6/public/images/rocket-dark.png -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | handleRequest(Request::capture()); 20 | -------------------------------------------------------------------------------- /public/js/filament/forms/components/key-value.js: -------------------------------------------------------------------------------- 1 | function r({state:o}){return{state:o,rows:[],shouldUpdateRows:!0,init:function(){this.updateRows(),this.rows.length<=0?this.rows.push({key:"",value:""}):this.updateState(),this.$watch("state",(t,e)=>{let s=i=>i===null?0:Array.isArray(i)?i.length:typeof i!="object"?0:Object.keys(i).length;s(t)===0&&s(e)===0||this.updateRows()})},addRow:function(){this.rows.push({key:"",value:""}),this.updateState()},deleteRow:function(t){this.rows.splice(t,1),this.rows.length<=0&&this.addRow(),this.updateState()},reorderRows:function(t){let e=Alpine.raw(this.rows);this.rows=[];let s=e.splice(t.oldIndex,1)[0];e.splice(t.newIndex,0,s),this.$nextTick(()=>{this.rows=e,this.updateState()})},updateRows:function(){if(!this.shouldUpdateRows){this.shouldUpdateRows=!0;return}let t=[];for(let[e,s]of Object.entries(this.state??{}))t.push({key:e,value:s});this.rows=t},updateState:function(){let t={};this.rows.forEach(e=>{e.key===""||e.key===null||(t[e.key]=e.value)}),this.shouldUpdateRows=!1,this.state=t}}}export{r as default}; 2 | -------------------------------------------------------------------------------- /public/js/filament/forms/components/tags-input.js: -------------------------------------------------------------------------------- 1 | function i({state:a,splitKeys:n}){return{newTag:"",state:a,createTag:function(){if(this.newTag=this.newTag.trim(),this.newTag!==""){if(this.state.includes(this.newTag)){this.newTag="";return}this.state.push(this.newTag),this.newTag=""}},deleteTag:function(t){this.state=this.state.filter(e=>e!==t)},reorderTags:function(t){let e=this.state.splice(t.oldIndex,1)[0];this.state.splice(t.newIndex,0,e),this.state=[...this.state]},input:{"x-on:blur":"createTag()","x-model":"newTag","x-on:keydown"(t){["Enter",...n].includes(t.key)&&(t.preventDefault(),t.stopPropagation(),this.createTag())},"x-on:paste"(){this.$nextTick(()=>{if(n.length===0){this.createTag();return}let t=n.map(e=>e.replace(/[/\-\\^$*+?.()|[\]{}]/g,"\\$&")).join("|");this.newTag.split(new RegExp(t,"g")).forEach(e=>{this.newTag=e,this.createTag()})})}}}}export{i as default}; 2 | -------------------------------------------------------------------------------- /public/js/filament/forms/components/textarea.js: -------------------------------------------------------------------------------- 1 | function r({initialHeight:t,shouldAutosize:i,state:s}){return{state:s,wrapperEl:null,init:function(){this.wrapperEl=this.$el.parentNode,this.setInitialHeight(),i?this.$watch("state",()=>{this.resize()}):this.setUpResizeObserver()},setInitialHeight:function(){this.$el.scrollHeight<=0||(this.wrapperEl.style.height=t+"rem")},resize:function(){if(this.setInitialHeight(),this.$el.scrollHeight<=0)return;let e=this.$el.scrollHeight+"px";this.wrapperEl.style.height!==e&&(this.wrapperEl.style.height=e)},setUpResizeObserver:function(){new ResizeObserver(()=>{this.wrapperEl.style.height=this.$el.style.height}).observe(this.$el)}}}export{r as default}; 2 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /public/vendor/telescope/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shipfastlabs/larasonic-react/d6d77aa64d5b98b989fe98e170ea8bcd38193df6/public/vendor/telescope/favicon.ico -------------------------------------------------------------------------------- /public/vendor/telescope/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/app.js": "/app.js?id=a04a99f77a55ffcecde23cd7304b481b", 3 | "/app-dark.css": "/app-dark.css?id=1ea407db56c5163ae29311f1f38eb7b9", 4 | "/app.css": "/app.css?id=de4c978567bfd90b38d186937dee5ccf" 5 | } 6 | -------------------------------------------------------------------------------- /rector.php: -------------------------------------------------------------------------------- 1 | withPaths([ 12 | __DIR__.'/app', 13 | __DIR__.'/bootstrap/app.php', 14 | __DIR__.'/config', 15 | __DIR__.'/database', 16 | __DIR__.'/public', 17 | __DIR__.'/tests', 18 | ]) 19 | ->withSkip([ 20 | AddOverrideAttributeToOverriddenMethodsRector::class, 21 | EncapsedStringsToSprintfRector::class, 22 | ]) 23 | ->withPreparedSets( 24 | deadCode: true, 25 | codeQuality: true, 26 | typeDeclarations: true, 27 | privatization: true, 28 | earlyReturn: true, 29 | strictBooleans: true, 30 | codingStyle: true, 31 | ) 32 | ->withPhpSets(php83: true) 33 | ->withSets([ 34 | LaravelSetList::LARAVEL_CODE_QUALITY, 35 | LaravelSetList::LARAVEL_ELOQUENT_MAGIC_METHOD_TO_QUERY_BUILDER, 36 | LaravelSetList::LARAVEL_IF_HELPERS, 37 | LaravelSetList::LARAVEL_CONTAINER_STRING_TO_FULLY_QUALIFIED_NAME, 38 | LaravelSetList::LARAVEL_COLLECTION, 39 | ]); 40 | -------------------------------------------------------------------------------- /resources/css/fonts.css: -------------------------------------------------------------------------------- 1 | /* latin-ext */ 2 | @font-face { 3 | font-family: 'Geist'; 4 | font-style: normal; 5 | font-weight: 100 900; 6 | font-display: swap; 7 | src: url(https://fonts.gstatic.com/s/geist/v1/gyByhwUxId8gMEwSGFWNOITddY4.woff2) format('woff2'); 8 | unicode-range: 9 | U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, 10 | U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; 11 | } 12 | 13 | /* latin */ 14 | @font-face { 15 | font-family: 'Geist'; 16 | font-style: normal; 17 | font-weight: 100 900; 18 | font-display: swap; 19 | src: url(https://fonts.gstatic.com/s/geist/v1/gyByhwUxId8gMEwcGFWNOITd.woff2) format('woff2'); 20 | unicode-range: 21 | U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, 22 | U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 23 | } 24 | -------------------------------------------------------------------------------- /resources/js/Components/ActionSection.jsx: -------------------------------------------------------------------------------- 1 | import { memo } from 'react' 2 | 3 | export default memo(({ 4 | title, 5 | description, 6 | aside, 7 | content, 8 | className = '', 9 | }) => { 10 | return ( 11 |
12 |
13 |
14 |

15 | {title} 16 |

17 | 18 |

19 | {description} 20 |

21 |
22 | 23 |
24 | {aside} 25 |
26 |
27 | 28 |
29 |
30 | {content} 31 |
32 |
33 |
34 | ) 35 | }) 36 | -------------------------------------------------------------------------------- /resources/js/Components/ApplicationLogo.jsx: -------------------------------------------------------------------------------- 1 | import { Icon } from '@iconify/react' 2 | import { memo } from 'react' 3 | 4 | export default memo(() => { 5 | return ( 6 |