[feat RoleMiddleware, update AuthControlller, app.php, BrankasList, TrayList, Login.vue, web.php

This commit is contained in:
timotiabbauftech 2025-09-08 09:48:32 +07:00
parent e1a0711082
commit 8e59b1f1f1
7 changed files with 163 additions and 86 deletions

View File

@ -29,10 +29,14 @@ class AuthController extends Controller
// buat token Sanctum // buat token Sanctum
$token = $user->createToken('auth_token')->plainTextToken; $token = $user->createToken('auth_token')->plainTextToken;
$redirectUrl = $user->role === 'owner' ? '/brankas' : '/kasir';
return response()->json([ return response()->json([
'message' => 'Login berhasil', 'message' => 'Login berhasil',
'user' => $user, 'user' => $user,
'token' => $token, 'token' => $token,
'redirect' => $redirectUrl,
'role' => $user->role
]); ]);
} }

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class RoleMiddleware
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next, ...$roles): Response
{
// cek apakah user login
if (!$request->user()) {
return response()->json(['message' => 'Unauthenticated'], 401);
}
// cek role user
if (!in_array($request->user()->role, $roles)) {
return response()->json(['message' => 'Forbidden'], 403);
}
return $next($request);
}
}

View File

@ -15,6 +15,10 @@ return Application::configure(basePath: dirname(__DIR__))
$middleware->validateCsrfTokens(except: [ $middleware->validateCsrfTokens(except: [
'api/*' 'api/*'
]); ]);
$middleware->alias([
'role' => \App\Http\Middleware\RoleMiddleware::class,
]);
}) })
->withExceptions(function (Exceptions $exceptions): void { ->withExceptions(function (Exceptions $exceptions): void {
// //

View File

@ -45,7 +45,11 @@ const error = ref(null);
onMounted(async () => { onMounted(async () => {
try { try {
const res = await axios.get("/api/item"); // ganti sesuai URL backend const res = await axios.get("/api/item",{
headers:{
Authorization: `Bearer ${localStorage.getItem("token")}`,
}
}); // ganti sesuai URL backend
items.value = res.data; // pastikan backend return array of items items.value = res.data; // pastikan backend return array of items
console.log(res.data); console.log(res.data);

View File

@ -139,8 +139,14 @@ const saveMove = async () => {
if (!selectedTrayId.value || !selectedItem.value) return; if (!selectedTrayId.value || !selectedItem.value) return;
try { try {
await axios.put(`/api/item/${selectedItem.value.id}`, { await axios.put(`/api/item/${selectedItem.value.id}`, {
header:{
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
body:{
id_nampan: selectedTrayId.value, id_nampan: selectedTrayId.value,
id_produk: selectedItem.value.id_produk, // ikutkan id_produk karena API minta id_produk: selectedItem.value.id_produk, // ikutkan id_produk karena API minta
},
}); });
await refreshData(); await refreshData();

View File

@ -3,46 +3,70 @@
<div class="bg-white p-8 rounded-2xl shadow-xl w-80 text-center"> <div class="bg-white p-8 rounded-2xl shadow-xl w-80 text-center">
<!-- Logo + Title --> <!-- Logo + Title -->
<div class="mb-6"> <div class="mb-6">
<img :src="logo" alt="Logo" class="mx-auto w-34 py-5"> <img :src="logo" alt="Logo" class="mx-auto w-34 py-5" />
</div> </div>
<!-- Input --> <!-- Input -->
<div> <div>
<InputField v-model="username" type="text" placeholder="Username"class="mb-4"/> <InputField
v-model="username"
type="text"
placeholder="Username"
class="mb-4"
/>
<PasswordInput v-model="password" placeholder="Password" /> <PasswordInput v-model="password" placeholder="Password" />
</div> </div>
<!-- Button --> <!-- Button -->
<button <button
@click="handleLogin" @click="handleLogin"
class="w-full py-2 bg-sky-400 hover:bg-sky-500 rounded font-bold text-gray-800 transition" :disabled="loading"
class="w-full py-2 bg-sky-400 hover:bg-sky-500 rounded font-bold text-gray-800 transition disabled:opacity-50"
> >
Login {{ loading ? "Loading..." : "Login" }}
</button> </button>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref } from "vue"; import { ref } from "vue";
import logo from '@/../images/logo.png' import logo from '@/../images/logo.png'
import InputField from "@/components/InputField.vue"; import InputField from "@/components/InputField.vue";
import PasswordInput from "@/components/InputPassword.vue";
import axios from "axios";
const username = ref(""); const username = ref("");
const password = ref(""); const password = ref("");
import PasswordInput from "@/components/InputPassword.vue"; const loading = ref(false);
// import { ref } from "vue";
// const username = ref(""); const handleLogin = async () => {
// const password = ref(""); if (!username.value || !password.value) {
alert("Harap isi username dan password!");
return;
}
// const handleLogin = () => { loading.value = true;
// if (!username.value || !password.value) { try {
// alert("Harap isi username dan password!"); const res = await axios.post("/api/login", {
// return; nama: username.value,
// } password: password.value,
// // Contoh: panggil API login });
// console.log("Login dengan:", username.value, password.value);
// }; const data = res.data;
// Simpan token & role
localStorage.setItem("token", data.token);
localStorage.setItem("role", data.role);
// Redirect sesuai role
window.location.href = data.redirect;
} catch (error) {
console.error(error);
alert("Login gagal. Periksa username atau password.");
} finally {
loading.value = false;
}
};
</script> </script>

View File

@ -1,39 +1,37 @@
<?php <?php
use App\Http\Controllers\AuthController;
use App\Http\Controllers\FotoSementaraController; use App\Http\Controllers\AuthController;
use App\Http\Controllers\ItemController; use App\Http\Controllers\FotoSementaraController;
use App\Http\Controllers\KategoriController; use App\Http\Controllers\ItemController;
use App\Http\Controllers\NampanController; use App\Http\Controllers\KategoriController;
use App\Http\Controllers\ProdukController; use App\Http\Controllers\NampanController;
use App\Http\Controllers\SalesController; use App\Http\Controllers\ProdukController;
use App\Http\Controllers\UserController; use App\Http\Controllers\SalesController;
use App\Http\Controllers\TransaksiController; use App\Http\Controllers\UserController;
use App\Http\Controllers\LaporanController; use App\Http\Controllers\TransaksiController;
use Illuminate\Support\Facades\Route; use App\Http\Controllers\LaporanController;
use App\Models\Kategori;
use Illuminate\Support\Facades\Route;
Route::prefix('api')->group(function () {
// Backend API
Route::apiResource('nampan', NampanController::class);
Route::apiResource('produk', ProdukController::class);
Route::apiResource('item', ItemController::class);
Route::apiResource('sales', SalesController::class);
Route::apiResource('user', UserController::class);
Route::apiResource('transaksi', TransaksiController::class);
Route::apiResource('kategori', KategoriController::class);
// ============================
// Backend API // Backend API
// ============================
Route::prefix('api')->group(function () { Route::prefix('api')->group(function () {
Route::apiResource('nampan', NampanController::class); // Auth
Route::apiResource('produk', ProdukController::class); Route::post('/login', [AuthController::class, 'login'])->middleware('guest')->name('login');
Route::apiResource('item', ItemController::class); Route::post('/logout', [AuthController::class, 'logout'])->middleware('auth:sanctum')->name('logout');
Route::apiResource('sales', SalesController::class);
Route::apiResource('user', UserController::class); Route::middleware(['auth:sanctum', 'role:owner'])->group(function () {
Route::apiResource('transaksi', TransaksiController::class); Route::apiResource('nampan', NampanController::class)->except(['index', 'show']);
Route::apiResource('kategori', KategoriController::class); Route::apiResource('produk', ProdukController::class)->except(['index', 'show']);
Route::apiResource('item', ItemController::class)->except(['index', 'show']);
Route::apiResource('sales', SalesController::class)->except(['index', 'show']);
Route::apiResource('kategori', KategoriController::class)->except(['index', 'show']);
Route::apiResource('user', UserController::class);
// Custom Endpoint
Route::get('brankas', [ItemController::class, 'brankasItem']);
Route::delete('kosongkan-nampan', [NampanController::class, 'kosongkan']); Route::delete('kosongkan-nampan', [NampanController::class, 'kosongkan']);
// Foto Sementara // Foto Sementara
@ -45,22 +43,29 @@ Route::prefix('api')->group(function () {
// Laporan // Laporan
Route::get('laporan', [LaporanController::class, 'ringkasan']); Route::get('laporan', [LaporanController::class, 'ringkasan']);
Route::get('detail-laporan', [LaporanController::class, 'detail']); Route::get('detail-laporan', [LaporanController::class, 'detail']);
});
Route::get('brankas', [ItemController::class, 'brankasItem']);
Route::delete('kosongkan-nampan', [NampanController::class, 'kosongkan']);
// Foto Sementara
Route::post('foto/upload', [FotoSementaraController::class, 'upload']);
Route::delete('foto/hapus/{id}', [FotoSementaraController::class, 'hapus']);
Route::get('foto/{user_id}', [FotoSementaraController::class, 'getAll']);
Route::delete('foto/reset/{user_id}', [FotoSementaraController::class, 'reset']);
Route::post('/login', [AuthController::class, 'login'])->middleware('guest')->name('login');
Route::post('/logout', [AuthController::class, 'logout'])->middleware('auth:sanctum')->name('logout');
}); });
// Frontend SPA Route::middleware(['auth:sanctum', 'role:owner,kasir'])->group(function () {
Route::get('/{any}', function () { Route::apiResource('transaksi', TransaksiController::class);
Route::get('produk', [ProdukController::class, 'index']);
Route::get('produk/{id}', [ProdukController::class, 'show']);
Route::get('nampan', [NampanController::class, 'index']);
Route::get('nampan/{id}', [NampanController::class, 'show']);
Route::get('item', [ItemController::class, 'index']);
Route::get('item/{id}', [ItemController::class, 'show']);
Route::get('sales', [SalesController::class, 'index']);
Route::get('sales/{id}', [SalesController::class, 'show']);
Route::get('kategori', [KategoriController::class, 'index']);
Route::get('kategori/{id}', [KategoriController::class, 'show']);
Route::get('brankas', [ItemController::class, 'brankasItem']);
});
});
// ============================
// Frontend SPA (Vue / React dll.)
// ============================
Route::get('/{any}', function () {
return view('app'); return view('app');
})->where('any', '^(?!storage|api).*$'); })->where('any', '^(?!storage|api).*$');