Validate routes, pretty json, meteo keys

This commit is contained in:
Guntis Smaukstelis
2023-04-25 20:11:49 +03:00
parent 04872bbc4d
commit 3cd223dddd
2 changed files with 83 additions and 82 deletions
+40 -82
View File
@@ -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)
}
}
}