From eeaf43ab5db497a9ce43c44e634e34b7f00878fc Mon Sep 17 00:00:00 2001 From: Viktor Smagin Date: Mon, 5 Jan 2026 16:33:20 +0400 Subject: [PATCH] Skeleton is ready --- .editorconfig | 18 ++ .env.example | 38 +++ .gitignore | 25 ++ Jenkinsfile | 58 +++++ README.md | 60 +++++ app/Feature/Example/Command/DoSomething.php | 18 ++ app/Feature/Example/Dto/SearchUsers.php | 18 ++ app/Feature/Example/Http/Controller/User.php | 23 ++ app/Feature/Example/Http/Request/Index.php | 22 ++ app/Feature/Example/Http/Resource/User.php | 24 ++ app/Feature/Example/Http/Resource/Users.php | 21 ++ .../Example/Listener/DoSomethingOnEvent.php | 25 ++ app/Feature/Example/Provider.php | 60 +++++ .../Example/Service/UsersRepository.php | 26 +++ app/Kernel/DateTime/Format.php | 11 + app/Kernel/DateTime/Util.php | 17 ++ app/Kernel/Db/Ilike.php | 12 + app/Kernel/Db/Query.php | 16 ++ app/Kernel/Event/Event.php | 9 + app/Kernel/Feature/HasCommandsList.php | 15 ++ app/Kernel/Feature/HasEventListeners.php | 25 ++ app/Kernel/Http/ContentType.php | 10 + app/Kernel/Http/Header.php | 10 + .../Http/Middleware/CheckAcceptHeader.php | 39 ++++ app/Kernel/Http/Validation.php | 15 ++ app/Kernel/Object/Enum.php | 27 +++ app/Kernel/Object/HasHiddenAttributes.php | 17 ++ app/Kernel/Object/HasPermissions.php | 10 + app/Kernel/Object/HasRoles.php | 10 + app/Kernel/Object/HasRolesWithPermissions.php | 7 + app/Kernel/Object/HasUuidAsIdentifier.php | 10 + app/Kernel/Queue/Queue.php | 9 + app/Kernel/Role/Role.php | 11 + app/Kernel/String/Hash.php | 13 ++ app/Kernel/String/Regex.php | 11 + app/Kernel/String/String.php | 10 + app/Models/User/User.php | 57 +++++ app/Providers/SharedService.php | 24 ++ app/Shared/Event/User/Created.php | 27 +++ app/Shared/Event/User/Deleted.php | 27 +++ app/Shared/Event/User/Updated.php | 27 +++ app/Shared/Service/User/SearchById.php | 22 ++ .../Service/User/SearchByIdContract.php | 12 + artisan | 18 ++ bootstrap/app.php | 28 +++ bootstrap/cache/.gitignore | 2 + bootstrap/providers.php | 19 ++ composer.json | 73 ++++++ config/app.php | 126 ++++++++++ config/auth.php | 110 +++++++++ config/cache.php | 108 +++++++++ config/database.php | 108 +++++++++ config/filesystems.php | 80 +++++++ config/logging.php | 132 +++++++++++ config/mail.php | 118 ++++++++++ config/queue.php | 113 +++++++++ config/services.php | 17 ++ config/session.php | 217 ++++++++++++++++++ database/.gitignore | 1 + .../0001_01_01_000000_create_users_table.php | 31 +++ .../0001_01_02_000000_create_jobs_table.php | 32 +++ ..._01_03_000000_create_failed_jobs_table.php | 32 +++ ..._01_04_000000_create_job_batches_table.php | 35 +++ database/seeders/DatabaseSeeder.php | 19 ++ deploy/main/docker-compose.yaml | 68 ++++++ .../etc/supervisor/conf.d/default-worker.conf | 11 + deploy/main/php.Dockerfile | 27 +++ deploy/main/supervisor.Dockerfile | 39 ++++ .../usr/local/bin/supervisor.entrypoint.sh | 21 ++ .../etc/php/conf.d/docker-php-ext-xdebug.ini | 4 + phpunit.xml | 33 +++ public/.htaccess | 25 ++ public/favicon.ico | Bin 0 -> 417 bytes public/index.php | 20 ++ public/robots.txt | 2 + resources/views/blank.blade.php | 14 ++ storage/app/.gitignore | 4 + storage/app/private/.gitignore | 2 + storage/app/public/.gitignore | 2 + storage/framework/.gitignore | 9 + storage/framework/cache/.gitignore | 3 + storage/framework/cache/data/.gitignore | 2 + storage/framework/sessions/.gitignore | 2 + storage/framework/testing/.gitignore | 2 + storage/framework/views/.gitignore | 2 + storage/logs/.gitignore | 2 + tests/Feature/ExampleTest.php | 19 ++ tests/TestCase.php | 10 + tests/Unit/ExampleTest.php | 16 ++ 89 files changed, 2704 insertions(+) create mode 100644 .editorconfig create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 Jenkinsfile create mode 100644 README.md create mode 100644 app/Feature/Example/Command/DoSomething.php create mode 100644 app/Feature/Example/Dto/SearchUsers.php create mode 100644 app/Feature/Example/Http/Controller/User.php create mode 100644 app/Feature/Example/Http/Request/Index.php create mode 100644 app/Feature/Example/Http/Resource/User.php create mode 100644 app/Feature/Example/Http/Resource/Users.php create mode 100644 app/Feature/Example/Listener/DoSomethingOnEvent.php create mode 100644 app/Feature/Example/Provider.php create mode 100644 app/Feature/Example/Service/UsersRepository.php create mode 100644 app/Kernel/DateTime/Format.php create mode 100644 app/Kernel/DateTime/Util.php create mode 100644 app/Kernel/Db/Ilike.php create mode 100644 app/Kernel/Db/Query.php create mode 100644 app/Kernel/Event/Event.php create mode 100644 app/Kernel/Feature/HasCommandsList.php create mode 100644 app/Kernel/Feature/HasEventListeners.php create mode 100644 app/Kernel/Http/ContentType.php create mode 100644 app/Kernel/Http/Header.php create mode 100644 app/Kernel/Http/Middleware/CheckAcceptHeader.php create mode 100644 app/Kernel/Http/Validation.php create mode 100644 app/Kernel/Object/Enum.php create mode 100644 app/Kernel/Object/HasHiddenAttributes.php create mode 100644 app/Kernel/Object/HasPermissions.php create mode 100644 app/Kernel/Object/HasRoles.php create mode 100644 app/Kernel/Object/HasRolesWithPermissions.php create mode 100644 app/Kernel/Object/HasUuidAsIdentifier.php create mode 100644 app/Kernel/Queue/Queue.php create mode 100644 app/Kernel/Role/Role.php create mode 100644 app/Kernel/String/Hash.php create mode 100644 app/Kernel/String/Regex.php create mode 100644 app/Kernel/String/String.php create mode 100644 app/Models/User/User.php create mode 100644 app/Providers/SharedService.php create mode 100644 app/Shared/Event/User/Created.php create mode 100644 app/Shared/Event/User/Deleted.php create mode 100644 app/Shared/Event/User/Updated.php create mode 100644 app/Shared/Service/User/SearchById.php create mode 100644 app/Shared/Service/User/SearchByIdContract.php create mode 100755 artisan create mode 100644 bootstrap/app.php create mode 100644 bootstrap/cache/.gitignore create mode 100644 bootstrap/providers.php create mode 100644 composer.json create mode 100644 config/app.php create mode 100644 config/auth.php create mode 100644 config/cache.php create mode 100644 config/database.php create mode 100644 config/filesystems.php create mode 100644 config/logging.php create mode 100644 config/mail.php create mode 100644 config/queue.php create mode 100644 config/services.php create mode 100644 config/session.php create mode 100644 database/.gitignore create mode 100644 database/migrations/0001_01_01_000000_create_users_table.php create mode 100644 database/migrations/0001_01_02_000000_create_jobs_table.php create mode 100644 database/migrations/0001_01_03_000000_create_failed_jobs_table.php create mode 100644 database/migrations/0001_01_04_000000_create_job_batches_table.php create mode 100644 database/seeders/DatabaseSeeder.php create mode 100644 deploy/main/docker-compose.yaml create mode 100644 deploy/main/etc/supervisor/conf.d/default-worker.conf create mode 100644 deploy/main/php.Dockerfile create mode 100644 deploy/main/supervisor.Dockerfile create mode 100644 deploy/main/usr/local/bin/supervisor.entrypoint.sh create mode 100644 deploy/main/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini create mode 100644 phpunit.xml create mode 100644 public/.htaccess create mode 100644 public/favicon.ico create mode 100644 public/index.php create mode 100644 public/robots.txt create mode 100644 resources/views/blank.blade.php create mode 100644 storage/app/.gitignore create mode 100644 storage/app/private/.gitignore create mode 100644 storage/app/public/.gitignore create mode 100644 storage/framework/.gitignore create mode 100644 storage/framework/cache/.gitignore create mode 100644 storage/framework/cache/data/.gitignore create mode 100644 storage/framework/sessions/.gitignore create mode 100644 storage/framework/testing/.gitignore create mode 100644 storage/framework/views/.gitignore create mode 100644 storage/logs/.gitignore create mode 100644 tests/Feature/ExampleTest.php create mode 100644 tests/TestCase.php create mode 100644 tests/Unit/ExampleTest.php diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8f0de65 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 + +[docker-compose.yml] +indent_size = 4 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..0a6cfff --- /dev/null +++ b/.env.example @@ -0,0 +1,38 @@ +APP_NAME=project-api +APP_ENV=local +APP_KEY= +APP_DEBUG=true +APP_URL=http://api.project.local + +APP_LOCALE=en +APP_FALLBACK_LOCALE=en +APP_FAKER_LOCALE=en_US + +APP_MAINTENANCE_DRIVER=file +# APP_MAINTENANCE_STORE=database + +BCRYPT_ROUNDS=12 + +LOG_CHANNEL=stack +LOG_STACK=daily +LOG_DEPRECATIONS_CHANNEL=null +LOG_LEVEL=debug + +DB_CONNECTION=pgsql +DB_HOST=localhost +DB_PORT=5432 +DB_DATABASE= +DB_USERNAME= +DB_PASSWORD= + +BROADCAST_CONNECTION=log +FILESYSTEM_DISK=local + +QUEUE_CONNECTION=redis +CACHE_STORE=redis +# CACHE_PREFIX= + +REDIS_HOST=localhost +REDIS_PORT=6379 +REDIS_DB=0 +REDIS_CACHE_DB=0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..13ec5e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +*.log +.DS_Store +.env +.env.backup +.env.production +.phpactor.json +.phpunit.result.cache +/.fleet +/.idea +/.nova +/.phpunit.cache +/.vscode +/.zed +/auth.json +/node_modules +/public/build +/public/hot +/public/storage +/storage/*.key +/storage/pail +/vendor +Homestead.json +Homestead.yaml +Thumbs.db +*.pid diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..3b5a39a --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,58 @@ +pipeline { + agent any + + environment { + API_TOKEN = credentials('04f8a5d4-19fc-4ab3-979e-35bbb5b64611') + ENV_FILE = '/usr/local/share/environment/api.env' + COMPOSE_FILE = 'src/deploy/main/docker-compose.yaml' + PHP_CONTAINER = 'project-php' + } + + stages { + stage('Clean') { + steps { + cleanWs() + } + } + stage('Clone') { + steps { + sh """ + git clone https://gitea.diffteam.online/PHP/LaravelSkeletonAPI.git src + """ + } + } + stage('Environment') { + steps { + sh """ + if [ -f "${ENV_FILE}" ]; then + cp "${ENV_FILE}" src/.env + + echo ".env file copied successfully" + else + echo "ERROR: .env file not found at ${ENV_FILE}" + + exit 1 + fi + """ + } + } + stage('Build') { + steps { + sh """ + docker compose --env-file src/.env -f "${COMPOSE_FILE}" build + """ + } + } + stage('Deploy') { + steps { + sh """ + docker compose --env-file src/.env -f "${COMPOSE_FILE}" down --remove-orphans + docker compose --env-file src/.env -f "${COMPOSE_FILE}" up -d + + docker exec "${PHP_CONTAINER}" php artisan migrate --force + docker exec "${PHP_CONTAINER}" php artisan optimize + """ + } + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..d11ef61 --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +# Laravel API skeleton + +This is a feature-based API application skeleton. + +## Requirements + +* PHP 8.2+ + +## About + +### Packages + +The skeleton contains the following packages: + +* `diffhead/php-dto` - DTO implementation +* `laravel/framework` - Laravel version 12 +* `laravel/tinker` - Application interpreter +* `laravel/sanctum` - Authentication library +* `spatie/laravel-permission` - Authorization features + +It also includes packages for application testing. See `composer.json` for details. + +### Architecture + +This is a feature-based skeleton. Every feature has a +service provider which should be included by adding it to +`bootstrap/providers.php`. + +The skeleton has the `App\Kernel` namespace which contains +some basic utilities, enums, middlewares and other +fundamental components. + +Also this application has the `App\Shared` namespace for +services, events, or other things that will be shared +between features and components. + +`App\Models` namespace should contain global app models +which represent database structures. + +#### Feature + +A feature can contain route definitions, event listeners, +commands, services, and other required components needed to +implement a specific functionality. + +I strongly recommend keeping each feature isolated from the +others. A good sign of proper isolation is that the application +continues to work stably even when a feature is disabled. + +### Infrastructure + +The skeleton contains a Jenkinsfile in the root of the project as +an example of how to build and deploy the application. It also +contains Docker files inside the `deploy` directory. + +### Usage + +The skeleton includes an `app\Feature\Example` feature as an example of how to +define features, some shared services and events, and base +migrations with the user model. diff --git a/app/Feature/Example/Command/DoSomething.php b/app/Feature/Example/Command/DoSomething.php new file mode 100644 index 0000000..74a3de6 --- /dev/null +++ b/app/Feature/Example/Command/DoSomething.php @@ -0,0 +1,18 @@ + $page + * @property \Diffhead\PHP\Dto\Property $perPage + */ +class SearchUsers extends Dto +{ + protected Property $page; + protected Property $perPage; +} diff --git a/app/Feature/Example/Http/Controller/User.php b/app/Feature/Example/Http/Controller/User.php new file mode 100644 index 0000000..4d8a4b5 --- /dev/null +++ b/app/Feature/Example/Http/Controller/User.php @@ -0,0 +1,23 @@ +validated()); + return Users::make($this->users->search($dto)); + } +} diff --git a/app/Feature/Example/Http/Request/Index.php b/app/Feature/Example/Http/Request/Index.php new file mode 100644 index 0000000..1aedefa --- /dev/null +++ b/app/Feature/Example/Http/Request/Index.php @@ -0,0 +1,22 @@ + ['sometimes', 'integer', 'min:1'], + 'per_page' => ['sometimes', 'integer', 'min:1', 'max:200'], + ]; + } +} diff --git a/app/Feature/Example/Http/Resource/User.php b/app/Feature/Example/Http/Resource/User.php new file mode 100644 index 0000000..7c6b355 --- /dev/null +++ b/app/Feature/Example/Http/Resource/User.php @@ -0,0 +1,24 @@ + $this->resource->getKey(), + 'login' => $this->resource->login, + 'created_at' => $this->resource->created_at, + 'updated_at' => $this->resource->updated_at, + ]; + } +} diff --git a/app/Feature/Example/Http/Resource/Users.php b/app/Feature/Example/Http/Resource/Users.php new file mode 100644 index 0000000..c98578e --- /dev/null +++ b/app/Feature/Example/Http/Resource/Users.php @@ -0,0 +1,21 @@ + $collection + */ +class Users extends ResourceCollection +{ + public function toArray(Request $request): array + { + return [ + 'data' => User::collection($this->collection) + ]; + } +} diff --git a/app/Feature/Example/Listener/DoSomethingOnEvent.php b/app/Feature/Example/Listener/DoSomethingOnEvent.php new file mode 100644 index 0000000..aa50c54 --- /dev/null +++ b/app/Feature/Example/Listener/DoSomethingOnEvent.php @@ -0,0 +1,25 @@ + + */ + public array $bindings = [ + /** Bindings section */ + ]; + + /** + * @var array + */ + private array $commandsList = [ + DoSomething::class, + ]; + + private array $eventListeners = [ + UserCreated::class => [ + DoSomethingOnEvent::class, + ], + ]; + + public function register(): void + { + /** Register something here */ + } + + public function boot(): void + { + $this->registerCommands(); + $this->registerListeners(); + $this->registerRoutes(); + } + + private function registerRoutes(): void + { + Route::middleware('auth:sanctum') + ->controller(User::class) + ->group(function (): void { + Route::get('/users', 'index')->name('users.index'); + }); + } +} diff --git a/app/Feature/Example/Service/UsersRepository.php b/app/Feature/Example/Service/UsersRepository.php new file mode 100644 index 0000000..e283a16 --- /dev/null +++ b/app/Feature/Example/Service/UsersRepository.php @@ -0,0 +1,26 @@ + + */ + public function search(SearchUsers $dto): LengthAwarePaginator + { + return User::query()->paginate( + page: $dto->page->exists() ? $dto->page->value() : 1, + perPage: $dto->perPage->exists() ? $dto->perPage->value() : 20, + ); + } +} diff --git a/app/Kernel/DateTime/Format.php b/app/Kernel/DateTime/Format.php new file mode 100644 index 0000000..7b42873 --- /dev/null +++ b/app/Kernel/DateTime/Format.php @@ -0,0 +1,11 @@ +format($format->value); + } +} diff --git a/app/Kernel/Db/Ilike.php b/app/Kernel/Db/Ilike.php new file mode 100644 index 0000000..936e93d --- /dev/null +++ b/app/Kernel/Db/Ilike.php @@ -0,0 +1,12 @@ +value, $modifier), $value); + } +} diff --git a/app/Kernel/Event/Event.php b/app/Kernel/Event/Event.php new file mode 100644 index 0000000..035a6ac --- /dev/null +++ b/app/Kernel/Event/Event.php @@ -0,0 +1,9 @@ +commandsList) && method_exists($this, 'commands')) { + $this->commands($this->commandsList); + } + } +} diff --git a/app/Kernel/Feature/HasEventListeners.php b/app/Kernel/Feature/HasEventListeners.php new file mode 100644 index 0000000..8cee73b --- /dev/null +++ b/app/Kernel/Feature/HasEventListeners.php @@ -0,0 +1,25 @@ +eventListeners)) { + /** + * @var string $event + * @var array $listeners + */ + foreach ($this->eventListeners as $event => $listeners) { + foreach ($listeners as $listener) { + Event::listen($event, $listener); + } + } + } + } +} diff --git a/app/Kernel/Http/ContentType.php b/app/Kernel/Http/ContentType.php new file mode 100644 index 0000000..9380b02 --- /dev/null +++ b/app/Kernel/Http/ContentType.php @@ -0,0 +1,10 @@ +acceptApplicationJson($request)) { + $headerFound = true; + } + + if ($request->isMethod('OPTIONS') || $headerFound) { + return $next($request); + } + + throw new NotAcceptableHttpException('Valid accept header not found'); + } + + private function acceptApplicationJson(Request $request): bool + { + $passedHeader = $request->headers->get(Header::Accept->value); + $expectedHeader = Enum::value(ContentType::ApplicationJson); + + return $passedHeader === $expectedHeader; + } +} diff --git a/app/Kernel/Http/Validation.php b/app/Kernel/Http/Validation.php new file mode 100644 index 0000000..906840c --- /dev/null +++ b/app/Kernel/Http/Validation.php @@ -0,0 +1,15 @@ +value); + } +} diff --git a/app/Kernel/Object/Enum.php b/app/Kernel/Object/Enum.php new file mode 100644 index 0000000..a747954 --- /dev/null +++ b/app/Kernel/Object/Enum.php @@ -0,0 +1,27 @@ + $enums + * + * @return array + */ + public static function values(array $enums): array + { + return array_map(fn (BackedEnum $enum) => static::value($enum), $enums); + } + + public static function value(BackedEnum $enum): int|string|float + { + return $enum->value; + } +} diff --git a/app/Kernel/Object/HasHiddenAttributes.php b/app/Kernel/Object/HasHiddenAttributes.php new file mode 100644 index 0000000..d91b392 --- /dev/null +++ b/app/Kernel/Object/HasHiddenAttributes.php @@ -0,0 +1,17 @@ +hidden)) { + return in_array($key, $this->hidden, true); + } + + return false; + } +} diff --git a/app/Kernel/Object/HasPermissions.php b/app/Kernel/Object/HasPermissions.php new file mode 100644 index 0000000..04c8a1d --- /dev/null +++ b/app/Kernel/Object/HasPermissions.php @@ -0,0 +1,10 @@ + $roles + * @property \Illuminate\Database\Eloquent\Collection $permissions + * + * @method ?string getKey() + * + * @method static \App\Models\User\User findOrFail(string $id) + */ +class User extends Authenticatable +{ + use HasApiTokens, HasUuids, HasRoles, SoftDeletes; + + /** + * @var array + */ + protected $fillable = [ + 'login', + 'password', + 'is_active', + ]; + + /** + * @var array + */ + protected $hidden = [ + 'password', + 'laravel_through_key', + ]; + + /** + * @return array + */ + protected function casts(): array + { + return [ + 'is_active' => 'boolean', + 'password' => 'hashed', + ]; + } +} diff --git a/app/Providers/SharedService.php b/app/Providers/SharedService.php new file mode 100644 index 0000000..4955a91 --- /dev/null +++ b/app/Providers/SharedService.php @@ -0,0 +1,24 @@ + + */ + public array $bindings = [ + /** User repository */ + \App\Shared\Service\User\SearchByIdContract::class => + \App\Shared\Service\User\SearchById::class, + ]; + + /** + * @var array + */ + public array $singletons = [ + /** Singletons */ + ]; +} diff --git a/app/Shared/Event/User/Created.php b/app/Shared/Event/User/Created.php new file mode 100644 index 0000000..69436ba --- /dev/null +++ b/app/Shared/Event/User/Created.php @@ -0,0 +1,27 @@ +userId = $userId; + $this->createdAt = $createdAt; + } + + public function getUserId(): string + { + return $this->userId; + } + + public function getCreatedAt(): string + { + return $this->createdAt; + } +} diff --git a/app/Shared/Event/User/Deleted.php b/app/Shared/Event/User/Deleted.php new file mode 100644 index 0000000..492ae9d --- /dev/null +++ b/app/Shared/Event/User/Deleted.php @@ -0,0 +1,27 @@ +userId = $userId; + $this->deletedAt = $deletedAt; + } + + public function getUserId(): string + { + return $this->userId; + } + + public function getDeletedAt(): string + { + return $this->deletedAt; + } +} diff --git a/app/Shared/Event/User/Updated.php b/app/Shared/Event/User/Updated.php new file mode 100644 index 0000000..7a0e4b7 --- /dev/null +++ b/app/Shared/Event/User/Updated.php @@ -0,0 +1,27 @@ +userId = $userId; + $this->updatedAt = $updatedAt; + } + + public function getUserId(): string + { + return $this->userId; + } + + public function getUpdatedAt(): string + { + return $this->updatedAt; + } +} diff --git a/app/Shared/Service/User/SearchById.php b/app/Shared/Service/User/SearchById.php new file mode 100644 index 0000000..56208d7 --- /dev/null +++ b/app/Shared/Service/User/SearchById.php @@ -0,0 +1,22 @@ +handleCommand(new ArgvInput); + +exit($status); diff --git a/bootstrap/app.php b/bootstrap/app.php new file mode 100644 index 0000000..9278a1f --- /dev/null +++ b/bootstrap/app.php @@ -0,0 +1,28 @@ +withRouting() + ->withMiddleware(function (Middleware $middleware): void { + $middleware->use([ + TrimStrings::class, + ConvertEmptyStringsToNull::class, + CheckAcceptHeader::class, + ]); + + $middleware->alias([ + /** + * 'middleware.name' => Middleware:class, + */ + ]); + }) + ->withExceptions(function (Exceptions $exceptions): void { + + }) + ->create(); diff --git a/bootstrap/cache/.gitignore b/bootstrap/cache/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/bootstrap/cache/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/bootstrap/providers.php b/bootstrap/providers.php new file mode 100644 index 0000000..4a5bcee --- /dev/null +++ b/bootstrap/providers.php @@ -0,0 +1,19 @@ + env('APP_NAME', 'Laravel'), + + /* + |-------------------------------------------------------------------------- + | Application Environment + |-------------------------------------------------------------------------- + | + | This value determines the "environment" your application is currently + | running in. This may determine how you prefer to configure various + | services the application utilizes. Set this in your ".env" file. + | + */ + + 'env' => env('APP_ENV', 'production'), + + /* + |-------------------------------------------------------------------------- + | Application Debug Mode + |-------------------------------------------------------------------------- + | + | When your application is in debug mode, detailed error messages with + | stack traces will be shown on every error that occurs within your + | application. If disabled, a simple generic error page is shown. + | + */ + + 'debug' => (bool) env('APP_DEBUG', false), + + /* + |-------------------------------------------------------------------------- + | Application URL + |-------------------------------------------------------------------------- + | + | This URL is used by the console to properly generate URLs when using + | the Artisan command line tool. You should set this to the root of + | the application so that it's available within Artisan commands. + | + */ + + 'url' => env('APP_URL', 'http://localhost'), + + /* + |-------------------------------------------------------------------------- + | Application Timezone + |-------------------------------------------------------------------------- + | + | Here you may specify the default timezone for your application, which + | will be used by the PHP date and date-time functions. The timezone + | is set to "UTC" by default as it is suitable for most use cases. + | + */ + + 'timezone' => 'UTC', + + /* + |-------------------------------------------------------------------------- + | Application Locale Configuration + |-------------------------------------------------------------------------- + | + | The application locale determines the default locale that will be used + | by Laravel's translation / localization methods. This option can be + | set to any locale for which you plan to have translation strings. + | + */ + + 'locale' => env('APP_LOCALE', 'en'), + + 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'), + + 'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'), + + /* + |-------------------------------------------------------------------------- + | Encryption Key + |-------------------------------------------------------------------------- + | + | This key is utilized by Laravel's encryption services and should be set + | to a random, 32 character string to ensure that all encrypted values + | are secure. You should do this prior to deploying the application. + | + */ + + 'cipher' => 'AES-256-CBC', + + 'key' => env('APP_KEY'), + + 'previous_keys' => [ + ...array_filter( + explode(',', env('APP_PREVIOUS_KEYS', '')) + ), + ], + + /* + |-------------------------------------------------------------------------- + | Maintenance Mode Driver + |-------------------------------------------------------------------------- + | + | These configuration options determine the driver used to determine and + | manage Laravel's "maintenance mode" status. The "cache" driver will + | allow maintenance mode to be controlled across multiple machines. + | + | Supported drivers: "file", "cache" + | + */ + + 'maintenance' => [ + 'driver' => env('APP_MAINTENANCE_DRIVER', 'file'), + 'store' => env('APP_MAINTENANCE_STORE', 'database'), + ], + +]; diff --git a/config/auth.php b/config/auth.php new file mode 100644 index 0000000..580c48b --- /dev/null +++ b/config/auth.php @@ -0,0 +1,110 @@ + [ + 'guard' => env('AUTH_GUARD', 'web'), + 'passwords' => env('AUTH_PASSWORD_BROKER', 'users'), + ], + + /* + |-------------------------------------------------------------------------- + | Authentication Guards + |-------------------------------------------------------------------------- + | + | Next, you may define every authentication guard for your application. + | Of course, a great default configuration has been defined for you + | which utilizes session storage plus the Eloquent user provider. + | + | All authentication guards have a user provider, which defines how the + | users are actually retrieved out of your database or other storage + | system used by the application. Typically, Eloquent is utilized. + | + | Supported: "session" + | + */ + + 'guards' => [ + 'web' => [ + 'driver' => 'session', + 'provider' => 'users', + ], + ], + + /* + |-------------------------------------------------------------------------- + | User Providers + |-------------------------------------------------------------------------- + | + | All authentication guards have a user provider, which defines how the + | users are actually retrieved out of your database or other storage + | system used by the application. Typically, Eloquent is utilized. + | + | If you have multiple user tables or models you may configure multiple + | providers to represent the model / table. These providers may then + | be assigned to any extra authentication guards you have defined. + | + | Supported: "database", "eloquent" + | + */ + + 'providers' => [ + 'users' => [ + 'driver' => 'eloquent', + 'model' => env('AUTH_MODEL', App\Models\User\User::class), + ], + ], + + /* + |-------------------------------------------------------------------------- + | Resetting Passwords + |-------------------------------------------------------------------------- + | + | These configuration options specify the behavior of Laravel's password + | reset functionality, including the table utilized for token storage + | and the user provider that is invoked to actually retrieve users. + | + | The expiry time is the number of minutes that each reset token will be + | considered valid. This security feature keeps tokens short-lived so + | they have less time to be guessed. You may change this as needed. + | + | The throttle setting is the number of seconds a user must wait before + | generating more password reset tokens. This prevents the user from + | quickly generating a very large amount of password reset tokens. + | + */ + + 'passwords' => [ + 'users' => [ + 'provider' => 'users', + 'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'), + 'expire' => 60, + 'throttle' => 60, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Password Confirmation Timeout + |-------------------------------------------------------------------------- + | + | Here you may define the number of seconds before a password confirmation + | window expires and users are asked to re-enter their password via the + | confirmation screen. By default, the timeout lasts for three hours. + | + */ + + 'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800), + +]; diff --git a/config/cache.php b/config/cache.php new file mode 100644 index 0000000..925f7d2 --- /dev/null +++ b/config/cache.php @@ -0,0 +1,108 @@ + env('CACHE_STORE', 'database'), + + /* + |-------------------------------------------------------------------------- + | Cache Stores + |-------------------------------------------------------------------------- + | + | Here you may define all of the cache "stores" for your application as + | well as their drivers. You may even define multiple stores for the + | same cache driver to group types of items stored in your caches. + | + | Supported drivers: "array", "database", "file", "memcached", + | "redis", "dynamodb", "octane", "null" + | + */ + + 'stores' => [ + + 'array' => [ + 'driver' => 'array', + 'serialize' => false, + ], + + 'database' => [ + 'driver' => 'database', + 'connection' => env('DB_CACHE_CONNECTION'), + 'table' => env('DB_CACHE_TABLE', 'cache'), + 'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'), + 'lock_table' => env('DB_CACHE_LOCK_TABLE'), + ], + + 'file' => [ + 'driver' => 'file', + 'path' => storage_path('framework/cache/data'), + 'lock_path' => storage_path('framework/cache/data'), + ], + + 'memcached' => [ + 'driver' => 'memcached', + 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), + 'sasl' => [ + env('MEMCACHED_USERNAME'), + env('MEMCACHED_PASSWORD'), + ], + 'options' => [ + // Memcached::OPT_CONNECT_TIMEOUT => 2000, + ], + 'servers' => [ + [ + 'host' => env('MEMCACHED_HOST', '127.0.0.1'), + 'port' => env('MEMCACHED_PORT', 11211), + 'weight' => 100, + ], + ], + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => env('REDIS_CACHE_CONNECTION', 'cache'), + 'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'), + ], + + 'dynamodb' => [ + 'driver' => 'dynamodb', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), + 'endpoint' => env('DYNAMODB_ENDPOINT'), + ], + + 'octane' => [ + 'driver' => 'octane', + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Cache Key Prefix + |-------------------------------------------------------------------------- + | + | When utilizing the APC, database, memcached, Redis, and DynamoDB cache + | stores, there might be other applications using the same cache. For + | that reason, you may prefix every cache key to avoid collisions. + | + */ + + 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'), + +]; diff --git a/config/database.php b/config/database.php new file mode 100644 index 0000000..d987ec5 --- /dev/null +++ b/config/database.php @@ -0,0 +1,108 @@ + env('DB_CONNECTION', 'pgsql'), + + /* + |-------------------------------------------------------------------------- + | Database Connections + |-------------------------------------------------------------------------- + | + | Below are all of the database connections defined for your application. + | An example configuration is provided for each database system which + | is supported by Laravel. You're free to add / remove connections. + | + */ + + 'connections' => [ + + 'pgsql' => [ + 'driver' => 'pgsql', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '5432'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => env('DB_CHARSET', 'utf8'), + 'prefix' => '', + 'prefix_indexes' => true, + 'search_path' => 'public', + 'sslmode' => 'prefer', + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Migration Repository Table + |-------------------------------------------------------------------------- + | + | This table keeps track of all the migrations that have already run for + | your application. Using this information, we can determine which of + | the migrations on disk haven't actually been run on the database. + | + */ + + 'migrations' => [ + 'table' => 'migrations', + 'update_date_on_publish' => true, + ], + + /* + |-------------------------------------------------------------------------- + | Redis Databases + |-------------------------------------------------------------------------- + | + | Redis is an open source, fast, and advanced key-value store that also + | provides a richer body of commands than a typical key-value system + | such as Memcached. You may define your connection settings here. + | + */ + + 'redis' => [ + + 'client' => env('REDIS_CLIENT', 'phpredis'), + + 'options' => [ + 'cluster' => env('REDIS_CLUSTER', 'redis'), + 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), + 'persistent' => env('REDIS_PERSISTENT', false), + ], + + 'default' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'username' => env('REDIS_USERNAME'), + 'password' => env('REDIS_PASSWORD'), + 'port' => env('REDIS_PORT', '6379'), + 'database' => env('REDIS_DB', '2'), + ], + + 'cache' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'username' => env('REDIS_USERNAME'), + 'password' => env('REDIS_PASSWORD'), + 'port' => env('REDIS_PORT', '6379'), + 'database' => env('REDIS_CACHE_DB', '2'), + ], + + ], + +]; diff --git a/config/filesystems.php b/config/filesystems.php new file mode 100644 index 0000000..1bb86d1 --- /dev/null +++ b/config/filesystems.php @@ -0,0 +1,80 @@ + env('FILESYSTEM_DISK', 'local'), + + /* + |-------------------------------------------------------------------------- + | Filesystem Disks + |-------------------------------------------------------------------------- + | + | Below you may configure as many filesystem disks as necessary, and you + | may even configure multiple disks for the same driver. Examples for + | most supported storage drivers are configured here for reference. + | + | Supported drivers: "local", "ftp", "sftp", "s3" + | + */ + + 'disks' => [ + + 'local' => [ + 'driver' => 'local', + 'root' => storage_path('app/private'), + 'serve' => false, + 'throw' => false, + 'report' => false, + ], + + 'public' => [ + 'driver' => 'local', + 'root' => storage_path('app/public'), + 'url' => env('APP_URL').'/storage', + 'visibility' => 'public', + 'throw' => false, + 'report' => false, + ], + + 's3' => [ + 'driver' => 's3', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION'), + 'bucket' => env('AWS_BUCKET'), + 'url' => env('AWS_URL'), + 'endpoint' => env('AWS_ENDPOINT'), + 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), + 'throw' => false, + 'report' => false, + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Symbolic Links + |-------------------------------------------------------------------------- + | + | Here you may configure the symbolic links that will be created when the + | `storage:link` Artisan command is executed. The array keys should be + | the locations of the links and the values should be their targets. + | + */ + + 'links' => [ + public_path('storage') => storage_path('app/public'), + ], + +]; diff --git a/config/logging.php b/config/logging.php new file mode 100644 index 0000000..c394f7c --- /dev/null +++ b/config/logging.php @@ -0,0 +1,132 @@ + env('LOG_CHANNEL', 'daily'), + + /* + |-------------------------------------------------------------------------- + | Deprecations Log Channel + |-------------------------------------------------------------------------- + | + | This option controls the log channel that should be used to log warnings + | regarding deprecated PHP and library features. This allows you to get + | your application ready for upcoming major versions of dependencies. + | + */ + + 'deprecations' => [ + 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), + 'trace' => env('LOG_DEPRECATIONS_TRACE', false), + ], + + /* + |-------------------------------------------------------------------------- + | Log Channels + |-------------------------------------------------------------------------- + | + | Here you may configure the log channels for your application. Laravel + | utilizes the Monolog PHP logging library, which includes a variety + | of powerful log handlers and formatters that you're free to use. + | + | Available drivers: "single", "daily", "slack", "syslog", + | "errorlog", "monolog", "custom", "stack" + | + */ + + 'channels' => [ + + 'stack' => [ + 'driver' => 'stack', + 'channels' => explode(',', env('LOG_STACK', 'single')), + 'ignore_exceptions' => false, + ], + + 'single' => [ + 'driver' => 'single', + 'path' => storage_path('logs/laravel.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'replace_placeholders' => true, + ], + + 'daily' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/laravel.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'days' => env('LOG_DAILY_DAYS', 14), + 'replace_placeholders' => true, + ], + + 'slack' => [ + 'driver' => 'slack', + 'url' => env('LOG_SLACK_WEBHOOK_URL'), + 'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'), + 'emoji' => env('LOG_SLACK_EMOJI', ':boom:'), + 'level' => env('LOG_LEVEL', 'critical'), + 'replace_placeholders' => true, + ], + + 'papertrail' => [ + 'driver' => 'monolog', + 'level' => env('LOG_LEVEL', 'debug'), + 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class), + 'handler_with' => [ + 'host' => env('PAPERTRAIL_URL'), + 'port' => env('PAPERTRAIL_PORT'), + 'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'), + ], + 'processors' => [PsrLogMessageProcessor::class], + ], + + 'stderr' => [ + 'driver' => 'monolog', + 'level' => env('LOG_LEVEL', 'debug'), + 'handler' => StreamHandler::class, + 'handler_with' => [ + 'stream' => 'php://stderr', + ], + 'formatter' => env('LOG_STDERR_FORMATTER'), + 'processors' => [PsrLogMessageProcessor::class], + ], + + 'syslog' => [ + 'driver' => 'syslog', + 'level' => env('LOG_LEVEL', 'debug'), + 'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER), + 'replace_placeholders' => true, + ], + + 'errorlog' => [ + 'driver' => 'errorlog', + 'level' => env('LOG_LEVEL', 'debug'), + 'replace_placeholders' => true, + ], + + 'null' => [ + 'driver' => 'monolog', + 'handler' => NullHandler::class, + ], + + 'emergency' => [ + 'path' => storage_path('logs/laravel.log'), + ], + + ], + +]; diff --git a/config/mail.php b/config/mail.php new file mode 100644 index 0000000..0034532 --- /dev/null +++ b/config/mail.php @@ -0,0 +1,118 @@ + env('MAIL_MAILER', 'log'), + + /* + |-------------------------------------------------------------------------- + | Mailer Configurations + |-------------------------------------------------------------------------- + | + | Here you may configure all of the mailers used by your application plus + | their respective settings. Several examples have been configured for + | you and you are free to add your own as your application requires. + | + | Laravel supports a variety of mail "transport" drivers that can be used + | when delivering an email. You may specify which one you're using for + | your mailers below. You may also add additional mailers if needed. + | + | Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2", + | "postmark", "resend", "log", "array", + | "failover", "roundrobin" + | + */ + + 'mailers' => [ + + 'smtp' => [ + 'transport' => 'smtp', + 'scheme' => env('MAIL_SCHEME'), + 'url' => env('MAIL_URL'), + 'host' => env('MAIL_HOST', '127.0.0.1'), + 'port' => env('MAIL_PORT', 2525), + 'username' => env('MAIL_USERNAME'), + 'password' => env('MAIL_PASSWORD'), + 'timeout' => null, + 'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url(env('APP_URL', 'http://localhost'), PHP_URL_HOST)), + ], + + 'ses' => [ + 'transport' => 'ses', + ], + + 'postmark' => [ + 'transport' => 'postmark', + // 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'), + // 'client' => [ + // 'timeout' => 5, + // ], + ], + + 'resend' => [ + 'transport' => 'resend', + ], + + 'sendmail' => [ + 'transport' => 'sendmail', + 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'), + ], + + 'log' => [ + 'transport' => 'log', + 'channel' => env('MAIL_LOG_CHANNEL'), + ], + + 'array' => [ + 'transport' => 'array', + ], + + 'failover' => [ + 'transport' => 'failover', + 'mailers' => [ + 'smtp', + 'log', + ], + 'retry_after' => 60, + ], + + 'roundrobin' => [ + 'transport' => 'roundrobin', + 'mailers' => [ + 'ses', + 'postmark', + ], + 'retry_after' => 60, + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Global "From" Address + |-------------------------------------------------------------------------- + | + | You may wish for all emails sent by your application to be sent from + | the same address. Here you may specify a name and address that is + | used globally for all emails that are sent by your application. + | + */ + + 'from' => [ + 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), + 'name' => env('MAIL_FROM_NAME', 'Example'), + ], + +]; diff --git a/config/queue.php b/config/queue.php new file mode 100644 index 0000000..8c7d4c2 --- /dev/null +++ b/config/queue.php @@ -0,0 +1,113 @@ + env('QUEUE_CONNECTION', 'database'), + + /* + |-------------------------------------------------------------------------- + | Queue Connections + |-------------------------------------------------------------------------- + | + | Here you may configure the connection options for every queue backend + | used by your application. An example configuration is provided for + | each backend supported by Laravel. You're also free to add more. + | + | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null" + | + */ + + 'connections' => [ + + 'sync' => [ + 'driver' => 'sync', + ], + + 'database' => [ + 'driver' => 'database', + 'connection' => env('DB_QUEUE_CONNECTION'), + 'table' => env('DB_QUEUE_TABLE', 'jobs'), + 'queue' => env('DB_QUEUE', 'default'), + 'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90), + 'after_commit' => false, + ], + + 'beanstalkd' => [ + 'driver' => 'beanstalkd', + 'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'), + 'queue' => env('BEANSTALKD_QUEUE', 'default'), + 'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90), + 'block_for' => 0, + 'after_commit' => false, + ], + + 'sqs' => [ + 'driver' => 'sqs', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), + 'queue' => env('SQS_QUEUE', 'default'), + 'suffix' => env('SQS_SUFFIX'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'after_commit' => false, + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => env('REDIS_QUEUE_CONNECTION', 'default'), + 'queue' => env('REDIS_QUEUE', 'default'), + 'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90), + 'block_for' => null, + 'after_commit' => false, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Job Batching + |-------------------------------------------------------------------------- + | + | The following options configure the database and table that store job + | batching information. These options can be updated to any database + | connection and table which has been defined by your application. + | + */ + + 'batching' => [ + 'database' => env('DB_CONNECTION', 'sqlite'), + 'table' => 'job_batches', + ], + + /* + |-------------------------------------------------------------------------- + | Failed Queue Jobs + |-------------------------------------------------------------------------- + | + | These options configure the behavior of failed queue job logging so you + | can control how and where failed jobs are stored. Laravel ships with + | support for storing failed jobs in a simple file or in a database. + | + | Supported drivers: "database-uuids", "dynamodb", "file", "null" + | + */ + + 'failed' => [ + 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), + 'database' => env('DB_CONNECTION', 'sqlite'), + 'table' => 'failed_jobs', + ], + +]; diff --git a/config/services.php b/config/services.php new file mode 100644 index 0000000..f6432a2 --- /dev/null +++ b/config/services.php @@ -0,0 +1,17 @@ + env('SESSION_DRIVER', 'database'), + + /* + |-------------------------------------------------------------------------- + | Session Lifetime + |-------------------------------------------------------------------------- + | + | Here you may specify the number of minutes that you wish the session + | to be allowed to remain idle before it expires. If you want them + | to expire immediately when the browser is closed then you may + | indicate that via the expire_on_close configuration option. + | + */ + + 'lifetime' => (int) env('SESSION_LIFETIME', 120), + + 'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false), + + /* + |-------------------------------------------------------------------------- + | Session Encryption + |-------------------------------------------------------------------------- + | + | This option allows you to easily specify that all of your session data + | should be encrypted before it's stored. All encryption is performed + | automatically by Laravel and you may use the session like normal. + | + */ + + 'encrypt' => env('SESSION_ENCRYPT', false), + + /* + |-------------------------------------------------------------------------- + | Session File Location + |-------------------------------------------------------------------------- + | + | When utilizing the "file" session driver, the session files are placed + | on disk. The default storage location is defined here; however, you + | are free to provide another location where they should be stored. + | + */ + + 'files' => storage_path('framework/sessions'), + + /* + |-------------------------------------------------------------------------- + | Session Database Connection + |-------------------------------------------------------------------------- + | + | When using the "database" or "redis" session drivers, you may specify a + | connection that should be used to manage these sessions. This should + | correspond to a connection in your database configuration options. + | + */ + + 'connection' => env('SESSION_CONNECTION'), + + /* + |-------------------------------------------------------------------------- + | Session Database Table + |-------------------------------------------------------------------------- + | + | When using the "database" session driver, you may specify the table to + | be used to store sessions. Of course, a sensible default is defined + | for you; however, you're welcome to change this to another table. + | + */ + + 'table' => env('SESSION_TABLE', 'sessions'), + + /* + |-------------------------------------------------------------------------- + | Session Cache Store + |-------------------------------------------------------------------------- + | + | When using one of the framework's cache driven session backends, you may + | define the cache store which should be used to store the session data + | between requests. This must match one of your defined cache stores. + | + | Affects: "dynamodb", "memcached", "redis" + | + */ + + 'store' => env('SESSION_STORE'), + + /* + |-------------------------------------------------------------------------- + | Session Sweeping Lottery + |-------------------------------------------------------------------------- + | + | Some session drivers must manually sweep their storage location to get + | rid of old sessions from storage. Here are the chances that it will + | happen on a given request. By default, the odds are 2 out of 100. + | + */ + + 'lottery' => [2, 100], + + /* + |-------------------------------------------------------------------------- + | Session Cookie Name + |-------------------------------------------------------------------------- + | + | Here you may change the name of the session cookie that is created by + | the framework. Typically, you should not need to change this value + | since doing so does not grant a meaningful security improvement. + | + */ + + 'cookie' => env( + 'SESSION_COOKIE', + Str::slug(env('APP_NAME', 'laravel'), '_').'_session' + ), + + /* + |-------------------------------------------------------------------------- + | Session Cookie Path + |-------------------------------------------------------------------------- + | + | The session cookie path determines the path for which the cookie will + | be regarded as available. Typically, this will be the root path of + | your application, but you're free to change this when necessary. + | + */ + + 'path' => env('SESSION_PATH', '/'), + + /* + |-------------------------------------------------------------------------- + | Session Cookie Domain + |-------------------------------------------------------------------------- + | + | This value determines the domain and subdomains the session cookie is + | available to. By default, the cookie will be available to the root + | domain and all subdomains. Typically, this shouldn't be changed. + | + */ + + 'domain' => env('SESSION_DOMAIN'), + + /* + |-------------------------------------------------------------------------- + | HTTPS Only Cookies + |-------------------------------------------------------------------------- + | + | By setting this option to true, session cookies will only be sent back + | to the server if the browser has a HTTPS connection. This will keep + | the cookie from being sent to you when it can't be done securely. + | + */ + + 'secure' => env('SESSION_SECURE_COOKIE'), + + /* + |-------------------------------------------------------------------------- + | HTTP Access Only + |-------------------------------------------------------------------------- + | + | Setting this value to true will prevent JavaScript from accessing the + | value of the cookie and the cookie will only be accessible through + | the HTTP protocol. It's unlikely you should disable this option. + | + */ + + 'http_only' => env('SESSION_HTTP_ONLY', true), + + /* + |-------------------------------------------------------------------------- + | Same-Site Cookies + |-------------------------------------------------------------------------- + | + | This option determines how your cookies behave when cross-site requests + | take place, and can be used to mitigate CSRF attacks. By default, we + | will set this value to "lax" to permit secure cross-site requests. + | + | See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value + | + | Supported: "lax", "strict", "none", null + | + */ + + 'same_site' => env('SESSION_SAME_SITE', 'lax'), + + /* + |-------------------------------------------------------------------------- + | Partitioned Cookies + |-------------------------------------------------------------------------- + | + | Setting this value to true will tie the cookie to the top-level site for + | a cross-site context. Partitioned cookies are accepted by the browser + | when flagged "secure" and the Same-Site attribute is set to "none". + | + */ + + 'partitioned' => env('SESSION_PARTITIONED_COOKIE', false), + +]; diff --git a/database/.gitignore b/database/.gitignore new file mode 100644 index 0000000..9b19b93 --- /dev/null +++ b/database/.gitignore @@ -0,0 +1 @@ +*.sqlite* diff --git a/database/migrations/0001_01_01_000000_create_users_table.php b/database/migrations/0001_01_01_000000_create_users_table.php new file mode 100644 index 0000000..388c09d --- /dev/null +++ b/database/migrations/0001_01_01_000000_create_users_table.php @@ -0,0 +1,31 @@ +uuid('id')->primary(); + $table->string('login')->unique(); + $table->string('password'); + $table->boolean('is_active')->default(true); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('users'); + } +}; diff --git a/database/migrations/0001_01_02_000000_create_jobs_table.php b/database/migrations/0001_01_02_000000_create_jobs_table.php new file mode 100644 index 0000000..6098d9b --- /dev/null +++ b/database/migrations/0001_01_02_000000_create_jobs_table.php @@ -0,0 +1,32 @@ +bigIncrements('id'); + $table->string('queue')->index(); + $table->longText('payload'); + $table->unsignedTinyInteger('attempts'); + $table->unsignedInteger('reserved_at')->nullable(); + $table->unsignedInteger('available_at'); + $table->unsignedInteger('created_at'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('jobs'); + } +}; diff --git a/database/migrations/0001_01_03_000000_create_failed_jobs_table.php b/database/migrations/0001_01_03_000000_create_failed_jobs_table.php new file mode 100644 index 0000000..249da81 --- /dev/null +++ b/database/migrations/0001_01_03_000000_create_failed_jobs_table.php @@ -0,0 +1,32 @@ +id(); + $table->string('uuid')->unique(); + $table->text('connection'); + $table->text('queue'); + $table->longText('payload'); + $table->longText('exception'); + $table->timestamp('failed_at')->useCurrent(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('failed_jobs'); + } +}; diff --git a/database/migrations/0001_01_04_000000_create_job_batches_table.php b/database/migrations/0001_01_04_000000_create_job_batches_table.php new file mode 100644 index 0000000..50e38c2 --- /dev/null +++ b/database/migrations/0001_01_04_000000_create_job_batches_table.php @@ -0,0 +1,35 @@ +string('id')->primary(); + $table->string('name'); + $table->integer('total_jobs'); + $table->integer('pending_jobs'); + $table->integer('failed_jobs'); + $table->longText('failed_job_ids'); + $table->mediumText('options')->nullable(); + $table->integer('cancelled_at')->nullable(); + $table->integer('created_at'); + $table->integer('finished_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('job_batches'); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php new file mode 100644 index 0000000..b22151a --- /dev/null +++ b/database/seeders/DatabaseSeeder.php @@ -0,0 +1,19 @@ +call([ + + ]); + } +} diff --git a/deploy/main/docker-compose.yaml b/deploy/main/docker-compose.yaml new file mode 100644 index 0000000..3df9475 --- /dev/null +++ b/deploy/main/docker-compose.yaml @@ -0,0 +1,68 @@ +name: project + +services: + php: + build: + context: ../../ + dockerfile: deploy/main/php.Dockerfile + container_name: project-php + depends_on: + - pgsql + volumes: + - project-cache-volume:/var/www/html/bootstrap/cache + - project-vendor-volume:/var/www/html/vendor + - project-storage-volume:/var/www/html/storage + - project-log-volume:/var/www/html/storage/logs + networks: + project-network: + aliases: + - project-php + restart: unless-stopped + + supervisor: + build: + context: ../../ + dockerfile: deploy/main/supervisor.Dockerfile + container_name: project-supervisor + depends_on: + - pgsql + - php + environment: + - REDIS_HOST=${REDIS_HOST} + - REDIS_PORT=${REDIS_PORT} + - DB_HOST=${DB_HOST} + - DB_PORT=${DB_PORT} + volumes: + - project-cache-volume:/var/www/html/bootstrap/cache + - project-vendor-volume:/var/www/html/vendor + - project-storage-volume:/var/www/html/storage + - project-log-volume:/var/www/html/storage/logs + networks: + - project-network + restart: unless-stopped + + pgsql: + image: postgres:17 + container_name: project-pgsql + environment: + - POSTGRES_DB=${DB_DATABASE} + - POSTGRES_USER=${DB_USERNAME} + - POSTGRES_PASSWORD=${DB_PASSWORD} + volumes: + - project-pgsql-volume:/var/lib/postgresql/data + networks: + - project-network + ports: + - "127.0.0.1:5432:5432" + restart: unless-stopped + +volumes: + project-cache-volume: + project-vendor-volume: + project-storage-volume: + project-log-volume: + project-pgsql-volume: + +networks: + project-network: + external: true diff --git a/deploy/main/etc/supervisor/conf.d/default-worker.conf b/deploy/main/etc/supervisor/conf.d/default-worker.conf new file mode 100644 index 0000000..fd76ce5 --- /dev/null +++ b/deploy/main/etc/supervisor/conf.d/default-worker.conf @@ -0,0 +1,11 @@ +[program:default-worker] +process_name=%(program_name)s_%(process_num)02d +command=php /var/www/html/artisan queue:work --queue=default --sleep=3 --tries=3 +autostart=true +autorestart=true +user=www-data +numprocs=4 +redirect_stderr=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stopwaitsecs=3600 diff --git a/deploy/main/php.Dockerfile b/deploy/main/php.Dockerfile new file mode 100644 index 0000000..87b0b70 --- /dev/null +++ b/deploy/main/php.Dockerfile @@ -0,0 +1,27 @@ +FROM php:8.3-fpm + +ENV MAKEFLAGS="-j$(nproc)" + +RUN apt-get update +RUN apt-get install -y git zip unzip curl \ + libicu-dev libzip-dev libcurl4-openssl-dev libxml2-dev libpq-dev libonig-dev + +RUN docker-php-ext-install intl zip bcmath xml pdo pdo_pgsql dom mbstring curl + +# Install xdebug and redis extensions if needed +# +# RUN pecl install xdebug redis-6.2.0 +# RUN docker-php-ext-enable redis + +COPY --from=composer /usr/bin/composer /usr/bin/composer + +# xDebug configuration +# +# COPY ./usr/local/etc/php/conf.d/* /usr/local/etc/php/conf.d/ + +WORKDIR /var/www/html + +COPY composer.* . +RUN composer install --no-dev + +COPY . . diff --git a/deploy/main/supervisor.Dockerfile b/deploy/main/supervisor.Dockerfile new file mode 100644 index 0000000..d94b6f5 --- /dev/null +++ b/deploy/main/supervisor.Dockerfile @@ -0,0 +1,39 @@ +FROM php:8.3-cli + +RUN apt-get update +RUN apt-get install -y git zip unzip curl netcat-openbsd supervisor\ + libicu-dev libzip-dev libcurl4-openssl-dev libxml2-dev libpq-dev libonig-dev + +RUN docker-php-ext-install intl zip bcmath xml pdo pdo_pgsql dom mbstring curl + +# Install xdebug and redis extensions if needed +# +#RUN pecl install xdebug redis-6.2.0 +#RUN docker-php-ext-enable redis + +COPY --from=composer /usr/bin/composer /usr/bin/composer + +# xDebug configuration +# +# COPY ./usr/local/etc/php/conf.d/* /usr/local/etc/php/conf.d/ + +COPY deploy/main/usr/local/bin/supervisor.entrypoint.sh /usr/local/bin/supervisor.entrypoint.sh + +# Set permissions to match host user (optional) +# +# RUN usermod -u 1000 www-data +# RUN groupmod -g 1000 www-data + +RUN chmod +x /usr/local/bin/supervisor.entrypoint.sh +RUN chown -R www-data:www-data /var/www/html + +WORKDIR /var/www/html + +COPY composer.* . +RUN composer install --no-dev + +COPY . . + +ENTRYPOINT ["/usr/local/bin/supervisor.entrypoint.sh"] +CMD ["-c", "/etc/supervisor/supervisord.conf", "-n"] + diff --git a/deploy/main/usr/local/bin/supervisor.entrypoint.sh b/deploy/main/usr/local/bin/supervisor.entrypoint.sh new file mode 100644 index 0000000..37a404c --- /dev/null +++ b/deploy/main/usr/local/bin/supervisor.entrypoint.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +echo "Waiting for redis startup..." + +while ! nc -z "${REDIS_HOST}" "${REDIS_PORT}"; do + sleep 1 +done + +echo "Redis has started!" + +echo "Waiting for database startup..." + +while ! nc -z "${DB_HOST}" "${DB_PORT}"; do + sleep 1 +done + +echo "Database has started!" + +echo "Running supervisor daemon..." + +exec /usr/bin/supervisord $@ diff --git a/deploy/main/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini b/deploy/main/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini new file mode 100644 index 0000000..4459bb7 --- /dev/null +++ b/deploy/main/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini @@ -0,0 +1,4 @@ +xdebug.mode=debug +xdebug.start_with_request=trigger +xdebug.client_host=host.docker.internal +xdebug.client_port=9003 diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..61c031c --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,33 @@ + + + + + tests/Unit + + + tests/Feature + + + + + app + + + + + + + + + + + + + + + + diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..b574a59 --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,25 @@ + + + Options -MultiViews -Indexes + + + RewriteEngine On + + # Handle Authorization Header + RewriteCond %{HTTP:Authorization} . + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + + # Handle X-XSRF-Token Header + RewriteCond %{HTTP:x-xsrf-token} . + RewriteRule .* - [E=HTTP_X_XSRF_TOKEN:%{HTTP:X-XSRF-Token}] + + # Redirect Trailing Slashes If Not A Folder... + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_URI} (.+)/$ + RewriteRule ^ %1 [L,R=301] + + # Send Requests To Front Controller... + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [L] + diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..160ac288f250401aeda1b2548389596c16c478cd GIT binary patch literal 417 wcmZQzU<5(|0R|vYV8~!$U=RbcG=LZ+qyWT>3=%*RJNWlg#yh001Y;WuK)l5 literal 0 HcmV?d00001 diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..ee8f07e --- /dev/null +++ b/public/index.php @@ -0,0 +1,20 @@ +handleRequest(Request::capture()); diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..eb05362 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: diff --git a/resources/views/blank.blade.php b/resources/views/blank.blade.php new file mode 100644 index 0000000..c6a81ce --- /dev/null +++ b/resources/views/blank.blade.php @@ -0,0 +1,14 @@ + + + + + + + Blank + + +
+

