roles
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
51
app/Http/Livewire/Team/InviteLink.php
Normal file
51
app/Http/Livewire/Team/InviteLink.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Team;
|
||||
|
||||
use App\Models\TeamInvitation;
|
||||
use App\Models\User;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class InviteLink extends Component
|
||||
{
|
||||
public string $email;
|
||||
public function mount()
|
||||
{
|
||||
$this->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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
19
app/Models/TeamInvitation.php
Normal file
19
app/Models/TeamInvitation.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class TeamInvitation extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'team_id',
|
||||
'email',
|
||||
'role',
|
||||
'link',
|
||||
];
|
||||
public function team()
|
||||
{
|
||||
return $this->belongsTo(Team::class);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user