This commit is contained in:
Andras Bacsai 2023-06-09 15:55:21 +02:00
parent 127d42d873
commit b097842d01
45 changed files with 322 additions and 158 deletions

View File

@ -43,34 +43,21 @@ public function create(array $input): User
if (User::count() == 0) { if (User::count() == 0) {
// If this is the first user, make them the root user // If this is the first user, make them the root user
// Team is already created in the database/seeders/ProductionSeeder.php // Team is already created in the database/seeders/ProductionSeeder.php
$team = Team::find(0);
$user = User::create([ $user = User::create([
'id' => 0, 'id' => 0,
'name' => $input['name'], 'name' => $input['name'],
'email' => $input['email'], 'email' => $input['email'],
'password' => Hash::make($input['password']), 'password' => Hash::make($input['password']),
'is_root_user' => true,
]); ]);
$team = $user->teams()->first();
} else { } else {
$team = Team::create([
'name' => explode(' ', $input['name'], 2)[0] . "'s Team",
'personal_team' => true,
]);
$user = User::create([ $user = User::create([
'name' => $input['name'], 'name' => $input['name'],
'email' => $input['email'], 'email' => $input['email'],
'password' => Hash::make($input['password']), '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 // Set session variable
session(['currentTeam' => $user->currentTeam = $team]); session(['currentTeam' => $user->currentTeam = $team]);
return $user; return $user;

View File

@ -51,4 +51,16 @@ public function emails()
return redirect()->route('dashboard'); 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,
]);
}
} }

View File

@ -31,7 +31,7 @@ public function delete()
$this->destination->delete(); $this->destination->delete();
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} catch (\Exception $e) { } catch (\Exception $e) {
return general_error_handler($e); return general_error_handler(err: $e);
} }
} }
} }

View File

@ -14,7 +14,7 @@ public function upgrade()
$this->visible = true; $this->visible = true;
dispatch(new InstanceAutoUpdateJob(force: true)); dispatch(new InstanceAutoUpdateJob(force: true));
} catch (\Exception $e) { } catch (\Exception $e) {
return general_error_handler($e, $this); return general_error_handler(err: $e, that: $this);
} }
} }
} }

View File

@ -26,15 +26,15 @@ class EmailSettings extends Component
'model.extra_attributes.smtp_test_recipients' => 'nullable', 'model.extra_attributes.smtp_test_recipients' => 'nullable',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
'model.extra_attributes.smtp_from_address' => '', 'model.extra_attributes.smtp_from_address' => 'From Address',
'model.extra_attributes.smtp_from_name' => '', 'model.extra_attributes.smtp_from_name' => 'From Name',
'model.extra_attributes.smtp_recipients' => '', 'model.extra_attributes.smtp_recipients' => 'Recipients',
'model.extra_attributes.smtp_host' => '', 'model.extra_attributes.smtp_host' => 'Host',
'model.extra_attributes.smtp_port' => '', 'model.extra_attributes.smtp_port' => 'Port',
'model.extra_attributes.smtp_encryption' => '', 'model.extra_attributes.smtp_encryption' => 'Encryption',
'model.extra_attributes.smtp_username' => '', 'model.extra_attributes.smtp_username' => 'Username',
'model.extra_attributes.smtp_password' => '', 'model.extra_attributes.smtp_password' => 'Password',
'model.extra_attributes.smtp_test_recipients' => '', 'model.extra_attributes.smtp_test_recipients' => 'Test Recipients',
]; ];
public function copySMTP() public function copySMTP()
{ {

View File

@ -35,7 +35,7 @@ public function changePrivateKey()
$this->private_key->save(); $this->private_key->save();
session('currentTeam')->privateKeys = PrivateKey::where('team_id', session('currentTeam')->id)->get(); session('currentTeam')->privateKeys = PrivateKey::where('team_id', session('currentTeam')->id)->get();
} catch (\Exception $e) { } catch (\Exception $e) {
return general_error_handler($e, $this); return general_error_handler(err: $e, that: $this);
} }
} }
} }

View File

@ -38,7 +38,7 @@ public function createPrivateKey()
} }
return redirect()->route('private-key.show', ['private_key_uuid' => $private_key->uuid]); return redirect()->route('private-key.show', ['private_key_uuid' => $private_key->uuid]);
} catch (\Exception $e) { } catch (\Exception $e) {
return general_error_handler($e, $this); return general_error_handler(err: $e, that: $this);
} }
} }
} }

View File

