update: roles permission
parent
722115674f
commit
173ecf4834
|
@ -26,3 +26,5 @@ yarn-error.log
|
|||
/.nova
|
||||
/.vscode
|
||||
/.zed
|
||||
.kiro
|
||||
.github
|
|
@ -0,0 +1,150 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of users
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$users = User::with('roles')->paginate(10);
|
||||
|
||||
return view('admin.users.index', compact('users'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new user
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$roles = Role::all();
|
||||
|
||||
return view('admin.users.create', compact('roles'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created user
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
|
||||
'username' => ['required', 'string', 'max:255', 'unique:users'],
|
||||
'password' => [
|
||||
'required',
|
||||
'string',
|
||||
'min:8',
|
||||
'confirmed',
|
||||
'regex:/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}$/',
|
||||
],
|
||||
'roles' => ['array'],
|
||||
'roles.*' => ['exists:roles,name'],
|
||||
]);
|
||||
|
||||
$user = User::create([
|
||||
'name' => $validated['name'],
|
||||
'email' => $validated['email'],
|
||||
'username' => $validated['username'],
|
||||
'password' => Hash::make($validated['password']),
|
||||
]);
|
||||
|
||||
if (!empty($validated['roles'])) {
|
||||
$user->assignRole($validated['roles']);
|
||||
}
|
||||
|
||||
return redirect()->route('admin.users.index')
|
||||
->with('success', 'Pengguna berhasil ditambahkan.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified user
|
||||
*/
|
||||
public function show(User $user)
|
||||
{
|
||||
$user->load('roles', 'permissions');
|
||||
|
||||
return view('admin.users.show', compact('user'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified user
|
||||
*/
|
||||
public function edit(User $user)
|
||||
{
|
||||
$roles = Role::all();
|
||||
$userRoles = $user->roles->pluck('name')->toArray();
|
||||
|
||||
return view('admin.users.edit', compact('user', 'roles', 'userRoles'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified user
|
||||
*/
|
||||
public function update(Request $request, User $user)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users')->ignore($user->id)],
|
||||
'username' => ['required', 'string', 'max:255', Rule::unique('users')->ignore($user->id)],
|
||||
'password' => [
|
||||
'nullable',
|
||||
'string',
|
||||
'min:8',
|
||||
'confirmed',
|
||||
'regex:/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}$/',
|
||||
],
|
||||
'roles' => ['array'],
|
||||
'roles.*' => ['exists:roles,name'],
|
||||
]);
|
||||
|
||||
$user->update([
|
||||
'name' => $validated['name'],
|
||||
'email' => $validated['email'],
|
||||
'username' => $validated['username'],
|
||||
]);
|
||||
|
||||
// Update password only if provided
|
||||
if (!empty($validated['password'])) {
|
||||
$user->update([
|
||||
'password' => Hash::make($validated['password']),
|
||||
]);
|
||||
}
|
||||
|
||||
// Sync roles
|
||||
if (isset($validated['roles'])) {
|
||||
$user->syncRoles($validated['roles']);
|
||||
} else {
|
||||
$user->syncRoles([]);
|
||||
}
|
||||
|
||||
return redirect()->route('admin.users.index')
|
||||
->with('success', 'Pengguna berhasil diperbarui.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified user
|
||||
*/
|
||||
public function destroy(User $user)
|
||||
{
|
||||
// Prevent deleting current user
|
||||
if ($user->id === auth()->id()) {
|
||||
return redirect()->route('admin.users.index')
|
||||
->with('error', 'Anda tidak dapat menghapus akun sendiri.');
|
||||
}
|
||||
|
||||
$user->delete();
|
||||
|
||||
return redirect()->route('admin.users.index')
|
||||
->with('success', 'Pengguna berhasil dihapus.');
|
||||
}
|
||||
}
|
|
@ -3,38 +3,44 @@
|
|||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Auth\LoginRequest;
|
||||
use App\Http\Requests\Auth\ApiLoginRequest;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
// =============== API (Sanctum Token) ===============
|
||||
public function login(LoginRequest $request)
|
||||
/**
|
||||
* Handle API login using Sanctum tokens
|
||||
*/
|
||||
public function login(ApiLoginRequest $request)
|
||||
{
|
||||
$data = $request->validated();
|
||||
$identifier = $data['identifier'];
|
||||
$password = $data['password'];
|
||||
$device = $data['device_name'] ?? $request->header('User-Agent') ?? 'api';
|
||||
$device = $data['device_name'] ?? $request->header('User-Agent') ?? 'api-client';
|
||||
|
||||
// Find user by email or username
|
||||
$user = User::where('email', $identifier)
|
||||
->orWhere('username', $identifier)
|
||||
->first();
|
||||
|
||||
$user = User::where('email', $identifier)->first();
|
||||
if (!$user) {
|
||||
$user = User::where('username', $identifier)->first();
|
||||
}
|
||||
if (!$user || !Hash::check($password, $user->password)) {
|
||||
throw ValidationException::withMessages([
|
||||
'identifier' => ['email/username/password yang diberikan salah.'],
|
||||
'identifier' => ['Email/username atau password yang diberikan salah.'],
|
||||
]);
|
||||
}
|
||||
|
||||
// Delete existing tokens for this device to prevent token accumulation
|
||||
$user->tokens()->where('name', $device)->delete();
|
||||
|
||||
// Create new token
|
||||
$token = $user->createToken($device)->plainTextToken;
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Login berhasil',
|
||||
'token' => $token,
|
||||
'token_type' => 'Bearer',
|
||||
'user' => [
|
||||
|
@ -42,65 +48,66 @@ class AuthController extends Controller
|
|||
'name' => $user->name,
|
||||
'email' => $user->email,
|
||||
'username' => $user->username,
|
||||
'roles' => $user->getRoleNames(),
|
||||
'permissions' => $user->getAllPermissions()->pluck('name'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get authenticated user information
|
||||
*/
|
||||
public function me(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'user' => $request->user(),
|
||||
'user' => [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'email' => $user->email,
|
||||
'username' => $user->username,
|
||||
'roles' => $user->getRoleNames(),
|
||||
'permissions' => $user->getAllPermissions()->pluck('name'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout current API session (revoke current token)
|
||||
*/
|
||||
public function logout(Request $request)
|
||||
{
|
||||
$request->user()->currentAccessToken()->delete();
|
||||
return response()->json(['success' => true]);
|
||||
$user = $request->user();
|
||||
|
||||
// Revoke current access token
|
||||
if ($user && method_exists($user, 'currentAccessToken')) {
|
||||
$token = $user->currentAccessToken();
|
||||
if ($token) {
|
||||
$token->delete();
|
||||
}
|
||||
}
|
||||
|
||||
public function logoutAll(Request $request)
|
||||
{
|
||||
$request->user()->tokens()->delete();
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
|
||||
// =============== Web (Session Guard) ===============
|
||||
public function sessionLogin(LoginRequest $request)
|
||||
{
|
||||
$data = $request->validated();
|
||||
$identifier = $data['identifier'];
|
||||
$password = $data['password'];
|
||||
|
||||
$user = User::where('email', $identifier)->first();
|
||||
if (!$user) {
|
||||
$user = User::where('username', $identifier)->first();
|
||||
}
|
||||
|
||||
if (!$user || !Hash::check($password, $user->password)) {
|
||||
throw ValidationException::withMessages([
|
||||
'identifier' => ['email/username/password yang diberikan salah.'],
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Logout berhasil'
|
||||
]);
|
||||
}
|
||||
|
||||
Auth::login($user, true);
|
||||
$request->session()->regenerate();
|
||||
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
|
||||
public function sessionLogout(Request $request)
|
||||
/**
|
||||
* Logout from all devices (revoke all tokens)
|
||||
*/
|
||||
public function logoutAll(Request $request)
|
||||
{
|
||||
Auth::logout();
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
$user = $request->user();
|
||||
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
// Revoke all tokens for this user
|
||||
$user->tokens()->delete();
|
||||
|
||||
return redirect()->route('login.index');
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Logout dari semua perangkat berhasil'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,49 +2,90 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Auth\LoginRequest;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Cookie;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class WebAuthController extends Controller
|
||||
{
|
||||
/**
|
||||
* Handle web-based login using session authentication
|
||||
*/
|
||||
public function sessionLogin(LoginRequest $request)
|
||||
{
|
||||
$data = $request->validated();
|
||||
$identifier = $data['identifier'];
|
||||
$password = $data['password'];
|
||||
|
||||
$user = User::where('email', $identifier)->first();
|
||||
if (!$user) {
|
||||
$user = User::where('username', $identifier)->first();
|
||||
}
|
||||
// Find user by email or username
|
||||
$user = User::where('email', $identifier)
|
||||
->orWhere('username', $identifier)
|
||||
->first();
|
||||
|
||||
if (!$user || !Hash::check($password, $user->password)) {
|
||||
throw ValidationException::withMessages([
|
||||
'identifier' => ['email/username/password yang diberikan salah.'],
|
||||
'identifier' => ['Email/username atau password yang diberikan salah.'],
|
||||
]);
|
||||
}
|
||||
|
||||
Auth::login($user, true);
|
||||
request()->session()->regenerate();
|
||||
// Login using session guard (creates session cookies)
|
||||
Auth::guard('web')->login($user, false);
|
||||
$request->session()->regenerate();
|
||||
|
||||
return response()->json(['success' => true]);
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Login berhasil',
|
||||
'user' => [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'email' => $user->email,
|
||||
'username' => $user->username,
|
||||
],
|
||||
]);
|
||||
}
|
||||
public function logout(Request $request)
|
||||
|
||||
return redirect()->intended('/dashboard');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle web-based logout (clears session cookies)
|
||||
*/
|
||||
public function sessionLogout(Request $request)
|
||||
{
|
||||
Auth::logout();
|
||||
$user = Auth::user();
|
||||
|
||||
// Logout from session
|
||||
Auth::guard('web')->logout();
|
||||
|
||||
// Clear remember token if exists
|
||||
if ($user) {
|
||||
$user->setRememberToken(Str::random(60));
|
||||
$user->save();
|
||||
}
|
||||
|
||||
// Invalidate session and regenerate CSRF token
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
// Clear authentication cookies
|
||||
Cookie::queue(Cookie::forget(Auth::getRecallerName()));
|
||||
Cookie::queue(Cookie::forget(config('session.cookie')));
|
||||
Cookie::queue(Cookie::forget('XSRF-TOKEN'));
|
||||
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json(['success' => true]);
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Logout berhasil'
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->route('login.index');
|
||||
return redirect()->route('login.index')->with('message', 'Anda telah berhasil logout');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class WebAuthMiddleware
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request for web authentication
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (!Auth::guard('web')->check()) {
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Unauthenticated'
|
||||
], 401);
|
||||
}
|
||||
|
||||
return redirect()->route('login.index');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Auth;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ApiLoginRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'identifier' => ['required', 'string'],
|
||||
'password' => ['required', 'string'],
|
||||
'device_name' => ['nullable', 'string', 'max:255'],
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'identifier.required' => 'Email atau username wajib diisi.',
|
||||
'password.required' => 'Password wajib diisi.',
|
||||
'device_name.max' => 'Nama perangkat maksimal 255 karakter.',
|
||||
];
|
||||
}
|
||||
}
|
|
@ -9,9 +9,9 @@ use Spatie\Permission\Middleware\RoleOrPermissionMiddleware;
|
|||
|
||||
return Application::configure(basePath: dirname(__DIR__))
|
||||
->withRouting(
|
||||
web: __DIR__.'/../routes/web.php',
|
||||
api: __DIR__.'/../routes/api.php',
|
||||
commands: __DIR__.'/../routes/console.php',
|
||||
web: __DIR__ . '/../routes/web.php',
|
||||
api: __DIR__ . '/../routes/api.php',
|
||||
commands: __DIR__ . '/../routes/console.php',
|
||||
health: '/up',
|
||||
)
|
||||
->withMiddleware(function (Middleware $middleware) {
|
||||
|
@ -20,6 +20,7 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||
'role' => RoleMiddleware::class,
|
||||
'permission' => PermissionMiddleware::class,
|
||||
'role_or_permission' => RoleOrPermissionMiddleware::class,
|
||||
'web.auth' => \App\Http\Middleware\WebAuthMiddleware::class,
|
||||
]);
|
||||
})
|
||||
->withExceptions(function (Exceptions $exceptions) {
|
||||
|
|
|
@ -40,6 +40,10 @@ return [
|
|||
'driver' => 'session',
|
||||
'provider' => 'users',
|
||||
],
|
||||
'sanctum' => [
|
||||
'driver' => 'sanctum',
|
||||
'provider' => 'users',
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|
|
|
@ -26,6 +26,7 @@ class UserFactory extends Factory
|
|||
return [
|
||||
'name' => fake()->name(),
|
||||
'email' => fake()->unique()->safeEmail(),
|
||||
'username' => fake()->unique()->userName(),
|
||||
'email_verified_at' => now(),
|
||||
'password' => static::$password ??= Hash::make('password'),
|
||||
'remember_token' => Str::random(10),
|
||||
|
@ -37,7 +38,7 @@ class UserFactory extends Factory
|
|||
*/
|
||||
public function unverified(): static
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
return $this->state(fn(array $attributes) => [
|
||||
'email_verified_at' => null,
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -73,4 +73,3 @@ class RolesAndPermissionsSeeder extends Seeder
|
|||
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ class UserSeeder extends Seeder
|
|||
public function run(): void
|
||||
{
|
||||
$user = User::updateOrCreate(
|
||||
['email' => 'ammar@example.com'],
|
||||
['email' => 'ammar@dinaslhdki.id'],
|
||||
[
|
||||
'name' => 'Ammar',
|
||||
'username' => 'ammar',
|
||||
|
|
|
@ -0,0 +1,222 @@
|
|||
# Authentication System Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
The application uses a dual authentication system:
|
||||
|
||||
- **Web Authentication**: Session-based with cookies for browser access
|
||||
- **API Authentication**: Token-based using Laravel Sanctum for API access
|
||||
|
||||
## Web Authentication (Session + Cookies)
|
||||
|
||||
### Login
|
||||
|
||||
**Endpoint**: `POST /auth/session-login`
|
||||
**Controller**: `WebAuthController@sessionLogin`
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/auth/session-login \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-CSRF-TOKEN: your-csrf-token" \
|
||||
-d '{
|
||||
"identifier": "user@example.com",
|
||||
"password": "password123"
|
||||
}'
|
||||
```
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Login berhasil",
|
||||
"user": {
|
||||
"id": 1,
|
||||
"name": "John Doe",
|
||||
"email": "user@example.com",
|
||||
"username": "johndoe"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Logout
|
||||
|
||||
**Endpoint**: `POST /auth/logout`
|
||||
**Controller**: `WebAuthController@sessionLogout`
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/auth/logout \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-CSRF-TOKEN: your-csrf-token"
|
||||
```
|
||||
|
||||
### Protected Routes
|
||||
|
||||
All admin routes are protected with `web.auth` middleware:
|
||||
|
||||
- `/dashboard/*`
|
||||
- `/admin/*`
|
||||
|
||||
## API Authentication (Token-based)
|
||||
|
||||
### Login
|
||||
|
||||
**Endpoint**: `POST /api/auth/login`
|
||||
**Controller**: `AuthController@login`
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/api/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"identifier": "user@example.com",
|
||||
"password": "password123",
|
||||
"device_name": "mobile-app"
|
||||
}'
|
||||
```
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Login berhasil",
|
||||
"token": "1|abc123def456...",
|
||||
"token_type": "Bearer",
|
||||
"user": {
|
||||
"id": 1,
|
||||
"name": "John Doe",
|
||||
"email": "user@example.com",
|
||||
"username": "johndoe",
|
||||
"roles": ["admin"],
|
||||
"permissions": ["dashboard.view", "settings.manage"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Using API Token
|
||||
|
||||
Include the token in the Authorization header:
|
||||
|
||||
```bash
|
||||
curl -X GET http://localhost:8000/api/auth/me \
|
||||
-H "Authorization: Bearer 1|abc123def456..."
|
||||
```
|
||||
|
||||
### Get User Info
|
||||
|
||||
**Endpoint**: `GET /api/auth/me`
|
||||
|
||||
```bash
|
||||
curl -X GET http://localhost:8000/api/auth/me \
|
||||
-H "Authorization: Bearer your-token"
|
||||
```
|
||||
|
||||
### Logout (Current Device)
|
||||
|
||||
**Endpoint**: `POST /api/auth/logout`
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/api/auth/logout \
|
||||
-H "Authorization: Bearer your-token"
|
||||
```
|
||||
|
||||
### Logout All Devices
|
||||
|
||||
**Endpoint**: `POST /api/auth/logout-all`
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8000/api/auth/logout-all \
|
||||
-H "Authorization: Bearer your-token"
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Guards
|
||||
|
||||
- `web`: Session-based authentication for web interface
|
||||
- `sanctum`: Token-based authentication for API
|
||||
|
||||
### Middleware
|
||||
|
||||
- `web.auth`: Custom middleware for web session authentication
|
||||
- `auth:sanctum`: Laravel Sanctum middleware for API authentication
|
||||
|
||||
### Token Management
|
||||
|
||||
- Tokens are device-specific (one token per device)
|
||||
- Old tokens for the same device are automatically revoked on new login
|
||||
- Tokens don't expire by default (configurable in sanctum config)
|
||||
|
||||
## Security Features
|
||||
|
||||
### Web Authentication
|
||||
|
||||
- CSRF protection enabled
|
||||
- Session regeneration on login
|
||||
- Remember token invalidation on logout
|
||||
- Cookie cleanup on logout
|
||||
|
||||
### API Authentication
|
||||
|
||||
- Device-specific tokens
|
||||
- Token revocation on logout
|
||||
- Automatic cleanup of old tokens
|
||||
- Rate limiting (configurable)
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Common Error Responses
|
||||
|
||||
**Invalid Credentials**:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "The given data was invalid.",
|
||||
"errors": {
|
||||
"identifier": ["Email/username atau password yang diberikan salah."]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Unauthenticated**:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "Unauthenticated"
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Web Authentication Test
|
||||
|
||||
```bash
|
||||
# Login
|
||||
curl -c cookies.txt -X POST http://localhost:8000/auth/session-login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"identifier": "admin@example.com", "password": "password"}'
|
||||
|
||||
# Access protected route
|
||||
curl -b cookies.txt http://localhost:8000/dashboard
|
||||
|
||||
# Logout
|
||||
curl -b cookies.txt -X POST http://localhost:8000/auth/logout
|
||||
```
|
||||
|
||||
### API Authentication Test
|
||||
|
||||
```bash
|
||||
# Login and save token
|
||||
TOKEN=$(curl -X POST http://localhost:8000/api/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"identifier": "admin@example.com", "password": "password"}' \
|
||||
| jq -r '.token')
|
||||
|
||||
# Use token
|
||||
curl -H "Authorization: Bearer $TOKEN" http://localhost:8000/api/auth/me
|
||||
|
||||
# Logout
|
||||
curl -X POST http://localhost:8000/api/auth/logout \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
|
@ -0,0 +1,221 @@
|
|||
# User Management System
|
||||
|
||||
Sistem manajemen pengguna untuk aplikasi Perling dengan fitur CRUD lengkap dan integrasi role-based access control.
|
||||
|
||||
## 🚀 Fitur
|
||||
|
||||
### ✅ Manajemen Pengguna
|
||||
|
||||
- **View (Index)**: Daftar semua pengguna dengan pagination
|
||||
- **Create**: Tambah pengguna baru dengan role assignment
|
||||
- **Show**: Detail lengkap pengguna termasuk role dan permissions
|
||||
- **Edit**: Update informasi pengguna dan role
|
||||
- **Delete**: Hapus pengguna (dengan proteksi untuk user yang sedang login)
|
||||
|
||||
### 🔐 Keamanan
|
||||
|
||||
- Password validation dengan requirements yang kuat
|
||||
- Role-based access control menggunakan Spatie Laravel Permission
|
||||
- Proteksi CSRF untuk semua form
|
||||
- Validasi unique untuk email dan username
|
||||
- Proteksi penghapusan akun sendiri
|
||||
|
||||
## 📋 Endpoints
|
||||
|
||||
| Method | Route | Action | Permission Required |
|
||||
| ------ | -------------------------- | -------------------- | ------------------- |
|
||||
| GET | `/admin/users` | Daftar pengguna | `settings.manage` |
|
||||
| GET | `/admin/users/create` | Form tambah pengguna | `settings.manage` |
|
||||
| POST | `/admin/users` | Simpan pengguna baru | `settings.manage` |
|
||||
| GET | `/admin/users/{user}` | Detail pengguna | `settings.manage` |
|
||||
| GET | `/admin/users/{user}/edit` | Form edit pengguna | `settings.manage` |
|
||||
| PUT | `/admin/users/{user}` | Update pengguna | `settings.manage` |
|
||||
| DELETE | `/admin/users/{user}` | Hapus pengguna | `settings.manage` |
|
||||
|
||||
## 🎯 Validasi
|
||||
|
||||
### Create User
|
||||
|
||||
```php
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users',
|
||||
'username' => 'required|string|unique:users',
|
||||
'password' => 'required|min:8|confirmed|regex:/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}$/',
|
||||
'roles' => 'array|exists:roles,name'
|
||||
```
|
||||
|
||||
### Update User
|
||||
|
||||
```php
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|unique:users,email,{user_id}',
|
||||
'username' => 'required|string|unique:users,username,{user_id}',
|
||||
'password' => 'nullable|min:8|confirmed|regex:/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}$/',
|
||||
'roles' => 'array|exists:roles,name'
|
||||
```
|
||||
|
||||
## 🗂️ File Structure
|
||||
|
||||
```
|
||||
app/Http/Controllers/Admin/
|
||||
└── UserController.php # Controller utama
|
||||
|
||||
resources/views/admin/users/
|
||||
├── index.blade.php # Daftar pengguna
|
||||
├── create.blade.php # Form tambah pengguna
|
||||
├── edit.blade.php # Form edit pengguna
|
||||
└── show.blade.php # Detail pengguna
|
||||
|
||||
resources/views/layouts/
|
||||
└── app.blade.php # Layout utama
|
||||
|
||||
database/seeders/
|
||||
└── UserManagementSeeder.php # Seeder untuk data awal
|
||||
```
|
||||
|
||||
## 🎨 UI Components
|
||||
|
||||
### Index Page
|
||||
|
||||
- Tabel responsif dengan pagination
|
||||
- Badge untuk menampilkan role
|
||||
- Action buttons (View, Edit, Delete)
|
||||
- Modal konfirmasi untuk delete
|
||||
- Alert untuk feedback
|
||||
|
||||
### Create/Edit Forms
|
||||
|
||||
- Form validation dengan error display
|
||||
- Password strength requirements
|
||||
- Role selection dengan checkbox
|
||||
- Responsive layout (2 kolom)
|
||||
|
||||
### Show Page
|
||||
|
||||
- Informasi lengkap pengguna
|
||||
- Role dan permissions display
|
||||
- Activity summary cards
|
||||
- Action buttons
|
||||
|
||||
## 🔧 Setup & Installation
|
||||
|
||||
### 1. Run Migrations
|
||||
|
||||
```bash
|
||||
php artisan migrate
|
||||
```
|
||||
|
||||
### 2. Run Seeders
|
||||
|
||||
```bash
|
||||
php artisan db:seed
|
||||
```
|
||||
|
||||
### 3. Default Users
|
||||
|
||||
Sistem sudah memiliki user default dari UserSeeder:
|
||||
|
||||
- **ammar@dinaslhdki.id** (role: DLH) - Super admin dengan akses penuh
|
||||
- **kadis@dinaslhdki.id** (role: Kadis) - Management level access
|
||||
- **ppkl@dinaslhkdki.id** (role: PPKL) - Operational level access
|
||||
|
||||
Password sesuai dengan yang sudah dikonfigurasi di UserSeeder.
|
||||
|
||||
## 🚀 Usage Examples
|
||||
|
||||
### Akses User Management
|
||||
|
||||
1. Login sebagai user dengan role DLH (ammar@dinaslhdki.id)
|
||||
2. Klik menu "Pengaturan" → "Manajemen Pengguna"
|
||||
3. Atau akses langsung: `/admin/users`
|
||||
|
||||
### Tambah User Baru
|
||||
|
||||
1. Klik tombol "Tambah Pengguna"
|
||||
2. Isi form dengan data lengkap
|
||||
3. Pilih role yang sesuai
|
||||
4. Klik "Simpan"
|
||||
|
||||
### Edit User
|
||||
|
||||
1. Dari daftar user, klik tombol "Edit" (ikon pensil)
|
||||
2. Update informasi yang diperlukan
|
||||
3. Password bisa dikosongkan jika tidak ingin diubah
|
||||
4. Klik "Perbarui"
|
||||
|
||||
### Hapus User
|
||||
|
||||
1. Klik tombol "Hapus" (ikon trash)
|
||||
2. Konfirmasi di modal yang muncul
|
||||
3. User akan dihapus permanent
|
||||
|
||||
## 🔒 Security Features
|
||||
|
||||
### Password Requirements
|
||||
|
||||
- Minimal 8 karakter
|
||||
- Harus mengandung huruf besar
|
||||
- Harus mengandung huruf kecil
|
||||
- Harus mengandung angka
|
||||
- Harus mengandung simbol khusus
|
||||
|
||||
### Access Control
|
||||
|
||||
- Hanya user dengan permission `settings.manage` yang bisa akses
|
||||
- User tidak bisa menghapus akun sendiri
|
||||
- CSRF protection pada semua form
|
||||
- Input validation dan sanitization
|
||||
|
||||
### Role Management
|
||||
|
||||
- Role assignment saat create/edit user
|
||||
- Multiple role support
|
||||
- Permission inheritance dari role
|
||||
- Real-time role sync
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Permission Denied**
|
||||
|
||||
- Pastikan user memiliki permission `settings.manage`
|
||||
- Check role assignment di database
|
||||
|
||||
**Validation Errors**
|
||||
|
||||
- Email/username sudah digunakan
|
||||
- Password tidak memenuhi requirements
|
||||
- Role tidak valid
|
||||
|
||||
**Layout Issues**
|
||||
|
||||
- Pastikan Bootstrap CSS/JS ter-load
|
||||
- Check Lucide icons script
|
||||
- Verify Vite assets compiled
|
||||
|
||||
### Debug Commands
|
||||
|
||||
```bash
|
||||
# Check user permissions
|
||||
php artisan tinker
|
||||
>>> $user = User::find(1);
|
||||
>>> $user->getAllPermissions();
|
||||
|
||||
# Reset permissions
|
||||
php artisan permission:cache-reset
|
||||
|
||||
# Re-run seeders
|
||||
php artisan db:seed --class=UserManagementSeeder
|
||||
```
|
||||
|
||||
## 📈 Future Enhancements
|
||||
|
||||
- [ ] Bulk user operations
|
||||
- [ ] User import/export
|
||||
- [ ] Advanced filtering dan search
|
||||
- [ ] User activity logging
|
||||
- [ ] Email verification workflow
|
||||
- [ ] Password reset functionality
|
||||
- [ ] User profile pictures
|
||||
- [ ] Two-factor authentication
|
|
@ -0,0 +1,96 @@
|
|||
@extends('layout.layout')
|
||||
|
||||
@php
|
||||
$title = 'Tambah Pengguna';
|
||||
$subTitle = 'Manajemen Pengguna';
|
||||
@endphp
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="card basic-data-table">
|
||||
<div class="card-body">
|
||||
<div
|
||||
class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center mb-3 gap-3">
|
||||
<div>
|
||||
<h5 class="mb-0">Tambah Pengguna</h5>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<a href="{{ route('admin.users.index') }}"
|
||||
class="btn btn-secondary btn-sm d-flex align-items-center gap-2">
|
||||
<iconify-icon icon="iconoir:arrow-left"></iconify-icon>
|
||||
<span>Kembali</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if ($errors->any())
|
||||
<div class="alert alert-danger">
|
||||
<ul class="mb-0">
|
||||
@foreach ($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('admin.users.store') }}">
|
||||
@csrf
|
||||
<div class="row g-3">
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">Nama Lengkap</label>
|
||||
<input type="text" name="name" class="form-control" required value="{{ old('name') }}"
|
||||
placeholder="Masukkan nama lengkap">
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">Email</label>
|
||||
<input type="email" name="email" class="form-control" required value="{{ old('email') }}"
|
||||
placeholder="Masukkan email">
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">Username</label>
|
||||
<input type="text" name="username" class="form-control" required value="{{ old('username') }}"
|
||||
placeholder="Masukkan username">
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">Password</label>
|
||||
<input type="password" name="password" class="form-control" required
|
||||
placeholder="Masukkan password">
|
||||
<div class="form-text">Password harus minimal 8 karakter, mengandung huruf besar, huruf kecil,
|
||||
angka, dan simbol khusus.</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">Konfirmasi Password</label>
|
||||
<input type="password" name="password_confirmation" class="form-control" required
|
||||
placeholder="Konfirmasi password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<label class="form-label">Roles</label>
|
||||
<div class="row g-2">
|
||||
@foreach ($roles as $role)
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="form-check style-check d-flex align-items-center">
|
||||
<input class="form-check-input border border-neutral-300 me-8" type="checkbox"
|
||||
name="roles[]" value="{{ $role->name }}" id="role_{{ $role->id }}"
|
||||
{{ in_array($role->name, old('roles', [])) ? 'checked' : '' }}>
|
||||
<label class="form-check-label"
|
||||
for="role_{{ $role->id }}">{{ $role->name }}</label>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 d-flex gap-2">
|
||||
<button class="btn btn-primary d-flex align-items-center gap-2">
|
||||
<iconify-icon icon="material-symbols:save"></iconify-icon>
|
||||
<span>Simpan</span>
|
||||
</button>
|
||||
<a href="{{ route('admin.users.index') }}" class="btn btn-light">Batal</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endsection
|
|
@ -0,0 +1,117 @@
|
|||
@extends('layout.layout')
|
||||
|
||||
@php
|
||||
$title = 'Edit Pengguna';
|
||||
$subTitle = 'Manajemen Pengguna';
|
||||
@endphp
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="card basic-data-table">
|
||||
<div class="card-body">
|
||||
<div
|
||||
class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center mb-3 gap-3">
|
||||
<div>
|
||||
<h5 class="mb-0">Edit Pengguna: {{ $user->name }}</h5>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<a href="{{ route('admin.users.index') }}"
|
||||
class="btn btn-secondary btn-sm d-flex align-items-center gap-2">
|
||||
<iconify-icon icon="iconoir:arrow-left"></iconify-icon>
|
||||
<span>Kembali</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if ($errors->any())
|
||||
<div class="alert alert-danger">
|
||||
<ul class="mb-0">
|
||||
@foreach ($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('admin.users.update', $user) }}">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
<div class="row g-3">
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">Nama Lengkap</label>
|
||||
<input type="text" name="name" class="form-control" required
|
||||
value="{{ old('name', $user->name) }}" placeholder="Masukkan nama lengkap">
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">Email</label>
|
||||
<input type="email" name="email" class="form-control" required
|
||||
value="{{ old('email', $user->email) }}" placeholder="Masukkan email">
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">Username</label>
|
||||
<input type="text" name="username" class="form-control" required
|
||||
value="{{ old('username', $user->username) }}" placeholder="Masukkan username">
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">Password Baru</label>
|
||||
<input type="password" name="password" class="form-control"
|
||||
placeholder="Kosongkan jika tidak ingin mengubah">
|
||||
<div class="form-text">Kosongkan jika tidak ingin mengubah password. Password harus minimal 8
|
||||
karakter, mengandung huruf besar, huruf kecil, angka, dan simbol khusus.</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">Konfirmasi Password Baru</label>
|
||||
<input type="password" name="password_confirmation" class="form-control"
|
||||
placeholder="Konfirmasi password baru">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<label class="form-label">Roles</label>
|
||||
<div class="row g-2">
|
||||
@foreach ($roles as $role)
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="form-check style-check d-flex align-items-center">
|
||||
<input class="form-check-input border border-neutral-300 me-8" type="checkbox"
|
||||
name="roles[]" value="{{ $role->name }}" id="role_{{ $role->id }}"
|
||||
{{ in_array($role->name, old('roles', $userRoles)) ? 'checked' : '' }}>
|
||||
<label class="form-check-label"
|
||||
for="role_{{ $role->id }}">{{ $role->name }}</label>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- User Info -->
|
||||
<div class="mt-4">
|
||||
<div class="alert alert-info">
|
||||
<h6>Informasi Pengguna:</h6>
|
||||
<ul class="mb-0">
|
||||
<li>Dibuat: {{ $user->created_at->format('d/m/Y H:i:s') }}</li>
|
||||
<li>Terakhir diperbarui: {{ $user->updated_at->format('d/m/Y H:i:s') }}</li>
|
||||
@if ($user->email_verified_at)
|
||||
<li>Email diverifikasi: {{ $user->email_verified_at->format('d/m/Y H:i:s') }}</li>
|
||||
@else
|
||||
<li class="text-warning">Email belum diverifikasi</li>
|
||||
@endif
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 d-flex gap-2">
|
||||
<button class="btn btn-primary d-flex align-items-center gap-2">
|
||||
<iconify-icon icon="material-symbols:save"></iconify-icon>
|
||||
<span>Perbarui</span>
|
||||
</button>
|
||||
<a href="{{ route('admin.users.show', $user) }}" class="btn btn-info d-flex align-items-center gap-2">
|
||||
<iconify-icon icon="lucide:eye"></iconify-icon>
|
||||
<span>Lihat Detail</span>
|
||||
</a>
|
||||
<a href="{{ route('admin.users.index') }}" class="btn btn-light">Batal</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endsection
|
|
@ -0,0 +1,149 @@
|
|||
@extends('layout.layout')
|
||||
|
||||
@php
|
||||
$title = 'Daftar Pengguna';
|
||||
$subTitle = 'Manajemen Pengguna';
|
||||
$script = '
|
||||
<script>
|
||||
let table = new DataTable("#dataTable");
|
||||
|
||||
// Handle delete modal
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const deleteModal = document.getElementById("deleteModal");
|
||||
const deleteForm = document.getElementById("deleteForm");
|
||||
const userNameText = document.getElementById("userNameText");
|
||||
|
||||
deleteModal.addEventListener("show.bs.modal", function(event) {
|
||||
const button = event.relatedTarget;
|
||||
const userName = button.getAttribute("data-user-name");
|
||||
const userId = button.getAttribute("data-user-id");
|
||||
|
||||
userNameText.textContent = userName;
|
||||
deleteForm.action = "' . route('admin.users.index') . '/" + userId;
|
||||
});
|
||||
});
|
||||
</script>';
|
||||
@endphp
|
||||
|
||||
@section('content')
|
||||
<div class="card basic-data-table">
|
||||
<div class="card-body">
|
||||
<div
|
||||
class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center mb-3 gap-3">
|
||||
<div>
|
||||
<h5 class="mb-0">Daftar Pengguna</h5>
|
||||
</div>
|
||||
<div
|
||||
class="d-flex flex-column flex-sm-row align-items-stretch align-items-sm-center justify-content-end gap-2 w-md-auto">
|
||||
<div class="flex-fill flex-sm-fill-0">
|
||||
<a href="{{ route('admin.users.create') }}"
|
||||
class="btn btn-primary btn-sm d-flex align-items-center justify-content-center gap-2 w-100">
|
||||
<iconify-icon icon="material-symbols:add" class="text-lg"></iconify-icon>
|
||||
<span>Tambah Pengguna</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (session('success'))
|
||||
<div class="alert alert-success">{{ session('success') }}</div>
|
||||
@endif
|
||||
|
||||
@if (session('error'))
|
||||
<div class="alert alert-danger">{{ session('error') }}</div>
|
||||
@endif
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table bordered-table mb-0" id="dataTable" data-page-length='10'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="text-center">No</th>
|
||||
<th scope="col" class="text-center">Nama</th>
|
||||
<th scope="col" class="text-center">Email</th>
|
||||
<th scope="col" class="text-center">Username</th>
|
||||
<th scope="col" class="text-center">Role</th>
|
||||
<th scope="col" class="text-center">Dibuat</th>
|
||||
<th scope="col" class="text-center">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($users as $user)
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
{{ isset($users->firstItem) ? $users->firstItem() + $loop->index : $loop->iteration }}
|
||||
</td>
|
||||
<td class="text-center">{{ $user->name }}</td>
|
||||
<td class="text-center">{{ $user->email }}</td>
|
||||
<td class="text-center">{{ $user->username }}</td>
|
||||
<td class="text-center">
|
||||
@if ($user->roles->count() > 0)
|
||||
@foreach ($user->roles as $role)
|
||||
<span class="badge bg-primary me-1">{{ $role->name }}</span>
|
||||
@endforeach
|
||||
@else
|
||||
<span class="badge bg-secondary">Tidak ada role</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="text-center">{{ $user->created_at->format('d/m/Y H:i') }}</td>
|
||||
<td class="text-center">
|
||||
<div class="d-flex align-items-center gap-10 justify-content-center">
|
||||
<a href="{{ route('admin.users.show', $user) }}"
|
||||
class="bg-info-focus text-info-600 bg-hover-info-200 fw-medium w-40-px h-40-px d-flex justify-content-center align-items-center rounded-circle text-decoration-none"
|
||||
title="Lihat Detail">
|
||||
<iconify-icon icon="lucide:eye" class="menu-icon"></iconify-icon>
|
||||
</a>
|
||||
<a href="{{ route('admin.users.edit', $user) }}"
|
||||
class="bg-success-focus text-success-600 bg-hover-success-200 fw-medium w-40-px h-40-px d-flex justify-content-center align-items-center rounded-circle text-decoration-none"
|
||||
title="Edit Pengguna">
|
||||
<iconify-icon icon="lucide:edit" class="menu-icon"></iconify-icon>
|
||||
</a>
|
||||
@if ($user->id !== auth()->id())
|
||||
<button type="button"
|
||||
class="remove-item-btn bg-danger-focus bg-hover-danger-200 text-danger-600 fw-medium w-40-px h-40-px d-flex justify-content-center align-items-center rounded-circle"
|
||||
title="Hapus Pengguna" data-bs-toggle="modal" data-bs-target="#deleteModal"
|
||||
data-user-name="{{ $user->name }}" data-user-id="{{ $user->id }}">
|
||||
<iconify-icon icon="fluent:delete-24-regular"
|
||||
class="menu-icon"></iconify-icon>
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Modal -->
|
||||
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body text-center px-4 pb-4">
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="bg-danger-focus w-80-px h-80-px d-flex justify-content-center align-items-center rounded-circle mx-auto mb-3">
|
||||
<iconify-icon icon="fluent:delete-24-regular" class="text-danger-600"
|
||||
style="font-size: 2.5rem;"></iconify-icon>
|
||||
</div>
|
||||
<h4 class="mb-2 text-dark">Hapus Pengguna</h4>
|
||||
<p class="text-secondary mb-0">Apakah Anda yakin ingin menghapus pengguna <strong
|
||||
id="userNameText"></strong></br>? Tindakan ini tidak dapat dibatalkan.</p>
|
||||
</div>
|
||||
<div class="d-flex gap-2 justify-content-center pb-8">
|
||||
<button type="button" class="btn btn-light-secondary px-4" data-bs-dismiss="modal">Batal</button>
|
||||
<form id="deleteForm" method="POST" class="d-inline">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-danger px-4">Ya, Hapus</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
|
@ -0,0 +1,227 @@
|
|||
@extends('layout.layout')
|
||||
|
||||
@php
|
||||
$title = 'Detail Pengguna';
|
||||
$subTitle = 'Manajemen Pengguna';
|
||||
$script = '
|
||||
<script>
|
||||
// Handle delete modal
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const deleteModal = document.getElementById("deleteModal");
|
||||
const deleteForm = document.getElementById("deleteForm");
|
||||
const userNameText = document.getElementById("userNameText");
|
||||
|
||||
if (deleteModal) {
|
||||
deleteModal.addEventListener("show.bs.modal", function(event) {
|
||||
const button = event.relatedTarget;
|
||||
const userName = button.getAttribute("data-user-name");
|
||||
const userId = button.getAttribute("data-user-id");
|
||||
|
||||
userNameText.textContent = userName;
|
||||
deleteForm.action = "' . route('admin.users.index') . '/" + userId;
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>';
|
||||
@endphp
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="card basic-data-table">
|
||||
<div class="card-body">
|
||||
<div
|
||||
class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center mb-3 gap-3">
|
||||
<div>
|
||||
<h5 class="mb-0">Detail Pengguna: {{ $user->name }}</h5>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<a href="{{ route('admin.users.index') }}"
|
||||
class="btn btn-secondary btn-sm d-flex align-items-center gap-2">
|
||||
<iconify-icon icon="iconoir:arrow-left"></iconify-icon>
|
||||
<span>Kembali</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">Informasi Dasar</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-borderless mb-0">
|
||||
<tr>
|
||||
<td width="150"><strong>ID:</strong></td>
|
||||
<td>{{ $user->id }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Nama:</strong></td>
|
||||
<td>{{ $user->name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Email:</strong></td>
|
||||
<td>
|
||||
{{ $user->email }}
|
||||
@if ($user->email_verified_at)
|
||||
<span class="badge bg-success ms-2">Terverifikasi</span>
|
||||
@else
|
||||
<span class="badge bg-warning ms-2">Belum Terverifikasi</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Username:</strong></td>
|
||||
<td>{{ $user->username }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Dibuat:</strong></td>
|
||||
<td>{{ $user->created_at->format('d/m/Y H:i:s') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Diperbarui:</strong></td>
|
||||
<td>{{ $user->updated_at->format('d/m/Y H:i:s') }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">Role & Permissions</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<strong>Role:</strong><br>
|
||||
@if ($user->roles->count() > 0)
|
||||
@foreach ($user->roles as $role)
|
||||
<span class="badge bg-primary me-1 mb-1">{{ $role->name }}</span>
|
||||
@endforeach
|
||||
@else
|
||||
<span class="badge bg-secondary">Tidak ada role</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<strong>Permissions:</strong><br>
|
||||
@if ($user->getAllPermissions()->count() > 0)
|
||||
<div class="row">
|
||||
@foreach ($user->getAllPermissions()->chunk(ceil($user->getAllPermissions()->count() / 2)) as $permissionChunk)
|
||||
<div class="col-12">
|
||||
@foreach ($permissionChunk as $permission)
|
||||
<span class="badge bg-info me-1 mb-1">{{ $permission->name }}</span>
|
||||
@endforeach
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<span class="badge bg-secondary">Tidak ada permission</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Activity Summary -->
|
||||
<div class="row g-4 mt-2">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">Ringkasan Aktivitas</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-3">
|
||||
<div class="card bg-light text-center">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title">Total Role</h6>
|
||||
<h3 class="text-primary mb-0">{{ $user->roles->count() }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card bg-light text-center">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title">Total Permission</h6>
|
||||
<h3 class="text-info mb-0">{{ $user->getAllPermissions()->count() }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card bg-light text-center">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title">Status Email</h6>
|
||||
@if ($user->email_verified_at)
|
||||
<h3 class="text-success mb-0">✓</h3>
|
||||
@else
|
||||
<h3 class="text-warning mb-0">✗</h3>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card bg-light text-center">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title">Akun Aktif</h6>
|
||||
<h3 class="text-success mb-0">{{ $user->created_at->diffInDays(now()) }} hari
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 d-flex gap-2">
|
||||
<a href="{{ route('admin.users.edit', $user) }}" class="btn btn-primary d-flex align-items-center gap-2">
|
||||
<iconify-icon icon="lucide:edit"></iconify-icon>
|
||||
<span>Edit</span>
|
||||
</a>
|
||||
@if ($user->id !== auth()->id())
|
||||
<button type="button" class="btn btn-danger d-flex align-items-center gap-2" data-bs-toggle="modal"
|
||||
data-bs-target="#deleteModal" data-user-name="{{ $user->name }}"
|
||||
data-user-id="{{ $user->id }}">
|
||||
<iconify-icon icon="fluent:delete-24-regular"></iconify-icon>
|
||||
<span>Hapus</span>
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Modal -->
|
||||
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body text-center px-4 pb-4">
|
||||
<div class="mb-3">
|
||||
<div
|
||||
class="bg-danger-focus w-80-px h-80-px d-flex justify-content-center align-items-center rounded-circle mx-auto mb-3">
|
||||
<iconify-icon icon="fluent:delete-24-regular" class="text-danger-600 text-4xl"></iconify-icon>
|
||||
</div>
|
||||
<h4 class="mb-2 text-dark">Hapus Pengguna</h4>
|
||||
<p class="text-secondary mb-0">Apakah Anda yakin ingin menghapus pengguna <strong
|
||||
id="userNameText"></strong>? Tindakan ini tidak dapat dibatalkan.</p>
|
||||
</div>
|
||||
<div class="d-flex gap-2 justify-content-center">
|
||||
<button type="button" class="btn btn-light-secondary px-4" data-bs-dismiss="modal">Batal</button>
|
||||
<form id="deleteForm" method="POST" class="d-inline">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-danger px-4">Ya, Hapus</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endsection
|
|
@ -40,14 +40,7 @@
|
|||
<small id="login-error" class="text-danger" style="display:none;"></small>
|
||||
</div>
|
||||
|
||||
{{-- <div class="">
|
||||
<div class="d-flex justify-content-between gap-2">
|
||||
<div class="form-check style-check d-flex align-items-center">
|
||||
<input class="form-check-input border border-neutral-300" type="checkbox" value="" id="remember">
|
||||
<label class="form-check-label" for="remember">Ingat saya</label>
|
||||
</div>
|
||||
</div>
|
||||
</div> --}}
|
||||
|
||||
<button id="btn-login" type="submit" class="login-button">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -57,6 +50,12 @@
|
|||
|
||||
<x-script />
|
||||
<script>
|
||||
// Clear any leftover tokens on signin page
|
||||
try {
|
||||
localStorage.removeItem('auth_token');
|
||||
localStorage.removeItem('auth_user');
|
||||
} catch (e) {}
|
||||
|
||||
document.querySelector('.password-toggle').addEventListener('click', function() {
|
||||
const passwordInput = document.getElementById('password');
|
||||
const icon = this.querySelector('i');
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>@yield('title', 'Sistem Perizinan Lingkungan')</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
|
||||
<link rel="icon" type="image/ico" href="{{ asset('favicon.ico') }}" sizes="16x16">
|
||||
|
||||
|
@ -32,6 +33,15 @@
|
|||
|
||||
@stack('css')
|
||||
|
||||
@guest
|
||||
<script>
|
||||
try {
|
||||
localStorage.removeItem('auth_token');
|
||||
localStorage.removeItem('auth_user');
|
||||
} catch (e) {}
|
||||
</script>
|
||||
@endguest
|
||||
|
||||
@if (file_exists(public_path('build/manifest.json')) || file_exists(public_path('hot')))
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
@else
|
||||
|
|
|
@ -61,6 +61,15 @@
|
|||
{!! $css ?? '' !!}
|
||||
@stack('css')
|
||||
|
||||
@guest
|
||||
<script>
|
||||
try {
|
||||
localStorage.removeItem('auth_token');
|
||||
localStorage.removeItem('auth_user');
|
||||
} catch (e) {}
|
||||
</script>
|
||||
@endguest
|
||||
|
||||
<!-- Include select2 CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.0.13/dist/css/select2.min.css" rel="stylesheet" />
|
||||
</head>
|
||||
|
|
|
@ -236,6 +236,10 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form id="logout-form" action="{{ route('logout.session') }}" method="POST" class="d-none">
|
||||
@csrf
|
||||
<input type="hidden" name="_token" value="{{ csrf_token() }}">
|
||||
</form>
|
||||
<script>
|
||||
(function(){
|
||||
const btn = document.getElementById('btn-logout');
|
||||
|
@ -252,19 +256,15 @@
|
|||
}
|
||||
} catch (err) { /* ignore */ }
|
||||
|
||||
try {
|
||||
await fetch('{{ route('logout.session') }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
}).catch(()=>{});
|
||||
} catch (err) { /* ignore */ }
|
||||
|
||||
localStorage.removeItem('auth_token');
|
||||
localStorage.removeItem('auth_user');
|
||||
// Submit standard POST form to ensure session logout with CSRF
|
||||
const form = document.getElementById('logout-form');
|
||||
if (form) {
|
||||
form.submit();
|
||||
} else {
|
||||
window.location.href = '{{ route('login.index') }}';
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
|
|
@ -113,7 +113,7 @@
|
|||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ route ('persetujuan.amdal.index') }}"><i class="text-primary-600 w-auto"></i>
|
||||
<a href="{{ route('persetujuan.amdal.index') }}"><i class="text-primary-600 w-auto"></i>
|
||||
<iconify-icon icon="bi:journal-text" class="menu-icon"></iconify-icon>
|
||||
<span>Amdal</span>
|
||||
</a>
|
||||
|
@ -199,7 +199,7 @@
|
|||
{{-- submenu --}}
|
||||
<ul class="sidebar-submenu">
|
||||
<li>
|
||||
<a href="/admin/pengguna"><i class="text-primary-600 w-auto"></i>
|
||||
<a href="{{ route('admin.users.index') }}"><i class="text-primary-600 w-auto"></i>
|
||||
<iconify-icon icon="bi:person-fill" class="menu-icon"></iconify-icon>
|
||||
<span>Pengguna</span>
|
||||
</a>
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
|
||||
<title>@yield('title', 'Dashboard') - {{ config('app.name', 'Perling') }}</title>
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
|
||||
<!-- Lucide Icons -->
|
||||
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.js"></script>
|
||||
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
|
||||
@stack('styles')
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="{{ route('dashboard.index') }}">
|
||||
{{ config('app.name', 'Perling') }}
|
||||
</a>
|
||||
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ route('dashboard.index') }}">Dashboard</a>
|
||||
</li>
|
||||
@can('settings.manage')
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
|
||||
Pengaturan
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="{{ route('admin.users.index') }}">Manajemen
|
||||
Pengguna</a></li>
|
||||
<li><a class="dropdown-item" href="{{ route('admin.roles.index') }}">Manajemen Role</a>
|
||||
</li>
|
||||
<li><a class="dropdown-item" href="{{ route('admin.permissions.index') }}">Manajemen
|
||||
Permission</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
@endcan
|
||||
</ul>
|
||||
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
|
||||
{{ Auth::user()->name }}
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li><a class="dropdown-item" href="{{ route('profile.index') }}">Profile</a></li>
|
||||
<li>
|
||||
<hr class="dropdown-divider">
|
||||
</li>
|
||||
<li>
|
||||
<form method="POST" action="{{ route('logout.session') }}">
|
||||
@csrf
|
||||
<button type="submit" class="dropdown-item">Logout</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="py-4">
|
||||
@yield('content')
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<!-- Initialize Lucide Icons -->
|
||||
<script>
|
||||
lucide.createIcons();
|
||||
</script>
|
||||
|
||||
@stack('scripts')
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -2,12 +2,27 @@
|
|||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Controllers\AuthController;
|
||||
use App\Http\Controllers\DashboardController;
|
||||
|
||||
// API Authentication Routes (Token-based)
|
||||
Route::prefix('auth')->group(function () {
|
||||
Route::post('/login', [AuthController::class, 'login']);
|
||||
|
||||
Route::middleware('auth:sanctum')->group(function () {
|
||||
Route::get('/me', [AuthController::class, 'me']);
|
||||
Route::post('/logout', [AuthController::class, 'logout']);
|
||||
Route::post('/logout-all', [AuthController::class, 'logoutAll']);
|
||||
Route::get('/me', [AuthController::class, 'me']);
|
||||
});
|
||||
});
|
||||
|
||||
// Protected API Routes
|
||||
Route::middleware('auth:sanctum')->group(function () {
|
||||
// Dashboard API endpoints
|
||||
Route::prefix('dashboard')->group(function () {
|
||||
Route::get('/summary', [DashboardController::class, 'apiSummary']);
|
||||
Route::get('/pertek', [DashboardController::class, 'apiPertek']);
|
||||
Route::get('/rintek', [DashboardController::class, 'apiRintek']);
|
||||
Route::get('/amdal', [DashboardController::class, 'apiAmdal']);
|
||||
Route::get('/bengkel', [DashboardController::class, 'apiBengkel']);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,10 +6,10 @@ use App\Http\Controllers\Izin\IzinAngkutController;
|
|||
use App\Http\Controllers\Izin\IzinEmisiController;
|
||||
use App\Http\Controllers\JadwalSidangController;
|
||||
use App\Http\Controllers\LoginController;
|
||||
use App\Http\Controllers\AuthController as WebAuthController;
|
||||
use App\Http\Controllers\WebAuthController;
|
||||
use App\Http\Controllers\NewsController;
|
||||
use App\Http\Controllers\PenugasanController;
|
||||
use App\Http\Controllers\PerizinanLingkunganController;
|
||||
|
||||
use App\Http\Controllers\Admin\RoleController;
|
||||
use App\Http\Controllers\Admin\PermissionController;
|
||||
use App\Http\Controllers\Persetujuan\AddendumController;
|
||||
|
@ -50,7 +50,7 @@ Route::get('/surat/pertek/penerimaan', function () {
|
|||
});
|
||||
|
||||
Route::get('/admin/pengguna', function () {
|
||||
return view('/pengguna/index_user');
|
||||
return view('/admin/pengguna');
|
||||
});
|
||||
|
||||
Route::get('/news', [NewsController::class, 'index'])->name('news.index');
|
||||
|
@ -61,22 +61,35 @@ Route::post('/surat/save', [SuratArahanController::class, 'save'])->name('surat.
|
|||
// Route::get('/surat/exportPDF', [SuratArahanController::class, 'exportPDF'])->name('surat.exportPDF');
|
||||
Route::match(['get', 'post'], '/surat/exportPDF', [SuratArahanController::class, 'exportPDF'])->name('surat.exportPDF');
|
||||
|
||||
// Dashboard
|
||||
Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard.index');
|
||||
Route::get('/dashboard-pertek', [DashboardController::class, 'pertek'])->middleware('permission:dashboard.view.pertek')->name('dashboard.pertek');
|
||||
Route::get('/dashboard-rintek', [DashboardController::class, 'rintek'])->middleware('permission:dashboard.view.rintek')->name('dashboard.rintek');
|
||||
Route::get('/dashboard-amdal', [DashboardController::class, 'amdal'])->middleware('permission:dashboard.view.amdal')->name('dashboard.amdal');
|
||||
Route::get('/dashboard-bengkel', [DashboardController::class, 'bengkel'])->middleware('permission:dashboard.view.uji_emisi')->name('dashboard.bengkel');
|
||||
// Protected Web Routes (Session-based authentication)
|
||||
Route::middleware(['web.auth'])->group(function () {
|
||||
// Dashboard
|
||||
Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard.index');
|
||||
Route::get('/dashboard-pertek', [DashboardController::class, 'pertek'])->middleware('permission:dashboard.view.pertek')->name('dashboard.pertek');
|
||||
Route::get('/dashboard-rintek', [DashboardController::class, 'rintek'])->middleware('permission:dashboard.view.rintek')->name('dashboard.rintek');
|
||||
Route::get('/dashboard-amdal', [DashboardController::class, 'amdal'])->middleware('permission:dashboard.view.amdal')->name('dashboard.amdal');
|
||||
Route::get('/dashboard-bengkel', [DashboardController::class, 'bengkel'])->middleware('permission:dashboard.view.uji_emisi')->name('dashboard.bengkel');
|
||||
});
|
||||
|
||||
|
||||
|
||||
// login
|
||||
// Web Authentication (Session-based with cookies)
|
||||
Route::get('/auth/login', [LoginController::class, 'index'])->name('login.index');
|
||||
Route::post('/auth/session-login', [WebAuthController::class, 'sessionLogin'])->name('login.session');
|
||||
Route::post('/auth/logout', [WebAuthController::class, 'logout'])->name('logout.session');
|
||||
Route::post('/auth/logout', [WebAuthController::class, 'sessionLogout'])->name('logout.session');
|
||||
|
||||
// Roles & Permissions Management (restricted to settings.manage)
|
||||
Route::prefix('admin')->middleware('permission:settings.manage')->group(function () {
|
||||
// User Management & Settings (restricted to settings.manage)
|
||||
Route::prefix('admin')->middleware(['web.auth', 'permission:settings.manage'])->group(function () {
|
||||
// User Management
|
||||
Route::get('/users', [\App\Http\Controllers\Admin\UserController::class, 'index'])->name('admin.users.index');
|
||||
Route::get('/users/create', [\App\Http\Controllers\Admin\UserController::class, 'create'])->name('admin.users.create');
|
||||
Route::post('/users', [\App\Http\Controllers\Admin\UserController::class, 'store'])->name('admin.users.store');
|
||||
Route::get('/users/{user}', [\App\Http\Controllers\Admin\UserController::class, 'show'])->name('admin.users.show');
|
||||
Route::get('/users/{user}/edit', [\App\Http\Controllers\Admin\UserController::class, 'edit'])->name('admin.users.edit');
|
||||
Route::put('/users/{user}', [\App\Http\Controllers\Admin\UserController::class, 'update'])->name('admin.users.update');
|
||||
Route::delete('/users/{user}', [\App\Http\Controllers\Admin\UserController::class, 'destroy'])->name('admin.users.destroy');
|
||||
|
||||
// Roles & Permissions Management
|
||||
Route::get('/roles', [RoleController::class, 'index'])->name('admin.roles.index');
|
||||
Route::get('/roles/create', [RoleController::class, 'create'])->name('admin.roles.create');
|
||||
Route::post('/roles', [RoleController::class, 'store'])->name('admin.roles.store');
|
||||
|
@ -93,7 +106,7 @@ Route::prefix('admin')->middleware('permission:settings.manage')->group(function
|
|||
});
|
||||
|
||||
// Pertek
|
||||
Route::prefix('admin')->group(function () {
|
||||
Route::prefix('admin')->middleware(['web.auth'])->group(function () {
|
||||
Route::get('/pertek/arahan', [PersetujuanTeknisController::class, 'index_arahan'])->middleware('permission:persetujuan_teknis.access')->name('pertek.index_arahan');
|
||||
Route::get('/pertek/slo', [PersetujuanTeknisController::class, 'index_slo'])->middleware('permission:persetujuan_teknis.access')->name('pertek.index_slo');
|
||||
Route::get('/pertek/detail-slo', [PersetujuanTeknisController::class, 'detail_slo'])->middleware('permission:persetujuan_teknis.access')->name('pertek.detail_slo');
|
||||
|
@ -107,19 +120,19 @@ Route::prefix('admin')->group(function () {
|
|||
});
|
||||
|
||||
//rintek
|
||||
Route::prefix('admin')->group(function () {
|
||||
Route::prefix('admin')->middleware(['web.auth'])->group(function () {
|
||||
Route::get('/rintek/arahan', [RincianTeknisController::class, 'index_arahan'])->middleware('permission:rincian_teknis.access')->name('rintek.index_arahan');
|
||||
Route::get('/rintek/create-arahan', [RincianTeknisController::class, 'create_arahan'])->middleware('permission:rincian_teknis.access')->name('rintek.create_arahan');
|
||||
Route::get('/rintek/verifikator/arahan', [RincianTeknisController::class, 'verifikator_arahan'])->middleware('permission:rincian_teknis.access')->name('rintek.verifikator_arahan');
|
||||
});
|
||||
|
||||
//izin angkut
|
||||
Route::prefix('admin')->group(function () {
|
||||
Route::prefix('admin')->middleware(['web.auth'])->group(function () {
|
||||
Route::get('/izinangkut', [IzinAngkutController::class, 'index_angkut'])->middleware('permission:izin_angkut_olah.access')->name('izinangkut.index_permohonan');
|
||||
});
|
||||
|
||||
//izin angkut
|
||||
Route::prefix('admin')->group(function () {
|
||||
//izin emisi
|
||||
Route::prefix('admin')->middleware(['web.auth'])->group(function () {
|
||||
Route::get('/izinemisi', [IzinEmisiController::class, 'index_emisi'])->middleware('permission:izin_tempat_uji_emisi.access')->name('izinemisi.index_permohonan');
|
||||
});
|
||||
|
||||
|
@ -129,62 +142,59 @@ Route::prefix('admin')->group(function () {
|
|||
// Route::get('/jadwal/create', [JadwalSidangController::class, 'create'])->name('jadwal.create');
|
||||
// });
|
||||
|
||||
Route::prefix('admin')->group(function () {
|
||||
// Jadwal Sidang
|
||||
Route::prefix('admin')->middleware(['web.auth'])->group(function () {
|
||||
Route::get('/jadwal', [JadwalSidangController::class, 'index'])->middleware('permission:penjadwalan.access')->name('jadwal.index');
|
||||
Route::match(['get', 'post'], '/jadwal/create', [JadwalSidangController::class, 'create'])->middleware('permission:penjadwalan.access')->name('jadwal.create');
|
||||
});
|
||||
|
||||
// Penugasan
|
||||
Route::prefix('admin')->group(function () {
|
||||
Route::prefix('admin')->middleware(['web.auth'])->group(function () {
|
||||
Route::get('/penugasan', [PenugasanController::class, 'index'])->name('penugasan.index');
|
||||
});
|
||||
|
||||
Route::prefix('admin')->group(function () {
|
||||
// Profile
|
||||
Route::prefix('admin')->middleware(['web.auth'])->group(function () {
|
||||
Route::get('/profile', [ProfileController::class, 'index'])->name('profile.index');
|
||||
});
|
||||
|
||||
Route::prefix('admin')->group(function () {
|
||||
// News Management
|
||||
Route::prefix('admin')->middleware(['web.auth'])->group(function () {
|
||||
Route::get('/news', [NewsController::class, 'index_newsvideo'])->name('news.index_newsvideo');
|
||||
Route::get('/news/create', [NewsController::class, 'create_newsvideo'])->name('news.create_newsvideo');
|
||||
});
|
||||
|
||||
Route::prefix('admin')->group(function () {
|
||||
// Persetujuan Lingkungan Routes
|
||||
Route::prefix('admin')->middleware(['web.auth'])->group(function () {
|
||||
// Kerangka Acuan
|
||||
Route::get('/kerangka', [KerangkaController::class, 'index'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.kerangka.index');
|
||||
Route::get('/kerangka/create', [KerangkaController::class, 'create'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.kerangka.create');
|
||||
});
|
||||
|
||||
Route::prefix('admin')->group(function () {
|
||||
// AMDAL
|
||||
Route::get('/amdal', [AmdalController::class, 'index'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.amdal.index');
|
||||
Route::get('/amdal/detail', [AmdalController::class, 'detail'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.amdal.detail');
|
||||
});
|
||||
|
||||
Route::prefix('admin')->group(function () {
|
||||
// UKL-UPL
|
||||
Route::get('/ukl', [UklController::class, 'index'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.ukl.index');
|
||||
Route::get('/ukl/detail', [UklController::class, 'detail'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.ukl.detail');
|
||||
});
|
||||
|
||||
Route::prefix('admin')->group(function () {
|
||||
// RKL-RPL
|
||||
Route::get('/rkl', [RklController::class, 'index'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.rkl.index');
|
||||
Route::get('/rkl/detail', [RklController::class, 'detail'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.rkl.detail');
|
||||
});
|
||||
|
||||
Route::prefix('admin')->group(function () {
|
||||
// SPPL
|
||||
Route::get('/sppl', [SpplController::class, 'index'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.sppl.index');
|
||||
Route::get('/sppl/detail', [SpplController::class, 'detail'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.sppl.detail');
|
||||
});
|
||||
|
||||
Route::prefix('admin')->group(function () {
|
||||
// Addendum
|
||||
Route::get('/addendum', [AddendumController::class, 'index'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.addendum.index');
|
||||
Route::get('/addendum/detail', [AddendumController::class, 'detail'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.addendum.detail');
|
||||
});
|
||||
|
||||
Route::prefix('admin')->group(function () {
|
||||
// DELH
|
||||
Route::get('/delh', [DelhController::class, 'index'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.delh.index');
|
||||
Route::get('/delh/detail', [DelhController::class, 'detail'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.delh.detail');
|
||||
});
|
||||
|
||||
|
||||
Route::prefix('admin')->group(function () {
|
||||
// DPLH
|
||||
Route::get('/dplh', [DplhController::class, 'index'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.dplh.index');
|
||||
Route::get('/dplh/detail', [DplhController::class, 'detail'])->middleware('permission:persetujuan_lingkungan.access')->name('persetujuan.dplh.detail');
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue