Skip to content

Commit

Permalink
Add tenant with company module (#21)
Browse files Browse the repository at this point in the history
  • Loading branch information
a21ns1g4ts authored Oct 20, 2024
1 parent eb7db94 commit 137a9e7
Show file tree
Hide file tree
Showing 19 changed files with 795 additions and 1 deletion.
4 changes: 3 additions & 1 deletion src/OctoServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Octo\Features\FeaturesServiceProvider;
use Octo\Middleware\MiddlewareServiceProvider;
use Octo\Settings\SettingsServiceProvider;
use Octo\Tenant\TenantServiceProvider;
use Octo\User\UserServiceProvider;
use Spatie\LaravelPackageTools\Package;
use Spatie\LaravelPackageTools\PackageServiceProvider;
Expand All @@ -18,8 +19,9 @@ public function configurePackage(Package $package): void
$this->app->register(FeaturesServiceProvider::class);
$this->app->register(SettingsServiceProvider::class);
$this->app->register(MiddlewareServiceProvider::class);
$this->app->register(TenantServiceProvider::class);

Route::get('/', fn () => redirect(config('octo.admin_path')));
Route::get('/', fn() => redirect(config('octo.admin_path')));

/*
* This class is a Package Service Provider
Expand Down
86 changes: 86 additions & 0 deletions src/Tenant/Actions/FilamentCompanies/AddCompanyEmployee.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

namespace Octo\Tenant\Actions\FilamentCompanies;

use App\Models\Company;
use App\Models\User;
use Closure;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Validator;
use Wallo\FilamentCompanies\Contracts\AddsCompanyEmployees;
use Wallo\FilamentCompanies\Events\AddingCompanyEmployee;
use Wallo\FilamentCompanies\Events\CompanyEmployeeAdded;
use Wallo\FilamentCompanies\FilamentCompanies;
use Wallo\FilamentCompanies\Rules\Role;

class AddCompanyEmployee implements AddsCompanyEmployees
{
/**
* Add a new company employee to the given company.
*
* @throws AuthorizationException
*/
public function add(User $user, Company $company, string $email, ?string $role = null): void
{
Gate::forUser($user)->authorize('addCompanyEmployee', $company);

$this->validate($company, $email, $role);

$newCompanyEmployee = FilamentCompanies::findUserByEmailOrFail($email);

AddingCompanyEmployee::dispatch($company, $newCompanyEmployee);

$company->users()->attach(
$newCompanyEmployee,
['role' => $role]
);

CompanyEmployeeAdded::dispatch($company, $newCompanyEmployee);
}

/**
* Validate the add employee operation.
*/
protected function validate(Company $company, string $email, ?string $role): void
{
Validator::make([
'email' => $email,
'role' => $role,
], $this->rules(), [
'email.exists' => __('filament-companies::default.errors.email_not_found'),
])->after(
$this->ensureUserIsNotAlreadyOnCompany($company, $email)
)->validateWithBag('addCompanyEmployee');
}

/**
* Get the validation rules for adding a company employee.
*
* @return array<string, Rule|array|string>
*/
protected function rules(): array
{
return array_filter([
'email' => ['required', 'email', 'exists:users'],
'role' => FilamentCompanies::hasRoles()
? ['required', 'string', new Role]
: null,
]);
}

/**
* Ensure that the user is not already on the company.
*/
protected function ensureUserIsNotAlreadyOnCompany(Company $company, string $email): Closure
{
return static function ($validator) use ($company, $email) {
$validator->errors()->addIf(
$company->hasUserWithEmail($email),
'email',
__('filament-companies::default.errors.user_belongs_to_company')
);
};
}
}
40 changes: 40 additions & 0 deletions src/Tenant/Actions/FilamentCompanies/CreateCompany.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace Octo\Tenant\Actions\FilamentCompanies;

