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 db.DBService
import fetch.FetchService
import parse.{MeteoData, Parser}
import server.ValidateRoutes.{Aggregate, CityList, DateTimeRange, ValidDate}
import io.circe.{Json, Printer}
import org.http4s._
import org.http4s.dsl.io._
import org.http4s.implicits._
import org.http4s.server.Router
import org.http4s.server.blaze.BlazeServerBuilder
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 {
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] {
// http://localhost:3000/query/20230414_2200-20230501_1230/Liepāja,Rēzekne/tempAvg
case GET -> Root / "query" / timestampRange / cities / aggregate =>
// TODO proly better to Validated with chained errors
val parsedArguments = for {
(from, to) <- timestampRange.split("-").toList
.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")
}
case GET -> Root / "query" / DateTimeRange(from, to) / CityList(cities) / Aggregate(aggregate) =>
DBService.getInRange(from, to)
.map(Parser.queryData(_, cities, aggregate))
.flatMap(result => Ok(result.asJson.pretty))
// http://localhost:3000/fetch/date/20230423
case GET -> Root / "fetch" / "date" / dateStr => {
val dateFormatter = DateTimeFormatter.ofPattern("yyyyMMdd")
val maybeDate = Try(LocalDate.parse(dateStr, dateFormatter)).toEither
maybeDate match {
case Right(date) => {
val result = for {
fetched <- FetchService.fetchFromDate(date)
(fetchErrors, successDownloads) = fetched.partitionMap(identity)
saved <- successDownloads.traverse { case (name, content) => DBService.save(name, content) }
(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)
case GET -> Root / "fetch" / "date" / ValidDate(date) =>
val result = for {
fetched <- FetchService.fetchFromDate(date)
(fetchErrors, successDownloads) = fetched.partitionMap(identity)
saved <- successDownloads.traverse { case (name, content) => DBService.save(name, content) }
(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) =>
val responseBody = Json.obj(
"errors" -> errors.asJson,
"successes" -> successes.asJson
)
val printer = Printer.spaces2.copy(dropNullValues = true)
val prettyJson = printer.print(responseBody)
Ok(prettyJson)
}
}
case Left(_) => BadRequest("Invalid request format")
result.flatMap { case (successes, errors) =>
Ok(Json.obj(
"errors" -> errors.asJson,
"successes" -> successes.asJson
).pretty)
}
}
// http://localhost:3000/show/all_dates
case GET -> Root / "show" / "all_dates" =>
DBService.getDates().flatMap(dates => {
val printer = Printer.spaces2.copy(dropNullValues = true)
val prettyJson = printer.print(dates.asJson)
Ok(prettyJson)
})
DBService.getDates().flatMap(dates =>
Ok(dates.asJson.pretty)
)
// http://localhost:3000/show/date/20230423
case GET -> Root / "show" / "date" / dateStr =>
val dateFormatter = DateTimeFormatter.ofPattern("yyyyMMdd")
val maybeDate = Try(LocalDate.parse(dateStr, dateFormatter)).toEither
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 =>
???
case GET -> Root / "show" / "date" / ValidDate(date) =>
DBService.getDateFileNames(date).flatMap(fileNames =>
Ok(fileNames.asJson.pretty)
)
// http://localhost:3000/help
case GET -> Root / "help" =>
Ok(MeteoData.getKeys.asJson.pretty)
}
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)
}
}
}