OMEGA\lulufj.ho 1 ヶ月 前
コミット
e5e1c7c234
共有100 個のファイルを変更した13744 個の追加0 個の削除を含む
  1. 18
    0
      .editorconfig
  2. 65
    0
      .env.example
  3. 11
    0
      .gitattributes
  4. 24
    0
      .gitignore
  5. 58
    0
      README.md
  6. 43
    0
      app/Filament/Exports/QrcodeRecordExporter.php
  7. 11
    0
      app/Filament/Resources/QrcodeRecords/Pages/CreateQrcodeRecord.php
  8. 19
    0
      app/Filament/Resources/QrcodeRecords/Pages/EditQrcodeRecord.php
  9. 24
    0
      app/Filament/Resources/QrcodeRecords/Pages/ListQrcodeRecords.php
  10. 50
    0
      app/Filament/Resources/QrcodeRecords/QrcodeRecordResource.php
  11. 28
    0
      app/Filament/Resources/QrcodeRecords/Schemas/QrcodeRecordForm.php
  12. 67
    0
      app/Filament/Resources/QrcodeRecords/Tables/QrcodeRecordsTable.php
  13. 21
    0
      app/Helpers/PhoneHelper.php
  14. 81
    0
      app/Http/Controllers/Api/QrcodeController.php
  15. 8
    0
      app/Http/Controllers/Controller.php
  16. 20
    0
      app/Models/QrcodeRecord.php
  17. 32
    0
      app/Models/User.php
  18. 24
    0
      app/Providers/AppServiceProvider.php
  19. 60
    0
      app/Providers/Filament/AdminPanelProvider.php
  20. 18
    0
      artisan
  21. 19
    0
      bootstrap/app.php
  22. 2
    0
      bootstrap/cache/.gitignore
  23. 6
    0
      bootstrap/providers.php
  24. 87
    0
      composer.json
  25. 10276
    0
      composer.lock
  26. 129
    0
      config/app.php
  27. 117
    0
      config/auth.php
  28. 130
    0
      config/cache.php
  29. 184
    0
      config/database.php
  30. 120
    0
      config/filament.php
  31. 80
    0
      config/filesystems.php
  32. 132
    0
      config/logging.php
  33. 118
    0
      config/mail.php
  34. 129
    0
      config/queue.php
  35. 38
    0
      config/services.php
  36. 233
    0
      config/session.php
  37. 1
    0
      database/.gitignore
  38. 45
    0
      database/factories/UserFactory.php
  39. 49
    0
      database/migrations/0001_01_01_000000_create_users_table.php
  40. 35
    0
      database/migrations/0001_01_01_000001_create_cache_table.php
  41. 57
    0
      database/migrations/0001_01_01_000002_create_jobs_table.php
  42. 32
    0
      database/migrations/2026_04_17_104840_create_qrcode_records_table.php
  43. 31
    0
      database/migrations/2026_04_21_162744_create_notifications_table.php
  44. 35
    0
      database/migrations/2026_04_21_162751_create_imports_table.php
  45. 35
    0
      database/migrations/2026_04_21_162752_create_exports_table.php
  46. 30
    0
      database/migrations/2026_04_21_162753_create_failed_import_rows_table.php
  47. 25
    0
      database/seeders/DatabaseSeeder.php
  48. 17
    0
      package.json
  49. 36
    0
      phpunit.xml
  50. 25
    0
      public/.htaccess
  51. 2
    0
      public/css/filament/filament/app.css
  52. 0
    0
      public/favicon.ico
  53. 1
    0
      public/fonts/filament/filament/inter/index.css
  54. バイナリ
      public/fonts/filament/filament/inter/inter-cyrillic-ext-wght-normal-IYF56FF6.woff2
  55. バイナリ
      public/fonts/filament/filament/inter/inter-cyrillic-wght-normal-JEOLYBOO.woff2
  56. バイナリ
      public/fonts/filament/filament/inter/inter-greek-ext-wght-normal-EOVOK2B5.woff2
  57. バイナリ
      public/fonts/filament/filament/inter/inter-greek-wght-normal-IRE366VL.woff2
  58. バイナリ
      public/fonts/filament/filament/inter/inter-latin-ext-wght-normal-HA22NDSG.woff2
  59. バイナリ
      public/fonts/filament/filament/inter/inter-latin-wght-normal-NRMW37G5.woff2
  60. バイナリ
      public/fonts/filament/filament/inter/inter-vietnamese-wght-normal-CE5GGD3W.woff2
  61. 20
    0
      public/index.php
  62. 1
    0
      public/js/filament/actions/actions.js
  63. 1
    0
      public/js/filament/filament/app.js
  64. 13
    0
      public/js/filament/filament/echo.js
  65. 1
    0
      public/js/filament/forms/components/checkbox-list.js
  66. 38
    0
      public/js/filament/forms/components/code-editor.js
  67. 1
    0
      public/js/filament/forms/components/color-picker.js
  68. 1
    0
      public/js/filament/forms/components/date-time-picker.js
  69. 116
    0
      public/js/filament/forms/components/file-upload.js
  70. 1
    0
      public/js/filament/forms/components/key-value.js
  71. 51
    0
      public/js/filament/forms/components/markdown-editor.js
  72. 144
    0
      public/js/filament/forms/components/rich-editor.js
  73. 11
    0
      public/js/filament/forms/components/select.js
  74. 1
    0
      public/js/filament/forms/components/slider.js
  75. 1
    0
      public/js/filament/forms/components/tags-input.js
  76. 1
    0
      public/js/filament/forms/components/textarea.js
  77. 1
    0
      public/js/filament/notifications/notifications.js
  78. 1
    0
      public/js/filament/schemas/components/actions.js
  79. 1
    0
      public/js/filament/schemas/components/tabs.js
  80. 1
    0
      public/js/filament/schemas/components/wizard.js
  81. 1
    0
      public/js/filament/schemas/schemas.js
  82. 46
    0
      public/js/filament/support/support.js
  83. 1
    0
      public/js/filament/tables/components/columns/checkbox.js
  84. 11
    0
      public/js/filament/tables/components/columns/select.js
  85. 1
    0
      public/js/filament/tables/components/columns/text-input.js
  86. 1
    0
      public/js/filament/tables/components/columns/toggle.js
  87. 1
    0
      public/js/filament/tables/tables.js
  88. 30
    0
      public/js/filament/widgets/components/chart.js
  89. 22
    0
      public/js/filament/widgets/components/stats-overview/stat/chart.js
  90. 2
    0
      public/robots.txt
  91. 11
    0
      resources/css/app.css
  92. 1
    0
      resources/js/app.js
  93. 4
    0
      resources/js/bootstrap.js
  94. 225
    0
      resources/views/welcome.blade.php
  95. 22
    0
      routes/api.php
  96. 8
    0
      routes/console.php
  97. 7
    0
      routes/web.php
  98. 4
    0
      storage/app/.gitignore
  99. 2
    0
      storage/app/private/.gitignore
  100. 0
    0
      storage/app/public/.gitignore

+ 18
- 0
.editorconfig ファイルの表示

@@ -0,0 +1,18 @@
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
+[compose.yaml]
18
+indent_size = 4

+ 65
- 0
.env.example ファイルの表示

@@ -0,0 +1,65 @@
1
+APP_NAME=Laravel
2
+APP_ENV=local
3
+APP_KEY=
4
+APP_DEBUG=true
5
+APP_URL=http://localhost
6
+
7
+APP_LOCALE=en
8
+APP_FALLBACK_LOCALE=en
9
+APP_FAKER_LOCALE=en_US
10
+
11
+APP_MAINTENANCE_DRIVER=file
12
+# APP_MAINTENANCE_STORE=database
13
+
14
+# PHP_CLI_SERVER_WORKERS=4
15
+
16
+BCRYPT_ROUNDS=12
17
+
18
+LOG_CHANNEL=stack
19
+LOG_STACK=single
20
+LOG_DEPRECATIONS_CHANNEL=null
21
+LOG_LEVEL=debug
22
+
23
+DB_CONNECTION=sqlite
24
+# DB_HOST=127.0.0.1
25
+# DB_PORT=3306
26
+# DB_DATABASE=laravel
27
+# DB_USERNAME=root
28
+# DB_PASSWORD=
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=2525
54
+MAIL_USERNAME=null
55
+MAIL_PASSWORD=null
56
+MAIL_FROM_ADDRESS="hello@example.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}"

+ 11
- 0
.gitattributes ファイルの表示

@@ -0,0 +1,11 @@
1
+* text=auto eol=lf
2
+
3
+*.blade.php diff=html
4
+*.css diff=css
5
+*.html diff=html
6
+*.md diff=markdown
7
+*.php diff=php
8
+
9
+/.github export-ignore
10
+CHANGELOG.md export-ignore
11
+.styleci.yml export-ignore

+ 24
- 0
.gitignore ファイルの表示

