Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Verbstream #12

Merged
merged 7 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
51 changes: 51 additions & 0 deletions app/Actions/Fortify/CreateNewUser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace App\Actions\Fortify;

use App\Models\Team;
use App\Models\User;
use ArtisanBuild\Verbstream\Verbstream;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Laravel\Fortify\Contracts\CreatesNewUsers;

class CreateNewUser implements CreatesNewUsers
{
use PasswordValidationRules;

/**
* 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' => $this->passwordRules(),
'terms' => Verbstream::hasTermsAndPrivacyPolicyFeature() ? ['accepted', 'required'] : '',
])->validate();

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

/**
* Create a personal team for the user.
*/
protected function createTeam(User $user): void
{
$user->ownedTeams()->save(Team::forceCreate([
'user_id' => $user->id,
'name' => explode(' ', $user->name, 2)[0]."'s Team",
'personal_team' => true,
]));
}
}
18 changes: 18 additions & 0 deletions app/Actions/Fortify/PasswordValidationRules.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace App\Actions\Fortify;

use Illuminate\Validation\Rules\Password;

trait PasswordValidationRules
{
/**
* Get the validation rules used to validate passwords.
*
* @return array<int, \Illuminate\Contracts\Validation\Rule|array<mixed>|string>
*/
protected function passwordRules(): array
{
return ['required', 'string', Password::default(), 'confirmed'];
}
}
29 changes: 29 additions & 0 deletions app/Actions/Fortify/ResetUserPassword.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace App\Actions\Fortify;

use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Laravel\Fortify\Contracts\ResetsUserPasswords;

class ResetUserPassword implements ResetsUserPasswords
{
use PasswordValidationRules;

/**
* Validate and reset the user's forgotten password.
*
* @param array<string, string> $input
*/
public function reset(User $user, array $input): void
{
Validator::make($input, [
'password' => $this->passwordRules(),
])->validate();

$user->forceFill([
'password' => Hash::make($input['password']),
])->save();
}
}
32 changes: 32 additions & 0 deletions app/Actions/Fortify/UpdateUserPassword.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace App\Actions\Fortify;

use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Laravel\Fortify\Contracts\UpdatesUserPasswords;

class UpdateUserPassword implements UpdatesUserPasswords
{
use PasswordValidationRules;

/**
* Validate and update the user's password.
*
* @param array<string, string> $input
*/
public function update(User $user, array $input): void
{
Validator::make($input, [
'current_password' => ['required', 'string', 'current_password:web'],
'password' => $this->passwordRules(),
], [
'current_password.current_password' => __('The provided password does not match your current password.'),
])->validateWithBag('updatePassword');

$user->forceFill([
'password' => Hash::make($input['password']),
])->save();
}
}
54 changes: 54 additions & 0 deletions app/Actions/Fortify/UpdateUserProfileInformation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace App\Actions\Fortify;

use App\Models\User;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Laravel\Fortify\Contracts\UpdatesUserProfileInformation;

class UpdateUserProfileInformation implements UpdatesUserProfileInformation
{
/**
* Validate and update the given user's profile information.
*
* @param array<string, mixed> $input
*/
public function update(User $user, array $input): void
{
Validator::make($input, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'max:255', Rule::unique('users')->ignore($user->id)],
'photo' => ['nullable', 'mimes:jpg,jpeg,png', 'max:1024'],
])->validateWithBag('updateProfileInformation');

if (isset($input['photo'])) {
$user->updateProfilePhoto($input['photo']);
}

if ($input['email'] !== $user->email) {
$this->updateVerifiedUser($user, $input);
} else {
$user->forceFill([
'name' => $input['name'],
'email' => $input['email'],
])->save();
}
}

/**
* Update the given verified user's profile information.
*
* @param array<string, string> $input
*/
protected function updateVerifiedUser(User $user, array $input): void
{
$user->forceFill([
'name' => $input['name'],
'email' => $input['email'],
'email_verified_at' => null,
])->save();

$user->sendEmailVerificationNotification();
}
}
82 changes: 82 additions & 0 deletions app/Actions/Jetstream/AddTeamMember.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

