import { tripRepo } from "@/server/repositories/trip.repo"; import { participantRepo } from "@/server/repositories/participant.repo"; import { LIMITS } from "@/lib/limits"; import { utcStartOfDay, isTripDepartureDayPast } from "@/lib/trip-dates"; interface CreateTripInput { title: string; description?: string; mountain: string; location: string; date: Date; endDate?: Date; maxParticipants: number; price: number; organizerId: string; imageUrls?: string[]; } export const tripService = { async getOpenTrips(filters?: { q?: string; from?: string; to?: string }) { return tripRepo.findOpen(filters); }, async getAllTrips() { return tripRepo.findAll(); }, async getTripById(id: string) { const trip = await tripRepo.findById(id); if (!trip) { throw new Error("Trip tidak ditemukan"); } return trip; }, async createTrip(input: CreateTripInput) { const since = utcStartOfDay(new Date()); const todayCount = await tripRepo.countByOrganizerSince( input.organizerId, since ); if (todayCount >= LIMITS.MAX_TRIPS_PER_ORGANIZER_PER_DAY) { throw new Error( `Batas harian: maksimal ${LIMITS.MAX_TRIPS_PER_ORGANIZER_PER_DAY} trip per hari (UTC). Coba lagi besok.` ); } if (isTripDepartureDayPast(input.date)) { throw new Error("Tanggal berangkat tidak boleh di masa lalu"); } if (input.endDate && input.endDate.getTime() < input.date.getTime()) { throw new Error("Tanggal pulang tidak boleh sebelum tanggal berangkat"); } const images = input.imageUrls?.length ? { create: input.imageUrls.map((url, i) => ({ url, order: i })), } : undefined; return tripRepo.create({ title: input.title, description: input.description, mountain: input.mountain, location: input.location, date: input.date, endDate: input.endDate, maxParticipants: input.maxParticipants, price: input.price, organizer: { connect: { id: input.organizerId } }, images, }); }, async joinTrip(tripId: string, userId: string) { const trip = await tripRepo.findById(tripId); if (!trip) { throw new Error("Trip tidak ditemukan"); } if (trip.status !== "OPEN") { throw new Error("Trip tidak tersedia untuk pendaftaran"); } if (isTripDepartureDayPast(trip.date)) { throw new Error( "Trip sudah melewati tanggal berangkat, tidak bisa mendaftar" ); } if (trip.organizerId === userId) { throw new Error("Organizer tidak bisa join trip sendiri"); } const existing = await participantRepo.findByTripAndUser(tripId, userId); if (existing && existing.status !== "CANCELLED") { throw new Error("Kamu sudah terdaftar di trip ini"); } const participantCount = await participantRepo.countByTrip(tripId); if (participantCount >= trip.maxParticipants) { await tripRepo.updateStatus(tripId, "FULL"); throw new Error("Trip sudah penuh"); } const participant = existing?.status === "CANCELLED" ? await participantRepo.reactivate(tripId, userId) : await participantRepo.create(tripId, userId); const newCount = await participantRepo.countByTrip(tripId); if (newCount >= trip.maxParticipants) { await tripRepo.updateStatus(tripId, "FULL"); } return participant; }, async cancelJoin(tripId: string, userId: string) { const trip = await tripRepo.findById(tripId); if (!trip) { throw new Error("Trip tidak ditemukan"); } if (isTripDepartureDayPast(trip.date)) { throw new Error( "Tanggal berangkat trip sudah lewat — pendaftaran tidak bisa dibatalkan. Jika trip sudah selesai, gunakan bagian ulasan." ); } const existing = await participantRepo.findByTripAndUser(tripId, userId); if (!existing || existing.status === "CANCELLED") { throw new Error("Kamu tidak terdaftar di trip ini"); } const result = await participantRepo.cancel(tripId, userId); if (trip.status === "FULL") { const count = await participantRepo.countByTrip(tripId); if (count < trip.maxParticipants) { await tripRepo.updateStatus(tripId, "OPEN"); } } return result; }, };