package com.estateunified.panel import com.google.gson.Gson import com.google.gson.reflect.TypeToken import java.nio.charset.StandardCharsets import java.nio.file.Files import java.nio.file.Path import java.nio.file.StandardCopyOption import java.util.UUID internal data class RentalReviewDto( val id: String = UUID.randomUUID().toString().take(12), val listingId: String, val authorUserId: String, val authorName: String, val rating: Int, val comment: String = "", val stayFromIso: String = "", val stayToIso: String = "", val createdAtIso: String = java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(java.time.LocalDateTime.now()), ) internal class RentalReviewsStore( private val gson: Gson, private val file: Path, ) { private val lock = Any() fun absolutePath(): String = file.toAbsolutePath().normalize().toString() fun forListing(listingId: String): List = synchronized(lock) { load().filter { it.listingId == listingId }.sortedByDescending { it.createdAtIso } } fun add(review: RentalReviewDto): RentalReviewDto { val clamped = review.copy(rating = review.rating.coerceIn(1, 5)) synchronized(lock) { val list = load().toMutableList() list.add(0, clamped) persist(list) } return clamped } fun delete(id: String, authorUserId: String): Boolean = synchronized(lock) { val list = load().toMutableList() val removed = list.removeIf { it.id == id && it.authorUserId == authorUserId } if (removed) persist(list) removed } fun avgRating(listingId: String): Double { val list = forListing(listingId) if (list.isEmpty()) return 0.0 return list.map { it.rating }.average() } private fun load(): MutableList { if (!Files.isRegularFile(file)) return mutableListOf() val text = Files.readString(file, StandardCharsets.UTF_8) if (text.isBlank()) return mutableListOf() return runCatching { gson.fromJson>(text, listType) ?: mutableListOf() }.getOrElse { mutableListOf() } } private fun persist(list: List) { Files.createDirectories(file.toAbsolutePath().parent) val tmp = file.resolveSibling(file.fileName.toString() + ".tmp") Files.writeString(tmp, gson.toJson(list), StandardCharsets.UTF_8) Files.move(tmp, file, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE) } private companion object { val listType = object : TypeToken>() {}.type } }