@@ -0,0 +1,24 @@
1
+*.log
2
+.DS_Store
3
+.env
4
+.env.backup
5
+.env.production
6
+.phpactor.json
7
+.phpunit.result.cache
8
+/.fleet
9
+/.idea
10
+/.nova
11
+/.phpunit.cache
12
+/.vscode
13
+/.zed
14
+/auth.json
15
+/node_modules
16
+/public/build
17
+/public/hot
18
+/public/storage
19
+/storage/*.key
20
+/storage/pail
21
+/vendor
22
+Homestead.json
23
+Homestead.yaml
24
+Thumbs.db

+ 58
- 0
README.md ファイルの表示

@@ -0,0 +1,58 @@
1
+<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
2
+
3
+<p align="center">
4
+<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
5
+<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
6
+<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
7
+<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
8
+</p>
9
+
10
+## About Laravel
11
+
12
+Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
13
+
14
+- [Simple, fast routing engine](https://laravel.com/docs/routing).
15
+- [Powerful dependency injection container](https://laravel.com/docs/container).
16
+- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
17
+- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
18
+- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
19
+- [Robust background job processing](https://laravel.com/docs/queues).
20
+- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
21
+
22
+Laravel is accessible, powerful, and provides tools required for large, robust applications.
23
+
24
+## Learning Laravel
25
+
26
+Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework.
27
+
28
+In addition, [Laracasts](https://laracasts.com) contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
29
+
30
+You can also watch bite-sized lessons with real-world projects on [Laravel Learn](https://laravel.com/learn), where you will be guided through building a Laravel application from scratch while learning PHP fundamentals.
31
+
32
+## Agentic Development
33
+
34
+Laravel's predictable structure and conventions make it ideal for AI coding agents like Claude Code, Cursor, and GitHub Copilot. Install [Laravel Boost](https://laravel.com/docs/ai) to supercharge your AI workflow:
35
+
36
+```bash
37
+composer require laravel/boost --dev
38
+
39
+php artisan boost:install
40
+```
41
+
42
+Boost provides your agent 15+ tools and skills that help agents build Laravel applications while following best practices.
43
+
44
+## Contributing
45
+
46
+Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
47
+
48
+## Code of Conduct
49
+
50
+In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
51
+
52
+## Security Vulnerabilities
53
+
54
+If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
55
+
56
+## License
57
+
58
+The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).

+ 43
- 0
app/Filament/Exports/QrcodeRecordExporter.php ファイルの表示

@@ -0,0 +1,43 @@
1
+<?php
2
+
3
+namespace App\Filament\Exports;
4
+
5
+use App\Models\QrcodeRecord;
6
+use Filament\Actions\Exports\ExportColumn;
7
+use Filament\Actions\Exports\Exporter;
8
+use Filament\Actions\Exports\Models\Export;
9
+use Illuminate\Support\Number;
10
+
11
+class QrcodeRecordExporter extends Exporter
12
+{
13
+    protected static ?string $model = QrcodeRecord::class;
14
+
15
+    public static function getColumns(): array
16
+    {
17
+        return [
18
+
19
+            ExportColumn::make('outlet_id')
20
+            ->label('店號'),
21
+            ExportColumn::make('outlet_name')
22
+            ->label('店名'),
23
+            ExportColumn::make('name')
24
+            ->label('姓名'),
25
+            ExportColumn::make('phone')
26
+            ->label('手機'),
27
+            ExportColumn::make('qr_generated_at')
28
+            ->label('QR Code 產生時間'),
29
+
30
+        ];
31
+    }
32
+
33
+    public static function getCompletedNotificationBody(Export $export): string
34
+    {
35
+        $body = 'Your qrcode record export has completed and ' . Number::format($export->successful_rows) . ' ' . str('row')->plural($export->successful_rows) . ' exported.';
36
+
37
+        if ($failedRowsCount = $export->getFailedRowsCount()) {
38
+            $body .= ' ' . Number::format($failedRowsCount) . ' ' . str('row')->plural($failedRowsCount) . ' failed to export.';
39
+        }
40
+
41
+        return $body;
42
+    }
43
+}

+ 11
- 0
app/Filament/Resources/QrcodeRecords/Pages/CreateQrcodeRecord.php ファイルの表示

@@ -0,0 +1,11 @@
1
+<?php
2
+
3
+namespace App\Filament\Resources\QrcodeRecords\Pages;
4
+
5
+use App\Filament\Resources\QrcodeRecords\QrcodeRecordResource;
6
+use Filament\Resources\Pages\CreateRecord;
7
+
8
+class CreateQrcodeRecord extends CreateRecord
9
+{
10
+    protected static string $resource = QrcodeRecordResource::class;
11
+}

+ 19
- 0
app/Filament/Resources/QrcodeRecords/Pages/EditQrcodeRecord.php ファイルの表示

@@ -0,0 +1,19 @@
1
+<?php
2
+
3
+namespace App\Filament\Resources\QrcodeRecords\Pages;
4
+
5
+use App\Filament\Resources\QrcodeRecords\QrcodeRecordResource;
6
+use Filament\Actions\DeleteAction;
7
+use Filament\Resources\Pages\EditRecord;
8
+
9
+class EditQrcodeRecord extends EditRecord
10
+{
11
+    protected static string $resource = QrcodeRecordResource::class;
12
+
13
+    protected function getHeaderActions(): array
14
+    {
15
+        return [
16
+            DeleteAction::make(),
17
+        ];
18
+    }
19
+}

+ 24
- 0
app/Filament/Resources/QrcodeRecords/Pages/ListQrcodeRecords.php ファイルの表示

@@ -0,0 +1,24 @@
1
+<?php
2
+
3
+namespace App\Filament\Resources\QrcodeRecords\Pages;
4
+
5
+use App\Filament\Resources\QrcodeRecords\QrcodeRecordResource;
6
+use Filament\Actions\CreateAction;
7
+use Filament\Resources\Pages\ListRecords;
8
+use Filament\Actions\ExportAction;
9
+use App\Filament\Exports\QrcodeRecordExporter;
10
+
11
+class ListQrcodeRecords extends ListRecords
12
+{
13
+    protected static string $resource = QrcodeRecordResource::class;
14
+
15
+    protected function getHeaderActions(): array
16
+    {
17
+        return [
18
+            ExportAction::make()
19
+                ->exporter(QrcodeRecordExporter::class),
20
+        ];
21
+    }
22
+
23
+    
24
+}

+ 50
- 0
app/Filament/Resources/QrcodeRecords/QrcodeRecordResource.php ファイルの表示

@@ -0,0 +1,50 @@
1
+<?php
2
+
3
+namespace App\Filament\Resources\QrcodeRecords;
4
+
5
+use App\Filament\Resources\QrcodeRecords\Pages\CreateQrcodeRecord;
6
+use App\Filament\Resources\QrcodeRecords\Pages\EditQrcodeRecord;
7
+use App\Filament\Resources\QrcodeRecords\Pages\ListQrcodeRecords;
8
+use App\Filament\Resources\QrcodeRecords\Schemas\QrcodeRecordForm;
9
+use App\Filament\Resources\QrcodeRecords\Tables\QrcodeRecordsTable;
10
+use App\Models\QrcodeRecord;
11
+use BackedEnum;
12
+use Filament\Resources\Resource;
13
+use Filament\Schemas\Schema;
14
+use Filament\Support\Icons\Heroicon;
15
+use Filament\Tables\Table;
16
+
17
+class QrcodeRecordResource extends Resource
18
+{
19
+    protected static ?string $model = QrcodeRecord::class;
20
+
21
+    protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack;
22
+
23
+    protected static ?string $recordTitleAttribute = 'table';
24
+
25
+    public static function form(Schema $schema): Schema
26
+    {
27
+        return QrcodeRecordForm::configure($schema);
28
+    }
29
+
30
+    public static function table(Table $table): Table
31
+    {
32
+        return QrcodeRecordsTable::configure($table);
33
+    }
34
+
35
+    public static function getRelations(): array
36
+    {
37
+        return [
38
+            //
39
+        ];
40
+    }
41
+    public static function canCreate(): bool { return false; }
42
+    public static function canEdit($record): bool { return false; }
43
+    public static function canDelete($record): bool { return false; }
44
+    public static function getPages(): array
45
+    {
46
+        return [
47
+            'index' => ListQrcodeRecords::route('/'),
48
+        ];
49
+    }
50
+}

+ 28
- 0
app/Filament/Resources/QrcodeRecords/Schemas/QrcodeRecordForm.php ファイルの表示

@@ -0,0 +1,28 @@
1
+<?php
2
+
3
+namespace App\Filament\Resources\QrcodeRecords\Schemas;
4
+
5
+use Filament\Forms\Components\DateTimePicker;
6
+use Filament\Forms\Components\TextInput;
7
+use Filament\Schemas\Schema;
8
+
9
+class QrcodeRecordForm
10
+{
11
+    public static function configure(Schema $schema): Schema
12
+    {
13
+        return $schema
14
+            ->components([
15
+                TextInput::make('outlet_id')
16
+                    ->required(),
17
+                TextInput::make('outlet_name')
18
+                    ->required(),
19
+                TextInput::make('name')
20
+                    ->required(),
21
+                TextInput::make('phone')
22
+                    ->tel()
23
+                    ->required(),
24
+                DateTimePicker::make('qr_generated_at')
25
+                    ->required(),
26
+            ]);
27
+    }
28
+}

+ 67
- 0
app/Filament/Resources/QrcodeRecords/Tables/QrcodeRecordsTable.php ファイルの表示

@@ -0,0 +1,67 @@
1
+<?php
2
+
3
+namespace App\Filament\Resources\QrcodeRecords\Tables;
4
+
5
+use Filament\Actions\BulkActionGroup;
6
+use Filament\Actions\DeleteBulkAction;
7
+use Filament\Actions\EditAction;
8
+use Filament\Tables\Columns\TextColumn;
9
+use Filament\Tables\Table;
10
+use Filament\Tables\Filters\Filter;
11
+use Filament\Forms\Components\DatePicker;
12
+use Illuminate\Database\Eloquent\Builder;
13
+
14
+class QrcodeRecordsTable
15
+{
16
+    public static function configure(Table $table): Table
17
+    {
18
+        return $table
19
+            ->columns([
20
+                TextColumn::make('outlet_id')
21
+                    ->searchable()
22
+                    ->label('店號'),
23
+                TextColumn::make('outlet_name')
24
+                    ->searchable()
25
+                    ->label('店名'),
26
+                TextColumn::make('name')
27
+                    ->searchable()
28
+                    ->label('姓名'),
29
+                TextColumn::make('phone')
30
+                    ->searchable()
31
+                    ->label('手機'),
32
+                TextColumn::make('qr_generated_at')
33
+                    ->dateTime()
34
+                    ->sortable()
35
+                    ->label('QR Code 產生時間'),
36
+                TextColumn::make('created_at')
37
+                    ->dateTime()
38
+                    ->sortable()
39
+                    ->toggleable(isToggledHiddenByDefault: true),
40
+                TextColumn::make('updated_at')
41
+                    ->dateTime()
42
+                    ->sortable()
43
+                    ->toggleable(isToggledHiddenByDefault: true),
44
+            ])
45
+            ->filters([
46
+                Filter::make('qr_generated_at')
47
+                    ->form([
48
+                        DatePicker::make('from')->label('開始日期'),
49
+                        DatePicker::make('until')->label('結束日期'),
50
+                    ])
51
+                    ->query(function (Builder $query, array $data) {
52
+                        return $query
53
+                            ->when($data['from'], fn ($q) => $q->whereDate('qr_generated_at', '>=', $data['from']))
54
+                            ->when($data['until'], fn ($q) => $q->whereDate('qr_generated_at', '<=', $data['until']));
55
+                    }),
56
+            ])
57
+            ->recordActions([
58
+                EditAction::make(),
59
+            ])
60
+            ->toolbarActions([
61
+                BulkActionGroup::make([
62
+                    DeleteBulkAction::make(),
63
+                ]),
64
+            ])
65
+            ->searchPlaceholder('請輸入店號、店名、姓名或電話進行搜尋');
66
+    }
67
+}

+ 21
- 0
app/Helpers/PhoneHelper.php ファイルの表示

@@ -0,0 +1,21 @@
1
+<?php
2
+
3
+namespace App\Helpers;
4
+
5
+class PhoneHelper
6
+{
7
+    public static function encrypt(string $phone): string
8
+    {
9
+        $key = config('app.phone_encrypt_key');
10
+        $iv = random_bytes(16);
11
+        $encrypted = openssl_encrypt($phone, 'AES-128-CBC', $key, 0, $iv);
12
+        return base64_encode($iv . '||' . $encrypted);
13
+    }
14
+
15
+    public static function decrypt(string $encrypted): string
16
+    {
17
+        $key = config('app.phone_encrypt_key');
18
+        [$iv, $data] = explode('||', base64_decode($encrypted));
19
+        return openssl_decrypt($data, 'AES-128-CBC', $key, 0, $iv);
20
+    }
21
+}

+ 81
- 0
app/Http/Controllers/Api/QrcodeController.php ファイルの表示

@@ -0,0 +1,81 @@
1
+<?php
2
+
3
+namespace App\Http\Controllers\Api;
4
+
5
+use App\Http\Controllers\Controller;
6
+use App\Helpers\PhoneHelper;
7
+use App\Models\QrcodeRecord;
8
+use Illuminate\Http\Request;
9
+use Illuminate\Support\Facades\Http;
10
+
11
+class QrcodeController extends Controller
12
+{
13
+    public function generate(Request $request)
14
+    {
15
+        $validator = \Validator::make($request->all(), [
16
+            'tel' => 'required|digits:10',
17
+        ]);
18
+
19
+        if ($validator->fails()) {
20
+            return response()->json([
21
+                'success' => false,
22
+                'message' => $validator->errors()->first(),
23
+            ], 422);
24
+        }
25
+
26
+        $tel = $request->input('tel');
27
+        $outletid = '';
28
+        $logkey = config('app.oneapp_logkey');
29
+        $baseUrl = config('app.oneapp_base_url');
30
+
31
+        // Step 1: 取得 oneTimeKey
32
+        $step1 = Http::post("{$baseUrl}/API/GetPrivateKey.ashx", [
33
+            'outletid' => $outletid,
34
+            'tel'      => $tel,
35
+            'logkey'   => $logkey,
36
+        ]);
37
+
38
+        if (!$step1->json('Success')) {
39
+            return response()->json(['success' => false, 'message' => 'GetPrivateKey failed'], 400);
40
+        }
41
+
42
+        $oneTimeKey = $step1->json('data.oneTimeKey');
43
+
44
+        // Step 2: 取得 User Data
45
+        $step2 = Http::post("{$baseUrl}/API/GetUserData.ashx", [
46
+            'oneTimeKey' => $oneTimeKey,
47
+        ]);
48
+
49
+        if (!$step2->json('Success')) {
50
+            return response()->json(['success' => false, 'message' => 'GetUserData failed'], 400);
51
+        }
52
+
53
+        $data = $step2->json('data');
54
+
55
+        // Step 3: 處理資料
56
+        $encryptedPhone = PhoneHelper::encrypt($tel);
57
+        $maskedPhone = substr($tel, 0, 2) . '*****' . substr($tel, 7);
58
+
59
+        // Step 4: 寫入紀錄
60
+        QrcodeRecord::create([
61
+            'outlet_id'      => $data['outletid'],
62
+            'outlet_name'    => $data['name'],
63
+            'name'           => $data['username'],
64
+            'phone'          => $encryptedPhone,
65
+            'phone'          => $tel,
66
+            'qr_generated_at' => now(),
67
+        ]);
68
+
69
+        // Step 5: 回傳
70
+        return response()->json([
71
+            'success' => true,
72
+            'data' => [
73
+                'otid'         => $data['otid'],
74
+                'outletid'     => $data['outletid'],
75
+                'name'         => $data['name'],
76
+                'phone'        => $encryptedPhone,
77
+                'phone_masked' => $maskedPhone,
78
+            ],
79
+        ]);
80
+    }
81
+}

+ 8
- 0
app/Http/Controllers/Controller.php ファイルの表示

@@ -0,0 +1,8 @@
1
+<?php
2
+
3
+namespace App\Http\Controllers;
4
+
5
+abstract class Controller
6
+{
7
+    //
8
+}

+ 20
- 0
app/Models/QrcodeRecord.php ファイルの表示

@@ -0,0 +1,20 @@
1
+<?php
2
+
3
+namespace App\Models;
4
+
5
+use Illuminate\Database\Eloquent\Model;
6
+
7
+class QrcodeRecord extends Model
8
+{
9
+    protected $fillable = [
10
+        'outlet_id',
11
+        'outlet_name',
12
+        'name',
13
+        'phone',
14
+        'qr_generated_at',
15
+    ];
16
+
17
+    protected $casts = [
18
+        'qr_generated_at' => 'datetime',
19
+    ];
20
+}

+ 32
- 0
app/Models/User.php ファイルの表示

@@ -0,0 +1,32 @@
1
+<?php
2
+
3
+namespace App\Models;
4
+
5
+// use Illuminate\Contracts\Auth\MustVerifyEmail;
6
+use Database\Factories\UserFactory;
7
+use Illuminate\Database\Eloquent\Attributes\Fillable;
8
+use Illuminate\Database\Eloquent\Attributes\Hidden;
9
+use Illuminate\Database\Eloquent\Factories\HasFactory;
10
+use Illuminate\Foundation\Auth\User as Authenticatable;
11
+use Illuminate\Notifications\Notifiable;
12
+
13
+#[Fillable(['name', 'email', 'password'])]
14
+#[Hidden(['password', 'remember_token'])]
15
+class User extends Authenticatable
16
+{
17
+    /** @use HasFactory<UserFactory> */
18
+    use HasFactory, Notifiable;
19
+
20
+    /**
21
+     * Get the attributes that should be cast.
22
+     *
23
+     * @return array<string, string>
24
+     */
25
+    protected function casts(): array
26
+    {
27
+        return [
28
+            'email_verified_at' => 'datetime',
29
+            'password' => 'hashed',
30
+        ];
31
+    }
32
+}

+ 24
- 0
app/Providers/AppServiceProvider.php ファイルの表示

@@ -0,0 +1,24 @@
1
+<?php
2
+
3
+namespace App\Providers;
4
+
5
+use Illuminate\Support\ServiceProvider;
6
+
7
+class AppServiceProvider extends ServiceProvider
8
+{
9
+    /**
10
+     * Register any application services.
11
+     */
12
+    public function register(): void
13
+    {
14
+        //
15
+    }
16
+
17
+    /**
18
+     * Bootstrap any application services.
19
+     */
20
+    public function boot(): void
21
+    {
22
+        //
23
+    }
24
+}

+ 60
- 0
app/Providers/Filament/AdminPanelProvider.php ファイルの表示

@@ -0,0 +1,60 @@
1
+<?php
2
+
3
+namespace App\Providers\Filament;
4
+
5
+use Filament\Http\Middleware\Authenticate;
6
+use Filament\Http\Middleware\AuthenticateSession;
7
+use Filament\Http\Middleware\DisableBladeIconComponents;
8
+use Filament\Http\Middleware\DispatchServingFilamentEvent;
9
+use Filament\Pages\Dashboard;
10
+use Filament\Panel;
11
+use Filament\PanelProvider;
12
+use Filament\Support\Colors\Color;
13
+use Filament\Widgets\AccountWidget;
14
+use Filament\Widgets\FilamentInfoWidget;
15
+use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
16
+use Illuminate\Cookie\Middleware\EncryptCookies;
17
+use Illuminate\Foundation\Http\Middleware\PreventRequestForgery;
18
+use Illuminate\Routing\Middleware\SubstituteBindings;
19
+use Illuminate\Session\Middleware\StartSession;
20
+use Illuminate\View\Middleware\ShareErrorsFromSession;
21
+
22
+class AdminPanelProvider extends PanelProvider
23
+{
24
+    public function panel(Panel $panel): Panel
25
+    {
26
+        return $panel
27
+            ->default()
28
+            ->id('admin')
29
+            ->path('admin')
30
+            ->login()
31
+            ->colors([
32
+                'primary' => Color::Amber,
33
+            ])
34
+            ->discoverResources(in: app_path('Filament/Resources'), for: 'App\Filament\Resources')
35
+            ->discoverPages(in: app_path('Filament/Pages'), for: 'App\Filament\Pages')
36
+            ->pages([
37
+                Dashboard::class,
38
+            ])
39
+            ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\Filament\Widgets')
40
+            ->widgets([
41
+                AccountWidget::class,
42
+                FilamentInfoWidget::class,
43
+            ])
44
+            ->middleware([
45
+                EncryptCookies::class,
46
+                AddQueuedCookiesToResponse::class,
47
+                StartSession::class,
48
+                AuthenticateSession::class,
49
+                ShareErrorsFromSession::class,
50
+                PreventRequestForgery::class,
51
+                SubstituteBindings::class,
52
+                DisableBladeIconComponents::class,
53
+                DispatchServingFilamentEvent::class,
54
+            ])
55
+            ->authMiddleware([
56
+                Authenticate::class,
57
+            ])
58
+            ->databaseNotifications();
59
+    }
60
+}

