Fix booking profile request parsing without JsonNode
All checks were successful
build-and-deploy / build-deploy (push) Successful in 54s

This commit is contained in:
androidlover5842
2026-02-07 22:30:54 +05:30
parent e15c72a159
commit cac3f272a2

View File

@@ -53,7 +53,6 @@ import com.android.trisolarisserver.repo.room.RoomRepo
import com.android.trisolarisserver.repo.room.RoomStayAuditLogRepo import com.android.trisolarisserver.repo.room.RoomStayAuditLogRepo
import com.android.trisolarisserver.repo.room.RoomStayRepo import com.android.trisolarisserver.repo.room.RoomStayRepo
import com.android.trisolarisserver.security.MyPrincipal import com.android.trisolarisserver.security.MyPrincipal
import com.fasterxml.jackson.databind.JsonNode
import jakarta.servlet.http.HttpServletResponse import jakarta.servlet.http.HttpServletResponse
import org.springframework.http.HttpStatus import org.springframework.http.HttpStatus
import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.security.core.annotation.AuthenticationPrincipal
@@ -643,12 +642,9 @@ class BookingFlow(
@PathVariable propertyId: UUID, @PathVariable propertyId: UUID,
@PathVariable bookingId: UUID, @PathVariable bookingId: UUID,
@AuthenticationPrincipal principal: MyPrincipal?, @AuthenticationPrincipal principal: MyPrincipal?,
@RequestBody request: JsonNode @RequestBody request: Map<String, Any?>
) { ) {
requireRole(propertyAccess, propertyId, principal, Role.ADMIN, Role.MANAGER) requireRole(propertyAccess, propertyId, principal, Role.ADMIN, Role.MANAGER)
if (!request.isObject) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid request body")
}
val allowedFields = setOf( val allowedFields = setOf(
"transportMode", "transportMode",
@@ -659,7 +655,7 @@ class BookingFlow(
"toCity", "toCity",
"memberRelation" "memberRelation"
) )
val fieldNames = request.fieldNames().asSequence().toList() val fieldNames = request.keys.toList()
val unknownFields = fieldNames.filter { it !in allowedFields } val unknownFields = fieldNames.filter { it !in allowedFields }
if (unknownFields.isNotEmpty()) { if (unknownFields.isNotEmpty()) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Unknown fields: ${unknownFields.joinToString(",")}") throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Unknown fields: ${unknownFields.joinToString(",")}")
@@ -670,34 +666,34 @@ class BookingFlow(
val booking = requireBooking(propertyId, bookingId) val booking = requireBooking(propertyId, bookingId)
if (request.has("transportMode")) { if (request.containsKey("transportMode")) {
val transportNode = request.get("transportMode") booking.transportMode = parseNullableTransportMode(booking, request["transportMode"])
booking.transportMode = parseNullableTransportMode(booking, transportNode)
} }
if (request.has("fromCity")) { if (request.containsKey("fromCity")) {
booking.fromCity = parseNullableText(request.get("fromCity"), "fromCity") booking.fromCity = parseNullableText(request["fromCity"], "fromCity")
} }
if (request.has("toCity")) { if (request.containsKey("toCity")) {
booking.toCity = parseNullableText(request.get("toCity"), "toCity") booking.toCity = parseNullableText(request["toCity"], "toCity")
} }
if (request.has("memberRelation")) { if (request.containsKey("memberRelation")) {
booking.memberRelation = parseNullableMemberRelation(request.get("memberRelation")) booking.memberRelation = parseNullableMemberRelation(request["memberRelation"])
} }
val hasGuestCountPatch = request.has("maleCount") || request.has("femaleCount") || request.has("childCount") val hasGuestCountPatch =
request.containsKey("maleCount") || request.containsKey("femaleCount") || request.containsKey("childCount")
if (hasGuestCountPatch) { if (hasGuestCountPatch) {
val maleCount = if (request.has("maleCount")) { val maleCount = if (request.containsKey("maleCount")) {
parseNullableNonNegativeInt(request.get("maleCount"), "maleCount") parseNullableNonNegativeInt(request["maleCount"], "maleCount")
} else { } else {
booking.maleCount booking.maleCount
} }
val femaleCount = if (request.has("femaleCount")) { val femaleCount = if (request.containsKey("femaleCount")) {
parseNullableNonNegativeInt(request.get("femaleCount"), "femaleCount") parseNullableNonNegativeInt(request["femaleCount"], "femaleCount")
} else { } else {
booking.femaleCount booking.femaleCount
} }
val childCount = if (request.has("childCount")) { val childCount = if (request.containsKey("childCount")) {
parseNullableNonNegativeInt(request.get("childCount"), "childCount") parseNullableNonNegativeInt(request["childCount"], "childCount")
} else { } else {
booking.childCount booking.childCount
} }
@@ -1036,13 +1032,13 @@ class BookingFlow(
private fun parseNullableTransportMode( private fun parseNullableTransportMode(
booking: com.android.trisolarisserver.models.booking.Booking, booking: com.android.trisolarisserver.models.booking.Booking,
node: JsonNode? value: Any?
): TransportMode? { ): TransportMode? {
if (node == null || node.isNull) return null if (value == null) return null
if (!node.isTextual) { if (value !is String) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "transportMode must be string or null") throw ResponseStatusException(HttpStatus.BAD_REQUEST, "transportMode must be string or null")
} }
val raw = node.asText().trim() val raw = value.trim()
if (raw.isBlank()) return null if (raw.isBlank()) return null
val mode = parseTransportMode(raw.uppercase()) val mode = parseTransportMode(raw.uppercase())
if (!isTransportModeAllowed(booking.property, mode)) { if (!isTransportModeAllowed(booking.property, mode)) {
@@ -1060,34 +1056,41 @@ class BookingFlow(
} }
} }
private fun parseNullableMemberRelation(node: JsonNode?): MemberRelation? { private fun parseNullableMemberRelation(value: Any?): MemberRelation? {
if (node == null || node.isNull) return null if (value == null) return null
if (!node.isTextual) { if (value !is String) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "memberRelation must be string or null") throw ResponseStatusException(HttpStatus.BAD_REQUEST, "memberRelation must be string or null")
} }
return parseMemberRelation(node.asText()) return parseMemberRelation(value)
} }
private fun parseNullableText(node: JsonNode?, fieldName: String): String? { private fun parseNullableText(value: Any?, fieldName: String): String? {
if (node == null || node.isNull) return null if (value == null) return null
if (!node.isTextual) { if (value !is String) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "$fieldName must be string or null") throw ResponseStatusException(HttpStatus.BAD_REQUEST, "$fieldName must be string or null")
} }
return node.asText().trim().ifBlank { null } return value.trim().ifBlank { null }
} }
private fun parseNullableNonNegativeInt(node: JsonNode?, fieldName: String): Int? { private fun parseNullableNonNegativeInt(value: Any?, fieldName: String): Int? {
if (node == null || node.isNull) return null if (value == null) return null
if (!node.isIntegralNumber) { val intValue = when (value) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "$fieldName must be integer or null") is Int -> value
is Long -> value.toInt()
is Number -> {
val asDouble = value.toDouble()
if (asDouble % 1.0 != 0.0) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "$fieldName must be integer or null")
}
asDouble.toInt()
}
else -> throw ResponseStatusException(HttpStatus.BAD_REQUEST, "$fieldName must be integer or null")
} }
val value = node.asInt() if (intValue < 0) {
if (value < 0) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "$fieldName must be >= 0") throw ResponseStatusException(HttpStatus.BAD_REQUEST, "$fieldName must be >= 0")
} }
return value return intValue
} }
private fun parseRateSource(value: String?): RateSource? { private fun parseRateSource(value: String?): RateSource? {
if (value.isNullOrBlank()) return null if (value.isNullOrBlank()) return null
return try { return try {