Validate routes, pretty json, meteo keys
This commit is contained in:
@@ -4,109 +4,67 @@ import cats.effect._
|
|||||||
import cats.implicits.toTraverseOps
|
import cats.implicits.toTraverseOps
|
||||||
import db.DBService
|
import db.DBService
|
||||||
import fetch.FetchService
|
import fetch.FetchService
|
||||||
|
import parse.{MeteoData, Parser}
|
||||||
|
import server.ValidateRoutes.{Aggregate, CityList, DateTimeRange, ValidDate}
|
||||||
import io.circe.{Json, Printer}
|
import io.circe.{Json, Printer}
|
||||||
import org.http4s._
|
import org.http4s._
|
||||||
import org.http4s.dsl.io._
|
import org.http4s.dsl.io._
|
||||||
import org.http4s.implicits._
|
|
||||||
import org.http4s.server.Router
|
import org.http4s.server.Router
|
||||||
import org.http4s.server.blaze.BlazeServerBuilder
|
import org.http4s.server.blaze.BlazeServerBuilder
|
||||||
import io.circe.syntax._
|
import io.circe.syntax._
|
||||||
import io.circe.generic.auto._
|
|
||||||
import org.http4s.circe.jsonEncoder
|
|
||||||
import parse.{Meteo, Parser}
|
|
||||||
|
|
||||||
import java.time.{LocalDate, LocalDateTime}
|
|
||||||
import java.time.format.DateTimeFormatter
|
|
||||||
import scala.util.Try
|
|
||||||
|
|
||||||
object Server extends IOApp {
|
object Server extends IOApp {
|
||||||
private val formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmm")
|
|
||||||
|
// Define the extension method `pretty` for Json
|
||||||
|
implicit class JsonPrettyPrinter(json: Json) {
|
||||||
|
def pretty: String = {
|
||||||
|
val printer = Printer.spaces2.copy(dropNullValues = true)
|
||||||
|
printer.print(json)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val appRoutes = HttpRoutes.of[IO] {
|
private val appRoutes = HttpRoutes.of[IO] {
|
||||||
|
|
||||||
// http://localhost:3000/query/20230414_2200-20230501_1230/Liepāja,Rēzekne/tempAvg
|
// http://localhost:3000/query/20230414_2200-20230501_1230/Liepāja,Rēzekne/tempAvg
|
||||||
case GET -> Root / "query" / timestampRange / cities / aggregate =>
|
case GET -> Root / "query" / DateTimeRange(from, to) / CityList(cities) / Aggregate(aggregate) =>
|
||||||
// TODO proly better to Validated with chained errors
|
DBService.getInRange(from, to)
|
||||||
val parsedArguments = for {
|
.map(Parser.queryData(_, cities, aggregate))
|
||||||
(from, to) <- timestampRange.split("-").toList
|
.flatMap(result => Ok(result.asJson.pretty))
|
||||||
.map(str => Try(LocalDateTime.parse(str, formatter)).toOption) match {
|
|
||||||
case List(Some(from), Some(to)) => Some(from, to)
|
|
||||||
case _ => None
|
|
||||||
}
|
|
||||||
cityList <- cities.split(",").toList match {
|
|
||||||
case list => Some(list)
|
|
||||||
case Nil => None
|
|
||||||
}
|
|
||||||
aggregate <- Meteo.stringToAggregateParam(aggregate)
|
|
||||||
|
|
||||||
}
|
|
||||||
yield (from, to, cityList, aggregate)
|
|
||||||
|
|
||||||
parsedArguments match {
|
|
||||||
case Some((from, to, cityList, aggregate)) => {
|
|
||||||
val res = for {
|
|
||||||
lines <- db.DBService.getInRange(from, to)
|
|
||||||
parsedData <- IO.pure(parse.Parser.queryData(lines, cityList, aggregate))
|
|
||||||
} yield parsedData
|
|
||||||
|
|
||||||
Ok(res.map(_.toString()))
|
|
||||||
}
|
|
||||||
case _ => BadRequest(s"Invalid request format")
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://localhost:3000/fetch/date/20230423
|
// http://localhost:3000/fetch/date/20230423
|
||||||
case GET -> Root / "fetch" / "date" / dateStr => {
|
case GET -> Root / "fetch" / "date" / ValidDate(date) =>
|
||||||
val dateFormatter = DateTimeFormatter.ofPattern("yyyyMMdd")
|
val result = for {
|
||||||
val maybeDate = Try(LocalDate.parse(dateStr, dateFormatter)).toEither
|
fetched <- FetchService.fetchFromDate(date)
|
||||||
maybeDate match {
|
(fetchErrors, successDownloads) = fetched.partitionMap(identity)
|
||||||
case Right(date) => {
|
saved <- successDownloads.traverse { case (name, content) => DBService.save(name, content) }
|
||||||
val result = for {
|
(saveErrors, successSaves) = saved.partitionMap(identity)
|
||||||
fetched <- FetchService.fetchFromDate(date)
|
successes = successDownloads.map(s => s"fetched: ${s._1}") ++ successSaves.map(s => s"saved: $s")
|
||||||
(fetchErrors, successDownloads) = fetched.partitionMap(identity)
|
errors = fetchErrors.map(e => s"FetchError: ${e.getMessage}") ++ saveErrors.map(e => s"SaveError: ${e.getMessage}")
|
||||||
saved <- successDownloads.traverse { case (name, content) => DBService.save(name, content) }
|
} yield (successes, errors)
|
||||||
(saveErrors, successSaves) = saved.partitionMap(identity)
|
|
||||||
successes = successDownloads.map(s => s"fetched: ${s._1}") ++ successSaves.map(s => s"saved: $s")
|
|
||||||
errors = fetchErrors.map(e => s"FetchError: ${e.getMessage}") ++ saveErrors.map(e => s"SaveError: ${e.getMessage}")
|
|
||||||
} yield (successes, errors)
|
|
||||||
|
|
||||||
result.flatMap { case (successes, errors) =>
|
result.flatMap { case (successes, errors) =>
|
||||||
val responseBody = Json.obj(
|
Ok(Json.obj(
|
||||||
"errors" -> errors.asJson,
|
"errors" -> errors.asJson,
|
||||||
"successes" -> successes.asJson
|
"successes" -> successes.asJson
|
||||||
)
|
).pretty)
|
||||||
val printer = Printer.spaces2.copy(dropNullValues = true)
|
|
||||||
val prettyJson = printer.print(responseBody)
|
|
||||||
Ok(prettyJson)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case Left(_) => BadRequest("Invalid request format")
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// http://localhost:3000/show/all_dates
|
// http://localhost:3000/show/all_dates
|
||||||
case GET -> Root / "show" / "all_dates" =>
|
case GET -> Root / "show" / "all_dates" =>
|
||||||
DBService.getDates().flatMap(dates => {
|
DBService.getDates().flatMap(dates =>
|
||||||
val printer = Printer.spaces2.copy(dropNullValues = true)
|
Ok(dates.asJson.pretty)
|
||||||
val prettyJson = printer.print(dates.asJson)
|
)
|
||||||
Ok(prettyJson)
|
|
||||||
})
|
|
||||||
|
|
||||||
// http://localhost:3000/show/date/20230423
|
// http://localhost:3000/show/date/20230423
|
||||||
case GET -> Root / "show" / "date" / dateStr =>
|
case GET -> Root / "show" / "date" / ValidDate(date) =>
|
||||||
val dateFormatter = DateTimeFormatter.ofPattern("yyyyMMdd")
|
DBService.getDateFileNames(date).flatMap(fileNames =>
|
||||||
val maybeDate = Try(LocalDate.parse(dateStr, dateFormatter)).toEither
|
Ok(fileNames.asJson.pretty)
|
||||||
maybeDate match {
|
)
|
||||||
case Right(date) => {
|
|
||||||
DBService.getDateFileNames(date).flatMap(fileNames => {
|
|
||||||
val printer = Printer.spaces2.copy(dropNullValues = true)
|
|
||||||
val prettyJson = printer.print(fileNames.asJson)
|
|
||||||
Ok(prettyJson)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
case Left(_) => BadRequest("Invalid request format")
|
|
||||||
}
|
|
||||||
|
|
||||||
case GET -> Root / "show" / dateRange =>
|
|
||||||
???
|
|
||||||
|
|
||||||
|
// http://localhost:3000/help
|
||||||
|
case GET -> Root / "help" =>
|
||||||
|
Ok(MeteoData.getKeys.asJson.pretty)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val httpApp = Router("/" -> appRoutes).orNotFound
|
private val httpApp = Router("/" -> appRoutes).orNotFound
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import parse.{AggregateMeteo, Meteo}
|
||||||
|
|
||||||
|
import java.time.{LocalDate, LocalDateTime}
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
import scala.util.Try
|
||||||
|
|
||||||
|
object ValidateRoutes {
|
||||||
|
object DateTimeRange {
|
||||||
|
def unapply(str: String): Option[(LocalDateTime, LocalDateTime)] = {
|
||||||
|
val formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmm")
|
||||||
|
str.split("-")
|
||||||
|
.toList
|
||||||
|
.map(str => Try(LocalDateTime.parse(str, formatter)).toOption) match {
|
||||||
|
case List(Some(from), Some(to)) => Some(from, to)
|
||||||
|
case _ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object ValidDate {
|
||||||
|
def unapply(str: String): Option[LocalDate] = {
|
||||||
|
val formatter = DateTimeFormatter.ofPattern("yyyyMMdd")
|
||||||
|
Try(LocalDate.parse(str, formatter)).toOption
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object CityList {
|
||||||
|
def unapply(str: String): Option[List[String]] = {
|
||||||
|
str.split(",").toList match {
|
||||||
|
case list => Some(list)
|
||||||
|
case Nil => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object Aggregate {
|
||||||
|
def unapply(str: String): Option[AggregateMeteo] = {
|
||||||
|
Meteo.stringToAggregateParam(str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user