diff --git a/app/Actions/Fortify/CreateNewUser.php b/app/Actions/Fortify/CreateNewUser.php index 90f1bacb4..cd4c63fd8 100644 --- a/app/Actions/Fortify/CreateNewUser.php +++ b/app/Actions/Fortify/CreateNewUser.php @@ -43,34 +43,21 @@ class CreateNewUser implements CreatesNewUsers if (User::count() == 0) { // If this is the first user, make them the root user // Team is already created in the database/seeders/ProductionSeeder.php - $team = Team::find(0); $user = User::create([ 'id' => 0, 'name' => $input['name'], 'email' => $input['email'], 'password' => Hash::make($input['password']), - 'is_root_user' => true, ]); + $team = $user->teams()->first(); } else { - $team = Team::create([ - 'name' => explode(' ', $input['name'], 2)[0] . "'s Team", - 'personal_team' => true, - ]); $user = User::create([ 'name' => $input['name'], 'email' => $input['email'], 'password' => Hash::make($input['password']), - 'is_root_user' => false, ]); + $team = $user->teams()->first(); } - - // Add user to team - DB::table('team_user')->insert([ - 'user_id' => $user->id, - 'team_id' => $team->id, - 'role' => 'admin', - ]); - // Set session variable session(['currentTeam' => $user->currentTeam = $team]); return $user; diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index ab87e2fec..63d24a219 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -51,4 +51,16 @@ class Controller extends BaseController return redirect()->route('dashboard'); } } + public function team() + { + ray(auth()->user()->isAdmin()); + $invitations = []; + if (auth()->user()->isAdmin()) { + $invitations = auth()->user()->currentTeam()->invitations; + } + return view('team.show', [ + 'transactional_emails_active' => data_get(InstanceSettings::get(), 'extra_attributes.smtp_host') ? true : false, + 'invitations' => $invitations, + ]); + } } diff --git a/app/Http/Livewire/Destination/Form.php b/app/Http/Livewire/Destination/Form.php index bc1c24c88..19ba37cfe 100644 --- a/app/Http/Livewire/Destination/Form.php +++ b/app/Http/Livewire/Destination/Form.php @@ -31,7 +31,7 @@ class Form extends Component $this->destination->delete(); return redirect()->route('dashboard'); } catch (\Exception $e) { - return general_error_handler($e); + return general_error_handler(err: $e); } } } diff --git a/app/Http/Livewire/ForceUpgrade.php b/app/Http/Livewire/ForceUpgrade.php index 9642a6af4..a41651bf0 100644 --- a/app/Http/Livewire/ForceUpgrade.php +++ b/app/Http/Livewire/ForceUpgrade.php @@ -14,7 +14,7 @@ class ForceUpgrade extends Component $this->visible = true; dispatch(new InstanceAutoUpdateJob(force: true)); } catch (\Exception $e) { - return general_error_handler($e, $this); + return general_error_handler(err: $e, that: $this); } } } diff --git a/app/Http/Livewire/Notifications/EmailSettings.php b/app/Http/Livewire/Notifications/EmailSettings.php index ae2b9a815..5e7c8323c 100644 --- a/app/Http/Livewire/Notifications/EmailSettings.php +++ b/app/Http/Livewire/Notifications/EmailSettings.php @@ -26,15 +26,15 @@ class EmailSettings extends Component 'model.extra_attributes.smtp_test_recipients' => 'nullable', ]; protected $validationAttributes = [ - 'model.extra_attributes.smtp_from_address' => '', - 'model.extra_attributes.smtp_from_name' => '', - 'model.extra_attributes.smtp_recipients' => '', - 'model.extra_attributes.smtp_host' => '', - 'model.extra_attributes.smtp_port' => '', - 'model.extra_attributes.smtp_encryption' => '', - 'model.extra_attributes.smtp_username' => '', - 'model.extra_attributes.smtp_password' => '', - 'model.extra_attributes.smtp_test_recipients' => '', + 'model.extra_attributes.smtp_from_address' => 'From Address', + 'model.extra_attributes.smtp_from_name' => 'From Name', + 'model.extra_attributes.smtp_recipients' => 'Recipients', + 'model.extra_attributes.smtp_host' => 'Host', + 'model.extra_attributes.smtp_port' => 'Port', + 'model.extra_attributes.smtp_encryption' => 'Encryption', + 'model.extra_attributes.smtp_username' => 'Username', + 'model.extra_attributes.smtp_password' => 'Password', + 'model.extra_attributes.smtp_test_recipients' => 'Test Recipients', ]; public function copySMTP() { diff --git a/app/Http/Livewire/PrivateKey/Change.php b/app/Http/Livewire/PrivateKey/Change.php index 27889feb1..4a7070c7f 100644 --- a/app/Http/Livewire/PrivateKey/Change.php +++ b/app/Http/Livewire/PrivateKey/Change.php @@ -35,7 +35,7 @@ class Change extends Component $this->private_key->save(); session('currentTeam')->privateKeys = PrivateKey::where('team_id', session('currentTeam')->id)->get(); } catch (\Exception $e) { - return general_error_handler($e, $this); + return general_error_handler(err: $e, that: $this); } } } diff --git a/app/Http/Livewire/PrivateKey/Create.php b/app/Http/Livewire/PrivateKey/Create.php index e8b22e0af..fda4ca722 100644 --- a/app/Http/Livewire/PrivateKey/Create.php +++ b/app/Http/Livewire/PrivateKey/Create.php @@ -38,7 +38,7 @@ class Create extends Component } return redirect()->route('private-key.show', ['private_key_uuid' => $private_key->uuid]); } catch (\Exception $e) { - return general_error_handler($e, $this); + return general_error_handler(err: $e, that: $this); } } } diff --git a/app/Http/Livewire/Profile/Form.php b/app/Http/Livewire/Profile/Form.php index 4b463b4d3..d09f540da 100644 --- a/app/Http/Livewire/Profile/Form.php +++ b/app/Http/Livewire/Profile/Form.php @@ -28,8 +28,8 @@ class Form extends Component User::where('id', $this->userId)->update([ 'name' => $this->name, ]); - } catch (\Throwable $error) { - return general_error_handler($error, $this); + } catch (\Throwable $e) { + return general_error_handler(err: $e, that: $this); } } } diff --git a/app/Http/Livewire/Project/Application/DeploymentNavbar.php b/app/Http/Livewire/Project/Application/DeploymentNavbar.php index 20809d2db..8f39ff762 100644 --- a/app/Http/Livewire/Project/Application/DeploymentNavbar.php +++ b/app/Http/Livewire/Project/Application/DeploymentNavbar.php @@ -37,8 +37,8 @@ class DeploymentNavbar extends Component // Remove builder container instant_remote_process(["docker rm -f {$this->deployment_uuid}"], $this->application->destination->server, throwError: false, repeat: 25); queue_next_deployment($this->application); - } catch (\Throwable $th) { - return general_error_handler($th, $this); + } catch (\Throwable $e) { + return general_error_handler(err: $e, that: $this); } } } diff --git a/app/Http/Livewire/Project/Application/EnvironmentVariable/All.php b/app/Http/Livewire/Project/Application/EnvironmentVariable/All.php index f2d6f9a64..401ec2ed0 100644 --- a/app/Http/Livewire/Project/Application/EnvironmentVariable/All.php +++ b/app/Http/Livewire/Project/Application/EnvironmentVariable/All.php @@ -27,7 +27,7 @@ class All extends Component $this->application->refresh(); $this->emit('clearAddEnv'); } catch (\Exception $e) { - return general_error_handler($e, $this); + return general_error_handler(err: $e, that: $this); } } } diff --git a/app/Http/Livewire/Project/Application/General.php b/app/Http/Livewire/Project/Application/General.php index 2591482c8..38cc106c5 100644 --- a/app/Http/Livewire/Project/Application/General.php +++ b/app/Http/Livewire/Project/Application/General.php @@ -112,7 +112,7 @@ class General extends Component $this->application->fqdn = $domains->implode(','); $this->application->save(); } catch (\Exception $e) { - return general_error_handler($e, $this); + return general_error_handler(err: $e, that: $this); } } } diff --git a/app/Http/Livewire/Project/Application/Previews.php b/app/Http/Livewire/Project/Application/Previews.php index 9e924c0c1..acb7268c5 100644 --- a/app/Http/Livewire/Project/Application/Previews.php +++ b/app/Http/Livewire/Project/Application/Previews.php @@ -41,9 +41,9 @@ class Previews extends Component ['rate_limit_remaining' => $rate_limit_remaining, 'data' => $data] = get_from_git_api($this->application->source, "/repos/{$this->application->git_repository}/pulls"); $this->rate_limit_remaining = $rate_limit_remaining; $this->pull_requests = $data->sortBy('number')->values(); - } catch (\Throwable $th) { + } catch (\Throwable $e) { $this->rate_limit_remaining = 0; - return general_error_handler($th, $this); + return general_error_handler(err: $e, that: $this); } } public function deploy(int $pull_request_id, string|null $pull_request_html_url = null) @@ -70,8 +70,8 @@ class Previews extends Component 'deployment_uuid' => $this->deployment_uuid, 'environment_name' => $this->parameters['environment_name'], ]); - } catch (\Throwable $th) { - return general_error_handler($th, $this); + } catch (\Throwable $e) { + return general_error_handler(err: $e, that: $this); } } public function stop(int $pull_request_id) @@ -83,8 +83,8 @@ class Previews extends Component instant_remote_process(["docker rm -f $container_name"], $this->application->destination->server, throwError: false); ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->delete(); $this->application->refresh(); - } catch (\Throwable $th) { - return general_error_handler($th, $this); + } catch (\Throwable $e) { + return general_error_handler(err: $e, that: $this); } } } diff --git a/app/Http/Livewire/Project/Application/ResourceLimits.php b/app/Http/Livewire/Project/Application/ResourceLimits.php index 161d29ecc..2b67f004e 100644 --- a/app/Http/Livewire/Project/Application/ResourceLimits.php +++ b/app/Http/Livewire/Project/Application/ResourceLimits.php @@ -44,7 +44,7 @@ class ResourceLimits extends Component $this->validate(); $this->application->save(); } catch (\Exception $e) { - return general_error_handler($e, $this); + return general_error_handler(err: $e, that: $this); } } } diff --git a/app/Http/Livewire/Project/Application/Rollback.php b/app/Http/Livewire/Project/Application/Rollback.php index b1bbcec7f..ff89fce70 100644 --- a/app/Http/Livewire/Project/Application/Rollback.php +++ b/app/Http/Livewire/Project/Application/Rollback.php @@ -63,7 +63,7 @@ class Rollback extends Component ]; })->toArray(); } catch (\Throwable $e) { - return general_error_handler($e, $this); + return general_error_handler(err: $e, that: $this); } } } diff --git a/app/Http/Livewire/Project/Application/Storages/All.php b/app/Http/Livewire/Project/Application/Storages/All.php index 0d1d2966b..d6691857e 100644 --- a/app/Http/Livewire/Project/Application/Storages/All.php +++ b/app/Http/Livewire/Project/Application/Storages/All.php @@ -27,7 +27,7 @@ class All extends Component $this->application->refresh(); $this->emit('clearAddStorage'); } catch (\Exception $e) { - return general_error_handler($e, $this); + return general_error_handler(err: $e, that: $this); } } } diff --git a/app/Http/Livewire/Project/New/GithubPrivateRepository.php b/app/Http/Livewire/Project/New/GithubPrivateRepository.php index 4f78ba771..73b07984b 100644 --- a/app/Http/Livewire/Project/New/GithubPrivateRepository.php +++ b/app/Http/Livewire/Project/New/GithubPrivateRepository.php @@ -138,7 +138,7 @@ class GithubPrivateRepository extends Component 'project_uuid' => $project->uuid, ]); } catch (\Exception $e) { - return general_error_handler($e, $this); + return general_error_handler(err: $e, that: $this); } } public function instantSave() diff --git a/app/Http/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php b/app/Http/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php index 71860358c..52baa8790 100644 --- a/app/Http/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php +++ b/app/Http/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php @@ -96,7 +96,7 @@ class GithubPrivateRepositoryDeployKey extends Component 'application_uuid' => $application->uuid, ]); } catch (\Exception $e) { - return general_error_handler($e, $this); + return general_error_handler(err: $e, that: $this); } } } diff --git a/app/Http/Livewire/Project/New/PublicGitRepository.php b/app/Http/Livewire/Project/New/PublicGitRepository.php index b5a3b3403..e048b0106 100644 --- a/app/Http/Livewire/Project/New/PublicGitRepository.php +++ b/app/Http/Livewire/Project/New/PublicGitRepository.php @@ -68,8 +68,8 @@ class PublicGitRepository extends Component try { ['data' => $data] = get_from_git_api($this->git_source, "/repos/{$this->git_repository}/branches"); $this->branches = collect($data)->pluck('name')->toArray(); - } catch (\Throwable $th) { - return general_error_handler($th, $this); + } catch (\Throwable $e) { + return general_error_handler(err: $e, that: $this); } } private function get_git_source() @@ -135,7 +135,7 @@ class PublicGitRepository extends Component 'application_uuid' => $application->uuid, ]); } catch (\Exception $e) { - return general_error_handler($e, $this); + return general_error_handler(err: $e, that: $this); } } } diff --git a/app/Http/Livewire/RunCommand.php b/app/Http/Livewire/RunCommand.php index ea5d5596e..187ea3a35 100755 --- a/app/Http/Livewire/RunCommand.php +++ b/app/Http/Livewire/RunCommand.php @@ -29,7 +29,7 @@ class RunCommand extends Component $activity = remote_process([$this->command], Server::where('uuid', $this->server)->first(), ignore_errors: true); $this->emit('newMonitorActivity', $activity->id); } catch (\Exception $e) { - return general_error_handler($e); + return general_error_handler(err: $e); } } } diff --git a/app/Http/Livewire/Server/Form.php b/app/Http/Livewire/Server/Form.php index 21fc9b281..6680042a2 100644 --- a/app/Http/Livewire/Server/Form.php +++ b/app/Http/Livewire/Server/Form.php @@ -55,7 +55,7 @@ class Form extends Component $this->dockerComposeVersion = 'Not installed.'; } } catch (\Exception $e) { - return general_error_handler($e, $this); + return general_error_handler(err: $e, that: $this); } } public function delete() diff --git a/app/Http/Livewire/Server/New/ByIp.php b/app/Http/Livewire/Server/New/ByIp.php index 632ae89ef..23a76f5f9 100644 --- a/app/Http/Livewire/Server/New/ByIp.php +++ b/app/Http/Livewire/Server/New/ByIp.php @@ -61,7 +61,7 @@ class ByIp extends Component $server->settings->save(); return redirect()->route('server.show', $server->uuid); } catch (\Exception $e) { - return general_error_handler($e); + return general_error_handler(err: $e); } } } diff --git a/app/Http/Livewire/Server/Proxy.php b/app/Http/Livewire/Server/Proxy.php index 5b3f5e119..4a5eb466d 100644 --- a/app/Http/Livewire/Server/Proxy.php +++ b/app/Http/Livewire/Server/Proxy.php @@ -64,7 +64,7 @@ class Proxy extends Component "echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml", ], $server); } catch (\Exception $e) { - return general_error_handler($e); + return general_error_handler(err: $e); } } public function resetProxy() @@ -72,7 +72,7 @@ class Proxy extends Component try { $this->proxy_settings = resolve(CheckProxySettingsInSync::class)($this->server, true); } catch (\Exception $e) { - return general_error_handler($e); + return general_error_handler(err: $e); } } public function checkProxySettingsInSync() @@ -80,7 +80,7 @@ class Proxy extends Component try { $this->proxy_settings = resolve(CheckProxySettingsInSync::class)($this->server); } catch (\Exception $e) { - return general_error_handler($e); + return general_error_handler(err: $e); } } } diff --git a/app/Http/Livewire/Source/Github/Change.php b/app/Http/Livewire/Source/Github/Change.php index ee06e1748..f0b922088 100644 --- a/app/Http/Livewire/Source/Github/Change.php +++ b/app/Http/Livewire/Source/Github/Change.php @@ -35,7 +35,7 @@ class Change extends Component $this->validate(); $this->github_app->save(); } catch (\Exception $e) { - return general_error_handler($e, $this); + return general_error_handler(err: $e, that: $this); } } public function instantSave() @@ -45,7 +45,7 @@ class Change extends Component $this->github_app->save(); $this->emit('saved', 'GitHub settings updated!'); } catch (\Exception $e) { - return general_error_handler($e, $this); + return general_error_handler(err: $e, that: $this); } } public function mount() @@ -63,7 +63,7 @@ class Change extends Component $this->github_app->delete(); redirect()->route('dashboard'); } catch (\Exception $e) { - return general_error_handler($e, $this); + return general_error_handler(err: $e, that: $this); } } } diff --git a/app/Http/Livewire/Source/Github/Create.php b/app/Http/Livewire/Source/Github/Create.php index d6eb34f40..b95bde919 100644 --- a/app/Http/Livewire/Source/Github/Create.php +++ b/app/Http/Livewire/Source/Github/Create.php @@ -43,7 +43,7 @@ class Create extends Component ]); redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]); } catch (\Exception $e) { - return general_error_handler($e, $this); + return general_error_handler(err: $e, that: $this); } } } diff --git a/app/Http/Livewire/Team/InviteLink.php b/app/Http/Livewire/Team/InviteLink.php new file mode 100644 index 000000000..04159fc15 --- /dev/null +++ b/app/Http/Livewire/Team/InviteLink.php @@ -0,0 +1,51 @@ +email = config('app.env') === 'local' ? 'test@example.com' : ''; + } + public function inviteByLink() + { + $uuid = new Cuid2(32); + $link = url('/') . '/api/invitation/' . $uuid; + try { + $user_exists = User::whereEmail($this->email)->exists(); + if (!$user_exists) { + return general_error_handler(that: $this, customErrorMessage: "$this->email must be registered first (or activate transactional emails to invite via email)."); + } + $invitation = TeamInvitation::where('email', $this->email); + if ($invitation->exists()) { + $created_at = $invitation->first()->created_at; + $diff = $created_at->diffInMinutes(now()); + if ($diff < 11) { + return general_error_handler(that: $this, customErrorMessage: "Invitation already sent and active for $this->email."); + } else { + $invitation->delete(); + } + } + $invitation = TeamInvitation::firstOrCreate([ + 'team_id' => session('currentTeam')->id, + 'email' => $this->email, + 'role' => 'readonly', + 'link' => $link, + ]); + $this->emit('reloadWindow'); + } catch (\Throwable $e) { + $error_message = $e->getMessage(); + if ($e->getCode() === '23505') { + $error_message = 'Invitation already sent.'; + } + return general_error_handler(err: $e, that: $this, customErrorMessage: $error_message); + } + } +} diff --git a/app/Http/Livewire/Team/Member.php b/app/Http/Livewire/Team/Member.php index ad21cc0f4..65cca3038 100644 --- a/app/Http/Livewire/Team/Member.php +++ b/app/Http/Livewire/Team/Member.php @@ -8,10 +8,19 @@ use Livewire\Component; class Member extends Component { public User $member; + public function makeAdmin() + { + $this->member->teams()->updateExistingPivot(session('currentTeam')->id, ['role' => 'admin']); + $this->emit('reloadWindow'); + } + public function makeReadonly() + { + $this->member->teams()->updateExistingPivot(session('currentTeam')->id, ['role' => 'readonly']); + $this->emit('reloadWindow'); + } public function remove() { $this->member->teams()->detach(session('currentTeam')); - session(['currentTeam' => session('currentTeam')->fresh()]); $this->emit('reloadWindow'); } } diff --git a/app/Models/Team.php b/app/Models/Team.php index a1d7c7c13..64bdf3c1c 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -62,6 +62,10 @@ class Team extends BaseModel implements SendsDiscord, SendsEmail } public function members() { - return $this->belongsToMany(User::class, 'team_user', 'team_id', 'user_id'); + return $this->belongsToMany(User::class, 'team_user', 'team_id', 'user_id')->withPivot('role'); + } + public function invitations() + { + return $this->hasMany(TeamInvitation::class); } } diff --git a/app/Models/TeamInvitation.php b/app/Models/TeamInvitation.php new file mode 100644 index 000000000..91ad9837a --- /dev/null +++ b/app/Models/TeamInvitation.php @@ -0,0 +1,19 @@ +belongsTo(Team::class); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 209d1e840..9f64bcd5c 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -33,11 +33,37 @@ class User extends Authenticatable static::creating(function (Model $model) { $model->uuid = (string) new Cuid2(7); }); + static::created(function (User $user) { + $team = [ + 'name' => $user->name . "'s Team", + 'personal_team' => true, + ]; + if ($user->id === 0) { + $team['id'] = 0; + $team['name'] = 'Root Team'; + } + $new_team = Team::create($team); + $user->teams()->attach($new_team, ['role' => 'owner']); + }); } public function isAdmin() { - ray(session('currentTeam')); - return session('currentTeam'); + if (auth()->user()->id === 0) { + ray('is root user'); + return true; + } + $teams = $this->teams()->get(); + + $is_part_of_root_team = $teams->where('id', 0)->first(); + $is_admin_of_root_team = $is_part_of_root_team && + ($is_part_of_root_team->pivot->role === 'admin' || $is_part_of_root_team->pivot->role === 'owner'); + + if ($is_part_of_root_team && $is_admin_of_root_team) { + ray('is admin of root team'); + return true; + } + $role = $teams->where('id', session('currentTeam')->id)->first()->pivot->role; + return $role === 'admin' || $role === 'owner'; } public function isInstanceAdmin() { @@ -51,14 +77,12 @@ class User extends Authenticatable } public function teams() { - return $this->belongsToMany(Team::class); + return $this->belongsToMany(Team::class)->withPivot('role'); + } + public function currentTeam() + { + return $this->teams()->where('team_id', session('currentTeam')->id)->first(); } - - // public function currentTeam() - // { - // return $this->belongsTo(Team::class); - // } - public function otherTeams() { $team_id = session('currentTeam')->id; @@ -66,6 +90,13 @@ class User extends Authenticatable return $team->id != $team_id; }); } + public function role() + { + if ($this->teams()->where('team_id', 0)->first()) { + return 'admin'; + } + return $this->teams()->where('team_id', session('currentTeam')->id)->first()->pivot->role; + } public function resources() { $team_id = session('currentTeam')->id; diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 89725d0bc..8a7a99af8 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -6,30 +6,31 @@ use Illuminate\Support\Facades\Route; use Visus\Cuid2\Cuid2; use Illuminate\Support\Str; -function general_error_handler(\Throwable $e, $that = null, $isJson = false) +function general_error_handler(\Throwable|null $err = null, $that = null, $isJson = false, $customErrorMessage = null) { try { - ray('ERROR OCCURED: ' . $e->getMessage()); - if ($e instanceof QueryException) { - if ($e->errorInfo[0] === '23505') { - throw new \Exception('Duplicate entry found.', '23505'); - } else if (count($e->errorInfo) === 4) { - throw new \Exception($e->errorInfo[3]); + ray('ERROR OCCURED: ' . $err->getMessage()); + if ($err instanceof QueryException) { + if ($err->errorInfo[0] === '23505') { + throw new \Exception($customErrorMessage ?? 'Duplicate entry found.', '23505'); + } else if (count($err->errorInfo) === 4) { + throw new \Exception($customErrorMessage ?? $err->errorInfo[3]); } else { - throw new \Exception($e->errorInfo[2]); + throw new \Exception($customErrorMessage ?? $err->errorInfo[2]); } } else { - throw new \Exception($e->getMessage()); + throw new \Exception($customErrorMessage ?? $err->getMessage()); } } catch (\Throwable $error) { if ($that) { - return $that->emit('error', $error->getMessage()); + return $that->emit('error', $customErrorMessage ?? $error->getMessage()); } elseif ($isJson) { return response()->json([ 'code' => $error->getCode(), 'error' => $error->getMessage(), ]); } else { + ray($customErrorMessage); ray($error); } } diff --git a/database/migrations/2014_10_12_000000_create_users_table.php b/database/migrations/2014_10_12_000000_create_users_table.php index ff7a7e716..623148331 100644 --- a/database/migrations/2014_10_12_000000_create_users_table.php +++ b/database/migrations/2014_10_12_000000_create_users_table.php @@ -14,7 +14,6 @@ return new class extends Migration Schema::create('users', function (Blueprint $table) { $table->id(); $table->string('uuid')->unique(); - $table->boolean('is_root_user')->default(false); $table->string('name')->default('Your Name Here'); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); diff --git a/database/migrations/2023_03_20_112812_create_team_user_table.php b/database/migrations/2023_03_20_112812_create_team_user_table.php index 3e2787630..e794dc645 100644 --- a/database/migrations/2023_03_20_112812_create_team_user_table.php +++ b/database/migrations/2023_03_20_112812_create_team_user_table.php @@ -15,7 +15,7 @@ return new class extends Migration $table->id(); $table->foreignId('team_id'); $table->foreignId('user_id'); - $table->string('role')->nullable(); + $table->string('role')->default('readonly'); $table->timestamps(); $table->unique(['team_id', 'user_id']); diff --git a/database/migrations/2023_03_20_112813_create_team_invitations_table.php b/database/migrations/2023_03_20_112813_create_team_invitations_table.php new file mode 100644 index 000000000..55be41302 --- /dev/null +++ b/database/migrations/2023_03_20_112813_create_team_invitations_table.php @@ -0,0 +1,33 @@ +id(); + $table->foreignId('team_id')->constrained()->cascadeOnDelete(); + $table->string('email'); + $table->string('role')->default('readonly'); + $table->string('link')->nullable(); + $table->timestamps(); + + $table->unique(['team_id', 'email']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('team_invitations'); + } +}; diff --git a/database/migrations/2023_03_20_112813_create_instance_settings_table.php b/database/migrations/2023_03_20_112814_create_instance_settings_table.php similarity index 100% rename from database/migrations/2023_03_20_112813_create_instance_settings_table.php rename to database/migrations/2023_03_20_112814_create_instance_settings_table.php diff --git a/database/seeders/ProductionSeeder.php b/database/seeders/ProductionSeeder.php index d383fc823..253a4ba30 100644 --- a/database/seeders/ProductionSeeder.php +++ b/database/seeders/ProductionSeeder.php @@ -46,13 +46,13 @@ class ProductionSeeder extends Seeder } // Add first Team if it doesn't exist - if (Team::find(0) == null) { - Team::create([ - 'id' => 0, - 'name' => "Root's Team", - 'personal_team' => true, - ]); - } + // if (Team::find(0) == null) { + // Team::create([ + // 'id' => 0, + // 'name' => "Root's Team", + // 'personal_team' => true, + // ]); + // } // Save SSH Keys for the Coolify Host $coolify_key_name = "id.root@host.docker.internal"; diff --git a/database/seeders/TeamSeeder.php b/database/seeders/TeamSeeder.php index fa4439540..c2bab6ac5 100644 --- a/database/seeders/TeamSeeder.php +++ b/database/seeders/TeamSeeder.php @@ -11,42 +11,13 @@ class TeamSeeder extends Seeder { public function run(): void { - $root_user = User::find(0); - $normal_user = User::find(1); + $normal_user_in_root_team = User::find(1); + $root_user_personal_team = Team::find(0); - $root_user_personal_team = Team::create([ - 'id' => 0, - 'name' => "Root Team", - 'personal_team' => true, - ]); - $root_user_other_team = Team::create([ - 'name' => "Root User's Other Team", - 'personal_team' => false, - ]); - $normal_user_personal_team = Team::create([ - 'name' => 'Normal Team', - 'personal_team' => true, - ]); - // $root_user->teams()->attach($root_user_personal_team); - // $root_user->teams()->attach($root_user_other_team); - // $normal_user->teams()->attach($normal_user_personal_team); - // $normal_user->teams()->attach($root_user_personal_team); - DB::table('team_user')->insert([ - 'team_id' => $root_user_personal_team->id, - 'user_id' => $root_user->id, - 'role' => 'admin', - ]); - DB::table('team_user')->insert([ - 'team_id' => $root_user_other_team->id, - 'user_id' => $root_user->id, - ]); - DB::table('team_user')->insert([ - 'team_id' => $normal_user_personal_team->id, - 'user_id' => $normal_user->id, - ]); - DB::table('team_user')->insert([ - 'team_id' => $root_user_personal_team->id, - 'user_id' => $normal_user->id, - ]); + $normal_user_in_root_team->teams()->attach($root_user_personal_team); + + $normal_user_not_in_root_team = User::find(2); + $normal_user_in_root_team_personal_team = Team::find(1); + $normal_user_not_in_root_team->teams()->attach($normal_user_in_root_team_personal_team, ['role' => 'admin']); } } diff --git a/database/seeders/UserSeeder.php b/database/seeders/UserSeeder.php index a5ab30dda..19d3aa42e 100644 --- a/database/seeders/UserSeeder.php +++ b/database/seeders/UserSeeder.php @@ -13,11 +13,14 @@ class UserSeeder extends Seeder 'id' => 0, 'name' => 'Root User', 'email' => 'test@example.com', - 'is_root_user' => true, ]); User::factory()->create([ - 'name' => 'Normal User', + 'name' => 'Normal User (but in root team)', 'email' => 'test2@example.com', ]); + User::factory()->create([ + 'name' => 'Normal User (not in root team)', + 'email' => 'test3@example.com', + ]); } } diff --git a/resources/views/livewire/notifications/discord-settings.blade.php b/resources/views/livewire/notifications/discord-settings.blade.php index 548a26279..6272608ce 100644 --- a/resources/views/livewire/notifications/discord-settings.blade.php +++ b/resources/views/livewire/notifications/discord-settings.blade.php @@ -5,10 +5,12 @@ Save - - Send Test Notifications - + @if ($model->extra_attributes->discord_active) + + Send Test Notifications + + @endif
diff --git a/resources/views/livewire/notifications/email-settings.blade.php b/resources/views/livewire/notifications/email-settings.blade.php index e528632ff..66829ebc7 100644 --- a/resources/views/livewire/notifications/email-settings.blade.php +++ b/resources/views/livewire/notifications/email-settings.blade.php @@ -10,10 +10,12 @@ Copy from Instance Settings @endif - - Send Test Notifications - + @if ($model->extra_attributes->smtp_active) + + Send Test Notifications + + @endif
diff --git a/resources/views/livewire/team/invite-link.blade.php b/resources/views/livewire/team/invite-link.blade.php new file mode 100644 index 000000000..fce4cadc0 --- /dev/null +++ b/resources/views/livewire/team/invite-link.blade.php @@ -0,0 +1,6 @@ +
+
+ + Invite with link + +
diff --git a/resources/views/livewire/team/member.blade.php b/resources/views/livewire/team/member.blade.php index 865c11a6a..55164cc3a 100644 --- a/resources/views/livewire/team/member.blade.php +++ b/resources/views/livewire/team/member.blade.php @@ -1,17 +1,25 @@ - {{ $member->id }} - {{ $member->name }} + {{-- {{ $member->id }} --}} + {{ $member->name }} {{ $member->email }} + {{ data_get($member, 'pivot.role') }} - {{-- @if (auth()->user()->isAdmin()) - Make admin - @else - Make admin - @endif --}} - @if ($member->id !== auth()->user()->id) - Remove - @else - Remove + {{-- TODO: This is not good --}} + @if (auth()->user()->isAdmin()) + @if ($member->id !== auth()->user()->id) + @if (data_get($member, 'pivot.role') !== 'owner') + @if (data_get($member, 'pivot.role') !== 'admin') + Make admin + @else + Make readonly + @endif + Remove + @else + Remove + @endif + @else + Remove + @endif @endif diff --git a/resources/views/team/show.blade.php b/resources/views/team/show.blade.php index 7ab15bace..c9262cdbd 100644 --- a/resources/views/team/show.blade.php +++ b/resources/views/team/show.blade.php @@ -5,24 +5,46 @@ - + - @foreach (session('currentTeam')->members as $member) + @foreach (auth()->user()->currentTeam()->members->sortBy('name') as $member) @endforeach
Name EmailRole Actions
- {{--
-

Invite a new member

-
- - Invite - -
--}} + @if (auth()->user()->isAdmin()) + @if (!$transactional_emails_active) +
+

Invite a new member

+
+ + Invite + +
+ @else +
+

Invite a new member

+ +
You need to configure SMTP settings before you can invite a new member + via + email. +
+
+ @if ($invitations->count() > 0) +

Pending Invitations

+ @endif + @foreach ($invitations as $invite) +
+
{{ $invite->email }}
+
Sent: {{ $invite->created_at }}
+
+ @endforeach + @endif + @endif diff --git a/routes/api.php b/routes/api.php index 7005837de..62d3a4851 100644 --- a/routes/api.php +++ b/routes/api.php @@ -18,6 +18,10 @@ use Illuminate\Support\Facades\Route; Route::get('/health', function () { return 'OK'; }); +Route::get('/invitation/{uuid}', function () { + ray('Invitation', request()->route('uuid')); + return 'OK'; +}); // Route::middleware('auth:sanctum')->get('/user', function (Request $request) { // return $request->user(); // }); diff --git a/routes/web.php b/routes/web.php index 658b674f8..5effbbc75 100644 --- a/routes/web.php +++ b/routes/web.php @@ -54,7 +54,7 @@ Route::middleware(['auth'])->group(function () { Route::get('/settings', [Controller::class, 'settings'])->name('settings.configuration'); Route::get('/settings/emails', [Controller::class, 'emails'])->name('settings.emails'); Route::get('/profile', fn () => view('profile', ['request' => request()]))->name('profile'); - Route::get('/profile/team', fn () => view('team.show'))->name('team.show'); + Route::get('/profile/team', [Controller::class, 'team'])->name('team.show'); Route::get('/profile/team/notifications', fn () => view('team.notifications'))->name('team.notifications'); Route::get('/command-center', fn () => view('command-center', ['servers' => Server::validated()->get()]))->name('command-center'); }); diff --git a/routes/webhooks.php b/routes/webhooks.php index a0ee3e720..14244b69a 100644 --- a/routes/webhooks.php +++ b/routes/webhooks.php @@ -37,7 +37,7 @@ Route::get('/source/github/redirect', function () { $github_app->save(); return redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]); } catch (\Exception $e) { - return general_error_handler($e); + return general_error_handler(err: $e); } }); @@ -53,7 +53,7 @@ Route::get('/source/github/install', function () { } return redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]); } catch (\Exception $e) { - return general_error_handler($e); + return general_error_handler(err: $e); } }); Route::post('/source/github/events', function () { @@ -166,6 +166,6 @@ Route::post('/source/github/events', function () { } } } catch (\Exception $e) { - return general_error_handler($e); + return general_error_handler(err: $e); } });