namespace App\Actions\Jetstream;

use App\Models\Team;
use App\Models\User;
use ArtisanBuild\Verbstream\Contracts\AddsTeamMembers;
use ArtisanBuild\Verbstream\Events\AddingTeamMember;
use ArtisanBuild\Verbstream\Events\TeamMemberAdded;
use ArtisanBuild\Verbstream\Rules\Role;
use ArtisanBuild\Verbstream\Verbstream;
use Closure;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Validator;

class AddTeamMember implements AddsTeamMembers
{
/**
* Add a new team member to the given team.
*/
public function add(User $user, Team $team, string $email, ?string $role = null): void
{
Gate::forUser($user)->authorize('addTeamMember', $team);

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

$newTeamMember = Verbstream::findUserByEmailOrFail($email);

AddingTeamMember::dispatch($team, $newTeamMember);

$team->users()->attach(
$newTeamMember, ['role' => $role]
);

TeamMemberAdded::dispatch($team, $newTeamMember);
}

/**
* Validate the add member operation.
*/
protected function validate(Team $team, string $email, ?string $role): void
{
Validator::make([
'email' => $email,
'role' => $role,
], $this->rules(), [
'email.exists' => __('We were unable to find a registered user with this email address.'),
])->after(
$this->ensureUserIsNotAlreadyOnTeam($team, $email)
)->validateWithBag('addTeamMember');
}

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

/**
* Ensure that the user is not already on the team.
*/
protected function ensureUserIsNotAlreadyOnTeam(Team $team, string $email): Closure
{
return function ($validator) use ($team, $email): void {
$validator->errors()->addIf(
$team->hasUserWithEmail($email),
'email',
__('This user already belongs to the team.')
);
};
}
}
38 changes: 38 additions & 0 deletions app/Actions/Jetstream/CreateTeam.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace App\Actions\Jetstream;

use App\Models\Team;
use App\Models\User;
use ArtisanBuild\Verbstream\Contracts\CreatesTeams;
use ArtisanBuild\Verbstream\Events\AddingTeam;
use ArtisanBuild\Verbstream\Verbstream;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Validator;

class CreateTeam implements CreatesTeams
{
/**
* Validate and create a new team for the given user.
*
* @param array<string, string> $input
*/
public function create(User $user, array $input): Model
{
Gate::forUser($user)->authorize('create', Verbstream::newTeamModel());

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

AddingTeam::dispatch($user);

$user->switchTeam($team = $user->ownedTeams()->create([
'name' => $input['name'],
'personal_team' => false,
]));

return $team;
}
}
17 changes: 17 additions & 0 deletions app/Actions/Jetstream/DeleteTeam.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace App\Actions\Jetstream;

use App\Models\Team;
use ArtisanBuild\Verbstream\Contracts\DeletesTeams;

class DeleteTeam implements DeletesTeams
{
/**
* Delete the given team.
*/
public function delete(Team $team): void
{
$team->purge();
}
}
42 changes: 42 additions & 0 deletions app/Actions/Jetstream/DeleteUser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace App\Actions\Jetstream;

use App\Models\Team;
use App\Models\User;
use ArtisanBuild\Verbstream\Contracts\DeletesTeams;
use ArtisanBuild\Verbstream\Contracts\DeletesUsers;
use Illuminate\Support\Facades\DB;

class DeleteUser implements DeletesUsers
{
/**
* Create a new action instance.
*/
public function __construct(protected DeletesTeams $deletesTeams) {}

/**
* Delete the given user.
*/
public function delete(User $user): void
{
DB::transaction(function () use ($user): void {
$this->deleteTeams($user);
$user->deleteProfilePhoto();
$user->tokens->each->delete();
$user->delete();
});
}

/**
* Delete the teams and team associations attached to the user.
*/
protected function deleteTeams(User $user): void
{
$user->teams()->detach();

$user->ownedTeams->each(function (Team $team): void {
$this->deletesTeams->delete($team);
});
}
}
Loading
Loading