commit e9f0a9ec2c03267aa65713190376eda89de4dcef Author: Nguyen Van Nguyen Date: Mon Apr 22 14:45:23 2024 +0700 first commit Signed-off-by: Nguyen Van Nguyen 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..7b49625 --- /dev/null +++ b/.env.example @@ -0,0 +1,64 @@ +APP_NAME=Laravel +APP_ENV=local +APP_KEY= +APP_DEBUG=true +APP_TIMEZONE=UTC +APP_URL=http://localhost + +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=single +LOG_DEPRECATIONS_CHANNEL=null +LOG_LEVEL=debug + +DB_CONNECTION=sqlite +# DB_HOST=127.0.0.1 +# DB_PORT=3306 +# DB_DATABASE=laravel +# DB_USERNAME=root +# DB_PASSWORD= + +SESSION_DRIVER=database +SESSION_LIFETIME=120 +SESSION_ENCRYPT=false +SESSION_PATH=/ +SESSION_DOMAIN=null + +BROADCAST_CONNECTION=log +FILESYSTEM_DISK=local +QUEUE_CONNECTION=database + +CACHE_STORE=database +CACHE_PREFIX= + +MEMCACHED_HOST=127.0.0.1 + +REDIS_CLIENT=phpredis +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 + +MAIL_MAILER=log +MAIL_HOST=127.0.0.1 +MAIL_PORT=2525 +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_ENCRYPTION=null +MAIL_FROM_ADDRESS="hello@example.com" +MAIL_FROM_NAME="${APP_NAME}" + +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_DEFAULT_REGION=us-east-1 +AWS_BUCKET= +AWS_USE_PATH_STYLE_ENDPOINT=false + +VITE_APP_NAME="${APP_NAME}" diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..fcb21d3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +* text=auto eol=lf + +*.blade.php diff=html +*.css diff=css +*.html diff=html +*.md diff=markdown +*.php diff=php + +/.github export-ignore +CHANGELOG.md export-ignore +.styleci.yml export-ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7467ea6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +/.phpunit.cache +/node_modules +/public/build +/public/hot +/public/storage +/public/css +/public/js +/storage/*.key +/storage/database +/vendor +.env +.env.backup +.env.production +.phpunit.result.cache +Homestead.json +Homestead.yaml +auth.json +npm-debug.log +yarn-error.log +/.fleet +/.idea +/.vscode +docker* +composer.phar +composer.lock diff --git a/README.md b/README.md new file mode 100644 index 0000000..5a14369 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +Zimbra Multi-Tenancy Admin Panel +-------------------------------- + +Zimbra Multi-Tenancy Admin Panel diff --git a/app/Enums/AccountStatus.php b/app/Enums/AccountStatus.php new file mode 100644 index 0000000..c14736b --- /dev/null +++ b/app/Enums/AccountStatus.php @@ -0,0 +1,39 @@ + __('Active'), + self::Maintenance => __('Maintenance'), + self::Locked => __('Locked'), + self::Closed => __('Closed'), + self::Lockout => __('Lock Out'), + self::Pending => __('Pending'), + }; + } +} diff --git a/app/Enums/DomainStatus.php b/app/Enums/DomainStatus.php new file mode 100644 index 0000000..4cbc3ff --- /dev/null +++ b/app/Enums/DomainStatus.php @@ -0,0 +1,39 @@ + __('Active'), + self::Maintenance => __('Maintenance'), + self::Locked => __('Locked'), + self::Closed => __('Closed'), + self::Suspended => __('Suspended'), + self::Shutdown => __('Shutdown'), + }; + } +} diff --git a/app/Enums/UserRole.php b/app/Enums/UserRole.php new file mode 100644 index 0000000..8807acf --- /dev/null +++ b/app/Enums/UserRole.php @@ -0,0 +1,36 @@ + __('Administrator'), + self::Agency => __('Agency'), + self::DomainManager => __('Domain Manager'), + self::GroupManager => __('Group Manager'), + }; + } +} diff --git a/app/Filament/Pages/ManageZimbraSettings.php b/app/Filament/Pages/ManageZimbraSettings.php new file mode 100644 index 0000000..93159f0 --- /dev/null +++ b/app/Filament/Pages/ManageZimbraSettings.php @@ -0,0 +1,37 @@ +schema([ + TextInput::make('serviceUrl') + ->url()->required()->label(__('Service Url')), + TextInput::make('adminUser') + ->required()->label(__('Admin User')), + TextInput::make('adminPassword') + ->password()->required()->label(__('Admin Password')), + ]); + } +} diff --git a/app/Filament/Resources/AdminResource.php b/app/Filament/Resources/AdminResource.php new file mode 100644 index 0000000..919ac6f --- /dev/null +++ b/app/Filament/Resources/AdminResource.php @@ -0,0 +1,26 @@ +user()->isAdministrator(); + } +} diff --git a/app/Filament/Resources/AgencyResource.php b/app/Filament/Resources/AgencyResource.php new file mode 100644 index 0000000..3a588db --- /dev/null +++ b/app/Filament/Resources/AgencyResource.php @@ -0,0 +1,64 @@ +schema([ + // + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + // + ]) + ->filters([ + // + ]) + ->actions([ + Tables\Actions\EditAction::make(), + ]) + ->bulkActions([ + Tables\Actions\BulkActionGroup::make([ + Tables\Actions\DeleteBulkAction::make(), + ]), + ]); + } + + public static function getRelations(): array + { + return [ + // + ]; + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListAgencies::route('/'), + 'create' => Pages\CreateAgency::route('/create'), + 'edit' => Pages\EditAgency::route('/{record}/edit'), + ]; + } +} diff --git a/app/Filament/Resources/AgencyResource/Pages/CreateAgency.php b/app/Filament/Resources/AgencyResource/Pages/CreateAgency.php new file mode 100644 index 0000000..50d5a90 --- /dev/null +++ b/app/Filament/Resources/AgencyResource/Pages/CreateAgency.php @@ -0,0 +1,12 @@ +schema([ + // + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + // + ]) + ->filters([ + // + ]) + ->actions([ + Tables\Actions\EditAction::make(), + ]) + ->bulkActions([ + Tables\Actions\BulkActionGroup::make([ + Tables\Actions\DeleteBulkAction::make(), + ]), + ]); + } + + public static function getRelations(): array + { + return [ + // + ]; + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListClassOfServices::route('/'), + 'create' => Pages\CreateClassOfService::route('/create'), + 'edit' => Pages\EditClassOfService::route('/{record}/edit'), + ]; + } +} diff --git a/app/Filament/Resources/ClassOfServiceResource/Pages/CreateClassOfService.php b/app/Filament/Resources/ClassOfServiceResource/Pages/CreateClassOfService.php new file mode 100644 index 0000000..277c435 --- /dev/null +++ b/app/Filament/Resources/ClassOfServiceResource/Pages/CreateClassOfService.php @@ -0,0 +1,12 @@ +schema([ + // + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + // + ]) + ->filters([ + // + ]) + ->actions([ + Tables\Actions\EditAction::make(), + ]) + ->bulkActions([ + Tables\Actions\BulkActionGroup::make([ + Tables\Actions\DeleteBulkAction::make(), + ]), + ]); + } + + public static function getRelations(): array + { + return [ + // + ]; + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListMailHosts::route('/'), + 'create' => Pages\CreateMailHost::route('/create'), + 'edit' => Pages\EditMailHost::route('/{record}/edit'), + ]; + } +} diff --git a/app/Filament/Resources/MailHostResource/Pages/CreateMailHost.php b/app/Filament/Resources/MailHostResource/Pages/CreateMailHost.php new file mode 100644 index 0000000..082277d --- /dev/null +++ b/app/Filament/Resources/MailHostResource/Pages/CreateMailHost.php @@ -0,0 +1,12 @@ + Pages\ListUsers::route('/'), + 'create' => Pages\CreateUser::route('/create'), + 'edit' => Pages\EditUser::route('/{record}/edit'), + ]; + } +} diff --git a/app/Filament/Resources/UserResource/Pages/CreateUser.php b/app/Filament/Resources/UserResource/Pages/CreateUser.php new file mode 100644 index 0000000..9e068cd --- /dev/null +++ b/app/Filament/Resources/UserResource/Pages/CreateUser.php @@ -0,0 +1,73 @@ +schema([ + TextInput::make('name')->required()->label(__('Name')), + TextInput::make('email')->email()->required()->unique() + ->endsWith( + Domain::all()->pluck('name', 'name') + )->validationMessages([ + 'unique' => __('The email address has already been taken.'), + 'ends_with' => __('The email address does not belong to any domains.'), + ])->label(__('Email Address')), + TextInput::make('password')->password() + ->dehydrateStateUsing(fn ($state) => Hash::make($state)) + ->dehydrated(fn ($state) => filled($state)) + ->required()->label(__('Password')), + Select::make('role')->options( + UserRole::class + )->required()->label(__('Role')), + ]); + } + + protected function handleRecordCreation(array $data): Model + { + $user = parent::handleRecordCreation($data); + $user->assignRole($data['role']); + return $user; + } + + protected function getCreatedNotificationTitle(): ?string + { + return __('User has been created!'); + } + + protected function getRedirectUrl(): string + { + return static::getResource()::getUrl(); + } +} diff --git a/app/Filament/Resources/UserResource/Pages/EditUser.php b/app/Filament/Resources/UserResource/Pages/EditUser.php new file mode 100644 index 0000000..789b7c4 --- /dev/null +++ b/app/Filament/Resources/UserResource/Pages/EditUser.php @@ -0,0 +1,79 @@ +schema([ + TextInput::make('name')->required()->label(__('Name')), + TextInput::make('email')->readonly()->label(__('Email Address')), + TextInput::make('password')->password() + ->dehydrateStateUsing(fn ($state) => Hash::make($state)) + ->dehydrated(fn ($state) => filled($state)) + ->required(false)->label(__('Password')), + Select::make('role')->options( + UserRole::class + )->hidden( + $this->record->isSupperAdmin() + )->required()->label(__('Role')), + ]); + } + + protected function mutateFormDataBeforeFill(array $data): array + { + $data['role'] = $this->record->getRoleNames()->first(); + return $data; + } + + protected function handleRecordUpdate(Model $record, array $data): Model + { + $assignedRole = $record->getRoleNames()->first(); + $role = $data['role'] ?? ''; + if (empty($assignedRole)) { + $record->assignRole($role); + } + elseif (!empty($role) && $role !== $assignedRole) { + $record->removeRole($assignedRole); + $record->assignRole($role); + } + return parent::handleRecordUpdate($record, $data); + } + + protected function getSavedNotificationTitle(): ?string + { + return __('User has been saved!'); + } + + protected function getRedirectUrl(): string + { + return static::getResource()::getUrl(); + } +} diff --git a/app/Filament/Resources/UserResource/Pages/ListUsers.php b/app/Filament/Resources/UserResource/Pages/ListUsers.php new file mode 100644 index 0000000..6425fd4 --- /dev/null +++ b/app/Filament/Resources/UserResource/Pages/ListUsers.php @@ -0,0 +1,46 @@ +columns([ + TextColumn::make('name')->label(__('Name')), + TextColumn::make('email')->label(__('Email')), + TextColumn::make('created_at')->dateTime()->sortable()->label(__('Created At')), + ])->actions([ + EditAction::make(), + ]); + } + + protected function getHeaderActions(): array + { + return [ + CreateAction::make()->label(__('New User')), + ]; + } +} diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php new file mode 100644 index 0000000..8677cd5 --- /dev/null +++ b/app/Http/Controllers/Controller.php @@ -0,0 +1,8 @@ + + */ + protected $fillable = [ + 'agency_id', + 'domain_id', + 'cos_id', + 'zimbra_cos_id', + 'zimbra_id', + 'name', + 'status', + 'mail_host', + 'display_name', + 'title', + 'organization', + 'organization_unit', + 'telephone', + 'mobile', + 'address', + 'zimbra_create', + 'description', + 'attributes', + 'created_by', + 'updated_by', + ]; +} diff --git a/app/Models/Agency.php b/app/Models/Agency.php new file mode 100644 index 0000000..2576fea --- /dev/null +++ b/app/Models/Agency.php @@ -0,0 +1,88 @@ + + */ + protected $fillable = [ + 'name', + 'email', + 'telephone', + 'mobile', + 'address', + 'organization', + 'description', + 'created_by', + 'updated_by', + ]; + + /** + * Get the members for the agency. + */ + public function members(): HasManyThrough + { + return $this->hasManyThrough( + User::class, + AgencyMember::class, + 'agency_id', + 'user_id', + ); + } + + /** + * Get the mail hosts for the agency. + */ + public function mailHosts(): HasManyThrough + { + return $this->hasManyThrough( + MailHost::class, + AgencyMailHost::class, + 'agency_id', + 'mail_host_id', + ); + } + + /** + * Get the coses for the agency. + */ + public function coses(): HasManyThrough + { + return $this->hasManyThrough( + ClassOfService::class, + AgencyCos::class, + 'agency_id', + 'cos_id', + ); + } +} diff --git a/app/Models/AgencyCos.php b/app/Models/AgencyCos.php new file mode 100644 index 0000000..7c60988 --- /dev/null +++ b/app/Models/AgencyCos.php @@ -0,0 +1,48 @@ + + */ + protected $fillable = [ + 'agency_id', + 'cos_id', + ]; +} diff --git a/app/Models/AgencyMailHost.php b/app/Models/AgencyMailHost.php new file mode 100644 index 0000000..bcb9bbf --- /dev/null +++ b/app/Models/AgencyMailHost.php @@ -0,0 +1,48 @@ + + */ + protected $fillable = [ + 'agency_id', + 'mail_host_id', + ]; +} diff --git a/app/Models/AgencyMember.php b/app/Models/AgencyMember.php new file mode 100644 index 0000000..a9d82ee --- /dev/null +++ b/app/Models/AgencyMember.php @@ -0,0 +1,48 @@ + + */ + protected $fillable = [ + 'agency_id', + 'user_id', + ]; +} diff --git a/app/Models/Alias.php b/app/Models/Alias.php new file mode 100644 index 0000000..f9ec16c --- /dev/null +++ b/app/Models/Alias.php @@ -0,0 +1,47 @@ + + */ + protected $fillable = [ + 'agency_id', + 'domain_id', + 'account_id', + 'name', + 'zimbra_target_id', + 'zimbra_create', + 'created_by', + 'updated_by', + ]; +} diff --git a/app/Models/ClassOfService.php b/app/Models/ClassOfService.php new file mode 100644 index 0000000..8e059e2 --- /dev/null +++ b/app/Models/ClassOfService.php @@ -0,0 +1,48 @@ + + */ + protected $fillable = [ + 'zimbra_id', + 'name', + 'mail_quota', + 'max_accounts', + 'zimbra_create', + 'description', + 'attributes', + 'created_by', + 'updated_by', + ]; +} diff --git a/app/Models/DistributionList.php b/app/Models/DistributionList.php new file mode 100644 index 0000000..626fb4b --- /dev/null +++ b/app/Models/DistributionList.php @@ -0,0 +1,92 @@ + + */ + protected $fillable = [ + 'agency_id', + 'domain_id', + 'user_id', + 'zimbra_id', + 'name', + 'display_name', + 'total_members', + 'zimbra_create', + 'description', + 'attributes', + 'created_by', + 'updated_by', + ]; + + /** + * Get the distribution list's parent. + */ + public function parent(): HasOneThrough + { + return $this->hasOneThrough( + DistributionList::class, + DlistHierarchy::class, + 'dlist_id', + 'parent_id', + ); + } + + /** + * Get the children for the distribution list. + */ + public function children(): HasManyThrough + { + return $this->hasManyThrough( + DistributionList::class, + DlistHierarchy::class, + 'parent_id', + 'dlist_id', + ); + } + + /** + * Get the members for the distribution list. + */ + public function members(): HasManyThrough + { + return $this->hasManyThrough( + Account::class, + DlistMember::class, + 'dlist_id', + 'account_id', + ); + } +} diff --git a/app/Models/DlistHierarchy.php b/app/Models/DlistHierarchy.php new file mode 100644 index 0000000..5474a79 --- /dev/null +++ b/app/Models/DlistHierarchy.php @@ -0,0 +1,48 @@ + + */ + protected $fillable = [ + 'dlist_id', + 'parent_id', + ]; +} diff --git a/app/Models/DlistMember.php b/app/Models/DlistMember.php new file mode 100644 index 0000000..aeed160 --- /dev/null +++ b/app/Models/DlistMember.php @@ -0,0 +1,48 @@ + + */ + protected $fillable = [ + 'dlist_id', + 'account_id', + ]; +} diff --git a/app/Models/Domain.php b/app/Models/Domain.php new file mode 100644 index 0000000..ee0971c --- /dev/null +++ b/app/Models/Domain.php @@ -0,0 +1,65 @@ + + */ + protected $fillable = [ + 'agency_id', + 'user_id', + 'zimbra_id', + 'name', + 'status', + 'max_accounts', + 'total_accounts', + 'zimbra_create', + 'description', + 'attributes', + 'created_by', + 'updated_by', + ]; + + /** + * Get the coses for the domain. + */ + public function coses(): HasManyThrough + { + return $this->hasManyThrough( + ClassOfService::class, + DomainCos::class, + 'domain_id', + 'cos_id', + ); + } +} diff --git a/app/Models/DomainCos.php b/app/Models/DomainCos.php new file mode 100644 index 0000000..dd4172d --- /dev/null +++ b/app/Models/DomainCos.php @@ -0,0 +1,48 @@ + + */ + protected $fillable = [ + 'domain_id', + 'cos_id', + ]; +} diff --git a/app/Models/MailHost.php b/app/Models/MailHost.php new file mode 100644 index 0000000..852f8ba --- /dev/null +++ b/app/Models/MailHost.php @@ -0,0 +1,44 @@ + + */ + protected $fillable = [ + 'zimbra_id', + 'name', + 'zimbra_create', + 'attributes', + 'created_by', + ]; +} diff --git a/app/Models/User.php b/app/Models/User.php new file mode 100644 index 0000000..e2b05a5 --- /dev/null +++ b/app/Models/User.php @@ -0,0 +1,109 @@ + + */ + protected $fillable = [ + 'name', + 'email', + 'password', + ]; + + /** + * The attributes that should be hidden for serialization. + * + * @var array + */ + protected $hidden = [ + 'password', + 'remember_token', + ]; + + /** + * Get the attributes that should be cast. + * + * @return array + */ + protected function casts(): array + { + return [ + 'email_verified_at' => 'datetime', + 'password' => 'hashed', + ]; + } + + /** + * User can access panel in production. + * + * @param Panel $panel + * @return bool + */ + public function canAccessPanel(Panel $panel): bool + { + if ($this->isAdministrator()) { + return true; + } + return $this->hasAnyRole([ + UserRole::Agency, + UserRole::DomainManager, + UserRole::GroupManager, + ]); + } + + /** + * User is administrator. + * + * @return bool + */ + public function isAdministrator(): bool + { + return $this->isSupperAdmin() + || $this->hasRole(UserRole::Administrator); + } + + /** + * User is supper admin. + * + * @return bool + */ + public function isSupperAdmin(): bool + { + return $this->id === 1; + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php new file mode 100644 index 0000000..935cf6c --- /dev/null +++ b/app/Providers/AppServiceProvider.php @@ -0,0 +1,40 @@ +default() + ->id('admin') + ->path('admin') + ->login() + ->colors([ + 'primary' => Color::Amber, + ]) + ->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources') + ->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages') + ->pages([ + Pages\Dashboard::class, + ]) + ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets') + ->widgets([ + Widgets\AccountWidget::class, + Widgets\FilamentInfoWidget::class, + ]) + ->middleware([ + EncryptCookies::class, + AddQueuedCookiesToResponse::class, + StartSession::class, + AuthenticateSession::class, + ShareErrorsFromSession::class, + VerifyCsrfToken::class, + SubstituteBindings::class, + DisableBladeIconComponents::class, + DispatchServingFilamentEvent::class, + ]) + ->authMiddleware([ + Authenticate::class, + ]); + } +} diff --git a/app/Settings/PanelSettings.php b/app/Settings/PanelSettings.php new file mode 100644 index 0000000..f3bdd27 --- /dev/null +++ b/app/Settings/PanelSettings.php @@ -0,0 +1,20 @@ +handleCommand(new ArgvInput); + +exit($status); diff --git a/bootstrap/app.php b/bootstrap/app.php new file mode 100644 index 0000000..7b162da --- /dev/null +++ b/bootstrap/app.php @@ -0,0 +1,18 @@ +withRouting( + web: __DIR__.'/../routes/web.php', + commands: __DIR__.'/../routes/console.php', + health: '/up', + ) + ->withMiddleware(function (Middleware $middleware) { + // + }) + ->withExceptions(function (Exceptions $exceptions) { + // + })->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..22744d1 --- /dev/null +++ b/bootstrap/providers.php @@ -0,0 +1,6 @@ + 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' => env('APP_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-GCM', + + '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..0ba5d5d --- /dev/null +++ b/config/auth.php @@ -0,0 +1,115 @@ + [ + '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::class), + ], + + // 'users' => [ + // 'driver' => 'database', + // 'table' => 'users', + // ], + ], + + /* + |-------------------------------------------------------------------------- + | 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 amount 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..3868091 --- /dev/null +++ b/config/cache.php @@ -0,0 +1,107 @@ + 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: "apc", "array", "database", "file", "memcached", + | "redis", "dynamodb", "octane", "null" + | + */ + + 'stores' => [ + + 'array' => [ + 'driver' => 'array', + 'serialize' => false, + ], + + 'database' => [ + 'driver' => 'database', + 'table' => env('DB_CACHE_TABLE', 'cache'), + 'connection' => env('DB_CACHE_CONNECTION'), + 'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'), + ], + + '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..f8e8dcb --- /dev/null +++ b/config/database.php @@ -0,0 +1,170 @@ + env('DB_CONNECTION', 'sqlite'), + + /* + |-------------------------------------------------------------------------- + | 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' => [ + + 'sqlite' => [ + 'driver' => 'sqlite', + 'url' => env('DB_URL'), + 'database' => env('DB_DATABASE', database_path('database.sqlite')), + 'prefix' => '', + 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), + ], + + 'mysql' => [ + 'driver' => 'mysql', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '3306'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => env('DB_CHARSET', 'utf8mb4'), + 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + ]) : [], + ], + + 'mariadb' => [ + 'driver' => 'mariadb', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '3306'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => env('DB_CHARSET', 'utf8mb4'), + 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + ]) : [], + ], + + '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', + ], + + 'sqlsrv' => [ + 'driver' => 'sqlsrv', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT', '1433'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => env('DB_CHARSET', 'utf8'), + 'prefix' => '', + 'prefix_indexes' => true, + // 'encrypt' => env('DB_ENCRYPT', 'yes'), + // 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'), + ], + + ], + + /* + |-------------------------------------------------------------------------- + | 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_'), + ], + + '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', '0'), + ], + + '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', '1'), + ], + + ], + +]; diff --git a/config/filament.php b/config/filament.php new file mode 100644 index 0000000..433549d --- /dev/null +++ b/config/filament.php @@ -0,0 +1,89 @@ + [ + + // 'echo' => [ + // 'broadcaster' => 'pusher', + // 'key' => env('VITE_PUSHER_APP_KEY'), + // 'cluster' => env('VITE_PUSHER_APP_CLUSTER'), + // 'wsHost' => env('VITE_PUSHER_HOST'), + // 'wsPort' => env('VITE_PUSHER_PORT'), + // 'wssPort' => env('VITE_PUSHER_PORT'), + // 'authEndpoint' => '/api/v1/broadcasting/auth', + // 'disableStats' => true, + // 'encrypted' => true, + // 'forceTLS' => true, + // ], + + ], + + /* + |-------------------------------------------------------------------------- + | Default Filesystem Disk + |-------------------------------------------------------------------------- + | + | This is the storage disk Filament will use to store files. You may use + | any of the disks defined in the `config/filesystems.php`. + | + */ + + 'default_filesystem_disk' => env('FILAMENT_FILESYSTEM_DISK', 'public'), + + /* + |-------------------------------------------------------------------------- + | Assets Path + |-------------------------------------------------------------------------- + | + | This is the directory where Filament's assets will be published to. It + | is relative to the `public` directory of your Laravel application. + | + | After changing the path, you should run `php artisan filament:assets`. + | + */ + + 'assets_path' => null, + + /* + |-------------------------------------------------------------------------- + | Cache Path + |-------------------------------------------------------------------------- + | + | This is the directory that Filament will use to store cache files that + | are used to optimize the registration of components. + | + | After changing the path, you should run `php artisan filament:cache-components`. + | + */ + + 'cache_path' => base_path('bootstrap/cache/filament'), + + /* + |-------------------------------------------------------------------------- + | Livewire Loading Delay + |-------------------------------------------------------------------------- + | + | This sets the delay before loading indicators appear. + | + | Setting this to 'none' makes indicators appear immediately, which can be + | desirable for high-latency connections. Setting it to 'default' applies + | Livewire's standard 200ms delay. + | + */ + + 'livewire_loading_delay' => 'default', + +]; diff --git a/config/filesystems.php b/config/filesystems.php new file mode 100644 index 0000000..44fe9c8 --- /dev/null +++ b/config/filesystems.php @@ -0,0 +1,76 @@ + 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'), + 'throw' => false, + ], + + 'public' => [ + 'driver' => 'local', + 'root' => storage_path('app/public'), + 'url' => env('APP_URL').'/storage', + 'visibility' => 'public', + 'throw' => 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, + ], + + ], + + /* + |-------------------------------------------------------------------------- + | 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..d526b64 --- /dev/null +++ b/config/logging.php @@ -0,0 +1,132 @@ + env('LOG_CHANNEL', 'stack'), + + /* + |-------------------------------------------------------------------------- + | 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, + 'formatter' => env('LOG_STDERR_FORMATTER'), + 'with' => [ + 'stream' => 'php://stderr', + ], + '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..8666659 --- /dev/null +++ b/config/mail.php @@ -0,0 +1,111 @@ + 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", "log", "array", "failover", "roundrobin" + | + */ + + 'mailers' => [ + + 'smtp' => [ + 'transport' => 'smtp', + 'url' => env('MAIL_URL'), + 'host' => env('MAIL_HOST', '127.0.0.1'), + 'port' => env('MAIL_PORT', 2525), + 'encryption' => env('MAIL_ENCRYPTION', 'tls'), + 'username' => env('MAIL_USERNAME'), + 'password' => env('MAIL_PASSWORD'), + 'timeout' => null, + 'local_domain' => env('MAIL_EHLO_DOMAIN'), + ], + + 'ses' => [ + 'transport' => 'ses', + ], + + 'postmark' => [ + 'transport' => 'postmark', + // 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'), + // 'client' => [ + // 'timeout' => 5, + // ], + ], + + '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', + ], + ], + + 'roundrobin' => [ + 'transport' => 'roundrobin', + 'mailers' => [ + 'ses', + 'postmark', + ], + ], + + ], + + /* + |-------------------------------------------------------------------------- + | 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/permission.php b/config/permission.php new file mode 100644 index 0000000..2a520f3 --- /dev/null +++ b/config/permission.php @@ -0,0 +1,186 @@ + [ + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * Eloquent model should be used to retrieve your permissions. Of course, it + * is often just the "Permission" model but you may use whatever you like. + * + * The model you want to use as a Permission model needs to implement the + * `Spatie\Permission\Contracts\Permission` contract. + */ + + 'permission' => Spatie\Permission\Models\Permission::class, + + /* + * When using the "HasRoles" trait from this package, we need to know which + * Eloquent model should be used to retrieve your roles. Of course, it + * is often just the "Role" model but you may use whatever you like. + * + * The model you want to use as a Role model needs to implement the + * `Spatie\Permission\Contracts\Role` contract. + */ + + 'role' => Spatie\Permission\Models\Role::class, + + ], + + 'table_names' => [ + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'roles' => 'roles', + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your permissions. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'permissions' => 'permissions', + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your models permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_permissions' => 'model_has_permissions', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your models roles. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_roles' => 'model_has_roles', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'role_has_permissions' => 'role_has_permissions', + ], + + 'column_names' => [ + /* + * Change this if you want to name the related pivots other than defaults + */ + 'role_pivot_key' => null, //default 'role_id', + 'permission_pivot_key' => null, //default 'permission_id', + + /* + * Change this if you want to name the related model primary key other than + * `model_id`. + * + * For example, this would be nice if your primary keys are all UUIDs. In + * that case, name this `model_uuid`. + */ + + 'model_morph_key' => 'model_id', + + /* + * Change this if you want to use the teams feature and your related model's + * foreign key is other than `team_id`. + */ + + 'team_foreign_key' => 'team_id', + ], + + /* + * When set to true, the method for checking permissions will be registered on the gate. + * Set this to false if you want to implement custom logic for checking permissions. + */ + + 'register_permission_check_method' => true, + + /* + * When set to true, Laravel\Octane\Events\OperationTerminated event listener will be registered + * this will refresh permissions on every TickTerminated, TaskTerminated and RequestTerminated + * NOTE: This should not be needed in most cases, but an Octane/Vapor combination benefited from it. + */ + 'register_octane_reset_listener' => false, + + /* + * Teams Feature. + * When set to true the package implements teams using the 'team_foreign_key'. + * If you want the migrations to register the 'team_foreign_key', you must + * set this to true before doing the migration. + * If you already did the migration then you must make a new migration to also + * add 'team_foreign_key' to 'roles', 'model_has_roles', and 'model_has_permissions' + * (view the latest version of this package's migration file) + */ + + 'teams' => false, + + /* + * Passport Client Credentials Grant + * When set to true the package will use Passports Client to check permissions + */ + + 'use_passport_client_credentials' => false, + + /* + * When set to true, the required permission names are added to exception messages. + * This could be considered an information leak in some contexts, so the default + * setting is false here for optimum safety. + */ + + 'display_permission_in_exception' => false, + + /* + * When set to true, the required role names are added to exception messages. + * This could be considered an information leak in some contexts, so the default + * setting is false here for optimum safety. + */ + + 'display_role_in_exception' => false, + + /* + * By default wildcard permission lookups are disabled. + * See documentation to understand supported syntax. + */ + + 'enable_wildcard_permission' => false, + + /* + * The class to use for interpreting wildcard permissions. + * If you need to modify delimiters, override the class and specify its name here. + */ + // 'permission.wildcard_permission' => Spatie\Permission\WildcardPermission::class, + + /* Cache-specific settings */ + + 'cache' => [ + + /* + * By default all permissions are cached for 24 hours to speed up performance. + * When permissions or roles are updated the cache is flushed automatically. + */ + + 'expiration_time' => \DateInterval::createFromDateString('24 hours'), + + /* + * The cache key used to store all permissions. + */ + + 'key' => 'spatie.permission.cache', + + /* + * You may optionally indicate a specific cache driver to use for permission and + * role caching using any of the `store` drivers listed in the cache.php config + * file. Using 'default' here means to use the `default` set in cache.php. + */ + + 'store' => 'default', + ], +]; diff --git a/config/queue.php b/config/queue.php new file mode 100644 index 0000000..116bd8d --- /dev/null +++ b/config/queue.php @@ -0,0 +1,112 @@ + 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..6bb68f6 --- /dev/null +++ b/config/services.php @@ -0,0 +1,34 @@ + [ + 'token' => env('POSTMARK_TOKEN'), + ], + + 'ses' => [ + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + ], + + 'slack' => [ + 'notifications' => [ + 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'), + 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'), + ], + ], + +]; diff --git a/config/session.php b/config/session.php new file mode 100644 index 0000000..f0b6541 --- /dev/null +++ b/config/session.php @@ -0,0 +1,217 @@ + 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' => 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: "apc", "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/config/settings.php b/config/settings.php new file mode 100644 index 0000000..5ccf245 --- /dev/null +++ b/config/settings.php @@ -0,0 +1,94 @@ + [ + + ], + + /* + * The path where the settings classes will be created. + */ + 'setting_class_path' => app_path('Settings'), + + /* + * In these directories settings migrations will be stored and ran when migrating. A settings + * migration created via the make:settings-migration command will be stored in the first path or + * a custom defined path when running the command. + */ + 'migrations_paths' => [ + database_path('settings'), + ], + + /* + * When no repository was set for a settings class the following repository + * will be used for loading and saving settings. + */ + 'default_repository' => 'database', + + /* + * Settings will be stored and loaded from these repositories. + */ + 'repositories' => [ + 'database' => [ + 'type' => Spatie\LaravelSettings\SettingsRepositories\DatabaseSettingsRepository::class, + 'model' => null, + 'table' => null, + 'connection' => null, + ], + 'redis' => [ + 'type' => Spatie\LaravelSettings\SettingsRepositories\RedisSettingsRepository::class, + 'connection' => null, + 'prefix' => null, + ], + ], + + /* + * The encoder and decoder will determine how settings are stored and + * retrieved in the database. By default, `json_encode` and `json_decode` + * are used. + */ + 'encoder' => null, + 'decoder' => null, + + /* + * The contents of settings classes can be cached through your application, + * settings will be stored within a provided Laravel store and can have an + * additional prefix. + */ + 'cache' => [ + 'enabled' => env('SETTINGS_CACHE_ENABLED', false), + 'store' => null, + 'prefix' => null, + 'ttl' => null, + ], + + /* + * These global casts will be automatically used whenever a property within + * your settings class isn't a default PHP type. + */ + 'global_casts' => [ + DateTimeInterface::class => Spatie\LaravelSettings\SettingsCasts\DateTimeInterfaceCast::class, + DateTimeZone::class => Spatie\LaravelSettings\SettingsCasts\DateTimeZoneCast::class, +// Spatie\DataTransferObject\DataTransferObject::class => Spatie\LaravelSettings\SettingsCasts\DtoCast::class, + Spatie\LaravelData\Data::class => Spatie\LaravelSettings\SettingsCasts\DataCast::class, + ], + + /* + * The package will look for settings in these paths and automatically + * register them. + */ + 'auto_discover_settings' => [ + app_path('Settings'), + ], + + /* + * Automatically discovered settings classes can be cached, so they don't + * need to be searched each time the application boots up. + */ + 'discovered_settings_cache_path' => base_path('bootstrap/cache'), +]; 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/factories/UserFactory.php b/database/factories/UserFactory.php new file mode 100644 index 0000000..584104c --- /dev/null +++ b/database/factories/UserFactory.php @@ -0,0 +1,44 @@ + + */ +class UserFactory extends Factory +{ + /** + * The current password being used by the factory. + */ + protected static ?string $password; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'name' => fake()->name(), + 'email' => fake()->unique()->safeEmail(), + 'email_verified_at' => now(), + 'password' => static::$password ??= Hash::make('password'), + 'remember_token' => Str::random(10), + ]; + } + + /** + * Indicate that the model's email address should be unverified. + */ + public function unverified(): static + { + return $this->state(fn (array $attributes) => [ + 'email_verified_at' => null, + ]); + } +} 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..05fb5d9 --- /dev/null +++ b/database/migrations/0001_01_01_000000_create_users_table.php @@ -0,0 +1,49 @@ +id(); + $table->string('name'); + $table->string('email')->unique(); + $table->timestamp('email_verified_at')->nullable(); + $table->string('password'); + $table->rememberToken(); + $table->timestamps(); + }); + + Schema::create('password_reset_tokens', function (Blueprint $table) { + $table->string('email')->primary(); + $table->string('token'); + $table->timestamp('created_at')->nullable(); + }); + + Schema::create('sessions', function (Blueprint $table) { + $table->string('id')->primary(); + $table->foreignId('user_id')->nullable()->index(); + $table->string('ip_address', 45)->nullable(); + $table->text('user_agent')->nullable(); + $table->longText('payload'); + $table->integer('last_activity')->index(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('users'); + Schema::dropIfExists('password_reset_tokens'); + Schema::dropIfExists('sessions'); + } +}; diff --git a/database/migrations/0001_01_01_000001_create_cache_table.php b/database/migrations/0001_01_01_000001_create_cache_table.php new file mode 100644 index 0000000..b9c106b --- /dev/null +++ b/database/migrations/0001_01_01_000001_create_cache_table.php @@ -0,0 +1,35 @@ +string('key')->primary(); + $table->mediumText('value'); + $table->integer('expiration'); + }); + + Schema::create('cache_locks', function (Blueprint $table) { + $table->string('key')->primary(); + $table->string('owner'); + $table->integer('expiration'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('cache'); + Schema::dropIfExists('cache_locks'); + } +}; diff --git a/database/migrations/0001_01_01_000002_create_jobs_table.php b/database/migrations/0001_01_01_000002_create_jobs_table.php new file mode 100644 index 0000000..425e705 --- /dev/null +++ b/database/migrations/0001_01_01_000002_create_jobs_table.php @@ -0,0 +1,57 @@ +id(); + $table->string('queue')->index(); + $table->longText('payload'); + $table->unsignedTinyInteger('attempts'); + $table->unsignedInteger('reserved_at')->nullable(); + $table->unsignedInteger('available_at'); + $table->unsignedInteger('created_at'); + }); + + Schema::create('job_batches', function (Blueprint $table) { + $table->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(); + }); + + Schema::create('failed_jobs', function (Blueprint $table) { + $table->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('jobs'); + Schema::dropIfExists('job_batches'); + Schema::dropIfExists('failed_jobs'); + } +}; diff --git a/database/migrations/2022_12_14_083707_create_settings_table.php b/database/migrations/2022_12_14_083707_create_settings_table.php new file mode 100644 index 0000000..9b14b86 --- /dev/null +++ b/database/migrations/2022_12_14_083707_create_settings_table.php @@ -0,0 +1,24 @@ +id(); + + $table->string('group'); + $table->string('name'); + $table->boolean('locked')->default(false); + $table->json('payload'); + + $table->timestamps(); + + $table->unique(['group', 'name']); + }); + } +}; diff --git a/database/migrations/2024_04_15_031849_create_permission_tables.php b/database/migrations/2024_04_15_031849_create_permission_tables.php new file mode 100644 index 0000000..b865d48 --- /dev/null +++ b/database/migrations/2024_04_15_031849_create_permission_tables.php @@ -0,0 +1,138 @@ +bigIncrements('id'); // permission id + $table->string('name'); // For MySQL 8.0 use string('name', 125); + $table->string('guard_name'); // For MySQL 8.0 use string('guard_name', 125); + $table->timestamps(); + + $table->unique(['name', 'guard_name']); + }); + + Schema::create($tableNames['roles'], function (Blueprint $table) use ($teams, $columnNames) { + $table->bigIncrements('id'); // role id + if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing + $table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable(); + $table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index'); + } + $table->string('name'); // For MySQL 8.0 use string('name', 125); + $table->string('guard_name'); // For MySQL 8.0 use string('guard_name', 125); + $table->timestamps(); + if ($teams || config('permission.testing')) { + $table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']); + } else { + $table->unique(['name', 'guard_name']); + } + }); + + Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) { + $table->unsignedBigInteger($pivotPermission); + + $table->string('model_type'); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index'); + + $table->foreign($pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->onDelete('cascade'); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index'); + + $table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } else { + $table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } + + }); + + Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) { + $table->unsignedBigInteger($pivotRole); + + $table->string('model_type'); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index'); + + $table->foreign($pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->onDelete('cascade'); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index'); + + $table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } else { + $table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } + }); + + Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) { + $table->unsignedBigInteger($pivotPermission); + $table->unsignedBigInteger($pivotRole); + + $table->foreign($pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->onDelete('cascade'); + + $table->foreign($pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->onDelete('cascade'); + + $table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary'); + }); + + app('cache') + ->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null) + ->forget(config('permission.cache.key')); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + $tableNames = config('permission.table_names'); + + if (empty($tableNames)) { + throw new \Exception('Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.'); + } + + Schema::drop($tableNames['role_has_permissions']); + Schema::drop($tableNames['model_has_roles']); + Schema::drop($tableNames['model_has_permissions']); + Schema::drop($tableNames['roles']); + Schema::drop($tableNames['permissions']); + } +}; diff --git a/database/migrations/2024_04_15_032849_create_user_roles.php b/database/migrations/2024_04_15_032849_create_user_roles.php new file mode 100644 index 0000000..a8eecd9 --- /dev/null +++ b/database/migrations/2024_04_15_032849_create_user_roles.php @@ -0,0 +1,22 @@ +findOrCreate($role->value, 'web'); + } + } + } +}; diff --git a/database/migrations/2024_04_15_034100_create_agencies_table.php b/database/migrations/2024_04_15_034100_create_agencies_table.php new file mode 100644 index 0000000..7a07d3d --- /dev/null +++ b/database/migrations/2024_04_15_034100_create_agencies_table.php @@ -0,0 +1,57 @@ +id(); + $table->string('name'); + $table->string('email'); + $table->string('telephone')->nullable(); + $table->string('mobile')->nullable(); + $table->text('address')->nullable(); + $table->string('organization')->nullable(); + $table->text('description')->nullable(); + $table->unsignedInteger('created_by')->default(0)->index(); + $table->unsignedInteger('updated_by')->default(0)->index(); + $table->timestamps(); + }); + + Schema::create('agency_members', function (Blueprint $table) { + $table->id(); + $table->foreignId('agency_id')->default(0)->index(); + $table->unsignedInteger('user_id')->default(0); + }); + + Schema::create('agency_mail_hosts', function (Blueprint $table) { + $table->id(); + $table->foreignId('agency_id')->default(0)->index(); + $table->unsignedInteger('mail_host_id')->default(0); + }); + + Schema::create('agency_coses', function (Blueprint $table) { + $table->id(); + $table->foreignId('agency_id')->default(0)->index(); + $table->unsignedInteger('cos_id')->default(0); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('agency_coses'); + Schema::dropIfExists('agency_mail_hosts'); + Schema::dropIfExists('agency_members'); + Schema::dropIfExists('agencies'); + } +}; diff --git a/database/migrations/2024_04_15_034110_create_mail_hosts_table.php b/database/migrations/2024_04_15_034110_create_mail_hosts_table.php new file mode 100644 index 0000000..a13be93 --- /dev/null +++ b/database/migrations/2024_04_15_034110_create_mail_hosts_table.php @@ -0,0 +1,32 @@ +id(); + $table->string('zimbra_id')->unique(); + $table->string('name')->unique(); + $table->timestamp('zimbra_create')->nullable(); + $table->json('attributes')->nullable(); + $table->unsignedInteger('created_by')->default(0)->index(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('mail_hosts'); + } +}; diff --git a/database/migrations/2024_04_15_034120_create_class_of_services_table.php b/database/migrations/2024_04_15_034120_create_class_of_services_table.php new file mode 100644 index 0000000..0187bf7 --- /dev/null +++ b/database/migrations/2024_04_15_034120_create_class_of_services_table.php @@ -0,0 +1,36 @@ +id(); + $table->string('zimbra_id')->unique(); + $table->string('name'); + $table->unsignedInteger('mail_quota'); + $table->unsignedMediumInteger('max_accounts'); + $table->timestamp('zimbra_create')->nullable(); + $table->text('description')->nullable(); + $table->json('attributes')->nullable(); + $table->unsignedInteger('created_by')->default(0)->index(); + $table->unsignedInteger('updated_by')->default(0)->index(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('class_of_services'); + } +}; diff --git a/database/migrations/2024_04_15_034952_create_domains_table.php b/database/migrations/2024_04_15_034952_create_domains_table.php new file mode 100644 index 0000000..da82c46 --- /dev/null +++ b/database/migrations/2024_04_15_034952_create_domains_table.php @@ -0,0 +1,52 @@ +id(); + $table->foreignId('agency_id')->default(0)->index(); + $table->foreignId('user_id')->default(0)->index(); + $table->string('zimbra_id')->unique(); + $table->string('name')->unique(); + $table->enum('status', [ + 'active', + 'maintenance', + 'locked', + 'closed', + 'suspended', + 'shutdown', + ]); + $table->unsignedMediumInteger('max_accounts')->default(0); + $table->unsignedMediumInteger('total_accounts')->default(0); + $table->timestamp('zimbra_create')->nullable(); + $table->text('description')->nullable(); + $table->json('attributes')->nullable(); + $table->unsignedInteger('created_by')->default(0)->index(); + $table->unsignedInteger('updated_by')->default(0)->index(); + $table->timestamps(); + }); + + Schema::create('domain_coses', function (Blueprint $table) { + $table->id(); + $table->foreignId('domain_id')->default(0)->index(); + $table->unsignedInteger('cos_id'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('domains'); + } +}; diff --git a/database/migrations/2024_04_15_035002_create_accounts_table.php b/database/migrations/2024_04_15_035002_create_accounts_table.php new file mode 100644 index 0000000..6ad3bb8 --- /dev/null +++ b/database/migrations/2024_04_15_035002_create_accounts_table.php @@ -0,0 +1,68 @@ +id(); + $table->foreignId('agency_id')->default(0)->index(); + $table->foreignId('domain_id')->default(0)->index(); + $table->foreignId('cos_id')->default(0)->index(); + $table->string('zimbra_cos_id')->nullable(); + $table->string('zimbra_id')->unique(); + $table->string('name')->unique(); + $table->enum('status', [ + 'active', + 'maintenance', + 'locked', + 'closed', + 'lockout', + 'pending', + ]); + $table->string('mail_host'); + $table->string('display_name'); + $table->string('title')->nullable(); + $table->string('organization')->nullable(); + $table->string('organization_unit')->nullable(); + $table->string('telephone')->nullable(); + $table->string('mobile')->nullable(); + $table->string('address')->nullable(); + $table->timestamp('zimbra_create')->nullable(); + $table->text('description')->nullable(); + $table->json('attributes')->nullable(); + $table->unsignedInteger('created_by')->default(0)->index(); + $table->unsignedInteger('updated_by')->default(0)->index(); + $table->timestamps(); + }); + + Schema::create('aliases', function (Blueprint $table) { + $table->id(); + $table->foreignId('agency_id')->default(0)->index(); + $table->foreignId('domain_id')->default(0)->index(); + $table->foreignId('account_id')->default(0)->index(); + $table->string('name')->unique(); + $table->string('zimbra_target_id'); + $table->timestamp('zimbra_create')->nullable(); + $table->unsignedInteger('created_by')->default(0)->index(); + $table->unsignedInteger('updated_by')->default(0)->index(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('aliases'); + Schema::dropIfExists('accounts'); + } +}; diff --git a/database/migrations/2024_04_15_035041_create_distribution_lists_table.php b/database/migrations/2024_04_15_035041_create_distribution_lists_table.php new file mode 100644 index 0000000..df54b1f --- /dev/null +++ b/database/migrations/2024_04_15_035041_create_distribution_lists_table.php @@ -0,0 +1,53 @@ +id(); + $table->foreignId('agency_id')->index(); + $table->foreignId('domain_id')->index(); + $table->foreignId('user_id')->default(0)->index(); + $table->string('zimbra_id')->unique(); + $table->string('name')->unique(); + $table->string('display_name'); + $table->unsignedMediumInteger('total_members')->default(0); + $table->timestamp('zimbra_create')->nullable(); + $table->text('description')->nullable(); + $table->json('attributes')->nullable(); + $table->unsignedInteger('created_by')->default(0)->index(); + $table->unsignedInteger('updated_by')->default(0)->index(); + $table->timestamps(); + }); + + Schema::create('dlist_hierarchy', function (Blueprint $table) { + $table->id(); + $table->foreignId('dlist_id')->index(); + $table->foreignId('parent_id')->default(0)->index(); + }); + + Schema::create('dlist_members', function (Blueprint $table) { + $table->id(); + $table->foreignId('dlist_id')->index(); + $table->foreignId('account_id')->default(0); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('dlist_members'); + Schema::dropIfExists('dlist_hierarchy'); + Schema::dropIfExists('distribution_lists'); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php new file mode 100644 index 0000000..d01a0ef --- /dev/null +++ b/database/seeders/DatabaseSeeder.php @@ -0,0 +1,23 @@ +create(); + + User::factory()->create([ + 'name' => 'Test User', + 'email' => 'test@example.com', + ]); + } +} diff --git a/database/settings/2024_04_16_025458_create_panel_settings.php b/database/settings/2024_04_16_025458_create_panel_settings.php new file mode 100644 index 0000000..b323f79 --- /dev/null +++ b/database/settings/2024_04_16_025458_create_panel_settings.php @@ -0,0 +1,16 @@ +migrator->add('panel.id', 'admin'); + $this->migrator->add('panel.path', 'admin'); + $this->migrator->add('panel.favicon', ''); + $this->migrator->add('panel.spaMode', false); + $this->migrator->add('panel.brandName', ''); + $this->migrator->add('panel.brandLogo', ''); + } +}; diff --git a/database/settings/2024_04_16_025540_create_zimbra_settings.php b/database/settings/2024_04_16_025540_create_zimbra_settings.php new file mode 100644 index 0000000..1e06338 --- /dev/null +++ b/database/settings/2024_04_16_025540_create_zimbra_settings.php @@ -0,0 +1,13 @@ +migrator->add('zimbra.serviceUrl', ''); + $this->migrator->add('zimbra.adminUser', ''); + $this->migrator->addEncrypted('zimbra.adminPassword', ''); + } +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..4e934ca --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build" + }, + "devDependencies": { + "axios": "^1.6.4", + "laravel-vite-plugin": "^1.0", + "vite": "^5.0" + } +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..506b9a3 --- /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..3aec5e2 --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,21 @@ + + + Options -MultiViews -Indexes + + + RewriteEngine On + + # Handle Authorization Header + RewriteCond %{HTTP:Authorization} . + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + + # 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 0000000..e69de29 diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..947d989 --- /dev/null +++ b/public/index.php @@ -0,0 +1,17 @@ +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/css/app.css b/resources/css/app.css new file mode 100644 index 0000000..e69de29 diff --git a/resources/js/app.js b/resources/js/app.js new file mode 100644 index 0000000..e59d6a0 --- /dev/null +++ b/resources/js/app.js @@ -0,0 +1 @@ +import './bootstrap'; diff --git a/resources/js/bootstrap.js b/resources/js/bootstrap.js new file mode 100644 index 0000000..5f1390b --- /dev/null +++ b/resources/js/bootstrap.js @@ -0,0 +1,4 @@ +import axios from 'axios'; +window.axios = axios; + +window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; diff --git a/resources/views/welcome.blade.php b/resources/views/welcome.blade.php new file mode 100644 index 0000000..a9898e3 --- /dev/null +++ b/resources/views/welcome.blade.php @@ -0,0 +1,172 @@ + + + + + + + Laravel + + + + + + + + + + + + diff --git a/routes/console.php b/routes/console.php new file mode 100644 index 0000000..eff2ed2 --- /dev/null +++ b/routes/console.php @@ -0,0 +1,8 @@ +comment(Inspiring::quote()); +})->purpose('Display an inspiring quote')->hourly(); diff --git a/routes/web.php b/routes/web.php new file mode 100644 index 0000000..86a06c5 --- /dev/null +++ b/routes/web.php @@ -0,0 +1,7 @@ +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); + } +} diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..421b569 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,11 @@ +import { defineConfig } from 'vite'; +import laravel from 'laravel-vite-plugin'; + +export default defineConfig({ + plugins: [ + laravel({ + input: ['resources/css/app.css', 'resources/js/app.js'], + refresh: true, + }), + ], +});