+ 18
- 0
artisan ファイルの表示

@@ -0,0 +1,18 @@
1
+#!/usr/bin/env php
2
+<?php
3
+
4
+use Illuminate\Foundation\Application;
5
+use Symfony\Component\Console\Input\ArgvInput;
6
+
7
+define('LARAVEL_START', microtime(true));
8
+
9
+// Register the Composer autoloader...
10
+require __DIR__.'/vendor/autoload.php';
11
+
12
+// Bootstrap Laravel and handle the command...
13
+/** @var Application $app */
14
+$app = require_once __DIR__.'/bootstrap/app.php';
15
+
16
+$status = $app->handleCommand(new ArgvInput);
17
+
18
+exit($status);

+ 19
- 0
bootstrap/app.php ファイルの表示

@@ -0,0 +1,19 @@
1
+<?php
2
+
3
+use Illuminate\Foundation\Application;
4
+use Illuminate\Foundation\Configuration\Exceptions;
5
+use Illuminate\Foundation\Configuration\Middleware;
6
+
7
+return Application::configure(basePath: dirname(__DIR__))
8
+    ->withRouting(
9
+        web: __DIR__.'/../routes/web.php',
10
+        api: __DIR__.'/../routes/api.php',
11
+        commands: __DIR__.'/../routes/console.php',
12
+        health: '/up',
13
+    )
14
+    ->withMiddleware(function (Middleware $middleware): void {
15
+        //
16
+    })
17
+    ->withExceptions(function (Exceptions $exceptions): void {
18
+        //
19
+    })->create();

+ 2
- 0
bootstrap/cache/.gitignore ファイルの表示

@@ -0,0 +1,2 @@
1
+*
2
+!.gitignore

+ 6
- 0
bootstrap/providers.php ファイルの表示

@@ -0,0 +1,6 @@
1
+<?php
2
+
3
+return [
4
+    App\Providers\AppServiceProvider::class,
5
+    App\Providers\Filament\AdminPanelProvider::class,
6
+];

+ 87
- 0
composer.json ファイルの表示

@@ -0,0 +1,87 @@
1
+{
2
+    "$schema": "https://getcomposer.org/schema.json",
3
+    "name": "laravel/laravel",
4
+    "type": "project",
5
+    "description": "The skeleton application for the Laravel framework.",
6
+    "keywords": ["laravel", "framework"],
7
+    "license": "MIT",
8
+    "require": {
9
+        "php": "^8.3",
10
+        "filament/filament": "^5.0",
11
+        "laravel/framework": "^13.0",
12
+        "laravel/tinker": "^3.0"
13
+    },
14
+    "require-dev": {
15
+        "fakerphp/faker": "^1.23",
16
+        "laravel/pail": "^1.2.5",
17
+        "laravel/pint": "^1.27",
18
+        "mockery/mockery": "^1.6",
19
+        "nunomaduro/collision": "^8.6",
20
+        "phpunit/phpunit": "^12.5.12"
21
+    },
22
+    "autoload": {
23
+        "psr-4": {
24
+            "App\\": "app/",
25
+            "Database\\Factories\\": "database/factories/",
26
+            "Database\\Seeders\\": "database/seeders/"
27
+        }
28
+    },
29
+    "autoload-dev": {
30
+        "psr-4": {
31
+            "Tests\\": "tests/"
32
+        }
33
+    },
34
+    "scripts": {
35
+        "setup": [
36
+            "composer install",
37
+            "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"",
38
+            "@php artisan key:generate",
39
+            "@php artisan migrate --force",
40
+            "npm install",
41
+            "npm run build"
42
+        ],
43
+        "dev": [
44
+            "Composer\\Config::disableProcessTimeout",
45
+            "npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1 --timeout=0\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite --kill-others"
46
+        ],
47
+        "test": [
48
+            "@php artisan config:clear --ansi",
49
+            "@php artisan test"
50
+        ],
51
+        "post-autoload-dump": [
52
+            "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
53
+            "@php artisan package:discover --ansi",
54
+            "@php artisan filament:upgrade"
55
+        ],
56
+        "post-update-cmd": [
57
+            "@php artisan vendor:publish --tag=laravel-assets --ansi --force"
58
+        ],
59
+        "post-root-package-install": [
60
+            "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
61
+        ],
62
+        "post-create-project-cmd": [
63
+            "@php artisan key:generate --ansi",
64
+            "@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
65
+            "@php artisan migrate --graceful --ansi"
66
+        ],
67
+        "pre-package-uninstall": [
68
+            "Illuminate\\Foundation\\ComposerScripts::prePackageUninstall"
69
+        ]
70
+    },
71
+    "extra": {
72
+        "laravel": {
73
+            "dont-discover": []
74
+        }
75
+    },
76
+    "config": {
77
+        "optimize-autoloader": true,
78
+        "preferred-install": "dist",
79
+        "sort-packages": true,
80
+        "allow-plugins": {
81
+            "pestphp/pest-plugin": true,
82
+            "php-http/discovery": true
83
+        }
84
+    },
85
+    "minimum-stability": "dev",
86
+    "prefer-stable": true
87
+}

+ 10276
- 0
composer.lock
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 129
- 0
config/app.php ファイルの表示

@@ -0,0 +1,129 @@
1
+<?php
2
+
3
+return [
4
+
5
+    /*
6
+    |--------------------------------------------------------------------------
7
+    | Application Name
8
+    |--------------------------------------------------------------------------
9
+    |
10
+    | This value is the name of your application, which will be used when the
11
+    | framework needs to place the application's name in a notification or
12
+    | other UI elements where an application name needs to be displayed.
13
+    |
14
+    */
15
+
16
+    'name' => env('APP_NAME', 'Laravel'),
17
+
18
+    /*
19
+    |--------------------------------------------------------------------------
20
+    | Application Environment
21
+    |--------------------------------------------------------------------------
22
+    |
23
+    | This value determines the "environment" your application is currently
24
+    | running in. This may determine how you prefer to configure various
25
+    | services the application utilizes. Set this in your ".env" file.
26
+    |
27
+    */
28
+
29
+    'env' => env('APP_ENV', 'production'),
30
+
31
+    /*
32
+    |--------------------------------------------------------------------------
33
+    | Application Debug Mode
34
+    |--------------------------------------------------------------------------
35
+    |
36
+    | When your application is in debug mode, detailed error messages with
37
+    | stack traces will be shown on every error that occurs within your
38
+    | application. If disabled, a simple generic error page is shown.
39
+    |
40
+    */
41
+
42
+    'debug' => (bool) env('APP_DEBUG', false),
43
+
44
+    /*
45
+    |--------------------------------------------------------------------------
46
+    | Application URL
47
+    |--------------------------------------------------------------------------
48
+    |
49
+    | This URL is used by the console to properly generate URLs when using
50
+    | the Artisan command line tool. You should set this to the root of
51
+    | the application so that it's available within Artisan commands.
52
+    |
53
+    */
54
+
55
+    'url' => env('APP_URL', 'http://localhost'),
56
+
57
+    /*
58
+    |--------------------------------------------------------------------------
59
+    | Application Timezone
60
+    |--------------------------------------------------------------------------
61
+    |
62
+    | Here you may specify the default timezone for your application, which
63
+    | will be used by the PHP date and date-time functions. The timezone
64
+    | is set to "UTC" by default as it is suitable for most use cases.
65
+    |
66
+    */
67
+
68
+    'timezone' => 'Asia/Taipei',
69
+
70
+    /*
71
+    |--------------------------------------------------------------------------
72
+    | Application Locale Configuration
73
+    |--------------------------------------------------------------------------
74
+    |
75
+    | The application locale determines the default locale that will be used
76
+    | by Laravel's translation / localization methods. This option can be
77
+    | set to any locale for which you plan to have translation strings.
78
+    |
79
+    */
80
+
81
+    'locale' => env('APP_LOCALE', 'zh_TW'),
82
+
83
+    'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
84
+
85
+    'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'),
86
+
87
+    /*
88
+    |--------------------------------------------------------------------------
89
+    | Encryption Key
90
+    |--------------------------------------------------------------------------
91
+    |
92
+    | This key is utilized by Laravel's encryption services and should be set
93
+    | to a random, 32 character string to ensure that all encrypted values
94
+    | are secure. You should do this prior to deploying the application.
95
+    |
96
+    */
97
+
98
+    'cipher' => 'AES-256-CBC',
99
+
100
+    'key' => env('APP_KEY'),
101
+
102
+    'previous_keys' => [
103
+        ...array_filter(
104
+            explode(',', (string) env('APP_PREVIOUS_KEYS', ''))
105
+        ),
106
+    ],
107
+
108
+    /*
109
+    |--------------------------------------------------------------------------
110
+    | Maintenance Mode Driver
111
+    |--------------------------------------------------------------------------
112
+    |
113
+    | These configuration options determine the driver used to determine and
114
+    | manage Laravel's "maintenance mode" status. The "cache" driver will
115
+    | allow maintenance mode to be controlled across multiple machines.
116
+    |
117
+    | Supported drivers: "file", "cache"
118
+    |
119
+    */
120
+
121
+    'maintenance' => [
122
+        'driver' => env('APP_MAINTENANCE_DRIVER', 'file'),
123
+        'store' => env('APP_MAINTENANCE_STORE', 'database'),
124
+    ],
125
+
126
+    'oneapp_base_url' => env('ONEAPP_BASE_URL'),
127
+    'oneapp_logkey'   => env('ONEAPP_LOGKEY'),
128
+    'phone_encrypt_key' => env('PHONE_ENCRYPT_KEY'),
129
+];

+ 117
- 0
config/auth.php ファイルの表示

@@ -0,0 +1,117 @@
1
+<?php
2
+
3
+use App\Models\User;
4
+
5
+return [
6
+
7
+    /*
8
+    |--------------------------------------------------------------------------
9
+    | Authentication Defaults
10
+    |--------------------------------------------------------------------------
11
+    |
12
+    | This option defines the default authentication "guard" and password
13
+    | reset "broker" for your application. You may change these values
14
+    | as required, but they're a perfect start for most applications.
15
+    |
16
+    */
17
+
18
+    'defaults' => [
19
+        'guard' => env('AUTH_GUARD', 'web'),
20
+        'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
21
+    ],
22
+
23
+    /*
24
+    |--------------------------------------------------------------------------
25
+    | Authentication Guards
26
+    |--------------------------------------------------------------------------
27
+    |
28
+    | Next, you may define every authentication guard for your application.
29
+    | Of course, a great default configuration has been defined for you
30
+    | which utilizes session storage plus the Eloquent user provider.
31
+    |
32
+    | All authentication guards have a user provider, which defines how the
33
+    | users are actually retrieved out of your database or other storage
34
+    | system used by the application. Typically, Eloquent is utilized.
35
+    |
36
+    | Supported: "session"
37
+    |
38
+    */
39
+
40
+    'guards' => [
41
+        'web' => [
42
+            'driver' => 'session',
43
+            'provider' => 'users',
44
+        ],
45
+    ],
46
+
47
+    /*
48
+    |--------------------------------------------------------------------------
49
+    | User Providers
50
+    |--------------------------------------------------------------------------
51
+    |
52
+    | All authentication guards have a user provider, which defines how the
53
+    | users are actually retrieved out of your database or other storage
54
+    | system used by the application. Typically, Eloquent is utilized.
55
+    |
56
+    | If you have multiple user tables or models you may configure multiple
57
+    | providers to represent the model / table. These providers may then
58
+    | be assigned to any extra authentication guards you have defined.
59
+    |
60
+    | Supported: "database", "eloquent"
61
+    |
62
+    */
63
+
64
+    'providers' => [
65
+        'users' => [
66
+            'driver' => 'eloquent',
67
+            'model' => env('AUTH_MODEL', User::class),
68
+        ],
69
+
70
+        // 'users' => [
71
+        //     'driver' => 'database',
72
+        //     'table' => 'users',
73
+        // ],
74
+    ],
75
+
76
+    /*
77
+    |--------------------------------------------------------------------------
78
+    | Resetting Passwords
79
+    |--------------------------------------------------------------------------
80
+    |
81
+    | These configuration options specify the behavior of Laravel's password
82
+    | reset functionality, including the table utilized for token storage
83
+    | and the user provider that is invoked to actually retrieve users.
84
+    |
85
+    | The expiry time is the number of minutes that each reset token will be
86
+    | considered valid. This security feature keeps tokens short-lived so
87
+    | they have less time to be guessed. You may change this as needed.
88
+    |
89
+    | The throttle setting is the number of seconds a user must wait before
90
+    | generating more password reset tokens. This prevents the user from
91
+    | quickly generating a very large amount of password reset tokens.
92
+    |
93
+    */
94
+
95
+    'passwords' => [
96
+        'users' => [
97
+            'provider' => 'users',
98
+            'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
99
+            'expire' => 60,
100
+            'throttle' => 60,
101
+        ],
102
+    ],
103
+
104
+    /*
105
+    |--------------------------------------------------------------------------
106
+    | Password Confirmation Timeout
107
+    |--------------------------------------------------------------------------
108
+    |
109
+    | Here you may define the number of seconds before a password confirmation
110
+    | window expires and users are asked to re-enter their password via the
111
+    | confirmation screen. By default, the timeout lasts for three hours.
112
+    |
113
+    */
114
+
115
+    'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
116
+
117
+];

+ 130
- 0
config/cache.php ファイルの表示