@ -28,8 +28,8 @@ public function submit()
User::where('id', $this->userId)->update([ User::where('id', $this->userId)->update([
'name' => $this->name, 'name' => $this->name,
]); ]);
} catch (\Throwable $error) { } catch (\Throwable $e) {
return general_error_handler($error, $this); return general_error_handler(err: $e, that: $this);
} }
} }
} }

View File

@ -37,8 +37,8 @@ public function cancel()
// Remove builder container // Remove builder container
instant_remote_process(["docker rm -f {$this->deployment_uuid}"], $this->application->destination->server, throwError: false, repeat: 25); instant_remote_process(["docker rm -f {$this->deployment_uuid}"], $this->application->destination->server, throwError: false, repeat: 25);
queue_next_deployment($this->application); queue_next_deployment($this->application);
} catch (\Throwable $th) { } catch (\Throwable $e) {
return general_error_handler($th, $this); return general_error_handler(err: $e, that: $this);
} }
} }
} }

View File

@ -27,7 +27,7 @@ public function submit($data)
$this->application->refresh(); $this->application->refresh();
$this->emit('clearAddEnv'); $this->emit('clearAddEnv');
} catch (\Exception $e) { } catch (\Exception $e) {
return general_error_handler($e, $this); return general_error_handler(err: $e, that: $this);
} }
} }
} }

View File

@ -112,7 +112,7 @@ public function submit()
$this->application->fqdn = $domains->implode(','); $this->application->fqdn = $domains->implode(',');
$this->application->save(); $this->application->save();
} catch (\Exception $e) { } catch (\Exception $e) {
return general_error_handler($e, $this); return general_error_handler(err: $e, that: $this);
} }
} }
} }

View File

@ -41,9 +41,9 @@ public function load_prs()
['rate_limit_remaining' => $rate_limit_remaining, 'data' => $data] = get_from_git_api($this->application->source, "/repos/{$this->application->git_repository}/pulls"); ['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->rate_limit_remaining = $rate_limit_remaining;
$this->pull_requests = $data->sortBy('number')->values(); $this->pull_requests = $data->sortBy('number')->values();
} catch (\Throwable $th) { } catch (\Throwable $e) {
$this->rate_limit_remaining = 0; $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) public function deploy(int $pull_request_id, string|null $pull_request_html_url = null)
@ -70,8 +70,8 @@ public function deploy(int $pull_request_id, string|null $pull_request_html_url
'deployment_uuid' => $this->deployment_uuid, 'deployment_uuid' => $this->deployment_uuid,
'environment_name' => $this->parameters['environment_name'], 'environment_name' => $this->parameters['environment_name'],
]); ]);
} catch (\Throwable $th) { } catch (\Throwable $e) {
return general_error_handler($th, $this); return general_error_handler(err: $e, that: $this);
} }
} }
public function stop(int $pull_request_id) public function stop(int $pull_request_id)
@ -83,8 +83,8 @@ public function stop(int $pull_request_id)
instant_remote_process(["docker rm -f $container_name"], $this->application->destination->server, throwError: false); 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(); ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->delete();
$this->application->refresh(); $this->application->refresh();
} catch (\Throwable $th) { } catch (\Throwable $e) {
return general_error_handler($th, $this); return general_error_handler(err: $e, that: $this);
} }
} }
} }

View File

@ -44,7 +44,7 @@ public function submit()
$this->validate(); $this->validate();
$this->application->save(); $this->application->save();
} catch (\Exception $e) { } catch (\Exception $e) {
return general_error_handler($e, $this); return general_error_handler(err: $e, that: $this);
} }
} }
} }

View File

@ -63,7 +63,7 @@ public function loadImages()
]; ];
})->toArray(); })->toArray();
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return general_error_handler(err: $e, that: $this);
} }
} }
} }

View File

@ -27,7 +27,7 @@ public function submit($data)
$this->application->refresh(); $this->application->refresh();
$this->emit('clearAddStorage'); $this->emit('clearAddStorage');
} catch (\Exception $e) { } catch (\Exception $e) {
return general_error_handler($e, $this); return general_error_handler(err: $e, that: $this);
} }
} }
} }

View File

@ -138,7 +138,7 @@ public function submit()
'project_uuid' => $project->uuid, 'project_uuid' => $project->uuid,
]); ]);
} catch (\Exception $e) { } catch (\Exception $e) {
return general_error_handler($e, $this); return general_error_handler(err: $e, that: $this);
} }
} }
public function instantSave() public function instantSave()