Blank

+
+ + diff --git a/storage/app/.gitignore b/storage/app/.gitignore new file mode 100644 index 0000000..fedb287 --- /dev/null +++ b/storage/app/.gitignore @@ -0,0 +1,4 @@ +* +!private/ +!public/ +!.gitignore diff --git a/storage/app/private/.gitignore b/storage/app/private/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/app/private/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/app/public/.gitignore b/storage/app/public/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/app/public/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/.gitignore b/storage/framework/.gitignore new file mode 100644 index 0000000..05c4471 --- /dev/null +++ b/storage/framework/.gitignore @@ -0,0 +1,9 @@ +compiled.php +config.php +down +events.scanned.php +maintenance.php +routes.php +routes.scanned.php +schedule-* +services.json diff --git a/storage/framework/cache/.gitignore b/storage/framework/cache/.gitignore new file mode 100644 index 0000000..01e4a6c --- /dev/null +++ b/storage/framework/cache/.gitignore @@ -0,0 +1,3 @@ +* +!data/ +!.gitignore diff --git a/storage/framework/cache/data/.gitignore b/storage/framework/cache/data/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/framework/cache/data/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/sessions/.gitignore b/storage/framework/sessions/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/framework/sessions/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/testing/.gitignore b/storage/framework/testing/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/framework/testing/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/views/.gitignore b/storage/framework/views/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/framework/views/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/logs/.gitignore b/storage/logs/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/logs/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php new file mode 100644 index 0000000..8364a84 --- /dev/null +++ b/tests/Feature/ExampleTest.php @@ -0,0 +1,19 @@ +get('/'); + + $response->assertStatus(200); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..fe1ffc2 --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,10 @@ +assertTrue(true); + } +}