@@ -0,0 +1,130 @@
1
+<?php
2
+
3
+use Illuminate\Support\Str;
4
+
5
+return [
6
+
7
+    /*
8
+    |--------------------------------------------------------------------------
9
+    | Default Cache Store
10
+    |--------------------------------------------------------------------------
11
+    |
12
+    | This option controls the default cache store that will be used by the
13
+    | framework. This connection is utilized if another isn't explicitly
14
+    | specified when running a cache operation inside the application.
15
+    |
16
+    */
17
+
18
+    'default' => env('CACHE_STORE', 'database'),
19
+
20
+    /*
21
+    |--------------------------------------------------------------------------
22
+    | Cache Stores
23
+    |--------------------------------------------------------------------------
24
+    |
25
+    | Here you may define all of the cache "stores" for your application as
26
+    | well as their drivers. You may even define multiple stores for the
27
+    | same cache driver to group types of items stored in your caches.
28
+    |
29
+    | Supported drivers: "array", "database", "file", "memcached",
30
+    |                    "redis", "dynamodb", "octane",
31
+    |                    "failover", "null"
32
+    |
33
+    */
34
+
35
+    'stores' => [
36
+
37
+        'array' => [
38
+            'driver' => 'array',
39
+            'serialize' => false,
40
+        ],
41
+
42
+        'database' => [
43
+            'driver' => 'database',
44
+            'connection' => env('DB_CACHE_CONNECTION'),
45
+            'table' => env('DB_CACHE_TABLE', 'cache'),
46
+            'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
47
+            'lock_table' => env('DB_CACHE_LOCK_TABLE'),
48
+        ],
49
+
50
+        'file' => [
51
+            'driver' => 'file',
52
+            'path' => storage_path('framework/cache/data'),
53
+            'lock_path' => storage_path('framework/cache/data'),
54
+        ],
55
+
56
+        'memcached' => [
57
+            'driver' => 'memcached',
58
+            'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
59
+            'sasl' => [
60
+                env('MEMCACHED_USERNAME'),
61
+                env('MEMCACHED_PASSWORD'),
62
+            ],
63
+            'options' => [
64
+                // Memcached::OPT_CONNECT_TIMEOUT => 2000,
65
+            ],
66
+            'servers' => [
67
+                [
68
+                    'host' => env('MEMCACHED_HOST', '127.0.0.1'),
69
+                    'port' => env('MEMCACHED_PORT', 11211),
70
+                    'weight' => 100,
71
+                ],
72
+            ],
73
+        ],
74
+
75
+        'redis' => [
76
+            'driver' => 'redis',
77
+            'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
78
+            'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
79
+        ],
80
+
81
+        'dynamodb' => [
82
+            'driver' => 'dynamodb',
83
+            'key' => env('AWS_ACCESS_KEY_ID'),
84
+            'secret' => env('AWS_SECRET_ACCESS_KEY'),
85
+            'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
86
+            'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
87
+            'endpoint' => env('DYNAMODB_ENDPOINT'),
88
+        ],
89
+
90
+        'octane' => [
91
+            'driver' => 'octane',
92
+        ],
93
+
94
+        'failover' => [
95
+            'driver' => 'failover',
96
+            'stores' => [
97
+                'database',
98
+                'array',
99
+            ],
100
+        ],
101
+
102
+    ],
103
+
104
+    /*
105
+    |--------------------------------------------------------------------------
106
+    | Cache Key Prefix
107
+    |--------------------------------------------------------------------------
108
+    |
109
+    | When utilizing the APC, database, memcached, Redis, and DynamoDB cache
110
+    | stores, there might be other applications using the same cache. For
111
+    | that reason, you may prefix every cache key to avoid collisions.
112
+    |
113
+    */
114
+
115
+    'prefix' => env('CACHE_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-cache-'),
116
+
117
+    /*
118
+    |--------------------------------------------------------------------------
119
+    | Serializable Classes
120
+    |--------------------------------------------------------------------------
121
+    |
122
+    | This value determines the classes that can be unserialized from cache
123
+    | storage. By default, no PHP classes will be unserialized from your
124
+    | cache to prevent gadget chain attacks if your APP_KEY is leaked.
125
+    |
126
+    */
127
+
128
+    'serializable_classes' => false,
129
+
130
+];

+ 184
- 0
config/database.php ファイルの表示

