220 lines
10 KiB
PHP
220 lines
10 KiB
PHP
<?php
|
|
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Route;
|
|
use App\Http\Controllers\Api\AuthController;
|
|
use App\Http\Controllers\Api\KontrakanController;
|
|
use App\Http\Controllers\Api\LaundryController;
|
|
use App\Http\Controllers\Api\SAWController;
|
|
use App\Http\Controllers\Api\BookingController;
|
|
use App\Http\Controllers\Api\ReviewController;
|
|
use App\Http\Controllers\Api\FavoriteController;
|
|
use App\Http\Controllers\Api\DeviceTokenController;
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| API Routes untuk Mobile App
|
|
|--------------------------------------------------------------------------
|
|
| Routes ini khusus untuk aplikasi mobile Flutter
|
|
| Semua response menggunakan format JSON
|
|
| Rate limiting diterapkan di semua endpoint
|
|
*/
|
|
|
|
// -------------------------------------------------
|
|
// API HEALTH CHECK & DOCUMENTATION
|
|
// -------------------------------------------------
|
|
Route::get('/health', function () {
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'API is running',
|
|
'version' => '1.0.0',
|
|
'server_time' => now()->toIso8601String(),
|
|
'status' => 'healthy',
|
|
]);
|
|
});
|
|
|
|
Route::get('/docs', function () {
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'SPK Kontrakan & Laundry API Documentation',
|
|
'version' => '1.0.0',
|
|
'base_url' => url('/api'),
|
|
'endpoints' => [
|
|
'health' => [
|
|
'GET /api/health' => 'Cek status API',
|
|
],
|
|
'auth' => [
|
|
'POST /api/register' => 'Register user baru (rate: 3/hour)',
|
|
'POST /api/login' => 'Login user (rate: 5/min)',
|
|
'POST /api/logout' => 'Logout user [AUTH]',
|
|
'PUT /api/profile/update' => 'Update profile [AUTH]',
|
|
],
|
|
'kontrakan' => [
|
|
'GET /api/kontrakan' => 'List kontrakan (filter: search, harga_min, harga_max, jumlah_kamar, sort_by, sort_order, per_page)',
|
|
'GET /api/kontrakan/{id}' => 'Detail kontrakan',
|
|
'GET /api/kontrakan/{id}/galeri' => 'Galeri foto kontrakan',
|
|
'GET /api/kontrakan/{id}/reviews' => 'Reviews kontrakan',
|
|
],
|
|
'laundry' => [
|
|
'GET /api/laundry' => 'List laundry (filter: search, harga_min, harga_max, sort_by, sort_order, per_page)',
|
|
'GET /api/laundry/{id}' => 'Detail laundry',
|
|
'GET /api/laundry/{id}/galeri' => 'Galeri foto laundry',
|
|
'GET /api/laundry/{id}/reviews' => 'Reviews laundry',
|
|
],
|
|
'saw_calculation' => [
|
|
'GET /api/saw/kriteria/kontrakan' => 'Get kriteria SAW kontrakan',
|
|
'POST /api/saw/calculate/kontrakan' => 'Hitung SAW kontrakan (rate: 30/min)',
|
|
'GET /api/saw/kriteria/laundry' => 'Get kriteria SAW laundry',
|
|
'POST /api/saw/calculate/laundry' => 'Hitung SAW laundry (rate: 30/min)',
|
|
],
|
|
'bookings' => [
|
|
'GET /api/bookings' => 'History booking [AUTH]',
|
|
'GET /api/bookings/{id}' => 'Detail booking [AUTH]',
|
|
'GET /api/bookings/{id}/payment-proof' => 'Lihat bukti bayar (secure) [AUTH]',
|
|
'POST /api/bookings' => 'Create booking [AUTH]',
|
|
'POST /api/bookings/{id}/cancel' => 'Cancel booking [AUTH]',
|
|
'POST /api/bookings/{id}/extend' => 'Extend booking [AUTH]',
|
|
'POST /api/bookings/{id}/payment-proof' => 'Upload bukti bayar [AUTH]',
|
|
],
|
|
'reviews' => [
|
|
'POST /api/reviews/kontrakan/{id}' => 'Review kontrakan [AUTH]',
|
|
'POST /api/reviews/laundry/{id}' => 'Review laundry [AUTH]',
|
|
'PUT /api/reviews/{id}' => 'Update review [AUTH]',
|
|
'DELETE /api/reviews/{id}' => 'Hapus review [AUTH]',
|
|
],
|
|
'favorites' => [
|
|
'GET /api/favorites' => 'List favorites [AUTH]',
|
|
'POST /api/favorites/kontrakan/{id}' => 'Toggle favorite kontrakan [AUTH]',
|
|
'POST /api/favorites/laundry/{id}' => 'Toggle favorite laundry [AUTH]',
|
|
'DELETE /api/favorites/{id}' => 'Hapus favorite [AUTH]',
|
|
],
|
|
],
|
|
'authentication' => [
|
|
'type' => 'Bearer Token (Laravel Sanctum)',
|
|
'header' => 'Authorization: Bearer {token}',
|
|
'note' => 'Token didapat dari endpoint login/register',
|
|
],
|
|
'response_format' => [
|
|
'success' => ['success' => true, 'message' => '...', 'data' => '...'],
|
|
'error' => ['success' => false, 'message' => '...', 'error_code' => '...', 'errors' => '...'],
|
|
],
|
|
'error_codes' => [
|
|
'VALIDATION_ERROR' => '422 - Input tidak valid',
|
|
'UNAUTHENTICATED' => '401 - Token tidak valid / kadaluarsa',
|
|
'NOT_FOUND' => '404 - Resource tidak ditemukan',
|
|
'METHOD_NOT_ALLOWED' => '405 - HTTP method salah',
|
|
'TOO_MANY_REQUESTS' => '429 - Rate limit exceeded',
|
|
'RATE_LIMIT_EXCEEDED' => '429 - Batas request terlampaui',
|
|
'SERVER_ERROR' => '500 - Kesalahan server',
|
|
'INVALID_CREDENTIALS' => '401 - Email/password salah',
|
|
],
|
|
]);
|
|
});
|
|
|
|
// -------------------------------------------------
|
|
// AUTH ROUTES (PUBLIC - WITH RATE LIMITING)
|
|
// -------------------------------------------------
|
|
// ✅ FIXED: Added rate limiting to prevent brute force attacks
|
|
Route::post('/register', [AuthController::class, 'register'])->middleware('throttle:3,1'); // 3 attempts per 1 minute
|
|
Route::post('/login', [AuthController::class, 'login'])->middleware('throttle:5,1'); // 5 attempts per 1 minute
|
|
|
|
// -------------------------------------------------
|
|
// KONTRAKAN ROUTES (PUBLIC - TANPA AUTH)
|
|
// -------------------------------------------------
|
|
Route::prefix('kontrakan')->group(function () {
|
|
Route::get('/range', [KontrakanController::class, 'getRange']); // HARUS SEBELUM /{id}
|
|
Route::get('/', [KontrakanController::class, 'index']);
|
|
Route::get('/{id}', [KontrakanController::class, 'show']);
|
|
Route::get('/{id}/galeri', [KontrakanController::class, 'getGaleri']);
|
|
Route::get('/{id}/reviews', [KontrakanController::class, 'getReviews']);
|
|
});
|
|
|
|
// -------------------------------------------------
|
|
// LAUNDRY ROUTES (PUBLIC - TANPA AUTH)
|
|
// -------------------------------------------------
|
|
Route::prefix('laundry')->group(function () {
|
|
Route::get('/range', [SAWController::class, 'getRangeLaundry']);
|
|
Route::get('/', [LaundryController::class, 'index']);
|
|
Route::get('/{id}', [LaundryController::class, 'show']);
|
|
Route::get('/{id}/galeri', [LaundryController::class, 'getGaleri']);
|
|
Route::get('/{id}/reviews', [LaundryController::class, 'getReviews']);
|
|
});
|
|
|
|
// -------------------------------------------------
|
|
// SAW CALCULATION ROUTES (PUBLIC - RATE LIMITED)
|
|
// -------------------------------------------------
|
|
Route::prefix('saw')->middleware('throttle:saw')->group(function () {
|
|
// Kontrakan
|
|
Route::get('/kriteria/kontrakan', [SAWController::class, 'getKriteriaKontrakan']);
|
|
Route::post('/calculate/kontrakan', [SAWController::class, 'calculateKontrakan']);
|
|
|
|
// Laundry
|
|
Route::get('/kriteria/laundry', [SAWController::class, 'getKriteriaLaundry']);
|
|
Route::post('/calculate/laundry', [SAWController::class, 'calculateLaundry']);
|
|
});
|
|
|
|
// -------------------------------------------------
|
|
// PROTECTED ROUTES (PERLU AUTH TOKEN)
|
|
// -------------------------------------------------
|
|
Route::middleware('auth:sanctum')->group(function () {
|
|
|
|
// User Profile
|
|
Route::get('/user', function (Request $request) {
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => $request->user(),
|
|
]);
|
|
});
|
|
Route::post('/logout', [AuthController::class, 'logout']);
|
|
Route::put('/profile/update', [AuthController::class, 'updateProfile']);
|
|
|
|
// Send/Resend Email Verification Notification
|
|
Route::post('/email/verification-notification', function (Request $request) {
|
|
if ($request->user()->hasVerifiedEmail()) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Email Anda sudah diverifikasi.',
|
|
], 400);
|
|
}
|
|
|
|
$request->user()->sendEmailVerificationNotification();
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Tautan verifikasi baru telah dikirim ke email Anda.',
|
|
]);
|
|
})->middleware('throttle:3,1');
|
|
|
|
// Booking Routes
|
|
Route::prefix('bookings')->group(function () {
|
|
Route::get('/', [BookingController::class, 'index']);
|
|
Route::get('/{id}', [BookingController::class, 'show']);
|
|
// ✅ Secure access to payment proof (no public storage URL)
|
|
Route::get('/{id}/payment-proof', [BookingController::class, 'getPaymentProof']);
|
|
Route::post('/', [BookingController::class, 'store']);
|
|
Route::post('/{id}/cancel', [BookingController::class, 'cancel']);
|
|
Route::post('/{id}/extend', [BookingController::class, 'extend']);
|
|
Route::post('/{id}/payment-proof', [BookingController::class, 'uploadPaymentProof']);
|
|
});
|
|
|
|
// Review Routes
|
|
Route::prefix('reviews')->group(function () {
|
|
Route::post('/kontrakan/{id}', [ReviewController::class, 'storeKontrakan']);
|
|
Route::post('/laundry/{id}', [ReviewController::class, 'storeLaundry']);
|
|
Route::put('/{id}', [ReviewController::class, 'update']);
|
|
Route::delete('/{id}', [ReviewController::class, 'destroy']);
|
|
});
|
|
|
|
// Device Token Routes (Admin push notifications)
|
|
Route::post('/device-tokens', [DeviceTokenController::class, 'store']);
|
|
Route::delete('/device-tokens', [DeviceTokenController::class, 'destroy']);
|
|
|
|
// Favorite Routes
|
|
Route::prefix('favorites')->group(function () {
|
|
Route::get('/', [FavoriteController::class, 'index']);
|
|
Route::post('/kontrakan/{id}', [FavoriteController::class, 'toggleKontrakan']);
|
|
Route::post('/laundry/{id}', [FavoriteController::class, 'toggleLaundry']);
|
|
Route::delete('/{id}', [FavoriteController::class, 'destroy']);
|
|
});
|
|
});
|