Enhanced admin panel

This commit is contained in:
Constantin Graf
2024-04-26 14:39:36 +02:00
committed by Constantin Graf
parent 42ad5e004a
commit 0bf8a25d64
15 changed files with 258 additions and 77 deletions

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Actions\Fortify; namespace App\Actions\Fortify;
use App\Enums\Role;
use App\Enums\Weekday; use App\Enums\Weekday;
use App\Models\Organization; use App\Models\Organization;
use App\Models\User; use App\Models\User;
@@ -81,7 +82,7 @@ class CreateNewUser implements CreatesNewUsers
$organization->users()->attach( $organization->users()->attach(
$user, [ $user, [
'role' => 'owner', 'role' => Role::Owner->value,
] ]
); );

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Actions\Jetstream; namespace App\Actions\Jetstream;
use App\Enums\Role;
use App\Models\Organization; use App\Models\Organization;
use App\Models\User; use App\Models\User;
use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Auth\Access\AuthorizationException;
@@ -42,7 +43,7 @@ class CreateOrganization implements CreatesTeams
$organization->users()->attach( $organization->users()->attach(
$user, [ $user, [
'role' => 'owner', 'role' => Role::Owner->value,
] ]
); );

View File

@@ -8,6 +8,7 @@ use App\Filament\Resources\UserResource\Pages;
use App\Filament\Resources\UserResource\RelationManagers\OrganizationsRelationManager; use App\Filament\Resources\UserResource\RelationManagers\OrganizationsRelationManager;
use App\Filament\Resources\UserResource\RelationManagers\OwnedOrganizationsRelationManager; use App\Filament\Resources\UserResource\RelationManagers\OwnedOrganizationsRelationManager;
use App\Models\User; use App\Models\User;
use Exception;
use Filament\Forms; use Filament\Forms;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Form; use Filament\Forms\Form;
@@ -15,6 +16,7 @@ use Filament\Resources\Resource;
use Filament\Tables; use Filament\Tables;
use Filament\Tables\Table; use Filament\Tables\Table;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use STS\FilamentImpersonate\Tables\Actions\Impersonate;
class UserResource extends Resource class UserResource extends Resource
{ {
@@ -70,6 +72,19 @@ class UserResource extends Resource
// //
]) ])
->actions([ ->actions([
Impersonate::make()->before(function (User $record): void {
if ($record->currentTeam === null) {
$organization = $record->organizations()->where('personal_team', '=', true)->first();
if ($organization === null) {
$organization = $record->organizations()->first();
}
if ($organization === null) {
throw new Exception('User has no organization');
}
$record->currentTeam()->associate($organization);
$record->save();
}
}),
Tables\Actions\EditAction::make(), Tables\Actions\EditAction::make(),
]) ])
->bulkActions([ ->bulkActions([

View File

@@ -7,6 +7,7 @@ namespace App\Filament\Resources\UserResource\Pages;
use App\Filament\Resources\UserResource; use App\Filament\Resources\UserResource;
use Filament\Actions; use Filament\Actions;
use Filament\Resources\Pages\EditRecord; use Filament\Resources\Pages\EditRecord;
use STS\FilamentImpersonate\Pages\Actions\Impersonate;
class EditUser extends EditRecord class EditUser extends EditRecord
{ {
@@ -15,6 +16,7 @@ class EditUser extends EditRecord
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return [ return [
Impersonate::make()->record($this->getRecord()),
Actions\DeleteAction::make(), Actions\DeleteAction::make(),
]; ];
} }

View File

@@ -126,6 +126,11 @@ class User extends Authenticatable implements FilamentUser, MustVerifyEmail
return in_array($this->email, config('auth.super_admins', []), true) && $this->hasVerifiedEmail(); return in_array($this->email, config('auth.super_admins', []), true) && $this->hasVerifiedEmail();
} }
public function canBeImpersonated(): bool
{
return $this->is_placeholder === false;
}
/** /**
* @return BelongsToMany<Organization> * @return BelongsToMany<Organization>
*/ */

View File

@@ -59,10 +59,14 @@ class PermissionStore
?->membership ?->membership
?->role; ?->role;
if ($role === null) {
return [];
}
/** @var Role|null $roleObj */ /** @var Role|null $roleObj */
$roleObj = Jetstream::findRole($role); $roleObj = Jetstream::findRole($role);
return $role !== null ? ($roleObj?->permissions ?? []) : []; return $roleObj?->permissions ?? [];
} }
/** /**

View File

@@ -23,6 +23,7 @@
"nwidart/laravel-modules": "dev-feature/fixed_path", "nwidart/laravel-modules": "dev-feature/fixed_path",
"pxlrbt/filament-environment-indicator": "^2.0", "pxlrbt/filament-environment-indicator": "^2.0",
"spatie/temporary-directory": "^2.2", "spatie/temporary-directory": "^2.2",
"stechstudio/filament-impersonate": "^3.8",
"tightenco/ziggy": "^2.1.0", "tightenco/ziggy": "^2.1.0",
"tpetry/laravel-postgresql-enhanced": "^0.38.0", "tpetry/laravel-postgresql-enhanced": "^0.38.0",
"wikimedia/composer-merge-plugin": "^2.1.0" "wikimedia/composer-merge-plugin": "^2.1.0"

155
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "ad100a87fbe76efc70708726bc921412", "content-hash": "5779fb7e87efa88e5db2b37e64fe89e3",
"packages": [ "packages": [
{ {
"name": "anourvalar/eloquent-serialize", "name": "anourvalar/eloquent-serialize",
@@ -74,28 +74,28 @@
}, },
{ {
"name": "bacon/bacon-qr-code", "name": "bacon/bacon-qr-code",
"version": "2.0.8", "version": "v3.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/Bacon/BaconQrCode.git", "url": "https://github.com/Bacon/BaconQrCode.git",
"reference": "8674e51bb65af933a5ffaf1c308a660387c35c22" "reference": "510de6eca6248d77d31b339d62437cc995e2fb41"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/8674e51bb65af933a5ffaf1c308a660387c35c22", "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/510de6eca6248d77d31b339d62437cc995e2fb41",
"reference": "8674e51bb65af933a5ffaf1c308a660387c35c22", "reference": "510de6eca6248d77d31b339d62437cc995e2fb41",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"dasprid/enum": "^1.0.3", "dasprid/enum": "^1.0.3",
"ext-iconv": "*", "ext-iconv": "*",
"php": "^7.1 || ^8.0" "php": "^8.1"
}, },
"require-dev": { "require-dev": {
"phly/keep-a-changelog": "^2.1", "phly/keep-a-changelog": "^2.12",
"phpunit/phpunit": "^7 | ^8 | ^9", "phpunit/phpunit": "^10.5.11 || 11.0.4",
"spatie/phpunit-snapshot-assertions": "^4.2.9", "spatie/phpunit-snapshot-assertions": "^5.1.5",
"squizlabs/php_codesniffer": "^3.4" "squizlabs/php_codesniffer": "^3.9"
}, },
"suggest": { "suggest": {
"ext-imagick": "to generate QR code images" "ext-imagick": "to generate QR code images"
@@ -122,9 +122,9 @@
"homepage": "https://github.com/Bacon/BaconQrCode", "homepage": "https://github.com/Bacon/BaconQrCode",
"support": { "support": {
"issues": "https://github.com/Bacon/BaconQrCode/issues", "issues": "https://github.com/Bacon/BaconQrCode/issues",
"source": "https://github.com/Bacon/BaconQrCode/tree/2.0.8" "source": "https://github.com/Bacon/BaconQrCode/tree/v3.0.0"
}, },
"time": "2022-12-07T17:46:57+00:00" "time": "2024-04-18T11:16:25+00:00"
}, },
{ {
"name": "blade-ui-kit/blade-heroicons", "name": "blade-ui-kit/blade-heroicons",
@@ -3496,6 +3496,73 @@
}, },
"time": "2024-02-28T15:07:15+00:00" "time": "2024-02-28T15:07:15+00:00"
}, },
{
"name": "lab404/laravel-impersonate",
"version": "1.7.5",
"source": {
"type": "git",
"url": "https://github.com/404labfr/laravel-impersonate.git",
"reference": "82cad73700a8699d63de169bb41abd5ae283e9a8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/404labfr/laravel-impersonate/zipball/82cad73700a8699d63de169bb41abd5ae283e9a8",
"reference": "82cad73700a8699d63de169bb41abd5ae283e9a8",
"shasum": ""
},
"require": {
"laravel/framework": "^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0",
"php": "^7.2 | ^8.0"
},
"require-dev": {
"mockery/mockery": "^1.3.3",
"orchestra/testbench": "^4.0 | ^5.0 | ^6.0 | ^7.0 | ^8.0 | ^9.0",
"phpunit/phpunit": "^7.5 | ^8.0 | ^9.0 | ^10.0"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Lab404\\Impersonate\\ImpersonateServiceProvider"
]
}
},
"autoload": {
"files": [
"src/helpers.php"
],
"psr-4": {
"Lab404\\Impersonate\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Marceau Casals",
"email": "marceau@casals.fr"
}
],
"description": "Laravel Impersonate is a plugin that allows to you to authenticate as your users.",
"keywords": [
"auth",
"impersonate",
"impersonation",
"laravel",
"laravel-package",
"laravel-plugin",
"package",
"plugin",
"user"
],
"support": {
"issues": "https://github.com/404labfr/laravel-impersonate/issues",
"source": "https://github.com/404labfr/laravel-impersonate/tree/1.7.5"
},
"time": "2024-03-11T14:26:14+00:00"
},
{ {
"name": "laminas/laminas-diactoros", "name": "laminas/laminas-diactoros",
"version": "3.3.1", "version": "3.3.1",
@@ -3583,20 +3650,20 @@
}, },
{ {
"name": "laravel/fortify", "name": "laravel/fortify",
"version": "v1.21.1", "version": "v1.21.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/fortify.git", "url": "https://github.com/laravel/fortify.git",
"reference": "405388fd399264715573e23ed2f368fbce426da3" "reference": "cb122ceec7f8d0231985c1dde8161b3c561bfe90"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/fortify/zipball/405388fd399264715573e23ed2f368fbce426da3", "url": "https://api.github.com/repos/laravel/fortify/zipball/cb122ceec7f8d0231985c1dde8161b3c561bfe90",
"reference": "405388fd399264715573e23ed2f368fbce426da3", "reference": "cb122ceec7f8d0231985c1dde8161b3c561bfe90",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"bacon/bacon-qr-code": "^2.0", "bacon/bacon-qr-code": "^3.0",
"ext-json": "*", "ext-json": "*",
"illuminate/support": "^10.0|^11.0", "illuminate/support": "^10.0|^11.0",
"php": "^8.1", "php": "^8.1",
@@ -3644,7 +3711,7 @@
"issues": "https://github.com/laravel/fortify/issues", "issues": "https://github.com/laravel/fortify/issues",
"source": "https://github.com/laravel/fortify" "source": "https://github.com/laravel/fortify"
}, },
"time": "2024-03-19T20:08:25+00:00" "time": "2024-04-25T14:17:43+00:00"
}, },
{ {
"name": "laravel/framework", "name": "laravel/framework",
@@ -8038,6 +8105,48 @@
], ],
"time": "2023-12-25T11:46:58+00:00" "time": "2023-12-25T11:46:58+00:00"
}, },
{
"name": "stechstudio/filament-impersonate",
"version": "3.8",
"source": {
"type": "git",
"url": "https://github.com/stechstudio/filament-impersonate.git",
"reference": "d24a9ffc1ef2f87940d151ca1cb2c2d2e5e524a8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/stechstudio/filament-impersonate/zipball/d24a9ffc1ef2f87940d151ca1cb2c2d2e5e524a8",
"reference": "d24a9ffc1ef2f87940d151ca1cb2c2d2e5e524a8",
"shasum": ""
},
"require": {
"filament/filament": "^3.0",
"lab404/laravel-impersonate": "^1.7"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"STS\\FilamentImpersonate\\FilamentImpersonateServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"STS\\FilamentImpersonate\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "A Filament package to impersonate your users.",
"support": {
"issues": "https://github.com/stechstudio/filament-impersonate/issues",
"source": "https://github.com/stechstudio/filament-impersonate/tree/3.8"
},
"time": "2024-03-25T03:05:55+00:00"
},
{ {
"name": "symfony/clock", "name": "symfony/clock",
"version": "v7.0.5", "version": "v7.0.5",
@@ -13908,16 +14017,16 @@
}, },
{ {
"name": "spatie/ignition", "name": "spatie/ignition",
"version": "1.13.2", "version": "1.14.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/spatie/ignition.git", "url": "https://github.com/spatie/ignition.git",
"reference": "952798e239d9969e4e694b124c2cc222798dbb28" "reference": "80385994caed328f6f9c9952926932e65b9b774c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/spatie/ignition/zipball/952798e239d9969e4e694b124c2cc222798dbb28", "url": "https://api.github.com/repos/spatie/ignition/zipball/80385994caed328f6f9c9952926932e65b9b774c",
"reference": "952798e239d9969e4e694b124c2cc222798dbb28", "reference": "80385994caed328f6f9c9952926932e65b9b774c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -13987,7 +14096,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-04-16T08:49:17+00:00" "time": "2024-04-26T08:45:51+00:00"
}, },
{ {
"name": "spatie/laravel-ignition", "name": "spatie/laravel-ignition",

View File

@@ -44,8 +44,8 @@ return [
], ],
'replacements' => [ 'replacements' => [
'routes/web' => ['LOWER_NAME', 'STUDLY_NAME', 'MODULE_NAMESPACE', 'CONTROLLER_NAMESPACE'], 'routes/web' => ['LOWER_NAME', 'STUDLY_NAME', 'MODULE_NAMESPACE', 'CONTROLLER_NAMESPACE'],
'routes/api' => ['LOWER_NAME', 'STUDLY_NAME'], 'routes/api' => ['LOWER_NAME', 'STUDLY_NAME', 'MODULE_NAMESPACE', 'CONTROLLER_NAMESPACE'],
'vite' => ['LOWER_NAME'], 'vite' => ['LOWER_NAME', 'STUDLY_NAME'],
'json' => ['LOWER_NAME', 'STUDLY_NAME', 'MODULE_NAMESPACE', 'PROVIDER_NAMESPACE'], 'json' => ['LOWER_NAME', 'STUDLY_NAME', 'MODULE_NAMESPACE', 'PROVIDER_NAMESPACE'],
'views/index' => ['LOWER_NAME'], 'views/index' => ['LOWER_NAME'],
'views/master' => ['LOWER_NAME', 'STUDLY_NAME'], 'views/master' => ['LOWER_NAME', 'STUDLY_NAME'],
@@ -58,6 +58,7 @@ return [
'AUTHOR_EMAIL', 'AUTHOR_EMAIL',
'MODULE_NAMESPACE', 'MODULE_NAMESPACE',
'PROVIDER_NAMESPACE', 'PROVIDER_NAMESPACE',
'APP_FOLDER_NAME',
], ],
], ],
'gitkeep' => true, 'gitkeep' => true,
@@ -93,8 +94,18 @@ return [
| the migration files? | the migration files?
| |
*/ */
'migration' => base_path('database/migrations'), 'migration' => base_path('database/migrations'),
/*
|--------------------------------------------------------------------------
| The app path
|--------------------------------------------------------------------------
|
| app folder name
| for example can change it to 'src' or 'App'
*/
'app_folder' => 'app/',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Generator path | Generator path
@@ -103,35 +114,52 @@ return [
| Setting the generate key to false will not generate that folder | Setting the generate key to false will not generate that folder
*/ */
'generator' => [ 'generator' => [
// app/
'channels' => ['path' => 'app/Broadcasting', 'generate' => false],
'command' => ['path' => 'app/Console', 'generate' => false],
'emails' => ['path' => 'app/Emails', 'generate' => false],
'event' => ['path' => 'app/Events', 'generate' => false],
'jobs' => ['path' => 'app/Jobs', 'generate' => false],
'listener' => ['path' => 'app/Listeners', 'generate' => false],
'model' => ['path' => 'app/Models', 'generate' => false],
'notifications' => ['path' => 'app/Notifications', 'generate' => false],
'observer' => ['path' => 'app/Observers', 'generate' => false],
'policies' => ['path' => 'app/Policies', 'generate' => false],
'provider' => ['path' => 'app/Providers', 'generate' => true],
'route-provider' => ['path' => 'app/Providers', 'generate' => true],
'repository' => ['path' => 'app/Repositories', 'generate' => false],
'resource' => ['path' => 'app/Transformers', 'generate' => false],
'rules' => ['path' => 'app/Rules', 'generate' => false],
'component-class' => ['path' => 'app/View/Components', 'generate' => false],
'service' => ['path' => 'app/Services', 'generate' => false],
// app/Http/
'controller' => ['path' => 'app/Http/Controllers', 'generate' => true],
'filter' => ['path' => 'app/Http/Middleware', 'generate' => false],
'request' => ['path' => 'app/Http/Requests', 'generate' => false],
// config/
'config' => ['path' => 'config', 'generate' => true], 'config' => ['path' => 'config', 'generate' => true],
'command' => ['path' => 'App/Console', 'generate' => false],
'channels' => ['path' => 'App/Broadcasting', 'generate' => false], // database/
'migration' => ['path' => 'Database/migrations', 'generate' => false], 'migration' => ['path' => 'database/migrations', 'generate' => true],
'seeder' => ['path' => 'Database/Seeders', 'generate' => true], 'seeder' => ['path' => 'database/seeders', 'namespace' => 'Database\Seeders', 'generate' => true],
'factory' => ['path' => 'Database/Factories', 'generate' => false], 'factory' => ['path' => 'database/factories', 'namespace' => 'Database\Factories', 'generate' => true],
'model' => ['path' => 'App/Models', 'generate' => false],
'observer' => ['path' => 'App/Observers', 'generate' => false], // lang/
'routes' => ['path' => 'routes', 'generate' => true],
'controller' => ['path' => 'App/Http/Controllers', 'generate' => true],
'filter' => ['path' => 'App/Http/Middleware', 'generate' => false],
'request' => ['path' => 'App/Http/Requests', 'generate' => false],
'provider' => ['path' => 'App/Providers', 'generate' => true],
'assets' => ['path' => 'resources/assets', 'generate' => false],
'lang' => ['path' => 'lang', 'generate' => false], 'lang' => ['path' => 'lang', 'generate' => false],
// resource/
'assets' => ['path' => 'resources/assets', 'generate' => true],
'views' => ['path' => 'resources/views', 'generate' => true], 'views' => ['path' => 'resources/views', 'generate' => true],
'test' => ['path' => 'tests/Unit', 'generate' => false],
'test-feature' => ['path' => 'tests/Feature', 'generate' => false],
'repository' => ['path' => 'App/Repositories', 'generate' => false],
'event' => ['path' => 'App/Events', 'generate' => false],
'listener' => ['path' => 'App/Listeners', 'generate' => false],
'policies' => ['path' => 'App/Policies', 'generate' => false],
'rules' => ['path' => 'App/Rules', 'generate' => false],
'jobs' => ['path' => 'App/Jobs', 'generate' => false],
'emails' => ['path' => 'App/Emails', 'generate' => false],
'notifications' => ['path' => 'App/Notifications', 'generate' => false],
'resource' => ['path' => 'App/resources', 'generate' => false],
'component-view' => ['path' => 'resources/views/components', 'generate' => false], 'component-view' => ['path' => 'resources/views/components', 'generate' => false],
'component-class' => ['path' => 'App/View/Components', 'generate' => false],
// routes/
'routes' => ['path' => 'routes', 'generate' => true],
// tests/
'test-unit' => ['path' => 'tests/Unit', 'generate' => true],
'test-feature' => ['path' => 'tests/Feature', 'generate' => true],
], ],
], ],
@@ -158,13 +186,13 @@ return [
| directory. This is useful if you host the package in packagist website. | directory. This is useful if you host the package in packagist website.
| |
*/ */
'scan' => [ 'scan' => [
'enabled' => false, 'enabled' => false,
'paths' => [ 'paths' => [
base_path('vendor/*/*'), base_path('vendor/*/*'),
], ],
], ],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Composer File Template | Composer File Template
@@ -173,12 +201,11 @@ return [
| Here is the config for the composer.json file, generated by this package | Here is the config for the composer.json file, generated by this package
| |
*/ */
'composer' => [ 'composer' => [
'vendor' => 'nwidart', 'vendor' => env('MODULE_VENDOR', 'solidtime-io'),
'author' => [ 'author' => [
'name' => 'Nicolas Widart', 'name' => env('MODULE_AUTHOR_NAME', 'Nicolas Widart'),
'email' => 'n.widart@gmail.com', 'email' => env('MODULE_AUTHOR_EMAIL', 'n.widart@gmail.com'),
], ],
'composer-output' => false, 'composer-output' => false,
], ],
@@ -192,11 +219,12 @@ return [
| |
*/ */
'cache' => [ 'cache' => [
'enabled' => false, 'enabled' => env('MODULES_CACHE_ENABLED', false),
'driver' => 'file', 'driver' => env('MODULES_CACHE_DRIVER', 'file'),
'key' => 'laravel-modules', 'key' => env('MODULES_CACHE_KEY', 'laravel-modules'),
'lifetime' => 60, 'lifetime' => env('MODULES_CACHE_LIFETIME', 60),
], ],
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Choose what laravel-modules will register as custom namespaces. | Choose what laravel-modules will register as custom namespaces.

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Database\Factories; namespace Database\Factories;
use App\Enums\Role;
use App\Enums\Weekday; use App\Enums\Weekday;
use App\Models\Organization; use App\Models\Organization;
use App\Models\User; use App\Models\User;
@@ -100,7 +101,9 @@ class UserFactory extends Factory
->when(is_callable($callback), $callback) ->when(is_callable($callback), $callback)
->create(); ->create();
$organization->users()->attach($user, ['role' => 'owner']); $organization->users()->attach($user, ['role' => Role::Owner->value]);
$user->currentTeam()->associate($organization);
$user->save();
}); });
} }
} }