@@ -0,0 +1,184 @@
1
+<?php
2
+
3
+use Illuminate\Support\Str;
4
+use Pdo\Mysql;
5
+
6
+return [
7
+
8
+    /*
9
+    |--------------------------------------------------------------------------
10
+    | Default Database Connection Name
11
+    |--------------------------------------------------------------------------
12
+    |
13
+    | Here you may specify which of the database connections below you wish
14
+    | to use as your default connection for database operations. This is
15
+    | the connection which will be utilized unless another connection
16
+    | is explicitly specified when you execute a query / statement.
17
+    |
18
+    */
19
+
20
+    'default' => env('DB_CONNECTION', 'sqlite'),
21
+
22
+    /*
23
+    |--------------------------------------------------------------------------
24
+    | Database Connections
25
+    |--------------------------------------------------------------------------
26
+    |
27
+    | Below are all of the database connections defined for your application.
28
+    | An example configuration is provided for each database system which
29
+    | is supported by Laravel. You're free to add / remove connections.
30
+    |
31
+    */
32
+
33
+    'connections' => [
34
+
35
+        'sqlite' => [
36
+            'driver' => 'sqlite',
37
+            'url' => env('DB_URL'),
38
+            'database' => env('DB_DATABASE', database_path('database.sqlite')),
39
+            'prefix' => '',
40
+            'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
41
+            'busy_timeout' => null,
42
+            'journal_mode' => null,
43
+            'synchronous' => null,
44
+            'transaction_mode' => 'DEFERRED',
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
+                (PHP_VERSION_ID >= 80500 ? Mysql::ATTR_SSL_CA : 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
+                (PHP_VERSION_ID >= 80500 ? Mysql::ATTR_SSL_CA : 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' => env('DB_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
+            'max_retries' => env('REDIS_MAX_RETRIES', 3),
164
+            'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
165
+            'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
166
+            'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
167
+        ],
168
+
169
+        'cache' => [
170
+            'url' => env('REDIS_URL'),
171
+            'host' => env('REDIS_HOST', '127.0.0.1'),
172
+            'username' => env('REDIS_USERNAME'),
173
+            'password' => env('REDIS_PASSWORD'),
174
+            'port' => env('REDIS_PORT', '6379'),
175
+            'database' => env('REDIS_CACHE_DB', '1'),
176
+            'max_retries' => env('REDIS_MAX_RETRIES', 3),
177
+            'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
178
+            'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
179
+            'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
180
+        ],
181
+
182
+    ],
183
+
184
+];

+ 120
- 0
config/filament.php ファイルの表示

@@ -0,0 +1,120 @@
1
+<?php
2
+
3
+return [
4
+
5
+    /*
6
+    |--------------------------------------------------------------------------
7
+    | Broadcasting
8
+    |--------------------------------------------------------------------------
9
+    |
10
+    | By uncommenting the Laravel Echo configuration, you may connect Filament
11
+    | to any Pusher-compatible websockets server.
12
+    |
13
+    | This will allow your users to receive real-time notifications.
14
+    |
15
+    */
16
+
17
+    'broadcasting' => [
18
+
19
+        // 'echo' => [
20
+        //     'broadcaster' => 'pusher',
21
+        //     'key' => env('VITE_PUSHER_APP_KEY'),
22
+        //     'cluster' => env('VITE_PUSHER_APP_CLUSTER'),
23
+        //     'wsHost' => env('VITE_PUSHER_HOST'),
24
+        //     'wsPort' => env('VITE_PUSHER_PORT'),
25
+        //     'wssPort' => env('VITE_PUSHER_PORT'),
26
+        //     'authEndpoint' => '/broadcasting/auth',
27
+        //     'disableStats' => true,
28
+        //     'encrypted' => true,
29
+        //     'forceTLS' => env('VITE_PUSHER_SCHEME', 'https') === 'https',
30
+        // ],
31
+
32
+    ],
33
+
34
+    /*
35
+    |--------------------------------------------------------------------------
36
+    | Default Filesystem Disk
37
+    |--------------------------------------------------------------------------
38
+    |
39
+    | This is the storage disk Filament will use to store files. You may use
40
+    | any of the disks defined in the `config/filesystems.php`.
41
+    |
42
+    */
43
+
44
+    'default_filesystem_disk' => env('FILESYSTEM_DISK', 'local'),
45
+
46
+    /*
47
+    |--------------------------------------------------------------------------
48
+    | Assets Path
49
+    |--------------------------------------------------------------------------
50
+    |
51
+    | This is the directory where Filament's assets will be published to. It
52
+    | is relative to the `public` directory of your Laravel application.
53
+    |
54
+    | After changing the path, you should run `php artisan filament:assets`.
55
+    |
56
+    */
57
+
58
+    'assets_path' => null,
59
+
60
+    /*
61
+    |--------------------------------------------------------------------------
62
+    | Cache Path
63
+    |--------------------------------------------------------------------------
64
+    |
65
+    | This is the directory that Filament will use to store cache files that
66
+    | are used to optimize the registration of components.
67
+    |
68
+    | After changing the path, you should run `php artisan filament:cache-components`.
69
+    |
70
+    */
71
+
72
+    'cache_path' => base_path('bootstrap/cache/filament'),
73
+
74
+    /*
75
+    |--------------------------------------------------------------------------
76
+    | Livewire Loading Delay
77
+    |--------------------------------------------------------------------------
78
+    |
79
+    | This sets the delay before loading indicators appear.
80
+    |
81
+    | Setting this to 'none' makes indicators appear immediately, which can be
82
+    | desirable for high-latency connections. Setting it to 'default' applies
83
+    | Livewire's standard 200ms delay.
84
+    |
85
+    */
86
+
87
+    'livewire_loading_delay' => 'default',
88
+
89
+    /*
90
+    |--------------------------------------------------------------------------
91
+    | File Generation
92
+    |--------------------------------------------------------------------------
93
+    |
94
+    | Artisan commands that generate files can be configured here by setting
95
+    | configuration flags that will impact their location or content.
96
+    |
97
+    | Often, this is useful to preserve file generation behavior from a
98
+    | previous version of Filament, to ensure consistency between older and
99
+    | newer generated files. These flags are often documented in the upgrade
100
+    | guide for the version of Filament you are upgrading to.
101
+    |
102
+    */
103
+
104
+    'file_generation' => [
105
+        'flags' => [],
106
+    ],
107
+
108
+    /*
109
+    |--------------------------------------------------------------------------
110
+    | System Route Prefix
111
+    |--------------------------------------------------------------------------
112
+    |
113
+    | This is the prefix used for the system routes that Filament registers,
114
+    | such as the routes for downloading exports and failed import rows.
115
+    |
116
+    */
117
+
118
+    'system_route_prefix' => 'filament',
119
+
120
+];

+ 80
- 0
config/filesystems.php ファイルの表示

@@ -0,0 +1,80 @@
1
+<?php
2
+
3
+return [
4
+
5
+    /*
6
+    |--------------------------------------------------------------------------
7
+    | Default Filesystem Disk
8
+    |--------------------------------------------------------------------------
9
+    |
10
+    | Here you may specify the default filesystem disk that should be used
11
+    | by the framework. The "local" disk, as well as a variety of cloud
12
+    | based disks are available to your application for file storage.
13
+    |
14
+    */
15
+
16
+    'default' => env('FILESYSTEM_DISK', 'local'),
17
+
18
+    /*
19
+    |--------------------------------------------------------------------------
20
+    | Filesystem Disks
21
+    |--------------------------------------------------------------------------
22
+    |
23
+    | Below you may configure as many filesystem disks as necessary, and you
24
+    | may even configure multiple disks for the same driver. Examples for
25
+    | most supported storage drivers are configured here for reference.
26
+    |
27
+    | Supported drivers: "local", "ftp", "sftp", "s3"
28
+    |
29
+    */
30
+
31
+    'disks' => [
32
+
33
+        'local' => [
34
+            'driver' => 'local',
35
+            'root' => storage_path('app/private'),
36
+            'serve' => true,
37
+            'throw' => false,
38
+            'report' => false,
39
+        ],
40
+
41
+        'public' => [
42
+            'driver' => 'local',
43
+            'root' => storage_path('app/public'),
44
+            'url' => rtrim(env('APP_URL', 'http://localhost'), '/').'/storage',
45
+            'visibility' => 'public',
46
+            'throw' => false,
47
+            'report' => false,
48
+        ],
49
+
50
+        's3' => [
51
+            'driver' => 's3',
52
+            'key' => env('AWS_ACCESS_KEY_ID'),
53
+            'secret' => env('AWS_SECRET_ACCESS_KEY'),
54
+            'region' => env('AWS_DEFAULT_REGION'),
55
+            'bucket' => env('AWS_BUCKET'),
56
+            'url' => env('AWS_URL'),
57
+            'endpoint' => env('AWS_ENDPOINT'),
58
+            'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
59
+            'throw' => false,
60
+            'report' => false,
61
+        ],
62
+
63
+    ],
64
+
65
+    /*
66
+    |--------------------------------------------------------------------------
67
+    | Symbolic Links
68
+    |--------------------------------------------------------------------------
69
+    |
70
+    | Here you may configure the symbolic links that will be created when the
71
+    | `storage:link` Artisan command is executed. The array keys should be
72
+    | the locations of the links and the values should be their targets.
73
+    |
74
+    */
75
+
76
+    'links' => [
77
+        public_path('storage') => storage_path('app/public'),
78
+    ],
79
+
80
+];

+ 132
- 0
config/logging.php ファイルの表示

@@ -0,0 +1,132 @@
1
+<?php
2
+
3
+use Monolog\Handler\NullHandler;
4
+use Monolog\Handler\StreamHandler;
5
+use Monolog\Handler\SyslogUdpHandler;
6
+use Monolog\Processor\PsrLogMessageProcessor;
7
+
8
+return [
9
+
10
+    /*
11
+    |--------------------------------------------------------------------------
12
+    | Default Log Channel
13
+    |--------------------------------------------------------------------------
14
+    |
15
+    | This option defines the default log channel that is utilized to write
16
+    | messages to your logs. The value provided here should match one of
17
+    | the channels present in the list of "channels" configured below.
18
+    |
19
+    */
20
+
21
+    'default' => env('LOG_CHANNEL', 'stack'),
22
+
23
+    /*
24
+    |--------------------------------------------------------------------------
25
+    | Deprecations Log Channel
26
+    |--------------------------------------------------------------------------
27
+    |
28
+    | This option controls the log channel that should be used to log warnings
29
+    | regarding deprecated PHP and library features. This allows you to get
30
+    | your application ready for upcoming major versions of dependencies.
31
+    |
32
+    */
33
+
34
+    'deprecations' => [
35
+        'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
36
+        'trace' => env('LOG_DEPRECATIONS_TRACE', false),
37
+    ],
38
+
39
+    /*
40
+    |--------------------------------------------------------------------------
41
+    | Log Channels
42
+    |--------------------------------------------------------------------------
43
+    |
44
+    | Here you may configure the log channels for your application. Laravel
45
+    | utilizes the Monolog PHP logging library, which includes a variety
46
+    | of powerful log handlers and formatters that you're free to use.
47
+    |
48
+    | Available drivers: "single", "daily", "slack", "syslog",
49
+    |                    "errorlog", "monolog", "custom", "stack"
50
+    |
51
+    */
52
+
53
+    'channels' => [
54
+
55
+        'stack' => [
56
+            'driver' => 'stack',
57
+            'channels' => explode(',', (string) env('LOG_STACK', 'single')),
58
+            'ignore_exceptions' => false,
59
+        ],
60
+
61
+        'single' => [
62
+            'driver' => 'single',
63
+            'path' => storage_path('logs/laravel.log'),
64
+            'level' => env('LOG_LEVEL', 'debug'),
65
+            'replace_placeholders' => true,
66
+        ],
67
+
68
+        'daily' => [
69
+            'driver' => 'daily',
70
+            'path' => storage_path('logs/laravel.log'),
71
+            'level' => env('LOG_LEVEL', 'debug'),
72
+            'days' => env('LOG_DAILY_DAYS', 14),
73
+            'replace_placeholders' => true,
74
+        ],
75
+
76
+        'slack' => [
77
+            'driver' => 'slack',
78
+            'url' => env('LOG_SLACK_WEBHOOK_URL'),
79
+            'username' => env('LOG_SLACK_USERNAME', env('APP_NAME', 'Laravel')),
80
+            'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
81
+            'level' => env('LOG_LEVEL', 'critical'),
82
+            'replace_placeholders' => true,
83
+        ],
84
+
85
+        'papertrail' => [
86
+            'driver' => 'monolog',
87
+            'level' => env('LOG_LEVEL', 'debug'),
88
+            'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
89
+            'handler_with' => [
90
+                'host' => env('PAPERTRAIL_URL'),
91
+                'port' => env('PAPERTRAIL_PORT'),
92
+                'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
93
+            ],
94
+            'processors' => [PsrLogMessageProcessor::class],
95
+        ],
96
+
97
+        'stderr' => [
98
+            'driver' => 'monolog',
99
+            'level' => env('LOG_LEVEL', 'debug'),
100
+            'handler' => StreamHandler::class,
101
+            'handler_with' => [
102
+                'stream' => 'php://stderr',
103
+            ],
104
+            'formatter' => env('LOG_STDERR_FORMATTER'),
105
+            'processors' => [PsrLogMessageProcessor::class],
106
+        ],
107
+
108
+        'syslog' => [
109
+            'driver' => 'syslog',
110
+            'level' => env('LOG_LEVEL', 'debug'),
111
+            'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
112
+            'replace_placeholders' => true,
113
+        ],
114
+
115
+        'errorlog' => [
116
+            'driver' => 'errorlog',
117
+            'level' => env('LOG_LEVEL', 'debug'),
118
+            'replace_placeholders' => true,
119
+        ],
120
+
121
+        'null' => [
122
+            'driver' => 'monolog',
123
+            'handler' => NullHandler::class,
124
+        ],
125
+
126
+        'emergency' => [
127
+            'path' => storage_path('logs/laravel.log'),
128
+        ],
129
+
130
+    ],
131
+
132
+];

+ 118
- 0
config/mail.php ファイルの表示

@@ -0,0 +1,118 @@
1
+<?php
2
+
3
+return [
4
+
5
+    /*
6
+    |--------------------------------------------------------------------------
7
+    | Default Mailer
8
+    |--------------------------------------------------------------------------
9
+    |
10
+    | This option controls the default mailer that is used to send all email
11
+    | messages unless another mailer is explicitly specified when sending
12
+    | the message. All additional mailers can be configured within the
13
+    | "mailers" array. Examples of each type of mailer are provided.
14
+    |
15
+    */
16
+
17
+    'default' => env('MAIL_MAILER', 'log'),
18
+
19
+    /*
20
+    |--------------------------------------------------------------------------
21
+    | Mailer Configurations
22
+    |--------------------------------------------------------------------------
23
+    |
24
+    | Here you may configure all of the mailers used by your application plus
25
+    | their respective settings. Several examples have been configured for
26
+    | you and you are free to add your own as your application requires.
27
+    |
28
+    | Laravel supports a variety of mail "transport" drivers that can be used
29
+    | when delivering an email. You may specify which one you're using for
30
+    | your mailers below. You may also add additional mailers if needed.
31
+    |
32
+    | Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
33
+    |            "postmark", "resend", "log", "array",
34
+    |            "failover", "roundrobin"
35
+    |
36
+    */
37
+
38
+    'mailers' => [
39
+
40
+        'smtp' => [
41
+            'transport' => 'smtp',
42
+            'scheme' => env('MAIL_SCHEME'),
43
+            'url' => env('MAIL_URL'),
44
+            'host' => env('MAIL_HOST', '127.0.0.1'),
45
+            'port' => env('MAIL_PORT', 2525),
46
+            'username' => env('MAIL_USERNAME'),
47
+            'password' => env('MAIL_PASSWORD'),
48
+            'timeout' => null,
49
+            'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url((string) env('APP_URL', 'http://localhost'), PHP_URL_HOST)),
50
+        ],
51
+
52
+        'ses' => [
53
+            'transport' => 'ses',
54
+        ],
55
+
56
+        'postmark' => [
57
+            'transport' => 'postmark',
58
+            // 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),
59
+            // 'client' => [
60
+            //     'timeout' => 5,
61
+            // ],
62
+        ],
63
+
64
+        'resend' => [
65
+            'transport' => 'resend',
66
+        ],
67
+
68
+        'sendmail' => [
69
+            'transport' => 'sendmail',
70
+            'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
71
+        ],
72
+
73
+        'log' => [
74
+            'transport' => 'log',
75
+            'channel' => env('MAIL_LOG_CHANNEL'),
76
+        ],
77
+
78
+        'array' => [
79
+            'transport' => 'array',
80
+        ],
81
+
82
+        'failover' => [
83
+            'transport' => 'failover',
84
+            'mailers' => [
85
+                'smtp',
86
+                'log',
87
+            ],
88
+            'retry_after' => 60,
89
+        ],
90
+
91
+        'roundrobin' => [
92
+            'transport' => 'roundrobin',
93
+            'mailers' => [
94
+                'ses',
95
+                'postmark',
96
+            ],
97
+            'retry_after' => 60,
98
+        ],
99
+
100
+    ],
101
+
102
+    /*
103
+    |--------------------------------------------------------------------------
104
+    | Global "From" Address
105
+    |--------------------------------------------------------------------------
106
+    |
107
+    | You may wish for all emails sent by your application to be sent from
108
+    | the same address. Here you may specify a name and address that is
109
+    | used globally for all emails that are sent by your application.
110
+    |
111
+    */
112
+
113
+    'from' => [
114
+        'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
115
+        'name' => env('MAIL_FROM_NAME', env('APP_NAME', 'Laravel')),
116
+    ],
117
+
118
+];

+ 129
- 0
config/queue.php ファイルの表示

@@ -0,0 +1,129 @@
1
+<?php
2
+
3
+return [
4
+
5
+    /*
6
+    |--------------------------------------------------------------------------
7
+    | Default Queue Connection Name
8
+    |--------------------------------------------------------------------------
9
+    |
10
+    | Laravel's queue supports a variety of backends via a single, unified
11
+    | API, giving you convenient access to each backend using identical
12
+    | syntax for each. The default queue connection is defined below.
13
+    |
14
+    */
15
+
16
+    'default' => env('QUEUE_CONNECTION', 'database'),
17
+
18
+    /*
19
+    |--------------------------------------------------------------------------
20
+    | Queue Connections
21
+    |--------------------------------------------------------------------------
22
+    |
23
+    | Here you may configure the connection options for every queue backend
24
+    | used by your application. An example configuration is provided for
25
+    | each backend supported by Laravel. You're also free to add more.
26
+    |
27
+    | Drivers: "sync", "database", "beanstalkd", "sqs", "redis",
28
+    |          "deferred", "background", "failover", "null"
29
+    |
30
+    */
31
+
32
+    'connections' => [
33
+
34
+        'sync' => [
35
+            'driver' => 'sync',
36
+        ],
37
+
38
+        'database' => [
39
+            'driver' => 'database',
40
+            'connection' => env('DB_QUEUE_CONNECTION'),
41
+            'table' => env('DB_QUEUE_TABLE', 'jobs'),
42
+            'queue' => env('DB_QUEUE', 'default'),
43
+            'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90),
44
+            'after_commit' => false,
45
+        ],
46
+
47
+        'beanstalkd' => [
48
+            'driver' => 'beanstalkd',
49
+            'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'),
50
+            'queue' => env('BEANSTALKD_QUEUE', 'default'),
51
+            'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90),
52
+            'block_for' => 0,
53
+            'after_commit' => false,
54
+        ],
55
+
56
+        'sqs' => [
57
+            'driver' => 'sqs',
58
+            'key' => env('AWS_ACCESS_KEY_ID'),
59
+            'secret' => env('AWS_SECRET_ACCESS_KEY'),
60
+            'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
61
+            'queue' => env('SQS_QUEUE', 'default'),
62
+            'suffix' => env('SQS_SUFFIX'),
63
+            'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
64
+            'after_commit' => false,
65
+        ],
66
+
67
+        'redis' => [
68
+            'driver' => 'redis',
69
+            'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
70
+            'queue' => env('REDIS_QUEUE', 'default'),
71
+            'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90),
72
+            'block_for' => null,
73
+            'after_commit' => false,
74
+        ],
75
+
76
+        'deferred' => [
77
+            'driver' => 'deferred',
78
+        ],
79
+
80
+        'background' => [
81
+            'driver' => 'background',
82
+        ],
83
+
84
+        'failover' => [
85
+            'driver' => 'failover',
86
+            'connections' => [
87
+                'database',
88
+                'deferred',
89
+            ],
90
+        ],
91
+
92
+    ],
93
+
94
+    /*
95
+    |--------------------------------------------------------------------------
96
+    | Job Batching
97
+    |--------------------------------------------------------------------------
98
+    |
99
+    | The following options configure the database and table that store job
100
+    | batching information. These options can be updated to any database
101
+    | connection and table which has been defined by your application.
102
+    |
103
+    */
104
+
105
+    'batching' => [
106
+        'database' => env('DB_CONNECTION', 'sqlite'),
107
+        'table' => 'job_batches',
108
+    ],
109
+
110
+    /*
111
+    |--------------------------------------------------------------------------
112
+    | Failed Queue Jobs
113
+    |--------------------------------------------------------------------------
114
+    |
115
+    | These options configure the behavior of failed queue job logging so you
116
+    | can control how and where failed jobs are stored. Laravel ships with
117
+    | support for storing failed jobs in a simple file or in a database.
118
+    |
119
+    | Supported drivers: "database-uuids", "dynamodb", "file", "null"
120
+    |
121
+    */
122
+
123
+    'failed' => [
124
+        'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
125
+        'database' => env('DB_CONNECTION', 'sqlite'),
126
+        'table' => 'failed_jobs',
127
+    ],
128
+
129
+];

+ 38
- 0
config/services.php ファイルの表示

@@ -0,0 +1,38 @@
1
+<?php
2
+
3
+return [
4
+
5
+    /*
6
+    |--------------------------------------------------------------------------
7
+    | Third Party Services
8
+    |--------------------------------------------------------------------------
9
+    |
10
+    | This file is for storing the credentials for third party services such
11
+    | as Mailgun, Postmark, AWS and more. This file provides the de facto
12
+    | location for this type of information, allowing packages to have
13
+    | a conventional file to locate the various service credentials.
14
+    |
15
+    */
16
+
17
+    'postmark' => [
18
+        'key' => env('POSTMARK_API_KEY'),
19
+    ],
20
+
21
+    'resend' => [
22
+        'key' => env('RESEND_API_KEY'),
23
+    ],
24
+
25
+    'ses' => [
26
+        'key' => env('AWS_ACCESS_KEY_ID'),
27
+        'secret' => env('AWS_SECRET_ACCESS_KEY'),
28
+        'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
29
+    ],
30
+
31
+    'slack' => [
32
+        'notifications' => [
33
+            'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
34
+            'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
35
+        ],
36
+    ],
37
+
38
+];

+ 233
- 0
config/session.php ファイルの表示

@@ -0,0 +1,233 @@
1
+<?php
2
+
3
+use Illuminate\Support\Str;
4
+
5
+return [
6
+
7
+    /*
8
+    |--------------------------------------------------------------------------
9
+    | Default Session Driver
10
+    |--------------------------------------------------------------------------
11
+    |
12
+    | This option determines the default session driver that is utilized for
13
+    | incoming requests. Laravel supports a variety of storage options to
14
+    | persist session data. Database storage is a great default choice.
15
+    |
16
+    | Supported: "file", "cookie", "database", "memcached",
17
+    |            "redis", "dynamodb", "array"
18
+    |
19
+    */
20
+
21
+    'driver' => env('SESSION_DRIVER', 'database'),
22
+
23
+    /*
24
+    |--------------------------------------------------------------------------
25
+    | Session Lifetime
26
+    |--------------------------------------------------------------------------
27
+    |
28
+    | Here you may specify the number of minutes that you wish the session
29
+    | to be allowed to remain idle before it expires. If you want them
30
+    | to expire immediately when the browser is closed then you may
31
+    | indicate that via the expire_on_close configuration option.
32
+    |
33
+    */
34
+
35
+    'lifetime' => (int) env('SESSION_LIFETIME', 120),
36
+
37
+    'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
38
+
39
+    /*
40
+    |--------------------------------------------------------------------------
41
+    | Session Encryption
42
+    |--------------------------------------------------------------------------
43
+    |
44
+    | This option allows you to easily specify that all of your session data
45
+    | should be encrypted before it's stored. All encryption is performed
46
+    | automatically by Laravel and you may use the session like normal.
47
+    |
48
+    */
49
+
50
+    'encrypt' => env('SESSION_ENCRYPT', false),
51
+
52
+    /*
53
+    |--------------------------------------------------------------------------
54
+    | Session File Location
55
+    |--------------------------------------------------------------------------
56
+    |
57
+    | When utilizing the "file" session driver, the session files are placed
58
+    | on disk. The default storage location is defined here; however, you
59
+    | are free to provide another location where they should be stored.
60
+    |
61
+    */
62
+
63
+    'files' => storage_path('framework/sessions'),
64
+
65
+    /*
66
+    |--------------------------------------------------------------------------
67
+    | Session Database Connection
68
+    |--------------------------------------------------------------------------
69
+    |
70
+    | When using the "database" or "redis" session drivers, you may specify a
71
+    | connection that should be used to manage these sessions. This should
72
+    | correspond to a connection in your database configuration options.
73
+    |
74
+    */
75
+
76
+    'connection' => env('SESSION_CONNECTION'),
77
+
78
+    /*
79
+    |--------------------------------------------------------------------------
80
+    | Session Database Table
81
+    |--------------------------------------------------------------------------
82
+    |
83
+    | When using the "database" session driver, you may specify the table to
84
+    | be used to store sessions. Of course, a sensible default is defined
85
+    | for you; however, you're welcome to change this to another table.
86
+    |
87
+    */
88
+
89
+    'table' => env('SESSION_TABLE', 'sessions'),
90
+
91
+    /*
92
+    |--------------------------------------------------------------------------
93
+    | Session Cache Store
94
+    |--------------------------------------------------------------------------
95
+    |
96
+    | When using one of the framework's cache driven session backends, you may
97
+    | define the cache store which should be used to store the session data
98
+    | between requests. This must match one of your defined cache stores.
99
+    |
100
+    | Affects: "dynamodb", "memcached", "redis"
101
+    |
102
+    */
103
+
104
+    'store' => env('SESSION_STORE'),
105
+
106
+    /*
107
+    |--------------------------------------------------------------------------
108
+    | Session Sweeping Lottery
109
+    |--------------------------------------------------------------------------
110
+    |
111
+    | Some session drivers must manually sweep their storage location to get
112
+    | rid of old sessions from storage. Here are the chances that it will
113
+    | happen on a given request. By default, the odds are 2 out of 100.
114
+    |
115
+    */
116
+
117
+    'lottery' => [2, 100],
118
+
119
+    /*
120
+    |--------------------------------------------------------------------------
121
+    | Session Cookie Name
122
+    |--------------------------------------------------------------------------
123
+    |
124
+    | Here you may change the name of the session cookie that is created by
125
+    | the framework. Typically, you should not need to change this value
126
+    | since doing so does not grant a meaningful security improvement.
127
+    |
128
+    */
129
+
130
+    'cookie' => env(
131
+        'SESSION_COOKIE',
132
+        Str::slug((string) env('APP_NAME', 'laravel')).'-session'
133
+    ),
134
+
135
+    /*
136
+    |--------------------------------------------------------------------------
137
+    | Session Cookie Path
138
+    |--------------------------------------------------------------------------
139
+    |
140
+    | The session cookie path determines the path for which the cookie will
141
+    | be regarded as available. Typically, this will be the root path of
142
+    | your application, but you're free to change this when necessary.
143
+    |
144
+    */
145
+
146
+    'path' => env('SESSION_PATH', '/'),
147
+
148
+    /*
149
+    |--------------------------------------------------------------------------
150
+    | Session Cookie Domain
151
+    |--------------------------------------------------------------------------
152
+    |
153
+    | This value determines the domain and subdomains the session cookie is
154
+    | available to. By default, the cookie will be available to the root
155
+    | domain without subdomains. Typically, this shouldn't be changed.
156
+    |
157
+    */
158
+
159
+    'domain' => env('SESSION_DOMAIN'),
160
+
161
+    /*
162
+    |--------------------------------------------------------------------------
163
+    | HTTPS Only Cookies
164
+    |--------------------------------------------------------------------------
165
+    |
166
+    | By setting this option to true, session cookies will only be sent back
167
+    | to the server if the browser has a HTTPS connection. This will keep
168
+    | the cookie from being sent to you when it can't be done securely.
169
+    |
170
+    */
171
+
172
+    'secure' => env('SESSION_SECURE_COOKIE'),
173
+
174
+    /*
175
+    |--------------------------------------------------------------------------
176
+    | HTTP Access Only
177
+    |--------------------------------------------------------------------------
178
+    |
179
+    | Setting this value to true will prevent JavaScript from accessing the
180
+    | value of the cookie and the cookie will only be accessible through
181
+    | the HTTP protocol. It's unlikely you should disable this option.
182
+    |
183
+    */
184
+
185
+    'http_only' => env('SESSION_HTTP_ONLY', true),
186
+
187
+    /*
188
+    |--------------------------------------------------------------------------
189
+    | Same-Site Cookies
190
+    |--------------------------------------------------------------------------
191
+    |
192
+    | This option determines how your cookies behave when cross-site requests
193
+    | take place, and can be used to mitigate CSRF attacks. By default, we
194
+    | will set this value to "lax" to permit secure cross-site requests.
195
+    |
196
+    | See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value
197
+    |
198
+    | Supported: "lax", "strict", "none", null
199
+    |
200
+    */
201
+
202
+    'same_site' => env('SESSION_SAME_SITE', 'lax'),
203
+
204
+    /*
205
+    |--------------------------------------------------------------------------
206
+    | Partitioned Cookies
207
+    |--------------------------------------------------------------------------
208
+    |
209
+    | Setting this value to true will tie the cookie to the top-level site for
210
+    | a cross-site context. Partitioned cookies are accepted by the browser
211
+    | when flagged "secure" and the Same-Site attribute is set to "none".
212
+    |
213
+    */
214
+
215
+    'partitioned' => env('SESSION_PARTITIONED_COOKIE', false),
216
+
217
+    /*
218
+    |--------------------------------------------------------------------------
219
+    | Session Serialization
220
+    |--------------------------------------------------------------------------
221
+    |
222
+    | This value controls the serialization strategy for session data, which
223
+    | is JSON by default. Setting this to "php" allows the storage of PHP
224
+    | objects in the session but can make an application vulnerable to
225
+    | "gadget chain" serialization attacks if the APP_KEY is leaked.
226
+    |
227
+    | Supported: "json", "php"
228
+    |
229
+    */
230
+
231
+    'serialization' => 'json',
232
+
233
+];

+ 1
- 0
database/.gitignore ファイルの表示

@@ -0,0 +1 @@
1
+*.sqlite*

+ 45
- 0
database/factories/UserFactory.php ファイルの表示

@@ -0,0 +1,45 @@
1
+<?php
2
+
3
+namespace Database\Factories;
4
+
5
+use App\Models\User;
6
+use Illuminate\Database\Eloquent\Factories\Factory;
7
+use Illuminate\Support\Facades\Hash;
8
+use Illuminate\Support\Str;
9
+
10
+/**
11
+ * @extends Factory<User>
12
+ */
13
+class UserFactory extends Factory
14
+{
15
+    /**
16
+     * The current password being used by the factory.
17
+     */
18
+    protected static ?string $password;
19
+
20
+    /**
21
+     * Define the model's default state.
22
+     *
23
+     * @return array<string, mixed>
24
+     */
25
+    public function definition(): array
26
+    {
27
+        return [
28
+            'name' => fake()->name(),
29
+            'email' => fake()->unique()->safeEmail(),
30
+            'email_verified_at' => now(),
31
+            'password' => static::$password ??= Hash::make('password'),
32
+            'remember_token' => Str::random(10),
33
+        ];
34
+    }
35
+
36
+    /**
37
+     * Indicate that the model's email address should be unverified.
38
+     */
39
+    public function unverified(): static
40
+    {
41
+        return $this->state(fn (array $attributes) => [
42
+            'email_verified_at' => null,
43
+        ]);
44
+    }
45
+}

+ 49
- 0
database/migrations/0001_01_01_000000_create_users_table.php ファイルの表示

@@ -0,0 +1,49 @@
1
+<?php
2
+
3
+use Illuminate\Database\Migrations\Migration;
4
+use Illuminate\Database\Schema\Blueprint;
5
+use Illuminate\Support\Facades\Schema;
6
+
7
+return new class extends Migration
8
+{
9
+    /**
10
+     * Run the migrations.
11
+     */
12
+    public function up(): void
13
+    {
14
+        Schema::create('users', function (Blueprint $table) {
15
+            $table->id();
16
+            $table->string('name');
17
+            $table->string('email')->unique();
18
+            $table->timestamp('email_verified_at')->nullable();
19
+            $table->string('password');
20
+            $table->rememberToken();
21
+            $table->timestamps();
22
+        });
23
+
24
+        Schema::create('password_reset_tokens', function (Blueprint $table) {
25
+            $table->string('email')->primary();
26
+            $table->string('token');
27
+            $table->timestamp('created_at')->nullable();
28
+        });
29
+
30
+        Schema::create('sessions', function (Blueprint $table) {
31
+            $table->string('id')->primary();
32
+            $table->foreignId('user_id')->nullable()->index();
33
+            $table->string('ip_address', 45)->nullable();
34
+            $table->text('user_agent')->nullable();
35
+            $table->longText('payload');
36
+            $table->integer('last_activity')->index();
37
+        });
38
+    }
39
+
40
+    /**
41
+     * Reverse the migrations.
42
+     */
43
+    public function down(): void
44
+    {
45
+        Schema::dropIfExists('users');
46
+        Schema::dropIfExists('password_reset_tokens');
47
+        Schema::dropIfExists('sessions');
48
+    }
49
+};

+ 35
- 0
database/migrations/0001_01_01_000001_create_cache_table.php ファイルの表示

@@ -0,0 +1,35 @@
1
+<?php
2
+
3
+use Illuminate\Database\Migrations\Migration;
4
+use Illuminate\Database\Schema\Blueprint;
5
+use Illuminate\Support\Facades\Schema;
6
+
7
+return new class extends Migration
8
+{
9
+    /**
10
+     * Run the migrations.
11
+     */
12
+    public function up(): void
13
+    {
14
+        Schema::create('cache', function (Blueprint $table) {
15
+            $table->string('key')->primary();
16
+            $table->mediumText('value');
17
+            $table->bigInteger('expiration')->index();
18
+        });
19
+
20
+        Schema::create('cache_locks', function (Blueprint $table) {
21
+            $table->string('key')->primary();
22
+            $table->string('owner');
23
+            $table->bigInteger('expiration')->index();
24
+        });
25
+    }
26
+
27
+    /**
28
+     * Reverse the migrations.
29
+     */
30
+    public function down(): void
31
+    {
32
+        Schema::dropIfExists('cache');
33
+        Schema::dropIfExists('cache_locks');
34
+    }
35
+};

+ 57
- 0
database/migrations/0001_01_01_000002_create_jobs_table.php ファイルの表示

@@ -0,0 +1,57 @@
1
+<?php
2
+
3
+use Illuminate\Database\Migrations\Migration;
4
+use Illuminate\Database\Schema\Blueprint;
5
+use Illuminate\Support\Facades\Schema;
6
+
7
+return new class extends Migration
8
+{
9
+    /**
10
+     * Run the migrations.
11
+     */
12
+    public function up(): void
13
+    {
14
+        Schema::create('jobs', function (Blueprint $table) {
15
+            $table->id();
16
+            $table->string('queue')->index();
17
+            $table->longText('payload');
18
+            $table->unsignedTinyInteger('attempts');
19
+            $table->unsignedInteger('reserved_at')->nullable();
20
+            $table->unsignedInteger('available_at');
21
+            $table->unsignedInteger('created_at');
22
+        });
23
+
24
+        Schema::create('job_batches', function (Blueprint $table) {
25
+            $table->string('id')->primary();
26
+            $table->string('name');
27
+            $table->integer('total_jobs');
28
+            $table->integer('pending_jobs');
29
+            $table->integer('failed_jobs');
30
+            $table->longText('failed_job_ids');
31
+            $table->mediumText('options')->nullable();
32
+            $table->integer('cancelled_at')->nullable();
33
+            $table->integer('created_at');
34
+            $table->integer('finished_at')->nullable();
35
+        });
36
+
37
+        Schema::create('failed_jobs', function (Blueprint $table) {
38
+            $table->id();
39
+            $table->string('uuid')->unique();
40
+            $table->text('connection');
41
+            $table->text('queue');
42
+            $table->longText('payload');
43
+            $table->longText('exception');
44
+            $table->timestamp('failed_at')->useCurrent();
45
+        });
46
+    }
47
+
48
+    /**
49
+     * Reverse the migrations.
50
+     */
51
+    public function down(): void
52
+    {
53
+        Schema::dropIfExists('jobs');
54
+        Schema::dropIfExists('job_batches');
55
+        Schema::dropIfExists('failed_jobs');
56
+    }
57
+};

+ 32
- 0
database/migrations/2026_04_17_104840_create_qrcode_records_table.php ファイルの表示

@@ -0,0 +1,32 @@
1
+<?php
2
+
3
+use Illuminate\Database\Migrations\Migration;
4
+use Illuminate\Database\Schema\Blueprint;
5
+use Illuminate\Support\Facades\Schema;
6
+
7
+return new class extends Migration
8
+{
9
+    /**
10
+     * Run the migrations.
11
+     */
12
+    public function up(): void
13
+    {
14
+        Schema::create('qrcode_records', function (Blueprint $table) {
15
+            $table->id();
16
+            $table->string('outlet_id');        // 店號
17
+            $table->string('outlet_name');      // 店名
18
+            $table->string('name');             // 姓名
19
+            $table->string('phone');            // 電話(加密後)
20
+            $table->timestamp('qr_generated_at'); // QR Code 產生時間
21
+            $table->timestamps();
22
+        });
23
+    }
24
+
25
+    /**
26
+     * Reverse the migrations.
27
+     */
28
+    public function down(): void
29
+    {
30
+        Schema::dropIfExists('qrcode_records');
31
+    }
32
+};

+ 31
- 0
database/migrations/2026_04_21_162744_create_notifications_table.php ファイルの表示

@@ -0,0 +1,31 @@
1
+<?php
2
+
3
+use Illuminate\Database\Migrations\Migration;
4
+use Illuminate\Database\Schema\Blueprint;
5
+use Illuminate\Support\Facades\Schema;
6
+
7
+return new class extends Migration
8
+{
9
+    /**
10
+     * Run the migrations.
11
+     */
12
+    public function up(): void
13
+    {
14
+        Schema::create('notifications', function (Blueprint $table) {
15
+            $table->uuid('id')->primary();
16
+            $table->string('type');
17
+            $table->morphs('notifiable');
18
+            $table->text('data');
19
+            $table->timestamp('read_at')->nullable();
20
+            $table->timestamps();
21
+        });
22
+    }
23
+
24
+    /**
25
+     * Reverse the migrations.
26
+     */
27
+    public function down(): void
28
+    {
29
+        Schema::dropIfExists('notifications');
30
+    }
31
+};

+ 35
- 0
database/migrations/2026_04_21_162751_create_imports_table.php ファイルの表示

@@ -0,0 +1,35 @@
1
+<?php
2
+
3
+use Illuminate\Database\Migrations\Migration;
4
+use Illuminate\Database\Schema\Blueprint;
5
+use Illuminate\Support\Facades\Schema;
6
+
7
+return new class extends Migration
8
+{
9
+    /**
10
+     * Run the migrations.
11
+     */
12
+    public function up(): void
13
+    {
14
+        Schema::create('imports', function (Blueprint $table): void {
15
+            $table->id();
16
+            $table->timestamp('completed_at')->nullable();
17
+            $table->string('file_name');
18
+            $table->string('file_path');
19
+            $table->string('importer');
20
+            $table->unsignedInteger('processed_rows')->default(0);
21
+            $table->unsignedInteger('total_rows');
22
+            $table->unsignedInteger('successful_rows')->default(0);
23
+            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
24
+            $table->timestamps();
25
+        });
26
+    }
27
+
28
+    /**
29
+     * Reverse the migrations.
30
+     */
31
+    public function down(): void
32
+    {
33
+        Schema::dropIfExists('imports');
34
+    }
35
+};

+ 35
- 0
database/migrations/2026_04_21_162752_create_exports_table.php ファイルの表示

@@ -0,0 +1,35 @@
1
+<?php
2
+
3
+use Illuminate\Database\Migrations\Migration;
4
+use Illuminate\Database\Schema\Blueprint;
5
+use Illuminate\Support\Facades\Schema;
6
+
7
+return new class extends Migration
8
+{
9
+    /**
10
+     * Run the migrations.
11
+     */
12
+    public function up(): void
13
+    {
14
+        Schema::create('exports', function (Blueprint $table): void {
15
+            $table->id();
16
+            $table->timestamp('completed_at')->nullable();
17
+            $table->string('file_disk');
18
+            $table->string('file_name')->nullable();
19
+            $table->string('exporter');
20
+            $table->unsignedInteger('processed_rows')->default(0);
21
+            $table->unsignedInteger('total_rows');
22
+            $table->unsignedInteger('successful_rows')->default(0);
23
+            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
24
+            $table->timestamps();
25
+        });
26
+    }
27
+
28
+    /**
29
+     * Reverse the migrations.
30
+     */
31
+    public function down(): void
32
+    {
33
+        Schema::dropIfExists('exports');
34
+    }
35
+};

+ 30
- 0
database/migrations/2026_04_21_162753_create_failed_import_rows_table.php ファイルの表示

@@ -0,0 +1,30 @@
1
+<?php
2
+
3
+use Illuminate\Database\Migrations\Migration;
4
+use Illuminate\Database\Schema\Blueprint;
5
+use Illuminate\Support\Facades\Schema;
6
+
7
+return new class extends Migration
8
+{
9
+    /**
10
+     * Run the migrations.
11
+     */
12
+    public function up(): void
13
+    {
14
+        Schema::create('failed_import_rows', function (Blueprint $table): void {
15
+            $table->id();
16
+            $table->json('data');
17
+            $table->foreignId('import_id')->constrained()->cascadeOnDelete();
18
+            $table->text('validation_error')->nullable();
19
+            $table->timestamps();
20
+        });
21
+    }
22
+
23
+    /**
24
+     * Reverse the migrations.
25
+     */
26
+    public function down(): void
27
+    {
28
+        Schema::dropIfExists('failed_import_rows');
29
+    }
30
+};

+ 25
- 0
database/seeders/DatabaseSeeder.php ファイルの表示

@@ -0,0 +1,25 @@
1
+<?php
2
+
3
+namespace Database\Seeders;
4
+
5
+use App\Models\User;
6
+use Illuminate\Database\Console\Seeds\WithoutModelEvents;
7
+use Illuminate\Database\Seeder;
8
+
9
+class DatabaseSeeder extends Seeder
10
+{
11
+    use WithoutModelEvents;
12
+
13
+    /**
14
+     * Seed the application's database.
15
+     */
16
+    public function run(): void
17
+    {
18
+        // User::factory(10)->create();
19
+
20
+        User::factory()->create([
21
+            'name' => 'Test User',
22
+            'email' => 'test@example.com',
23
+        ]);
24
+    }
25
+}

+ 17
- 0
package.json ファイルの表示

@@ -0,0 +1,17 @@
1
+{
2
+    "$schema": "https://www.schemastore.org/package.json",
3
+    "private": true,
4
+    "type": "module",
5
+    "scripts": {
6
+        "build": "vite build",
7
+        "dev": "vite"
8
+    },
9
+    "devDependencies": {
10
+        "@tailwindcss/vite": "^4.0.0",
11
+        "axios": "^1.11.0",
12
+        "concurrently": "^9.0.1",
13
+        "laravel-vite-plugin": "^2.0.0",
14
+        "tailwindcss": "^4.0.0",
15
+        "vite": "^7.0.7"
16
+    }
17
+}

+ 36
- 0
phpunit.xml ファイルの表示

@@ -0,0 +1,36 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3
+         xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
4
+         bootstrap="vendor/autoload.php"
5
+         colors="true"
6
+>
7
+    <testsuites>
8
+        <testsuite name="Unit">
9
+            <directory>tests/Unit</directory>
10
+        </testsuite>
11
+        <testsuite name="Feature">
12
+            <directory>tests/Feature</directory>
13
+        </testsuite>
14
+    </testsuites>
15
+    <source>
16
+        <include>
17
+            <directory>app</directory>
18
+        </include>
19
+    </source>
20
+    <php>
21
+        <env name="APP_ENV" value="testing"/>
22
+        <env name="APP_MAINTENANCE_DRIVER" value="file"/>
23
+        <env name="BCRYPT_ROUNDS" value="4"/>
24
+        <env name="BROADCAST_CONNECTION" value="null"/>
25
+        <env name="CACHE_STORE" value="array"/>
26
+        <env name="DB_CONNECTION" value="sqlite"/>
27
+        <env name="DB_DATABASE" value=":memory:"/>
28
+        <env name="DB_URL" value=""/>
29
+        <env name="MAIL_MAILER" value="array"/>
30
+        <env name="QUEUE_CONNECTION" value="sync"/>
31
+        <env name="SESSION_DRIVER" value="array"/>
32
+        <env name="PULSE_ENABLED" value="false"/>
33
+        <env name="TELESCOPE_ENABLED" value="false"/>
34
+        <env name="NIGHTWATCH_ENABLED" value="false"/>
35
+    </php>
36
+</phpunit>

+ 25
- 0
public/.htaccess ファイルの表示

@@ -0,0 +1,25 @@
1
+<IfModule mod_rewrite.c>
2
+    <IfModule mod_negotiation.c>
3
+        Options -MultiViews -Indexes
4
+    </IfModule>
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
+</IfModule>

+ 2
- 0
public/css/filament/filament/app.css
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 0
- 0
public/favicon.ico ファイルの表示


+ 1
- 0
public/fonts/filament/filament/inter/index.css ファイルの表示

@@ -0,0 +1 @@
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}

