Add public country search API backed by country_reference
All checks were successful
build-and-deploy / build-deploy (push) Successful in 36s
All checks were successful
build-and-deploy / build-deploy (push) Successful in 36s
This commit is contained in:
@@ -148,6 +148,32 @@ AUTH + SYSTEM APIS
|
||||
- 400 Bad Request (q too short)
|
||||
- 401 Unauthorized
|
||||
|
||||
|
||||
- Country search API is this one:
|
||||
|
||||
GET /geo/countries/search
|
||||
|
||||
What it does:
|
||||
|
||||
- Searches countries from local country_reference table.
|
||||
- Case-insensitive match on country name, official name, ISO alpha-2, and ISO alpha-3.
|
||||
- Example: q=IND returns matches like India and related country names.
|
||||
|
||||
Request body:
|
||||
|
||||
- None.
|
||||
|
||||
Query params:
|
||||
|
||||
- q (required, minimum 3 characters)
|
||||
- limit (optional, default 20, min 1, max 100)
|
||||
|
||||
- Allowed roles: Public endpoint.
|
||||
|
||||
Error Codes
|
||||
|
||||
- 400 Bad Request (q too short)
|
||||
|
||||
================================================================================
|
||||
PROPERTY + USER APIS
|
||||
================================================================================
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.android.trisolarisserver.controller.system
|
||||
|
||||
import com.android.trisolarisserver.repo.property.CountryReferenceRepo
|
||||
import org.springframework.data.domain.PageRequest
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.server.ResponseStatusException
|
||||
|
||||
@RestController
|
||||
class CountrySearch(
|
||||
private val countryReferenceRepo: CountryReferenceRepo
|
||||
) {
|
||||
@GetMapping("/geo/countries/search")
|
||||
fun searchCountries(
|
||||
@RequestParam("q") q: String,
|
||||
@RequestParam("limit", required = false, defaultValue = "20") limit: Int
|
||||
): List<CountrySearchResponse> {
|
||||
val query = q.trim()
|
||||
if (query.length < 3) {
|
||||
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "q must be at least 3 characters")
|
||||
}
|
||||
val boundedLimit = limit.coerceIn(1, 100)
|
||||
return countryReferenceRepo
|
||||
.searchCountries(query, PageRequest.of(0, boundedLimit))
|
||||
.map {
|
||||
CountrySearchResponse(
|
||||
name = it.name,
|
||||
officialName = it.officialName,
|
||||
isoAlpha2 = it.isoAlpha2,
|
||||
isoAlpha3 = it.isoAlpha3
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class CountrySearchResponse(
|
||||
val name: String,
|
||||
val officialName: String?,
|
||||
val isoAlpha2: String?,
|
||||
val isoAlpha3: String?
|
||||
)
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.android.trisolarisserver.models.property
|
||||
|
||||
import jakarta.persistence.Column
|
||||
import jakarta.persistence.Entity
|
||||
import jakarta.persistence.GeneratedValue
|
||||
import jakarta.persistence.GenerationType
|
||||
import jakarta.persistence.Id
|
||||
import jakarta.persistence.Table
|
||||
import java.math.BigDecimal
|
||||
import java.time.OffsetDateTime
|
||||
|
||||
@Entity
|
||||
@Table(name = "country_reference")
|
||||
class CountryReference(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id")
|
||||
val id: Long? = null,
|
||||
|
||||
@Column(name = "name", nullable = false)
|
||||
val name: String,
|
||||
|
||||
@Column(name = "official_name")
|
||||
val officialName: String? = null,
|
||||
|
||||
@Column(name = "iso_alpha2")
|
||||
val isoAlpha2: String? = null,
|
||||
|
||||
@Column(name = "iso_alpha3")
|
||||
val isoAlpha3: String? = null,
|
||||
|
||||
@Column(name = "latitude", precision = 11, scale = 8)
|
||||
val latitude: BigDecimal? = null,
|
||||
|
||||
@Column(name = "longitude", precision = 11, scale = 8)
|
||||
val longitude: BigDecimal? = null,
|
||||
|
||||
@Column(name = "source_row_code")
|
||||
val sourceRowCode: Int? = null,
|
||||
|
||||
@Column(name = "created_at", nullable = false, columnDefinition = "timestamptz")
|
||||
val createdAt: OffsetDateTime = OffsetDateTime.now()
|
||||
)
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.android.trisolarisserver.repo.property
|
||||
|
||||
import com.android.trisolarisserver.models.property.CountryReference
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.data.jpa.repository.Query
|
||||
import org.springframework.data.repository.query.Param
|
||||
|
||||
interface CountryReferenceRepo : JpaRepository<CountryReference, Long> {
|
||||
@Query(
|
||||
"""
|
||||
select c.name as name,
|
||||
c.officialName as officialName,
|
||||
c.isoAlpha2 as isoAlpha2,
|
||||
c.isoAlpha3 as isoAlpha3
|
||||
from CountryReference c
|
||||
where trim(c.name) <> ''
|
||||
and (
|
||||
lower(c.name) like lower(concat('%', :query, '%'))
|
||||
or lower(coalesce(c.officialName, '')) like lower(concat('%', :query, '%'))
|
||||
or lower(coalesce(c.isoAlpha2, '')) like lower(concat('%', :query, '%'))
|
||||
or lower(coalesce(c.isoAlpha3, '')) like lower(concat('%', :query, '%'))
|
||||
)
|
||||
order by
|
||||
case
|
||||
when lower(c.name) = lower(:query) then 0
|
||||
when lower(coalesce(c.isoAlpha2, '')) = lower(:query) then 1
|
||||
when lower(coalesce(c.isoAlpha3, '')) = lower(:query) then 2
|
||||
when lower(c.name) like lower(concat(:query, '%')) then 3
|
||||
else 4
|
||||
end,
|
||||
c.name asc
|
||||
"""
|
||||
)
|
||||
fun searchCountries(
|
||||
@Param("query") query: String,
|
||||
pageable: Pageable
|
||||
): List<CountrySearchRow>
|
||||
}
|
||||
|
||||
interface CountrySearchRow {
|
||||
val name: String
|
||||
val officialName: String?
|
||||
val isoAlpha2: String?
|
||||
val isoAlpha3: String?
|
||||
}
|
||||
@@ -14,6 +14,7 @@ internal object PublicEndpoints {
|
||||
private val razorpayReturn = Regex("^/properties/[^/]+/razorpay/return/(success|failure)$")
|
||||
private val guestDocumentFile = Regex("^/properties/[^/]+/guests/[^/]+/documents/[^/]+/file$")
|
||||
private val cancellationPolicy = Regex("^/properties/[^/]+/cancellation-policy$")
|
||||
private val countrySearch = Regex("^/geo/countries/search$")
|
||||
|
||||
fun isPublic(request: HttpServletRequest): Boolean {
|
||||
val path = request.requestURI
|
||||
@@ -34,5 +35,6 @@ internal object PublicEndpoints {
|
||||
|| iconPngFile.matches(path)
|
||||
|| guestDocumentFile.matches(path)
|
||||
|| (cancellationPolicy.matches(path) && method == "GET")
|
||||
|| (countrySearch.matches(path) && method == "GET")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user