View File

@ -96,7 +96,7 @@ public function submit()
'application_uuid' => $application->uuid, 'application_uuid' => $application->uuid,
]); ]);
} catch (\Exception $e) { } catch (\Exception $e) {
return general_error_handler($e, $this); return general_error_handler(err: $e, that: $this);
} }
} }
} }

View File

@ -68,8 +68,8 @@ public function load_branches()
try { try {
['data' => $data] = get_from_git_api($this->git_source, "/repos/{$this->git_repository}/branches"); ['data' => $data] = get_from_git_api($this->git_source, "/repos/{$this->git_repository}/branches");
$this->branches = collect($data)->pluck('name')->toArray(); $this->branches = collect($data)->pluck('name')->toArray();
} catch (\Throwable $th) { } catch (\Throwable $e) {
return general_error_handler($th, $this); return general_error_handler(err: $e, that: $this);
} }
} }
private function get_git_source() private function get_git_source()
@ -135,7 +135,7 @@ public function submit()
'application_uuid' => $application->uuid, 'application_uuid' => $application->uuid,
]); ]);
} catch (\Exception $e) { } catch (\Exception $e) {
return general_error_handler($e, $this); return general_error_handler(err: $e, that: $this);
} }
} }
} }

View File

@ -29,7 +29,7 @@ public function runCommand()
$activity = remote_process([$this->command], Server::where('uuid', $this->server)->first(), ignore_errors: true); $activity = remote_process([$this->command], Server::where('uuid', $this->server)->first(), ignore_errors: true);
$this->emit('newMonitorActivity', $activity->id); $this->emit('newMonitorActivity', $activity->id);
} catch (\Exception $e) { } catch (\Exception $e) {
return general_error_handler($e); return general_error_handler(err: $e);
} }
} }
} }

View File

@ -55,7 +55,7 @@ public function validateServer()
$this->dockerComposeVersion = 'Not installed.'; $this->dockerComposeVersion = 'Not installed.';
} }
} catch (\Exception $e) { } catch (\Exception $e) {
return general_error_handler($e, $this); return general_error_handler(err: $e, that: $this);
} }
} }
public function delete() public function delete()

View File

@ -61,7 +61,7 @@ public function submit()
$server->settings->save(); $server->settings->save();
return redirect()->route('server.show', $server->uuid); return redirect()->route('server.show', $server->uuid);
} catch (\Exception $e) { } catch (\Exception $e) {
return general_error_handler($e); return general_error_handler(err: $e);
} }
} }
} }

View File

@ -64,7 +64,7 @@ public function saveConfiguration(Server $server)
"echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml", "echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml",
], $server); ], $server);
} catch (\Exception $e) { } catch (\Exception $e) {
return general_error_handler($e); return general_error_handler(err: $e);
} }
} }
public function resetProxy() public function resetProxy()
@ -72,7 +72,7 @@ public function resetProxy()
try { try {
$this->proxy_settings = resolve(CheckProxySettingsInSync::class)($this->server, true); $this->proxy_settings = resolve(CheckProxySettingsInSync::class)($this->server, true);
} catch (\Exception $e) { } catch (\Exception $e) {
return general_error_handler($e); return general_error_handler(err: $e);
} }
} }
public function checkProxySettingsInSync() public function checkProxySettingsInSync()
@ -80,7 +80,7 @@ public function checkProxySettingsInSync()
try { try {
$this->proxy_settings = resolve(CheckProxySettingsInSync::class)($this->server); $this->proxy_settings = resolve(CheckProxySettingsInSync::class)($this->server);
} catch (\Exception $e) { } catch (\Exception $e) {
return general_error_handler($e); return general_error_handler(err: $e);
} }
} }
} }

View File

@ -35,7 +35,7 @@ public function submit()
$this->validate(); $this->validate();
$this->github_app->save(); $this->github_app->save();
} catch (\Exception $e) { } catch (\Exception $e) {
return general_error_handler($e, $this); return general_error_handler(err: $e, that: $this);
} }
} }
public function instantSave() public function instantSave()
@ -45,7 +45,7 @@ public function instantSave()
$this->github_app->save(); $this->github_app->save();
$this->emit('saved', 'GitHub settings updated!'); $this->emit('saved', 'GitHub settings updated!');
} catch (\Exception $e) { } catch (\Exception $e) {
return general_error_handler($e, $this); return general_error_handler(err: $e, that: $this);
} }
} }
public function mount() public function mount()
@ -63,7 +63,7 @@ public function delete()
$this->github_app->delete(); $this->github_app->delete();
redirect()->route('dashboard'); redirect()->route('dashboard');
} catch (\Exception $e) { } catch (\Exception $e) {
return general_error_handler($e, $this); return general_error_handler(err: $e, that: $this);
} }
} }
} }

