From 841b4b6ec99aa6c3cb012f9312170ee25c482fde Mon Sep 17 00:00:00 2001 From: Guntis Smaukstelis Date: Mon, 10 Mar 2025 16:56:24 +0200 Subject: [PATCH] Getting lvgmc csv files in frontend --- src/main/scala/fetch/lvgmc/FetchService.scala | 8 +++--- .../scala/fetch/lvgmc/FetchServiceTest.scala | 2 +- src/main/scala/server/Server.scala | 10 +++++++ web/src/App.tsx | 4 ++- web/src/helpers/fetch.ts | 26 +++++++++++++++++++ web/src/lvgmc-forecast/LvgmcForecast.tsx | 18 +++++++++++++ 6 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 web/src/lvgmc-forecast/LvgmcForecast.tsx diff --git a/src/main/scala/fetch/lvgmc/FetchService.scala b/src/main/scala/fetch/lvgmc/FetchService.scala index 214ee33..a94ca3d 100644 --- a/src/main/scala/fetch/lvgmc/FetchService.scala +++ b/src/main/scala/fetch/lvgmc/FetchService.scala @@ -63,7 +63,7 @@ class FetchService(log: Logger[IO]) { } } - private def retrieveFileAsString(ftpClient: FTPClient, remotePath: String): IO[String] = IO.blocking { + private def retrieveFileAsBytes(ftpClient: FTPClient, remotePath: String): IO[Array[Byte]] = IO.blocking { val outputStream = new ByteArrayOutputStream() val success = ftpClient.retrieveFile(remotePath, outputStream) @@ -71,16 +71,16 @@ class FetchService(log: Logger[IO]) { throw new RuntimeException(s"Failed to retrieve file: $remotePath, reply: ${ftpClient.getReplyString}") } - outputStream.toString("UTF-8") + outputStream.toByteArray() }.onError(e => log.error(e)(s"Error retrieving file $remotePath")) - def fetchFile(fileName: String): IO[String] = { + def fetchFile(fileName: String): IO[Array[Byte]] = { val remotePath = s"/ltv/tabulas/$fileName" createFtpClient.use { ftpClient => for { _ <- log.info(s"Fetching file: $remotePath") - content <- retrieveFileAsString(ftpClient, remotePath) + content <- retrieveFileAsBytes(ftpClient, remotePath) _ <- log.info(s"Successfully fetched file: $fileName") } yield content } diff --git a/src/main/scala/fetch/lvgmc/FetchServiceTest.scala b/src/main/scala/fetch/lvgmc/FetchServiceTest.scala index 24915c3..ffa4f9e 100644 --- a/src/main/scala/fetch/lvgmc/FetchServiceTest.scala +++ b/src/main/scala/fetch/lvgmc/FetchServiceTest.scala @@ -18,7 +18,7 @@ object FetchServiceTest { val program = for { fetch <- FetchService.of result <- fetch.fetchFile("Latvija_LTV_pilsetas_tekosa_dn.csv") - _ <- IO.println(result) + _ <- IO.println(s"fetched file, size: ${result.length}") } yield () program } diff --git a/src/main/scala/server/Server.scala b/src/main/scala/server/Server.scala index 44670cc..a08c769 100644 --- a/src/main/scala/server/Server.scala +++ b/src/main/scala/server/Server.scala @@ -6,6 +6,7 @@ import com.comcast.ip4s.IpLiteralSyntax import data.DataService import db.PostgresService import fetch.csv.FetchService +import fetch.lvgmc import fs2.io.file.Files import server.ValidateRoutes.{AggFieldList, AggKey, CityList, DateTimeRange, Granularity, ValidateDate, ValidateDateTime, ValidateInt, ValidateMonths, ValidateZonedDateTime} import io.circe.{Encoder, Json, Printer} @@ -28,6 +29,7 @@ import org.typelevel.log4cats.Logger import org.typelevel.log4cats.slf4j.Slf4jLogger import parse.csv.Aggregate import fs2.io.file.{Files, Path} +import org.http4s.headers.`Content-Type` import java.time.{ZoneOffset, ZonedDateTime} import scala.concurrent.duration.DurationInt @@ -54,6 +56,13 @@ class Server(postgresService: PostgresService, dataService: DataService, fetch: private case class ResponseWrapper(result: Map[String, Option[Aggregate.AggregateValue]], query: UserQuery) private val apiRoutes = HttpRoutes.of[IO] { + // http://0.0.0.0:8080/api/show/lvgmc-forecast/Latvija_LTV_pilsetas_tekosa_dn.csv + case GET -> Root / "show" / "lvgmc-forecast" / fileName => + println("lvgmc-forecast GET") + lvgmc.FetchService.of.flatMap(f => f.fetchFile(fileName)).flatMap(bytes => + Ok(bytes).map(_.withContentType(`Content-Type`(MediaType.text.csv))) + ) + case GET -> Root / "show" / "gribName" => Ok("{\"fileName\":\"TODO replace this fake name\"}") // http://0.0.0.0:8080/api/show/grib-all-structure @@ -198,6 +207,7 @@ class Server(postgresService: PostgresService, dataService: DataService, fetch: "/latvia" -> staticcontent.fileService[IO](FileService.Config("./web/dist")), "/database" -> staticcontent.fileService[IO](FileService.Config("./web/dist")), "/harmonie" -> staticcontent.fileService[IO](FileService.Config("./web/dist")), + "/lvgmc-forecast" -> staticcontent.fileService[IO](FileService.Config("./web/dist")), ).orNotFound def run: IO[ExitCode] = diff --git a/web/src/App.tsx b/web/src/App.tsx index 300d86f..10746bc 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -9,12 +9,13 @@ import { Station } from './station/Station'; import { apiHost } from './consts'; import { WeatherIconStub } from './components/weatherIcons/WeatherIconStub'; import { Harmonie } from './harmonie/Harmonie' +import { LvgmcForecast } from './lvgmc-forecast/LvgmcForecast' console.log("env:", import.meta.env.MODE) console.log("api host:", apiHost) const App: Component = () => { - const pages = ['station', 'cities', 'latvia', 'database', 'harmonie'] + const pages = ['station', 'cities', 'latvia', 'database', 'harmonie', 'lvgmc-forecast'] return (
@@ -32,6 +33,7 @@ const App: Component = () => { + diff --git a/web/src/helpers/fetch.ts b/web/src/helpers/fetch.ts index 7cd0501..bdfdbb4 100644 --- a/web/src/helpers/fetch.ts +++ b/web/src/helpers/fetch.ts @@ -25,6 +25,32 @@ export const fetchJson = (url: string, options = {}) => { }) } +export const fetchText = (url: string, options = {}) => { + return fetch(url, options).then(async response => { + let responseCode = '' + if (!response.ok) { + responseCode = `error ${response.status}` + } + + let text = '' + let responseMessage = '' + try { + text = await response.text() + } catch (err) { + responseMessage = 'Invalid TEXT response' + } + + if (responseCode) { + if (responseMessage) throw new Error(`${responseCode}: ${responseMessage}`) + else throw new Error(responseCode) + } else if (responseMessage) { + throw new Error(responseMessage) + } + + return text + }) +} + export const fetchBuffer = (url: string, options = {}) => { return fetch(url, options).then(async response => { diff --git a/web/src/lvgmc-forecast/LvgmcForecast.tsx b/web/src/lvgmc-forecast/LvgmcForecast.tsx new file mode 100644 index 0000000..9c23e1a --- /dev/null +++ b/web/src/lvgmc-forecast/LvgmcForecast.tsx @@ -0,0 +1,18 @@ +import { Component } from 'solid-js' +import { apiHost } from '../consts' +import { fetchText } from '../helpers/fetch' + +// Eiropa_LTV_pilsetas_nakama_dn.csv +// Eiropa_LTV_pilsetas_tekosa_dn.csv +// Latvija_LTV_pilsetas_nakama_dnn.csv +// Latvija_LTV_pilsetas_tekosa_dn.csv +// Latvija_faktiskais_laiks.csv + +export const LvgmcForecast: Component<{}> = () => { + fetchText(`${apiHost}/api/show/lvgmc-forecast/Latvija_LTV_pilsetas_tekosa_dn.csv`) + .then(console.log) + + return <> + LvgmcForecast + +}