{"openapi":"3.0.0","info":{"title":"SportBook REST API","version":"1.0.0","description":"Dokumentasi resmi RESTful API untuk aplikasi pemesanan lapangan olahraga SportBook. Dilengkapi proteksi autentikasi JWT sesi, validasi Zod terpusat, dan jaminan pencegahan double-booking di level database (HTTP 409 Conflict).","contact":{"name":"SportBook Technical Team","email":"support@sportbook.id"}},"servers":[{"url":"http://localhost:3000","description":"Development Server"}],"components":{"securitySchemes":{"cookieAuth":{"type":"apiKey","in":"cookie","name":"sportbook_session","description":"JWT session token disimpan di HTTP-only cookie."},"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Opsional: token JWT via Authorization header."}},"schemas":{"RegisterRequest":{"type":"object","required":["name","email","password"],"properties":{"name":{"type":"string","example":"Dimas Pratama"},"email":{"type":"string","format":"email","example":"dimas@sportbook.id"},"password":{"type":"string","minLength":6,"example":"password123"},"phone":{"type":"string","example":"081234567890"}}},"LoginRequest":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","format":"email","example":"dimas@sportbook.id"},"password":{"type":"string","example":"password123"}}},"Court":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","example":"Smash Arena Senayan — Hall A"},"slug":{"type":"string","example":"smash-arena-senayan"},"sportType":{"type":"string","enum":["BADMINTON","FUTSAL","BASKETBALL","TENNIS","PADEL"]},"location":{"type":"string","example":"Senayan, Jakarta Pusat"},"city":{"type":"string","example":"Jakarta Pusat"},"address":{"type":"string"},"description":{"type":"string"},"pricePerHour":{"type":"integer","example":120000},"rating":{"type":"number","example":4.9},"reviewCount":{"type":"integer","example":248},"surfaceType":{"type":"string","example":"Karpet Vinyl Li-Ning 4.5mm"},"facilities":{"type":"array","items":{"type":"string"}},"images":{"type":"array","items":{"type":"string"}},"isActive":{"type":"boolean","example":true}}},"CreateBookingRequest":{"type":"object","required":["courtId","bookingDate","startTime"],"properties":{"courtId":{"type":"string","description":"ID atau Slug lapangan"},"bookingDate":{"type":"string","format":"date","example":"2026-09-20"},"startTime":{"type":"string","example":"14:00"},"endTime":{"type":"string","example":"15:00"},"customerName":{"type":"string","example":"Dimas Pratama"},"customerEmail":{"type":"string","format":"email","example":"dimas@sportbook.id"},"customerPhone":{"type":"string","example":"081234567890"},"notes":{"type":"string","example":"Bawa rompi sparring"}}},"Booking":{"type":"object","properties":{"id":{"type":"string"},"bookingCode":{"type":"string","example":"SBK-123456"},"userId":{"type":"string"},"courtId":{"type":"string"},"bookingDate":{"type":"string","format":"date-time"},"dateString":{"type":"string","example":"2026-09-20"},"startTime":{"type":"string","example":"14:00"},"endTime":{"type":"string","example":"15:00"},"durationHours":{"type":"integer","example":1},"pricePerHour":{"type":"integer","example":120000},"totalPrice":{"type":"integer","example":120000},"status":{"type":"string","enum":["CONFIRMED","COMPLETED","CANCELLED"]},"customerName":{"type":"string"},"customerEmail":{"type":"string"}}},"StandardResponse":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"data":{"type":"object"}}},"ConflictResponse":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string","example":"Jadwal sudah dipesan oleh pengguna lain. Silakan pilih slot jam yang lain."}}}}},"paths":{"/api/auth/register":{"post":{"tags":["Authentication"],"summary":"Mendaftarkan akun pengguna baru","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterRequest"}}}},"responses":{"201":{"description":"Registrasi berhasil dan cookie sesi disetel.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse"}}}},"400":{"description":"Validasi data gagal"},"409":{"description":"Email sudah terdaftar"}}}},"/api/auth/login":{"post":{"tags":["Authentication"],"summary":"Masuk ke akun (Login)","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}}},"responses":{"200":{"description":"Login berhasil dan cookie sesi disetel.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse"}}}},"401":{"description":"Email atau password tidak sesuai"}}}},"/api/auth/logout":{"post":{"tags":["Authentication"],"summary":"Keluar dari sesi (Logout)","responses":{"200":{"description":"Logout berhasil dan cookie sesi dihapus."}}}},"/api/courts":{"get":{"tags":["Courts"],"summary":"Mengambil daftar lapangan olahraga dengan pagination dan filter","parameters":[{"name":"search","in":"query","schema":{"type":"string"},"description":"Pencarian nama atau lokasi"},{"name":"sportType","in":"query","schema":{"type":"string","enum":["BADMINTON","FUTSAL","BASKETBALL","TENNIS","PADEL"]}},{"name":"city","in":"query","schema":{"type":"string"},"description":"Filter kota (contoh: Jakarta Selatan)"},{"name":"sortBy","in":"query","schema":{"type":"string","enum":["rating","price_asc","price_desc","newest"]}},{"name":"page","in":"query","schema":{"type":"integer","default":1}},{"name":"limit","in":"query","schema":{"type":"integer","default":10}}],"responses":{"200":{"description":"Daftar lapangan ditemukan","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/Court"}},"pagination":{"type":"object","properties":{"total":{"type":"integer"},"page":{"type":"integer"},"limit":{"type":"integer"},"totalPages":{"type":"integer"}}}}}}}}}}},"/api/courts/{id}":{"get":{"tags":["Courts"],"summary":"Mengambil detail satu lapangan berdasarkan ID atau Slug","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"ID atau slug lapangan"}],"responses":{"200":{"description":"Detail lapangan ditemukan","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Court"}}}}}},"404":{"description":"Lapangan tidak ditemukan"}}}},"/api/courts/{id}/availability":{"get":{"tags":["Courts"],"summary":"Pemeriksaan ketersediaan slot jam bermain pada tanggal tertentu","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"ID atau slug lapangan"},{"name":"date","in":"query","required":true,"schema":{"type":"string","format":"date"},"example":"2026-09-20"}],"responses":{"200":{"description":"Ketersediaan slot berhasil dihitung oleh server."},"404":{"description":"Lapangan tidak ditemukan"}}}},"/api/bookings":{"get":{"tags":["Bookings"],"summary":"Mengambil riwayat reservasi pengguna yang sedang login","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"Riwayat booking pengguna berhasil diambil."},"401":{"description":"Unauthorized: Belum login."}}},"post":{"tags":["Bookings"],"summary":"Membuat reservasi booking baru (Anti-Double Booking)","description":"Memproses pembuatan booking dalam transaksi serializable database. Jika slot sudah dipesan oleh pengguna lain di waktu bersamaan, server mengembalikan 409 Conflict.","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBookingRequest"}}}},"responses":{"201":{"description":"Booking berhasil dikonfirmasi.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandardResponse"}}}},"400":{"description":"Input data tidak valid atau lapangan maintenance"},"401":{"description":"Unauthorized: Silakan login terlebih dahulu"},"409":{"description":"Conflict: Jadwal sudah dipesan oleh pengguna lain.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConflictResponse"}}}}}}},"/api/bookings/{id}":{"get":{"tags":["Bookings"],"summary":"Mengambil detail satu reservasi booking","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Detail booking ditemukan."},"403":{"description":"Forbidden: Bukan pemilik booking."},"404":{"description":"Booking tidak ditemukan."}}}},"/api/bookings/{id}/cancel":{"patch":{"tags":["Bookings"],"summary":"Membatalkan reservasi jadwal dan melepas slot ke publik","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","example":"Ada urusan mendadak"}}}}}},"responses":{"200":{"description":"Reservasi berhasil dibatalkan."},"400":{"description":"Booking sudah dibatalkan atau selesai main."},"403":{"description":"Forbidden."},"404":{"description":"Booking tidak ditemukan."}}}},"/api/profile":{"get":{"tags":["Profile"],"summary":"Mengambil profil pengguna yang login","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"description":"Profil berhasil diambil."},"401":{"description":"Unauthorized."}}},"patch":{"tags":["Profile"],"summary":"Memperbarui nama atau nomor telepon pengguna","security":[{"cookieAuth":[]},{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"phone":{"type":"string"}}}}}},"responses":{"200":{"description":"Profil berhasil diperbarui."},"401":{"description":"Unauthorized."}}}}}}