バイナリ
public/fonts/filament/filament/inter/inter-cyrillic-ext-wght-normal-IYF56FF6.woff2 ファイルの表示


バイナリ
public/fonts/filament/filament/inter/inter-cyrillic-wght-normal-JEOLYBOO.woff2 ファイルの表示


バイナリ
public/fonts/filament/filament/inter/inter-greek-ext-wght-normal-EOVOK2B5.woff2 ファイルの表示


バイナリ
public/fonts/filament/filament/inter/inter-greek-wght-normal-IRE366VL.woff2 ファイルの表示


バイナリ
public/fonts/filament/filament/inter/inter-latin-ext-wght-normal-HA22NDSG.woff2 ファイルの表示


バイナリ
public/fonts/filament/filament/inter/inter-latin-wght-normal-NRMW37G5.woff2 ファイルの表示


バイナリ
public/fonts/filament/filament/inter/inter-vietnamese-wght-normal-CE5GGD3W.woff2 ファイルの表示


+ 20
- 0
public/index.php ファイルの表示

@@ -0,0 +1,20 @@
1
+<?php
2
+
3
+use Illuminate\Foundation\Application;
4
+use Illuminate\Http\Request;
5
+
6
+define('LARAVEL_START', microtime(true));
7
+
8
+// Determine if the application is in maintenance mode...
9
+if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
10
+    require $maintenance;
11
+}
12
+
13
+// Register the Composer autoloader...
14
+require __DIR__.'/../vendor/autoload.php';
15
+
16
+// Bootstrap Laravel and handle the request...
17
+/** @var Application $app */
18
+$app = require_once __DIR__.'/../bootstrap/app.php';
19
+
20
+$app->handleRequest(Request::capture());