View File

@@ -25,12 +25,14 @@ class DatabaseSeeder extends Seeder
public function run(): void public function run(): void
{ {
$this->deleteAll(); $this->deleteAll();
$userAcmeOwner = User::factory()->create([ $userAcmeOwner = User::factory()->withPersonalOrganization()->create([
'name' => 'Acme Owner', 'name' => 'Acme Owner',
'email' => 'owner@acme.test', 'email' => 'owner@acme.test',
]); ]);
$organizationAcme = Organization::factory()->withOwner($userAcmeOwner)->create([ $organizationAcme = Organization::factory()->withOwner($userAcmeOwner)->create([
'name' => 'ACME Corp', 'name' => 'ACME Corp',
'personal_team' => false,
'currency' => 'EUR',
]); ]);
$userAcmeManager = User::factory()->withPersonalOrganization()->create([ $userAcmeManager = User::factory()->withPersonalOrganization()->create([
'name' => 'Acme Manager', 'name' => 'Acme Manager',
@@ -80,29 +82,35 @@ class DatabaseSeeder extends Seeder
->forUser($userAcmeEmployee) ->forUser($userAcmeEmployee)
->forOrganization($organizationAcme) ->forOrganization($organizationAcme)
->create(); ->create();
$client = Client::factory()->create([ $client = Client::factory()->forOrganization($organizationAcme)->create([
'name' => 'Big Company', 'name' => 'Big Company',
]); ]);
$bigCompanyProject = Project::factory()->forClient($client)->create([ $bigCompanyProject = Project::factory()->forOrganization($organizationAcme)->forClient($client)->create([
'name' => 'Big Company Project', 'name' => 'Big Company Project',
]); ]);
Task::factory()->forProject($bigCompanyProject)->create(); Task::factory()->forOrganization($organizationAcme)->forProject($bigCompanyProject)->create();
$internalProject = Project::factory()->create([ $internalProject = Project::factory()->forOrganization($organizationAcme)->create([
'name' => 'Internal Project', 'name' => 'Internal Project',
]); ]);
$organization2 = Organization::factory()->create([ $organization2Owner = User::factory()->create([
'name' => 'Other Owner',
'email' => 'owner@rival-company.test',
]);
$organization2 = Organization::factory()->withOwner($organization2Owner)->create([
'name' => 'Rival Corp', 'name' => 'Rival Corp',
'personal_team' => true,
'currency' => 'USD',
]); ]);
$userAcmeManager = User::factory()->withPersonalOrganization()->create([ $userAcmeManager = User::factory()->withPersonalOrganization()->create([
'name' => 'Other User', 'name' => 'Other User',
'email' => 'test@rival-company.test', 'email' => 'test@rival-company.test',
]); ]);
$userAcmeManager->organizations()->attach($organization2, [ $userAcmeManager->organizations()->attach($organization2, [
'role' => 'admin', 'role' => Role::Admin->value,
]); ]);
$otherCompanyProject = Project::factory()->forClient($client)->create([ $otherCompanyProject = Project::factory()->forOrganization($organization2)->forClient($client)->create([
'name' => 'Scale Company', 'name' => 'Scale Company',
]); ]);

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Tests\Feature; namespace Tests\Feature;
use App\Enums\Role;
use App\Models\Membership; use App\Models\Membership;
use App\Models\User; use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
@@ -29,6 +30,6 @@ class CreateTeamTest extends TestCase
$this->assertCount(2, $user->fresh()->ownedTeams); $this->assertCount(2, $user->fresh()->ownedTeams);
$this->assertEquals('Test Organization', $newOrganization->name); $this->assertEquals('Test Organization', $newOrganization->name);
$member = Membership::query()->whereBelongsTo($user, 'user')->whereBelongsTo($newOrganization, 'organization')->firstOrFail(); $member = Membership::query()->whereBelongsTo($user, 'user')->whereBelongsTo($newOrganization, 'organization')->firstOrFail();
$this->assertSame('owner', $member->role); $this->assertSame(Role::Owner->value, $member->role);
} }
} }

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Tests\Feature; namespace Tests\Feature;
use App\Enums\Role;
use App\Models\Membership; use App\Models\Membership;
use App\Models\User; use App\Models\User;
use App\Providers\RouteServiceProvider; use App\Providers\RouteServiceProvider;
@@ -58,7 +59,7 @@ class RegistrationTest extends TestCase
$organization = $user->organizations()->firstOrFail(); $organization = $user->organizations()->firstOrFail();
$this->assertSame(true, $organization->personal_team); $this->assertSame(true, $organization->personal_team);
$member = Membership::query()->whereBelongsTo($user, 'user')->whereBelongsTo($organization, 'organization')->firstOrFail(); $member = Membership::query()->whereBelongsTo($user, 'user')->whereBelongsTo($organization, 'organization')->firstOrFail();
$this->assertSame('owner', $member->role); $this->assertSame(Role::Owner->value, $member->role);
} }
public function test_new_users_can_register_and_frontend_can_send_timezone_for_user(): void public function test_new_users_can_register_and_frontend_can_send_timezone_for_user(): void

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Tests\Feature; namespace Tests\Feature;
use App\Enums\Role;
use App\Models\User; use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase; use Tests\TestCase;
@@ -64,12 +65,12 @@ class UpdateTeamMemberRoleTest extends TestCase
// Act // Act
$response = $this->withoutExceptionHandling()->put('/teams/'.$user->currentTeam->id.'/members/'.$otherUser->getKey(), [ $response = $this->withoutExceptionHandling()->put('/teams/'.$user->currentTeam->id.'/members/'.$otherUser->getKey(), [
'role' => 'owner', 'role' => Role::Owner->value,
]); ]);
// Assert // Assert
$this->assertTrue($otherUser->fresh()->hasTeamRole( $this->assertTrue($otherUser->fresh()->hasTeamRole(
$user->currentTeam->fresh(), 'owner' $user->currentTeam->fresh(), Role::Owner->value
)); ));
$this->assertSame($user->currentTeam->fresh()->user_id, $otherUser->getKey()); $this->assertSame($user->currentTeam->fresh()->user_id, $otherUser->getKey());
} }

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Tests\Unit\Service; namespace Tests\Unit\Service;
use App\Enums\Role;
use App\Enums\Weekday; use App\Enums\Weekday;
use App\Models\Organization; use App\Models\Organization;
use App\Models\Project; use App\Models\Project;
@@ -267,7 +268,7 @@ class DashboardServiceTest extends TestCase
]); ]);
$organization = Organization::factory()->withOwner($user)->create(); $organization = Organization::factory()->withOwner($user)->create();
$organization->users()->attach($user, [ $organization->users()->attach($user, [
'role' => 'owner', 'role' => Role::Owner->value,
]); ]);
$project1 = Project::factory()->forOrganization($organization)->create(); $project1 = Project::factory()->forOrganization($organization)->create();
$project2 = Project::factory()->forOrganization($organization)->create(); $project2 = Project::factory()->forOrganization($organization)->create();