use App\Models\Company;
use App\Models\User;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Validator;
use Wallo\FilamentCompanies\Contracts\CreatesCompanies;
use Wallo\FilamentCompanies\Events\AddingCompany;
use Wallo\FilamentCompanies\FilamentCompanies;

class CreateCompany implements CreatesCompanies
{
/**
* Validate and create a new company for the given user.
*
* @param array<string, string> $input
*
* @throws AuthorizationException
*/
public function create(User $user, array $input): Company
{
Gate::forUser($user)->authorize('create', FilamentCompanies::newCompanyModel());

Validator::make($input, [
'name' => ['required', 'string', 'max:255'],
])->validateWithBag('createCompany');

AddingCompany::dispatch($user);

$user->switchCompany($company = $user->ownedCompanies()->create([
'name' => $input['name'],
'personal_company' => false,
]));

return $company;
}
}
32 changes: 32 additions & 0 deletions src/Tenant/Actions/FilamentCompanies/CreateConnectedAccount.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Octo\Tenant\Actions\FilamentCompanies;

use Illuminate\Contracts\Auth\Authenticatable;
use Laravel\Socialite\Contracts\User as ProviderUser;
use Wallo\FilamentCompanies\ConnectedAccount;
use Wallo\FilamentCompanies\Contracts\CreatesConnectedAccounts;
use Wallo\FilamentCompanies\FilamentCompanies;

class CreateConnectedAccount implements CreatesConnectedAccounts
{
/**
* Create a connected account for a given user.
*/
public function create(Authenticatable $user, string $provider, ProviderUser $providerUser): ConnectedAccount
{
return FilamentCompanies::connectedAccountModel()::forceCreate([
'user_id' => $user->getAuthIdentifier(),
'provider' => strtolower($provider),
'provider_id' => $providerUser->getId(),
'name' => $providerUser->getName(),
'nickname' => $providerUser->getNickname(),
'email' => $providerUser->getEmail(),
'avatar_path' => $providerUser->getAvatar(),
'token' => $providerUser->token,
'secret' => $providerUser->tokenSecret ?? null,
'refresh_token' => $providerUser->refreshToken ?? null,
'expires_at' => property_exists($providerUser, 'expiresIn') ? now()->addSeconds($providerUser->expiresIn) : null,
]);
}
}
51 changes: 51 additions & 0 deletions src/Tenant/Actions/FilamentCompanies/CreateNewUser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace Octo\Tenant\Actions\FilamentCompanies;

use App\Models\Company;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Wallo\FilamentCompanies\Contracts\CreatesNewUsers;
use Wallo\FilamentCompanies\FilamentCompanies;

class CreateNewUser implements CreatesNewUsers
{
/**
* Create a newly registered user.
*
* @param array<string, string> $input
*/
public function create(array $input): User
{
Validator::make($input, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
'terms' => FilamentCompanies::hasTermsAndPrivacyPolicyFeature() ? ['accepted', 'required'] : '',
])->validate();

return DB::transaction(function () use ($input) {
return tap(User::create([
'name' => $input['name'],
'email' => $input['email'],
'password' => Hash::make($input['password']),
]), function (User $user) {
$this->createCompany($user);
});
});
}

/**
* Create a personal company for the user.
*/
protected function createCompany(User $user): void
{
$user->ownedCompanies()->save(Company::forceCreate([
'user_id' => $user->id,
'name' => explode(' ', $user->name, 2)[0] . "'s Company",
'personal_company' => true,
]));
}
}
72 changes: 72 additions & 0 deletions src/Tenant/Actions/FilamentCompanies/CreateUserFromProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

namespace Octo\Tenant\Actions\FilamentCompanies;

use App\Models\Company;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Laravel\Socialite\Contracts\User as ProviderUserContract;
use Wallo\FilamentCompanies\Contracts\CreatesConnectedAccounts;
use Wallo\FilamentCompanies\Contracts\CreatesUserFromProvider;
use Wallo\FilamentCompanies\Enums\Feature;
use Wallo\FilamentCompanies\FilamentCompanies;

class CreateUserFromProvider implements CreatesUserFromProvider
{
/**
* The creates connected accounts instance.
*/
public CreatesConnectedAccounts $createsConnectedAccounts;

/**
* Create a new action instance.
*/
public function __construct(CreatesConnectedAccounts $createsConnectedAccounts)
{
$this->createsConnectedAccounts = $createsConnectedAccounts;
}

/**
* Create a new user from a social provider user.
*/
public function create(string $provider, ProviderUserContract $providerUser): User
{
return DB::transaction(function () use ($providerUser, $provider) {
return tap(User::create([
'name' => $providerUser->getName(),
'email' => $providerUser->getEmail(),
]), function (User $user) use ($providerUser, $provider) {
$user->markEmailAsVerified();

if ($this->shouldSetProfilePhoto($providerUser)) {
$user->setProfilePhotoFromUrl($providerUser->getAvatar());
}

$user->switchConnectedAccount(
$this->createsConnectedAccounts->create($user, $provider, $providerUser)
);

$this->createCompany($user);
});
});
}

private function shouldSetProfilePhoto(ProviderUserContract $providerUser): bool
{
return Feature::ProviderAvatars->isEnabled() &&
FilamentCompanies::managesProfilePhotos() &&
$providerUser->getAvatar();
}

/**
* Create a personal company for the user.
*/
protected function createCompany(User $user): void
{
$user->ownedCompanies()->save(Company::forceCreate([
'user_id' => $user->id,
'name' => explode(' ', $user->name, 2)[0] . "'s Company",
'personal_company' => true,
]));
}
}
17 changes: 17 additions & 0 deletions src/Tenant/Actions/FilamentCompanies/DeleteCompany.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Octo\Tenant\Actions\FilamentCompanies;

use App\Models\Company;
use Wallo\FilamentCompanies\Contracts\DeletesCompanies;

class DeleteCompany implements DeletesCompanies
{
/**
* Delete the given company.
*/
public function delete(Company $company): void
{
$company->purge();
}
}
51 changes: 51 additions & 0 deletions src/Tenant/Actions/FilamentCompanies/DeleteUser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace Octo\Tenant\Actions\FilamentCompanies;

use App\Models\Company;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Wallo\FilamentCompanies\Contracts\DeletesCompanies;
use Wallo\FilamentCompanies\Contracts\DeletesUsers;

class DeleteUser implements DeletesUsers
{
/**
* The company deleter implementation.
*/
protected DeletesCompanies $deletesCompanies;

/**
* Create a new action instance.
*/
public function __construct(DeletesCompanies $deletesCompanies)
{
$this->deletesCompanies = $deletesCompanies;
}

/**
* Delete the given user.
*/
public function delete(User $user): void
{
DB::transaction(function () use ($user) {
$this->deleteCompanies($user);
$user->deleteProfilePhoto();
$user->connectedAccounts->each(static fn($account) => $account->delete());
$user->tokens->each(static fn($token) => $token->delete());
$user->delete();
});
}

/**
* Delete the companies and company associations attached to the user.
*/
protected function deleteCompanies(User $user): void
{
$user->companies()->detach();

$user->ownedCompanies->each(function (Company $company) {
$this->deletesCompanies->delete($company);
});
}
}
22 changes: 22 additions & 0 deletions src/Tenant/Actions/FilamentCompanies/HandleInvalidState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Octo\Tenant\Actions\FilamentCompanies;

use Illuminate\Http\Response;
use Laravel\Socialite\Two\InvalidStateException;
use Wallo\FilamentCompanies\Contracts\HandlesInvalidState;

class HandleInvalidState implements HandlesInvalidState
{
/**
* Handle an invalid state exception from a Socialite provider.
*/
public function handle(InvalidStateException $exception, ?callable $callback = null): Response
{
if ($callback) {
return $callback($exception);
}

throw $exception;
}
}
Loading

0 comments on commit 137a9e7

Please sign in to comment.