+ 1
- 0
public/js/filament/actions/actions.js ファイルの表示

@@ -0,0 +1 @@
1
+(()=>{var n=({livewireId:e})=>({actionNestingIndex:null,init(){window.addEventListener("sync-action-modals",t=>{t.detail.id===e&&this.syncActionModals(t.detail.newActionNestingIndex,t.detail.shouldOverlayParentActions??!1)})},syncActionModals(t,i=!1){if(this.actionNestingIndex===t){this.actionNestingIndex!==null&&this.$nextTick(()=>this.openModal());return}let s=this.actionNestingIndex!==null&&t!==null&&t>this.actionNestingIndex;if(this.actionNestingIndex!==null&&!(i&&s)&&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)});})();

+ 1
- 0
public/js/filament/filament/app.js
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 13
- 0
public/js/filament/filament/echo.js
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 1
- 0
public/js/filament/forms/components/checkbox-list.js ファイルの表示

@@ -0,0 +1 @@
1
+function c({livewireId:s}){return{areAllCheckboxesChecked:!1,checkboxListOptions:[],search:"",unsubscribeLivewireHook:null,visibleCheckboxListOptions:[],init(){this.checkboxListOptions=Array.from(this.$root.querySelectorAll(".fi-fo-checkbox-list-option")),this.updateVisibleCheckboxListOptions(),this.$nextTick(()=>{this.checkIfAllCheckboxesAreChecked()}),this.unsubscribeLivewireHook=Livewire.interceptMessage(({message:e,onSuccess:t})=>{t(()=>{this.$nextTick(()=>{e.component.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.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()))},destroy(){this.unsubscribeLivewireHook?.()}}}export{c as default};

+ 38
- 0
public/js/filament/forms/components/code-editor.js
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 1
- 0
public/js/filament/forms/components/color-picker.js
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 1
- 0
public/js/filament/forms/components/date-time-picker.js
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 116
- 0
public/js/filament/forms/components/file-upload.js
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 1
- 0
public/js/filament/forms/components/key-value.js ファイルの表示

@@ -0,0 +1 @@
1
+function a({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)=>{if(!Array.isArray(e))return;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{a as default};

+ 51
- 0
public/js/filament/forms/components/markdown-editor.js
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 144
- 0
public/js/filament/forms/components/rich-editor.js
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 11
- 0
public/js/filament/forms/components/select.js
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 1
- 0
public/js/filament/forms/components/slider.js
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 1
- 0
public/js/filament/forms/components/tags-input.js ファイルの表示

@@ -0,0 +1 @@
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};

+ 1
- 0
public/js/filament/forms/components/textarea.js ファイルの表示

@@ -0,0 +1 @@
1
+function n({initialHeight:e,shouldAutosize:i,state:h}){return{state:h,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=e+"rem")},resize(){if(this.$el.scrollHeight<=0)return;let t=this.$el.style.height;this.$el.style.height="0px";let r=this.$el.scrollHeight;this.$el.style.height=t;let l=parseFloat(e)*parseFloat(getComputedStyle(document.documentElement).fontSize),s=Math.max(r,l)+"px";this.wrapperEl.style.height!==s&&(this.wrapperEl.style.height=s)},setUpResizeObserver(){new ResizeObserver(()=>{this.wrapperEl.style.height=this.$el.style.height}).observe(this.$el)}}}export{n as default};

+ 1
- 0
public/js/filament/notifications/notifications.js
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 1
- 0
public/js/filament/schemas/components/actions.js ファイルの表示

@@ -0,0 +1 @@
1
+var i=()=>({isSticky:!1,width:0,resizeObserver:null,boundUpdateWidth:null,init(){let e=this.$el.parentElement;e&&(this.updateWidth(),this.resizeObserver=new ResizeObserver(()=>this.updateWidth()),this.resizeObserver.observe(e),this.boundUpdateWidth=this.updateWidth.bind(this),window.addEventListener("resize",this.boundUpdateWidth))},enableSticky(){this.isSticky=this.$el.getBoundingClientRect().top>0},disableSticky(){this.isSticky=!1},updateWidth(){let e=this.$el.parentElement;if(!e)return;let t=getComputedStyle(this.$root.querySelector(".fi-ac"));this.width=e.offsetWidth+parseInt(t.marginInlineStart,10)*-1+parseInt(t.marginInlineEnd,10)*-1},destroy(){this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.boundUpdateWidth&&(window.removeEventListener("resize",this.boundUpdateWidth),this.boundUpdateWidth=null)}});export{i as default};

+ 1
- 0
public/js/filament/schemas/components/tabs.js ファイルの表示

@@ -0,0 +1 @@
1
+function v({activeTab:w,isScrollable:f,isTabPersistedInQueryString:m,livewireId:g,tab:T,tabQueryStringKey:r}){return{boundResizeHandler:null,isScrollable:f,resizeDebounceTimer:null,tab:T,unsubscribeLivewireHook:null,withinDropdownIndex:null,withinDropdownMounted:!1,init(){let t=this.getTabs(),e=new URLSearchParams(window.location.search);m&&e.has(r)&&t.includes(e.get(r))&&(this.tab=e.get(r)),(!this.tab||!t.includes(this.tab))&&(this.tab=t[w-1]),this.$watch("tab",()=>{this.updateQueryString(),this.autofocusFields()}),this.autofocusFields(!0),this.unsubscribeLivewireHook=Livewire.interceptMessage(({message:i,onSuccess:a})=>{a(()=>{this.$nextTick(()=>{if(i.component.id!==g)return;let l=this.getTabs();l.includes(this.tab)||(this.tab=l[w-1]??this.tab)})})}),f||(this.boundResizeHandler=this.debouncedUpdateTabsWithinDropdown.bind(this),window.addEventListener("resize",this.boundResizeHandler),this.updateTabsWithinDropdown())},calculateAvailableWidth(t){let e=window.getComputedStyle(t);return Math.floor(t.clientWidth)-Math.ceil(parseFloat(e.paddingLeft))*2},calculateContainerGap(t){let e=window.getComputedStyle(t);return Math.ceil(parseFloat(e.columnGap))},calculateDropdownIconWidth(t){let e=t.querySelector(".fi-icon");return Math.ceil(e.clientWidth)},calculateTabItemGap(t){let e=window.getComputedStyle(t);return Math.ceil(parseFloat(e.columnGap)||8)},calculateTabItemPadding(t){let e=window.getComputedStyle(t);return Math.ceil(parseFloat(e.paddingLeft))+Math.ceil(parseFloat(e.paddingRight))},findOverflowIndex(t,e,i,a,l,h){let u=t.map(n=>Math.ceil(n.clientWidth)),b=t.map(n=>{let c=n.querySelector(".fi-tabs-item-label"),s=n.querySelector(".fi-badge"),o=Math.ceil(c.clientWidth),d=s?Math.ceil(s.clientWidth):0;return{label:o,badge:d,total:o+(d>0?a+d:0)}});for(let n=0;n<t.length;n++){let c=u.slice(0,n+1).reduce((p,y)=>p+y,0),s=n*i,o=b.slice(n+1),d=o.length>0,D=d?Math.max(...o.map(p=>p.total)):0,W=d?l+D+a+h+i:0;if(c+s+W>e)return n}return-1},get isDropdownButtonVisible(){return this.withinDropdownMounted?this.withinDropdownIndex===null?!1:this.getTabs().findIndex(e=>e===this.tab)<this.withinDropdownIndex:!0},getTabs(){return this.$refs.tabsData?JSON.parse(this.$refs.tabsData.value):[]},updateQueryString(){if(!m)return;let t=new URL(window.location.href);t.searchParams.set(r,this.tab),history.replaceState(null,document.title,t.toString())},autofocusFields(t=!1){this.$nextTick(()=>{if(t&&document.activeElement&&document.activeElement!==document.body&&this.$el.compareDocumentPosition(document.activeElement)&Node.DOCUMENT_POSITION_PRECEDING)return;let e=this.$el.querySelectorAll(".fi-sc-tabs-tab.fi-active [autofocus]");for(let i of e)if(i.focus(),document.activeElement===i)break})},debouncedUpdateTabsWithinDropdown(){clearTimeout(this.resizeDebounceTimer),this.resizeDebounceTimer=setTimeout(()=>this.updateTabsWithinDropdown(),150)},async updateTabsWithinDropdown(){this.withinDropdownIndex=null,this.withinDropdownMounted=!1,await this.$nextTick();let t=this.$el.querySelector(".fi-tabs"),e=t.querySelector(".fi-tabs-item:last-child"),i=Array.from(t.children).slice(0,-1),a=i.map(s=>s.style.display);i.forEach(s=>s.style.display=""),t.offsetHeight;let l=this.calculateAvailableWidth(t),h=this.calculateContainerGap(t),u=this.calculateDropdownIconWidth(e),b=this.calculateTabItemGap(i[0]),n=this.calculateTabItemPadding(i[0]),c=this.findOverflowIndex(i,l,h,b,n,u);i.forEach((s,o)=>s.style.display=a[o]),c!==-1&&(this.withinDropdownIndex=c),this.withinDropdownMounted=!0},destroy(){this.unsubscribeLivewireHook?.(),this.boundResizeHandler&&window.removeEventListener("resize",this.boundResizeHandler),clearTimeout(this.resizeDebounceTimer)}}}export{v as default};

+ 1
- 0
public/js/filament/schemas/components/wizard.js ファイルの表示

@@ -0,0 +1 @@
1
+function p({isSkippable:i,isStepPersistedInQueryString:n,key:r,startStep:o,stepQueryStringKey:h}){return{step:null,init(){this.step=this.getSteps().at(o-1),this.$watch("step",()=>{this.updateQueryString(),this.autofocusFields()}),this.autofocusFields(!0)},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.scroll())},goToPreviousStep(){let t=this.getStepIndex(this.step)-1;t<0||(this.step=this.getSteps()[t],this.scroll())},goToStep(t){let e=this.getStepIndex(t);e<=-1||!i&&e>this.getStepIndex(this.step)||(this.step=t,this.scroll())},scroll(){this.$nextTick(()=>{this.$refs.header?.children[this.getStepIndex(this.step)].scrollIntoView({behavior:"smooth",block:"start"})})},autofocusFields(t=!1){this.$nextTick(()=>{if(t&&document.activeElement&&document.activeElement!==document.body&&this.$el.compareDocumentPosition(document.activeElement)&Node.DOCUMENT_POSITION_PRECEDING)return;let e=this.$refs[`step-${this.step}`]?.querySelectorAll("[autofocus]")??[];for(let s of e)if(s.focus(),document.activeElement===s)break})},getStepIndex(t){let e=this.getSteps().findIndex(s=>s===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 i||this.getStepIndex(this.step)>this.getStepIndex(t)},updateQueryString(){if(!n)return;let t=new URL(window.location.href);t.searchParams.set(h,this.step),history.replaceState(null,document.title,t.toString())}}}export{p as default};

+ 1
- 0
public/js/filament/schemas/schemas.js ファイルの表示

@@ -0,0 +1 @@
1
+(()=>{var o=()=>({isSticky:!1,width:0,resizeObserver:null,boundUpdateWidth:null,init(){let i=this.$el.parentElement;i&&(this.updateWidth(),this.resizeObserver=new ResizeObserver(()=>this.updateWidth()),this.resizeObserver.observe(i),this.boundUpdateWidth=this.updateWidth.bind(this),window.addEventListener("resize",this.boundUpdateWidth))},enableSticky(){this.isSticky=this.$el.getBoundingClientRect().top>0},disableSticky(){this.isSticky=!1},updateWidth(){let i=this.$el.parentElement;if(!i)return;let e=getComputedStyle(this.$root.querySelector(".fi-ac"));this.width=i.offsetWidth+parseInt(e.marginInlineStart,10)*-1+parseInt(e.marginInlineEnd,10)*-1},destroy(){this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.boundUpdateWidth&&(window.removeEventListener("resize",this.boundUpdateWidth),this.boundUpdateWidth=null)}});var a=function(i,e,n){let t=i;if(e.startsWith("/")&&(n=!0,e=e.slice(1)),n)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}`},d=i=>{let e=Alpine.findClosest(i,n=>n.__livewire);if(!e)throw"Could not find Livewire component in DOM tree.";return e.__livewire};document.addEventListener("alpine:init",()=>{window.Alpine.data("filamentSchema",({livewireId:i})=>({handleFormValidationError(e){e.detail.livewireId===i&&this.$nextTick(()=>{let n=this.$el.querySelector("[data-validation-error]");if(!n)return;let t=n;for(;t;)t.dispatchEvent(new CustomEvent("expand")),t=t.parentNode;setTimeout(()=>n.closest("[data-field-wrapper]").scrollIntoView({behavior:"smooth",block:"start",inline:"start"}),200)})},isStateChanged(e,n){if(e===void 0)return!1;try{return JSON.stringify(e)!==JSON.stringify(n)}catch{return e!==n}}})),window.Alpine.data("filamentSchemaComponent",({path:i,containerPath:e,$wire:n})=>({$statePath:i,$get:(t,r)=>n.$get(a(e,t,r)),$set:(t,r,s,l=!1)=>n.$set(a(e,t,s),r,l),get $state(){return n.$get(i)}})),window.Alpine.data("filamentActionsSchemaComponent",o),Livewire.interceptMessage(({message:i,onSuccess:e})=>{e(({payload:n})=>{n.effects?.dispatches?.forEach(t=>{if(!t.params?.awaitSchemaComponent)return;let r=Array.from(i.component.el.querySelectorAll(`[wire\\:partial="schema-component::${t.params.awaitSchemaComponent}"]`)).filter(s=>d(s)===i.component);if(r.length!==1){if(r.length>1)throw`Multiple schema components found with key [${t.params.awaitSchemaComponent}].`;window.addEventListener(`schema-component-${component.id}-${t.params.awaitSchemaComponent}-loaded`,()=>{window.dispatchEvent(new CustomEvent(t.name,{detail:t.params}))},{once:!0})}})})})});})();

+ 46
- 0
public/js/filament/support/support.js
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 1
- 0
public/js/filament/tables/components/columns/checkbox.js ファイルの表示

@@ -0,0 +1 @@
1
+function a({name:r,recordKey:s,state:n}){return{error:void 0,isLoading:!1,state:n,unsubscribeLivewireHook:null,init(){this.unsubscribeLivewireHook=Livewire.interceptMessage(({message:e,onSuccess:t})=>{t(()=>{this.$nextTick(()=>{if(this.isLoading||e.component.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let i=this.getServerState();i===void 0||Alpine.raw(this.state)===i||(this.state=i)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||Alpine.raw(this.state)===e)return;this.isLoading=!0;let t=await this.$wire.updateTableColumnState(r,s,this.state);this.error=t?.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)},destroy(){this.unsubscribeLivewireHook?.()}}}export{a as default};

+ 11
- 0
public/js/filament/tables/components/columns/select.js
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 1
- 0
public/js/filament/tables/components/columns/text-input.js ファイルの表示

@@ -0,0 +1 @@
1
+function a({name:i,recordKey:s,state:n}){return{error:void 0,isLoading:!1,state:n,unsubscribeLivewireHook:null,init(){this.unsubscribeLivewireHook=Livewire.interceptMessage(({message:e,onSuccess:t})=>{t(()=>{this.$nextTick(()=>{if(this.isLoading||e.component.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let r=this.getServerState();r===void 0||this.getNormalizedState()===r||(this.state=r)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||this.getNormalizedState()===e)return;this.isLoading=!0;let t=await this.$wire.updateTableColumnState(i,s,this.state);this.error=t?.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},destroy(){this.unsubscribeLivewireHook?.()}}}export{a as default};

+ 1
- 0
public/js/filament/tables/components/columns/toggle.js ファイルの表示

@@ -0,0 +1 @@
1
+function a({name:r,recordKey:s,state:n}){return{error:void 0,isLoading:!1,state:n,unsubscribeLivewireHook:null,init(){this.unsubscribeLivewireHook=Livewire.interceptMessage(({message:e,onSuccess:t})=>{t(()=>{this.$nextTick(()=>{if(this.isLoading||e.component.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let i=this.getServerState();i===void 0||Alpine.raw(this.state)===i||(this.state=i)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||Alpine.raw(this.state)===e)return;this.isLoading=!0;let t=await this.$wire.updateTableColumnState(r,s,this.state);this.error=t?.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)},destroy(){this.unsubscribeLivewireHook?.()}}}export{a as default};

+ 1
- 0
public/js/filament/tables/tables.js
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 30
- 0
public/js/filament/widgets/components/chart.js
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 22
- 0
public/js/filament/widgets/components/stats-overview/stat/chart.js
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 2
- 0
public/robots.txt ファイルの表示

@@ -0,0 +1,2 @@
1
+User-agent: *
2
+Disallow:

+ 11
- 0
resources/css/app.css ファイルの表示

@@ -0,0 +1,11 @@
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: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
10
+        'Segoe UI Symbol', 'Noto Color Emoji';
11
+}

+ 1
- 0
resources/js/app.js ファイルの表示

@@ -0,0 +1 @@
1
+import './bootstrap';

+ 4
- 0
resources/js/bootstrap.js ファイルの表示

@@ -0,0 +1,4 @@
1
+import axios from 'axios';
2
+window.axios = axios;
3
+
4
+window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

+ 225
- 0
resources/views/welcome.blade.php
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 22
- 0
routes/api.php ファイルの表示

@@ -0,0 +1,22 @@
1
+<?php
2
+
3
+use App\Http\Controllers\Api\AlbumController;
4
+use App\Http\Controllers\Api\ContactController;
5
+use App\Http\Controllers\Api\DeeplTranslateController;
6
+use App\Http\Controllers\Api\DevelopmentController;
7
+use App\Http\Controllers\Api\HomePageController;
8
+use App\Http\Controllers\Api\NewsController;
9
+use Illuminate\Http\Request;
10
+use Illuminate\Support\Facades\Route;
11
+
12
+/*
13
+|--------------------------------------------------------------------------
14
+| API Routes
15
+|--------------------------------------------------------------------------
16
+|
17
+| Here is where you can register API routes for your application. These
18
+| routes are loaded by the RouteServiceProvider and all of them will
19
+| be assigned to the "api" middleware group. Make something great!
20
+|
21
+*/
22
+Route::post('/qrcode/generate', [App\Http\Controllers\Api\QrcodeController::class, 'generate']);

+ 8
- 0
routes/console.php ファイルの表示

@@ -0,0 +1,8 @@
1
+<?php
2
+
3
+use Illuminate\Foundation\Inspiring;
4
+use Illuminate\Support\Facades\Artisan;
5
+
6
+Artisan::command('inspire', function () {
7
+    $this->comment(Inspiring::quote());
8
+})->purpose('Display an inspiring quote');

+ 7
- 0
routes/web.php ファイルの表示

@@ -0,0 +1,7 @@
1
+<?php
2
+
3
+use Illuminate\Support\Facades\Route;
4
+
5
+Route::get('/', function () {
6
+    return view('welcome');
7
+});

+ 4
- 0
storage/app/.gitignore ファイルの表示

@@ -0,0 +1,4 @@
1
+*
2
+!private/
3
+!public/
4
+!.gitignore

+ 2
- 0
storage/app/private/.gitignore ファイルの表示

@@ -0,0 +1,2 @@
1
+*
2
+!.gitignore

+ 0
- 0
storage/app/public/.gitignore ファイルの表示


多くのファイルが変更されたため、一部のファイルはこの差分に表示されません