├── .nvmrc ├── public ├── favicon.ico ├── robots.txt ├── js │ └── filament │ │ ├── schemas │ │ ├── components │ │ │ ├── actions.js │ │ │ ├── tabs.js │ │ │ └── wizard.js │ │ └── schemas.js │ │ ├── forms │ │ └── components │ │ │ ├── textarea.js │ │ │ ├── tags-input.js │ │ │ ├── key-value.js │ │ │ ├── checkbox-list.js │ │ │ └── color-picker.js │ │ ├── tables │ │ ├── components │ │ │ ├── columns │ │ │ │ ├── checkbox.js │ │ │ │ ├── toggle.js │ │ │ │ └── text-input.js │ │ │ └── table.js │ │ └── tables.js │ │ ├── actions │ │ └── actions.js │ │ ├── notifications │ │ └── notifications.js │ │ └── filament │ │ └── app.js ├── fonts │ └── filament │ │ └── filament │ │ └── inter │ │ ├── inter-greek-wght-normal-AXVTPQD5.woff2 │ │ ├── inter-greek-wght-normal-IRE366VL.woff2 │ │ ├── inter-greek-wght-normal-N43DBLU2.woff2 │ │ ├── inter-latin-wght-normal-NRMW37G5.woff2 │ │ ├── inter-latin-wght-normal-O25CN4JL.woff2 │ │ ├── inter-latin-wght-normal-OPIJAQLS.woff2 │ │ ├── inter-cyrillic-wght-normal-EWLSKVKN.woff2 │ │ ├── inter-cyrillic-wght-normal-JEOLYBOO.woff2 │ │ ├── inter-cyrillic-wght-normal-R5CMSONN.woff2 │ │ ├── inter-greek-ext-wght-normal-7GGTF7EK.woff2 │ │ ├── inter-greek-ext-wght-normal-EOVOK2B5.woff2 │ │ ├── inter-greek-ext-wght-normal-ZEVLMORV.woff2 │ │ ├── inter-latin-ext-wght-normal-5SRY4DMZ.woff2 │ │ ├── inter-latin-ext-wght-normal-GZCIV3NH.woff2 │ │ ├── inter-latin-ext-wght-normal-HA22NDSG.woff2 │ │ ├── inter-vietnamese-wght-normal-CE5GGD3W.woff2 │ │ ├── inter-vietnamese-wght-normal-TWG5UU7E.woff2 │ │ ├── inter-cyrillic-ext-wght-normal-ASVAGXXE.woff2 │ │ ├── inter-cyrillic-ext-wght-normal-IYF56FF6.woff2 │ │ ├── inter-cyrillic-ext-wght-normal-XKHXBTUO.woff2 │ │ └── index.css ├── index.php ├── .htaccess └── css │ └── filament │ └── support │ └── support.css ├── .tool-versions ├── database ├── .gitignore ├── seeders │ └── DatabaseSeeder.php ├── migrations │ ├── 0001_01_01_000001_create_cache_table.php │ ├── 2025_07_20_033509_create_media_table.php │ ├── 0001_01_01_000000_create_users_table.php │ ├── 0001_01_01_000002_create_jobs_table.php │ └── 2025_07_20_024239_create_telescope_entries_table.php └── factories │ └── UserFactory.php ├── bootstrap ├── cache │ └── .gitignore ├── providers.php └── app.php ├── resources ├── js │ ├── app.js │ └── bootstrap.js ├── views │ ├── components │ │ ├── post.blade.php │ │ ├── sidebar.blade.php │ │ ├── navbar.blade.php │ │ ├── feed.blade.php │ │ └── layouts │ │ │ └── guest.blade.php │ └── welcome.blade.php ├── css │ ├── filament │ │ └── admin │ │ │ └── theme.css │ └── app.css └── README.md ├── storage ├── logs │ └── .gitignore ├── app │ ├── private │ │ └── .gitignore │ ├── public │ │ └── .gitignore │ └── .gitignore ├── debugbar │ └── .gitignore └── framework │ ├── testing │ └── .gitignore │ ├── views │ └── .gitignore │ ├── cache │ ├── data │ │ └── .gitignore │ └── .gitignore │ ├── sessions │ └── .gitignore │ └── .gitignore ├── .husky └── pre-commit ├── .ncurc.json ├── .prettierignore ├── DEVELOPMENT.md ├── routes ├── web.php └── console.php ├── tests ├── Unit │ └── ExampleTest.php ├── TestCase.php ├── Feature │ └── ExampleTest.php ├── Browser │ ├── HomeTest.php │ └── Filament │ │ └── Admin │ │ └── Auth │ │ └── LoginTest.php ├── Arch │ └── PresetTest.php └── Pest.php ├── app ├── Http │ └── Controllers │ │ └── Controller.php ├── Filament │ ├── README.md │ └── Shared │ │ └── Pages │ │ └── LoginPage.php ├── Models │ └── User.php └── Providers │ ├── TelescopeServiceProvider.php │ ├── Filament │ └── AdminPanelProvider.php │ └── AppServiceProvider.php ├── phpstan.neon ├── .prettierrc ├── .gitattributes ├── .editorconfig ├── vite.config.js ├── docker ├── redis.Dockerfile ├── mailpit.Dockerfile └── postgres.Dockerfile ├── artisan ├── .gitignore ├── LICENSE ├── config ├── services.php ├── filesystems.php ├── cache.php ├── mail.php ├── queue.php ├── auth.php ├── app.php ├── logging.php ├── database.php ├── telescope.php ├── session.php └── debugbar.php ├── package.json ├── .env.testing ├── .env.docker ├── .env.example ├── phpunit.xml ├── docker-compose.env.yml ├── Makefile ├── pint.json ├── Taskfile.yml ├── rector.php ├── composer.json └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 22.19.0 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 22.19.0 2 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite* 2 | -------------------------------------------------------------------------------- /bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /resources/js/app.js: -------------------------------------------------------------------------------- 1 | import './bootstrap'; 2 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /storage/app/private/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/debugbar/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | echo "#Lint Staged" 2 | npx lint-staged 3 | -------------------------------------------------------------------------------- /storage/framework/cache/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !data/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /resources/views/components/post.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /resources/views/components/sidebar.blade.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !private/ 3 | !public/ 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /resources/views/components/navbar.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
-------------------------------------------------------------------------------- /resources/views/components/feed.blade.php: -------------------------------------------------------------------------------- 1 |
2 | {{ $slot }} 3 |
-------------------------------------------------------------------------------- /.ncurc.json: -------------------------------------------------------------------------------- 1 | { 2 | "reject": [], 3 | "interactive": true, 4 | "format": ["group"] 5 | } 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .env* 4 | vendor/ 5 | public/ 6 | .git 7 | package-lock.json 8 | composer.lock 9 | storage/ 10 | -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | # Annotations 2 | 3 | [//]: # (Todas suas anotações sobre o projeto. Esse arquivo será mais importante que boa parte do projeto!) -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | view('welcome')); 8 | -------------------------------------------------------------------------------- /resources/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | window.axios = axios; 3 | 4 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; 5 | -------------------------------------------------------------------------------- /tests/Unit/ExampleTest.php: -------------------------------------------------------------------------------- 1 | toBeTrue(); 7 | }); 8 | -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | ({isSticky:!1,enableSticky(){this.isSticky=this.$el.getBoundingClientRect().top>0},disableSticky(){this.isSticky=!1}});export{i as default}; 2 | -------------------------------------------------------------------------------- /public/fonts/filament/filament/inter/inter-greek-wght-normal-AXVTPQD5.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3pontos-tech/fullstack-test/HEAD/public/fonts/filament/filament/inter/inter-greek-wght-normal-AXVTPQD5.woff2 -------------------------------------------------------------------------------- /public/fonts/filament/filament/inter/inter-greek-wght-normal-IRE366VL.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3pontos-tech/fullstack-test/HEAD/public/fonts/filament/filament/inter/inter-greek-wght-normal-IRE366VL.woff2 -------------------------------------------------------------------------------- /public/fonts/filament/filament/inter/inter-greek-wght-normal-N43DBLU2.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3pontos-tech/fullstack-test/HEAD/public/fonts/filament/filament/inter/inter-greek-wght-normal-N43DBLU2.woff2 -------------------------------------------------------------------------------- /public/fonts/filament/filament/inter/inter-latin-wght-normal-NRMW37G5.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3pontos-tech/fullstack-test/HEAD/public/fonts/filament/filament/inter/inter-latin-wght-normal-NRMW37G5.woff2 -------------------------------------------------------------------------------- /public/fonts/filament/filament/inter/inter-latin-wght-normal-O25CN4JL.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3pontos-tech/fullstack-test/HEAD/public/fonts/filament/filament/inter/inter-latin-wght-normal-O25CN4JL.woff2 -------------------------------------------------------------------------------- /public/fonts/filament/filament/inter/inter-latin-wght-normal-OPIJAQLS.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3pontos-tech/fullstack-test/HEAD/public/fonts/filament/filament/inter/inter-latin-wght-normal-OPIJAQLS.woff2 -------------------------------------------------------------------------------- /public/fonts/filament/filament/inter/inter-cyrillic-wght-normal-EWLSKVKN.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3pontos-tech/fullstack-test/HEAD/public/fonts/filament/filament/inter/inter-cyrillic-wght-normal-EWLSKVKN.woff2 -------------------------------------------------------------------------------- /public/fonts/filament/filament/inter/inter-cyrillic-wght-normal-JEOLYBOO.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3pontos-tech/fullstack-test/HEAD/public/fonts/filament/filament/inter/inter-cyrillic-wght-normal-JEOLYBOO.woff2 -------------------------------------------------------------------------------- /public/fonts/filament/filament/inter/inter-cyrillic-wght-normal-R5CMSONN.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3pontos-tech/fullstack-test/HEAD/public/fonts/filament/filament/inter/inter-cyrillic-wght-normal-R5CMSONN.woff2 -------------------------------------------------------------------------------- /public/fonts/filament/filament/inter/inter-greek-ext-wght-normal-7GGTF7EK.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3pontos-tech/fullstack-test/HEAD/public/fonts/filament/filament/inter/inter-greek-ext-wght-normal-7GGTF7EK.woff2 -------------------------------------------------------------------------------- /public/fonts/filament/filament/inter/inter-greek-ext-wght-normal-EOVOK2B5.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3pontos-tech/fullstack-test/HEAD/public/fonts/filament/filament/inter/inter-greek-ext-wght-normal-EOVOK2B5.woff2 -------------------------------------------------------------------------------- /public/fonts/filament/filament/inter/inter-greek-ext-wght-normal-ZEVLMORV.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3pontos-tech/fullstack-test/HEAD/public/fonts/filament/filament/inter/inter-greek-ext-wght-normal-ZEVLMORV.woff2 -------------------------------------------------------------------------------- /public/fonts/filament/filament/inter/inter-latin-ext-wght-normal-5SRY4DMZ.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3pontos-tech/fullstack-test/HEAD/public/fonts/filament/filament/inter/inter-latin-ext-wght-normal-5SRY4DMZ.woff2 -------------------------------------------------------------------------------- /public/fonts/filament/filament/inter/inter-latin-ext-wght-normal-GZCIV3NH.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3pontos-tech/fullstack-test/HEAD/public/fonts/filament/filament/inter/inter-latin-ext-wght-normal-GZCIV3NH.woff2 -------------------------------------------------------------------------------- /public/fonts/filament/filament/inter/inter-latin-ext-wght-normal-HA22NDSG.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3pontos-tech/fullstack-test/HEAD/public/fonts/filament/filament/inter/inter-latin-ext-wght-normal-HA22NDSG.woff2 -------------------------------------------------------------------------------- /public/fonts/filament/filament/inter/inter-vietnamese-wght-normal-CE5GGD3W.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3pontos-tech/fullstack-test/HEAD/public/fonts/filament/filament/inter/inter-vietnamese-wght-normal-CE5GGD3W.woff2 -------------------------------------------------------------------------------- /public/fonts/filament/filament/inter/inter-vietnamese-wght-normal-TWG5UU7E.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3pontos-tech/fullstack-test/HEAD/public/fonts/filament/filament/inter/inter-vietnamese-wght-normal-TWG5UU7E.woff2 -------------------------------------------------------------------------------- /public/fonts/filament/filament/inter/inter-cyrillic-ext-wght-normal-ASVAGXXE.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3pontos-tech/fullstack-test/HEAD/public/fonts/filament/filament/inter/inter-cyrillic-ext-wght-normal-ASVAGXXE.woff2 -------------------------------------------------------------------------------- /public/fonts/filament/filament/inter/inter-cyrillic-ext-wght-normal-IYF56FF6.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3pontos-tech/fullstack-test/HEAD/public/fonts/filament/filament/inter/inter-cyrillic-ext-wght-normal-IYF56FF6.woff2 -------------------------------------------------------------------------------- /public/fonts/filament/filament/inter/inter-cyrillic-ext-wght-normal-XKHXBTUO.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3pontos-tech/fullstack-test/HEAD/public/fonts/filament/filament/inter/inter-cyrillic-ext-wght-normal-XKHXBTUO.woff2 -------------------------------------------------------------------------------- /resources/css/filament/admin/theme.css: -------------------------------------------------------------------------------- 1 | @import '../../../../vendor/filament/filament/resources/css/theme.css'; 2 | 3 | @source '../../../../app/Filament/Admin/**/*'; 4 | @source '../../../../resources/views/filament/admin/**/*'; 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "endOfLine": "lf", 5 | "printWidth": 120, 6 | "semi": true, 7 | "plugins": ["prettier-plugin-blade", "prettier-plugin-tailwindcss"] 8 | } 9 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | get('/'); 7 | 8 | $response->assertStatus(200); 9 | }); 10 | -------------------------------------------------------------------------------- /tests/Browser/HomeTest.php: -------------------------------------------------------------------------------- 1 | assertSee('Reddit') 8 | ->assertNoSmoke(); 9 | })->skip(); 10 | -------------------------------------------------------------------------------- /bootstrap/providers.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 10 | })->purpose('Display an inspiring quote'); 11 | -------------------------------------------------------------------------------- /resources/README.md: -------------------------------------------------------------------------------- 1 | # Componentes 2 | 3 | Já existe uma base de componentes no projeto, você não necessáriamente precisa seguir à risca. 4 | 5 | Crie algo que seja confortável para você manter, ou siga o que já está no projeto. 6 | 7 | > Não necessáriamente o que existe hoje suporta tudo que precisamos, mas você terá que justificar sua escolha. -------------------------------------------------------------------------------- /resources/views/welcome.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |
-------------------------------------------------------------------------------- /.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 | [{Makefile,**.mk}] 18 | indent_style = tab 19 | 20 | [{Dockerfile,*.Dockerfile}] 21 | indent_style = space 22 | indent_size = 4 -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import laravel from 'laravel-vite-plugin'; 3 | import tailwindcss from '@tailwindcss/vite'; 4 | 5 | export default defineConfig({ 6 | plugins: [ 7 | laravel({ 8 | input: ['resources/css/filament/admin/theme.css', 'resources/css/app.css', 'resources/js/app.js'], 9 | refresh: true, 10 | }), 11 | tailwindcss(), 12 | ], 13 | }); 14 | -------------------------------------------------------------------------------- /tests/Arch/PresetTest.php: -------------------------------------------------------------------------------- 1 | preset() 7 | ->laravel() 8 | ->ignoring('App\Providers'); 9 | 10 | arch('application must follow the defined PHP architectural rules') 11 | ->preset() 12 | ->php(); 13 | 14 | arch('application must follow security best practices') 15 | ->preset() 16 | ->security(); 17 | -------------------------------------------------------------------------------- /docker/redis.Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1.15.0 2 | 3 | FROM redis:7-alpine3.21@sha256:bb186d083732f669da90be8b0f975a37812b15e913465bb14d845db72a4e3e08 4 | 5 | ENV TZ=America/Sao_Paulo 6 | 7 | RUN set -xeu; \ 8 | apk update;\ 9 | apk add --no-cache tzdata nano ca-certificates;\ 10 | ln -snf /usr/share/zoneinfo/"${TZ}" /etc/localtime;\ 11 | echo "${TZ}" > /etc/timezone;\ 12 | update-ca-certificates;\ 13 | rm -rf /var/cache/apk/*; 14 | -------------------------------------------------------------------------------- /resources/css/app.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss'; 2 | 3 | @source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php'; 4 | @source '../../storage/framework/views/*.php'; 5 | @source '../**/*.blade.php'; 6 | @source '../**/*.js'; 7 | 8 | @theme { 9 | --font-sans: 10 | 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 11 | 'Segoe UI Symbol', 'Noto Color Emoji'; 12 | } 13 | -------------------------------------------------------------------------------- /docker/mailpit.Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1.15.0 2 | 3 | FROM axllent/mailpit:v1.27.8@sha256:6abc8e633df15eaf785cfcf38bae48e66f64beecdc03121e249d0f9ec15f0707 4 | 5 | ENV TZ=America/Sao_Paulo 6 | 7 | RUN set -xeu;\ 8 | apk update;\ 9 | apk add --no-cache tzdata nano ca-certificates;\ 10 | ln -snf /usr/share/zoneinfo/"${TZ}" /etc/localtime;\ 11 | echo "${TZ}" > /etc/timezone; \ 12 | update-ca-certificates;\ 13 | rm -rf /var/cache/apk/*; 14 | -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | handleCommand(new ArgvInput); 17 | 18 | exit($status); 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | .env 4 | .env.backup 5 | .env.production 6 | .phpactor.json 7 | .phpstorm.meta.php 8 | .phpunit.result.cache 9 | .pint.result.cache 10 | /.fleet 11 | /.idea 12 | /.nova 13 | /.phpunit.cache 14 | /.rector.cache 15 | /.vscode 16 | /.zed 17 | /auth.json 18 | /node_modules 19 | /public/build 20 | /public/hot 21 | /public/storage 22 | /storage/*.key 23 | /storage/pail 24 | /vendor 25 | Homestead.json 26 | Homestead.yaml 27 | Thumbs.db 28 | _ide_helper.php 29 | tests/Browser/Screenshots 30 | -------------------------------------------------------------------------------- /database/seeders/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | isLocal()) { 18 | User::factory()->admin()->create(); 19 | } 20 | 21 | User::factory(10)->create(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /docker/postgres.Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1.15.0 2 | 3 | FROM postgres:17-alpine3.22@sha256:d5f196a551b5cef1c70853c6dd588f456d16ca4ea733e3f31c75bc1ae2f65f3f 4 | 5 | ENV TZ=America/Sao_Paulo 6 | 7 | RUN set -eux;\ 8 | apk update;\ 9 | apk add --no-cache tzdata nano ca-certificates;\ 10 | apk add --no-cache postgresql-contrib postgis;\ 11 | ln -snf /usr/share/zoneinfo/"${TZ}" /etc/localtime;\ 12 | echo "${TZ}" > /etc/timezone;\ 13 | update-ca-certificates;\ 14 | rm -rf /var/cache/apk/*; 15 | -------------------------------------------------------------------------------- /app/Filament/Shared/Pages/LoginPage.php: -------------------------------------------------------------------------------- 1 | isProduction()) { 16 | $this->form->fill([ 17 | 'email' => 'admin@admin.com', 18 | 'password' => 'password', 19 | 'remember' => true, 20 | ]); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Browser/Filament/Admin/Auth/LoginTest.php: -------------------------------------------------------------------------------- 1 | create([ 9 | 'email' => 'gvieira18@sycorax.com', 10 | 'password' => 'password', 11 | ]); 12 | 13 | visit('/admin/login') 14 | ->fill('form.email', $user->email) 15 | ->fill('form.password', 'password') 16 | ->submit() 17 | ->assertSee('Dashboard'); 18 | 19 | $this->assertAuthenticated(); 20 | })->skip(); 21 | -------------------------------------------------------------------------------- /public/js/filament/forms/components/textarea.js: -------------------------------------------------------------------------------- 1 | function r({initialHeight:t,shouldAutosize:i,state:s}){return{state:s,wrapperEl:null,init(){this.wrapperEl=this.$el.parentNode,this.setInitialHeight(),i?this.$watch("state",()=>{this.resize()}):this.setUpResizeObserver()},setInitialHeight(){this.$el.scrollHeight<=0||(this.wrapperEl.style.height=t+"rem")},resize(){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(){new ResizeObserver(()=>{this.wrapperEl.style.height=this.$el.style.height}).observe(this.$el)}}}export{r as default}; 2 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | withRouting( 11 | web: __DIR__.'/../routes/web.php', 12 | commands: __DIR__.'/../routes/console.php', 13 | health: '/up', 14 | ) 15 | ->withMiddleware(function (Middleware $middleware): void { 16 | // 17 | }) 18 | ->withExceptions(function (Exceptions $exceptions): void { 19 | // 20 | })->create(); 21 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | handleRequest(Request::capture()); 23 | -------------------------------------------------------------------------------- /resources/views/components/layouts/guest.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Reddit-like Home • Laravel 8 | 9 | 10 | 11 | 12 | 13 | 14 | @vite(['resources/css/app.css', 'resources/js/app.js']) 15 | 16 | 17 | 18 | 19 | 20 | 21 | {{ $slot }} 22 | 23 | -------------------------------------------------------------------------------- /public/js/filament/forms/components/tags-input.js: -------------------------------------------------------------------------------- 1 | function s({state:n,splitKeys:a}){return{newTag:"",state:n,createTag(){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(t){this.state=this.state.filter(e=>e!==t)},reorderTags(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",...a].includes(t.key)&&(t.preventDefault(),t.stopPropagation(),this.createTag())},"x-on:paste"(){this.$nextTick(()=>{if(a.length===0){this.createTag();return}let t=a.map(e=>e.replace(/[/\-\\^$*+?.()|[\]{}]/g,"\\$&")).join("|");this.newTag.split(new RegExp(t,"g")).forEach(e=>{this.newTag=e,this.createTag()})})}}}}export{s as default}; 2 | -------------------------------------------------------------------------------- /public/js/filament/schemas/components/tabs.js: -------------------------------------------------------------------------------- 1 | function u({activeTab:a,isTabPersistedInQueryString:e,livewireId:h,tab:o,tabQueryStringKey:s}){return{tab:o,init(){let t=this.getTabs(),i=new URLSearchParams(window.location.search);e&&i.has(s)&&t.includes(i.get(s))&&(this.tab=i.get(s)),this.$watch("tab",()=>this.updateQueryString()),(!this.tab||!t.includes(this.tab))&&(this.tab=t[a-1]),Livewire.hook("commit",({component:r,commit:f,succeed:c,fail:l,respond:b})=>{c(({snapshot:d,effect:m})=>{this.$nextTick(()=>{if(r.id!==h)return;let n=this.getTabs();n.includes(this.tab)||(this.tab=n[a-1]??this.tab)})})})},getTabs(){return this.$refs.tabsData?JSON.parse(this.$refs.tabsData.value):[]},updateQueryString(){if(!e)return;let t=new URL(window.location.href);t.searchParams.set(s,this.tab),history.replaceState(null,document.title,t.toString())}}}export{u as default}; 2 | -------------------------------------------------------------------------------- /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_AUTHORIZATION:%{HTTP:Authorization}] 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/js/filament/tables/components/columns/checkbox.js: -------------------------------------------------------------------------------- 1 | function o({name:i,recordKey:s,state:a}){return{error:void 0,isLoading:!1,state:a,init(){Livewire.hook("commit",({component:e,commit:r,succeed:n,fail:h,respond:u})=>{n(({snapshot:f,effect:d})=>{this.$nextTick(()=>{if(this.isLoading||e.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let t=this.getServerState();t===void 0||Alpine.raw(this.state)===t||(this.state=t)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||Alpine.raw(this.state)===e)return;this.isLoading=!0;let r=await this.$wire.updateTableColumnState(i,s,this.state);this.error=r?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.state?"1":"0"),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[1,"1"].includes(this.$refs.serverState.value)}}}export{o as default}; 2 | -------------------------------------------------------------------------------- /public/js/filament/tables/components/columns/toggle.js: -------------------------------------------------------------------------------- 1 | function o({name:i,recordKey:s,state:a}){return{error:void 0,isLoading:!1,state:a,init(){Livewire.hook("commit",({component:e,commit:r,succeed:n,fail:h,respond:u})=>{n(({snapshot:f,effect:d})=>{this.$nextTick(()=>{if(this.isLoading||e.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let t=this.getServerState();t===void 0||Alpine.raw(this.state)===t||(this.state=t)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||Alpine.raw(this.state)===e)return;this.isLoading=!0;let r=await this.$wire.updateTableColumnState(i,s,this.state);this.error=r?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.state?"1":"0"),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[1,"1"].includes(this.$refs.serverState.value)}}}export{o as default}; 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/js/filament/forms/components/key-value.js: -------------------------------------------------------------------------------- 1 | function h({state:r}){return{state:r,rows:[],init(){this.updateRows(),this.rows.length<=0?this.rows.push({key:"",value:""}):this.updateState(),this.$watch("state",(e,t)=>{let s=i=>i===null?0:Array.isArray(i)?i.length:typeof i!="object"?0:Object.keys(i).length;s(e)===0&&s(t)===0||this.updateRows()})},addRow(){this.rows.push({key:"",value:""}),this.updateState()},deleteRow(e){this.rows.splice(e,1),this.rows.length<=0&&this.addRow(),this.updateState()},reorderRows(e){let t=Alpine.raw(this.rows);this.rows=[];let s=t.splice(e.oldIndex,1)[0];t.splice(e.newIndex,0,s),this.$nextTick(()=>{this.rows=t,this.updateState()})},updateRows(){let t=Alpine.raw(this.state).map(({key:s,value:i})=>({key:s,value:i}));this.rows.forEach(s=>{(s.key===""||s.key===null)&&t.push({key:"",value:s.value})}),this.rows=t},updateState(){let e=[];this.rows.forEach(t=>{t.key===""||t.key===null||e.push({key:t.key,value:t.value})}),JSON.stringify(this.state)!==JSON.stringify(e)&&(this.state=e)}}}export{h as default}; 2 | -------------------------------------------------------------------------------- /public/js/filament/actions/actions.js: -------------------------------------------------------------------------------- 1 | (()=>{var n=({livewireId:e})=>({actionNestingIndex:null,init(){window.addEventListener("sync-action-modals",t=>{t.detail.id===e&&this.syncActionModals(t.detail.newActionNestingIndex)})},syncActionModals(t){if(this.actionNestingIndex===t){this.actionNestingIndex!==null&&this.$nextTick(()=>this.openModal());return}if(this.actionNestingIndex!==null&&this.closeModal(),this.actionNestingIndex=t,this.actionNestingIndex!==null){if(!this.$el.querySelector(`#${this.generateModalId(t)}`)){this.$nextTick(()=>this.openModal());return}this.openModal()}},generateModalId(t){return`fi-${e}-action-`+t},openModal(){let t=this.generateModalId(this.actionNestingIndex);document.dispatchEvent(new CustomEvent("open-modal",{bubbles:!0,composed:!0,detail:{id:t}}))},closeModal(){let t=this.generateModalId(this.actionNestingIndex);document.dispatchEvent(new CustomEvent("close-modal-quietly",{bubbles:!0,composed:!0,detail:{id:t}}))}});document.addEventListener("alpine:init",()=>{window.Alpine.data("filamentActionModals",n)});})(); 2 | -------------------------------------------------------------------------------- /public/js/filament/tables/components/columns/text-input.js: -------------------------------------------------------------------------------- 1 | function o({name:i,recordKey:s,state:a}){return{error:void 0,isLoading:!1,state:a,init(){Livewire.hook("commit",({component:e,commit:r,succeed:n,fail:d,respond:u})=>{n(({snapshot:f,effect:h})=>{this.$nextTick(()=>{if(this.isLoading||e.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let t=this.getServerState();t===void 0||this.getNormalizedState()===t||(this.state=t)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||this.getNormalizedState()===e)return;this.isLoading=!0;let r=await this.$wire.updateTableColumnState(i,s,this.state);this.error=r?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.getNormalizedState()),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[null,void 0].includes(this.$refs.serverState.value)?"":this.$refs.serverState.value.replaceAll('\\"','"')},getNormalizedState(){let e=Alpine.raw(this.state);return[null,void 0].includes(e)?"":e}}}export{o as default}; 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Gabriel Vieira 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 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'token' => env('POSTMARK_TOKEN'), 21 | ], 22 | 23 | 'resend' => [ 24 | 'key' => env('RESEND_KEY'), 25 | ], 26 | 27 | 'ses' => [ 28 | 'key' => env('AWS_ACCESS_KEY_ID'), 29 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 30 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 31 | ], 32 | 33 | 'slack' => [ 34 | 'notifications' => [ 35 | 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'), 36 | 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'), 37 | ], 38 | ], 39 | 40 | ]; 41 | -------------------------------------------------------------------------------- /database/migrations/2025_07_20_033509_create_media_table.php: -------------------------------------------------------------------------------- 1 | id()->generatedAs(); 15 | 16 | $table->morphs('model'); 17 | $table->uuid()->nullable()->unique(); 18 | $table->string('collection_name'); 19 | $table->string('name'); 20 | $table->string('file_name'); 21 | $table->string('mime_type')->nullable(); 22 | $table->string('disk'); 23 | $table->string('conversions_disk')->nullable(); 24 | $table->unsignedBigInteger('size'); 25 | $table->json('manipulations'); 26 | $table->json('custom_properties'); 27 | $table->json('generated_conversions'); 28 | $table->json('responsive_images'); 29 | $table->unsignedInteger('order_column')->nullable()->index(); 30 | 31 | $table->timestampsTz(); 32 | }); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gvieira18/sycorax", 3 | "license": "MIT", 4 | "$schema": "https://json.schemastore.org/package.json", 5 | "private": true, 6 | "type": "module", 7 | "engines": { 8 | "node": "^22" 9 | }, 10 | "scripts": { 11 | "build": "vite build", 12 | "dev": "vite", 13 | "format": "npx prettier --write \"**/*.{js,ts,json,yml,md,css,blade.php}\"", 14 | "prepare": "npx -y husky", 15 | "outdated": "npx npm-check-updates" 16 | }, 17 | "devDependencies": { 18 | "@tailwindcss/vite": "4.1.13", 19 | "axios": "1.12.2", 20 | "concurrently": "9.2.1", 21 | "husky": "9.1.7", 22 | "laravel-vite-plugin": "2.0.1", 23 | "lint-staged": "16.2.0", 24 | "npm-check-updates": "18.3.0", 25 | "playwright": "1.55.1", 26 | "prettier": "3.6.2", 27 | "prettier-plugin-blade": "2.1.21", 28 | "prettier-plugin-tailwindcss": "0.6.14", 29 | "tailwindcss": "4.1.13", 30 | "vite": "7.1.7" 31 | }, 32 | "lint-staged": { 33 | "*.{js,ts,json,yml,md,css,blade.php}": [ 34 | "prettier --write" 35 | ], 36 | "*.php": [ 37 | "vendor/bin/pint", 38 | "vendor/bin/rector process" 39 | ] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /public/js/filament/schemas/components/wizard.js: -------------------------------------------------------------------------------- 1 | function o({isSkippable:s,isStepPersistedInQueryString:i,key:r,startStep:h,stepQueryStringKey:n}){return{step:null,init(){this.$watch("step",()=>this.updateQueryString()),this.step=this.getSteps().at(h-1),this.autofocusFields()},async requestNextStep(){await this.$wire.callSchemaComponentMethod(r,"nextStep",{currentStepIndex:this.getStepIndex(this.step)})},goToNextStep(){let t=this.getStepIndex(this.step)+1;t>=this.getSteps().length||(this.step=this.getSteps()[t],this.autofocusFields(),this.scroll())},goToPreviousStep(){let t=this.getStepIndex(this.step)-1;t<0||(this.step=this.getSteps()[t],this.autofocusFields(),this.scroll())},scroll(){this.$nextTick(()=>{this.$refs.header?.children[this.getStepIndex(this.step)].scrollIntoView({behavior:"smooth",block:"start"})})},autofocusFields(){this.$nextTick(()=>this.$refs[`step-${this.step}`].querySelector("[autofocus]")?.focus())},getStepIndex(t){let e=this.getSteps().findIndex(p=>p===t);return e===-1?0:e},getSteps(){return JSON.parse(this.$refs.stepsData.value)},isFirstStep(){return this.getStepIndex(this.step)<=0},isLastStep(){return this.getStepIndex(this.step)+1>=this.getSteps().length},isStepAccessible(t){return s||this.getStepIndex(this.step)>this.getStepIndex(t)},updateQueryString(){if(!i)return;let t=new URL(window.location.href);t.searchParams.set(n,this.step),history.replaceState(null,document.title,t.toString())}}}export{o as default}; 2 | -------------------------------------------------------------------------------- /.env.testing: -------------------------------------------------------------------------------- 1 | APP_NAME=Laravel 2 | APP_ENV=local 3 | APP_KEY=base64:QR0ZFC+lpAKXTfvo0N82jnq7U9Q9bgNcy2xw1qDSYMo= 4 | APP_DEBUG=true 5 | APP_URL=http://127.0.0.1:8000 6 | APP_TIMEZONE=UTC 7 | 8 | APP_LOCALE=en 9 | APP_FALLBACK_LOCALE=en 10 | APP_FAKER_LOCALE=en_US 11 | 12 | DEBUGBAR_ENABLED=false 13 | TELESCOPE_ENABLED=false 14 | 15 | APP_MAINTENANCE_DRIVER=file 16 | # APP_MAINTENANCE_STORE=database 17 | 18 | PHP_CLI_SERVER_WORKERS=4 19 | 20 | BCRYPT_ROUNDS=12 21 | 22 | LOG_CHANNEL=stack 23 | LOG_STACK=single 24 | LOG_DEPRECATIONS_CHANNEL=null 25 | LOG_LEVEL=debug 26 | 27 | DB_CONNECTION=sqlite 28 | DB_DATABASE=:memory: 29 | 30 | SESSION_DRIVER=database 31 | SESSION_LIFETIME=120 32 | SESSION_ENCRYPT=false 33 | SESSION_PATH=/ 34 | SESSION_DOMAIN=null 35 | 36 | BROADCAST_CONNECTION=log 37 | FILESYSTEM_DISK=local 38 | QUEUE_CONNECTION=database 39 | 40 | CACHE_STORE=database 41 | # CACHE_PREFIX= 42 | 43 | MEMCACHED_HOST=127.0.0.1 44 | 45 | REDIS_CLIENT=phpredis 46 | REDIS_HOST=127.0.0.1 47 | REDIS_PASSWORD=null 48 | REDIS_PORT=6379 49 | 50 | MAIL_MAILER=log 51 | MAIL_SCHEME=null 52 | MAIL_HOST=127.0.0.1 53 | MAIL_PORT=1025 54 | MAIL_USERNAME=null 55 | MAIL_PASSWORD=null 56 | MAIL_FROM_ADDRESS="noreply@localhost.com" 57 | MAIL_FROM_NAME="${APP_NAME}" 58 | 59 | AWS_ACCESS_KEY_ID= 60 | AWS_SECRET_ACCESS_KEY= 61 | AWS_DEFAULT_REGION=us-east-1 62 | AWS_BUCKET= 63 | AWS_USE_PATH_STYLE_ENDPOINT=false 64 | 65 | VITE_APP_NAME="${APP_NAME}" 66 | -------------------------------------------------------------------------------- /.env.docker: -------------------------------------------------------------------------------- 1 | APP_NAME=Laravel 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_URL=http://127.0.0.1:8000 6 | APP_TIMEZONE=UTC 7 | 8 | APP_LOCALE=en 9 | APP_FALLBACK_LOCALE=en 10 | APP_FAKER_LOCALE=en_US 11 | 12 | DEBUGBAR_ENABLED=false 13 | TELESCOPE_ENABLED=false 14 | 15 | APP_MAINTENANCE_DRIVER=file 16 | # APP_MAINTENANCE_STORE=database 17 | 18 | PHP_CLI_SERVER_WORKERS=4 19 | 20 | BCRYPT_ROUNDS=12 21 | 22 | LOG_CHANNEL=stack 23 | LOG_STACK=single 24 | LOG_DEPRECATIONS_CHANNEL=null 25 | LOG_LEVEL=debug 26 | 27 | DB_CONNECTION=pgsql 28 | DB_HOST=127.0.0.1 29 | DB_PORT=5432 30 | DB_DATABASE=dev_sycorax 31 | DB_USERNAME=postgres 32 | DB_PASSWORD=postgres 33 | 34 | SESSION_DRIVER=redis 35 | SESSION_LIFETIME=120 36 | SESSION_ENCRYPT=false 37 | SESSION_PATH=/ 38 | SESSION_DOMAIN=null 39 | 40 | BROADCAST_CONNECTION=log 41 | FILESYSTEM_DISK=local 42 | QUEUE_CONNECTION=redis 43 | 44 | CACHE_STORE=redis 45 | # CACHE_PREFIX= 46 | 47 | MEMCACHED_HOST=127.0.0.1 48 | 49 | REDIS_CLIENT=phpredis 50 | REDIS_HOST=127.0.0.1 51 | REDIS_PASSWORD=null 52 | REDIS_PORT=6379 53 | 54 | MAIL_MAILER=smtp 55 | MAIL_SCHEME=null 56 | MAIL_HOST=127.0.0.1 57 | MAIL_PORT=1025 58 | MAIL_USERNAME=null 59 | MAIL_PASSWORD=null 60 | MAIL_FROM_ADDRESS="noreply@localhost.com" 61 | MAIL_FROM_NAME="${APP_NAME}" 62 | 63 | AWS_ACCESS_KEY_ID= 64 | AWS_SECRET_ACCESS_KEY= 65 | AWS_DEFAULT_REGION=us-east-1 66 | AWS_BUCKET= 67 | AWS_USE_PATH_STYLE_ENDPOINT=false 68 | 69 | VITE_APP_NAME="${APP_NAME}" 70 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=Laravel 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_URL=http://127.0.0.1:8000 6 | APP_TIMEZONE=UTC 7 | 8 | APP_LOCALE=en 9 | APP_FALLBACK_LOCALE=en 10 | APP_FAKER_LOCALE=en_US 11 | 12 | DEBUGBAR_ENABLED=false 13 | TELESCOPE_ENABLED=false 14 | 15 | APP_MAINTENANCE_DRIVER=file 16 | # APP_MAINTENANCE_STORE=database 17 | 18 | PHP_CLI_SERVER_WORKERS=4 19 | 20 | BCRYPT_ROUNDS=12 21 | 22 | LOG_CHANNEL=stack 23 | LOG_STACK=single 24 | LOG_DEPRECATIONS_CHANNEL=null 25 | LOG_LEVEL=debug 26 | 27 | DB_CONNECTION=sqlite 28 | #DB_HOST=127.0.0.1 29 | #DB_PORT=5432 30 | DB_DATABASE=database/database.sqlite 31 | #DB_USERNAME=postgres 32 | #DB_PASSWORD=postgres 33 | 34 | SESSION_DRIVER=database 35 | SESSION_LIFETIME=120 36 | SESSION_ENCRYPT=false 37 | SESSION_PATH=/ 38 | SESSION_DOMAIN=null 39 | 40 | BROADCAST_CONNECTION=log 41 | FILESYSTEM_DISK=local 42 | QUEUE_CONNECTION=database 43 | 44 | CACHE_STORE=database 45 | # CACHE_PREFIX= 46 | 47 | MEMCACHED_HOST=127.0.0.1 48 | 49 | REDIS_CLIENT=phpredis 50 | REDIS_HOST=127.0.0.1 51 | REDIS_PASSWORD=null 52 | REDIS_PORT=6379 53 | 54 | MAIL_MAILER=log 55 | MAIL_SCHEME=null 56 | MAIL_HOST=127.0.0.1 57 | MAIL_PORT=1025 58 | MAIL_USERNAME=null 59 | MAIL_PASSWORD=null 60 | MAIL_FROM_ADDRESS="noreply@localhost.com" 61 | MAIL_FROM_NAME="${APP_NAME}" 62 | 63 | AWS_ACCESS_KEY_ID= 64 | AWS_SECRET_ACCESS_KEY= 65 | AWS_DEFAULT_REGION=us-east-1 66 | AWS_BUCKET= 67 | AWS_USE_PATH_STYLE_ENDPOINT=false 68 | 69 | VITE_APP_NAME="${APP_NAME}" 70 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | id()->generatedAs(); 18 | $table->string('name'); 19 | $table->string('email')->unique(); 20 | $table->timestampTz('email_verified_at')->nullable(); 21 | $table->string('password'); 22 | $table->rememberToken(); 23 | $table->timestampsTz(); 24 | }); 25 | 26 | Schema::create('password_reset_tokens', function (Blueprint $table): void { 27 | $table->string('email')->primary(); 28 | $table->string('token'); 29 | $table->timestampTz('created_at')->nullable(); 30 | }); 31 | 32 | Schema::create('sessions', function (Blueprint $table): void { 33 | $table->string('id')->primary(); 34 | $table->foreignId('user_id')->nullable()->index(); 35 | $table->string('ip_address', 45)->nullable(); 36 | $table->text('user_agent')->nullable(); 37 | $table->longText('payload'); 38 | $table->integer('last_activity')->index(); 39 | }); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | tests/Arch 10 | 11 | 12 | tests/Unit 13 | 14 | 15 | tests/Feature 16 | 17 | 18 | tests/Browser 19 | 20 | 21 | 22 | 23 | app 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final class UserFactory extends Factory 16 | { 17 | protected $model = User::class; 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 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 | 'remember_token' => Str::random(10), 37 | ]; 38 | } 39 | 40 | /** 41 | * Indicate that the model's email address should be unverified. 42 | */ 43 | public function unverified(): self 44 | { 45 | return $this->state(fn (array $attributes): array => [ 46 | 'email_verified_at' => null, 47 | ]); 48 | } 49 | 50 | /** 51 | * Create a self-contained admin 52 | */ 53 | public function admin(): self 54 | { 55 | return $this->state(fn (array $attributes): array => [ 56 | 'name' => 'admin', 57 | 'email' => 'admin@admin.com', 58 | ]); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /public/js/filament/forms/components/checkbox-list.js: -------------------------------------------------------------------------------- 1 | function c({livewireId:s}){return{areAllCheckboxesChecked:!1,checkboxListOptions:[],search:"",visibleCheckboxListOptions:[],init(){this.checkboxListOptions=Array.from(this.$root.querySelectorAll(".fi-fo-checkbox-list-option")),this.updateVisibleCheckboxListOptions(),this.$nextTick(()=>{this.checkIfAllCheckboxesAreChecked()}),Livewire.hook("commit",({component:e,commit:t,succeed:i,fail:o,respond:h})=>{i(({snapshot:r,effect:l})=>{this.$nextTick(()=>{e.id===s&&(this.checkboxListOptions=Array.from(this.$root.querySelectorAll(".fi-fo-checkbox-list-option")),this.updateVisibleCheckboxListOptions(),this.checkIfAllCheckboxesAreChecked())})})}),this.$watch("search",()=>{this.updateVisibleCheckboxListOptions(),this.checkIfAllCheckboxesAreChecked()})},checkIfAllCheckboxesAreChecked(){this.areAllCheckboxesChecked=this.visibleCheckboxListOptions.length===this.visibleCheckboxListOptions.filter(e=>e.querySelector("input[type=checkbox]:checked, input[type=checkbox]:disabled")).length},toggleAllCheckboxes(){this.checkIfAllCheckboxesAreChecked();let e=!this.areAllCheckboxesChecked;this.visibleCheckboxListOptions.forEach(t=>{let i=t.querySelector("input[type=checkbox]");i.disabled||(i.checked=e,i.dispatchEvent(new Event("change")))}),this.areAllCheckboxesChecked=e},updateVisibleCheckboxListOptions(){this.visibleCheckboxListOptions=this.checkboxListOptions.filter(e=>["",null,void 0].includes(this.search)||e.querySelector(".fi-fo-checkbox-list-option-label")?.innerText.toLowerCase().includes(this.search.toLowerCase())?!0:e.querySelector(".fi-fo-checkbox-list-option-description")?.innerText.toLowerCase().includes(this.search.toLowerCase()))}}}export{c as default}; 2 | -------------------------------------------------------------------------------- /app/Models/User.php: -------------------------------------------------------------------------------- 1 | */ 20 | use HasFactory; 21 | 22 | use InteractsWithMedia; 23 | use Notifiable; 24 | 25 | /** 26 | * The attributes that are mass assignable. 27 | * 28 | * @var list 29 | */ 30 | protected $fillable = [ 31 | 'name', 32 | 'email', 33 | 'password', 34 | ]; 35 | 36 | /** 37 | * The attributes that should be hidden for serialization. 38 | * 39 | * @var list 40 | */ 41 | protected $hidden = [ 42 | 'password', 43 | 'remember_token', 44 | ]; 45 | 46 | public function canAccessPanel(Panel $panel): bool 47 | { 48 | return true; 49 | } 50 | 51 | public function getFilamentAvatarUrl(): ?string 52 | { 53 | $avatar = $this->getFirstMedia('profile-pictures'); 54 | 55 | return $avatar?->getUrl(); 56 | } 57 | 58 | /** 59 | * Get the attributes that should be cast. 60 | * 61 | * @return array 62 | */ 63 | protected function casts(): array 64 | { 65 | return [ 66 | 'email_verified_at' => 'datetime', 67 | 'password' => 'hashed', 68 | ]; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /public/js/filament/schemas/schemas.js: -------------------------------------------------------------------------------- 1 | (()=>{var d=()=>({isSticky:!1,enableSticky(){this.isSticky=this.$el.getBoundingClientRect().top>0},disableSticky(){this.isSticky=!1}});var m=function(n,e,i){let t=n;if(e.startsWith("/")&&(i=!0,e=e.slice(1)),i)return e;for(;e.startsWith("../");)t=t.includes(".")?t.slice(0,t.lastIndexOf(".")):null,e=e.slice(3);return["",null,void 0].includes(t)?e:["",null,void 0].includes(e)?t:`${t}.${e}`},u=n=>{let e=Alpine.findClosest(n,i=>i.__livewire);if(!e)throw"Could not find Livewire component in DOM tree.";return e.__livewire};document.addEventListener("alpine:init",()=>{window.Alpine.data("filamentSchema",({livewireId:n})=>({handleFormValidationError(e){e.detail.livewireId===n&&this.$nextTick(()=>{let i=this.$el.querySelector("[data-validation-error]");if(!i)return;let t=i;for(;t;)t.dispatchEvent(new CustomEvent("expand")),t=t.parentNode;setTimeout(()=>i.closest("[data-field-wrapper]").scrollIntoView({behavior:"smooth",block:"start",inline:"start"}),200)})}})),window.Alpine.data("filamentSchemaComponent",({path:n,containerPath:e,isLive:i,$wire:t})=>({$statePath:n,$get:(r,l)=>t.$get(m(e,r,l)),$set:(r,l,a,o=null)=>(o??(o=i),t.$set(m(e,r,a),l,o)),get $state(){return t.$get(n)}})),window.Alpine.data("filamentActionsSchemaComponent",d),Livewire.hook("commit",({component:n,commit:e,respond:i,succeed:t,fail:r})=>{t(({snapshot:l,effects:a})=>{a.dispatches?.forEach(o=>{if(!o.params?.awaitSchemaComponent)return;let s=Array.from(n.el.querySelectorAll(`[wire\\:partial="schema-component::${o.params.awaitSchemaComponent}"]`)).filter(c=>u(c)===n);if(s.length!==1){if(s.length>1)throw`Multiple schema components found with key [${o.params.awaitSchemaComponent}].`;window.addEventListener(`schema-component-${n.id}-${o.params.awaitSchemaComponent}-loaded`,()=>{window.dispatchEvent(new CustomEvent(o.name,{detail:o.params}))},{once:!0})}})})})});})(); 2 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000002_create_jobs_table.php: -------------------------------------------------------------------------------- 1 | id()->generatedAs(); 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()->generatedAs(); 41 | $table->string('uuid')->unique(); 42 | $table->text('connection'); 43 | $table->text('queue'); 44 | $table->longText('payload'); 45 | $table->longText('exception'); 46 | $table->timestampTz('failed_at')->useCurrent(); 47 | }); 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /public/fonts/filament/filament/inter/index.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-cyrillic-ext-wght-normal-IYF56FF6.woff2") format("woff2-variations");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-cyrillic-wght-normal-JEOLYBOO.woff2") format("woff2-variations");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-greek-ext-wght-normal-EOVOK2B5.woff2") format("woff2-variations");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-greek-wght-normal-IRE366VL.woff2") format("woff2-variations");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-vietnamese-wght-normal-CE5GGD3W.woff2") format("woff2-variations");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-latin-ext-wght-normal-HA22NDSG.woff2") format("woff2-variations");unicode-range: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,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url("./inter-latin-wght-normal-NRMW37G5.woff2") format("woff2-variations");unicode-range: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,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD} 2 | -------------------------------------------------------------------------------- /docker-compose.env.yml: -------------------------------------------------------------------------------- 1 | services: 2 | sycorax-db: 3 | build: 4 | dockerfile: docker/postgres.Dockerfile 5 | context: . 6 | image: postgres.local:17 7 | container_name: sycorax-db 8 | restart: no 9 | hostname: dev 10 | environment: 11 | POSTGRES_USER: postgres 12 | POSTGRES_PASSWORD: postgres 13 | POSTGRES_DB: dev_sycorax 14 | ports: 15 | - '5432:5432' 16 | volumes: 17 | - sycorax-db:/var/lib/postgresql/data 18 | networks: 19 | - dev-sycorax 20 | healthcheck: 21 | test: ['CMD-SHELL', 'pg_isready --quiet --username=postgres --dbname=postgres || exit 1'] 22 | interval: 10s 23 | retries: 5 24 | timeout: 10s 25 | start_period: 15s 26 | 27 | sycorax-redis: 28 | build: 29 | dockerfile: docker/redis.Dockerfile 30 | context: . 31 | image: redis.local:7 32 | container_name: sycorax-redis 33 | restart: no 34 | hostname: dev 35 | ports: 36 | - '6379:6379' 37 | networks: 38 | - dev-sycorax 39 | healthcheck: 40 | test: ['CMD-SHELL', 'redis-cli --raw incr ping || exit 1'] 41 | interval: 10s 42 | retries: 5 43 | timeout: 10s 44 | start_period: 15s 45 | 46 | sycorax-mailpit: 47 | build: 48 | dockerfile: docker/mailpit.Dockerfile 49 | context: . 50 | image: mailpit.local:1 51 | container_name: sycorax-mailpit 52 | restart: no 53 | hostname: dev 54 | ports: 55 | - '1025:1025' 56 | - '8025:8025' 57 | networks: 58 | - dev-sycorax 59 | healthcheck: 60 | test: ['CMD', '/mailpit', 'readyz'] 61 | interval: 10s 62 | retries: 5 63 | timeout: 10s 64 | start_period: 10s 65 | 66 | volumes: 67 | sycorax-db: 68 | name: sycorax-db 69 | 70 | networks: 71 | dev-sycorax: 72 | name: dev-sycorax 73 | driver: bridge 74 | driver_opts: 75 | com.docker.network.bridge.name: dev-sycorax 76 | com.docker.network.bridge.host_binding_ipv4: '127.0.0.1' 77 | -------------------------------------------------------------------------------- /tests/Pest.php: -------------------------------------------------------------------------------- 1 | group('feature')->in('Feature'); 20 | pest()->group('unit')->in('Unit'); 21 | pest()->group('browser')->in('Browser'); 22 | 23 | pest()->extend(TestCase::class) 24 | ->use(RefreshDatabase::class) 25 | ->in('Feature', 'Unit', 'Browser'); 26 | 27 | pest()->browser()->inFirefox(); 28 | 29 | pest()->printer()->compact(); 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Expectations 34 | |-------------------------------------------------------------------------- 35 | | 36 | | When you're writing tests, you often need to check that values meet certain conditions. The 37 | | "expect()" function gives you access to a set of "expectations" methods that you can use 38 | | to assert different things. Of course, you may extend the Expectation API at any time. 39 | | 40 | */ 41 | 42 | expect()->extend('toBeOne', fn () => $this->toBe(1)); 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Functions 47 | |-------------------------------------------------------------------------- 48 | | 49 | | While Pest is very powerful out-of-the-box, you may have some testing code specific to your 50 | | project that you don't want to repeat in every file. Here you can also expose helpers as 51 | | global functions to help you to reduce the number of lines of code in your test files. 52 | | 53 | */ 54 | 55 | function something(): void 56 | { 57 | // .. 58 | } 59 | -------------------------------------------------------------------------------- /database/migrations/2025_07_20_024239_create_telescope_entries_table.php: -------------------------------------------------------------------------------- 1 | getConnection()); 25 | 26 | $schema->create('telescope_entries', function (Blueprint $table): void { 27 | $table->bigIncrements('sequence'); 28 | $table->uuid('uuid'); 29 | $table->uuid('batch_id'); 30 | $table->string('family_hash')->nullable(); 31 | $table->boolean('should_display_on_index')->default(true); 32 | $table->string('type', 20); 33 | $table->longText('content'); 34 | $table->dateTimeTz('created_at')->nullable(); 35 | 36 | $table->unique('uuid'); 37 | $table->index('batch_id'); 38 | $table->index('family_hash'); 39 | $table->index('created_at'); 40 | $table->index(['type', 'should_display_on_index']); 41 | }); 42 | 43 | $schema->create('telescope_entries_tags', function (Blueprint $table): void { 44 | $table->uuid('entry_uuid'); 45 | $table->string('tag'); 46 | 47 | $table->primary(['entry_uuid', 'tag']); 48 | $table->index('tag'); 49 | 50 | $table->foreign('entry_uuid') 51 | ->references('uuid') 52 | ->on('telescope_entries') 53 | ->onDelete('cascade'); 54 | }); 55 | 56 | $schema->create('telescope_monitoring', function (Blueprint $table): void { 57 | $table->string('tag')->primary(); 58 | }); 59 | } 60 | }; 61 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := help 2 | 3 | .PHONY: help 4 | help: ## Show available commands 5 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' 6 | 7 | .PHONY: pint 8 | pint: ## Run Pint code style fixer 9 | @export XDEBUG_MODE=off 10 | @$(CURDIR)/vendor/bin/pint --parallel 11 | @unset XDEBUG_MODE 12 | 13 | .PHONY: test-pint 14 | test-pint: ## Run Pint code style fixer in test mode 15 | @export XDEBUG_MODE=off 16 | @$(CURDIR)/vendor/bin/pint --test --parallel 17 | @unset XDEBUG_MODE=off 18 | 19 | .PHONY: rector 20 | rector: ## Run Rector 21 | @$(CURDIR)/vendor/bin/rector process 22 | 23 | .PHONY: test-rector 24 | test-rector: ## Run Rector in test mode 25 | @$(CURDIR)/vendor/bin/rector process --dry-run 26 | 27 | .PHONY: phpstan 28 | phpstan: ## Run PHPStan 29 | @$(CURDIR)/vendor/bin/phpstan analyse --ansi 30 | 31 | .PHONY: test-phpstan 32 | test-phpstan: ## Run PHPStan in test mode 33 | @$(CURDIR)/vendor/bin/phpstan analyse --ansi 34 | 35 | .PHONY: format 36 | format: rector pint ## Run Pint and Rector and try to fixes the source code 37 | 38 | .PHONY: check 39 | check: test-rector test-pint test-phpstan ## Run Pint, PHPStan with Rector in dry-run mode 40 | 41 | .PHONY: test 42 | test: ## Run all tests 43 | @$(CURDIR)/vendor/bin/pest --parallel --compact 44 | 45 | .PHONY: test-unit 46 | test-unit: ## Run unit tests 47 | @$(CURDIR)/vendor/bin/pest --parallel --compact --group=unit 48 | 49 | .PHONY: test-feature 50 | test-feature: ## Run feature tests 51 | @$(CURDIR)/vendor/bin/pest --parallel --compact --group=feature 52 | 53 | .PHONY: test-browser 54 | test-browser: ## Run browser tests 55 | @$(CURDIR)/vendor/bin/pest --parallel --compact --group=browser 56 | 57 | .PHONY: migrate-fresh 58 | migrate-fresh: ## Run migrations and seed the database 59 | @php artisan migrate:fresh --seed 60 | 61 | .PHONY: env-up 62 | env-up: ## Start the development environment 63 | @docker compose --file docker-compose.env.yml up --detach 64 | 65 | .PHONY: env-down 66 | env-down: ## Start the development environment 67 | @docker compose --file docker-compose.env.yml down --rmi all --volumes 68 | 69 | .PHONY: dev 70 | dev: ## Start the server 71 | @composer run-script dev 72 | -------------------------------------------------------------------------------- /pint.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "laravel", 3 | "cache-file": ".pint.result.cache", 4 | "notPath": ["tests/TestCase.php"], 5 | "rules": { 6 | "operator_linebreak": true, 7 | "no_unused_imports": true, 8 | "array_push": true, 9 | "concat_space": { "spacing": "none" }, 10 | "backtick_to_shell_exec": true, 11 | "date_time_immutable": true, 12 | "declare_strict_types": true, 13 | "lowercase_keywords": true, 14 | "lowercase_static_reference": true, 15 | "final_class": true, 16 | "final_internal_class": { 17 | "annotation_include": [], 18 | "annotation_exclude": ["internal"] 19 | }, 20 | "final_public_method_for_abstract_class": true, 21 | "fully_qualified_strict_types": true, 22 | "global_namespace_import": { 23 | "import_classes": true, 24 | "import_constants": true, 25 | "import_functions": true 26 | }, 27 | "mb_str_functions": true, 28 | "modernize_types_casting": true, 29 | "new_with_parentheses": false, 30 | "no_superfluous_elseif": true, 31 | "no_useless_else": true, 32 | "no_multiple_statements_per_line": true, 33 | "ordered_class_elements": { 34 | "order": [ 35 | "use_trait", 36 | "case", 37 | "constant", 38 | "constant_public", 39 | "constant_protected", 40 | "constant_private", 41 | "property_public", 42 | "property_protected", 43 | "property_private", 44 | "construct", 45 | "destruct", 46 | "magic", 47 | "phpunit", 48 | "method_abstract", 49 | "method_public_static", 50 | "method_public", 51 | "method_protected_static", 52 | "method_protected", 53 | "method_private_static", 54 | "method_private" 55 | ], 56 | "sort_algorithm": "none" 57 | }, 58 | "ordered_interfaces": true, 59 | "ordered_traits": true, 60 | "protected_to_private": true, 61 | "self_accessor": true, 62 | "self_static_accessor": true, 63 | "strict_comparison": true, 64 | "visibility_required": true 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/Providers/TelescopeServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->hasDebugModeEnabled() 21 | && $this->app->isLocal() 22 | && $this->hasProviderToRegister() 23 | && $this->hasEnvEnabled(); 24 | 25 | Log::notice('Registering Telescope Service Provider'); 26 | 27 | if (! $canRegister) { 28 | return; 29 | } 30 | 31 | $this->registerTelescopeServiceProvider(); 32 | $this->setNightMode(); 33 | $this->hideSensitiveRequestDetails(); 34 | $this->setFilter(); 35 | } 36 | 37 | public function setFilter(): void 38 | { 39 | Telescope::filter(fn (): bool => true); 40 | Telescope::filterBatch(fn (): bool => true); 41 | } 42 | 43 | /** 44 | * Register the Telescope gate. 45 | * 46 | * This gate determines who can access Telescope in non-local environments. 47 | */ 48 | protected function gate(): void 49 | { 50 | Gate::define('viewTelescope', fn (User $user): bool => in_array($user->email, [ 51 | // 52 | ])); 53 | } 54 | 55 | private function hasEnvEnabled(): bool 56 | { 57 | return config('telescope.enabled'); 58 | } 59 | 60 | /** 61 | * Prevent sensitive request details from being logged by Telescope. 62 | */ 63 | private function hideSensitiveRequestDetails(): void 64 | { 65 | Telescope::hideRequestParameters(['_token']); 66 | Telescope::hideRequestHeaders([ 67 | 'cookie', 68 | 'x-csrf-token', 69 | 'x-xsrf-token', 70 | ]); 71 | } 72 | 73 | private function hasProviderToRegister(): bool 74 | { 75 | return class_exists(\Laravel\Telescope\TelescopeServiceProvider::class); 76 | } 77 | 78 | private function registerTelescopeServiceProvider(): void 79 | { 80 | $this->app->register(provider: \Laravel\Telescope\TelescopeServiceProvider::class); 81 | } 82 | 83 | private function setNightMode(): void 84 | { 85 | Telescope::night(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /app/Providers/Filament/AdminPanelProvider.php: -------------------------------------------------------------------------------- 1 | default() 31 | ->id('admin') 32 | ->path('admin') 33 | ->login(LoginPage::class) 34 | ->colors([ 35 | 'primary' => Color::Amber, 36 | ]) 37 | ->viteTheme('resources/css/filament/admin/theme.css') 38 | ->discoverResources(in: app_path('Filament/Admin/Resources'), for: 'App\\Filament\\Admin\\Resources') 39 | ->discoverPages(in: app_path('Filament/Admin/Pages'), for: 'App\\Filament\\Admin\\Pages') 40 | ->pages([ 41 | Dashboard::class, 42 | ]) 43 | ->discoverWidgets(in: app_path('Filament/Admin/Widgets'), for: 'App\\Filament\\Admin\\Widgets') 44 | ->sidebarFullyCollapsibleOnDesktop() 45 | ->widgets([ 46 | AccountWidget::class, 47 | FilamentInfoWidget::class, 48 | ]) 49 | ->middleware([ 50 | EncryptCookies::class, 51 | AddQueuedCookiesToResponse::class, 52 | StartSession::class, 53 | AuthenticateSession::class, 54 | ShareErrorsFromSession::class, 55 | VerifyCsrfToken::class, 56 | SubstituteBindings::class, 57 | DisableBladeIconComponents::class, 58 | DispatchServingFilamentEvent::class, 59 | ]) 60 | ->authMiddleware([ 61 | Authenticate::class, 62 | ]); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Taskfile.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | silent: true 4 | 5 | env: 6 | WORK_DIR: $(pwd) 7 | 8 | tasks: 9 | default: 10 | aliases: 11 | - help 12 | - h 13 | desc: Show available commands 14 | cmds: 15 | - task --list-all --sort none 16 | 17 | pint: 18 | desc: Run Pint code style fixer 19 | cmds: 20 | - export XDEBUG_MODE=off 21 | - '{{ .ROOT_DIR }}/vendor/bin/pint --parallel' 22 | - unset XDEBUG_MODE 23 | 24 | test-pint: 25 | desc: Run Pint code style fixer in test mode 26 | cmds: 27 | - '{{ .ROOT_DIR }}/vendor/bin/pint --parallel --test' 28 | 29 | rector: 30 | desc: Run Rector 31 | cmds: 32 | - '{{ .ROOT_DIR }}/vendor/bin/rector process' 33 | 34 | test-rector: 35 | desc: Run Rector in test mode 36 | cmds: 37 | - '{{ .ROOT_DIR }}/vendor/bin/rector process --dry-run' 38 | 39 | phpstan: 40 | desc: Run PHPStan 41 | cmds: 42 | - '{{ .ROOT_DIR }}/vendor/bin/phpstan analyse --ansi' 43 | 44 | test-phpstan: 45 | desc: Run PHPStan in test mode 46 | cmds: 47 | - '{{ .ROOT_DIR }}/vendor/bin/phpstan analyse --ansi' 48 | 49 | format: 50 | desc: Run Pint and Rector and try to fixes the source code 51 | aliases: [f] 52 | cmds: 53 | - task: rector 54 | - task: pint 55 | 56 | check: 57 | desc: Run Pint, PHPStan with Rector in dry-run mode 58 | aliases: 59 | - c 60 | cmds: 61 | - task: test-rector 62 | - task: test-pint 63 | - task: test-phpstan 64 | 65 | test: 66 | desc: Run all tests 67 | aliases: [t] 68 | cmd: '{{ .ROOT_DIR }}/vendor/bin/pest --parallel --compact' 69 | 70 | test-unit: 71 | desc: Run unit tests 72 | aliases: [tu] 73 | cmd: '{{ .ROOT_DIR }}/vendor/bin/pest --parallel --compact --group=unit' 74 | 75 | test-feature: 76 | desc: Run feature tests 77 | aliases: [tf] 78 | cmd: '{{ .ROOT_DIR }}/vendor/bin/pest --parallel --compact --group=feature' 79 | 80 | test-browser: 81 | desc: Run browser tests 82 | aliases: [tb] 83 | cmd: '{{ .ROOT_DIR }}/vendor/bin/pest --parallel --compact --group=browser' 84 | 85 | migrate-fresh: 86 | desc: Run migrations and seed the database 87 | aliases: [mf] 88 | cmd: 'php artisan migrate:fresh --seed' 89 | 90 | env-up: 91 | desc: Start the development environment 92 | aliases: [eu] 93 | cmd: 'docker compose --file docker-compose.env.yml up --detach' 94 | 95 | env-down: 96 | desc: Stop the development environment 97 | aliases: [ed] 98 | cmd: 'docker compose --file docker-compose.env.yml down --rmi all --volumes' 99 | 100 | dev: 101 | desc: Start the server 102 | aliases: [d] 103 | cmd: 'composer run-script dev' 104 | -------------------------------------------------------------------------------- /config/filesystems.php: -------------------------------------------------------------------------------- 1 | env('FILESYSTEM_DISK', 'local'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Filesystem Disks 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Below you may configure as many filesystem disks as necessary, and you 26 | | may even configure multiple disks for the same driver. Examples for 27 | | most supported storage drivers are configured here for reference. 28 | | 29 | | Supported drivers: "local", "ftp", "sftp", "s3" 30 | | 31 | */ 32 | 33 | 'disks' => [ 34 | 35 | 'local' => [ 36 | 'driver' => 'local', 37 | 'root' => storage_path('app/private'), 38 | 'serve' => true, 39 | 'throw' => false, 40 | 'report' => false, 41 | ], 42 | 43 | 'public' => [ 44 | 'driver' => 'local', 45 | 'root' => storage_path('app/public'), 46 | 'url' => env('APP_URL').'/storage', 47 | 'visibility' => 'public', 48 | 'throw' => false, 49 | 'report' => false, 50 | ], 51 | 52 | 's3' => [ 53 | 'driver' => 's3', 54 | 'key' => env('AWS_ACCESS_KEY_ID'), 55 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 56 | 'region' => env('AWS_DEFAULT_REGION'), 57 | 'bucket' => env('AWS_BUCKET'), 58 | 'url' => env('AWS_URL'), 59 | 'endpoint' => env('AWS_ENDPOINT'), 60 | 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), 61 | 'throw' => false, 62 | 'report' => false, 63 | ], 64 | 65 | ], 66 | 67 | /* 68 | |-------------------------------------------------------------------------- 69 | | Symbolic Links 70 | |-------------------------------------------------------------------------- 71 | | 72 | | Here you may configure the symbolic links that will be created when the 73 | | `storage:link` Artisan command is executed. The array keys should be 74 | | the locations of the links and the values should be their targets. 75 | | 76 | */ 77 | 78 | 'links' => [ 79 | public_path('storage') => storage_path('app/public'), 80 | ], 81 | 82 | ]; 83 | -------------------------------------------------------------------------------- /public/css/filament/support/support.css: -------------------------------------------------------------------------------- 1 | .fi-pagination-items,.fi-pagination-overview,.fi-pagination-records-per-page-select:not(.fi-compact){display:none}@supports (container-type:inline-size){.fi-pagination{container-type:inline-size}@container (min-width: 28rem){.fi-pagination-records-per-page-select.fi-compact{display:none}.fi-pagination-records-per-page-select:not(.fi-compact){display:inline}}@container (min-width: 56rem){.fi-pagination:not(.fi-simple)>.fi-pagination-previous-btn{display:none}.fi-pagination-overview{display:inline}.fi-pagination:not(.fi-simple)>.fi-pagination-next-btn{display:none}.fi-pagination-items{display:flex}}}@supports not (container-type:inline-size){@media (min-width:640px){.fi-pagination-records-per-page-select.fi-compact{display:none}.fi-pagination-records-per-page-select:not(.fi-compact){display:inline}}@media (min-width:768px){.fi-pagination:not(.fi-simple)>.fi-pagination-previous-btn{display:none}.fi-pagination-overview{display:inline}.fi-pagination:not(.fi-simple)>.fi-pagination-next-btn{display:none}.fi-pagination-items{display:flex}}}.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{background-color:#333;border-radius:4px;color:#fff;font-size:14px;line-height:1.4;outline:0;position:relative;transition-property:transform,visibility,opacity;white-space:normal}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{border-top-color:initial;border-width:8px 8px 0;bottom:-7px;left:0;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:initial;border-width:0 8px 8px;left:0;top:-7px;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-left-color:initial;border-width:8px 0 8px 8px;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{border-right-color:initial;border-width:8px 8px 8px 0;left:-7px;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{color:#333;height:16px;width:16px}.tippy-arrow:before{border-color:transparent;border-style:solid;content:"";position:absolute}.tippy-content{padding:5px 9px;position:relative;z-index:1}.tippy-box[data-theme~=light]{background-color:#fff;box-shadow:0 0 20px 4px #9aa1b126,0 4px 80px -8px #24282f40,0 4px 4px -2px #5b5e6926;color:#26323d}.tippy-box[data-theme~=light][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.tippy-box[data-theme~=light][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff}.tippy-box[data-theme~=light][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.tippy-box[data-theme~=light][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff}.tippy-box[data-theme~=light]>.tippy-backdrop{background-color:#fff}.tippy-box[data-theme~=light]>.tippy-svg-arrow{fill:#fff}.fi-sortable-ghost{opacity:.3} -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | registerTelescope(); 25 | $this->registerDebugbar(); 26 | } 27 | 28 | /** 29 | * Bootstrap any application services. 30 | */ 31 | public function boot(): void 32 | { 33 | $this->configureCommands(); 34 | $this->configureDatabase(); 35 | $this->configureDates(); 36 | $this->configureVite(); 37 | $this->configureUrl(); 38 | $this->configureHttp(); 39 | } 40 | 41 | /** 42 | * Configure the application's commands 43 | */ 44 | private function configureCommands(): void 45 | { 46 | DB::prohibitDestructiveCommands($this->app->isProduction()); 47 | } 48 | 49 | /** 50 | * Configure the application's models 51 | */ 52 | private function configureDatabase(): void 53 | { 54 | Model::shouldBeStrict(! $this->app->isProduction()); 55 | Model::automaticallyEagerLoadRelationships(); 56 | } 57 | 58 | /** 59 | * Configure the application's Vite 60 | */ 61 | private function configureVite(): void 62 | { 63 | Vite::useAggressivePrefetching(); 64 | } 65 | 66 | /** 67 | * Configure the dates. 68 | */ 69 | private function configureDates(): void 70 | { 71 | Date::use(CarbonImmutable::class); 72 | } 73 | 74 | /** 75 | * Configure the application's URL 76 | */ 77 | private function configureUrl(): void 78 | { 79 | URL::forceHttps($this->app->isProduction()); 80 | } 81 | 82 | /** 83 | * Configure Http module 84 | */ 85 | private function configureHttp(): void 86 | { 87 | Http::preventStrayRequests(); 88 | } 89 | 90 | private function registerTelescope(): void 91 | { 92 | if (app()->isLocal() && class_exists(\Laravel\Telescope\TelescopeServiceProvider::class)) { 93 | $this->app->register(TelescopeServiceProvider::class); 94 | } 95 | } 96 | 97 | private function registerDebugbar(): void 98 | { 99 | if (app()->isLocal() 100 | && app()->hasDebugModeEnabled() 101 | && class_exists(\Barryvdh\Debugbar\ServiceProvider::class) 102 | && config('debugbar.enabled') 103 | ) { 104 | Log::notice('Registering Debugbar Service Provider'); 105 | $this->app->register(\Barryvdh\Debugbar\ServiceProvider::class); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /public/js/filament/tables/components/table.js: -------------------------------------------------------------------------------- 1 | function n(){return{checkboxClickController:null,collapsedGroups:[],isLoading:!1,selectedRecords:[],shouldCheckUniqueSelection:!0,lastCheckedRecord:null,livewireId:null,init:function(){this.livewireId=this.$root.closest("[wire\\:id]").attributes["wire:id"].value,this.$wire.$on("deselectAllTableRecords",()=>this.deselectAllRecords()),this.$watch("selectedRecords",()=>{if(!this.shouldCheckUniqueSelection){this.shouldCheckUniqueSelection=!0;return}this.selectedRecords=[...new Set(this.selectedRecords)],this.shouldCheckUniqueSelection=!1}),this.$nextTick(()=>this.watchForCheckboxClicks()),Livewire.hook("element.init",({component:e})=>{e.id===this.livewireId&&this.watchForCheckboxClicks()})},mountAction:function(e,t=null){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableAction(e,t)},mountBulkAction:function(e){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableBulkAction(e)},toggleSelectRecordsOnPage:function(){let e=this.getRecordsOnPage();if(this.areRecordsSelected(e)){this.deselectRecords(e);return}this.selectRecords(e)},toggleSelectRecordsInGroup:async function(e){if(this.isLoading=!0,this.areRecordsSelected(this.getRecordsInGroupOnPage(e))){this.deselectRecords(await this.$wire.getGroupedSelectableTableRecordKeys(e));return}this.selectRecords(await this.$wire.getGroupedSelectableTableRecordKeys(e)),this.isLoading=!1},getRecordsInGroupOnPage:function(e){let t=[];for(let s of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])s.dataset.group===e&&t.push(s.value);return t},getRecordsOnPage:function(){let e=[];for(let t of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])e.push(t.value);return e},selectRecords:function(e){for(let t of e)this.isRecordSelected(t)||this.selectedRecords.push(t)},deselectRecords:function(e){for(let t of e){let s=this.selectedRecords.indexOf(t);s!==-1&&this.selectedRecords.splice(s,1)}},selectAllRecords:async function(){this.isLoading=!0,this.selectedRecords=await this.$wire.getAllSelectableTableRecordKeys(),this.isLoading=!1},deselectAllRecords:function(){this.selectedRecords=[]},isRecordSelected:function(e){return this.selectedRecords.includes(e)},areRecordsSelected:function(e){return e.every(t=>this.isRecordSelected(t))},toggleCollapseGroup:function(e){if(this.isGroupCollapsed(e)){this.collapsedGroups.splice(this.collapsedGroups.indexOf(e),1);return}this.collapsedGroups.push(e)},isGroupCollapsed:function(e){return this.collapsedGroups.includes(e)},resetCollapsedGroups:function(){this.collapsedGroups=[]},watchForCheckboxClicks:function(){this.checkboxClickController&&this.checkboxClickController.abort(),this.checkboxClickController=new AbortController;let{signal:e}=this.checkboxClickController;this.$root?.addEventListener("click",t=>t.target?.matches(".fi-ta-record-checkbox")&&this.handleCheckboxClick(t,t.target),{signal:e})},handleCheckboxClick:function(e,t){if(!this.lastChecked){this.lastChecked=t;return}if(e.shiftKey){let s=Array.from(this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[]);if(!s.includes(this.lastChecked)){this.lastChecked=t;return}let o=s.indexOf(this.lastChecked),r=s.indexOf(t),l=[o,r].sort((i,d)=>i-d),c=[];for(let i=l[0];i<=l[1];i++)s[i].checked=t.checked,c.push(s[i].value);t.checked?this.selectRecords(c):this.deselectRecords(c)}this.lastChecked=t}}}export{n as default}; 2 | -------------------------------------------------------------------------------- /config/cache.php: -------------------------------------------------------------------------------- 1 | env('CACHE_STORE', 'database'), 21 | 22 | /* 23 | |-------------------------------------------------------------------------- 24 | | Cache Stores 25 | |-------------------------------------------------------------------------- 26 | | 27 | | Here you may define all of the cache "stores" for your application as 28 | | well as their drivers. You may even define multiple stores for the 29 | | same cache driver to group types of items stored in your caches. 30 | | 31 | | Supported drivers: "array", "database", "file", "memcached", 32 | | "redis", "dynamodb", "octane", "null" 33 | | 34 | */ 35 | 36 | 'stores' => [ 37 | 38 | 'array' => [ 39 | 'driver' => 'array', 40 | 'serialize' => false, 41 | ], 42 | 43 | 'database' => [ 44 | 'driver' => 'database', 45 | 'connection' => env('DB_CACHE_CONNECTION'), 46 | 'table' => env('DB_CACHE_TABLE', 'cache'), 47 | 'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'), 48 | 'lock_table' => env('DB_CACHE_LOCK_TABLE'), 49 | ], 50 | 51 | 'file' => [ 52 | 'driver' => 'file', 53 | 'path' => storage_path('framework/cache/data'), 54 | 'lock_path' => storage_path('framework/cache/data'), 55 | ], 56 | 57 | 'memcached' => [ 58 | 'driver' => 'memcached', 59 | 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), 60 | 'sasl' => [ 61 | env('MEMCACHED_USERNAME'), 62 | env('MEMCACHED_PASSWORD'), 63 | ], 64 | 'options' => [ 65 | // Memcached::OPT_CONNECT_TIMEOUT => 2000, 66 | ], 67 | 'servers' => [ 68 | [ 69 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 70 | 'port' => env('MEMCACHED_PORT', 11211), 71 | 'weight' => 100, 72 | ], 73 | ], 74 | ], 75 | 76 | 'redis' => [ 77 | 'driver' => 'redis', 78 | 'connection' => env('REDIS_CACHE_CONNECTION', 'cache'), 79 | 'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'), 80 | ], 81 | 82 | 'dynamodb' => [ 83 | 'driver' => 'dynamodb', 84 | 'key' => env('AWS_ACCESS_KEY_ID'), 85 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 86 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 87 | 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), 88 | 'endpoint' => env('DYNAMODB_ENDPOINT'), 89 | ], 90 | 91 | 'octane' => [ 92 | 'driver' => 'octane', 93 | ], 94 | 95 | ], 96 | 97 | /* 98 | |-------------------------------------------------------------------------- 99 | | Cache Key Prefix 100 | |-------------------------------------------------------------------------- 101 | | 102 | | When utilizing the APC, database, memcached, Redis, and DynamoDB cache 103 | | stores, there might be other applications using the same cache. For 104 | | that reason, you may prefix every cache key to avoid collisions. 105 | | 106 | */ 107 | 108 | 'prefix' => env('CACHE_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-cache-'), 109 | 110 | ]; 111 | -------------------------------------------------------------------------------- /rector.php: -------------------------------------------------------------------------------- 1 | withPaths([ 28 | __DIR__.'/app', 29 | __DIR__.'/config', 30 | __DIR__.'/bootstrap/app.php', 31 | __DIR__.'/database', 32 | __DIR__.'/routes', 33 | __DIR__.'/public', 34 | __DIR__.'/tests', 35 | ]) 36 | ->withCache( 37 | cacheDirectory: __DIR__.'/.rector.cache', 38 | cacheClass: FileCacheStorage::class, 39 | ) 40 | ->withImportNames(importShortClasses: false, removeUnusedImports: true) 41 | ->withRootFiles() 42 | ->withBootstrapFiles([__DIR__.'/vendor/larastan/larastan/bootstrap.php']) 43 | ->withPHPStanConfigs([__DIR__.'/phpstan.neon']) 44 | ->withPreparedSets( 45 | deadCode: true, 46 | codeQuality: true, 47 | codingStyle: true, 48 | typeDeclarations: true, 49 | privatization: true, 50 | instanceOf: true, 51 | earlyReturn: true, 52 | strictBooleans: true, 53 | carbon: true, 54 | rectorPreset: true, 55 | ) 56 | ->withPhpSets(php84: true) 57 | ->withRules([ 58 | ValidationRuleArrayStringValueToArrayRector::class, 59 | AnonymousMigrationsRector::class, 60 | AssertStatusToAssertMethodRector::class, 61 | AddExtendsAnnotationToModelFactoriesRector::class, 62 | AddGenericReturnTypeToRelationsRector::class, 63 | ApplyDefaultInsteadOfNullCoalesceRector::class, 64 | DispatchToHelperFunctionsRector::class, 65 | EmptyToBlankAndFilledFuncRector::class, 66 | ModelCastsPropertyToCastsMethodRector::class, 67 | NotFilledBlankFuncCallToBlankFilledFuncCallRector::class, 68 | RefactorBlueprintGeometryColumnsRector::class, 69 | ReplaceExpectsMethodsInTestsRector::class, 70 | ReplaceFakerInstanceWithHelperRector::class, 71 | ]) 72 | ->withSets([ 73 | LaravelSetList::LARAVEL_ARRAYACCESS_TO_METHOD_CALL, 74 | LaravelSetList::LARAVEL_ARRAY_STR_FUNCTION_TO_STATIC_CALL, 75 | LaravelSetList::LARAVEL_CODE_QUALITY, 76 | LaravelSetList::LARAVEL_COLLECTION, 77 | LaravelSetList::LARAVEL_CONTAINER_STRING_TO_FULLY_QUALIFIED_NAME, 78 | LaravelSetList::LARAVEL_ELOQUENT_MAGIC_METHOD_TO_QUERY_BUILDER, 79 | LaravelSetList::LARAVEL_FACADE_ALIASES_TO_FULL_NAMES, 80 | LaravelSetList::LARAVEL_IF_HELPERS, 81 | LaravelSetList::LARAVEL_LEGACY_FACTORIES_TO_CLASSES, 82 | ]) 83 | ->withSkip([ 84 | AddOverrideAttributeToOverriddenMethodsRector::class, 85 | ChangeOrIfContinueToMultiContinueRector::class, 86 | PostIncDecToPreIncDecRector::class, 87 | AddArrowFunctionReturnTypeRector::class, 88 | ]); 89 | -------------------------------------------------------------------------------- /config/mail.php: -------------------------------------------------------------------------------- 1 | env('MAIL_MAILER', 'log'), 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Mailer Configurations 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Here you may configure all of the mailers used by your application plus 27 | | their respective settings. Several examples have been configured for 28 | | you and you are free to add your own as your application requires. 29 | | 30 | | Laravel supports a variety of mail "transport" drivers that can be used 31 | | when delivering an email. You may specify which one you're using for 32 | | your mailers below. You may also add additional mailers if needed. 33 | | 34 | | Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2", 35 | | "postmark", "resend", "log", "array", 36 | | "failover", "roundrobin" 37 | | 38 | */ 39 | 40 | 'mailers' => [ 41 | 42 | 'smtp' => [ 43 | 'transport' => 'smtp', 44 | 'scheme' => env('MAIL_SCHEME'), 45 | 'url' => env('MAIL_URL'), 46 | 'host' => env('MAIL_HOST', '127.0.0.1'), 47 | 'port' => env('MAIL_PORT', 2525), 48 | 'username' => env('MAIL_USERNAME'), 49 | 'password' => env('MAIL_PASSWORD'), 50 | 'timeout' => null, 51 | 'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url((string) env('APP_URL', 'http://localhost'), PHP_URL_HOST)), 52 | ], 53 | 54 | 'ses' => [ 55 | 'transport' => 'ses', 56 | ], 57 | 58 | 'postmark' => [ 59 | 'transport' => 'postmark', 60 | // 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'), 61 | // 'client' => [ 62 | // 'timeout' => 5, 63 | // ], 64 | ], 65 | 66 | 'resend' => [ 67 | 'transport' => 'resend', 68 | ], 69 | 70 | 'sendmail' => [ 71 | 'transport' => 'sendmail', 72 | 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'), 73 | ], 74 | 75 | 'log' => [ 76 | 'transport' => 'log', 77 | 'channel' => env('MAIL_LOG_CHANNEL'), 78 | ], 79 | 80 | 'array' => [ 81 | 'transport' => 'array', 82 | ], 83 | 84 | 'failover' => [ 85 | 'transport' => 'failover', 86 | 'mailers' => [ 87 | 'smtp', 88 | 'log', 89 | ], 90 | 'retry_after' => 60, 91 | ], 92 | 93 | 'roundrobin' => [ 94 | 'transport' => 'roundrobin', 95 | 'mailers' => [ 96 | 'ses', 97 | 'postmark', 98 | ], 99 | 'retry_after' => 60, 100 | ], 101 | 102 | ], 103 | 104 | /* 105 | |-------------------------------------------------------------------------- 106 | | Global "From" Address 107 | |-------------------------------------------------------------------------- 108 | | 109 | | You may wish for all emails sent by your application to be sent from 110 | | the same address. Here you may specify a name and address that is 111 | | used globally for all emails that are sent by your application. 112 | | 113 | */ 114 | 115 | 'from' => [ 116 | 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), 117 | 'name' => env('MAIL_FROM_NAME', 'Example'), 118 | ], 119 | 120 | ]; 121 | -------------------------------------------------------------------------------- /config/queue.php: -------------------------------------------------------------------------------- 1 | env('QUEUE_CONNECTION', 'database'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Queue Connections 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may configure the connection options for every queue backend 26 | | used by your application. An example configuration is provided for 27 | | each backend supported by Laravel. You're also free to add more. 28 | | 29 | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null" 30 | | 31 | */ 32 | 33 | 'connections' => [ 34 | 35 | 'sync' => [ 36 | 'driver' => 'sync', 37 | ], 38 | 39 | 'database' => [ 40 | 'driver' => 'database', 41 | 'connection' => env('DB_QUEUE_CONNECTION'), 42 | 'table' => env('DB_QUEUE_TABLE', 'jobs'), 43 | 'queue' => env('DB_QUEUE', 'default'), 44 | 'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90), 45 | 'after_commit' => false, 46 | ], 47 | 48 | 'beanstalkd' => [ 49 | 'driver' => 'beanstalkd', 50 | 'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'), 51 | 'queue' => env('BEANSTALKD_QUEUE', 'default'), 52 | 'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90), 53 | 'block_for' => 0, 54 | 'after_commit' => false, 55 | ], 56 | 57 | 'sqs' => [ 58 | 'driver' => 'sqs', 59 | 'key' => env('AWS_ACCESS_KEY_ID'), 60 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 61 | 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), 62 | 'queue' => env('SQS_QUEUE', 'default'), 63 | 'suffix' => env('SQS_SUFFIX'), 64 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 65 | 'after_commit' => false, 66 | ], 67 | 68 | 'redis' => [ 69 | 'driver' => 'redis', 70 | 'connection' => env('REDIS_QUEUE_CONNECTION', 'default'), 71 | 'queue' => env('REDIS_QUEUE', 'default'), 72 | 'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90), 73 | 'block_for' => null, 74 | 'after_commit' => false, 75 | ], 76 | 77 | ], 78 | 79 | /* 80 | |-------------------------------------------------------------------------- 81 | | Job Batching 82 | |-------------------------------------------------------------------------- 83 | | 84 | | The following options configure the database and table that store job 85 | | batching information. These options can be updated to any database 86 | | connection and table which has been defined by your application. 87 | | 88 | */ 89 | 90 | 'batching' => [ 91 | 'database' => env('DB_CONNECTION', 'sqlite'), 92 | 'table' => 'job_batches', 93 | ], 94 | 95 | /* 96 | |-------------------------------------------------------------------------- 97 | | Failed Queue Jobs 98 | |-------------------------------------------------------------------------- 99 | | 100 | | These options configure the behavior of failed queue job logging so you 101 | | can control how and where failed jobs are stored. Laravel ships with 102 | | support for storing failed jobs in a simple file or in a database. 103 | | 104 | | Supported drivers: "database-uuids", "dynamodb", "file", "null" 105 | | 106 | */ 107 | 108 | 'failed' => [ 109 | 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), 110 | 'database' => env('DB_CONNECTION', 'sqlite'), 111 | 'table' => 'failed_jobs', 112 | ], 113 | 114 | ]; 115 | -------------------------------------------------------------------------------- /config/auth.php: -------------------------------------------------------------------------------- 1 | [ 21 | 'guard' => env('AUTH_GUARD', 'web'), 22 | 'passwords' => env('AUTH_PASSWORD_BROKER', 'users'), 23 | ], 24 | 25 | /* 26 | |-------------------------------------------------------------------------- 27 | | Authentication Guards 28 | |-------------------------------------------------------------------------- 29 | | 30 | | Next, you may define every authentication guard for your application. 31 | | Of course, a great default configuration has been defined for you 32 | | which utilizes session storage plus the Eloquent user provider. 33 | | 34 | | All authentication guards have a user provider, which defines how the 35 | | users are actually retrieved out of your database or other storage 36 | | system used by the application. Typically, Eloquent is utilized. 37 | | 38 | | Supported: "session" 39 | | 40 | */ 41 | 42 | 'guards' => [ 43 | 'web' => [ 44 | 'driver' => 'session', 45 | 'provider' => 'users', 46 | ], 47 | ], 48 | 49 | /* 50 | |-------------------------------------------------------------------------- 51 | | User Providers 52 | |-------------------------------------------------------------------------- 53 | | 54 | | All authentication guards have a user provider, which defines how the 55 | | users are actually retrieved out of your database or other storage 56 | | system used by the application. Typically, Eloquent is utilized. 57 | | 58 | | If you have multiple user tables or models you may configure multiple 59 | | providers to represent the model / table. These providers may then 60 | | be assigned to any extra authentication guards you have defined. 61 | | 62 | | Supported: "database", "eloquent" 63 | | 64 | */ 65 | 66 | 'providers' => [ 67 | 'users' => [ 68 | 'driver' => 'eloquent', 69 | 'model' => env('AUTH_MODEL', User::class), 70 | ], 71 | 72 | // 'users' => [ 73 | // 'driver' => 'database', 74 | // 'table' => 'users', 75 | // ], 76 | ], 77 | 78 | /* 79 | |-------------------------------------------------------------------------- 80 | | Resetting Passwords 81 | |-------------------------------------------------------------------------- 82 | | 83 | | These configuration options specify the behavior of Laravel's password 84 | | reset functionality, including the table utilized for token storage 85 | | and the user provider that is invoked to actually retrieve users. 86 | | 87 | | The expiry time is the number of minutes that each reset token will be 88 | | considered valid. This security feature keeps tokens short-lived so 89 | | they have less time to be guessed. You may change this as needed. 90 | | 91 | | The throttle setting is the number of seconds a user must wait before 92 | | generating more password reset tokens. This prevents the user from 93 | | quickly generating a very large amount of password reset tokens. 94 | | 95 | */ 96 | 97 | 'passwords' => [ 98 | 'users' => [ 99 | 'provider' => 'users', 100 | 'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'), 101 | 'expire' => 60, 102 | 'throttle' => 60, 103 | ], 104 | ], 105 | 106 | /* 107 | |-------------------------------------------------------------------------- 108 | | Password Confirmation Timeout 109 | |-------------------------------------------------------------------------- 110 | | 111 | | Here you may define the number of seconds before a password confirmation 112 | | window expires and users are asked to re-enter their password via the 113 | | confirmation screen. By default, the timeout lasts for three hours. 114 | | 115 | */ 116 | 117 | 'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800), 118 | 119 | ]; 120 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://getcomposer.org/schema.json", 3 | "name": "gvieira18/sycorax", 4 | "type": "project", 5 | "description": "", 6 | "keywords": [ 7 | "laravel" 8 | ], 9 | "license": "MIT", 10 | "require": { 11 | "php": "^8.4", 12 | "filament/filament": "^4.0.20", 13 | "filament/spatie-laravel-media-library-plugin": "^4.0.20", 14 | "laravel/framework": "^12.31.1", 15 | "laravel/tinker": "^2.10.1" 16 | }, 17 | "require-dev": { 18 | "barryvdh/laravel-debugbar": "^3.16.0", 19 | "barryvdh/laravel-ide-helper": "^3.6.0", 20 | "driftingly/rector-laravel": "^2.0.7", 21 | "fakerphp/faker": "^1.24.1", 22 | "larastan/larastan": "^3.7.2", 23 | "laravel/pail": "^1.2.3", 24 | "laravel/pint": "^1.25.1", 25 | "laravel/sail": "^1.46.0", 26 | "laravel/telescope": "^5.12.0", 27 | "mockery/mockery": "^1.6.12", 28 | "nunomaduro/collision": "^8.8.2", 29 | "pestphp/pest": "~4.0.4", 30 | "pestphp/pest-plugin-browser": "~4.0.3", 31 | "pestphp/pest-plugin-faker": "^4.0.0", 32 | "pestphp/pest-plugin-laravel": "^4.0.0", 33 | "pestphp/pest-plugin-livewire": "^4.0.1", 34 | "rector/rector": "^2.1.7" 35 | }, 36 | "autoload": { 37 | "psr-4": { 38 | "App\\": "app/", 39 | "Database\\Factories\\": "database/factories/", 40 | "Database\\Seeders\\": "database/seeders/" 41 | } 42 | }, 43 | "autoload-dev": { 44 | "psr-4": { 45 | "Tests\\": "tests/" 46 | } 47 | }, 48 | "scripts": { 49 | "post-autoload-dump": [ 50 | "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", 51 | "@php artisan package:discover --ansi", 52 | "@php artisan filament:upgrade" 53 | ], 54 | "post-update-cmd": [ 55 | "@php artisan vendor:publish --tag=laravel-assets --ansi --force" 56 | ], 57 | "post-root-package-install": [ 58 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" 59 | ], 60 | "post-create-project-cmd": [ 61 | "@php artisan key:generate --ansi", 62 | "@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"", 63 | "@php artisan migrate --graceful --ansi" 64 | ], 65 | "ide-helper": [ 66 | "@php artisan ide-helper:generate -q", 67 | "@php artisan ide-helper:meta -q" 68 | ], 69 | "setup": [ 70 | "@php composer run post-root-package-install", 71 | "@php composer run post-create-project-cmd", 72 | "@php artisan storage:link --ansi", 73 | "@php composer run ide-helper" 74 | ], 75 | "dev": [ 76 | "Composer\\Config::disableProcessTimeout", 77 | "npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite" 78 | ], 79 | "pint": "vendor/bin/pint --parallel", 80 | "test:pint": "vendor/bin/pint --test --parallel", 81 | "rector": "vendor/bin/rector", 82 | "test:rector": "vendor/bin/rector --dry-run", 83 | "phpstan": "vendor/bin/phpstan analyse --ansi", 84 | "test:phpstan": "vendor/bin/phpstan analyse --ansi", 85 | "test:unit": "vendor/bin/pest --parallel --compact --group=unit", 86 | "test:feature": "vendor/bin/pest --parallel --compact --group=feature", 87 | "test:browser": "vendor/bin/pest --parallel --compact --group=browser", 88 | "test:cov": "vendor/bin/pest --parallel --compact --coverage", 89 | "check": [ 90 | "@test:rector", 91 | "@test:pint", 92 | "@test:phpstan" 93 | ], 94 | "test": [ 95 | "@php artisan config:clear --ansi", 96 | "vendor/bin/pest --parallel --compact" 97 | ] 98 | }, 99 | "extra": { 100 | "laravel": { 101 | "dont-discover": [ 102 | "laravel/telescope", 103 | "barryvdh/laravel-debugbar" 104 | ] 105 | } 106 | }, 107 | "config": { 108 | "optimize-autoloader": true, 109 | "classmap-authoritative": true, 110 | "bump-after-update": true, 111 | "preferred-install": "dist", 112 | "sort-packages": true, 113 | "allow-plugins": { 114 | "pestphp/pest-plugin": true, 115 | "php-http/discovery": true 116 | } 117 | }, 118 | "minimum-stability": "stable", 119 | "prefer-stable": true 120 | } 121 | -------------------------------------------------------------------------------- /config/app.php: -------------------------------------------------------------------------------- 1 | env('APP_NAME', 'Laravel'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Application Environment 23 | |-------------------------------------------------------------------------- 24 | | 25 | | This value determines the "environment" your application is currently 26 | | running in. This may determine how you prefer to configure various 27 | | services the application utilizes. Set this in your ".env" file. 28 | | 29 | */ 30 | 31 | 'env' => env('APP_ENV', 'production'), 32 | 33 | /* 34 | |-------------------------------------------------------------------------- 35 | | Application Debug Mode 36 | |-------------------------------------------------------------------------- 37 | | 38 | | When your application is in debug mode, detailed error messages with 39 | | stack traces will be shown on every error that occurs within your 40 | | application. If disabled, a simple generic error page is shown. 41 | | 42 | */ 43 | 44 | 'debug' => (bool) env('APP_DEBUG', false), 45 | 46 | /* 47 | |-------------------------------------------------------------------------- 48 | | Application URL 49 | |-------------------------------------------------------------------------- 50 | | 51 | | This URL is used by the console to properly generate URLs when using 52 | | the Artisan command line tool. You should set this to the root of 53 | | the application so that it's available within Artisan commands. 54 | | 55 | */ 56 | 57 | 'url' => env('APP_URL', 'http://localhost'), 58 | 59 | /* 60 | |-------------------------------------------------------------------------- 61 | | Application Timezone 62 | |-------------------------------------------------------------------------- 63 | | 64 | | Here you may specify the default timezone for your application, which 65 | | will be used by the PHP date and date-time functions. The timezone 66 | | is set to "UTC" by default as it is suitable for most use cases. 67 | | 68 | */ 69 | 70 | 'timezone' => env('APP_TIMEZONE', 'UTC'), 71 | 72 | /* 73 | |-------------------------------------------------------------------------- 74 | | Application Locale Configuration 75 | |-------------------------------------------------------------------------- 76 | | 77 | | The application locale determines the default locale that will be used 78 | | by Laravel's translation / localization methods. This option can be 79 | | set to any locale for which you plan to have translation strings. 80 | | 81 | */ 82 | 83 | 'locale' => env('APP_LOCALE', 'en'), 84 | 85 | 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'), 86 | 87 | 'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'), 88 | 89 | /* 90 | |-------------------------------------------------------------------------- 91 | | Encryption Key 92 | |-------------------------------------------------------------------------- 93 | | 94 | | This key is utilized by Laravel's encryption services and should be set 95 | | to a random, 32 character string to ensure that all encrypted values 96 | | are secure. You should do this prior to deploying the application. 97 | | 98 | */ 99 | 100 | 'cipher' => 'AES-256-CBC', 101 | 102 | 'key' => env('APP_KEY'), 103 | 104 | 'previous_keys' => [ 105 | ...array_filter( 106 | explode(',', (string) env('APP_PREVIOUS_KEYS', '')) 107 | ), 108 | ], 109 | 110 | /* 111 | |-------------------------------------------------------------------------- 112 | | Maintenance Mode Driver 113 | |-------------------------------------------------------------------------- 114 | | 115 | | These configuration options determine the driver used to determine and 116 | | manage Laravel's "maintenance mode" status. The "cache" driver will 117 | | allow maintenance mode to be controlled across multiple machines. 118 | | 119 | | Supported drivers: "file", "cache" 120 | | 121 | */ 122 | 123 | 'maintenance' => [ 124 | 'driver' => env('APP_MAINTENANCE_DRIVER', 'file'), 125 | 'store' => env('APP_MAINTENANCE_STORE', 'database'), 126 | ], 127 | 128 | ]; 129 | -------------------------------------------------------------------------------- /config/logging.php: -------------------------------------------------------------------------------- 1 | env('LOG_CHANNEL', 'stack'), 24 | 25 | /* 26 | |-------------------------------------------------------------------------- 27 | | Deprecations Log Channel 28 | |-------------------------------------------------------------------------- 29 | | 30 | | This option controls the log channel that should be used to log warnings 31 | | regarding deprecated PHP and library features. This allows you to get 32 | | your application ready for upcoming major versions of dependencies. 33 | | 34 | */ 35 | 36 | 'deprecations' => [ 37 | 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), 38 | 'trace' => env('LOG_DEPRECATIONS_TRACE', false), 39 | ], 40 | 41 | /* 42 | |-------------------------------------------------------------------------- 43 | | Log Channels 44 | |-------------------------------------------------------------------------- 45 | | 46 | | Here you may configure the log channels for your application. Laravel 47 | | utilizes the Monolog PHP logging library, which includes a variety 48 | | of powerful log handlers and formatters that you're free to use. 49 | | 50 | | Available drivers: "single", "daily", "slack", "syslog", 51 | | "errorlog", "monolog", "custom", "stack" 52 | | 53 | */ 54 | 55 | 'channels' => [ 56 | 57 | 'stack' => [ 58 | 'driver' => 'stack', 59 | 'channels' => explode(',', (string) env('LOG_STACK', 'single')), 60 | 'ignore_exceptions' => false, 61 | ], 62 | 63 | 'single' => [ 64 | 'driver' => 'single', 65 | 'path' => storage_path('logs/laravel.log'), 66 | 'level' => env('LOG_LEVEL', 'debug'), 67 | 'replace_placeholders' => true, 68 | ], 69 | 70 | 'daily' => [ 71 | 'driver' => 'daily', 72 | 'path' => storage_path('logs/laravel.log'), 73 | 'level' => env('LOG_LEVEL', 'debug'), 74 | 'days' => env('LOG_DAILY_DAYS', 14), 75 | 'replace_placeholders' => true, 76 | ], 77 | 78 | 'slack' => [ 79 | 'driver' => 'slack', 80 | 'url' => env('LOG_SLACK_WEBHOOK_URL'), 81 | 'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'), 82 | 'emoji' => env('LOG_SLACK_EMOJI', ':boom:'), 83 | 'level' => env('LOG_LEVEL', 'critical'), 84 | 'replace_placeholders' => true, 85 | ], 86 | 87 | 'papertrail' => [ 88 | 'driver' => 'monolog', 89 | 'level' => env('LOG_LEVEL', 'debug'), 90 | 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class), 91 | 'handler_with' => [ 92 | 'host' => env('PAPERTRAIL_URL'), 93 | 'port' => env('PAPERTRAIL_PORT'), 94 | 'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'), 95 | ], 96 | 'processors' => [PsrLogMessageProcessor::class], 97 | ], 98 | 99 | 'stderr' => [ 100 | 'driver' => 'monolog', 101 | 'level' => env('LOG_LEVEL', 'debug'), 102 | 'handler' => StreamHandler::class, 103 | 'handler_with' => [ 104 | 'stream' => 'php://stderr', 105 | ], 106 | 'formatter' => env('LOG_STDERR_FORMATTER'), 107 | 'processors' => [PsrLogMessageProcessor::class], 108 | ], 109 | 110 | 'syslog' => [ 111 | 'driver' => 'syslog', 112 | 'level' => env('LOG_LEVEL', 'debug'), 113 | 'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER), 114 | 'replace_placeholders' => true, 115 | ], 116 | 117 | 'errorlog' => [ 118 | 'driver' => 'errorlog', 119 | 'level' => env('LOG_LEVEL', 'debug'), 120 | 'replace_placeholders' => true, 121 | ], 122 | 123 | 'null' => [ 124 | 'driver' => 'monolog', 125 | 'handler' => NullHandler::class, 126 | ], 127 | 128 | 'emergency' => [ 129 | 'path' => storage_path('logs/laravel.log'), 130 | ], 131 | 132 | ], 133 | 134 | ]; 135 | -------------------------------------------------------------------------------- /public/js/filament/notifications/notifications.js: -------------------------------------------------------------------------------- 1 | (()=>{var O=Object.create;var N=Object.defineProperty;var V=Object.getOwnPropertyDescriptor;var Y=Object.getOwnPropertyNames;var H=Object.getPrototypeOf,W=Object.prototype.hasOwnProperty;var d=(i,t)=>()=>(t||i((t={exports:{}}).exports,t),t.exports);var j=(i,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Y(t))!W.call(i,n)&&n!==e&&N(i,n,{get:()=>t[n],enumerable:!(s=V(t,n))||s.enumerable});return i};var J=(i,t,e)=>(e=i!=null?O(H(i)):{},j(t||!i||!i.__esModule?N(e,"default",{value:i,enumerable:!0}):e,i));var S=d((ut,_)=>{var v,g=typeof global<"u"&&(global.crypto||global.msCrypto);g&&g.getRandomValues&&(y=new Uint8Array(16),v=function(){return g.getRandomValues(y),y});var y;v||(T=new Array(16),v=function(){for(var i=0,t;i<16;i++)(i&3)===0&&(t=Math.random()*4294967296),T[i]=t>>>((i&3)<<3)&255;return T});var T;_.exports=v});var C=d((ct,U)=>{var P=[];for(f=0;f<256;++f)P[f]=(f+256).toString(16).substr(1);var f;function K(i,t){var e=t||0,s=P;return s[i[e++]]+s[i[e++]]+s[i[e++]]+s[i[e++]]+"-"+s[i[e++]]+s[i[e++]]+"-"+s[i[e++]]+s[i[e++]]+"-"+s[i[e++]]+s[i[e++]]+"-"+s[i[e++]]+s[i[e++]]+s[i[e++]]+s[i[e++]]+s[i[e++]]+s[i[e++]]}U.exports=K});var R=d((lt,F)=>{var Q=S(),X=C(),a=Q(),Z=[a[0]|1,a[1],a[2],a[3],a[4],a[5]],b=(a[6]<<8|a[7])&16383,D=0,A=0;function tt(i,t,e){var s=t&&e||0,n=t||[];i=i||{};var r=i.clockseq!==void 0?i.clockseq:b,o=i.msecs!==void 0?i.msecs:new Date().getTime(),h=i.nsecs!==void 0?i.nsecs:A+1,l=o-D+(h-A)/1e4;if(l<0&&i.clockseq===void 0&&(r=r+1&16383),(l<0||o>D)&&i.nsecs===void 0&&(h=0),h>=1e4)throw new Error("uuid.v1(): Can't create more than 10M uuids/sec");D=o,A=h,b=r,o+=122192928e5;var c=((o&268435455)*1e4+h)%4294967296;n[s++]=c>>>24&255,n[s++]=c>>>16&255,n[s++]=c>>>8&255,n[s++]=c&255;var u=o/4294967296*1e4&268435455;n[s++]=u>>>8&255,n[s++]=u&255,n[s++]=u>>>24&15|16,n[s++]=u>>>16&255,n[s++]=r>>>8|128,n[s++]=r&255;for(var $=i.node||Z,m=0;m<6;++m)n[s+m]=$[m];return t||X(n)}F.exports=tt});var G=d((dt,B)=>{var it=S(),et=C();function st(i,t,e){var s=t&&e||0;typeof i=="string"&&(t=i=="binary"?new Array(16):null,i=null),i=i||{};var n=i.random||(i.rng||it)();if(n[6]=n[6]&15|64,n[8]=n[8]&63|128,t)for(var r=0;r<16;++r)t[s+r]=n[r];return t||et(n)}B.exports=st});var M=d((ft,L)=>{var nt=R(),I=G(),E=I;E.v1=nt;E.v4=I;L.exports=E});function k(i,t=()=>{}){let e=!1;return function(){e?t.apply(this,arguments):(e=!0,i.apply(this,arguments))}}var q=i=>{i.data("notificationComponent",({notification:t})=>({isShown:!1,computedStyle:null,transitionDuration:null,transitionEasing:null,init(){this.computedStyle=window.getComputedStyle(this.$el),this.transitionDuration=parseFloat(this.computedStyle.transitionDuration)*1e3,this.transitionEasing=this.computedStyle.transitionTimingFunction,this.configureTransitions(),this.configureAnimations(),t.duration&&t.duration!=="persistent"&&setTimeout(()=>{if(!this.$el.matches(":hover")){this.close();return}this.$el.addEventListener("mouseleave",()=>this.close())},t.duration),this.isShown=!0},configureTransitions(){let e=this.computedStyle.display,s=()=>{i.mutateDom(()=>{this.$el.style.setProperty("display",e),this.$el.style.setProperty("visibility","visible")}),this.$el._x_isShown=!0},n=()=>{i.mutateDom(()=>{this.$el._x_isShown?this.$el.style.setProperty("visibility","hidden"):this.$el.style.setProperty("display","none")})},r=k(o=>o?s():n(),o=>{this.$el._x_toggleAndCascadeWithTransitions(this.$el,o,s,n)});i.effect(()=>r(this.isShown))},configureAnimations(){let e;Livewire.hook("commit",({component:s,commit:n,succeed:r,fail:o,respond:h})=>{s.snapshot.data.isFilamentNotificationsComponent&&requestAnimationFrame(()=>{let l=()=>this.$el.getBoundingClientRect().top,c=l();h(()=>{e=()=>{this.isShown&&this.$el.animate([{transform:`translateY(${c-l()}px)`},{transform:"translateY(0px)"}],{duration:this.transitionDuration,easing:this.transitionEasing})},this.$el.getAnimations().forEach(u=>u.finish())}),r(({snapshot:u,effect:$})=>{e()})})})},close(){this.isShown=!1,setTimeout(()=>window.dispatchEvent(new CustomEvent("notificationClosed",{detail:{id:t.id}})),this.transitionDuration)},markAsRead(){window.dispatchEvent(new CustomEvent("markedNotificationAsRead",{detail:{id:t.id}}))},markAsUnread(){window.dispatchEvent(new CustomEvent("markedNotificationAsUnread",{detail:{id:t.id}}))}}))};var z=J(M(),1),p=class{constructor(){return this.id((0,z.v4)()),this}id(t){return this.id=t,this}title(t){return this.title=t,this}body(t){return this.body=t,this}actions(t){return this.actions=t,this}status(t){return this.status=t,this}color(t){return this.color=t,this}icon(t){return this.icon=t,this}iconColor(t){return this.iconColor=t,this}duration(t){return this.duration=t,this}seconds(t){return this.duration(t*1e3),this}persistent(){return this.duration("persistent"),this}danger(){return this.status("danger"),this}info(){return this.status("info"),this}success(){return this.status("success"),this}warning(){return this.status("warning"),this}view(t){return this.view=t,this}viewData(t){return this.viewData=t,this}send(){return window.dispatchEvent(new CustomEvent("notificationSent",{detail:{notification:this}})),this}},w=class{constructor(t){return this.name(t),this}name(t){return this.name=t,this}color(t){return this.color=t,this}dispatch(t,e){return this.event(t),this.eventData(e),this}dispatchSelf(t,e){return this.dispatch(t,e),this.dispatchDirection="self",this}dispatchTo(t,e,s){return this.dispatch(e,s),this.dispatchDirection="to",this.dispatchToComponent=t,this}emit(t,e){return this.dispatch(t,e),this}emitSelf(t,e){return this.dispatchSelf(t,e),this}emitTo(t,e,s){return this.dispatchTo(t,e,s),this}dispatchDirection(t){return this.dispatchDirection=t,this}dispatchToComponent(t){return this.dispatchToComponent=t,this}event(t){return this.event=t,this}eventData(t){return this.eventData=t,this}extraAttributes(t){return this.extraAttributes=t,this}icon(t){return this.icon=t,this}iconPosition(t){return this.iconPosition=t,this}outlined(t=!0){return this.isOutlined=t,this}disabled(t=!0){return this.isDisabled=t,this}label(t){return this.label=t,this}close(t=!0){return this.shouldClose=t,this}openUrlInNewTab(t=!0){return this.shouldOpenUrlInNewTab=t,this}size(t){return this.size=t,this}url(t){return this.url=t,this}view(t){return this.view=t,this}button(){return this.view("filament::components.button.index"),this}grouped(){return this.view("filament::components.dropdown.list.item"),this}iconButton(){return this.view("filament::components.icon-button"),this}link(){return this.view("filament::components.link"),this}},x=class{constructor(t){return this.actions(t),this}actions(t){return this.actions=t.map(e=>e.grouped()),this}color(t){return this.color=t,this}icon(t){return this.icon=t,this}iconPosition(t){return this.iconPosition=t,this}label(t){return this.label=t,this}tooltip(t){return this.tooltip=t,this}};window.FilamentNotificationAction=w;window.FilamentNotificationActionGroup=x;window.FilamentNotification=p;document.addEventListener("alpine:init",()=>{window.Alpine.plugin(q)});})(); 2 | -------------------------------------------------------------------------------- /public/js/filament/tables/tables.js: -------------------------------------------------------------------------------- 1 | (()=>{var g=({canTrackDeselectedRecords:u,currentSelectionLivewireProperty:d,maxSelectableRecords:l,selectsCurrentPageOnly:r,$wire:s})=>({checkboxClickController:null,collapsedGroups:[],isLoading:!1,selectedRecords:new Set,deselectedRecords:new Set,isTrackingDeselectedRecords:!1,shouldCheckUniqueSelection:!0,lastCheckedRecord:null,livewireId:null,entangledSelectedRecords:d?s.$entangle(d):null,init(){this.livewireId=this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value,s.$on("deselectAllTableRecords",()=>this.deselectAllRecords()),s.$on("scrollToTopOfTable",()=>this.$root.scrollIntoView({block:"start",inline:"nearest"})),d&&(l!==1?this.selectedRecords=new Set(this.entangledSelectedRecords):this.selectedRecords=new Set(this.entangledSelectedRecords?[this.entangledSelectedRecords]:[])),this.$nextTick(()=>this.watchForCheckboxClicks()),Livewire.hook("element.init",({component:e})=>{e.id===this.livewireId&&this.watchForCheckboxClicks()})},mountAction(...e){s.set("isTrackingDeselectedTableRecords",this.isTrackingDeselectedRecords,!1),s.set("selectedTableRecords",[...this.selectedRecords],!1),s.set("deselectedTableRecords",[...this.deselectedRecords],!1),s.mountAction(...e)},toggleSelectRecordsOnPage(){let e=this.getRecordsOnPage();if(this.areRecordsSelected(e)){this.deselectRecords(e);return}this.selectRecords(e)},toggleSelectRecords(e){this.areRecordsSelected(e)?this.deselectRecords(e):this.selectRecords(e)},getSelectedRecordsCount(){return this.isTrackingDeselectedRecords?(this.$refs.allSelectableRecordsCount?.value??this.deselectedRecords.size)-this.deselectedRecords.size:this.selectedRecords.size},getRecordsOnPage(){let e=[];for(let t of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])e.push(t.value);return e},selectRecords(e){l===1&&(this.deselectAllRecords(),e=e.slice(0,1));for(let t of e)if(!this.isRecordSelected(t)){if(this.isTrackingDeselectedRecords){this.deselectedRecords.delete(t);continue}this.selectedRecords.add(t)}this.updatedSelectedRecords()},deselectRecords(e){for(let t of e){if(this.isTrackingDeselectedRecords){this.deselectedRecords.add(t);continue}this.selectedRecords.delete(t)}this.updatedSelectedRecords()},updatedSelectedRecords(){if(l!==1){this.entangledSelectedRecords=[...this.selectedRecords];return}this.entangledSelectedRecords=[...this.selectedRecords][0]??null},toggleSelectedRecord(e){if(this.isRecordSelected(e)){this.deselectRecords([e]);return}this.selectRecords([e])},async selectAllRecords(){if(!u||r){this.isLoading=!0,this.selectedRecords=new Set(await s.getAllSelectableTableRecordKeys()),this.updatedSelectedRecords(),this.isLoading=!1;return}this.isTrackingDeselectedRecords=!0,this.selectedRecords=new Set,this.deselectedRecords=new Set,this.updatedSelectedRecords()},canSelectAllRecords(){if(r){let i=this.getRecordsOnPage();return!this.areRecordsSelected(i)&&this.areRecordsToggleable(i)}let e=parseInt(this.$refs.allSelectableRecordsCount?.value);if(!e)return!1;let t=this.getSelectedRecordsCount();return e===t?!1:l===null||e<=l},deselectAllRecords(){this.isTrackingDeselectedRecords=!1,this.selectedRecords=new Set,this.deselectedRecords=new Set,this.updatedSelectedRecords()},isRecordSelected(e){return this.isTrackingDeselectedRecords?!this.deselectedRecords.has(e):this.selectedRecords.has(e)},areRecordsSelected(e){return e.every(t=>this.isRecordSelected(t))},areRecordsToggleable(e){if(l===null||l===1)return!0;let t=e.filter(i=>this.isRecordSelected(i));return t.length===e.length?!0:this.getSelectedRecordsCount()+(e.length-t.length)<=l},toggleCollapseGroup(e){if(this.isGroupCollapsed(e)){this.collapsedGroups.splice(this.collapsedGroups.indexOf(e),1);return}this.collapsedGroups.push(e)},isGroupCollapsed(e){return this.collapsedGroups.includes(e)},resetCollapsedGroups(){this.collapsedGroups=[]},watchForCheckboxClicks(){this.checkboxClickController&&this.checkboxClickController.abort(),this.checkboxClickController=new AbortController;let{signal:e}=this.checkboxClickController;this.$root?.addEventListener("click",t=>t.target?.matches(".fi-ta-record-checkbox")&&this.handleCheckboxClick(t,t.target),{signal:e})},handleCheckboxClick(e,t){if(!this.lastChecked){this.lastChecked=t;return}if(e.shiftKey){let i=Array.from(this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[]);if(!i.includes(this.lastChecked)){this.lastChecked=t;return}let o=i.indexOf(this.lastChecked),n=i.indexOf(t),a=[o,n].sort((c,R)=>c-R),h=[];for(let c=a[0];c<=a[1];c++)h.push(i[c].value);if(t.checked){if(!this.areRecordsToggleable(h)){t.checked=!1,this.deselectRecords([t.value]);return}this.selectRecords(h)}else this.deselectRecords(h)}this.lastChecked=t}});function f({columns:u,isLive:d}){return{error:void 0,isLoading:!1,columns:u,isLive:d,init(){if(!this.columns||this.columns.length===0){this.columns=[];return}},get groupedColumns(){let l={};return this.columns.filter(r=>r.type==="group").forEach(r=>{l[r.name]=this.calculateGroupedColumns(r)}),l},calculateGroupedColumns(l){if((l?.columns?.filter(i=>!i.isHidden)??[]).length===0)return{hidden:!0,checked:!1,disabled:!1,indeterminate:!1};let s=l.columns.filter(i=>!i.isHidden&&i.isToggleable!==!1);if(s.length===0)return{checked:!0,disabled:!0,indeterminate:!1};let e=s.filter(i=>i.isToggled).length,t=l.columns.filter(i=>!i.isHidden&&i.isToggleable===!1);return e===0&&t.length>0?{checked:!0,disabled:!1,indeterminate:!0}:e===0?{checked:!1,disabled:!1,indeterminate:!1}:e===s.length?{checked:!0,disabled:!1,indeterminate:!1}:{checked:!0,disabled:!1,indeterminate:!0}},getColumn(l,r=null){return r?this.columns.find(e=>e.type==="group"&&e.name===r)?.columns?.find(e=>e.name===l):this.columns.find(s=>s.name===l)},toggleGroup(l){let r=this.columns.find(o=>o.type==="group"&&o.name===l);if(!r?.columns)return;let s=this.calculateGroupedColumns(r);if(s.disabled)return;let t=r.columns.filter(o=>o.isToggleable!==!1).some(o=>o.isToggled),i=s.indeterminate?!0:!t;r.columns.filter(o=>o.isToggleable!==!1).forEach(o=>{o.isToggled=i}),this.columns=[...this.columns],this.isLive&&this.applyTableColumnManager()},toggleColumn(l,r=null){let s=this.getColumn(l,r);!s||s.isToggleable===!1||(s.isToggled=!s.isToggled,this.columns=[...this.columns],this.isLive&&this.applyTableColumnManager())},reorderColumns(l){let r=l.map(s=>s.split("::"));this.reorderTopLevel(r),this.isLive&&this.applyTableColumnManager()},reorderGroupColumns(l,r){let s=this.columns.find(i=>i.type==="group"&&i.name===r);if(!s)return;let e=l.map(i=>i.split("::")),t=[];e.forEach(([i,o])=>{let n=s.columns.find(a=>a.name===o);n&&t.push(n)}),s.columns=t,this.columns=[...this.columns],this.isLive&&this.applyTableColumnManager()},reorderTopLevel(l){let r=this.columns,s=[];l.forEach(([e,t])=>{let i=r.find(o=>e==="group"?o.type==="group"&&o.name===t:e==="column"?o.type!=="group"&&o.name===t:!1);i&&s.push(i)}),this.columns=s},async applyTableColumnManager(){this.isLoading=!0;try{await this.$wire.call("applyTableColumnManager",this.columns),this.error=void 0}catch(l){this.error="Failed to update column visibility",console.error("Table toggle columns error:",l)}finally{this.isLoading=!1}}}}document.addEventListener("alpine:init",()=>{window.Alpine.data("filamentTable",g),window.Alpine.data("filamentTableColumnManager",f)});})(); 2 | -------------------------------------------------------------------------------- /config/database.php: -------------------------------------------------------------------------------- 1 | env('DB_CONNECTION', 'sqlite'), 22 | 23 | /* 24 | |-------------------------------------------------------------------------- 25 | | Database Connections 26 | |-------------------------------------------------------------------------- 27 | | 28 | | Below are all of the database connections defined for your application. 29 | | An example configuration is provided for each database system which 30 | | is supported by Laravel. You're free to add / remove connections. 31 | | 32 | */ 33 | 34 | 'connections' => [ 35 | 36 | 'sqlite' => [ 37 | 'driver' => 'sqlite', 38 | 'url' => env('DB_URL'), 39 | 'database' => env('DB_DATABASE', database_path('database.sqlite')), 40 | 'prefix' => '', 41 | 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), 42 | 'busy_timeout' => null, 43 | 'journal_mode' => null, 44 | 'synchronous' => null, 45 | ], 46 | 47 | 'mysql' => [ 48 | 'driver' => 'mysql', 49 | 'url' => env('DB_URL'), 50 | 'host' => env('DB_HOST', '127.0.0.1'), 51 | 'port' => env('DB_PORT', '3306'), 52 | 'database' => env('DB_DATABASE', 'laravel'), 53 | 'username' => env('DB_USERNAME', 'root'), 54 | 'password' => env('DB_PASSWORD', ''), 55 | 'unix_socket' => env('DB_SOCKET', ''), 56 | 'charset' => env('DB_CHARSET', 'utf8mb4'), 57 | 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), 58 | 'prefix' => '', 59 | 'prefix_indexes' => true, 60 | 'strict' => true, 61 | 'engine' => null, 62 | 'options' => extension_loaded('pdo_mysql') ? array_filter([ 63 | PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), 64 | ]) : [], 65 | ], 66 | 67 | 'mariadb' => [ 68 | 'driver' => 'mariadb', 69 | 'url' => env('DB_URL'), 70 | 'host' => env('DB_HOST', '127.0.0.1'), 71 | 'port' => env('DB_PORT', '3306'), 72 | 'database' => env('DB_DATABASE', 'laravel'), 73 | 'username' => env('DB_USERNAME', 'root'), 74 | 'password' => env('DB_PASSWORD', ''), 75 | 'unix_socket' => env('DB_SOCKET', ''), 76 | 'charset' => env('DB_CHARSET', 'utf8mb4'), 77 | 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), 78 | 'prefix' => '', 79 | 'prefix_indexes' => true, 80 | 'strict' => true, 81 | 'engine' => null, 82 | 'options' => extension_loaded('pdo_mysql') ? array_filter([ 83 | PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), 84 | ]) : [], 85 | ], 86 | 87 | 'pgsql' => [ 88 | 'driver' => 'pgsql', 89 | 'url' => env('DB_URL'), 90 | 'host' => env('DB_HOST', '127.0.0.1'), 91 | 'port' => env('DB_PORT', '5432'), 92 | 'database' => env('DB_DATABASE', 'laravel'), 93 | 'username' => env('DB_USERNAME', 'root'), 94 | 'password' => env('DB_PASSWORD', ''), 95 | 'charset' => env('DB_CHARSET', 'utf8'), 96 | 'prefix' => '', 97 | 'prefix_indexes' => true, 98 | 'search_path' => 'public', 99 | 'sslmode' => 'prefer', 100 | ], 101 | 102 | 'sqlsrv' => [ 103 | 'driver' => 'sqlsrv', 104 | 'url' => env('DB_URL'), 105 | 'host' => env('DB_HOST', 'localhost'), 106 | 'port' => env('DB_PORT', '1433'), 107 | 'database' => env('DB_DATABASE', 'laravel'), 108 | 'username' => env('DB_USERNAME', 'root'), 109 | 'password' => env('DB_PASSWORD', ''), 110 | 'charset' => env('DB_CHARSET', 'utf8'), 111 | 'prefix' => '', 112 | 'prefix_indexes' => true, 113 | // 'encrypt' => env('DB_ENCRYPT', 'yes'), 114 | // 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'), 115 | ], 116 | 117 | ], 118 | 119 | /* 120 | |-------------------------------------------------------------------------- 121 | | Migration Repository Table 122 | |-------------------------------------------------------------------------- 123 | | 124 | | This table keeps track of all the migrations that have already run for 125 | | your application. Using this information, we can determine which of 126 | | the migrations on disk haven't actually been run on the database. 127 | | 128 | */ 129 | 130 | 'migrations' => [ 131 | 'table' => 'migrations', 132 | 'update_date_on_publish' => true, 133 | ], 134 | 135 | /* 136 | |-------------------------------------------------------------------------- 137 | | Redis Databases 138 | |-------------------------------------------------------------------------- 139 | | 140 | | Redis is an open source, fast, and advanced key-value store that also 141 | | provides a richer body of commands than a typical key-value system 142 | | such as Memcached. You may define your connection settings here. 143 | | 144 | */ 145 | 146 | 'redis' => [ 147 | 148 | 'client' => env('REDIS_CLIENT', 'phpredis'), 149 | 150 | 'options' => [ 151 | 'cluster' => env('REDIS_CLUSTER', 'redis'), 152 | 'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-database-'), 153 | 'persistent' => env('REDIS_PERSISTENT', false), 154 | ], 155 | 156 | 'default' => [ 157 | 'url' => env('REDIS_URL'), 158 | 'host' => env('REDIS_HOST', '127.0.0.1'), 159 | 'username' => env('REDIS_USERNAME'), 160 | 'password' => env('REDIS_PASSWORD'), 161 | 'port' => env('REDIS_PORT', '6379'), 162 | 'database' => env('REDIS_DB', '0'), 163 | ], 164 | 165 | 'cache' => [ 166 | 'url' => env('REDIS_URL'), 167 | 'host' => env('REDIS_HOST', '127.0.0.1'), 168 | 'username' => env('REDIS_USERNAME'), 169 | 'password' => env('REDIS_PASSWORD'), 170 | 'port' => env('REDIS_PORT', '6379'), 171 | 'database' => env('REDIS_CACHE_DB', '1'), 172 | ], 173 | 174 | ], 175 | 176 | ]; 177 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Desafio Full Stack – 3Pontos Tech 2 | 3 | Bem-vindo(a) ao processo seletivo da 3Pontos Tech. Este desafio avalia o seu domínio das tecnologias listadas, sua 4 | capacidade de criar um código "limpo" e de comunicar decisões de forma clara. 5 | 6 | ## Projeto proposto 7 | 8 | Este projeto é um clone simplificado do Reddit, com foco em funcionalidades essenciais de comunidades, postagens, 9 | comentários e votos. 10 | 11 | A aplicação permite que usuários explorem subreddits (comunidades temáticas), publiquem conteúdo em Markdown, interajam 12 | por meio de comentários e realizem votos positivos ou negativos nos posts. 13 | 14 | O objetivo é replicar, de forma objetiva e enxuta, a estrutura básica de um fórum de discussão como o Reddit, servindo 15 | como base para estudos, testes técnicos ou evolução futura. 16 | 17 | | Item | Descrição | 18 | |---------------|----------------------------| 19 | | **Linguagem** | PHP 8.3^ | 20 | | **Framework** | Laravel 12 + FilamentPHP 4 | 21 | | **Database** | SQLite ou Postgres | 22 | | **Front-end** | Blade e Tailwind v4 | 23 | 24 | > [!NOTE] 25 | > Use **SQLite** para agilizar a configuração. Caso deseje, inclua instruções para trocar facilmente para **PostgreSQL** 26 | > ou **MySQL** com **Docker**. 27 | 28 | ## Critérios gerais de avaliação 29 | 30 | 1. **Modelagem de dados** – consistência, normalização e previsibilidade. 31 | 2. **Laravel / FilamentPHP** – uso adequado de recursos nativos (Eloquent, policies, actions, widgets, etc.). 32 | 3. **Design do Sistema** – Como deixar o sistema robusto. 33 | 4. **TailwindCSS** – domínio dos utilitários essenciais para layout e tipografia. 34 | 5. **Qualidade de código** – legibilidade, organização em camadas, nomenclatura coerente. 35 | 6. **Commits** – adoção de _Conventional Commits_ e histórico incremental. 36 | 7. **Documentação** – arquivo `DEVELOPMENT.md` escrito por você (sem geração automática por IA) contendo: 37 | - Visão geral da solução. 38 | - Justificativa das principais decisões técnicas. 39 | - Anotações do processo 40 | 8. **Capacidade analítica** – explicação dos trade-offs escolhidos. 41 | 9. **Painel Administrativo** - uso do FilamentPHP para gerenciar subreddits e posts. 42 | 43 | 44 | > [!WARNING] 45 | > Não nos importamos com o uso de I.A desde que você saiba justificar suas decisões. O principal é ter certeza da entrega feita e isso será questionado durante a avaliação. 46 | 47 | 48 | > [!WARNING] 49 | > Não será permitido uso de Plugins externos fora o `MediaLibrary` para agilizar a implementação de uma feature. 50 | 51 | ## Níveis e critérios de aceite 52 | 53 | No projeto disponível no [Figma](https://bit.ly/3pontos-fs-challenge-figma), você encontrará **versões da interface**, cada uma representando um **nível de 54 | complexidade** e refinamento visual correspondente a diferentes níveis de senioridade. 55 | 56 | > Senha do figma: 'pontopontoponto' 57 | 58 | Com base nessas telas, você deverá analisar a UI e modelar o sistema seguindo os critérios definidos abaixo: 59 | 60 | | Nível | Foco principal | 61 | |------------------|------------------------------------------------------------------------------------------------------------------------------------------| 62 | | **Básico** | Conseguir entregar as funcionalidades centrais usando recursos básicos de Laravel, Filament e Tailwind. | 63 | | **Intermedário** | Ênfase em modelagem de dados sólida, separação clara de camadas, e testes de unidade básicos. | 64 | | **Avançado** | Arquitetura escalável, uso avançado de Filament (Resources, Panels, Actions), políticas de segurança, testes abrangentes, CI/CD simples. | 65 | 66 | ## Desafio 67 | 68 | Estamos iniciando um novo projeto: um **fórum de perguntas e respostas inspirado no Reddit** baseado em 69 | subreddits/tópicos. 70 | 71 | > A criação desses subreddits deverá ser dinâmica usando o FilamentPHP. 72 | 73 | Todo o desenvolvimento será guiado pelo layout disponível no Figma, que apresenta três páginas principais, repetidas em 74 | diferentes estágios de evolução visual: 75 | 76 | - **Home/Dashboard**: exibe todas as postagens para usuários logados ou não logados. 77 | - **Subreddit**: página inicial de uma comunidade, listando todos os seus posts. 78 | - **Post**: visualização de um post específico pertencente a uma comunidade/produto. 79 | 80 | > Serão dois ambientes: admin (Filament) e front-end (Blade + Tailwind). 81 | 82 | Você deverá utilizar o layout como base para modelar os dados do projeto, utilizando sua análise visual e interpretação. 83 | 84 | > Você também poderá utilizar uma I.A para modelagem, porém caso use, deixe explicito no `DEVELOPMENT.md` quais foram suas decisões de aceite e o que foi feito por você. 85 | 86 | Cada etapa implementada deve ser versionada com commits no seguinte formato: 87 | 88 | ``` 89 | git commit -m "feat(panel): sua-mensagem-de-preferencia-em-ingles" 90 | git commit -m "feat(component): sua-mensagem-de-preferencia-em-ingles" 91 | git commit -m "feat(anything): sua-mensagem-de-preferencia-em-ingles" 92 | ``` 93 | 94 | > Caso algum ponto do projeto não esteja claro, registre suas suposições no final do `README.md`. 95 | 96 | 97 | --- 98 | 99 | ## Etapas do Processo 100 | 101 | ### 1. Submissão do Projeto 102 | 103 | 1. Crie um novo repositório utilizando este template (no topo da pagina, no botão verde "Use this template"); 104 | 2. Crie uma branch `develop` e faça todo o desenvolvimento nela; 105 | 3. Abra um Pull Request para a branch `main` do **seu repositório pessoal**. 106 | 4. Envie um e-mail para `daniel@3pontos.com` com uma breve apresentação sobre você e o link do Pull Request. 107 | 5. Aguarde nossa resposta. 108 | 109 | > Data de entrega: **01/10/2025** às **13:00h** 110 | > Você pode acompanhar outras submissões e discussões sobre no [Discord da 3Pontos](https://bit.ly/3pontos-discord). 111 | 112 | ### 2. Entrevista Técnica 113 | 114 | Caso avance para a próxima fase, você participará de uma conversa com o time técnico. Vamos discutir suas decisões, 115 | entender seu raciocínio e trocar ideias de forma descontraída e técnica ao mesmo tempo. 116 | 117 | > Data das entrevistas: 02/10 ~ 03/10 na parte da tarde. 118 | 119 | ### 3. Conversa com a Liderança 120 | 121 | Neste momento, você terá um bate-papo com as lideranças da empresa para avaliarmos o alinhamento cultural. Por mais que 122 | pareça uma etapa “clichê”, ela é fundamental: buscamos pessoas que compartilhem dos nossos valores e estejam prontas 123 | para crescer com o time. 124 | 125 | ### 4. Carta Proposta 126 | 127 | Se tudo estiver alinhado, você receberá nossa proposta para integrar o time oficialmente e colar com a gente! 128 | 129 | ## Materiais de Suporte 130 | 131 | - [Filament Brasil](https://filament.com.br) 132 | - [Filament Docs](https://filament.com/docs) 133 | - [https://refactoring.guru/](https://refactoring.guru/) 134 | - [http://br.phptherightway.com/](http://br.phptherightway.com/) 135 | - [https://www.php-fig.org/psr/psr-12/](https://www.php-fig.org/psr/psr-12/) 136 | 137 | ## Agradecimentos 138 | 139 | - [@jeffersongoncalves](https://github.com/jeffersongoncalves) pelo setup inicial do Filament V4 Beta. 140 | - [@nexturhe4rt](https://github.com/nexturhe4rt) pelo UI feita. 141 | - [@gvieira18 - vulgo usb777](https://github.com/gvieira18) pelo setup inicial do projeto. -------------------------------------------------------------------------------- /config/telescope.php: -------------------------------------------------------------------------------- 1 | env('TELESCOPE_ENABLED', false), 39 | 40 | /* 41 | |-------------------------------------------------------------------------- 42 | | Telescope Domain 43 | |-------------------------------------------------------------------------- 44 | | 45 | | This is the subdomain where Telescope will be accessible from. If the 46 | | setting is null, Telescope will reside under the same domain as the 47 | | application. Otherwise, this value will be used as the subdomain. 48 | | 49 | */ 50 | 51 | 'domain' => env('TELESCOPE_DOMAIN'), 52 | 53 | /* 54 | |-------------------------------------------------------------------------- 55 | | Telescope Path 56 | |-------------------------------------------------------------------------- 57 | | 58 | | This is the URI path where Telescope will be accessible from. Feel free 59 | | to change this path to anything you like. Note that the URI will not 60 | | affect the paths of its internal API that aren't exposed to users. 61 | | 62 | */ 63 | 64 | 'path' => env('TELESCOPE_PATH', 'telescope'), 65 | 66 | /* 67 | |-------------------------------------------------------------------------- 68 | | Telescope Storage Driver 69 | |-------------------------------------------------------------------------- 70 | | 71 | | This configuration options determines the storage driver that will 72 | | be used to store Telescope's data. In addition, you may set any 73 | | custom options as needed by the particular driver you choose. 74 | | 75 | */ 76 | 77 | 'driver' => env('TELESCOPE_DRIVER', 'database'), 78 | 79 | 'storage' => [ 80 | 'database' => [ 81 | 'connection' => env('DB_CONNECTION', 'mysql'), 82 | 'chunk' => 1000, 83 | ], 84 | ], 85 | 86 | /* 87 | |-------------------------------------------------------------------------- 88 | | Telescope Queue 89 | |-------------------------------------------------------------------------- 90 | | 91 | | This configuration options determines the queue connection and queue 92 | | which will be used to process ProcessPendingUpdate jobs. This can 93 | | be changed if you would prefer to use a non-default connection. 94 | | 95 | */ 96 | 97 | 'queue' => [ 98 | 'connection' => env('TELESCOPE_QUEUE_CONNECTION', null), 99 | 'queue' => env('TELESCOPE_QUEUE', null), 100 | 'delay' => env('TELESCOPE_QUEUE_DELAY', 10), 101 | ], 102 | 103 | /* 104 | |-------------------------------------------------------------------------- 105 | | Telescope Route Middleware 106 | |-------------------------------------------------------------------------- 107 | | 108 | | These middleware will be assigned to every Telescope route, giving you 109 | | the chance to add your own middleware to this list or change any of 110 | | the existing middleware. Or, you can simply stick with this list. 111 | | 112 | */ 113 | 114 | 'middleware' => [ 115 | 'web', 116 | Authorize::class, 117 | ], 118 | 119 | /* 120 | |-------------------------------------------------------------------------- 121 | | Allowed / Ignored Paths & Commands 122 | |-------------------------------------------------------------------------- 123 | | 124 | | The following array lists the URI paths and Artisan commands that will 125 | | not be watched by Telescope. In addition to this list, some Laravel 126 | | commands, like migrations and queue commands, are always ignored. 127 | | 128 | */ 129 | 130 | 'only_paths' => [ 131 | // 'api/*' 132 | ], 133 | 134 | 'ignore_paths' => [ 135 | 'livewire*', 136 | 'nova-api*', 137 | 'pulse*', 138 | ], 139 | 140 | 'ignore_commands' => [ 141 | // 142 | ], 143 | 144 | /* 145 | |-------------------------------------------------------------------------- 146 | | Telescope Watchers 147 | |-------------------------------------------------------------------------- 148 | | 149 | | The following array lists the "watchers" that will be registered with 150 | | Telescope. The watchers gather the application's profile data when 151 | | a request or task is executed. Feel free to customize this list. 152 | | 153 | */ 154 | 155 | 'watchers' => [ 156 | BatchWatcher::class => env('TELESCOPE_BATCH_WATCHER', true), 157 | 158 | CacheWatcher::class => [ 159 | 'enabled' => env('TELESCOPE_CACHE_WATCHER', true), 160 | 'hidden' => [], 161 | 'ignore' => [], 162 | ], 163 | 164 | ClientRequestWatcher::class => env('TELESCOPE_CLIENT_REQUEST_WATCHER', true), 165 | 166 | CommandWatcher::class => [ 167 | 'enabled' => env('TELESCOPE_COMMAND_WATCHER', true), 168 | 'ignore' => [], 169 | ], 170 | 171 | DumpWatcher::class => [ 172 | 'enabled' => env('TELESCOPE_DUMP_WATCHER', true), 173 | 'always' => env('TELESCOPE_DUMP_WATCHER_ALWAYS', false), 174 | ], 175 | 176 | EventWatcher::class => [ 177 | 'enabled' => env('TELESCOPE_EVENT_WATCHER', true), 178 | 'ignore' => [], 179 | ], 180 | 181 | ExceptionWatcher::class => env('TELESCOPE_EXCEPTION_WATCHER', true), 182 | 183 | GateWatcher::class => [ 184 | 'enabled' => env('TELESCOPE_GATE_WATCHER', true), 185 | 'ignore_abilities' => [], 186 | 'ignore_packages' => true, 187 | 'ignore_paths' => [], 188 | ], 189 | 190 | JobWatcher::class => env('TELESCOPE_JOB_WATCHER', true), 191 | 192 | LogWatcher::class => [ 193 | 'enabled' => env('TELESCOPE_LOG_WATCHER', true), 194 | 'level' => 'error', 195 | ], 196 | 197 | MailWatcher::class => env('TELESCOPE_MAIL_WATCHER', true), 198 | 199 | ModelWatcher::class => [ 200 | 'enabled' => env('TELESCOPE_MODEL_WATCHER', true), 201 | 'events' => ['eloquent.*'], 202 | 'hydrations' => true, 203 | ], 204 | 205 | NotificationWatcher::class => env('TELESCOPE_NOTIFICATION_WATCHER', true), 206 | 207 | QueryWatcher::class => [ 208 | 'enabled' => env('TELESCOPE_QUERY_WATCHER', true), 209 | 'ignore_packages' => true, 210 | 'ignore_paths' => [], 211 | 'slow' => 100, 212 | ], 213 | 214 | RedisWatcher::class => env('TELESCOPE_REDIS_WATCHER', true), 215 | 216 | RequestWatcher::class => [ 217 | 'enabled' => env('TELESCOPE_REQUEST_WATCHER', true), 218 | 'size_limit' => env('TELESCOPE_RESPONSE_SIZE_LIMIT', 64), 219 | 'ignore_http_methods' => [], 220 | 'ignore_status_codes' => [], 221 | ], 222 | 223 | ScheduleWatcher::class => env('TELESCOPE_SCHEDULE_WATCHER', true), 224 | ViewWatcher::class => env('TELESCOPE_VIEW_WATCHER', true), 225 | ], 226 | ]; 227 | -------------------------------------------------------------------------------- /config/session.php: -------------------------------------------------------------------------------- 1 | env('SESSION_DRIVER', 'database'), 24 | 25 | /* 26 | |-------------------------------------------------------------------------- 27 | | Session Lifetime 28 | |-------------------------------------------------------------------------- 29 | | 30 | | Here you may specify the number of minutes that you wish the session 31 | | to be allowed to remain idle before it expires. If you want them 32 | | to expire immediately when the browser is closed then you may 33 | | indicate that via the expire_on_close configuration option. 34 | | 35 | */ 36 | 37 | 'lifetime' => (int) env('SESSION_LIFETIME', 120), 38 | 39 | 'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false), 40 | 41 | /* 42 | |-------------------------------------------------------------------------- 43 | | Session Encryption 44 | |-------------------------------------------------------------------------- 45 | | 46 | | This option allows you to easily specify that all of your session data 47 | | should be encrypted before it's stored. All encryption is performed 48 | | automatically by Laravel and you may use the session like normal. 49 | | 50 | */ 51 | 52 | 'encrypt' => env('SESSION_ENCRYPT', false), 53 | 54 | /* 55 | |-------------------------------------------------------------------------- 56 | | Session File Location 57 | |-------------------------------------------------------------------------- 58 | | 59 | | When utilizing the "file" session driver, the session files are placed 60 | | on disk. The default storage location is defined here; however, you 61 | | are free to provide another location where they should be stored. 62 | | 63 | */ 64 | 65 | 'files' => storage_path('framework/sessions'), 66 | 67 | /* 68 | |-------------------------------------------------------------------------- 69 | | Session Database Connection 70 | |-------------------------------------------------------------------------- 71 | | 72 | | When using the "database" or "redis" session drivers, you may specify a 73 | | connection that should be used to manage these sessions. This should 74 | | correspond to a connection in your database configuration options. 75 | | 76 | */ 77 | 78 | 'connection' => env('SESSION_CONNECTION'), 79 | 80 | /* 81 | |-------------------------------------------------------------------------- 82 | | Session Database Table 83 | |-------------------------------------------------------------------------- 84 | | 85 | | When using the "database" session driver, you may specify the table to 86 | | be used to store sessions. Of course, a sensible default is defined 87 | | for you; however, you're welcome to change this to another table. 88 | | 89 | */ 90 | 91 | 'table' => env('SESSION_TABLE', 'sessions'), 92 | 93 | /* 94 | |-------------------------------------------------------------------------- 95 | | Session Cache Store 96 | |-------------------------------------------------------------------------- 97 | | 98 | | When using one of the framework's cache driven session backends, you may 99 | | define the cache store which should be used to store the session data 100 | | between requests. This must match one of your defined cache stores. 101 | | 102 | | Affects: "dynamodb", "memcached", "redis" 103 | | 104 | */ 105 | 106 | 'store' => env('SESSION_STORE'), 107 | 108 | /* 109 | |-------------------------------------------------------------------------- 110 | | Session Sweeping Lottery 111 | |-------------------------------------------------------------------------- 112 | | 113 | | Some session drivers must manually sweep their storage location to get 114 | | rid of old sessions from storage. Here are the chances that it will 115 | | happen on a given request. By default, the odds are 2 out of 100. 116 | | 117 | */ 118 | 119 | 'lottery' => [2, 100], 120 | 121 | /* 122 | |-------------------------------------------------------------------------- 123 | | Session Cookie Name 124 | |-------------------------------------------------------------------------- 125 | | 126 | | Here you may change the name of the session cookie that is created by 127 | | the framework. Typically, you should not need to change this value 128 | | since doing so does not grant a meaningful security improvement. 129 | | 130 | */ 131 | 132 | 'cookie' => env( 133 | 'SESSION_COOKIE', 134 | Str::snake((string) env('APP_NAME', 'laravel')).'_session' 135 | ), 136 | 137 | /* 138 | |-------------------------------------------------------------------------- 139 | | Session Cookie Path 140 | |-------------------------------------------------------------------------- 141 | | 142 | | The session cookie path determines the path for which the cookie will 143 | | be regarded as available. Typically, this will be the root path of 144 | | your application, but you're free to change this when necessary. 145 | | 146 | */ 147 | 148 | 'path' => env('SESSION_PATH', '/'), 149 | 150 | /* 151 | |-------------------------------------------------------------------------- 152 | | Session Cookie Domain 153 | |-------------------------------------------------------------------------- 154 | | 155 | | This value determines the domain and subdomains the session cookie is 156 | | available to. By default, the cookie will be available to the root 157 | | domain and all subdomains. Typically, this shouldn't be changed. 158 | | 159 | */ 160 | 161 | 'domain' => env('SESSION_DOMAIN'), 162 | 163 | /* 164 | |-------------------------------------------------------------------------- 165 | | HTTPS Only Cookies 166 | |-------------------------------------------------------------------------- 167 | | 168 | | By setting this option to true, session cookies will only be sent back 169 | | to the server if the browser has a HTTPS connection. This will keep 170 | | the cookie from being sent to you when it can't be done securely. 171 | | 172 | */ 173 | 174 | 'secure' => env('SESSION_SECURE_COOKIE'), 175 | 176 | /* 177 | |-------------------------------------------------------------------------- 178 | | HTTP Access Only 179 | |-------------------------------------------------------------------------- 180 | | 181 | | Setting this value to true will prevent JavaScript from accessing the 182 | | value of the cookie and the cookie will only be accessible through 183 | | the HTTP protocol. It's unlikely you should disable this option. 184 | | 185 | */ 186 | 187 | 'http_only' => env('SESSION_HTTP_ONLY', true), 188 | 189 | /* 190 | |-------------------------------------------------------------------------- 191 | | Same-Site Cookies 192 | |-------------------------------------------------------------------------- 193 | | 194 | | This option determines how your cookies behave when cross-site requests 195 | | take place, and can be used to mitigate CSRF attacks. By default, we 196 | | will set this value to "lax" to permit secure cross-site requests. 197 | | 198 | | See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value 199 | | 200 | | Supported: "lax", "strict", "none", null 201 | | 202 | */ 203 | 204 | 'same_site' => env('SESSION_SAME_SITE', 'lax'), 205 | 206 | /* 207 | |-------------------------------------------------------------------------- 208 | | Partitioned Cookies 209 | |-------------------------------------------------------------------------- 210 | | 211 | | Setting this value to true will tie the cookie to the top-level site for 212 | | a cross-site context. Partitioned cookies are accepted by the browser 213 | | when flagged "secure" and the Same-Site attribute is set to "none". 214 | | 215 | */ 216 | 217 | 'partitioned' => env('SESSION_PARTITIONED_COOKIE', false), 218 | 219 | ]; 220 | -------------------------------------------------------------------------------- /public/js/filament/forms/components/color-picker.js: -------------------------------------------------------------------------------- 1 | var c=(e,t=0,r=1)=>e>r?r:eMath.round(r*e)/r;var at={grad:360/400,turn:360,rad:360/(Math.PI*2)},F=e=>G(v(e)),v=e=>(e[0]==="#"&&(e=e.substring(1)),e.length<6?{r:parseInt(e[0]+e[0],16),g:parseInt(e[1]+e[1],16),b:parseInt(e[2]+e[2],16),a:e.length===4?a(parseInt(e[3]+e[3],16)/255,2):1}:{r:parseInt(e.substring(0,2),16),g:parseInt(e.substring(2,4),16),b:parseInt(e.substring(4,6),16),a:e.length===8?a(parseInt(e.substring(6,8),16)/255,2):1}),nt=(e,t="deg")=>Number(e)*(at[t]||1),it=e=>{let r=/hsla?\(?\s*(-?\d*\.?\d+)(deg|rad|grad|turn)?[,\s]+(-?\d*\.?\d+)%?[,\s]+(-?\d*\.?\d+)%?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i.exec(e);return r?lt({h:nt(r[1],r[2]),s:Number(r[3]),l:Number(r[4]),a:r[5]===void 0?1:Number(r[5])/(r[6]?100:1)}):{h:0,s:0,v:0,a:1}},J=it,lt=({h:e,s:t,l:r,a:o})=>(t*=(r<50?r:100-r)/100,{h:e,s:t>0?2*t/(r+t)*100:0,v:r+t,a:o}),X=e=>ct(A(e)),Y=({h:e,s:t,v:r,a:o})=>{let s=(200-t)*r/100;return{h:a(e),s:a(s>0&&s<200?t*r/100/(s<=100?s:200-s)*100:0),l:a(s/2),a:a(o,2)}};var d=e=>{let{h:t,s:r,l:o}=Y(e);return`hsl(${t}, ${r}%, ${o}%)`},$=e=>{let{h:t,s:r,l:o,a:s}=Y(e);return`hsla(${t}, ${r}%, ${o}%, ${s})`},A=({h:e,s:t,v:r,a:o})=>{e=e/360*6,t=t/100,r=r/100;let s=Math.floor(e),n=r*(1-t),i=r*(1-(e-s)*t),l=r*(1-(1-e+s)*t),q=s%6;return{r:a([r,i,n,n,l,r][q]*255),g:a([l,r,r,i,n,n][q]*255),b:a([n,n,l,r,r,i][q]*255),a:a(o,2)}},B=e=>{let{r:t,g:r,b:o}=A(e);return`rgb(${t}, ${r}, ${o})`},D=e=>{let{r:t,g:r,b:o,a:s}=A(e);return`rgba(${t}, ${r}, ${o}, ${s})`};var I=e=>{let r=/rgba?\(?\s*(-?\d*\.?\d+)(%)?[,\s]+(-?\d*\.?\d+)(%)?[,\s]+(-?\d*\.?\d+)(%)?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i.exec(e);return r?G({r:Number(r[1])/(r[2]?100/255:1),g:Number(r[3])/(r[4]?100/255:1),b:Number(r[5])/(r[6]?100/255:1),a:r[7]===void 0?1:Number(r[7])/(r[8]?100:1)}):{h:0,s:0,v:0,a:1}},U=I,b=e=>{let t=e.toString(16);return t.length<2?"0"+t:t},ct=({r:e,g:t,b:r,a:o})=>{let s=o<1?b(a(o*255)):"";return"#"+b(e)+b(t)+b(r)+s},G=({r:e,g:t,b:r,a:o})=>{let s=Math.max(e,t,r),n=s-Math.min(e,t,r),i=n?s===e?(t-r)/n:s===t?2+(r-e)/n:4+(e-t)/n:0;return{h:a(60*(i<0?i+6:i)),s:a(s?n/s*100:0),v:a(s/255*100),a:o}};var L=(e,t)=>{if(e===t)return!0;for(let r in e)if(e[r]!==t[r])return!1;return!0},h=(e,t)=>e.replace(/\s/g,"")===t.replace(/\s/g,""),K=(e,t)=>e.toLowerCase()===t.toLowerCase()?!0:L(v(e),v(t));var Q={},H=e=>{let t=Q[e];return t||(t=document.createElement("template"),t.innerHTML=e,Q[e]=t),t},f=(e,t,r)=>{e.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:r}))};var m=!1,O=e=>"touches"in e,pt=e=>m&&!O(e)?!1:(m||(m=O(e)),!0),W=(e,t)=>{let r=O(t)?t.touches[0]:t,o=e.el.getBoundingClientRect();f(e.el,"move",e.getMove({x:c((r.pageX-(o.left+window.pageXOffset))/o.width),y:c((r.pageY-(o.top+window.pageYOffset))/o.height)}))},ut=(e,t)=>{let r=t.keyCode;r>40||e.xy&&r<37||r<33||(t.preventDefault(),f(e.el,"move",e.getMove({x:r===39?.01:r===37?-.01:r===34?.05:r===33?-.05:r===35?1:r===36?-1:0,y:r===40?.01:r===38?-.01:0},!0)))},u=class{constructor(t,r,o,s){let n=H(`
`);t.appendChild(n.content.cloneNode(!0));let i=t.querySelector(`[part=${r}]`);i.addEventListener("mousedown",this),i.addEventListener("touchstart",this),i.addEventListener("keydown",this),this.el=i,this.xy=s,this.nodes=[i.firstChild,i]}set dragging(t){let r=t?document.addEventListener:document.removeEventListener;r(m?"touchmove":"mousemove",this),r(m?"touchend":"mouseup",this)}handleEvent(t){switch(t.type){case"mousedown":case"touchstart":if(t.preventDefault(),!pt(t)||!m&&t.button!=0)return;this.el.focus(),W(this,t),this.dragging=!0;break;case"mousemove":case"touchmove":t.preventDefault(),W(this,t);break;case"mouseup":case"touchend":this.dragging=!1;break;case"keydown":ut(this,t);break}}style(t){t.forEach((r,o)=>{for(let s in r)this.nodes[o].style.setProperty(s,r[s])})}};var S=class extends u{constructor(t){super(t,"hue",'aria-label="Hue" aria-valuemin="0" aria-valuemax="360"',!1)}update({h:t}){this.h=t,this.style([{left:`${t/360*100}%`,color:d({h:t,s:100,v:100,a:1})}]),this.el.setAttribute("aria-valuenow",`${a(t)}`)}getMove(t,r){return{h:r?c(this.h+t.x*360,0,360):360*t.x}}};var T=class extends u{constructor(t){super(t,"saturation",'aria-label="Color"',!0)}update(t){this.hsva=t,this.style([{top:`${100-t.v}%`,left:`${t.s}%`,color:d(t)},{"background-color":d({h:t.h,s:100,v:100,a:1})}]),this.el.setAttribute("aria-valuetext",`Saturation ${a(t.s)}%, Brightness ${a(t.v)}%`)}getMove(t,r){return{s:r?c(this.hsva.s+t.x*100,0,100):t.x*100,v:r?c(this.hsva.v-t.y*100,0,100):Math.round(100-t.y*100)}}};var Z=':host{display:flex;flex-direction:column;position:relative;width:200px;height:200px;user-select:none;-webkit-user-select:none;cursor:default}:host([hidden]){display:none!important}[role=slider]{position:relative;touch-action:none;user-select:none;-webkit-user-select:none;outline:0}[role=slider]:last-child{border-radius:0 0 8px 8px}[part$=pointer]{position:absolute;z-index:1;box-sizing:border-box;width:28px;height:28px;display:flex;place-content:center center;transform:translate(-50%,-50%);background-color:#fff;border:2px solid #fff;border-radius:50%;box-shadow:0 2px 4px rgba(0,0,0,.2)}[part$=pointer]::after{content:"";width:100%;height:100%;border-radius:inherit;background-color:currentColor}[role=slider]:focus [part$=pointer]{transform:translate(-50%,-50%) scale(1.1)}';var tt="[part=hue]{flex:0 0 24px;background:linear-gradient(to right,red 0,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,red 100%)}[part=hue-pointer]{top:50%;z-index:2}";var rt="[part=saturation]{flex-grow:1;border-color:transparent;border-bottom:12px solid #000;border-radius:8px 8px 0 0;background-image:linear-gradient(to top,#000,transparent),linear-gradient(to right,#fff,rgba(255,255,255,0));box-shadow:inset 0 0 0 1px rgba(0,0,0,.05)}[part=saturation-pointer]{z-index:3}";var w=Symbol("same"),R=Symbol("color"),et=Symbol("hsva"),_=Symbol("update"),ot=Symbol("parts"),g=Symbol("css"),x=Symbol("sliders"),p=class extends HTMLElement{static get observedAttributes(){return["color"]}get[g](){return[Z,tt,rt]}get[x](){return[T,S]}get color(){return this[R]}set color(t){if(!this[w](t)){let r=this.colorModel.toHsva(t);this[_](r),this[R]=t}}constructor(){super();let t=H(``),r=this.attachShadow({mode:"open"});r.appendChild(t.content.cloneNode(!0)),r.addEventListener("move",this),this[ot]=this[x].map(o=>new o(r))}connectedCallback(){if(this.hasOwnProperty("color")){let t=this.color;delete this.color,this.color=t}else this.color||(this.color=this.colorModel.defaultColor)}attributeChangedCallback(t,r,o){let s=this.colorModel.fromAttr(o);this[w](s)||(this.color=s)}handleEvent(t){let r=this[et],o={...r,...t.detail};this[_](o);let s;!L(o,r)&&!this[w](s=this.colorModel.fromHsva(o))&&(this[R]=s,f(this,"color-changed",{value:s}))}[w](t){return this.color&&this.colorModel.equal(t,this.color)}[_](t){this[et]=t,this[ot].forEach(r=>r.update(t))}};var dt={defaultColor:"#000",toHsva:F,fromHsva:({h:e,s:t,v:r})=>X({h:e,s:t,v:r,a:1}),equal:K,fromAttr:e=>e},y=class extends p{get colorModel(){return dt}};var P=class extends y{};customElements.define("hex-color-picker",P);var ht={defaultColor:"hsl(0, 0%, 0%)",toHsva:J,fromHsva:d,equal:h,fromAttr:e=>e},M=class extends p{get colorModel(){return ht}};var z=class extends M{};customElements.define("hsl-string-color-picker",z);var mt={defaultColor:"rgb(0, 0, 0)",toHsva:U,fromHsva:B,equal:h,fromAttr:e=>e},C=class extends p{get colorModel(){return mt}};var V=class extends C{};customElements.define("rgb-string-color-picker",V);var k=class extends u{constructor(t){super(t,"alpha",'aria-label="Alpha" aria-valuemin="0" aria-valuemax="1"',!1)}update(t){this.hsva=t;let r=$({...t,a:0}),o=$({...t,a:1}),s=t.a*100;this.style([{left:`${s}%`,color:$(t)},{"--gradient":`linear-gradient(90deg, ${r}, ${o}`}]);let n=a(s);this.el.setAttribute("aria-valuenow",`${n}`),this.el.setAttribute("aria-valuetext",`${n}%`)}getMove(t,r){return{a:r?c(this.hsva.a+t.x):t.x}}};var st=`[part=alpha]{flex:0 0 24px}[part=alpha]::after{display:block;content:"";position:absolute;top:0;left:0;right:0;bottom:0;border-radius:inherit;background-image:var(--gradient);box-shadow:inset 0 0 0 1px rgba(0,0,0,.05)}[part^=alpha]{background-color:#fff;background-image:url('data:image/svg+xml,')}[part=alpha-pointer]{top:50%}`;var E=class extends p{get[g](){return[...super[g],st]}get[x](){return[...super[x],k]}};var ft={defaultColor:"rgba(0, 0, 0, 1)",toHsva:I,fromHsva:D,equal:h,fromAttr:e=>e},N=class extends E{get colorModel(){return ft}};var j=class extends N{};customElements.define("rgba-string-color-picker",j);function gt({isAutofocused:e,isDisabled:t,isLive:r,isLiveDebounced:o,isLiveOnBlur:s,liveDebounce:n,state:i}){return{state:i,init(){this.state===null||this.state===""||this.setState(this.state),e&&this.togglePanelVisibility(this.$refs.input),this.$refs.input.addEventListener("change",l=>{this.setState(l.target.value)}),this.$refs.panel.addEventListener("color-changed",l=>{this.setState(l.detail.value),!(s||!(r||o))&&setTimeout(()=>{this.state===l.detail.value&&this.commitState()},o?n:250)}),(r||o||s)&&new MutationObserver(()=>this.isOpen()?null:this.commitState()).observe(this.$refs.panel,{attributes:!0,childList:!0})},togglePanelVisibility(){t||this.$refs.panel.toggle(this.$refs.input)},setState(l){this.state=l,this.$refs.input.value=l,this.$refs.panel.color=l},isOpen(){return this.$refs.panel.style.display==="block"},commitState(){JSON.stringify(this.$wire.__instance.canonical)!==JSON.stringify(this.$wire.__instance.ephemeral)&&this.$wire.$commit()}}}export{gt as default}; 2 | -------------------------------------------------------------------------------- /public/js/filament/filament/app.js: -------------------------------------------------------------------------------- 1 | (()=>{var ee=Object.create,q=Object.defineProperty,te=Object.getPrototypeOf,re=Object.prototype.hasOwnProperty,ne=Object.getOwnPropertyNames,ie=Object.getOwnPropertyDescriptor,se=r=>q(r,"__esModule",{value:!0}),ae=(r,n)=>()=>(n||(n={exports:{}},r(n.exports,n)),n.exports),oe=(r,n,s)=>{if(n&&typeof n=="object"||typeof n=="function")for(let l of ne(n))!re.call(r,l)&&l!=="default"&&q(r,l,{get:()=>n[l],enumerable:!(s=ie(n,l))||s.enumerable});return r},fe=r=>oe(se(q(r!=null?ee(te(r)):{},"default",r&&r.__esModule&&"default"in r?{get:()=>r.default,enumerable:!0}:{value:r,enumerable:!0})),r),le=ae((r,n)=>{(function(s,l,g){if(!s)return;for(var d={8:"backspace",9:"tab",13:"enter",16:"shift",17:"ctrl",18:"alt",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"ins",46:"del",91:"meta",93:"meta",224:"meta"},w={106:"*",107:"+",109:"-",110:".",111:"/",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},b={"~":"`","!":"1","@":"2","#":"3",$:"4","%":"5","^":"6","&":"7","*":"8","(":"9",")":"0",_:"-","+":"=",":":";",'"':"'","<":",",">":".","?":"/","|":"\\"},x={option:"alt",command:"meta",return:"enter",escape:"esc",plus:"+",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},P,y=1;y<20;++y)d[111+y]="f"+y;for(y=0;y<=9;++y)d[y+96]=y.toString();function S(e,t,a){if(e.addEventListener){e.addEventListener(t,a,!1);return}e.attachEvent("on"+t,a)}function G(e){if(e.type=="keypress"){var t=String.fromCharCode(e.which);return e.shiftKey||(t=t.toLowerCase()),t}return d[e.which]?d[e.which]:w[e.which]?w[e.which]:String.fromCharCode(e.which).toLowerCase()}function N(e,t){return e.sort().join(",")===t.sort().join(",")}function V(e){var t=[];return e.shiftKey&&t.push("shift"),e.altKey&&t.push("alt"),e.ctrlKey&&t.push("ctrl"),e.metaKey&&t.push("meta"),t}function H(e){if(e.preventDefault){e.preventDefault();return}e.returnValue=!1}function F(e){if(e.stopPropagation){e.stopPropagation();return}e.cancelBubble=!0}function C(e){return e=="shift"||e=="ctrl"||e=="alt"||e=="meta"}function J(){if(!P){P={};for(var e in d)e>95&&e<112||d.hasOwnProperty(e)&&(P[d[e]]=e)}return P}function B(e,t,a){return a||(a=J()[e]?"keydown":"keypress"),a=="keypress"&&t.length&&(a="keydown"),a}function X(e){return e==="+"?["+"]:(e=e.replace(/\+{2}/g,"+plus"),e.split("+"))}function T(e,t){var a,h,k,A=[];for(a=X(e),k=0;k1){Q(i,m,f,p);return}u=T(i,p),t._callbacks[u.key]=t._callbacks[u.key]||[],I(u.key,u.modifiers,{type:u.action},o,i,c),t._callbacks[u.key][o?"unshift":"push"]({callback:f,modifiers:u.modifiers,action:u.action,seq:o,level:c,combo:i})}t._bindMultiple=function(i,f,p){for(var o=0;o-1||U(t,a.target))return!1;if("composedPath"in e&&typeof e.composedPath=="function"){var h=e.composedPath()[0];h!==e.target&&(t=h)}return t.tagName=="INPUT"||t.tagName=="SELECT"||t.tagName=="TEXTAREA"||t.isContentEditable},v.prototype.handleKey=function(){var e=this;return e._handleKey.apply(e,arguments)},v.addKeycodes=function(e){for(var t in e)e.hasOwnProperty(t)&&(d[t]=e[t]);P=null},v.init=function(){var e=v(l);for(var t in e)t.charAt(0)!=="_"&&(v[t]=(function(a){return function(){return e[a].apply(e,arguments)}})(t))},v.init(),s.Mousetrap=v,typeof n<"u"&&n.exports&&(n.exports=v),typeof define=="function"&&define.amd&&define(function(){return v})})(typeof window<"u"?window:null,typeof window<"u"?document:null)}),R=fe(le());(function(r){if(r){var n={},s=r.prototype.stopCallback;r.prototype.stopCallback=function(l,g,d,w){var b=this;return b.paused?!0:n[d]||n[w]?!1:s.call(b,l,g,d)},r.prototype.bindGlobal=function(l,g,d){var w=this;if(w.bind(l,g,d),l instanceof Array){for(var b=0;b{r.directive("mousetrap",(n,{modifiers:s,expression:l},{evaluate:g})=>{let d=()=>l?g(l):n.click();s=s.map(w=>w.replace(/--/g," ").replace(/-/g,"+").replace(/\bslash\b/g,"/")),s.includes("global")&&(s=s.filter(w=>w!=="global"),R.default.bindGlobal(s,w=>{w.preventDefault(),d()})),R.default.bind(s,w=>{w.preventDefault(),d()})})},W=ue;var j=()=>({isOpen:window.Alpine.$persist(!0).as("isOpen"),isOpenDesktop:window.Alpine.$persist(!0).as("isOpenDesktop"),collapsedGroups:window.Alpine.$persist(null).as("collapsedGroups"),init(){this.resizeObserver=null,this.setUpResizeObserver(),document.addEventListener("livewire:navigated",()=>{this.setUpResizeObserver()})},setUpResizeObserver(){this.resizeObserver&&this.resizeObserver.disconnect();let r=window.innerWidth;this.resizeObserver=new ResizeObserver(()=>{let n=window.innerWidth,s=r>=1024,l=n<1024,g=n>=1024;s&&l?(this.isOpenDesktop=this.isOpen,this.isOpen&&this.close()):!s&&g&&(this.isOpen=this.isOpenDesktop),r=n}),this.resizeObserver.observe(document.body),window.innerWidth<1024?this.isOpen&&(this.isOpenDesktop=!0,this.close()):this.isOpenDesktop=this.isOpen},groupIsCollapsed(r){return this.collapsedGroups.includes(r)},collapseGroup(r){this.collapsedGroups.includes(r)||(this.collapsedGroups=this.collapsedGroups.concat(r))},toggleCollapsedGroup(r){this.collapsedGroups=this.collapsedGroups.includes(r)?this.collapsedGroups.filter(n=>n!==r):this.collapsedGroups.concat(r)},close(){this.isOpen=!1,window.innerWidth>=1024&&(this.isOpenDesktop=!1)},open(){this.isOpen=!0,window.innerWidth>=1024&&(this.isOpenDesktop=!0)}});document.addEventListener("alpine:init",()=>{let r=localStorage.getItem("theme")??getComputedStyle(document.documentElement).getPropertyValue("--default-theme-mode");window.Alpine.store("theme",r==="dark"||r==="system"&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"),window.addEventListener("theme-changed",n=>{let s=n.detail;localStorage.setItem("theme",s),s==="system"&&(s=window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"),window.Alpine.store("theme",s)}),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",n=>{localStorage.getItem("theme")==="system"&&window.Alpine.store("theme",n.matches?"dark":"light")}),window.Alpine.effect(()=>{window.Alpine.store("theme")==="dark"?document.documentElement.classList.add("dark"):document.documentElement.classList.remove("dark")})});document.addEventListener("DOMContentLoaded",()=>{setTimeout(()=>{let r=document.querySelector(".fi-main-sidebar .fi-sidebar-item.fi-active");if((!r||r.offsetParent===null)&&(r=document.querySelector(".fi-main-sidebar .fi-sidebar-group.fi-active")),!r||r.offsetParent===null)return;let n=document.querySelector(".fi-main-sidebar .fi-sidebar-nav");n&&n.scrollTo(0,r.offsetTop-window.innerHeight/2)},10)});window.setUpUnsavedDataChangesAlert=({body:r,livewireComponent:n,$wire:s})=>{window.addEventListener("beforeunload",l=>{window.jsMd5(JSON.stringify(s.data).replace(/\\/g,""))===s.savedDataHash||s?.__instance?.effects?.redirect||(l.preventDefault(),l.returnValue=!0)})};window.setUpSpaModeUnsavedDataChangesAlert=({body:r,resolveLivewireComponentUsing:n,$wire:s})=>{let l=()=>s?.__instance?.effects?.redirect?!1:window.jsMd5(JSON.stringify(s.data).replace(/\\/g,""))!==s.savedDataHash,g=()=>confirm(r);document.addEventListener("livewire:navigate",d=>{if(typeof n()<"u"){if(!l()||g())return;d.preventDefault()}}),window.addEventListener("beforeunload",d=>{l()&&(d.preventDefault(),d.returnValue=!0)})};window.setUpUnsavedActionChangesAlert=({resolveLivewireComponentUsing:r,$wire:n})=>{window.addEventListener("beforeunload",s=>{if(!(typeof r()>"u")&&(n.mountedActions?.length??0)&&!n?.__instance?.effects?.redirect){s.preventDefault(),s.returnValue=!0;return}})};document.addEventListener("alpine:init",()=>{window.Alpine.plugin(W),window.Alpine.store("sidebar",j())});})(); 2 | -------------------------------------------------------------------------------- /config/debugbar.php: -------------------------------------------------------------------------------- 1 | env('DEBUGBAR_ENABLED', false), 20 | 'hide_empty_tabs' => true, // Hide tabs until they have content 21 | 'except' => [ 22 | 'telescope*', 23 | 'horizon*', 24 | ], 25 | 26 | /* 27 | |-------------------------------------------------------------------------- 28 | | Storage settings 29 | |-------------------------------------------------------------------------- 30 | | 31 | | Debugbar stores data for session/ajax requests. 32 | | You can disable this, so the debugbar stores data in headers/session, 33 | | but this can cause problems with large data collectors. 34 | | By default, file storage (in the storage folder) is used. Redis and PDO 35 | | can also be used. For PDO, run the package migrations first. 36 | | 37 | | Warning: Enabling storage.open will allow everyone to access previous 38 | | request, do not enable open storage in publicly available environments! 39 | | Specify a callback if you want to limit based on IP or authentication. 40 | | Leaving it to null will allow localhost only. 41 | */ 42 | 'storage' => [ 43 | 'enabled' => true, 44 | 'open' => env('DEBUGBAR_OPEN_STORAGE'), // bool/callback. 45 | 'driver' => 'file', // redis, file, pdo, socket, custom 46 | 'path' => storage_path('debugbar'), // For file driver 47 | 'connection' => null, // Leave null for default connection (Redis/PDO) 48 | 'provider' => '', // Instance of StorageInterface for custom driver 49 | 'hostname' => '127.0.0.1', // Hostname to use with the "socket" driver 50 | 'port' => 2304, // Port to use with the "socket" driver 51 | ], 52 | 53 | /* 54 | |-------------------------------------------------------------------------- 55 | | Editor 56 | |-------------------------------------------------------------------------- 57 | | 58 | | Choose your preferred editor to use when clicking file name. 59 | | 60 | | Supported: "phpstorm", "vscode", "vscode-insiders", "vscode-remote", 61 | | "vscode-insiders-remote", "vscodium", "textmate", "emacs", 62 | | "sublime", "atom", "nova", "macvim", "idea", "netbeans", 63 | | "xdebug", "espresso" 64 | | 65 | */ 66 | 67 | 'editor' => env('DEBUGBAR_EDITOR') ?: env('IGNITION_EDITOR', 'phpstorm'), 68 | 69 | /* 70 | |-------------------------------------------------------------------------- 71 | | Remote Path Mapping 72 | |-------------------------------------------------------------------------- 73 | | 74 | | If you are using a remote dev server, like Laravel Homestead, Docker, or 75 | | even a remote VPS, it will be necessary to specify your path mapping. 76 | | 77 | | Leaving one, or both of these, empty or null will not trigger the remote 78 | | URL changes and Debugbar will treat your editor links as local files. 79 | | 80 | | "remote_sites_path" is an absolute base path for your sites or projects 81 | | in Homestead, Vagrant, Docker, or another remote development server. 82 | | 83 | | Example value: "/home/vagrant/Code" 84 | | 85 | | "local_sites_path" is an absolute base path for your sites or projects 86 | | on your local computer where your IDE or code editor is running on. 87 | | 88 | | Example values: "/Users//Code", "C:\Users\\Documents\Code" 89 | | 90 | */ 91 | 92 | 'remote_sites_path' => env('DEBUGBAR_REMOTE_SITES_PATH'), 93 | 'local_sites_path' => env('DEBUGBAR_LOCAL_SITES_PATH', env('IGNITION_LOCAL_SITES_PATH')), 94 | 95 | /* 96 | |-------------------------------------------------------------------------- 97 | | Vendors 98 | |-------------------------------------------------------------------------- 99 | | 100 | | Vendor files are included by default, but can be set to false. 101 | | This can also be set to 'js' or 'css', to only include javascript or css vendor files. 102 | | Vendor files are for css: font-awesome (including fonts) and highlight.js (css files) 103 | | and for js: jquery and highlight.js 104 | | So if you want syntax highlighting, set it to true. 105 | | jQuery is set to not conflict with existing jQuery scripts. 106 | | 107 | */ 108 | 109 | 'include_vendors' => true, 110 | 111 | /* 112 | |-------------------------------------------------------------------------- 113 | | Capture Ajax Requests 114 | |-------------------------------------------------------------------------- 115 | | 116 | | The Debugbar can capture Ajax requests and display them. If you don't want this (ie. because of errors), 117 | | you can use this option to disable sending the data through the headers. 118 | | 119 | | Optionally, you can also send ServerTiming headers on ajax requests for the Chrome DevTools. 120 | | 121 | | Note for your request to be identified as ajax requests they must either send the header 122 | | X-Requested-With with the value XMLHttpRequest (most JS libraries send this), or have application/json as a Accept header. 123 | | 124 | | By default `ajax_handler_auto_show` is set to true allowing ajax requests to be shown automatically in the Debugbar. 125 | | Changing `ajax_handler_auto_show` to false will prevent the Debugbar from reloading. 126 | | 127 | | You can defer loading the dataset, so it will be loaded with ajax after the request is done. (Experimental) 128 | */ 129 | 130 | 'capture_ajax' => true, 131 | 'add_ajax_timing' => false, 132 | 'ajax_handler_auto_show' => true, 133 | 'ajax_handler_enable_tab' => true, 134 | 'defer_datasets' => false, 135 | /* 136 | |-------------------------------------------------------------------------- 137 | | Custom Error Handler for Deprecated warnings 138 | |-------------------------------------------------------------------------- 139 | | 140 | | When enabled, the Debugbar shows deprecated warnings for Symfony components 141 | | in the Messages tab. 142 | | 143 | */ 144 | 'error_handler' => false, 145 | 146 | /* 147 | |-------------------------------------------------------------------------- 148 | | Clockwork integration 149 | |-------------------------------------------------------------------------- 150 | | 151 | | The Debugbar can emulate the Clockwork headers, so you can use the Chrome 152 | | Extension, without the server-side code. It uses Debugbar collectors instead. 153 | | 154 | */ 155 | 'clockwork' => false, 156 | 157 | /* 158 | |-------------------------------------------------------------------------- 159 | | DataCollectors 160 | |-------------------------------------------------------------------------- 161 | | 162 | | Enable/disable DataCollectors 163 | | 164 | */ 165 | 166 | 'collectors' => [ 167 | 'phpinfo' => false, // Php version 168 | 'messages' => true, // Messages 169 | 'time' => true, // Time Datalogger 170 | 'memory' => true, // Memory usage 171 | 'exceptions' => true, // Exception displayer 172 | 'log' => true, // Logs from Monolog (merged in messages if enabled) 173 | 'db' => true, // Show database (PDO) queries and bindings 174 | 'views' => true, // Views with their data 175 | 'route' => false, // Current route information 176 | 'auth' => false, // Display Laravel authentication status 177 | 'gate' => true, // Display Laravel Gate checks 178 | 'session' => false, // Display session data 179 | 'symfony_request' => true, // Only one can be enabled.. 180 | 'mail' => true, // Catch mail messages 181 | 'laravel' => true, // Laravel version and environment 182 | 'events' => false, // All events fired 183 | 'default_request' => false, // Regular or special Symfony request logger 184 | 'logs' => false, // Add the latest log messages 185 | 'files' => false, // Show the included files 186 | 'config' => false, // Display config settings 187 | 'cache' => false, // Display cache events 188 | 'models' => true, // Display models 189 | 'livewire' => true, // Display Livewire (when available) 190 | 'jobs' => false, // Display dispatched jobs 191 | 'pennant' => false, // Display Pennant feature flags 192 | ], 193 | 194 | /* 195 | |-------------------------------------------------------------------------- 196 | | Extra options 197 | |-------------------------------------------------------------------------- 198 | | 199 | | Configure some DataCollectors 200 | | 201 | */ 202 | 203 | 'options' => [ 204 | 'time' => [ 205 | 'memory_usage' => false, // Calculated by subtracting memory start and end, it may be inaccurate 206 | ], 207 | 'messages' => [ 208 | 'trace' => true, // Trace the origin of the debug message 209 | 'capture_dumps' => true, // Capture laravel `dump();` as message 210 | ], 211 | 'memory' => [ 212 | 'reset_peak' => false, // run memory_reset_peak_usage before collecting 213 | 'with_baseline' => false, // Set boot memory usage as memory peak baseline 214 | 'precision' => 0, // Memory rounding precision 215 | ], 216 | 'auth' => [ 217 | 'show_name' => true, // Also show the users name/email in the debugbar 218 | 'show_guards' => true, // Show the guards that are used 219 | ], 220 | 'db' => [ 221 | 'with_params' => true, // Render SQL with the parameters substituted 222 | 'exclude_paths' => [ // Paths to exclude entirely from the collector 223 | // 'vendor/laravel/framework/src/Illuminate/Session', // Exclude sessions queries 224 | ], 225 | 'backtrace' => true, // Use a backtrace to find the origin of the query in your files. 226 | 'backtrace_exclude_paths' => [], // Paths to exclude from backtrace. (in addition to defaults) 227 | 'timeline' => false, // Add the queries to the timeline 228 | 'duration_background' => true, // Show shaded background on each query relative to how long it took to execute. 229 | 'explain' => [ // Show EXPLAIN output on queries 230 | 'enabled' => false, 231 | ], 232 | 'hints' => false, // Show hints for common mistakes 233 | 'show_copy' => true, // Show copy button next to the query, 234 | 'slow_threshold' => false, // Only track queries that last longer than this time in ms 235 | 'memory_usage' => false, // Show queries memory usage 236 | 'soft_limit' => 100, // After the soft limit, no parameters/backtrace are captured 237 | 'hard_limit' => 500, // After the hard limit, queries are ignored 238 | ], 239 | 'mail' => [ 240 | 'timeline' => true, // Add mails to the timeline 241 | 'show_body' => true, 242 | ], 243 | 'views' => [ 244 | 'timeline' => true, // Add the views to the timeline 245 | 'data' => false, // True for all data, 'keys' for only names, false for no parameters. 246 | 'group' => 50, // Group duplicate views. Pass value to auto-group, or true/false to force 247 | 'exclude_paths' => [ // Add the paths which you don't want to appear in the views 248 | 'vendor/filament', // Exclude Filament components by default 249 | ], 250 | ], 251 | 'route' => [ 252 | 'label' => true, // Show complete route on bar 253 | ], 254 | 'session' => [ 255 | 'hiddens' => [], // Hides sensitive values using array paths 256 | ], 257 | 'symfony_request' => [ 258 | 'label' => true, // Show route on bar 259 | 'hiddens' => [], // Hides sensitive values using array paths, example: request_request.password 260 | ], 261 | 'events' => [ 262 | 'data' => false, // Collect events data, listeners 263 | ], 264 | 'logs' => [ 265 | 'file' => null, 266 | ], 267 | 'cache' => [ 268 | 'values' => true, // Collect cache values 269 | ], 270 | ], 271 | 272 | /* 273 | |-------------------------------------------------------------------------- 274 | | Inject Debugbar in Response 275 | |-------------------------------------------------------------------------- 276 | | 277 | | Usually, the debugbar is added just before , by listening to the 278 | | Response after the App is done. If you disable this, you have to add them 279 | | in your template yourself. See http://phpdebugbar.com/docs/rendering.html 280 | | 281 | */ 282 | 283 | 'inject' => true, 284 | 285 | /* 286 | |-------------------------------------------------------------------------- 287 | | Debugbar route prefix 288 | |-------------------------------------------------------------------------- 289 | | 290 | | Sometimes you want to set route prefix to be used by Debugbar to load 291 | | its resources from. Usually the need comes from misconfigured web server or 292 | | from trying to overcome bugs like this: http://trac.nginx.org/nginx/ticket/97 293 | | 294 | */ 295 | 'route_prefix' => '_debugbar', 296 | 297 | /* 298 | |-------------------------------------------------------------------------- 299 | | Debugbar route middleware 300 | |-------------------------------------------------------------------------- 301 | | 302 | | Additional middleware to run on the Debugbar routes 303 | */ 304 | 'route_middleware' => [], 305 | 306 | /* 307 | |-------------------------------------------------------------------------- 308 | | Debugbar route domain 309 | |-------------------------------------------------------------------------- 310 | | 311 | | By default Debugbar route served from the same domain that request served. 312 | | To override default domain, specify it as a non-empty value. 313 | */ 314 | 'route_domain' => null, 315 | 316 | /* 317 | |-------------------------------------------------------------------------- 318 | | Debugbar theme 319 | |-------------------------------------------------------------------------- 320 | | 321 | | Switches between light and dark theme. If set to auto it will respect system preferences 322 | | Possible values: auto, light, dark 323 | */ 324 | 'theme' => env('DEBUGBAR_THEME', 'auto'), 325 | 326 | /* 327 | |-------------------------------------------------------------------------- 328 | | Backtrace stack limit 329 | |-------------------------------------------------------------------------- 330 | | 331 | | By default, the Debugbar limits the number of frames returned by the 'debug_backtrace()' function. 332 | | If you need larger stacktraces, you can increase this number. Setting it to 0 will result in no limit. 333 | */ 334 | 'debug_backtrace_limit' => 50, 335 | ]; 336 | --------------------------------------------------------------------------------