├── .editorconfig
├── .env.example
├── .gitattributes
├── .github
├── FUNDING.yml
├── dependabot.yml
└── workflows
│ ├── pull-requests.yml
│ └── tests.yml
├── .gitignore
├── LICENSE
├── app
├── Admin
│ ├── AdminServiceProvider.php
│ ├── Endpoints
│ │ ├── DeleteUser.php
│ │ ├── GetUser.php
│ │ ├── ListUsers.php
│ │ └── UpdateUser.php
│ └── Middlewares
│ │ └── MustBeAdmin.php
├── Api
│ ├── ApiServiceProvider.php
│ ├── Auth
│ │ ├── Endpoints
│ │ │ ├── Login.php
│ │ │ ├── Logout.php
│ │ │ └── Register.php
│ │ ├── Tests
│ │ │ ├── LoginTest.php
│ │ │ └── RegisterTest.php
│ │ └── routes.php
│ └── User
│ │ ├── Endpoints
│ │ ├── GetMe.php
│ │ └── GetUser.php
│ │ ├── Resources
│ │ └── User.php
│ │ ├── Tests
│ │ ├── GetMeTest.php
│ │ ├── GetUserTest.php
│ │ └── UserAgentTest.php
│ │ └── routes.php
├── App\Test
│ └── App\TestServiceProvider
└── Console
│ └── Kernel.php
├── artisan
├── bootstrap
├── app.php
├── cache
│ └── .gitignore
└── helpers.php
├── composer.json
├── composer.lock
├── config
├── app.php
├── auth.php
├── broadcasting.php
├── cache.php
├── cors.php
├── database.php
├── filesystems.php
├── hashing.php
├── logging.php
├── mail.php
├── passport.php
├── queue.php
├── sanctum.php
├── services.php
├── session.php
└── view.php
├── database
├── .gitignore
├── migrations
│ ├── 2014_10_12_000000_create_users_table.php
│ ├── 2014_10_12_100000_create_password_resets_table.php
│ ├── 2019_08_19_000000_create_failed_jobs_table.php
│ ├── 2019_09_30_081447_create_tags_table.php
│ ├── 2019_12_14_000001_create_personal_access_tokens_table.php
│ └── 2020_05_15_110212_create_user_agents_table.php
└── seeders
│ ├── DatabaseSeeder.php
│ └── UsersTableSeeder.php
├── domain
├── Tag
│ └── Tag.php
└── User
│ ├── Filters
│ └── UserFilter.php
│ ├── Jobs
│ ├── RefreshUserFirstActivedAt.php
│ └── RefreshUserLastActiveAt.php
│ ├── Notifications
│ ├── ResetPassword.php
│ └── VerifyEmail.php
│ ├── Policies
│ └── UserPolicy.php
│ ├── Rules
│ └── Phone.php
│ ├── User.php
│ ├── UserAgent.php
│ └── UserFactory.php
├── infrastructure
├── Actions
│ ├── Action.php
│ └── FakeAction.php
├── Exceptions
│ └── Handler.php
├── Generator
│ ├── AppHelper.php
│ ├── Commands
│ │ ├── App
│ │ │ ├── CreateAppCommand.php
│ │ │ ├── CreateControllerCommand.php
│ │ │ ├── CreateEndpointCommand.php
│ │ │ ├── CreateMiddlewareCommand.php
│ │ │ ├── CreateRequestCommand.php
│ │ │ └── CreateResourceCommand.php
│ │ ├── Domain
│ │ │ ├── CreateActionCommand.php
│ │ │ ├── CreateCastCommand.php
│ │ │ ├── CreateChannelCommand.php
│ │ │ ├── CreateDomainCommand.php
│ │ │ ├── CreateEventCommand.php
│ │ │ ├── CreateExceptionCommand.php
│ │ │ ├── CreateFactoryCommand.php
│ │ │ ├── CreateFilterCommand.php
│ │ │ ├── CreateJobCommand.php
│ │ │ ├── CreateListenerCommand.php
│ │ │ ├── CreateMailCommand.php
│ │ │ ├── CreateModelCommand.php
│ │ │ ├── CreateNotificationCommand.php
│ │ │ ├── CreateObserverCommand.php
│ │ │ ├── CreatePolicyCommand.php
│ │ │ ├── CreateRuleCommand.php
│ │ │ └── CreateScopeCommand.php
│ │ ├── WithAppOption.php
│ │ └── WithDomainOption.php
│ ├── DomainHelper.php
│ ├── GeneratorServiceProvider.php
│ └── stubs
│ │ ├── action.stub
│ │ ├── endpoint.stub
│ │ └── service-provider.stub
├── Http
│ ├── Kernel.php
│ └── Middleware
│ │ ├── AcceptJsonResponse.php
│ │ ├── Authenticate.php
│ │ ├── EncryptCookies.php
│ │ ├── PreventRequestsDuringMaintenance.php
│ │ ├── RedirectIfAuthenticated.php
│ │ ├── RefreshUserActiveAt.php
│ │ ├── SetLocale.php
│ │ ├── StoreUserAgent.php
│ │ ├── TrimStrings.php
│ │ ├── TrustProxies.php
│ │ ├── UseApiGuard.php
│ │ └── VerifyCsrfToken.php
├── Providers
│ ├── AppServiceProvider.php
│ ├── AuthServiceProvider.php
│ ├── BroadcastServiceProvider.php
│ ├── EventServiceProvider.php
│ └── RouteServiceProvider.php
└── Traits
│ ├── UseTableNameAsMorphClass.php
│ └── UsingUuidAsPrimaryKey.php
├── lang
├── en.json
├── en
│ ├── auth.php
│ ├── pagination.php
│ ├── passwords.php
│ ├── validation.php
│ └── verification.php
├── es
│ ├── auth.php
│ ├── pagination.php
│ ├── passwords.php
│ └── validation.php
└── zh-CN
│ ├── auth.php
│ ├── pagination.php
│ ├── passwords.php
│ └── validation.php
├── phpunit.xml
├── public
├── .htaccess
├── favicon.ico
├── img
│ └── default-avatar.png
├── index.php
├── robots.txt
└── web.config
├── readme.md
├── routes
├── api.php
├── channels.php
├── console.php
└── web.php
├── server.php
├── storage
├── app
│ ├── .gitignore
│ └── public
│ │ └── .gitignore
├── framework
│ ├── .gitignore
│ ├── cache
│ │ ├── .gitignore
│ │ └── data
│ │ │ └── .gitignore
│ ├── sessions
│ │ └── .gitignore
│ ├── testing
│ │ └── .gitignore
│ └── views
│ │ └── .gitignore
└── logs
│ └── .gitignore
├── tests
├── CreatesApplication.php
├── Feature
│ └── .gitkeep
├── TestCase.php
└── Unit
│ └── .gitkeep
└── worker.conf
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | insert_final_newline = true
7 | indent_style = space
8 | indent_size = 4
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
14 | [*.{vue,js,json,html,scss,blade.php,yml}]
15 | indent_size = 2
16 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | APP_NAME="Laravel"
2 | APP_ENV=local
3 | APP_KEY=
4 | APP_DEBUG=true
5 | APP_LOG_LEVEL=debug
6 | APP_URL=http://localhost
7 |
8 | LOG_CHANNEL=stack
9 |
10 | DB_CONNECTION=mysql
11 | DB_HOST=127.0.0.1
12 | DB_PORT=3306
13 | DB_DATABASE=laravel
14 | DB_USERNAME=root
15 | DB_PASSWORD=secret
16 |
17 | BROADCAST_DRIVER=log
18 | CACHE_DRIVER=file
19 | QUEUE_CONNECTION=sync
20 | SESSION_DRIVER=file
21 | SESSION_LIFETIME=120
22 |
23 | REDIS_HOST=127.0.0.1
24 | REDIS_PASSWORD=null
25 | REDIS_PORT=6379
26 | REDIS_PREFIX=laravel_
27 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 | *.css linguist-vendored
3 | *.scss linguist-vendored
4 | *.js linguist-vendored
5 | CHANGELOG.md export-ignore
6 | README.md export-ignore
7 | .travis.yml export-ignore
8 | .env.dusk.local export-ignore
9 | .env.dusk.testing export-ignore
10 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [overtrue]
2 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: composer
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | time: "21:00"
8 | open-pull-requests-limit: 10
9 |
--------------------------------------------------------------------------------
/.github/workflows/pull-requests.yml:
--------------------------------------------------------------------------------
1 | name: pull requests
2 |
3 | on:
4 | pull_request_target:
5 | types: [opened]
6 |
7 | permissions:
8 | pull-requests: write
9 |
10 | jobs:
11 | uneditable:
12 | uses: laravel/.github/.github/workflows/pull-requests.yml@main
13 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | tests:
7 | runs-on: ubuntu-latest
8 | strategy:
9 | matrix:
10 | php_version: [ 8.1, 8.2 ]
11 | steps:
12 | - name: Checkout code
13 | uses: actions/checkout@v2
14 |
15 | - name: Setup PHP
16 | uses: shivammathur/setup-php@v2
17 | with:
18 | php-version: ${{ matrix.php_version }}
19 | coverage: none
20 |
21 | - name: Install Composer dependencies
22 | run: composer install --prefer-dist --no-interaction
23 |
24 | - name: Copy environment file
25 | run: cp .env.example .env
26 |
27 | - name: Generate app key
28 | run: php artisan key:generate
29 |
30 | - name: Execute tests
31 | run: vendor/bin/phpunit --testdox
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /public/hot
3 | /public/storage
4 | /storage/*.key
5 | /vendor
6 | /public/dist
7 | /public/*.js
8 | /public/js
9 | /public/css
10 | /public/build
11 | /public/fonts
12 | /public/mix-manifest.json
13 | .env
14 | .phpunit.result.cache
15 | Homestead.json
16 | Homestead.yaml
17 | npm-debug.log
18 | yarn-error.log
19 | phpunit.dusk.xml
20 | *.code-workspace
21 | package-lock.json
22 | .idea
23 | .php-cs-fixer.cache
24 | .phpstorm.meta.php
25 | _ide_helper.php
26 | cghooks.lock
27 | hooks
28 | .env.backup
29 | .phpunit.cache/
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Cretu Eusebiu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/app/Admin/AdminServiceProvider.php:
--------------------------------------------------------------------------------
1 | middleware(['api', 'auth', MustBeAdmin::class])
15 | ->as('admin.')
16 | ->group(function () {
17 | Route::get('/users', Endpoints\ListUsers::class)->name('users.index');
18 | Route::get('/users/{user}', Endpoints\GetUser::class)->name('users.show');
19 | Route::put('/users/{user}', Endpoints\UpdateUser::class)->name('users.update');
20 | Route::delete('/users/{user}', Endpoints\DeleteUser::class)->name('users.delete');
21 | });
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/Admin/Endpoints/DeleteUser.php:
--------------------------------------------------------------------------------
1 | delete();
13 |
14 | return response()->noContent();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/Admin/Endpoints/GetUser.php:
--------------------------------------------------------------------------------
1 | all())
13 | ->latest()
14 | ->paginate($request->get('per_page', 20));
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/Admin/Endpoints/UpdateUser.php:
--------------------------------------------------------------------------------
1 | update($request->all());
13 |
14 | return $user;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/Admin/Middlewares/MustBeAdmin.php:
--------------------------------------------------------------------------------
1 | guest() || ! $request->user()->isAdmin(), 403, '非法访问!');
13 |
14 | return $next($request);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/Api/ApiServiceProvider.php:
--------------------------------------------------------------------------------
1 | middleware('api')
14 | ->group(__DIR__.'/User/routes.php')
15 | ->group(__DIR__.'/Auth/routes.php');
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/Api/Auth/Endpoints/Login.php:
--------------------------------------------------------------------------------
1 | validate([
21 | 'username' => 'required',
22 | 'password' => 'required',
23 | 'device_name' => 'string',
24 | ]);
25 |
26 | /* @var User $user */
27 | $user = User::where('username', $request->input('username'))->first();
28 |
29 | if (! $user || ! Hash::check($request->input('password'), $user->password)) {
30 | throw ValidationException::withMessages(['email' => ['The provided credentials are incorrect.']]);
31 | }
32 |
33 | return $user->createDeviceToken($request->get('device_name'));
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/Api/Auth/Endpoints/Logout.php:
--------------------------------------------------------------------------------
1 | user()->tokens()->delete();
13 |
14 | return \response()->noContent();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/Api/Auth/Endpoints/Register.php:
--------------------------------------------------------------------------------
1 | validate([
16 | 'username' => 'required|unique:users',
17 | 'password' => 'required',
18 | ]);
19 |
20 | /* @var \Domain\User\User $user */
21 | $user = User::create($request->all());
22 |
23 | return $user->createDeviceToken($request->get('device_name'));
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/Api/Auth/Tests/LoginTest.php:
--------------------------------------------------------------------------------
1 | create();
13 | $this->postJson(route('auth.login'), [
14 | 'username' => $user->username,
15 | 'password' => 'password',
16 | ])
17 | ->assertSuccessful()
18 | ->assertJsonStructure(['token'])
19 | ->assertJson(['type' => 'bearer']);
20 | }
21 |
22 | public function test_can_fetch_self()
23 | {
24 | $user = User::factory()->create();
25 | $this->actingAs($user);
26 | $this->getJson(route('users.me'))
27 | ->assertSuccessful()
28 | ->assertJsonStructure(['id', 'nickname', 'username']);
29 | }
30 |
31 | public function test_can_logout()
32 | {
33 | $user = User::factory()->create();
34 | $token = $this->postJson(route('auth.login'), [
35 | 'username' => $user->username,
36 | 'password' => 'password',
37 | ])->json()['token'];
38 |
39 | $headers = ['Authorization' => 'Bearer '.$token];
40 |
41 | $this->withHeaders($headers)->postJson(route('auth.logout'))
42 | ->assertSuccessful();
43 |
44 | $this->refreshApplication();
45 |
46 | $this->flushHeaders()->getJson(route('users.me'))
47 | ->assertStatus(401);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/Api/Auth/Tests/RegisterTest.php:
--------------------------------------------------------------------------------
1 | postJson(route('auth.register'), [
14 | 'nickname' => 'Test User',
15 | 'username' => 'test@test.app',
16 | 'password' => 'secret',
17 | 'password_confirmation' => 'secret',
18 | ])
19 | ->assertSuccessful()
20 | ->assertJsonStructure(['token', 'type']);
21 | }
22 |
23 | /** @test */
24 | public function can_not_register_with_existing_username()
25 | {
26 | User::factory()->create(['username' => 'test@test.app']);
27 |
28 | $this->postJson(route('auth.register'), [
29 | 'nickname' => 'Test User',
30 | 'username' => 'test@test.app',
31 | 'password' => 'secret',
32 | 'password_confirmation' => 'secret',
33 | ])
34 | ->assertStatus(422)
35 | ->assertJsonValidationErrors(['username']);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/Api/Auth/routes.php:
--------------------------------------------------------------------------------
1 | group(function () {
8 | Route::post('login', Endpoints\Login::class)->name('auth.login');
9 | Route::post('register', Endpoints\Register::class)->name('auth.register');
10 |
11 | Route::middleware('auth:sanctum')->group(function () {
12 | Route::post('logout', Endpoints\Logout::class)->name('auth.logout');
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/app/Api/User/Endpoints/GetMe.php:
--------------------------------------------------------------------------------
1 | user();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/Api/User/Endpoints/GetUser.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | public function toArray(Request $request): array
16 | {
17 | return $this->resource->only(\Domain\User\User::SAFE_FIELDS);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/Api/User/Tests/GetMeTest.php:
--------------------------------------------------------------------------------
1 | create();
13 | $this->actingAs($user);
14 |
15 | $this->getJson(route('users.me'))->assertSuccessful()
16 | ->assertJsonFragment([
17 | 'id' => $user->id,
18 | 'username' => $user->username,
19 | 'nickname' => $user->nickname,
20 | ]);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/Api/User/Tests/GetUserTest.php:
--------------------------------------------------------------------------------
1 | create();
13 | $user2 = User::factory()->create();
14 | $this->actingAs($user);
15 |
16 | $this->getJson(route('users.show', $user2->id))->assertSuccessful()
17 | ->assertJsonFragment([
18 | 'id' => $user2->id,
19 | 'username' => $user2->username,
20 | 'nickname' => $user2->nickname,
21 | ]);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/Api/User/Tests/UserAgentTest.php:
--------------------------------------------------------------------------------
1 | create();
15 |
16 | // 任意访问一个地址通过中间件记录 UA
17 | $this->actingAs($alex)->get(route('users.show', $alex->id), ['User-Agent' => 'Alex\'s Browser'])->assertStatus(200);
18 | $this->assertDatabaseHas('user_agents', ['user_id' => $alex->id, 'agent' => 'Alex\'s Browser']);
19 | $this->assertDatabaseCount('user_agents', 1);
20 |
21 | // 不带 UA 的请求
22 | $this->get(route('users.show', $alex->id), ['User-Agent' => ''])->assertStatus(200);
23 | $this->assertDatabaseHas('user_agents', ['user_id' => $alex->id, 'agent' => 'Unknown']);
24 | $this->assertDatabaseCount('user_agents', 2);
25 |
26 | // 同一用户不同 UA
27 | $this->get(route('users.show', $alex->id), ['User-Agent' => 'Alex\'s Other Browser'])->assertStatus(200);
28 | $this->assertDatabaseHas('user_agents', ['user_id' => $alex->id, 'agent' => 'Alex\'s Other Browser']);
29 | $this->assertDatabaseCount('user_agents', 3);
30 |
31 | $this->get(route('users.show', $alex->id), ['User-Agent' => 'sketch'])->assertStatus(200);
32 | $this->assertDatabaseHas('user_agents', ['user_id' => $alex->id, 'agent' => 'sketch']);
33 | $this->assertDatabaseCount('user_agents', 4);
34 | }
35 |
36 | public function test_record_user_agent_middleware_update_availability()
37 | {
38 | $alex = User::factory()->create();
39 |
40 | $this->actingAs($alex)->get(route('users.show', $alex->id), ['User-Agent' => 'Alex\'s Browser'])->assertStatus(200);
41 |
42 | $oldRecord = UserAgent::where('user_id', $alex->id)->first();
43 |
44 | Carbon::setTestNow(\now()->addMinutes(5));
45 |
46 | $this->actingAs($alex)->get(route('users.show', $alex->id), ['User-Agent' => 'Alex\'s another Browser'])->assertStatus(200);
47 | $this->assertDatabaseHas('user_agents', ['user_id' => $alex->id, 'agent' => 'Alex\'s another Browser']);
48 | $this->assertNotEquals($oldRecord->last_used_at, UserAgent::where('user_id', $alex->id)->where('agent', 'Alex\'s another Browser')->first()->last_used_at);
49 |
50 | // 使用不同 UA 更新记录 last_used_at
51 | $this->actingAs($alex)->get(route('users.show', $alex->id), ['User-Agent' => 'sketch'])->assertStatus(200);
52 |
53 | $oldRecord = UserAgent::where('user_id', $alex->id)->where('agent', 'sketch')->first();
54 |
55 | Carbon::setTestNow(\now()->addMinutes(10));
56 | $this->actingAs($alex)->get(route('users.show', $alex->id), ['User-Agent' => 'sketch'])->assertStatus(200);
57 | $this->assertDatabaseHas('user_agents', ['user_id' => $alex->id, 'agent' => 'sketch']);
58 | $this->assertNotEquals($oldRecord->last_used_at, UserAgent::where('user_id', $alex->id)->where('agent', 'sketch')->first()->last_used_at);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/app/Api/User/routes.php:
--------------------------------------------------------------------------------
1 | group(function () {
7 | Route::get('/me', Endpoints\GetMe::class)->name('users.me');
8 | Route::get('/users/{user}', Endpoints\GetUser::class)->name('users.show');
9 | });
10 |
--------------------------------------------------------------------------------
/app/App\Test/App\TestServiceProvider:
--------------------------------------------------------------------------------
1 | name('users.index');
16 | });
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/Console/Kernel.php:
--------------------------------------------------------------------------------
1 | command('inspire')
17 | // ->hourly();
18 | }
19 |
20 | protected function commands()
21 | {
22 | $this->load(__DIR__.'/Commands');
23 |
24 | require base_path('routes/console.php');
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/artisan:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | make(Illuminate\Contracts\Console\Kernel::class);
34 |
35 | $status = $kernel->handle(
36 | $input = new Symfony\Component\Console\Input\ArgvInput,
37 | new Symfony\Component\Console\Output\ConsoleOutput
38 | );
39 |
40 | /*
41 | |--------------------------------------------------------------------------
42 | | Shutdown The Application
43 | |--------------------------------------------------------------------------
44 | |
45 | | Once Artisan has finished running, we will fire off the shutdown events
46 | | so that any final work may be done by the application before we shut
47 | | down the process. This is the last thing to happen to the request.
48 | |
49 | */
50 |
51 | $kernel->terminate($input, $status);
52 |
53 | exit($status);
54 |
--------------------------------------------------------------------------------
/bootstrap/app.php:
--------------------------------------------------------------------------------
1 | singleton(
30 | Illuminate\Contracts\Http\Kernel::class,
31 | \Infrastructure\Http\Kernel::class
32 | );
33 |
34 | $app->singleton(
35 | Illuminate\Contracts\Console\Kernel::class,
36 | \App\Console\Kernel::class
37 | );
38 |
39 | $app->singleton(
40 | Illuminate\Contracts\Debug\ExceptionHandler::class,
41 | \Infrastructure\Exceptions\Handler::class
42 | );
43 |
44 | /*
45 | |--------------------------------------------------------------------------
46 | | Return The Application
47 | |--------------------------------------------------------------------------
48 | |
49 | | This script returns the application instance. The instance is given to
50 | | the calling script so we can separate the building of the instances
51 | | from the actual running of the application and sending responses.
52 | |
53 | */
54 |
55 | return $app;
56 |
--------------------------------------------------------------------------------
/bootstrap/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/bootstrap/helpers.php:
--------------------------------------------------------------------------------
1 | env('APP_NAME', 'Laravel'),
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Application Environment
23 | |--------------------------------------------------------------------------
24 | |
25 | | This value determines the "environment" your application is currently
26 | | running in. This may determine how you prefer to configure various
27 | | services the application utilizes. Set this in your ".env" file.
28 | |
29 | */
30 |
31 | 'env' => env('APP_ENV', 'production'),
32 |
33 | /*
34 | |--------------------------------------------------------------------------
35 | | Application Debug Mode
36 | |--------------------------------------------------------------------------
37 | |
38 | | When your application is in debug mode, detailed error messages with
39 | | stack traces will be shown on every error that occurs within your
40 | | application. If disabled, a simple generic error page is shown.
41 | |
42 | */
43 |
44 | 'debug' => env('APP_DEBUG', false),
45 |
46 | /*
47 | |--------------------------------------------------------------------------
48 | | Application URL
49 | |--------------------------------------------------------------------------
50 | |
51 | | This URL is used by the console to properly generate URLs when using
52 | | the Artisan command line tool. You should set this to the root of
53 | | your application so that it is used when running Artisan tasks.
54 | |
55 | */
56 |
57 | 'url' => env('APP_URL', 'http://localhost'),
58 |
59 | 'asset_url' => env('ASSET_URL'),
60 |
61 | /*
62 | |--------------------------------------------------------------------------
63 | | Application Timezone
64 | |--------------------------------------------------------------------------
65 | |
66 | | Here you may specify the default timezone for your application, which
67 | | will be used by the PHP date and date-time functions. We have gone
68 | | ahead and set this to a sensible default for you out of the box.
69 | |
70 | */
71 |
72 | 'timezone' => 'UTC',
73 |
74 | /*
75 | |--------------------------------------------------------------------------
76 | | Application Locale Configuration
77 | |--------------------------------------------------------------------------
78 | |
79 | | The application locale determines the default locale that will be used
80 | | by the translation service provider. You are free to set this value
81 | | to any of the locales which will be supported by the application.
82 | |
83 | */
84 |
85 | 'locale' => 'zh-CN',
86 |
87 | 'locales' => [
88 | 'en' => 'EN',
89 | 'zh-CN' => '中文',
90 | 'es' => 'ES',
91 | ],
92 |
93 | /*
94 | |--------------------------------------------------------------------------
95 | | Application Fallback Locale
96 | |--------------------------------------------------------------------------
97 | |
98 | | The fallback locale determines the locale to use when the current one
99 | | is not available. You may change the value to correspond to any of
100 | | the language folders that are provided through your application.
101 | |
102 | */
103 |
104 | 'fallback_locale' => 'en',
105 |
106 | /*
107 | |--------------------------------------------------------------------------
108 | | Faker Locale
109 | |--------------------------------------------------------------------------
110 | |
111 | | This locale will be used by the Faker PHP library when generating fake
112 | | data for your database seeds. For example, this will be used to get
113 | | localized telephone numbers, street address information and more.
114 | |
115 | */
116 |
117 | 'faker_locale' => 'en_US',
118 |
119 | /*
120 | |--------------------------------------------------------------------------
121 | | Encryption Key
122 | |--------------------------------------------------------------------------
123 | |
124 | | This key is used by the Illuminate encrypter service and should be set
125 | | to a random, 32 character string, otherwise these encrypted strings
126 | | will not be safe. Please do this before deploying an application!
127 | |
128 | */
129 |
130 | 'key' => env('APP_KEY'),
131 |
132 | 'cipher' => 'AES-256-CBC',
133 |
134 | /*
135 | |--------------------------------------------------------------------------
136 | | Maintenance Mode Driver
137 | |--------------------------------------------------------------------------
138 | |
139 | | These configuration options determine the driver used to determine and
140 | | manage Laravel's "maintenance mode" status. The "cache" driver will
141 | | allow maintenance mode to be controlled across multiple machines.
142 | |
143 | | Supported drivers: "file", "cache"
144 | |
145 | */
146 |
147 | 'maintenance' => [
148 | 'driver' => 'file',
149 | // 'store' => 'redis',
150 | ],
151 |
152 | /*
153 | |--------------------------------------------------------------------------
154 | | Autoloaded Service Providers
155 | |--------------------------------------------------------------------------
156 | |
157 | | The service providers listed here will be automatically loaded on the
158 | | request to your application. Feel free to add your own services to
159 | | this array to grant expanded functionality to your applications.
160 | |
161 | */
162 |
163 | 'providers' => [
164 |
165 | /*
166 | * Laravel Framework Service Providers...
167 | */
168 | Illuminate\Auth\AuthServiceProvider::class,
169 | Illuminate\Broadcasting\BroadcastServiceProvider::class,
170 | Illuminate\Bus\BusServiceProvider::class,
171 | Illuminate\Cache\CacheServiceProvider::class,
172 | Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
173 | Illuminate\Cookie\CookieServiceProvider::class,
174 | Illuminate\Database\DatabaseServiceProvider::class,
175 | Illuminate\Encryption\EncryptionServiceProvider::class,
176 | Illuminate\Filesystem\FilesystemServiceProvider::class,
177 | Illuminate\Foundation\Providers\FoundationServiceProvider::class,
178 | Illuminate\Hashing\HashServiceProvider::class,
179 | Illuminate\Mail\MailServiceProvider::class,
180 | Illuminate\Notifications\NotificationServiceProvider::class,
181 | Illuminate\Pagination\PaginationServiceProvider::class,
182 | Illuminate\Pipeline\PipelineServiceProvider::class,
183 | Illuminate\Queue\QueueServiceProvider::class,
184 | Illuminate\Redis\RedisServiceProvider::class,
185 | Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
186 | Illuminate\Session\SessionServiceProvider::class,
187 | Illuminate\Translation\TranslationServiceProvider::class,
188 | Illuminate\Validation\ValidationServiceProvider::class,
189 | Illuminate\View\ViewServiceProvider::class,
190 |
191 | /*
192 | * Package Service Providers...
193 | */
194 |
195 | /**
196 | * Infrastructure Service Providers...
197 | */
198 | \Infrastructure\Generator\GeneratorServiceProvider::class,
199 | \Infrastructure\Providers\AppServiceProvider::class,
200 | \Infrastructure\Providers\AuthServiceProvider::class,
201 | \Infrastructure\Providers\EventServiceProvider::class,
202 | \Infrastructure\Providers\RouteServiceProvider::class,
203 |
204 | /*
205 | * Application Service Providers...
206 | */
207 | \App\Admin\AdminServiceProvider::class,
208 | \App\Api\ApiServiceProvider::class,
209 |
210 | ],
211 |
212 | 'aliases' => Facade::defaultAliases()->merge([
213 | // 'ExampleClass' => App\Example\ExampleClass::class,
214 | ])->toArray(),
215 | ];
216 |
--------------------------------------------------------------------------------
/config/auth.php:
--------------------------------------------------------------------------------
1 | [
17 | 'guard' => 'api',
18 | 'passwords' => 'users',
19 | ],
20 |
21 | /*
22 | |--------------------------------------------------------------------------
23 | | Authentication Guards
24 | |--------------------------------------------------------------------------
25 | |
26 | | Next, you may define every authentication guard for your application.
27 | | Of course, a great default configuration has been defined for you
28 | | here which uses session storage and the Eloquent user provider.
29 | |
30 | | All authentication drivers have a user provider. This defines how the
31 | | users are actually retrieved out of your database or other storage
32 | | mechanisms used by this application to persist your user's data.
33 | |
34 | | Supported: "session", "token"
35 | |
36 | */
37 |
38 | 'guards' => [
39 | 'web' => [
40 | 'driver' => 'session',
41 | 'provider' => 'users',
42 | ],
43 |
44 | 'api' => [
45 | 'driver' => 'sanctum',
46 | 'provider' => 'users',
47 | ],
48 | ],
49 |
50 | /*
51 | |--------------------------------------------------------------------------
52 | | User Providers
53 | |--------------------------------------------------------------------------
54 | |
55 | | All authentication drivers have a user provider. This defines how the
56 | | users are actually retrieved out of your database or other storage
57 | | mechanisms used by this application to persist your user's data.
58 | |
59 | | If you have multiple user tables or models you may configure multiple
60 | | sources which represent each model / table. These sources may then
61 | | be assigned to any extra authentication guards you have defined.
62 | |
63 | | Supported: "database", "eloquent"
64 | |
65 | */
66 |
67 | 'providers' => [
68 | 'users' => [
69 | 'driver' => 'eloquent',
70 | 'model' => \Domain\User\User::class,
71 | ],
72 |
73 | // 'users' => [
74 | // 'driver' => 'database',
75 | // 'table' => 'users',
76 | // ],
77 | ],
78 |
79 | /*
80 | |--------------------------------------------------------------------------
81 | | Resetting Passwords
82 | |--------------------------------------------------------------------------
83 | |
84 | | You may specify multiple password reset configurations if you have more
85 | | than one user table or model in the application and you want to have
86 | | separate password reset settings based on the specific user types.
87 | |
88 | | The expire time is the number of minutes that the reset token should be
89 | | considered valid. This security feature keeps tokens short-lived so
90 | | they have less time to be guessed. You may change this as needed.
91 | |
92 | */
93 |
94 | 'passwords' => [
95 | 'users' => [
96 | 'provider' => 'users',
97 | 'table' => 'password_resets',
98 | 'expire' => 60,
99 | ],
100 | ],
101 |
102 | ];
103 |
--------------------------------------------------------------------------------
/config/broadcasting.php:
--------------------------------------------------------------------------------
1 | env('BROADCAST_DRIVER', 'null'),
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Broadcast Connections
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here you may define all of the broadcast connections that will be used
26 | | to broadcast events to other systems or over websockets. Samples of
27 | | each available type of connection are provided inside this array.
28 | |
29 | */
30 |
31 | 'connections' => [
32 |
33 | 'pusher' => [
34 | 'driver' => 'pusher',
35 | 'key' => env('PUSHER_APP_KEY'),
36 | 'secret' => env('PUSHER_APP_SECRET'),
37 | 'app_id' => env('PUSHER_APP_ID'),
38 | 'options' => [
39 | 'cluster' => env('PUSHER_APP_CLUSTER'),
40 | 'encrypted' => true,
41 | ],
42 | ],
43 |
44 | 'redis' => [
45 | 'driver' => 'redis',
46 | 'connection' => 'default',
47 | ],
48 |
49 | 'log' => [
50 | 'driver' => 'log',
51 | ],
52 |
53 | 'null' => [
54 | 'driver' => 'null',
55 | ],
56 |
57 | ],
58 |
59 | ];
60 |
--------------------------------------------------------------------------------
/config/cache.php:
--------------------------------------------------------------------------------
1 | env('CACHE_DRIVER', 'file'),
22 |
23 | /*
24 | |--------------------------------------------------------------------------
25 | | Cache Stores
26 | |--------------------------------------------------------------------------
27 | |
28 | | Here you may define all of the cache "stores" for your application as
29 | | well as their drivers. You may even define multiple stores for the
30 | | same cache driver to group types of items stored in your caches.
31 | |
32 | */
33 |
34 | 'stores' => [
35 |
36 | 'apc' => [
37 | 'driver' => 'apc',
38 | ],
39 |
40 | 'array' => [
41 | 'driver' => 'array',
42 | ],
43 |
44 | 'database' => [
45 | 'driver' => 'database',
46 | 'table' => 'cache',
47 | 'connection' => null,
48 | ],
49 |
50 | 'file' => [
51 | 'driver' => 'file',
52 | 'path' => storage_path('framework/cache/data'),
53 | ],
54 |
55 | 'memcached' => [
56 | 'driver' => 'memcached',
57 | 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
58 | 'sasl' => [
59 | env('MEMCACHED_USERNAME'),
60 | env('MEMCACHED_PASSWORD'),
61 | ],
62 | 'options' => [
63 | // Memcached::OPT_CONNECT_TIMEOUT => 2000,
64 | ],
65 | 'servers' => [
66 | [
67 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'),
68 | 'port' => env('MEMCACHED_PORT', 11211),
69 | 'weight' => 100,
70 | ],
71 | ],
72 | ],
73 |
74 | 'redis' => [
75 | 'driver' => 'redis',
76 | 'connection' => 'cache',
77 | ],
78 |
79 | 'dynamodb' => [
80 | 'driver' => 'dynamodb',
81 | 'key' => env('AWS_ACCESS_KEY_ID'),
82 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
83 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
84 | 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
85 | ],
86 |
87 | ],
88 |
89 | /*
90 | |--------------------------------------------------------------------------
91 | | Cache Key Prefix
92 | |--------------------------------------------------------------------------
93 | |
94 | | When utilizing a RAM based store such as APC or Memcached, there might
95 | | be other applications utilizing the same cache. So, we'll specify a
96 | | value to get prefixed to all our keys so we can avoid collisions.
97 | |
98 | */
99 |
100 | 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
101 |
102 | ];
103 |
--------------------------------------------------------------------------------
/config/cors.php:
--------------------------------------------------------------------------------
1 | ['admin/*', 'api/*', 'oauth/*', 'sanctum/csrf-cookie'],
25 |
26 | /*
27 | * Matches the request method. `[*]` allows all methods.
28 | */
29 | 'allowed_methods' => ['*'],
30 |
31 | /*
32 | * Matches the request origin. `[*]` allows all origins.
33 | */
34 | 'allowed_origins' => ['*'],
35 |
36 | /*
37 | * Matches the request origin with, similar to `Request::is()`
38 | */
39 | 'allowed_origins_patterns' => [],
40 |
41 | /*
42 | * Sets the Access-Control-Allow-Headers response header. `[*]` allows all headers.
43 | */
44 | 'allowed_headers' => ['*'],
45 |
46 | /*
47 | * Sets the Access-Control-Expose-Headers response header with these headers.
48 | */
49 | 'exposed_headers' => [],
50 |
51 | /*
52 | * Sets the Access-Control-Max-Age response header when > 0.
53 | */
54 | 'max_age' => 0,
55 |
56 | /*
57 | * Sets the Access-Control-Allow-Credentials header.
58 | */
59 | 'supports_credentials' => false,
60 | ];
61 |
--------------------------------------------------------------------------------
/config/database.php:
--------------------------------------------------------------------------------
1 | env('DB_CONNECTION', 'mysql'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Database Connections
21 | |--------------------------------------------------------------------------
22 | |
23 | | Here are each of the database connections setup for your application.
24 | | Of course, examples of configuring each database platform that is
25 | | supported by Laravel is shown below to make development simple.
26 | |
27 | |
28 | | All database work in Laravel is done through the PHP PDO facilities
29 | | so make sure you have the driver for your particular database of
30 | | choice installed on your machine before you begin development.
31 | |
32 | */
33 |
34 | 'connections' => [
35 |
36 | 'sqlite' => [
37 | 'driver' => 'sqlite',
38 | 'database' => env('DB_DATABASE', database_path('database.sqlite')),
39 | 'prefix' => '',
40 | 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
41 | ],
42 |
43 | 'mysql' => [
44 | 'driver' => 'mysql',
45 | 'host' => env('DB_HOST', '127.0.0.1'),
46 | 'port' => env('DB_PORT', '3306'),
47 | 'database' => env('DB_DATABASE', 'forge'),
48 | 'username' => env('DB_USERNAME', 'forge'),
49 | 'password' => env('DB_PASSWORD', ''),
50 | 'unix_socket' => env('DB_SOCKET', ''),
51 | 'charset' => 'utf8mb4',
52 | 'collation' => 'utf8mb4_unicode_ci',
53 | 'prefix' => '',
54 | 'prefix_indexes' => true,
55 | 'strict' => true,
56 | 'engine' => null,
57 | 'options' => extension_loaded('pdo_mysql') ? array_filter([
58 | PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
59 | ]) : [],
60 | ],
61 |
62 | 'pgsql' => [
63 | 'driver' => 'pgsql',
64 | 'host' => env('DB_HOST', '127.0.0.1'),
65 | 'port' => env('DB_PORT', '5432'),
66 | 'database' => env('DB_DATABASE', 'forge'),
67 | 'username' => env('DB_USERNAME', 'forge'),
68 | 'password' => env('DB_PASSWORD', ''),
69 | 'charset' => 'utf8',
70 | 'prefix' => '',
71 | 'prefix_indexes' => true,
72 | 'search_path' => 'public',
73 | 'sslmode' => 'prefer',
74 | ],
75 | ],
76 |
77 | /*
78 | |--------------------------------------------------------------------------
79 | | Migration Repository Table
80 | |--------------------------------------------------------------------------
81 | |
82 | | This table keeps track of all the migrations that have already run for
83 | | your application. Using this information, we can determine which of
84 | | the migrations on disk haven't actually been run in the database.
85 | |
86 | */
87 |
88 | 'migrations' => 'migrations',
89 |
90 | /*
91 | |--------------------------------------------------------------------------
92 | | Redis Databases
93 | |--------------------------------------------------------------------------
94 | |
95 | | Redis is an open source, fast, and advanced key-value store that also
96 | | provides a richer body of commands than a typical key-value system
97 | | such as APC or Memcached. Laravel makes it easy to dig right in.
98 | |
99 | */
100 |
101 | 'redis' => [
102 |
103 | 'client' => env('REDIS_CLIENT', 'predis'),
104 |
105 | 'options' => [
106 | 'cluster' => env('REDIS_CLUSTER', 'predis'),
107 | 'prefix' => env('REDIS_PREFIX', 'laravel_'),
108 | ],
109 |
110 | 'default' => [
111 | 'host' => env('REDIS_HOST', '127.0.0.1'),
112 | 'username' => env('REDIS_USERNAME'),
113 | 'password' => env('REDIS_PASSWORD', null),
114 | 'port' => env('REDIS_PORT', 6379),
115 | 'database' => env('REDIS_DB', 0),
116 | ],
117 |
118 | 'cache' => [
119 | 'host' => env('REDIS_HOST', '127.0.0.1'),
120 | 'username' => env('REDIS_USERNAME'),
121 | 'password' => env('REDIS_PASSWORD', null),
122 | 'port' => env('REDIS_PORT', 6379),
123 | 'database' => env('REDIS_CACHE_DB', 1),
124 | ],
125 |
126 | ],
127 |
128 | ];
129 |
--------------------------------------------------------------------------------
/config/filesystems.php:
--------------------------------------------------------------------------------
1 | env('FILESYSTEM_DRIVER', 'local'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Default Cloud Filesystem Disk
21 | |--------------------------------------------------------------------------
22 | |
23 | | Many applications store files both locally and in the cloud. For this
24 | | reason, you may specify a default "cloud" driver here. This driver
25 | | will be bound as the Cloud disk implementation in the container.
26 | |
27 | */
28 |
29 | 'cloud' => env('FILESYSTEM_CLOUD', 's3'),
30 |
31 | /*
32 | |--------------------------------------------------------------------------
33 | | Filesystem Disks
34 | |--------------------------------------------------------------------------
35 | |
36 | | Here you may configure as many filesystem "disks" as you wish, and you
37 | | may even configure multiple disks of the same driver. Defaults have
38 | | been setup for each driver as an example of the required options.
39 | |
40 | | Supported Drivers: "local", "ftp", "sftp", "s3", "rackspace"
41 | |
42 | */
43 |
44 | 'disks' => [
45 |
46 | 'local' => [
47 | 'driver' => 'local',
48 | 'root' => storage_path('app'),
49 | 'throw' => false,
50 | ],
51 |
52 | 'public' => [
53 | 'driver' => 'local',
54 | 'root' => storage_path('app/public'),
55 | 'url' => env('APP_URL').'/storage',
56 | 'visibility' => 'public',
57 | 'throw' => false,
58 | ],
59 |
60 | 's3' => [
61 | 'driver' => 's3',
62 | 'key' => env('AWS_ACCESS_KEY_ID'),
63 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
64 | 'region' => env('AWS_DEFAULT_REGION'),
65 | 'bucket' => env('AWS_BUCKET'),
66 | 'url' => env('AWS_URL'),
67 | 'throw' => false,
68 | ],
69 |
70 | ],
71 |
72 | ];
73 |
--------------------------------------------------------------------------------
/config/hashing.php:
--------------------------------------------------------------------------------
1 | 'bcrypt',
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Bcrypt Options
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here you may specify the configuration options that should be used when
26 | | passwords are hashed using the Bcrypt algorithm. This will allow you
27 | | to control the amount of time it takes to hash the given password.
28 | |
29 | */
30 |
31 | 'bcrypt' => [
32 | 'rounds' => env('BCRYPT_ROUNDS', 10),
33 | ],
34 |
35 | /*
36 | |--------------------------------------------------------------------------
37 | | Argon Options
38 | |--------------------------------------------------------------------------
39 | |
40 | | Here you may specify the configuration options that should be used when
41 | | passwords are hashed using the Argon algorithm. These will allow you
42 | | to control the amount of time it takes to hash the given password.
43 | |
44 | */
45 |
46 | 'argon' => [
47 | 'memory' => 1024,
48 | 'threads' => 2,
49 | 'time' => 2,
50 | ],
51 |
52 | ];
53 |
--------------------------------------------------------------------------------
/config/logging.php:
--------------------------------------------------------------------------------
1 | env('LOG_CHANNEL', 'stack'),
20 |
21 | /*
22 | |--------------------------------------------------------------------------
23 | | Deprecations Log Channel
24 | |--------------------------------------------------------------------------
25 | |
26 | | This option controls the log channel that should be used to log warnings
27 | | regarding deprecated PHP and library features. This allows you to get
28 | | your application ready for upcoming major versions of dependencies.
29 | |
30 | */
31 |
32 | 'deprecations' => [
33 | 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
34 | 'trace' => false,
35 | ],
36 |
37 | /*
38 | |--------------------------------------------------------------------------
39 | | Log Channels
40 | |--------------------------------------------------------------------------
41 | |
42 | | Here you may configure the log channels for your application. Out of
43 | | the box, Laravel uses the Monolog PHP logging library. This gives
44 | | you a variety of powerful log handlers / formatters to utilize.
45 | |
46 | | Available Drivers: "single", "daily", "slack", "syslog",
47 | | "errorlog", "monolog",
48 | | "custom", "stack"
49 | |
50 | */
51 |
52 | 'channels' => [
53 | 'stack' => [
54 | 'driver' => 'stack',
55 | 'channels' => ['daily'],
56 | 'ignore_exceptions' => false,
57 | ],
58 |
59 | 'single' => [
60 | 'driver' => 'single',
61 | 'path' => storage_path('logs/laravel.log'),
62 | 'level' => 'debug',
63 | ],
64 |
65 | 'daily' => [
66 | 'driver' => 'daily',
67 | 'path' => storage_path('logs/laravel.log'),
68 | 'level' => 'debug',
69 | 'days' => 14,
70 | ],
71 |
72 | 'slack' => [
73 | 'driver' => 'slack',
74 | 'url' => env('LOG_SLACK_WEBHOOK_URL'),
75 | 'username' => 'Laravel Log',
76 | 'emoji' => ':boom:',
77 | 'level' => 'critical',
78 | ],
79 |
80 | 'papertrail' => [
81 | 'driver' => 'monolog',
82 | 'level' => 'debug',
83 | 'handler' => SyslogUdpHandler::class,
84 | 'handler_with' => [
85 | 'host' => env('PAPERTRAIL_URL'),
86 | 'port' => env('PAPERTRAIL_PORT'),
87 | ],
88 | ],
89 |
90 | 'stderr' => [
91 | 'driver' => 'monolog',
92 | 'handler' => StreamHandler::class,
93 | 'formatter' => env('LOG_STDERR_FORMATTER'),
94 | 'with' => [
95 | 'stream' => 'php://stderr',
96 | ],
97 | ],
98 |
99 | 'syslog' => [
100 | 'driver' => 'syslog',
101 | 'level' => 'debug',
102 | ],
103 |
104 | 'errorlog' => [
105 | 'driver' => 'errorlog',
106 | 'level' => 'debug',
107 | ],
108 | ],
109 |
110 | ];
111 |
--------------------------------------------------------------------------------
/config/mail.php:
--------------------------------------------------------------------------------
1 | env('MAIL_MAILER', 'smtp'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Mailer Configurations
21 | |--------------------------------------------------------------------------
22 | |
23 | | Here you may configure all of the mailers used by your application plus
24 | | their respective settings. Several examples have been configured for
25 | | you and you are free to add your own as your application requires.
26 | |
27 | | Laravel supports a variety of mail "transport" drivers to be used while
28 | | sending an e-mail. You will specify which one you are using for your
29 | | mailers below. You are free to add additional mailers as required.
30 | |
31 | | Supported: "smtp", "sendmail", "mailgun", "ses",
32 | | "postmark", "log", "array", "failover"
33 | |
34 | */
35 |
36 | 'mailers' => [
37 | 'smtp' => [
38 | 'transport' => 'smtp',
39 | 'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
40 | 'port' => env('MAIL_PORT', 587),
41 | 'encryption' => env('MAIL_ENCRYPTION', 'tls'),
42 | 'username' => env('MAIL_USERNAME'),
43 | 'password' => env('MAIL_PASSWORD'),
44 | 'timeout' => null,
45 | 'local_domain' => env('MAIL_EHLO_DOMAIN'),
46 | ],
47 |
48 | 'ses' => [
49 | 'transport' => 'ses',
50 | ],
51 |
52 | 'mailgun' => [
53 | 'transport' => 'mailgun',
54 | ],
55 |
56 | 'postmark' => [
57 | 'transport' => 'postmark',
58 | ],
59 |
60 | 'sendmail' => [
61 | 'transport' => 'sendmail',
62 | 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
63 | ],
64 |
65 | 'log' => [
66 | 'transport' => 'log',
67 | 'channel' => env('MAIL_LOG_CHANNEL'),
68 | ],
69 |
70 | 'array' => [
71 | 'transport' => 'array',
72 | ],
73 |
74 | 'failover' => [
75 | 'transport' => 'failover',
76 | 'mailers' => [
77 | 'smtp',
78 | 'log',
79 | ],
80 | ],
81 | ],
82 |
83 | /*
84 | |--------------------------------------------------------------------------
85 | | Global "From" Address
86 | |--------------------------------------------------------------------------
87 | |
88 | | You may wish for all e-mails sent by your application to be sent from
89 | | the same address. Here, you may specify a name and address that is
90 | | used globally for all e-mails that are sent by your application.
91 | |
92 | */
93 |
94 | 'from' => [
95 | 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
96 | 'name' => env('MAIL_FROM_NAME', 'Example'),
97 | ],
98 |
99 | /*
100 | |--------------------------------------------------------------------------
101 | | Markdown Mail Settings
102 | |--------------------------------------------------------------------------
103 | |
104 | | If you are using Markdown based email rendering, you may configure your
105 | | theme and component paths here, allowing you to customize the design
106 | | of the emails. Or, you may simply stick with the Laravel defaults!
107 | |
108 | */
109 |
110 | 'markdown' => [
111 | 'theme' => 'default',
112 |
113 | 'paths' => [
114 | resource_path('views/vendor/mail'),
115 | ],
116 | ],
117 |
118 | ];
119 |
--------------------------------------------------------------------------------
/config/passport.php:
--------------------------------------------------------------------------------
1 | [
5 | 'password' => [
6 | 'client_id' => env('PASSPORT_PASSWORD_CLIENT_ID'),
7 | 'client_secret' => env('PASSPORT_PASSWORD_CLIENT_SECRET'),
8 | ],
9 | ],
10 | ];
11 |
--------------------------------------------------------------------------------
/config/queue.php:
--------------------------------------------------------------------------------
1 | env('QUEUE_CONNECTION', 'sync'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Queue Connections
21 | |--------------------------------------------------------------------------
22 | |
23 | | Here you may configure the connection information for each server that
24 | | is used by your application. A default configuration has been added
25 | | for each back-end shipped with Laravel. You are free to add more.
26 | |
27 | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
28 | |
29 | */
30 |
31 | 'connections' => [
32 |
33 | 'sync' => [
34 | 'driver' => 'sync',
35 | ],
36 |
37 | 'database' => [
38 | 'driver' => 'database',
39 | 'table' => 'jobs',
40 | 'queue' => 'default',
41 | 'retry_after' => 90,
42 | ],
43 |
44 | 'beanstalkd' => [
45 | 'driver' => 'beanstalkd',
46 | 'host' => 'localhost',
47 | 'queue' => 'default',
48 | 'retry_after' => 90,
49 | 'block_for' => 0,
50 | ],
51 |
52 | 'sqs' => [
53 | 'driver' => 'sqs',
54 | 'key' => env('AWS_ACCESS_KEY_ID'),
55 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
56 | 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
57 | 'queue' => env('SQS_QUEUE', 'your-queue-name'),
58 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
59 | ],
60 |
61 | 'redis' => [
62 | 'driver' => 'redis',
63 | 'connection' => 'default',
64 | 'queue' => env('REDIS_QUEUE', 'default'),
65 | 'retry_after' => 90,
66 | 'block_for' => null,
67 | ],
68 |
69 | ],
70 |
71 | /*
72 | |--------------------------------------------------------------------------
73 | | Failed Queue Jobs
74 | |--------------------------------------------------------------------------
75 | |
76 | | These options configure the behavior of failed queue job logging so you
77 | | can control which database and table are used to store the jobs that
78 | | have failed. You may change them to any database / table you wish.
79 | |
80 | */
81 |
82 | 'failed' => [
83 | 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
84 | 'database' => env('DB_CONNECTION', 'mysql'),
85 | 'table' => 'failed_jobs',
86 | ],
87 |
88 | ];
89 |
--------------------------------------------------------------------------------
/config/sanctum.php:
--------------------------------------------------------------------------------
1 | explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
19 | '%s%s',
20 | 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
21 | Sanctum::currentApplicationUrlWithPort()
22 | ))),
23 |
24 | /*
25 | |--------------------------------------------------------------------------
26 | | Sanctum Guards
27 | |--------------------------------------------------------------------------
28 | |
29 | | This array contains the authentication guards that will be checked when
30 | | Sanctum is trying to authenticate a request. If none of these guards
31 | | are able to authenticate the request, Sanctum will use the bearer
32 | | token that's present on an incoming request for authentication.
33 | |
34 | */
35 |
36 | 'guard' => ['web'],
37 |
38 | /*
39 | |--------------------------------------------------------------------------
40 | | Expiration Minutes
41 | |--------------------------------------------------------------------------
42 | |
43 | | This value controls the number of minutes until an issued token will be
44 | | considered expired. If this value is null, personal access tokens do
45 | | not expire. This won't tweak the lifetime of first-party sessions.
46 | |
47 | */
48 |
49 | 'expiration' => null,
50 |
51 | /*
52 | |--------------------------------------------------------------------------
53 | | Sanctum Middleware
54 | |--------------------------------------------------------------------------
55 | |
56 | | When authenticating your first-party SPA with Sanctum you may need to
57 | | customize some of the middleware Sanctum uses while processing the
58 | | request. You may change the middleware listed below as required.
59 | |
60 | */
61 |
62 | 'middleware' => [
63 | 'verify_csrf_token' => \Infrastructure\Http\Middleware\VerifyCsrfToken::class,
64 | 'encrypt_cookies' => \Infrastructure\Http\Middleware\EncryptCookies::class,
65 | ],
66 |
67 | ];
68 |
--------------------------------------------------------------------------------
/config/services.php:
--------------------------------------------------------------------------------
1 | [
18 | 'domain' => env('MAILGUN_DOMAIN'),
19 | 'secret' => env('MAILGUN_SECRET'),
20 | 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
21 | ],
22 |
23 | 'postmark' => [
24 | 'token' => env('POSTMARK_TOKEN'),
25 | ],
26 |
27 | 'ses' => [
28 | 'key' => env('AWS_ACCESS_KEY_ID'),
29 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
30 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
31 | ],
32 |
33 | 'sparkpost' => [
34 | 'secret' => env('SPARKPOST_SECRET'),
35 | ],
36 |
37 | 'stripe' => [
38 | 'model' => \Domain\User\User::class,
39 | 'key' => env('STRIPE_KEY'),
40 | 'secret' => env('STRIPE_SECRET'),
41 | 'webhook' => [
42 | 'secret' => env('STRIPE_WEBHOOK_SECRET'),
43 | 'tolerance' => env('STRIPE_WEBHOOK_TOLERANCE', 300),
44 | ],
45 | ],
46 |
47 | 'github' => [
48 | 'client_id' => env('GITHUB_CLIENT_ID'),
49 | 'client_secret' => env('GITHUB_CLIENT_SECRET'),
50 | ],
51 | ];
52 |
--------------------------------------------------------------------------------
/config/session.php:
--------------------------------------------------------------------------------
1 | env('SESSION_DRIVER', 'file'),
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 immediately expire on the browser closing, set that option.
31 | |
32 | */
33 |
34 | 'lifetime' => env('SESSION_LIFETIME', 120),
35 |
36 | 'expire_on_close' => false,
37 |
38 | /*
39 | |--------------------------------------------------------------------------
40 | | Session Encryption
41 | |--------------------------------------------------------------------------
42 | |
43 | | This option allows you to easily specify that all of your session data
44 | | should be encrypted before it is stored. All encryption will be run
45 | | automatically by Laravel and you can use the Session like normal.
46 | |
47 | */
48 |
49 | 'encrypt' => false,
50 |
51 | /*
52 | |--------------------------------------------------------------------------
53 | | Session File Location
54 | |--------------------------------------------------------------------------
55 | |
56 | | When using the native session driver, we need a location where session
57 | | files may be stored. A default has been set for you but a different
58 | | location may be specified. This is only needed for file sessions.
59 | |
60 | */
61 |
62 | 'files' => storage_path('framework/sessions'),
63 |
64 | /*
65 | |--------------------------------------------------------------------------
66 | | Session Database Connection
67 | |--------------------------------------------------------------------------
68 | |
69 | | When using the "database" or "redis" session drivers, you may specify a
70 | | connection that should be used to manage these sessions. This should
71 | | correspond to a connection in your database configuration options.
72 | |
73 | */
74 |
75 | 'connection' => env('SESSION_CONNECTION', null),
76 |
77 | /*
78 | |--------------------------------------------------------------------------
79 | | Session Database Table
80 | |--------------------------------------------------------------------------
81 | |
82 | | When using the "database" session driver, you may specify the table we
83 | | should use to manage the sessions. Of course, a sensible default is
84 | | provided for you; however, you are free to change this as needed.
85 | |
86 | */
87 |
88 | 'table' => 'sessions',
89 |
90 | /*
91 | |--------------------------------------------------------------------------
92 | | Session Cache Store
93 | |--------------------------------------------------------------------------
94 | |
95 | | When using the "apc", "memcached", or "dynamodb" session drivers you may
96 | | list a cache store that should be used for these sessions. This value
97 | | must match with one of the application's configured cache "stores".
98 | |
99 | */
100 |
101 | 'store' => env('SESSION_STORE', null),
102 |
103 | /*
104 | |--------------------------------------------------------------------------
105 | | Session Sweeping Lottery
106 | |--------------------------------------------------------------------------
107 | |
108 | | Some session drivers must manually sweep their storage location to get
109 | | rid of old sessions from storage. Here are the chances that it will
110 | | happen on a given request. By default, the odds are 2 out of 100.
111 | |
112 | */
113 |
114 | 'lottery' => [2, 100],
115 |
116 | /*
117 | |--------------------------------------------------------------------------
118 | | Session Cookie Name
119 | |--------------------------------------------------------------------------
120 | |
121 | | Here you may change the name of the cookie used to identify a session
122 | | instance by ID. The name specified here will get used every time a
123 | | new session cookie is created by the framework for every driver.
124 | |
125 | */
126 |
127 | 'cookie' => env(
128 | 'SESSION_COOKIE',
129 | Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
130 | ),
131 |
132 | /*
133 | |--------------------------------------------------------------------------
134 | | Session Cookie Path
135 | |--------------------------------------------------------------------------
136 | |
137 | | The session cookie path determines the path for which the cookie will
138 | | be regarded as available. Typically, this will be the root path of
139 | | your application but you are free to change this when necessary.
140 | |
141 | */
142 |
143 | 'path' => '/',
144 |
145 | /*
146 | |--------------------------------------------------------------------------
147 | | Session Cookie Domain
148 | |--------------------------------------------------------------------------
149 | |
150 | | Here you may change the domain of the cookie used to identify a session
151 | | in your application. This will determine which domains the cookie is
152 | | available to in your application. A sensible default has been set.
153 | |
154 | */
155 |
156 | 'domain' => env('SESSION_DOMAIN', null),
157 |
158 | /*
159 | |--------------------------------------------------------------------------
160 | | HTTPS Only Cookies
161 | |--------------------------------------------------------------------------
162 | |
163 | | By setting this option to true, session cookies will only be sent back
164 | | to the server if the browser has a HTTPS connection. This will keep
165 | | the cookie from being sent to you if it can not be done securely.
166 | |
167 | */
168 |
169 | 'secure' => env('SESSION_SECURE_COOKIE', false),
170 |
171 | /*
172 | |--------------------------------------------------------------------------
173 | | HTTP Access Only
174 | |--------------------------------------------------------------------------
175 | |
176 | | Setting this value to true will prevent JavaScript from accessing the
177 | | value of the cookie and the cookie will only be accessible through
178 | | the HTTP protocol. You are free to modify this option if needed.
179 | |
180 | */
181 |
182 | 'http_only' => true,
183 |
184 | /*
185 | |--------------------------------------------------------------------------
186 | | Same-Site Cookies
187 | |--------------------------------------------------------------------------
188 | |
189 | | This option determines how your cookies behave when cross-site requests
190 | | take place, and can be used to mitigate CSRF attacks. By default, we
191 | | do not enable this as other CSRF protection services are in place.
192 | |
193 | | Supported: "lax", "strict"
194 | |
195 | */
196 |
197 | 'same_site' => null,
198 |
199 | ];
200 |
--------------------------------------------------------------------------------
/config/view.php:
--------------------------------------------------------------------------------
1 | [
17 | resource_path('views'),
18 | ],
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Compiled View Path
23 | |--------------------------------------------------------------------------
24 | |
25 | | This option determines where all the compiled Blade templates will be
26 | | stored for your application. Typically, this is within the storage
27 | | directory. However, as usual, you are free to change this value.
28 | |
29 | */
30 |
31 | 'compiled' => env(
32 | 'VIEW_COMPILED_PATH',
33 | realpath(storage_path('framework/views'))
34 | ),
35 |
36 | ];
37 |
--------------------------------------------------------------------------------
/database/.gitignore:
--------------------------------------------------------------------------------
1 | *.sqlite
2 | *.sqlite-journal
3 |
--------------------------------------------------------------------------------
/database/migrations/2014_10_12_000000_create_users_table.php:
--------------------------------------------------------------------------------
1 | unsignedBigInteger('id')->primary()->comment('用户ID');
13 | $table->string('username')->unique();
14 | $table->string('nickname')->nullable();
15 | $table->string('avatar')->nullable();
16 | $table->string('email')->unique()->nullable();
17 | $table->string('gender')->default('unknown')->comment('性别:unknown/female/male');
18 | $table->string('phone')->unique()->nullable();
19 | $table->date('birthday')->nullable();
20 | $table->string('password')->nullable();
21 | $table->json('settings')->nullable()->comment('用户设置');
22 | $table->boolean('is_admin')->default(false)->comment('是否为公司管理员');
23 | $table->string('system_remark')->nullable();
24 | $table->string('banned_reason')->nullable();
25 | $table->timestamp('banned_at')->nullable();
26 | $table->timestamp('first_active_at')->nullable()->comment('首次活跃时间');
27 | $table->timestamp('last_active_at')->nullable()->comment('最后活跃时间');
28 | $table->timestamp('email_verified_at')->nullable();
29 | $table->timestamps();
30 | $table->rememberToken();
31 | $table->softDeletes();
32 | });
33 | }
34 |
35 | public function down()
36 | {
37 | Schema::dropIfExists('users');
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/database/migrations/2014_10_12_100000_create_password_resets_table.php:
--------------------------------------------------------------------------------
1 | string('email')->index();
13 | $table->string('token');
14 | $table->timestamp('created_at')->nullable();
15 | });
16 | }
17 |
18 | public function down()
19 | {
20 | Schema::dropIfExists('password_resets');
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/database/migrations/2019_08_19_000000_create_failed_jobs_table.php:
--------------------------------------------------------------------------------
1 | id();
13 | $table->string('uuid')->unique();
14 | $table->text('connection');
15 | $table->text('queue');
16 | $table->longText('payload');
17 | $table->longText('exception');
18 | $table->timestamp('failed_at')->useCurrent();
19 | });
20 | }
21 |
22 | public function down()
23 | {
24 | Schema::dropIfExists('failed_jobs');
25 | }
26 | };
27 |
--------------------------------------------------------------------------------
/database/migrations/2019_09_30_081447_create_tags_table.php:
--------------------------------------------------------------------------------
1 | unsignedBigInteger('id')->primary()->comment('用户ID');
13 | $table->unsignedInteger('creator_id')->comment('创建者');
14 | $table->string('name');
15 | $table->string('color', 10)->nullable();
16 | $table->string('icon')->nullable();
17 | $table->timestamps();
18 | $table->softDeletes();
19 | });
20 |
21 | Schema::create('taggables', function (Blueprint $table) {
22 | $table->id('aid');
23 | $table->unsignedInteger('tag_id')->index();
24 | $table->morphs('taggable');
25 | });
26 | }
27 |
28 | public function down()
29 | {
30 | Schema::dropIfExists('tags');
31 | Schema::dropIfExists('taggables');
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php:
--------------------------------------------------------------------------------
1 | bigIncrements('id');
18 | $table->morphs('tokenable');
19 | $table->string('name');
20 | $table->string('token', 64)->unique();
21 | $table->text('abilities')->nullable();
22 | $table->timestamp('last_used_at')->nullable();
23 | $table->timestamp('expires_at')->nullable();
24 | $table->timestamps();
25 | });
26 | }
27 |
28 | /**
29 | * Reverse the migrations.
30 | *
31 | * @return void
32 | */
33 | public function down()
34 | {
35 | Schema::dropIfExists('personal_access_tokens');
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/database/migrations/2020_05_15_110212_create_user_agents_table.php:
--------------------------------------------------------------------------------
1 | unsignedBigInteger('id')->primary()->comment('用户ID');
13 | $table->unsignedBigInteger('user_id')->index();
14 | $table->string('agent');
15 | $table->timestamp('last_used_at');
16 | $table->softDeletes();
17 | $table->timestamps();
18 | });
19 | }
20 |
21 | public function down()
22 | {
23 | Schema::dropIfExists('user_agents');
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/database/seeders/DatabaseSeeder.php:
--------------------------------------------------------------------------------
1 | call(UsersTableSeeder::class);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/database/seeders/UsersTableSeeder.php:
--------------------------------------------------------------------------------
1 | 'admin',
16 | 'nickname' => '超级管理员',
17 | 'is_admin' => 1,
18 | 'password' => 'changeThis!!',
19 | ]);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/domain/Tag/Tag.php:
--------------------------------------------------------------------------------
1 | morphTo();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/domain/User/Filters/UserFilter.php:
--------------------------------------------------------------------------------
1 | [input_key1, input_key2]].
12 | *
13 | * @var array
14 | */
15 | public $relations = [];
16 | }
17 |
--------------------------------------------------------------------------------
/domain/User/Jobs/RefreshUserFirstActivedAt.php:
--------------------------------------------------------------------------------
1 | user = $user;
24 | }
25 |
26 | public function handle()
27 | {
28 | $this->user->refreshFirstActiveAt();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/domain/User/Jobs/RefreshUserLastActiveAt.php:
--------------------------------------------------------------------------------
1 | user->last_active_at) || $this->user->last_active_at->lt(\now()->subMinutes(5))) {
26 | $this->user->refreshLastActiveAt();
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/domain/User/Notifications/ResetPassword.php:
--------------------------------------------------------------------------------
1 | line('You are receiving this email because we received a password reset request for your account.')
14 | ->action('Reset Password', url(config('app.url').'/password/reset/'.$this->token).'?email='.urlencode($notifiable->email))
15 | ->line('If you did not request a password reset, no further action is required.');
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/domain/User/Notifications/VerifyEmail.php:
--------------------------------------------------------------------------------
1 | addMinutes(60),
16 | ['user' => $notifiable->id]
17 | );
18 |
19 | return str_replace('/api', '', $url);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/domain/User/Policies/UserPolicy.php:
--------------------------------------------------------------------------------
1 | is($targetUser);
25 | }
26 |
27 | public function delete(User $user, User $targetUser): bool
28 | {
29 | return false;
30 | }
31 |
32 | public function restore(User $user, User $targetUser): bool
33 | {
34 | return false;
35 | }
36 |
37 | public function forceDelete(User $user, User $targetUser): bool
38 | {
39 | return false;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/domain/User/Rules/Phone.php:
--------------------------------------------------------------------------------
1 | 'array',
94 | 'is_admin' => 'bool',
95 | 'birthday' => 'date',
96 | 'email_verified_at' => 'datetime',
97 | 'banned_at' => 'datetime',
98 | 'first_active_at' => 'datetime',
99 | 'last_active_at' => 'datetime',
100 | ];
101 |
102 | protected static function booted()
103 | {
104 | static::saving(
105 | function (User $user) {
106 | $user->username = $user->username ?? $user->email;
107 | $user->nickname ??= $user->username;
108 | $user->first_active_at = ! is_null($user->getOriginal('first_active_at')) ? $user->first_active_at : null;
109 |
110 | if (Hash::needsRehash($user->password)) {
111 | $user->password = bcrypt($user->password);
112 | }
113 | }
114 | );
115 | }
116 |
117 | public function sendPasswordResetNotification($token): void
118 | {
119 | $this->notify(new ResetPassword($token));
120 | }
121 |
122 | public function sendEmailVerificationNotification(): void
123 | {
124 | $this->notify(new VerifyEmail());
125 | }
126 |
127 | public function getAvatarAttribute(): string
128 | {
129 | return $this->attributes['avatar'] ?? self::DEFAULT_AVATAR;
130 | }
131 |
132 | public function isAdmin(): bool
133 | {
134 | return (bool) $this->is_admin;
135 | }
136 |
137 | #[ArrayShape(['type' => 'string', 'token' => 'string'])]
138 | public function createDeviceToken(
139 | ?string $device = 'web'
140 | ): array {
141 | return [
142 | 'type' => 'bearer',
143 | 'token' => $this->createToken($device ?: 'web')->plainTextToken,
144 | ];
145 | }
146 |
147 | public function refreshLastActiveAt(): static
148 | {
149 | $this->updateQuietly(
150 | [
151 | 'last_active_at' => now(),
152 | ]
153 | );
154 |
155 | return $this;
156 | }
157 |
158 | public function refreshFirstActiveAt(): static
159 | {
160 | $this->first_active_at || $this->updateQuietly(
161 | [
162 | 'first_active_at' => now(),
163 | ]
164 | );
165 |
166 | return $this;
167 | }
168 |
169 | public function getModelFilterClass(): string
170 | {
171 | return UserFilter::class;
172 | }
173 |
174 | protected static function newFactory(): UserFactory
175 | {
176 | return UserFactory::new();
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/domain/User/UserAgent.php:
--------------------------------------------------------------------------------
1 | 'string',
25 | ];
26 |
27 | protected $dates = [
28 | 'last_used_at',
29 | ];
30 |
31 | protected static function booted()
32 | {
33 | self::saving(
34 | function (UserAgent $userAgent) {
35 | $userAgent->last_used_at = now();
36 | }
37 | );
38 | }
39 |
40 | public function refreshLastUsedAt(): static
41 | {
42 | $this->updateQuietly(['last_used_at' => now()]);
43 |
44 | return $this;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/domain/User/UserFactory.php:
--------------------------------------------------------------------------------
1 | $this->faker->name,
16 | 'username' => $this->faker->userName,
17 | 'email' => $this->faker->unique()->safeEmail,
18 | 'email_verified_at' => now(),
19 | 'password' => 'password',
20 | 'remember_token' => Str::random(10),
21 | ];
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/infrastructure/Actions/Action.php:
--------------------------------------------------------------------------------
1 | runningUnitTests() && static::$fakeWhenTesting) {
32 | self::fake();
33 | }
34 |
35 | return self::make(static::class)->handle(...$arguments);
36 | }
37 |
38 | public static function runIf($boolean, ...$arguments)
39 | {
40 | return $boolean ? static::run(...$arguments) : new Fluent();
41 | }
42 |
43 | public static function runUnless($boolean, ...$arguments)
44 | {
45 | return static::runIf(! $boolean, ...$arguments);
46 | }
47 |
48 | // abstract public function handle(...$arguments): mixed;
49 |
50 | public static function make(string $action)
51 | {
52 | return self::$instances[$action] ??= app($action);
53 | }
54 |
55 | public static function shouldReturn(mixed $value): HigherOrderMessage|Expectation|ExpectationInterface
56 | {
57 | return self::fake($value);
58 | }
59 |
60 | public static function shouldRunWith(...$arguments): HigherOrderMessage|Expectation|ExpectationInterface
61 | {
62 | return self::mock(static::class)->shouldReceive('handle')->with(...$arguments)->andReturn(self::$fakeResult);
63 | }
64 |
65 | public static function shouldRunOnce(): HigherOrderMessage|Expectation|ExpectationInterface
66 | {
67 | return self::shouldRunTimes(1);
68 | }
69 |
70 | public static function shouldNeverRun(): HigherOrderMessage|Expectation|ExpectationInterface
71 | {
72 | return self::shouldRunTimes(0);
73 | }
74 |
75 | public static function shouldRunTimes(int $times = 1): HigherOrderMessage|Expectation|ExpectationInterface
76 | {
77 | return self::mock(static::class)->shouldReceive('handle')->times($times)->andReturn(self::$fakeResult);
78 | }
79 |
80 | public static function fake(mixed $result = null): Expectation|ExpectationInterface|HigherOrderMessage
81 | {
82 | return self::mock(static::class)->shouldReceive('handle')->andReturn($result ?? static::$fakeResult);
83 | }
84 |
85 | public static function mock(string $action)
86 | {
87 | if (empty(self::$instances[$action]) || ! (self::$instances[$action] instanceof MockInterface)) {
88 | self::$instances[$action] = new FakeAction(static::class);
89 | }
90 |
91 | return self::$instances[$action];
92 | }
93 |
94 | /**
95 | * @throws \Exception
96 | */
97 | public static function __callStatic(string $name, array $arguments)
98 | {
99 | if (in_array($name, ['spy', 'fake', 'partialMock', 'shouldReceive', 'assertCalled', 'assertCalledWith', 'assertNotCalled', 'assertCalledTimes', 'assertCalledWithClosure'])) {
100 | return self::mock(static::class)->$name(...$arguments);
101 | }
102 |
103 | throw new \Exception("Method [$name] does not exist.");
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/infrastructure/Actions/FakeAction.php:
--------------------------------------------------------------------------------
1 | action] = $arguments;
33 | self::$actionTimes[$this->action] = (self::$actionTimes[$this->action] ?? 0) + 1;
34 |
35 | if (isset(self::$instances[$this->action])) {
36 | return self::$instances[$this->action]->handle(...$arguments);
37 | }
38 |
39 | return new Fluent();
40 | }
41 |
42 | public function spy(): MockInterface
43 | {
44 | return self::$instances[$this->action] = Mockery::spy($this->action);
45 | }
46 |
47 | public function fake(): Mockery\LegacyMockInterface|Closure|MockInterface
48 | {
49 | self::$instances[$this->action] = Mockery::mock($this->action, function ($mock) {
50 | $mock->shouldAllowMockingProtectedMethods();
51 | });
52 |
53 | self::$instances[$this->action]->shouldReceive('handle')->andReturnNull();
54 |
55 | return self::$instances[$this->action];
56 | }
57 |
58 | public function partialMock(): Mock
59 | {
60 | self::$instances[$this->action] = Mockery::mock($this->action, function ($mock) {
61 | $mock->shouldAllowMockingProtectedMethods();
62 | });
63 |
64 | self::$instances[$this->action]->makePartial();
65 |
66 | return self::$instances[$this->action];
67 | }
68 |
69 | public function shouldReceive(string $method): ExpectationInterface|Expectation|HigherOrderMessage
70 | {
71 | if (empty(self::$instances[$this->action]) || ! (self::$instances[$this->action] instanceof MockInterface)) {
72 | self::$instances[$this->action] = Mockery::mock('stdClass', function ($mock) {
73 | $mock->shouldAllowMockingProtectedMethods();
74 | });
75 | }
76 |
77 | return self::$instances[$this->action]->shouldReceive($method);
78 | }
79 |
80 | public function assertNotCalled(): void
81 | {
82 | PHPUnit::assertFalse(isset(self::$actions[$this->action]), "ActionType [{$this->action}] was called.");
83 | }
84 |
85 | public function assertCalled(): void
86 | {
87 | $this->assertCalledTimes(1);
88 | }
89 |
90 | public function assertCalledWith(array $arguments = []): void
91 | {
92 | PHPUnit::assertTrue(
93 | array_key_exists($this->action, self::$actions) && self::$actions[$this->action] === $arguments,
94 | "The expected [{$this->action}] action was not called."
95 | );
96 | }
97 |
98 | public function assertCalledTimes(int $times = 1): void
99 | {
100 | PHPUnit::assertTrue(
101 | array_key_exists($this->action, self::$actionTimes) && self::$actionTimes[$this->action] === $times,
102 | "The expected [{$this->action}] action was not called {$times} times."
103 | );
104 | }
105 |
106 | public function assertCalledWithClosure(Closure $callback): void
107 | {
108 | PHPUnit::assertTrue(
109 | array_key_exists($this->action, self::$actions) && $callback(...self::$actions[$this->action]),
110 | "The expected [{$this->action}] action was not called."
111 | );
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/infrastructure/Exceptions/Handler.php:
--------------------------------------------------------------------------------
1 | , \Psr\Log\LogLevel::*>
12 | */
13 | protected $levels = [
14 | //
15 | ];
16 |
17 | protected $dontReport = [
18 | ];
19 |
20 | protected $dontFlash = [
21 | 'password',
22 | 'password_confirmation',
23 | ];
24 |
25 | /**
26 | * Report or log an exception.
27 | *
28 | * This is a great spot to send exceptions to Sentry, Bugsnag, etc.
29 | *
30 | * @return void
31 | */
32 | public function register()
33 | {
34 | //
35 | }
36 |
37 | protected function invalidJson($request, ValidationException $exception): \Illuminate\Http\JsonResponse
38 | {
39 | $errors = $exception->errors();
40 | $firstError = \reset($errors);
41 |
42 | return response()->json([
43 | 'message' => $firstError[0] ?? '参数错误',
44 | 'errors' => $errors,
45 | ], $exception->status);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/infrastructure/Generator/AppHelper.php:
--------------------------------------------------------------------------------
1 | transform(function ($append) {
26 | return explode(DIRECTORY_SEPARATOR, $append);
27 | })->flatten()->toArray();
28 |
29 | return base_path(implode(DIRECTORY_SEPARATOR, array_filter(['app', $domain, ...$appends])));
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/infrastructure/Generator/Commands/App/CreateAppCommand.php:
--------------------------------------------------------------------------------
1 | argument('name'));
24 |
25 | foreach (self::SCAFFOLD_FOLDERS as $folder) {
26 | $path = AppHelper::getPath($name, $folder);
27 |
28 | if (! $this->files->isDirectory($path)) {
29 | $this->files->makeDirectory($path, 0777, true, true);
30 | }
31 | }
32 |
33 | $namespace = AppHelper::getNamespace($name);
34 |
35 | // provider
36 | $providerName = Str::finish($name, 'ServiceProvider');
37 | $this->files->put(AppHelper::getPath($name, $providerName.'.php'), $this->sortImports($this->buildClass($providerName)));
38 |
39 | $this->components->info("Application [{$namespace}] created!");
40 | }
41 |
42 | protected function getStub(): string
43 | {
44 | return AppHelper::getStub('service-provider');
45 | }
46 |
47 | public function getNamespace($name): string
48 | {
49 | return AppHelper::getNamespace($this->argument('name'));
50 | }
51 |
52 | public function rootNamespace(): string
53 | {
54 | return AppHelper::getNamespace($this->argument('name'));
55 | }
56 |
57 | public function getPath($name): string
58 | {
59 | $name = Str::replaceFirst($this->rootNamespace(), '', $name);
60 |
61 | return AppHelper::getPath(str_replace('\\', '/', $name), class_basename($name).'.php');
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/infrastructure/Generator/Commands/App/CreateControllerCommand.php:
--------------------------------------------------------------------------------
1 | hasApp()) {
15 | return AppHelper::getStub('endpoint');
16 | }
17 |
18 | return parent::getStub();
19 | }
20 |
21 | protected function buildFormRequestReplacements(array $replace, $modelClass)
22 | {
23 | [$namespace, $storeRequestClass, $updateRequestClass] = [
24 | 'Illuminate\\Http', 'Request', 'Request',
25 | ];
26 |
27 | $domainNamespace = "Domains\\{$this->option('domain')}";
28 |
29 | if ($this->option('requests')) {
30 | $namespace = "{$domainNamespace}\\Requests";
31 |
32 | [$storeRequestClass, $updateRequestClass] = $this->generateFormRequests(
33 | $modelClass,
34 | $storeRequestClass,
35 | $updateRequestClass
36 | );
37 | }
38 |
39 | $namespacedRequests = $namespace.'\\'.$storeRequestClass.';';
40 | if ($storeRequestClass !== $updateRequestClass) {
41 | $namespacedRequests .= PHP_EOL.'use '.$namespace.'\\'.$updateRequestClass.';';
42 | }
43 |
44 | return array_merge($replace, [
45 | '{{ storeRequest }}' => $storeRequestClass,
46 | '{{storeRequest}}' => $storeRequestClass,
47 | '{{ updateRequest }}' => $updateRequestClass,
48 | '{{updateRequest}}' => $updateRequestClass,
49 | '{{ namespacedStoreRequest }}' => $namespace.'\\'.$storeRequestClass,
50 | '{{namespacedStoreRequest}}' => $namespace.'\\'.$storeRequestClass,
51 | '{{ namespacedUpdateRequest }}' => $namespace.'\\'.$updateRequestClass,
52 | '{{namespacedUpdateRequest}}' => $namespace.'\\'.$updateRequestClass,
53 | '{{ namespacedRequests }}' => $namespacedRequests,
54 | '{{namespacedRequests}}' => $namespacedRequests,
55 | ]);
56 | }
57 |
58 | /**
59 | * Generate the form requests for the given model and classes.
60 | *
61 | * @param string $modelClass
62 | * @param string $storeRequestClass
63 | * @param string $updateRequestClass
64 | * @return array
65 | */
66 | protected function generateFormRequests($modelClass, $storeRequestClass, $updateRequestClass)
67 | {
68 | $storeRequestClass = 'Store'.class_basename($modelClass).'Request';
69 |
70 | $this->call('make:request', [
71 | 'name' => $storeRequestClass,
72 | '--domain' => $this->option('domain'),
73 | ]);
74 |
75 | $updateRequestClass = 'Update'.class_basename($modelClass).'Request';
76 |
77 | $this->call('make:request', [
78 | 'name' => $updateRequestClass,
79 | '--domain' => $this->option('domain'),
80 | ]);
81 |
82 | return [$storeRequestClass, $updateRequestClass];
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/infrastructure/Generator/Commands/App/CreateEndpointCommand.php:
--------------------------------------------------------------------------------
1 | hasDomain()) {
22 | $this->error('Domain option is required.');
23 |
24 | return false;
25 | }
26 |
27 | return parent::handle();
28 | }
29 |
30 | protected function getStub(): string
31 | {
32 | return DomainHelper::getStub('action');
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/infrastructure/Generator/Commands/Domain/CreateCastCommand.php:
--------------------------------------------------------------------------------
1 | argument('name'));
26 |
27 | foreach (self::SCAFFOLD_FOLDERS as $folder) {
28 | $this->makeDirectory(DomainHelper::getPath($name, $folder));
29 | }
30 |
31 | $namespace = DomainHelper::getNamespace($name);
32 |
33 | $this->components->info("Domain [{$namespace}] created!");
34 | }
35 |
36 | public function makeDirectory(string $path, int $mode = 0755, bool $recursive = true, bool $force = true): void
37 | {
38 | File::makeDirectory($path, $mode, $recursive, $force);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/infrastructure/Generator/Commands/Domain/CreateEventCommand.php:
--------------------------------------------------------------------------------
1 | hasDomain()) {
16 | return parent::buildClass($name);
17 | }
18 |
19 | $factory = class_basename(Str::ucfirst(str_replace('Factory', '', $name)));
20 |
21 | $namespaceModel = $this->option('model')
22 | ? $this->qualifyModel($this->option('model'))
23 | : $this->qualifyModel($this->guessModelName($name));
24 |
25 | $model = class_basename($namespaceModel);
26 |
27 | $namespace = $this->getNamespace($this->qualifyClass($this->getNameInput()));
28 |
29 | $replace = [
30 | '{{ factoryNamespace }}' => $namespace,
31 | 'NamespacedDummyModel' => $namespaceModel,
32 | '{{ namespacedModel }}' => $namespaceModel,
33 | '{{namespacedModel}}' => $namespaceModel,
34 | 'DummyModel' => $model,
35 | '{{ model }}' => $model,
36 | '{{model}}' => $model,
37 | '{{ factory }}' => $factory,
38 | '{{factory}}' => $factory,
39 | ];
40 |
41 | $stub = $this->files->get($this->getStub());
42 |
43 | $content = $this->replaceNamespace($stub, $name)->replaceClass($stub, $name);
44 |
45 | return str_replace(
46 | array_keys($replace), array_values($replace), $content
47 | );
48 | }
49 |
50 | /**
51 | * Guess the model name from the Factory name or return a default model name.
52 | *
53 | * @param string $name
54 | * @return string
55 | */
56 | protected function guessModelName($name)
57 | {
58 | if (! $this->hasDomain()) {
59 | return parent::buildClass($name);
60 | }
61 |
62 | if (str_ends_with($name, 'Factory')) {
63 | $name = substr($name, 0, -7);
64 | }
65 |
66 | $modelName = $this->rootNamespace().'\\'.class_basename($name);
67 |
68 | if (class_exists($modelName)) {
69 | return $modelName;
70 | }
71 |
72 | if (is_dir($this->getPath('Models/'))) {
73 | return $this->rootNamespace().'Models\\'.$name;
74 | }
75 |
76 | return $this->getNamespace($name);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/infrastructure/Generator/Commands/Domain/CreateFilterCommand.php:
--------------------------------------------------------------------------------
1 | makeClassName()->compileStub();
23 | $this->components->info(sprintf('%s [%s] created successfully.', class_basename($this->getClassName()), $this->getPath()));
24 | }
25 |
26 | public function getAppendNamespace(): string
27 | {
28 | return 'Filters';
29 | }
30 |
31 | public function getPath(): string
32 | {
33 | if ($this->hasDomain()) {
34 | return DomainHelper::getPath($this->option('domain'), $this->getFileName());
35 | }
36 |
37 | return $this->laravel->basePath($this->getFileName());
38 | }
39 |
40 | public function getAppNamespace(): string
41 | {
42 | if ($this->hasDomain()) {
43 | return DomainHelper::getNamespace($this->option('domain'));
44 | }
45 |
46 | return $this->laravel->getNamespace();
47 | }
48 |
49 | public function makeClassName()
50 | {
51 | if ($this->hasDomain()) {
52 | $parts = array_map([Str::class, 'studly'], explode('\\', $this->argument('name')));
53 | $className = array_pop($parts);
54 | $ns = count($parts) > 0 ? implode('\\', $parts).'\\' : '';
55 |
56 | $fqClass = DomainHelper::getNamespace($this->option('domain'), 'Filters', $ns.$className);
57 |
58 | if (! str_ends_with($fqClass, 'Filter')) {
59 | $fqClass .= 'Filter';
60 | }
61 |
62 | if (class_exists($fqClass)) {
63 | $this->components->error("$fqClass Already Exists!");
64 | exit;
65 | }
66 |
67 | $this->setClassName($fqClass);
68 |
69 | return $this;
70 | }
71 |
72 | return parent::makeClassName();
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/infrastructure/Generator/Commands/Domain/CreateJobCommand.php:
--------------------------------------------------------------------------------
1 | option('event');
15 | $domain = $this->option('domain');
16 | $namespace = null !== $domain ? $this->getDomainNamespace() : $this->laravel->getNamespace();
17 |
18 | if (! Str::startsWith(
19 | $event,
20 | [
21 | $this->laravel->getNamespace(),
22 | 'Illuminate',
23 | '\\',
24 | ]
25 | )) {
26 | $event = $namespace.'Events\\'.str_replace('/', '\\', $event);
27 | }
28 |
29 | $stub = str_replace(
30 | ['DummyEvent', '{{ event }}'],
31 | class_basename($event),
32 | $this->parentBuildClass($name)
33 | );
34 |
35 | return str_replace(['DummyFullEvent', '{{ eventNamespace }}'], trim($event, '\\'), $stub);
36 | }
37 |
38 | protected function parentBuildClass($name)
39 | {
40 | $stub = $this->files->get($this->getStub());
41 |
42 | return $this->replaceNamespace($stub, $name)->replaceClass($stub, $name);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/infrastructure/Generator/Commands/Domain/CreateMailCommand.php:
--------------------------------------------------------------------------------
1 | argument('name')));
15 |
16 | $modelName = $this->qualifyClass($this->getNameInput());
17 |
18 | $this->call('make:controller', array_filter([
19 | 'name' => "{$controller}",
20 | '--model' => $this->option('resource') || $this->option('api') ? $modelName : null,
21 | '--api' => $this->option('domain') ? true : $this->option('api'),
22 | '--requests' => $this->option('requests') || $this->option('all'),
23 | '--domain' => $this->option('domain'),
24 | ]));
25 | }
26 |
27 | protected function createPolicy()
28 | {
29 | $policy = Str::studly(class_basename($this->argument('name')));
30 |
31 | $this->call('make:policy', [
32 | 'name' => "{$policy}Policy",
33 | '--model' => $this->qualifyClass($this->getNameInput()),
34 | '--domain' => $this->option('domain'),
35 | ]);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/infrastructure/Generator/Commands/Domain/CreateNotificationCommand.php:
--------------------------------------------------------------------------------
1 | option('domain')) {
14 | $scopePath = is_dir($this->getDomainPath('Models')) ? 'Models\\Scopes' : 'Scopes';
15 |
16 | return $this->getDomainNamespace($scopePath);
17 | }
18 |
19 | return parent::getDefaultNamespace($rootNamespace);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/infrastructure/Generator/Commands/WithAppOption.php:
--------------------------------------------------------------------------------
1 | Scopes
22 | return Str::plural(substr(class_basename(__CLASS__), strlen('create'), -strlen('Command')));
23 | }
24 |
25 | protected function getDefaultNamespace($rootNamespace): string
26 | {
27 | if ($this->hasApp()) {
28 | return AppHelper::getNamespace($this->option('app'), Str::studly($this->option('domain')), $this->getAppendNamespace());
29 | }
30 |
31 | return $rootNamespace;
32 | }
33 |
34 | protected function rootNamespace()
35 | {
36 | return $this->hasApp() ? AppHelper::getNamespace($this->option('app')) : parent::rootNamespace();
37 | }
38 |
39 | protected function getPath($name)
40 | {
41 | if ($this->hasApp()) {
42 | $name = Str::replaceFirst($this->rootNamespace(), '', $name);
43 |
44 | return AppHelper::getPath($this->option('app'), str_replace('\\', '/', $name).'.php');
45 | }
46 |
47 | return parent::getPath($name);
48 | }
49 |
50 | protected function hasApp(): bool
51 | {
52 | return null !== $this->option('app');
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/infrastructure/Generator/Commands/WithDomainOption.php:
--------------------------------------------------------------------------------
1 | Scopes
24 | return Str::plural(substr(class_basename(__CLASS__), strlen('create'), -strlen('Command')));
25 | }
26 |
27 | protected function getDefaultNamespace($rootNamespace): string
28 | {
29 | if ($this->hasDomain()) {
30 | return DomainHelper::getNamespace($this->option('domain'), $this->getAppendNamespace());
31 | }
32 |
33 | return $rootNamespace.'/'.$this->getAppendNamespace();
34 | }
35 |
36 | protected function rootNamespace()
37 | {
38 | return $this->hasDomain() ? DomainHelper::getNamespace($this->option('domain')) : parent::rootNamespace();
39 | }
40 |
41 | protected function getPath($name)
42 | {
43 | if ($this->hasDomain()) {
44 | $name = Str::replaceFirst($this->rootNamespace(), '', $name);
45 |
46 | return DomainHelper::getPath($this->option('domain'), str_replace('\\', '/', $name).'.php');
47 | }
48 |
49 | return parent::getPath($name);
50 | }
51 |
52 | protected function hasDomain(): bool
53 | {
54 | return null !== $this->option('domain');
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/infrastructure/Generator/DomainHelper.php:
--------------------------------------------------------------------------------
1 | transform(function ($append) {
26 | return explode(DIRECTORY_SEPARATOR, $append);
27 | })->flatten()->toArray();
28 |
29 | return base_path(implode(DIRECTORY_SEPARATOR, array_filter(['domain', $domain, ...$appends])));
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/infrastructure/Generator/GeneratorServiceProvider.php:
--------------------------------------------------------------------------------
1 | commands([
12 | // app
13 | Commands\App\CreateAppCommand::class,
14 | Commands\App\CreateEndpointCommand::class,
15 | Commands\App\CreateMiddlewareCommand::class,
16 | Commands\App\CreateRequestCommand::class,
17 | Commands\App\CreateResourceCommand::class,
18 | Commands\App\CreateControllerCommand::class,
19 |
20 | // domain
21 | Commands\Domain\CreateDomainCommand::class,
22 | Commands\Domain\CreateActionCommand::class,
23 | Commands\Domain\CreateFilterCommand::class,
24 |
25 | // override
26 | Commands\Domain\CreateCastCommand::class,
27 | Commands\Domain\CreateChannelCommand::class,
28 | Commands\Domain\CreateEventCommand::class,
29 | Commands\Domain\CreateExceptionCommand::class,
30 | Commands\Domain\CreateJobCommand::class,
31 | Commands\Domain\CreateListenerCommand::class,
32 | Commands\Domain\CreateMailCommand::class,
33 | Commands\Domain\CreateModelCommand::class,
34 | Commands\Domain\CreateNotificationCommand::class,
35 | Commands\Domain\CreateObserverCommand::class,
36 | Commands\Domain\CreatePolicyCommand::class,
37 | Commands\Domain\CreateRuleCommand::class,
38 | Commands\Domain\CreateScopeCommand::class,
39 | Commands\Domain\CreateFactoryCommand::class,
40 | ]);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/infrastructure/Generator/stubs/action.stub:
--------------------------------------------------------------------------------
1 | name('users.index');
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/infrastructure/Http/Kernel.php:
--------------------------------------------------------------------------------
1 | [
37 | \Infrastructure\Http\Middleware\EncryptCookies::class,
38 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
39 | \Illuminate\Session\Middleware\StartSession::class,
40 | \Illuminate\View\Middleware\ShareErrorsFromSession::class,
41 | \Infrastructure\Http\Middleware\VerifyCsrfToken::class,
42 | \Illuminate\Routing\Middleware\SubstituteBindings::class,
43 | \Infrastructure\Http\Middleware\StoreUserAgent::class,
44 | \Infrastructure\Http\Middleware\RefreshUserActiveAt::class,
45 | ],
46 |
47 | 'api' => [
48 | AcceptJsonResponse::class,
49 | EnsureFrontendRequestsAreStateful::class,
50 | 'throttle:api',
51 | \Illuminate\Routing\Middleware\SubstituteBindings::class,
52 | \Infrastructure\Http\Middleware\StoreUserAgent::class,
53 | ],
54 | ];
55 |
56 | /**
57 | * The application's route middleware.
58 | *
59 | * These middleware may be assigned to groups or used individually.
60 | *
61 | * @var array
62 | */
63 | protected $routeMiddleware = [
64 | 'auth' => \Infrastructure\Http\Middleware\Authenticate::class,
65 | 'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
66 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
67 | 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
68 | 'can' => \Illuminate\Auth\Middleware\Authorize::class,
69 | 'guest' => \Infrastructure\Http\Middleware\RedirectIfAuthenticated::class,
70 | 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
71 | 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
72 | 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
73 | ];
74 |
75 | /**
76 | * The priority-sorted list of middleware.
77 | *
78 | * This forces non-global middleware to always be in the given order.
79 | *
80 | * @var array
81 | */
82 | protected $middlewarePriority = [
83 | // \Illuminate\Session\Middleware\StartSession::class,
84 | // \Illuminate\View\Middleware\ShareErrorsFromSession::class,
85 | \Infrastructure\Http\Middleware\Authenticate::class,
86 | \Illuminate\Session\Middleware\AuthenticateSession::class,
87 | \Illuminate\Routing\Middleware\SubstituteBindings::class,
88 | \Illuminate\Auth\Middleware\Authorize::class,
89 | ];
90 | }
91 |
--------------------------------------------------------------------------------
/infrastructure/Http/Middleware/AcceptJsonResponse.php:
--------------------------------------------------------------------------------
1 | headers->set('Accept', 'application/json');
13 |
14 | return $next($request);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/infrastructure/Http/Middleware/Authenticate.php:
--------------------------------------------------------------------------------
1 | check()) {
16 | return response()->json(['error' => 'Already authenticated.'], 400);
17 | }
18 | }
19 |
20 | return $next($request);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/infrastructure/Http/Middleware/RefreshUserActiveAt.php:
--------------------------------------------------------------------------------
1 | 5 || RefreshUserLastActiveAt::dispatchAfterResponse($request->user());
16 |
17 | if (empty($request->user()->first_active_at)) {
18 | RefreshUserFirstActivedAt::dispatchAfterResponse($request->user());
19 | }
20 | }
21 |
22 | return $next($request);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/infrastructure/Http/Middleware/SetLocale.php:
--------------------------------------------------------------------------------
1 | parseLocale($request)) {
13 | app()->setLocale($locale);
14 | }
15 |
16 | return $next($request);
17 | }
18 |
19 | protected function parseLocale(Request $request): ?string
20 | {
21 | $locales = config('app.locales');
22 |
23 | $locale = $request->server('HTTP_ACCEPT_LANGUAGE');
24 | $locale = substr($locale, 0, strpos($locale, ',') ?: strlen($locale));
25 |
26 | if (\array_key_exists($locale, $locales)) {
27 | return $locale;
28 | }
29 |
30 | $locale = substr($locale, 0, 2);
31 |
32 | if (\array_key_exists($locale, $locales)) {
33 | return $locale;
34 | }
35 |
36 | return 'zh-CN';
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/infrastructure/Http/Middleware/StoreUserAgent.php:
--------------------------------------------------------------------------------
1 | header('User-Agent') ?: 'Unknown';
17 |
18 | $authId = Auth::id();
19 |
20 | dispatch(function () use ($agent, $authId) {
21 | UserAgent::firstOrCreate(['user_id' => $authId, 'agent' => Str::limit($agent, 500)])->refreshLastUsedAt();
22 | })->afterResponse();
23 | }
24 |
25 | return $next($request);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/infrastructure/Http/Middleware/TrimStrings.php:
--------------------------------------------------------------------------------
1 | |string|null
14 | */
15 | protected $proxies;
16 |
17 | /**
18 | * The headers that should be used to detect proxies.
19 | *
20 | * @var int
21 | */
22 | protected $headers =
23 | Request::HEADER_X_FORWARDED_FOR |
24 | Request::HEADER_X_FORWARDED_HOST |
25 | Request::HEADER_X_FORWARDED_PORT |
26 | Request::HEADER_X_FORWARDED_PROTO |
27 | Request::HEADER_X_FORWARDED_AWS_ELB;
28 | }
29 |
--------------------------------------------------------------------------------
/infrastructure/Http/Middleware/UseApiGuard.php:
--------------------------------------------------------------------------------
1 | registerRequestRelationMarco();
13 | }
14 |
15 | public function register()
16 | {
17 | }
18 |
19 | public function registerRequestRelationMarco()
20 | {
21 | Request::macro('relations', function (array $allows = []) {
22 | $request = \request();
23 |
24 | if ($request->has('with')) {
25 | $relations = \array_filter(\array_map('trim', \explode(';', $request->get('with'))));
26 |
27 | if (! empty($allows)) {
28 | return \array_intersect($allows, $relations);
29 | }
30 |
31 | return $relations;
32 | }
33 |
34 | return [];
35 | });
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/infrastructure/Providers/AuthServiceProvider.php:
--------------------------------------------------------------------------------
1 | registerPolicies();
16 |
17 | Sanctum::ignoreMigrations();
18 |
19 | Gate::guessPolicyNamesUsing(function ($modelClass) {
20 | return \sprintf('App\Policies\%sPolicy', \class_basename($modelClass));
21 | });
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/infrastructure/Providers/BroadcastServiceProvider.php:
--------------------------------------------------------------------------------
1 | [
13 | SendEmailVerificationNotification::class,
14 | ],
15 | ];
16 |
17 | public function boot()
18 | {
19 | //
20 | }
21 |
22 | public function shouldDiscoverEvents()
23 | {
24 | return false;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/infrastructure/Providers/RouteServiceProvider.php:
--------------------------------------------------------------------------------
1 | configureRateLimiting();
20 |
21 | $this->routes(function () {
22 | Route::prefix('api')
23 | ->middleware('api')
24 | ->group(base_path('routes/api.php'));
25 |
26 | Route::middleware('web')
27 | ->group(base_path('routes/web.php'));
28 | });
29 | }
30 |
31 | protected function configureRateLimiting()
32 | {
33 | RateLimiter::for('api', function (Request $request) {
34 | return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
35 | });
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/infrastructure/Traits/UseTableNameAsMorphClass.php:
--------------------------------------------------------------------------------
1 | getTable();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/infrastructure/Traits/UsingUuidAsPrimaryKey.php:
--------------------------------------------------------------------------------
1 | {$model->getKeyName()})) {
14 | $model->{$model->getKeyName()} = Str::orderedUuid()->toString();
15 | }
16 | });
17 | }
18 |
19 | public function getIncrementing(): bool
20 | {
21 | return false;
22 | }
23 |
24 | public function getKeyType(): string
25 | {
26 | return 'string';
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/lang/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "The :attribute must contain at least one letter.": "The :attribute must contain at least one letter.",
3 | "The :attribute must contain at least one number.": "The :attribute must contain at least one number.",
4 | "The :attribute must contain at least one symbol.": "The :attribute must contain at least one symbol.",
5 | "The :attribute must contain at least one uppercase and one lowercase letter.": "The :attribute must contain at least one uppercase and one lowercase letter.",
6 | "The given :attribute has appeared in a data leak. Please choose a different :attribute.": "The given :attribute has appeared in a data leak. Please choose a different :attribute."
7 | }
8 |
--------------------------------------------------------------------------------
/lang/en/auth.php:
--------------------------------------------------------------------------------
1 | 'These credentials do not match our records.',
17 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
18 |
19 | ];
20 |
--------------------------------------------------------------------------------
/lang/en/pagination.php:
--------------------------------------------------------------------------------
1 | '« Previous',
17 | 'next' => 'Next »',
18 |
19 | ];
20 |
--------------------------------------------------------------------------------
/lang/en/passwords.php:
--------------------------------------------------------------------------------
1 | 'Passwords must be at least eight characters and match the confirmation.',
17 | 'reset' => 'Your password has been reset!',
18 | 'sent' => 'We have e-mailed your password reset link!',
19 | 'token' => 'This password reset token is invalid.',
20 | 'user' => "We can't find a user with that e-mail address.",
21 |
22 | ];
23 |
--------------------------------------------------------------------------------
/lang/en/validation.php:
--------------------------------------------------------------------------------
1 | 'The :attribute must be accepted.',
17 | 'active_url' => 'The :attribute is not a valid URL.',
18 | 'after' => 'The :attribute must be a date after :date.',
19 | 'after_or_equal' => 'The :attribute must be a date after or equal to :date.',
20 | 'alpha' => 'The :attribute may only contain letters.',
21 | 'alpha_dash' => 'The :attribute may only contain letters, numbers, dashes and underscores.',
22 | 'alpha_num' => 'The :attribute may only contain letters and numbers.',
23 | 'array' => 'The :attribute must be an array.',
24 | 'before' => 'The :attribute must be a date before :date.',
25 | 'before_or_equal' => 'The :attribute must be a date before or equal to :date.',
26 | 'between' => [
27 | 'numeric' => 'The :attribute must be between :min and :max.',
28 | 'file' => 'The :attribute must be between :min and :max kilobytes.',
29 | 'string' => 'The :attribute must be between :min and :max characters.',
30 | 'array' => 'The :attribute must have between :min and :max items.',
31 | ],
32 | 'boolean' => 'The :attribute field must be true or false.',
33 | 'confirmed' => 'The :attribute confirmation does not match.',
34 | 'date' => 'The :attribute is not a valid date.',
35 | 'date_equals' => 'The :attribute must be a date equal to :date.',
36 | 'date_format' => 'The :attribute does not match the format :format.',
37 | 'different' => 'The :attribute and :other must be different.',
38 | 'digits' => 'The :attribute must be :digits digits.',
39 | 'digits_between' => 'The :attribute must be between :min and :max digits.',
40 | 'dimensions' => 'The :attribute has invalid image dimensions.',
41 | 'distinct' => 'The :attribute field has a duplicate value.',
42 | 'email' => 'The :attribute must be a valid email address.',
43 | 'exists' => 'The selected :attribute is invalid.',
44 | 'file' => 'The :attribute must be a file.',
45 | 'filled' => 'The :attribute field must have a value.',
46 | 'gt' => [
47 | 'numeric' => 'The :attribute must be greater than :value.',
48 | 'file' => 'The :attribute must be greater than :value kilobytes.',
49 | 'string' => 'The :attribute must be greater than :value characters.',
50 | 'array' => 'The :attribute must have more than :value items.',
51 | ],
52 | 'gte' => [
53 | 'numeric' => 'The :attribute must be greater than or equal :value.',
54 | 'file' => 'The :attribute must be greater than or equal :value kilobytes.',
55 | 'string' => 'The :attribute must be greater than or equal :value characters.',
56 | 'array' => 'The :attribute must have :value items or more.',
57 | ],
58 | 'image' => 'The :attribute must be an image.',
59 | 'in' => 'The selected :attribute is invalid.',
60 | 'in_array' => 'The :attribute field does not exist in :other.',
61 | 'integer' => 'The :attribute must be an integer.',
62 | 'ip' => 'The :attribute must be a valid IP address.',
63 | 'ipv4' => 'The :attribute must be a valid IPv4 address.',
64 | 'ipv6' => 'The :attribute must be a valid IPv6 address.',
65 | 'json' => 'The :attribute must be a valid JSON string.',
66 | 'lt' => [
67 | 'numeric' => 'The :attribute must be less than :value.',
68 | 'file' => 'The :attribute must be less than :value kilobytes.',
69 | 'string' => 'The :attribute must be less than :value characters.',
70 | 'array' => 'The :attribute must have less than :value items.',
71 | ],
72 | 'lte' => [
73 | 'numeric' => 'The :attribute must be less than or equal :value.',
74 | 'file' => 'The :attribute must be less than or equal :value kilobytes.',
75 | 'string' => 'The :attribute must be less than or equal :value characters.',
76 | 'array' => 'The :attribute must not have more than :value items.',
77 | ],
78 | 'max' => [
79 | 'numeric' => 'The :attribute may not be greater than :max.',
80 | 'file' => 'The :attribute may not be greater than :max kilobytes.',
81 | 'string' => 'The :attribute may not be greater than :max characters.',
82 | 'array' => 'The :attribute may not have more than :max items.',
83 | ],
84 | 'mimes' => 'The :attribute must be a file of type: :values.',
85 | 'mimetypes' => 'The :attribute must be a file of type: :values.',
86 | 'min' => [
87 | 'numeric' => 'The :attribute must be at least :min.',
88 | 'file' => 'The :attribute must be at least :min kilobytes.',
89 | 'string' => 'The :attribute must be at least :min characters.',
90 | 'array' => 'The :attribute must have at least :min items.',
91 | ],
92 | 'not_in' => 'The selected :attribute is invalid.',
93 | 'not_regex' => 'The :attribute format is invalid.',
94 | 'numeric' => 'The :attribute must be a number.',
95 | 'present' => 'The :attribute field must be present.',
96 | 'regex' => 'The :attribute format is invalid.',
97 | 'required' => 'The :attribute field is required.',
98 | 'required_if' => 'The :attribute field is required when :other is :value.',
99 | 'required_unless' => 'The :attribute field is required unless :other is in :values.',
100 | 'required_with' => 'The :attribute field is required when :values is present.',
101 | 'required_with_all' => 'The :attribute field is required when :values are present.',
102 | 'required_without' => 'The :attribute field is required when :values is not present.',
103 | 'required_without_all' => 'The :attribute field is required when none of :values are present.',
104 | 'same' => 'The :attribute and :other must match.',
105 | 'size' => [
106 | 'numeric' => 'The :attribute must be :size.',
107 | 'file' => 'The :attribute must be :size kilobytes.',
108 | 'string' => 'The :attribute must be :size characters.',
109 | 'array' => 'The :attribute must contain :size items.',
110 | ],
111 | 'starts_with' => 'The :attribute must start with one of the following: :values',
112 | 'string' => 'The :attribute must be a string.',
113 | 'timezone' => 'The :attribute must be a valid zone.',
114 | 'unique' => 'The :attribute has already been taken.',
115 | 'uploaded' => 'The :attribute failed to upload.',
116 | 'url' => 'The :attribute format is invalid.',
117 | 'uuid' => 'The :attribute must be a valid UUID.',
118 |
119 | /*
120 | |--------------------------------------------------------------------------
121 | | Custom Validation Language Lines
122 | |--------------------------------------------------------------------------
123 | |
124 | | Here you may specify custom validation messages for attributes using the
125 | | convention "attribute.rule" to name the lines. This makes it quick to
126 | | specify a specific custom language line for a given attribute rule.
127 | |
128 | */
129 |
130 | 'custom' => [
131 | 'attribute-name' => [
132 | 'rule-name' => 'custom-message',
133 | ],
134 | ],
135 |
136 | /*
137 | |--------------------------------------------------------------------------
138 | | Custom Validation Attributes
139 | |--------------------------------------------------------------------------
140 | |
141 | | The following language lines are used to swap our attribute placeholder
142 | | with something more reader friendly such as "E-Mail Address" instead
143 | | of "email". This simply helps us make our message more expressive.
144 | |
145 | */
146 |
147 | 'attributes' => [],
148 |
149 | ];
150 |
--------------------------------------------------------------------------------
/lang/en/verification.php:
--------------------------------------------------------------------------------
1 | 'Your email has been verified!',
6 | 'invalid' => 'The verification link is invalid.',
7 | 'already_verified' => 'The email is already verified.',
8 | 'user' => 'We can\'t find a user with that e-mail address.',
9 | 'sent' => 'We have e-mailed your verification link!',
10 |
11 | ];
12 |
--------------------------------------------------------------------------------
/lang/es/auth.php:
--------------------------------------------------------------------------------
1 | 'Estas credenciales no coinciden con nuestros registros.',
17 | 'throttle' => 'Demasiados intentos de acceso. Por favor intente nuevamente en :seconds segundos.',
18 |
19 | ];
20 |
--------------------------------------------------------------------------------
/lang/es/pagination.php:
--------------------------------------------------------------------------------
1 | '« Anterior',
17 | 'next' => 'Siguiente »',
18 |
19 | ];
20 |
--------------------------------------------------------------------------------
/lang/es/passwords.php:
--------------------------------------------------------------------------------
1 | 'Las contraseñas deben coincidir y contener al menos 6 caracteres',
17 | 'reset' => '¡Tu contraseña ha sido restablecida!',
18 | 'sent' => '¡Te hemos enviado por correo el enlace para restablecer tu contraseña!',
19 | 'token' => 'El token de recuperación de contraseña es inválido.',
20 | 'user' => 'No podemos encontrar ningún usuario con ese correo electrónico.',
21 |
22 | ];
23 |
--------------------------------------------------------------------------------
/lang/es/validation.php:
--------------------------------------------------------------------------------
1 | ':attribute debe ser aceptado.',
17 | 'active_url' => ':attribute no es una URL válida.',
18 | 'after' => ':attribute debe ser una fecha posterior a :date.',
19 | 'after_or_equal' => ':attribute debe ser una fecha posterior o igual a :date.',
20 | 'alpha' => ':attribute sólo debe contener letras.',
21 | 'alpha_dash' => ':attribute sólo debe contener letras, números y guiones.',
22 | 'alpha_num' => ':attribute sólo debe contener letras y números.',
23 | 'array' => ':attribute debe ser un conjunto.',
24 | 'before' => ':attribute debe ser una fecha anterior a :date.',
25 | 'before_or_equal' => ':attribute debe ser una fecha anterior o igual a :date.',
26 | 'between' => [
27 | 'numeric' => ':attribute tiene que estar entre :min - :max.',
28 | 'file' => ':attribute debe pesar entre :min - :max kilobytes.',
29 | 'string' => ':attribute tiene que tener entre :min - :max caracteres.',
30 | 'array' => ':attribute tiene que tener entre :min - :max ítems.',
31 | ],
32 | 'boolean' => 'El campo :attribute debe tener un valor verdadero o falso.',
33 | 'confirmed' => 'La confirmación de :attribute no coincide.',
34 | 'date' => ':attribute no es una fecha válida.',
35 | 'date_format' => ':attribute no corresponde al formato :format.',
36 | 'different' => ':attribute y :other deben ser diferentes.',
37 | 'digits' => ':attribute debe tener :digits dígitos.',
38 | 'digits_between' => ':attribute debe tener entre :min y :max dígitos.',
39 | 'dimensions' => 'Las dimensiones de la imagen :attribute no son válidas.',
40 | 'distinct' => 'El campo :attribute contiene un valor duplicado.',
41 | 'email' => ':attribute no es un correo válido',
42 | 'exists' => ':attribute es inválido.',
43 | 'file' => 'El campo :attribute debe ser un archivo.',
44 | 'filled' => 'El campo :attribute es obligatorio.',
45 | 'image' => ':attribute debe ser una imagen.',
46 | 'in' => ':attribute es inválido.',
47 | 'in_array' => 'El campo :attribute no existe en :other.',
48 | 'integer' => ':attribute debe ser un número entero.',
49 | 'ip' => ':attribute debe ser una dirección IP válida.',
50 | 'ipv4' => ':attribute debe ser un dirección IPv4 válida',
51 | 'ipv6' => ':attribute debe ser un dirección IPv6 válida.',
52 | 'json' => 'El campo :attribute debe tener una cadena JSON válida.',
53 | 'max' => [
54 | 'numeric' => ':attribute no debe ser mayor a :max.',
55 | 'file' => ':attribute no debe ser mayor que :max kilobytes.',
56 | 'string' => ':attribute no debe ser mayor que :max caracteres.',
57 | 'array' => ':attribute no debe tener más de :max elementos.',
58 | ],
59 | 'mimes' => ':attribute debe ser un archivo con formato: :values.',
60 | 'mimetypes' => ':attribute debe ser un archivo con formato: :values.',
61 | 'min' => [
62 | 'numeric' => 'El tamaño de :attribute debe ser de al menos :min.',
63 | 'file' => 'El tamaño de :attribute debe ser de al menos :min kilobytes.',
64 | 'string' => ':attribute debe contener al menos :min caracteres.',
65 | 'array' => ':attribute debe tener al menos :min elementos.',
66 | ],
67 | 'not_in' => ':attribute es inválido.',
68 | 'numeric' => ':attribute debe ser numérico.',
69 | 'present' => 'El campo :attribute debe estar presente.',
70 | 'regex' => 'El formato de :attribute es inválido.',
71 | 'required' => 'El campo :attribute es obligatorio.',
72 | 'required_if' => 'El campo :attribute es obligatorio cuando :other es :value.',
73 | 'required_unless' => 'El campo :attribute es obligatorio a menos que :other esté en :values.',
74 | 'required_with' => 'El campo :attribute es obligatorio cuando :values está presente.',
75 | 'required_with_all' => 'El campo :attribute es obligatorio cuando :values está presente.',
76 | 'required_without' => 'El campo :attribute es obligatorio cuando :values no está presente.',
77 | 'required_without_all' => 'El campo :attribute es obligatorio cuando ninguno de :values estén presentes.',
78 | 'same' => ':attribute y :other deben coincidir.',
79 | 'size' => [
80 | 'numeric' => 'El tamaño de :attribute debe ser :size.',
81 | 'file' => 'El tamaño de :attribute debe ser :size kilobytes.',
82 | 'string' => ':attribute debe contener :size caracteres.',
83 | 'array' => ':attribute debe contener :size elementos.',
84 | ],
85 | 'string' => 'El campo :attribute debe ser una cadena de caracteres.',
86 | 'timezone' => 'El :attribute debe ser una zona válida.',
87 | 'unique' => ':attribute ya ha sido registrado.',
88 | 'uploaded' => 'Subir :attribute ha fallado.',
89 | 'url' => 'El formato :attribute es inválido.',
90 |
91 | /*
92 | |--------------------------------------------------------------------------
93 | | Custom Validation Language Lines
94 | |--------------------------------------------------------------------------
95 | |
96 | | Here you may specify custom validation messages for attributes using the
97 | | convention "attribute.rule" to name the lines. This makes it quick to
98 | | specify a specific custom language line for a given attribute rule.
99 | |
100 | */
101 |
102 | 'custom' => [
103 | 'password' => [
104 | 'min' => 'La :attribute debe contener más de :min caracteres',
105 | ],
106 | 'email' => [
107 | 'unique' => 'El :attribute ya ha sido registrado.',
108 | ],
109 | ],
110 |
111 | /*
112 | |--------------------------------------------------------------------------
113 | | Custom Validation Attributes
114 | |--------------------------------------------------------------------------
115 | |
116 | | The following language lines are used to swap attribute place-holders
117 | | with something more reader friendly such as E-Mail Address instead
118 | | of "email". This simply helps us make messages a little cleaner.
119 | |
120 | */
121 |
122 | 'attributes' => [
123 | 'name' => 'nombre',
124 | 'username' => 'usuario',
125 | 'email' => 'correo electrónico',
126 | 'first_name' => 'nombre',
127 | 'last_name' => 'apellido',
128 | 'password' => 'contraseña',
129 | 'password_confirmation' => 'confirmación de la contraseña',
130 | 'city' => 'ciudad',
131 | 'country' => 'país',
132 | 'address' => 'dirección',
133 | 'phone' => 'teléfono',
134 | 'mobile' => 'móvil',
135 | 'age' => 'edad',
136 | 'sex' => 'sexo',
137 | 'gender' => 'género',
138 | 'year' => 'año',
139 | 'month' => 'mes',
140 | 'day' => 'día',
141 | 'hour' => 'hora',
142 | 'minute' => 'minuto',
143 | 'second' => 'segundo',
144 | 'title' => 'título',
145 | 'content' => 'contenido',
146 | 'body' => 'contenido',
147 | 'description' => 'descripción',
148 | 'excerpt' => 'extracto',
149 | 'date' => 'fecha',
150 | 'time' => 'hora',
151 | 'subject' => 'asunto',
152 | 'message' => 'mensaje',
153 | ],
154 |
155 | ];
156 |
--------------------------------------------------------------------------------
/lang/zh-CN/auth.php:
--------------------------------------------------------------------------------
1 | '用户名或手机号与密码不匹配或用户被禁用',
15 | 'throttle' => '失败次数太多,请在:seconds秒后再尝试',
16 | ];
17 |
--------------------------------------------------------------------------------
/lang/zh-CN/pagination.php:
--------------------------------------------------------------------------------
1 | '« 上一页',
15 | 'next' => '下一页 »',
16 | ];
17 |
--------------------------------------------------------------------------------
/lang/zh-CN/passwords.php:
--------------------------------------------------------------------------------
1 | '密码长度至少包含6个字符并且两次输入密码要一致',
15 | 'reset' => '密码已经被重置!',
16 | 'sent' => '我们已经发送密码重置链接到您的邮箱',
17 | 'token' => '密码重置令牌无效',
18 | 'user' => '抱歉,该邮箱对应的用户不存在!',
19 | ];
20 |
--------------------------------------------------------------------------------
/lang/zh-CN/validation.php:
--------------------------------------------------------------------------------
1 | ':attribute 已存在',
15 | 'accepted' => ':attribute 是被接受的',
16 | 'active_url' => ':attribute 必须是一个合法的 URL',
17 | 'after' => ':attribute 必须是 :date 之后的一个日期',
18 | 'alpha' => ':attribute 必须全部由字母字符构成。',
19 | 'alpha_dash' => ':attribute 必须全部由字母、数字、中划线或下划线字符构成',
20 | 'alpha_num' => ':attribute 必须全部由字母和数字构成',
21 | 'array' => ':attribute 必须是个数组',
22 | 'before' => ':attribute 必须是 :date 之前的一个日期',
23 | 'between' => [
24 | 'numeric' => ':attribute 必须在 :min 到 :max 之间',
25 | 'file' => ':attribute 必须在 :min 到 :max KB之间',
26 | 'string' => ':attribute 必须在 :min 到 :max 个字符之间',
27 | 'array' => ':attribute 必须在 :min 到 :max 项之间',
28 | ],
29 | 'boolean' => ':attribute 字符必须是 true 或 false',
30 | 'confirmed' => ':attribute 两次确认不匹配',
31 | 'date' => ':attribute 必须是一个合法的日期',
32 | 'date_format' => ':attribute 与给定的格式 :format 不符合',
33 | 'different' => ':attribute 必须不同于:other',
34 | 'digits' => ':attribute 必须是 :digits 位',
35 | 'digits_between' => ':attribute 必须在 :min and :max 位之间',
36 | 'email' => ':attribute 必须是一个合法的电子邮件地址。',
37 | 'filled' => ':attribute 的字段是必填的',
38 | 'exists' => '选定的 :attribute 是无效的',
39 | 'image' => ':attribute 必须是一个图片 (jpeg, png, bmp 或者 gif)',
40 | 'in' => '选定的 :attribute 是无效的',
41 | 'integer' => ':attribute 必须是个整数',
42 | 'ip' => ':attribute 必须是一个合法的 IP 地址。',
43 | 'max' => [
44 | 'numeric' => ':attribute 的最大长度为 :max 位',
45 | 'file' => ':attribute 的最大为 :max',
46 | 'string' => ':attribute 的最大长度为 :max 字符',
47 | 'array' => ':attribute 的最大个数为 :max 个',
48 | ],
49 | 'mimes' => ':attribute 的文件类型必须是:values',
50 | 'mimetypes' => ':attribute 的文件类型必须是: :values.',
51 | 'min' => [
52 | 'numeric' => ':attribute 的最小长度为 :min 位',
53 | 'string' => ':attribute 的最小长度为 :min 字符',
54 | 'file' => ':attribute 大小至少为:min KB',
55 | 'array' => ':attribute 至少有 :min 项',
56 | ],
57 | 'not_in' => '选定的 :attribute 是无效的',
58 | 'numeric' => ':attribute 必须是数字',
59 | 'regex' => ':attribute 格式是无效的',
60 | 'required' => ':attribute 字段必须填写',
61 | 'required_if' => ':attribute 字段是必须的当 :other 是 :value',
62 | 'required_with' => ':attribute 字段是必须的当 :values 是存在的',
63 | 'required_with_all' => ':attribute 字段是必须的当 :values 是存在的',
64 | 'required_without' => ':attribute 字段是必须的当 :values 是不存在的',
65 | 'required_without_all' => ':attribute 字段是必须的当 没有一个 :values 是存在的',
66 | 'same' => ':attribute 和 :other 必须匹配',
67 | 'size' => [
68 | 'numeric' => ':attribute 必须是 :size 位',
69 | 'file' => ':attribute 必须是 :size KB',
70 | 'string' => ':attribute 必须是 :size 个字符',
71 | 'array' => ':attribute 必须包括 :size 项',
72 | ],
73 | 'url' => ':attribute 无效的格式',
74 | 'timezone' => ':attribute 必须个有效的时区',
75 | /*
76 | |--------------------------------------------------------------------------
77 | | Custom Validation Language Lines
78 | |--------------------------------------------------------------------------
79 | |
80 | | Here you may specify custom validation messages for attributes using the
81 | | convention "attribute.rule" to name the lines. This makes it quick to
82 | | specify a specific custom language line for a given attribute rule.
83 | |
84 | */
85 | 'custom' => [],
86 | /*
87 | |--------------------------------------------------------------------------
88 | | Custom Validation Attributes
89 | |--------------------------------------------------------------------------
90 | |
91 | | The following language lines are used to swap attribute place-holders
92 | | with something more reader friendly such as E-Mail Address instead
93 | | of "email". This simply helps us make messages a little cleaner.
94 | |
95 | */
96 | 'attributes' => [],
97 | ];
98 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ./tests/Unit
6 |
7 |
8 | ./tests/Feature
9 |
10 |
11 | ./app/*/**/Tests/
12 |
13 |
14 | ./domain/*/**/Tests/
15 |
16 |
17 | ./infrastructure/*/**/Tests/
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | ./app
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/public/.htaccess:
--------------------------------------------------------------------------------
1 |
2 |
3 | Options -MultiViews -Indexes
4 |
5 |
6 | RewriteEngine On
7 |
8 | # Handle Authorization Header
9 | RewriteCond %{HTTP:Authorization} .
10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
11 |
12 | # Redirect Trailing Slashes If Not A Folder...
13 | RewriteCond %{REQUEST_FILENAME} !-d
14 | RewriteCond %{REQUEST_URI} (.+)/$
15 | RewriteRule ^ %1 [L,R=301]
16 |
17 | # Handle Front Controller...
18 | RewriteCond %{REQUEST_FILENAME} !-d
19 | RewriteCond %{REQUEST_FILENAME} !-f
20 | RewriteRule ^ index.php [L]
21 |
22 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/overtrue/laravel-skeleton/41a863d8aa9b91ef97387c6e00d04f39fe4778af/public/favicon.ico
--------------------------------------------------------------------------------
/public/img/default-avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/overtrue/laravel-skeleton/41a863d8aa9b91ef97387c6e00d04f39fe4778af/public/img/default-avatar.png
--------------------------------------------------------------------------------
/public/index.php:
--------------------------------------------------------------------------------
1 | make(Kernel::class);
50 |
51 | $response = tap($kernel->handle(
52 | $request = Request::capture()
53 | ))->send();
54 |
55 | $kernel->terminate($request, $response);
56 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow:
3 |
--------------------------------------------------------------------------------
/public/web.config:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Laravel API 基础模板
2 |
3 | 开箱即用的 Laravel API 基础结构。
4 | > 🚨自己用的哈,仅供参考,不提供咨询解答服务。
5 |
6 | ## 特点
7 | - DDD(领域模型驱动)结构;
8 | - 内置生成器,一键生成模块;
9 | - 内置 laravel/sanctum 的授权机制;
10 | - 高度完善的控制器、模型、模块模板;
11 | - 集成常用基础扩展;
12 | - 内置模型通用高阶 Traits 封装;
13 | - 自动注册 Policies;
14 | - 内置用户系统和基础接口;
15 | - 内置管理后台接口;
16 |
17 | ## 安装
18 |
19 | 1. 创建项目
20 |
21 | ```bash
22 | $ composer create-project overtrue/laravel-skeleton -vvv
23 | ```
24 |
25 |
26 | 2. 创建配置文件
27 |
28 | ```bash
29 | $ cp .env.example .env
30 | ```
31 |
32 | 3. 创建数据表和测试数据
33 |
34 | ```bash
35 | $ php artisan migrate --seed
36 | ```
37 |
38 | > 这一步将会创建管理员账号 `username:admin / password:changeThis!!` 和一个 demo 设置项。
39 |
40 | 然后访问 `http://laravel-skeleton.test/api/settings` 将会看到演示的设置项信息。
41 |
42 |
43 | ## 使用
44 |
45 | ### 创建新领域
46 |
47 | ```shell script
48 | php artisan make:domain Post
49 | ```
50 |
51 | > 该命令将会创建 `domain/Post` 目录,包含 `Actions`, `Models`, `Policies`, `Filters` 等目录。
52 |
53 | ### 创建领域类
54 |
55 | 所有官方的生成命令都增加了 `-d` 参数,用于指定领域名称,例如:
56 |
57 | ```shell
58 | php artisan make:model Post -d Post
59 | ```
60 | 另外,还有一些自定义的生成命令:
61 |
62 | ```shell script
63 | php artisan make:action MarkPostAsDraft -d Post
64 | ```
65 |
66 | ### 创建应用类
67 |
68 | ```shell script
69 | php artisan make:app Post
70 | php artisan make:endpoint GetPost -a Post
71 | php artisan make:middleware MustBePublished -a Post
72 | php artisan make:request CreatePost -a Post
73 | php artisan make:resource Post -a Post
74 | ```
75 |
76 | ### 内置接口
77 |
78 | #### 用户登录(获取 token)
79 |
80 | ##### POST /api/auth/login
81 |
82 | + Request (`application/json`)
83 | ```json
84 | {
85 | "username": "admin",
86 | "password": "changeThis!!"
87 | }
88 | ```
89 | + Response 200 (application/json)
90 | ```json
91 | {
92 | "type": "bearer",
93 | "token":"oVFV407i4jSTxjFO2tNxzh8lDaxVLbIkZZiDwjgMSYhvvkbUUXw8y0XgeYtxLAp4paznq0oxSMDdXmco"
94 | }
95 | ```
96 |
97 | #### 用户注册
98 | ##### POST /api/auth/register
99 |
100 | + Request (`application/json`)
101 | ```json
102 | {
103 | "username": "demo",
104 | "password": "123456"
105 | }
106 | ```
107 | + Response 200 (`application/json`)
108 | ```json
109 | {
110 | "type": "bearer",
111 | "token":"oVFV407i4jSTxjFO2tNxzh8lDaxVLbIkZZiDwjgMSYhvvkbUUXw8y0XgeYtxLAp4paznq0oxSMDdXmco"
112 | }
113 | ```
114 |
115 | #### 登出
116 | ##### POST /api/auth/logout
117 |
118 | + Request (`application/json`)
119 | + Headers
120 | ```
121 | Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1...
122 | ```
123 | + Response 204
124 |
125 | #### 获取当前登录用户
126 | ##### GET /api/me
127 |
128 | + Request (`application/json`)
129 | + Headers
130 | ```
131 | Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1...
132 | ```
133 | + Response 200 (`application/json`)
134 |
135 | ```json
136 | {
137 | "id": 1,
138 | "username": "admin",
139 | "nickname": "超级管理员",
140 | "avatar": "\/img\/default-avatar.png",
141 | "email": null,
142 | "gender": "none",
143 | "phone": null,
144 | "birthday": null,
145 | "settings": [],
146 | "is_admin": true,
147 | "last_active_at": null,
148 | "last_refreshed_at": null,
149 | "banned_at": null,
150 | "email_verified_at": null,
151 | "created_at": "2020-03-17T09:37:45.000000Z",
152 | "updated_at": "2020-03-17T09:37:45.000000Z",
153 | "deleted_at": null
154 | }
155 | ```
156 |
157 | ## :heart: Sponsor me
158 |
159 | If you like the work I do and want to support it, [you know what to do :heart:](https://github.com/sponsors/overtrue)
160 |
161 | 如果你喜欢我的项目并想支持它,[点击这里 :heart:](https://github.com/sponsors/overtrue)
162 |
163 | ## Project supported by JetBrains
164 |
165 | Many thanks to Jetbrains for kindly providing a license for me to work on this and other open-source projects.
166 |
167 | [](https://www.jetbrains.com/?from=https://github.com/overtrue)
168 |
169 | ## License
170 | MIT
171 |
--------------------------------------------------------------------------------
/routes/api.php:
--------------------------------------------------------------------------------
1 | json([
7 | 'status' => 'OK',
8 | ]);
9 | });
10 |
--------------------------------------------------------------------------------
/routes/channels.php:
--------------------------------------------------------------------------------
1 | id === (int) $id;
5 | });
6 |
--------------------------------------------------------------------------------
/routes/console.php:
--------------------------------------------------------------------------------
1 |
7 | */
8 | $uri = urldecode(
9 | parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)
10 | );
11 |
12 | // This file allows us to emulate Apache's "mod_rewrite" functionality from the
13 | // built-in PHP web server. This provides a convenient way to test a Laravel
14 | // application without having installed a "real" web server software here.
15 | if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) {
16 | return false;
17 | }
18 |
19 | require_once __DIR__.'/public/index.php';
20 |
--------------------------------------------------------------------------------
/storage/app/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !public/
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/storage/app/public/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/.gitignore:
--------------------------------------------------------------------------------
1 | config.php
2 | routes.php
3 | schedule-*
4 | compiled.php
5 | services.json
6 | events.scanned.php
7 | routes.scanned.php
8 | down
9 |
--------------------------------------------------------------------------------
/storage/framework/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !data/
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/storage/framework/cache/data/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/sessions/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/testing/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/views/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/logs/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/tests/CreatesApplication.php:
--------------------------------------------------------------------------------
1 | make(Kernel::class)->bootstrap();
14 |
15 | return $app;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/tests/Feature/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/overtrue/laravel-skeleton/41a863d8aa9b91ef97387c6e00d04f39fe4778af/tests/Feature/.gitkeep
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 |