View File

@ -43,7 +43,7 @@ public function createGitHubApp()
]); ]);
redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]); redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]);
} catch (\Exception $e) { } catch (\Exception $e) {
return general_error_handler($e, $this); return general_error_handler(err: $e, that: $this);
} }
} }
} }

View 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);
}
}
}

View File

@ -8,10 +8,19 @@
class Member extends Component class Member extends Component
{ {
public User $member; 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() public function remove()
{ {
$this->member->teams()->detach(session('currentTeam')); $this->member->teams()->detach(session('currentTeam'));
session(['currentTeam' => session('currentTeam')->fresh()]);
$this->emit('reloadWindow'); $this->emit('reloadWindow');
} }
} }

View File

@ -62,6 +62,10 @@ public function privateKeys()
} }
public function members() 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);
} }
} }

View 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);
}
}

View File

@ -33,11 +33,37 @@ protected static function boot()
static::creating(function (Model $model) { static::creating(function (Model $model) {
$model->uuid = (string) new Cuid2(7); $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() public function isAdmin()
{ {
ray(session('currentTeam')); if (auth()->user()->id === 0) {
return session('currentTeam'); 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() public function isInstanceAdmin()
{ {
@ -51,14 +77,12 @@ public function isInstanceAdmin()
} }
public function teams() 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() public function otherTeams()
{ {
$team_id = session('currentTeam')->id; $team_id = session('currentTeam')->id;
@ -66,6 +90,13 @@ public function otherTeams()
return $team->id != $team_id; 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() public function resources()
{ {
$team_id = session('currentTeam')->id; $team_id = session('currentTeam')->id;

View File

@ -6,30 +6,31 @@
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
use Illuminate\Support\Str; 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 { try {
ray('ERROR OCCURED: ' . $e->getMessage()); ray('ERROR OCCURED: ' . $err->getMessage());
if ($e instanceof QueryException) { if ($err instanceof QueryException) {
if ($e->errorInfo[0] === '23505') { if ($err->errorInfo[0] === '23505') {
throw new \Exception('Duplicate entry found.', '23505'); throw new \Exception($customErrorMessage ?? 'Duplicate entry found.', '23505');
} else if (count($e->errorInfo) === 4) { } else if (count($err->errorInfo) === 4) {
throw new \Exception($e->errorInfo[3]); throw new \Exception($customErrorMessage ?? $err->errorInfo[3]);
} else { } else {
throw new \Exception($e->errorInfo[2]); throw new \Exception($customErrorMessage ?? $err->errorInfo[2]);
} }
} else { } else {
throw new \Exception($e->getMessage()); throw new \Exception($customErrorMessage ?? $err->getMessage());
} }
} catch (\Throwable $error) { } catch (\Throwable $error) {
if ($that) { if ($that) {
return $that->emit('error', $error->getMessage()); return $that->emit('error', $customErrorMessage ?? $error->getMessage());
} elseif ($isJson) { } elseif ($isJson) {
return response()->json([ return response()->json([
'code' => $error->getCode(), 'code' => $error->getCode(),
'error' => $error->getMessage(), 'error' => $error->getMessage(),
]); ]);
} else { } else {
ray($customErrorMessage);
ray($error); ray($error);
} }
} }

View File

@ -14,7 +14,6 @@ public function up(): void
Schema::create('users', function (Blueprint $table) { Schema::create('users', function (Blueprint $table) {
$table->id(); $table->id();
$table->string('uuid')->unique(); $table->string('uuid')->unique();
$table->boolean('is_root_user')->default(false);
$table->string('name')->default('Your Name Here'); $table->string('name')->default('Your Name Here');
$table->string('email')->unique(); $table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable(); $table->timestamp('email_verified_at')->nullable();

View File

@ -15,7 +15,7 @@ public function up(): void
$table->id(); $table->id();
$table->foreignId('team_id'); $table->foreignId('team_id');
$table->foreignId('user_id'); $table->foreignId('user_id');
$table->string('role')->nullable(); $table->string('role')->default('readonly');
$table->timestamps(); $table->timestamps();
$table->unique(['team_id', 'user_id']); $table->unique(['team_id', 'user_id']);

View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('team_invitations', function (Blueprint $table) {
$table->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');
}
};

View File

@ -46,13 +46,13 @@ public function run(): void
} }
// Add first Team if it doesn't exist // Add first Team if it doesn't exist
if (Team::find(0) == null) { // if (Team::find(0) == null) {
Team::create([ // Team::create([
'id' => 0, // 'id' => 0,
'name' => "Root's Team", // 'name' => "Root's Team",
'personal_team' => true, // 'personal_team' => true,
]); // ]);
} // }
// Save SSH Keys for the Coolify Host // Save SSH Keys for the Coolify Host
$coolify_key_name = "id.root@host.docker.internal"; $coolify_key_name = "id.root@host.docker.internal";

View File

@ -11,42 +11,13 @@ class TeamSeeder extends Seeder
{ {
public function run(): void public function run(): void
{ {
$root_user = User::find(0); $normal_user_in_root_team = User::find(1);
$normal_user = User::find(1); $root_user_personal_team = Team::find(0);
$root_user_personal_team = Team::create([ $normal_user_in_root_team->teams()->attach($root_user_personal_team);
'id' => 0,
'name' => "Root Team", $normal_user_not_in_root_team = User::find(2);
'personal_team' => true, $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']);
$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,
]);
} }
} }

View File

@ -13,11 +13,14 @@ public function run(): void
'id' => 0, 'id' => 0,
'name' => 'Root User', 'name' => 'Root User',
'email' => 'test@example.com', 'email' => 'test@example.com',
'is_root_user' => true,
]); ]);
User::factory()->create([ User::factory()->create([
'name' => 'Normal User', 'name' => 'Normal User (but in root team)',
'email' => 'test2@example.com', 'email' => 'test2@example.com',
]); ]);
User::factory()->create([
'name' => 'Normal User (not in root team)',
'email' => 'test3@example.com',
]);
} }
} }

View File

@ -5,10 +5,12 @@
<x-forms.button type="submit"> <x-forms.button type="submit">
Save Save
</x-forms.button> </x-forms.button>
@if ($model->extra_attributes->discord_active)
<x-forms.button class="text-white normal-case btn btn-xs no-animation btn-primary" <x-forms.button class="text-white normal-case btn btn-xs no-animation btn-primary"
wire:click="sendTestNotification"> wire:click="sendTestNotification">
Send Test Notifications Send Test Notifications
</x-forms.button> </x-forms.button>
@endif
</div> </div>
<div class="flex flex-col gap-2 xl:flex-row w-96"> <div class="flex flex-col gap-2 xl:flex-row w-96">
<x-forms.checkbox instantSave id="model.extra_attributes.discord_active" label="Notification Enabled" /> <x-forms.checkbox instantSave id="model.extra_attributes.discord_active" label="Notification Enabled" />

View File

@ -10,10 +10,12 @@
Copy from Instance Settings Copy from Instance Settings
</x-forms.button> </x-forms.button>
@endif @endif
@if ($model->extra_attributes->smtp_active)
<x-forms.button class="text-white normal-case btn btn-xs no-animation btn-primary" <x-forms.button class="text-white normal-case btn btn-xs no-animation btn-primary"
wire:click="sendTestNotification"> wire:click="sendTestNotification">
Send Test Notifications Send Test Notifications
</x-forms.button> </x-forms.button>
@endif
</div> </div>
<div class="flex flex-col w-96"> <div class="flex flex-col w-96">
<x-forms.checkbox instantSave id="model.extra_attributes.smtp_active" label="Notification Enabled" /> <x-forms.checkbox instantSave id="model.extra_attributes.smtp_active" label="Notification Enabled" />

View File

@ -0,0 +1,6 @@
<div>
<form wire:submit.prevent='inviteByLink' class="flex items-center gap-2">
<x-forms.input id="email" type="email" name="email" placeholder="Email" />
<x-forms.button type="submit">Invite with link</x-forms.button>
</form>
</div>

View File

@ -1,17 +1,25 @@
<tr class="border-coolgray-200"> <tr class="border-coolgray-200">
<th class="text-warning">{{ $member->id }}</th> {{-- <th class="text-warning">{{ $member->id }}</th> --}}
<td>{{ $member->name }}</td> <th>{{ $member->name }}</th>
<td>{{ $member->email }}</td> <td>{{ $member->email }}</td>
<td>{{ data_get($member, 'pivot.role') }}</td>
<td> <td>
{{-- @if (auth()->user()->isAdmin()) {{-- 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')
<x-forms.button wire:click="makeAdmin">Make admin</x-forms.button> <x-forms.button wire:click="makeAdmin">Make admin</x-forms.button>
@else @else
<x-forms.button disabled>Make admin</x-forms.button> <x-forms.button wire:click="makeReadonly">Make readonly</x-forms.button>
@endif --}} @endif
@if ($member->id !== auth()->user()->id)
<x-forms.button wire:click="remove">Remove</x-forms.button> <x-forms.button wire:click="remove">Remove</x-forms.button>
@else @else
<x-forms.button disabled>Remove</x-forms.button> <x-forms.button disabled>Remove</x-forms.button>
@endif @endif
@else
<x-forms.button disabled>Remove</x-forms.button>
@endif
@endif
</td> </td>
</tr> </tr>

View File

@ -5,24 +5,46 @@
<table class="table"> <table class="table">
<thead> <thead>
<tr class="text-warning border-coolgray-200"> <tr class="text-warning border-coolgray-200">
<th></th>
<th>Name</th> <th>Name</th>
<th>Email</th> <th>Email</th>
<th>Role</th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@foreach (session('currentTeam')->members as $member) @foreach (auth()->user()->currentTeam()->members->sortBy('name') as $member)
<livewire:team.member :member="$member" :wire:key="$member->id" /> <livewire:team.member :member="$member" :wire:key="$member->id" />
@endforeach @endforeach
</tbody> </tbody>
</table> </table>
</div> </div>
{{-- <div class="py-4"> @if (auth()->user()->isAdmin())
<h3>Invite a new member</h3> @if (!$transactional_emails_active)
<div class="py-4">
<h3 class="pb-4">Invite a new member</h3>
<form class="flex items-center gap-2"> <form class="flex items-center gap-2">
<x-forms.input type="email" name="email" placeholder="Email" /> <x-forms.input type="email" name="email" placeholder="Email" />
<x-forms.button>Invite</x-forms.button> <x-forms.button>Invite</x-forms.button>
</form> </form>
</div> --}} </div>
@else
<div class="py-4">
<h3 class="pb-4">Invite a new member</h3>
<livewire:team.invite-link />
<div class="text-sm text-warning">You need to configure SMTP settings before you can invite a new member
via
email.
</div>
</div>
@if ($invitations->count() > 0)
<h2 class="pb-2">Pending Invitations</h2>
@endif
@foreach ($invitations as $invite)
<div class="flex gap-2 text-sm">
<div>{{ $invite->email }}</div>
<div>Sent: {{ $invite->created_at }}</div>
</div>
@endforeach
@endif
@endif
</x-layout> </x-layout>

View File

@ -18,6 +18,10 @@
Route::get('/health', function () { Route::get('/health', function () {
return 'OK'; return 'OK';
}); });
Route::get('/invitation/{uuid}', function () {
ray('Invitation', request()->route('uuid'));
return 'OK';
});
// Route::middleware('auth:sanctum')->get('/user', function (Request $request) { // Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
// return $request->user(); // return $request->user();
// }); // });

View File

@ -54,7 +54,7 @@
Route::get('/settings', [Controller::class, 'settings'])->name('settings.configuration'); Route::get('/settings', [Controller::class, 'settings'])->name('settings.configuration');
Route::get('/settings/emails', [Controller::class, 'emails'])->name('settings.emails'); Route::get('/settings/emails', [Controller::class, 'emails'])->name('settings.emails');
Route::get('/profile', fn () => view('profile', ['request' => request()]))->name('profile'); 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('/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'); Route::get('/command-center', fn () => view('command-center', ['servers' => Server::validated()->get()]))->name('command-center');
}); });

View File

@ -37,7 +37,7 @@
$github_app->save(); $github_app->save();
return redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]); return redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]);
} catch (\Exception $e) { } catch (\Exception $e) {
return general_error_handler($e); return general_error_handler(err: $e);
} }
}); });
@ -53,7 +53,7 @@
} }
return redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]); return redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]);
} catch (\Exception $e) { } catch (\Exception $e) {
return general_error_handler($e); return general_error_handler(err: $e);
} }
}); });
Route::post('/source/github/events', function () { Route::post('/source/github/events', function () {
@ -166,6 +166,6 @@
} }
} }
} catch (\Exception $e) { } catch (\Exception $e) {
return general_error_handler($e); return general_error_handler(err: $e);
} }
}); });