diff --git a/app/Http/Controllers/Admin/PermissionController.php b/app/Http/Controllers/Admin/PermissionController.php
new file mode 100644
index 0000000..3b30ad4
--- /dev/null
+++ b/app/Http/Controllers/Admin/PermissionController.php
@@ -0,0 +1,79 @@
+orderBy('group')
+ ->orderBy('id')
+ ->get()
+ ->groupBy('group');
+
+ $roles = ['admin', 'bk', 'wali_kelas', 'staff'];
+
+ $rolePermissions = [];
+ foreach ($roles as $role) {
+ $rolePermissions[$role] = DB::table('role_permissions')
+ ->where('role', $role)
+ ->pluck('permission_id')
+ ->toArray();
+ }
+
+ return view('settings.permissions', compact('permissions', 'roles', 'rolePermissions'));
+ }
+
+ public function update(Request $request): RedirectResponse
+ {
+ $data = $request->validate([
+ 'permissions' => ['nullable', 'array'],
+ 'permissions.*' => ['array'],
+ ]);
+
+ // Clear all role_permissions first
+ DB::table('role_permissions')->delete();
+
+ $now = now();
+
+ if (! empty($data['permissions'])) {
+ foreach ($data['permissions'] as $role => $permissionIds) {
+ foreach ($permissionIds as $permissionId) {
+ DB::table('role_permissions')->insert([
+ 'role' => $role,
+ 'permission_id' => (int) $permissionId,
+ 'created_at' => $now,
+ 'updated_at' => $now,
+ ]);
+ }
+ }
+ }
+
+ // Always give admin all permissions
+ $allPermissionIds = DB::table('permissions')->pluck('id');
+ foreach ($allPermissionIds as $pid) {
+ DB::table('role_permissions')->insertOrIgnore([
+ 'role' => 'admin',
+ 'permission_id' => $pid,
+ 'created_at' => $now,
+ 'updated_at' => $now,
+ ]);
+ }
+
+ // Clear cache
+ foreach (['admin', 'bk', 'wali_kelas', 'staff'] as $role) {
+ Cache::forget('role_permissions:' . $role);
+ }
+
+ return redirect()->route('settings.permissions')
+ ->with('success', 'Hak akses berhasil diperbarui.');
+ }
+}
diff --git a/app/Http/Middleware/CheckRole.php b/app/Http/Middleware/CheckRole.php
new file mode 100644
index 0000000..31f1328
--- /dev/null
+++ b/app/Http/Middleware/CheckRole.php
@@ -0,0 +1,32 @@
+user();
+
+ if (! $user) {
+ abort(403, 'Unauthorized.');
+ }
+
+ foreach ($roles as $role) {
+ if ($user->role === $role) {
+ return $next($request);
+ }
+ }
+
+ abort(403, 'Anda tidak memiliki izin untuk mengakses halaman ini.');
+ }
+}
diff --git a/app/Http/Middleware/PermissionMiddleware.php b/app/Http/Middleware/PermissionMiddleware.php
new file mode 100644
index 0000000..59e8bc3
--- /dev/null
+++ b/app/Http/Middleware/PermissionMiddleware.php
@@ -0,0 +1,47 @@
+user();
+
+ if (! $user) {
+ abort(403, 'Unauthorized.');
+ }
+
+ // Admin always has all permissions
+ if ($user->role === 'admin') {
+ return $next($request);
+ }
+
+ // Cache role permissions (cleared when role_permissions changes)
+ $rolePermissions = Cache::remember('role_permissions:' . $user->role, 3600, function () use ($user) {
+ return \DB::table('role_permissions')
+ ->join('permissions', 'role_permissions.permission_id', '=', 'permissions.id')
+ ->where('role_permissions.role', $user->role)
+ ->pluck('permissions.key')
+ ->toArray();
+ });
+
+ foreach ($permissions as $permission) {
+ if (in_array($permission, $rolePermissions)) {
+ return $next($request);
+ }
+ }
+
+ abort(403, 'Anda tidak memiliki izin untuk mengakses halaman ini.');
+ }
+}
diff --git a/app/Models/User.php b/app/Models/User.php
index a8f250a..704ce6c 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -28,6 +28,23 @@ class User extends Authenticatable
public function isAdmin(): bool { return $this->role === 'admin'; }
public function isBK(): bool { return $this->role === 'bk'; }
public function isWaliKelas(): bool { return $this->role === 'wali_kelas'; }
+ public function isStaff(): bool { return $this->role === 'staff'; }
+
+ public function canPermission(string $permission): bool
+ {
+ if ($this->isAdmin()) {
+ return true;
+ }
+ return in_array($permission, \Illuminate\Support\Facades\Cache::remember(
+ 'role_permissions:' . $this->role,
+ 3600,
+ fn () => \DB::table('role_permissions')
+ ->join('permissions', 'role_permissions.permission_id', '=', 'permissions.id')
+ ->where('role_permissions.role', $this->role)
+ ->pluck('permissions.key')
+ ->toArray()
+ ));
+ }
public function recordedViolations()
{
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
index 7ad9677..ae22a4c 100644
--- a/app/Providers/AppServiceProvider.php
+++ b/app/Providers/AppServiceProvider.php
@@ -3,6 +3,8 @@
namespace App\Providers;
use Illuminate\Pagination\Paginator;
+use Illuminate\Support\Facades\Blade;
+use Illuminate\Support\Facades\Cache;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
@@ -21,5 +23,24 @@ class AppServiceProvider extends ServiceProvider
public function boot(): void
{
Paginator::defaultView('vendor.pagination.tailwind');
+
+ // Blade directive: @canPermission('sync-data')
+ Blade::if('canPermission', function (string $permission) {
+ $user = auth()->user();
+ if (! $user) {
+ return false;
+ }
+ if ($user->role === 'admin') {
+ return true;
+ }
+ $rolePermissions = Cache::remember('role_permissions:' . $user->role, 3600, function () use ($user) {
+ return \DB::table('role_permissions')
+ ->join('permissions', 'role_permissions.permission_id', '=', 'permissions.id')
+ ->where('role_permissions.role', $user->role)
+ ->pluck('permissions.key')
+ ->toArray();
+ });
+ return in_array($permission, $rolePermissions);
+ });
}
}
diff --git a/bootstrap/app.php b/bootstrap/app.php
index 7a2848f..af2176c 100644
--- a/bootstrap/app.php
+++ b/bootstrap/app.php
@@ -12,7 +12,10 @@ return Application::configure(basePath: dirname(__DIR__))
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
- //
+ $middleware->alias([
+ 'role' => \App\Http\Middleware\CheckRole::class,
+ 'permission' => \App\Http\Middleware\PermissionMiddleware::class,
+ ]);
})
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->shouldRenderJsonWhen(
diff --git a/database/migrations/2026_07_23_063835_create_permissions_tables.php b/database/migrations/2026_07_23_063835_create_permissions_tables.php
new file mode 100644
index 0000000..d153a99
--- /dev/null
+++ b/database/migrations/2026_07_23_063835_create_permissions_tables.php
@@ -0,0 +1,89 @@
+id();
+ $table->string('key', 100)->unique();
+ $table->string('group', 50);
+ $table->string('label', 100);
+ $table->text('description')->nullable();
+ $table->timestamps();
+ });
+
+ Schema::create('role_permissions', function (Blueprint $table) {
+ $table->id();
+ $table->string('role', 50);
+ $table->foreignId('permission_id')->constrained()->cascadeOnDelete();
+ $table->timestamps();
+
+ $table->unique(['role', 'permission_id']);
+ });
+
+ // Seed default permissions
+ $permissions = [
+ // Master Data (admin + bk default)
+ ['categories-manage', 'master-data', 'Kelola Kategori Pelanggaran'],
+ ['violation-types-manage', 'master-data', 'Kelola Jenis Pelanggaran'],
+ ['thresholds-manage', 'master-data', 'Kelola Ambang SP'],
+ ['violations-export', 'master-data', 'Export Data Pelanggaran'],
+
+ // Administrasi (admin only default)
+ ['settings-manage', 'administrasi', 'Pengaturan Aplikasi'],
+ ['users-manage', 'administrasi', 'Manajemen User'],
+ ['sync-data', 'administrasi', 'Sinkronisasi Data'],
+ ['backup-database', 'administrasi', 'Backup Database'],
+ ['reset-application', 'administrasi', 'Reset Aplikasi'],
+ ['import-data', 'administrasi', 'Import Data'],
+ ['export-master', 'administrasi', 'Export Master Data'],
+ ];
+
+ $now = now();
+ foreach ($permissions as [$key, $group, $label]) {
+ DB::table('permissions')->insert([
+ 'key' => $key,
+ 'group' => $group,
+ 'label' => $label,
+ 'description' => null,
+ 'created_at' => $now,
+ 'updated_at' => $now,
+ ]);
+ }
+
+ // Default: admin gets all permissions, bk gets master-data
+ $adminRole = 'admin';
+ $bkRole = 'bk';
+ $masterKeys = ['categories-manage', 'violation-types-manage', 'thresholds-manage', 'violations-export'];
+
+ foreach ($permissions as [$key, $group, $label]) {
+ DB::table('role_permissions')->insert([
+ 'role' => $adminRole,
+ 'permission_id' => DB::table('permissions')->where('key', $key)->value('id'),
+ 'created_at' => $now,
+ 'updated_at' => $now,
+ ]);
+
+ if (in_array($key, $masterKeys)) {
+ DB::table('role_permissions')->insert([
+ 'role' => $bkRole,
+ 'permission_id' => DB::table('permissions')->where('key', $key)->value('id'),
+ 'created_at' => $now,
+ 'updated_at' => $now,
+ ]);
+ }
+ }
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists('role_permissions');
+ Schema::dropIfExists('permissions');
+ }
+};
diff --git a/resources/views/components/nav-item.blade.php b/resources/views/components/nav-item.blade.php
index 2f0dd2f..c8ad86a 100644
--- a/resources/views/components/nav-item.blade.php
+++ b/resources/views/components/nav-item.blade.php
@@ -20,6 +20,8 @@ $faIcons = [
'cog' => 'fa-solid fa-gear',
'users-cog' => 'fa-solid fa-user-gear',
'database' => 'fa-solid fa-database',
+ 'arrows-rotate' => 'fa-solid fa-arrows-rotate',
+ 'lock' => 'fa-solid fa-lock',
];
$faClass = $faIcons[$icon] ?? 'fa-solid fa-circle';
@endphp
diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php
index 1c90f39..0fb8829 100644
--- a/resources/views/layouts/app.blade.php
+++ b/resources/views/layouts/app.blade.php
@@ -44,24 +44,55 @@
{{-- Footer --}}
diff --git a/resources/views/settings/permissions.blade.php b/resources/views/settings/permissions.blade.php
new file mode 100644
index 0000000..4d40ffd
--- /dev/null
+++ b/resources/views/settings/permissions.blade.php
@@ -0,0 +1,71 @@
+@extends('layouts.app')
+
+@section('title', 'Hak Akses Role')
+
+@section('content')
+
+
+
Hak Akses Role
+
Atur permission/fitur apa saja yang bisa diakses oleh setiap role pengguna.
+
+
+ @if (session('success'))
+
{{ session('success') }}
+ @endif
+
+
+
+@endsection
diff --git a/routes/web.php b/routes/web.php
index 7e4e4a2..2fc8259 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -22,92 +22,127 @@ Route::get('/login', [LoginController::class, 'showLoginForm'])->name('login');
Route::post('/login', [LoginController::class, 'login']);
Route::post('/logout', [LoginController::class, 'logout'])->name('logout');
-// Protected routes
+// ===== Protected Routes (auth required) =====
Route::middleware('auth')->group(function () {
- Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
- // Notifications
- Route::get('/notifications/unread-count', [DashboardController::class, 'getUnreadCount'])->name('notifications.unread-count');
- Route::get('/calendar/data', [DashboardController::class, 'getCalendarData'])->name('calendar.data');
- Route::get('/attendances', [\App\Http\Controllers\AttendanceController::class, 'index'])->name('attendances.index');
- Route::get('/attendances/create', [\App\Http\Controllers\AttendanceController::class, 'create'])->name('attendances.create');
- Route::post('/attendances', [\App\Http\Controllers\AttendanceController::class, 'store'])->name('attendances.store');
- Route::get('/attendances/recap', [\App\Http\Controllers\AttendanceController::class, 'recap'])->name('attendances.recap');
- Route::get('/attendances/calendar-data', [\App\Http\Controllers\AttendanceController::class, 'calendarData'])->name('attendances.calendar-data');
+ // ===== Umum (permission-based) =====
- Route::get('/notifications/recent', [DashboardController::class, 'getRecentNotifications'])->name('notifications.recent');
- Route::post('/notifications/{id}/read', [DashboardController::class, 'markNotificationRead'])->name('notifications.read');
- Route::post('/notifications/read-all', [DashboardController::class, 'markAllNotificationsRead'])->name('notifications.read-all');
- Route::get('/notifications', [DashboardController::class, 'notificationsIndex'])->name('notifications.index');
+ Route::middleware('permission:access-dashboard')->group(function () {
+ Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
+ Route::get('/calendar/data', [DashboardController::class, 'getCalendarData'])->name('calendar.data');
+ });
- // Violations
- Route::get('/violations', [ViolationController::class, 'index'])->name('violations.index');
- Route::get('/violations/create', [ViolationController::class, 'create'])->name('violations.create');
- Route::post('/violations', [ViolationController::class, 'store'])->name('violations.store');
- Route::get('/violations/{violation}', [ViolationController::class, 'show'])->name('violations.show');
- Route::post('/violations/{violation}/verify', [ViolationController::class, 'verify'])->name('violations.verify');
- Route::delete('/violations/{violation}', [ViolationController::class, 'destroy'])->name('violations.destroy');
- Route::post('/violations/{violation}/handling', [ViolationController::class, 'storeHandling'])->name('violations.handling.store');
- Route::post('/violations/{violation}/resolve', [ViolationController::class, 'resolveHandling'])->name('violations.resolve');
- Route::delete('/violations/{violation}/handling/{handling}', [ViolationController::class, 'destroyHandling'])->name('violations.handling.destroy');
- Route::get('/api/students/search', [ViolationController::class, 'searchStudents'])->name('api.students.search');
+ Route::middleware('permission:view-notifications')->group(function () {
+ Route::get('/notifications/unread-count', [DashboardController::class, 'getUnreadCount'])->name('notifications.unread-count');
+ Route::get('/notifications/recent', [DashboardController::class, 'getRecentNotifications'])->name('notifications.recent');
+ Route::post('/notifications/{id}/read', [DashboardController::class, 'markNotificationRead'])->name('notifications.read');
+ Route::post('/notifications/read-all', [DashboardController::class, 'markAllNotificationsRead'])->name('notifications.read-all');
+ Route::get('/notifications', [DashboardController::class, 'notificationsIndex'])->name('notifications.index');
+ });
- // Students
- Route::get('/students', [StudentReportController::class, 'index'])->name('students.index');
- Route::get('/students/{student}', [StudentReportController::class, 'show'])->name('students.show');
- Route::put('/students/{student}', [StudentReportController::class, 'update'])->name('students.update');
+ Route::middleware('permission:input-violations')->group(function () {
+ Route::get('/violations/create', [ViolationController::class, 'create'])->name('violations.create');
+ Route::post('/violations', [ViolationController::class, 'store'])->name('violations.store');
+ });
- // SP Letters
- Route::get('/sp-letters', [SpLetterController::class, 'index'])->name('sp-letters.index');
- Route::get('/sp-letters/{spLetter}', [SpLetterController::class, 'show'])->name('sp-letters.show');
- Route::get('/sp-letters/{spLetter}/print', [SpLetterController::class, 'print'])->name('sp-letters.print');
+ Route::middleware('permission:view-violations')->group(function () {
+ Route::get('/violations', [ViolationController::class, 'index'])->name('violations.index');
+ Route::get('/violations/{violation}', [ViolationController::class, 'show'])->name('violations.show');
+ Route::post('/violations/{violation}/verify', [ViolationController::class, 'verify'])->name('violations.verify');
+ Route::delete('/violations/{violation}', [ViolationController::class, 'destroy'])->name('violations.destroy');
+ Route::post('/violations/{violation}/handling', [ViolationController::class, 'storeHandling'])->name('violations.handling.store');
+ Route::post('/violations/{violation}/resolve', [ViolationController::class, 'resolveHandling'])->name('violations.resolve');
+ Route::delete('/violations/{violation}/handling/{handling}', [ViolationController::class, 'destroyHandling'])->name('violations.handling.destroy');
+ Route::get('/api/students/search', [ViolationController::class, 'searchStudents'])->name('api.students.search');
+ });
- // Settings
- Route::get('/settings', [SettingsController::class, 'index'])->name('settings.index');
- Route::post('/settings', [SettingsController::class, 'update'])->name('settings.update');
+ Route::middleware('permission:view-students')->group(function () {
+ Route::get('/students', [StudentReportController::class, 'index'])->name('students.index');
+ Route::get('/students/{student}', [StudentReportController::class, 'show'])->name('students.show');
+ Route::put('/students/{student}', [StudentReportController::class, 'update'])->name('students.update');
+ });
- // Master Data
- Route::get('/settings/categories', [MasterDataController::class, 'categories'])->name('settings.categories');
- Route::post('/settings/categories', [MasterDataController::class, 'storeCategory'])->name('settings.categories.store');
- Route::put('/settings/categories/{category}', [MasterDataController::class, 'updateCategory'])->name('settings.categories.update');
- Route::delete('/settings/categories/{category}', [MasterDataController::class, 'destroyCategory'])->name('settings.categories.destroy');
+ Route::middleware('permission:manage-attendance')->group(function () {
+ Route::get('/attendances', [\App\Http\Controllers\AttendanceController::class, 'index'])->name('attendances.index');
+ Route::get('/attendances/create', [\App\Http\Controllers\AttendanceController::class, 'create'])->name('attendances.create');
+ Route::post('/attendances', [\App\Http\Controllers\AttendanceController::class, 'store'])->name('attendances.store');
+ Route::get('/attendances/recap', [\App\Http\Controllers\AttendanceController::class, 'recap'])->name('attendances.recap');
+ Route::get('/attendances/calendar-data', [\App\Http\Controllers\AttendanceController::class, 'calendarData'])->name('attendances.calendar-data');
+ });
- Route::get('/settings/violation-types', [MasterDataController::class, 'types'])->name('settings.violation-types');
- Route::post('/settings/violation-types', [MasterDataController::class, 'storeType'])->name('settings.violation-types.store');
- Route::put('/settings/violation-types/{type}', [MasterDataController::class, 'updateType'])->name('settings.violation-types.update');
- Route::delete('/settings/violation-types/{type}', [MasterDataController::class, 'destroyType'])->name('settings.violation-types.destroy');
+ Route::middleware('permission:view-sp-letters')->group(function () {
+ Route::get('/sp-letters', [SpLetterController::class, 'index'])->name('sp-letters.index');
+ Route::get('/sp-letters/{spLetter}', [SpLetterController::class, 'show'])->name('sp-letters.show');
+ Route::get('/sp-letters/{spLetter}/print', [SpLetterController::class, 'print'])->name('sp-letters.print');
+ });
- // Import
- Route::get('/settings/import/template', [ImportController::class, 'downloadTemplate'])->name('settings.import.template');
- Route::get('/settings/export/violation-types', [ImportController::class, 'exportViolationTypes'])->name('settings.export.violation-types');
- Route::get('/settings/backup', [\App\Http\Controllers\Admin\BackupController::class, 'index'])->name('settings.backup');
- Route::post('/settings/backup/create', [\App\Http\Controllers\Admin\BackupController::class, 'create'])->name('settings.backup.create');
- Route::get('/settings/backup/download/{filename}', [\App\Http\Controllers\Admin\BackupController::class, 'download'])->name('settings.backup.download');
- Route::delete('/settings/backup/{filename}', [\App\Http\Controllers\Admin\BackupController::class, 'destroy'])->name('settings.backup.destroy');
- Route::post('/settings/backup/restore', [\App\Http\Controllers\Admin\BackupController::class, 'restore'])->name('settings.backup.restore');
- Route::get('/export/violations', [\App\Http\Controllers\Admin\ViolationExportController::class, 'export'])->name('violations.export');
- Route::post('/settings/import/violation-types', [ImportController::class, 'importViolationTypes'])->name('settings.import.violation-types');
+ // ===== Master Data (permission-based) =====
+ Route::middleware('permission:categories-manage')->group(function () {
+ Route::get('/settings/categories', [MasterDataController::class, 'categories'])->name('settings.categories');
+ Route::post('/settings/categories', [MasterDataController::class, 'storeCategory'])->name('settings.categories.store');
+ Route::put('/settings/categories/{category}', [MasterDataController::class, 'updateCategory'])->name('settings.categories.update');
+ Route::delete('/settings/categories/{category}', [MasterDataController::class, 'destroyCategory'])->name('settings.categories.destroy');
+ });
- Route::get('/settings/thresholds', [MasterDataController::class, 'thresholds'])->name('settings.thresholds');
- Route::put('/settings/thresholds', [MasterDataController::class, 'updateThresholds'])->name('settings.thresholds.update');
- Route::post('/settings/thresholds', [MasterDataController::class, 'storeThreshold'])->name('settings.thresholds.store');
- Route::delete('/settings/thresholds/{threshold}', [MasterDataController::class, 'destroyThreshold'])->name('settings.thresholds.destroy');
+ Route::middleware('permission:violation-types-manage')->group(function () {
+ Route::get('/settings/violation-types', [MasterDataController::class, 'types'])->name('settings.violation-types');
+ Route::post('/settings/violation-types', [MasterDataController::class, 'storeType'])->name('settings.violation-types.store');
+ Route::put('/settings/violation-types/{type}', [MasterDataController::class, 'updateType'])->name('settings.violation-types.update');
+ Route::delete('/settings/violation-types/{type}', [MasterDataController::class, 'destroyType'])->name('settings.violation-types.destroy');
+ });
- // Sync
- Route::get('/settings/sync', [SyncController::class, 'index'])->name('settings.sync');
- Route::post('/settings/sync', [SyncController::class, 'syncNow'])->name('settings.sync.run');
+ Route::middleware('permission:thresholds-manage')->group(function () {
+ Route::get('/settings/thresholds', [MasterDataController::class, 'thresholds'])->name('settings.thresholds');
+ Route::put('/settings/thresholds', [MasterDataController::class, 'updateThresholds'])->name('settings.thresholds.update');
+ Route::post('/settings/thresholds', [MasterDataController::class, 'storeThreshold'])->name('settings.thresholds.store');
+ Route::delete('/settings/thresholds/{threshold}', [MasterDataController::class, 'destroyThreshold'])->name('settings.thresholds.destroy');
+ });
- // User Management
- Route::get('/users', [UserController::class, 'index'])->name('users.index');
- Route::post('/users', [UserController::class, 'store'])->name('users.store');
- Route::put('/users/{user}', [UserController::class, 'update'])->name('users.update');
- Route::delete('/users/{user}', [UserController::class, 'destroy'])->name('users.destroy');
+ Route::middleware('permission:violations-export')->group(function () {
+ Route::get('/export/violations', [\App\Http\Controllers\Admin\ViolationExportController::class, 'export'])->name('violations.export');
+ });
- // Reset Application
- Route::get('/settings/reset', [App\Http\Controllers\Admin\ResetController::class, 'index'])->name('settings.reset');
- Route::post('/settings/reset', [App\Http\Controllers\Admin\ResetController::class, 'reset'])->name('settings.reset.run');
+ // ===== Administrasi (permission-based) =====
+ Route::middleware('permission:settings-manage')->group(function () {
+ Route::get('/settings', [SettingsController::class, 'index'])->name('settings.index');
+ Route::post('/settings', [SettingsController::class, 'update'])->name('settings.update');
+ });
- // Profile Settings
+ Route::middleware('permission:import-data')->group(function () {
+ Route::get('/settings/import/template', [ImportController::class, 'downloadTemplate'])->name('settings.import.template');
+ Route::get('/settings/export/violation-types', [ImportController::class, 'exportViolationTypes'])->name('settings.export.violation-types');
+ Route::post('/settings/import/violation-types', [ImportController::class, 'importViolationTypes'])->name('settings.import.violation-types');
+ });
+
+ Route::middleware('permission:backup-database')->group(function () {
+ Route::get('/settings/backup', [\App\Http\Controllers\Admin\BackupController::class, 'index'])->name('settings.backup');
+ Route::post('/settings/backup/create', [\App\Http\Controllers\Admin\BackupController::class, 'create'])->name('settings.backup.create');
+ Route::get('/settings/backup/download/{filename}', [\App\Http\Controllers\Admin\BackupController::class, 'download'])->name('settings.backup.download');
+ Route::delete('/settings/backup/{filename}', [\App\Http\Controllers\Admin\BackupController::class, 'destroy'])->name('settings.backup.destroy');
+ Route::post('/settings/backup/restore', [\App\Http\Controllers\Admin\BackupController::class, 'restore'])->name('settings.backup.restore');
+ });
+
+ Route::middleware('permission:sync-data')->group(function () {
+ Route::get('/settings/sync', [SyncController::class, 'index'])->name('settings.sync');
+ Route::post('/settings/sync', [SyncController::class, 'syncNow'])->name('settings.sync.run');
+ });
+
+ Route::middleware('permission:users-manage')->group(function () {
+ Route::get('/users', [UserController::class, 'index'])->name('users.index');
+ Route::post('/users', [UserController::class, 'store'])->name('users.store');
+ Route::put('/users/{user}', [UserController::class, 'update'])->name('users.update');
+ Route::delete('/users/{user}', [UserController::class, 'destroy'])->name('users.destroy');
+
+ Route::get('/settings/permissions', [\App\Http\Controllers\Admin\PermissionController::class, 'index'])->name('settings.permissions');
+ Route::post('/settings/permissions', [\App\Http\Controllers\Admin\PermissionController::class, 'update'])->name('settings.permissions.update');
+ });
+
+ Route::middleware('permission:reset-application')->group(function () {
+ Route::get('/settings/reset', [\App\Http\Controllers\Admin\ResetController::class, 'index'])->name('settings.reset');
+ Route::post('/settings/reset', [\App\Http\Controllers\Admin\ResetController::class, 'reset'])->name('settings.reset.run');
+ });
+
+ // Profile (all roles — no permission needed, manage own account)
Route::get('/profile', [ProfileController::class, 'index'])->name('profile.index');
Route::post('/profile', [ProfileController::class, 'update'])->name('profile.update');
});