Skip to content

Commit ed4fd3c

Browse files
committed
allow only partial server editing for maintainers
1 parent d62b6da commit ed4fd3c

File tree

5 files changed

+76
-17
lines changed

5 files changed

+76
-17
lines changed

README.md

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33

44
A Laravel 12 application for preparing and deploying Minecraft modpack releases by comparing a modpack against a remote server snapshot, applying override rules, and syncing changed files over SFTP. The app provides a simple Web UI for server admins.
55

6-
**This README is current as of the workspace snapshot.**
7-
86
## Highlights
97
- Compare a modpack (zip or extracted folder) with a server snapshot to detect added, removed, and modified files.
108
- Apply override rules per-path using plain text replace, JSON merge, or YAML merge.
@@ -19,7 +17,7 @@ A Laravel 12 application for preparing and deploying Minecraft modpack releases
1917

2018
## Prerequisites
2119
- Composer 2
22-
- Node.js 18+ and npm/yarn
20+
- for dev only: (Node.js 18+ and npm/yarn)
2321
- SFTP-accessible remote server (password or key auth)
2422

2523
## Quick Setup (developer)
@@ -30,11 +28,7 @@ composer install
3028
cp .env.example .env
3129
php artisan key:generate
3230

33-
# Configure your DB in .env (SQLite / MySQL / Postgres)
34-
# For quick SQLite development:
35-
# echo "DB_CONNECTION=sqlite" >> .env
36-
# touch database/database.sqlite
37-
31+
# default env will use sqlite
3832
php artisan migrate --force
3933
npm install
4034
# For development with HMR

app/Filament/Resources/Servers/Schemas/ServerForm.php

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,20 @@
44

55
namespace App\Filament\Resources\Servers\Schemas;
66

7+
use App\Models\User;
78
use Filament\Forms\Components\Select;
89
use Filament\Forms\Components\TagsInput;
910
use Filament\Forms\Components\TextInput;
1011
use Filament\Schemas\Schema;
12+
use Illuminate\Support\Facades\Auth;
1113

1214
class ServerForm
1315
{
1416
public static function configure(Schema $schema): Schema
1517
{
18+
/** @var User $user */
19+
$user = Auth::user();
20+
1621
return $schema
1722
->components([
1823
TextInput::make('name')
@@ -28,18 +33,21 @@ public static function configure(Schema $schema): Schema
2833
TextInput::make('host')
2934
->label('Host')
3035
->required()
31-
->default('5.9.78.56'),
36+
->default('mc.stonebound.net')
37+
->disabled(fn ($operation) => $operation === 'update' && ! $user->isAdmin()),
3238
TextInput::make('port')
3339
->label('SSH port')
3440
->required()
3541
->numeric()
3642
->minValue(1)
3743
->maxValue(65535)
38-
->default(3875),
44+
->default(3875)
45+
->disabled(fn ($operation) => $operation === 'update' && ! $user->isAdmin()),
3946
TextInput::make('username')
4047
->label('SSH username')
4148
->required()
42-
->placeholder('deployer'),
49+
->placeholder('deployer')
50+
->disabled(fn ($operation) => $operation === 'update' && ! $user->isAdmin()),
4351
Select::make('auth_type')
4452
->label('Authentication')
4553
->options([
@@ -49,23 +57,28 @@ public static function configure(Schema $schema): Schema
4957
->required()
5058
->default('password')
5159
->native(false)
52-
->reactive(),
60+
->reactive()
61+
->disabled(fn ($operation) => $operation === 'update' && ! $user->isAdmin()),
5362
TextInput::make('password')
5463
->label('Password')
5564
->password()
5665
->revealable()
57-
->required(fn ($get) => $get('auth_type') === 'password')
66+
->required(fn ($get, $operation) => $operation === 'create' && $get('auth_type') === 'password')
5867
->hidden(fn ($get) => $get('auth_type') !== 'password')
59-
->dehydrated(fn ($get, $state) => $get('auth_type') === 'password' && filled($state)),
68+
->dehydrated(fn ($get, $state) => $get('auth_type') === 'password' && filled($state))
69+
->disabled(fn ($operation) => $operation === 'update' && ! $user->isAdmin()),
6070
TextInput::make('private_key_path')
6171
->label('Private key path')
6272
->placeholder('/home/user/.ssh/id_rsa')
73+
->required(fn ($get, $operation) => $operation === 'create' && $get('auth_type') === 'private_key')
6374
->hidden(fn ($get) => $get('auth_type') !== 'private_key')
64-
->dehydrated(fn ($get) => $get('auth_type') === 'private_key'),
75+
->dehydrated(fn ($get) => $get('auth_type') === 'private_key')
76+
->disabled(fn ($operation) => $operation === 'update' && ! $user->isAdmin()),
6577
TextInput::make('remote_root_path')
6678
->label('Remote root path')
6779
->required()
68-
->default('/'),
80+
->default('/')
81+
->disabled(fn ($operation) => $operation === 'update' && ! $user->isAdmin()),
6982
TagsInput::make('include_paths')
7083
->label('Include folders')
7184
->placeholder('Add folders to include')

app/Models/User.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ public function isMaintainer(): bool
6060
return in_array($this->role, [UserRole::Maintainer, UserRole::Admin], true);
6161
}
6262

63+
public function isAdmin(): bool
64+
{
65+
return $this->role === UserRole::Admin;
66+
}
67+
6368
public function canAccessPanel(Panel $panel): bool
6469
{
6570
return true;

app/Policies/ServerPolicy.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Policies;
6+
7+
use App\Enums\UserRole;
8+
use App\Models\Server;
9+
use App\Models\User;
10+
11+
class ServerPolicy
12+
{
13+
public function viewAny(User $user): bool
14+
{
15+
return in_array($user->role, [UserRole::Admin, UserRole::Maintainer], true);
16+
}
17+
18+
public function view(User $user, Server $server): bool
19+
{
20+
return in_array($user->role, [UserRole::Admin, UserRole::Maintainer], true);
21+
}
22+
23+
public function update(User $user, Server $server): bool
24+
{
25+
// Only Admins and Maintainers can update provider, game version, and title
26+
return in_array($user->role, [UserRole::Admin, UserRole::Maintainer], true);
27+
}
28+
29+
public function create(User $user): bool
30+
{
31+
return $user->role === UserRole::Admin;
32+
}
33+
34+
public function delete(User $user, Server $server): bool
35+
{
36+
return $user->role === UserRole::Admin;
37+
}
38+
}

tests/Feature/ServersControllerTest.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public function test_list_servers(): void
4040

4141
public function test_create_server(): void
4242
{
43-
$user = User::factory()->create(['role' => 'maintainer']);
43+
$user = User::factory()->create(['role' => 'admin']);
4444
$this->actingAs($user);
4545

4646
Livewire::test(\App\Filament\Resources\Servers\Pages\CreateServer::class)
@@ -56,4 +56,13 @@ public function test_create_server(): void
5656

5757
$this->assertDatabaseHas('servers', ['name' => 'TestSrv', 'host' => 'host.local']);
5858
}
59+
60+
public function test_maintainer_cant_create_server(): void
61+
{
62+
$user = User::factory()->create(['role' => 'maintainer']);
63+
$this->actingAs($user);
64+
65+
Livewire::test(\App\Filament\Resources\Servers\Pages\CreateServer::class)
66+
->assertForbidden();
67+
}
5968
}

0 commit comments

Comments
 (0)