diff --git a/docs/API_REFERENCE.txt b/docs/API_REFERENCE.txt index 6099f2d..b819fda 100644 --- a/docs/API_REFERENCE.txt +++ b/docs/API_REFERENCE.txt @@ -121,6 +121,33 @@ AUTH + SYSTEM APIS - 401 Unauthorized (missing principal / user not found) + +- City search API is this one: + + GET /geo/cities/search + + What it does: + + - Searches city names by prefix from local pincode directory table. + - Returns unique city + state rows only. + - Uses local DB only (no external API call in this endpoint). + + Request body: + + - None. + + Query params: + + - q (required, minimum 2 characters) + - limit (optional, default 20, min 1, max 100) + + - Allowed roles: authenticated Firebase user. + + Error Codes + + - 400 Bad Request (q too short) + - 401 Unauthorized + ================================================================================ PROPERTY + USER APIS ================================================================================ diff --git a/src/main/kotlin/com/android/trisolarisserver/controller/system/GeoSearch.kt b/src/main/kotlin/com/android/trisolarisserver/controller/system/GeoSearch.kt new file mode 100644 index 0000000..27aa333 --- /dev/null +++ b/src/main/kotlin/com/android/trisolarisserver/controller/system/GeoSearch.kt @@ -0,0 +1,39 @@ +package com.android.trisolarisserver.controller.system + +import com.android.trisolarisserver.controller.common.requirePrincipal +import com.android.trisolarisserver.repo.property.IndiaPincodeCityStateRepo +import com.android.trisolarisserver.security.MyPrincipal +import org.springframework.data.domain.PageRequest +import org.springframework.http.HttpStatus +import org.springframework.security.core.annotation.AuthenticationPrincipal +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 GeoSearch( + private val indiaPincodeCityStateRepo: IndiaPincodeCityStateRepo +) { + @GetMapping("/geo/cities/search") + fun searchCities( + @AuthenticationPrincipal principal: MyPrincipal?, + @RequestParam("q") q: String, + @RequestParam("limit", required = false, defaultValue = "20") limit: Int + ): List { + requirePrincipal(principal) + val query = q.trim() + if (query.length < 2) { + throw ResponseStatusException(HttpStatus.BAD_REQUEST, "q must be at least 2 characters") + } + val boundedLimit = limit.coerceIn(1, 100) + return indiaPincodeCityStateRepo + .searchCityStateByPrefix(query, PageRequest.of(0, boundedLimit)) + .map { CityStateResponse(city = it.city, state = it.state) } + } +} + +data class CityStateResponse( + val city: String, + val state: String +) diff --git a/src/main/kotlin/com/android/trisolarisserver/repo/property/IndiaPincodeCityStateRepo.kt b/src/main/kotlin/com/android/trisolarisserver/repo/property/IndiaPincodeCityStateRepo.kt index 6660f2c..81d09e5 100644 --- a/src/main/kotlin/com/android/trisolarisserver/repo/property/IndiaPincodeCityStateRepo.kt +++ b/src/main/kotlin/com/android/trisolarisserver/repo/property/IndiaPincodeCityStateRepo.kt @@ -25,6 +25,23 @@ interface IndiaPincodeCityStateRepo : JpaRepository @Param("pincode") pincode: Int, pageable: Pageable ): List + + @Query( + """ + select p.city as city, + p.state as state + from IndiaPincodeCityState p + where trim(p.city) <> '' + and trim(p.state) <> '' + and lower(p.city) like lower(concat(:prefix, '%')) + group by p.city, p.state + order by p.city asc, p.state asc + """ + ) + fun searchCityStateByPrefix( + @Param("prefix") prefix: String, + pageable: Pageable + ): List } interface PincodeCityStateCandidate { @@ -32,3 +49,8 @@ interface PincodeCityStateCandidate { val state: String val hitCount: Long } + +interface CityStateRow { + val city: String + val state: String +}