From 07523d7ea71fb18c31d062f440ca65bf77e4d226 Mon Sep 17 00:00:00 2001 From: trett Date: Sun, 30 Nov 2025 14:17:41 +0100 Subject: [PATCH 1/8] [client] simplify build --- build.sbt | 51 +++++++-------- client/src/main/scala/client/NavBar.scala | 3 +- .../src/main/scala/client/NetworkUtils.scala | 2 +- scripts/local-dev/Caddyfile | 8 +-- scripts/local-dev/docker-compose.yml | 12 ---- scripts/local-docker/Caddyfile | 15 +---- scripts/local-docker/docker-compose.yml | 6 -- server/src/main/resources/application.conf | 6 +- .../scala/ru/trett/rss/server/Server.scala | 64 ++++++++++++------- 9 files changed, 78 insertions(+), 89 deletions(-) diff --git a/build.sbt b/build.sbt index ed58c31..53e9a0f 100644 --- a/build.sbt +++ b/build.sbt @@ -24,7 +24,7 @@ lazy val shared = crossProject(JSPlatform, JVMPlatform) version := projectVersion, organization := organizationName, scalaVersion := scala3Version, - scalacOptions ++= customScalaOptions, + scalacOptions ++= customScalaOptions ) .jsSettings() .jvmSettings() @@ -32,7 +32,7 @@ lazy val shared = crossProject(JSPlatform, JVMPlatform) lazy val client = project .in(file("client")) .dependsOn(shared.js) - .enablePlugins(ScalaJSPlugin, DockerPlugin) + .enablePlugins(ScalaJSPlugin, UniversalPlugin) .settings( version := projectVersion, organization := organizationName, @@ -42,27 +42,21 @@ lazy val client = project _.withModuleKind(ModuleKind.ESModule) .withModuleSplitStyle(ModuleSplitStyle.SmallModulesFor(List("client"))) }, - Compile / sourceGenerators += Def.task { - val out = - (Compile / sourceManaged).value / "scala/client/AppConfig.scala" - IO.write( - out, - s""" - package client - object AppConfig { - val BASE_URI="${sys.env.getOrElse("SERVER_URL", "https://localhost")}" - } - """ - ) - Seq(out) - }, + //Compile / sourceGenerators += Def.task { + //val out = + //(Compile / sourceManaged).value / "scala/client/AppConfig.scala" + //IO.write( + //out, + //s""" + //package client + //object AppConfig { + //val BASE_URI="${sys.env.getOrElse("SERVER_URL", "http://localhost")}" + //} + //""" + //) + //Seq(out) + //}, Universal / mappings ++= directory(buildClientDist.value), - dockerRepository := sys.env.get("REGISTRY"), - dockerCommands := Seq( - Cmd("FROM", "nginx:1.29.1-alpine"), - Cmd("COPY", "opt/docker/dist/", "/usr/share/nginx/html/") - ), - dockerExposedPorts := Seq(80), libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "2.8.1", libraryDependencies += "com.raquo" %%% "laminar" % "17.2.1", libraryDependencies += "be.doeraene" %%% "web-components-ui5" % "2.12.1", @@ -73,6 +67,7 @@ lazy val client = project "io.circe" %%% "circe-parser" ).map(_ % circeVersion), scalacOptions ++= customScalaOptions, + Compile / packageDoc / mappings := Seq(), inThisBuild( List( scalaVersion := scala3Version, @@ -94,6 +89,8 @@ lazy val server = project dockerBaseImage := "eclipse-temurin:17-jre-noble", dockerRepository := sys.env.get("REGISTRY"), dockerExposedPorts := Seq(8080), + // trigger on compile for hot reloading, e.g. ~reStart + buildClientDist := buildClientDist.triggeredBy(Compile / compile).value, libraryDependencies ++= Seq( "org.typelevel" %% "cats-effect" % "3.6.3", "org.slf4j" % "slf4j-api" % "2.0.17", @@ -133,6 +130,12 @@ lazy val server = project libraryDependencies += "org.postgresql" % "postgresql" % "42.7.8" % Test, scalacOptions ++= customScalaOptions, Compile / run / fork := true, + Compile / packageDoc / mappings := Seq(), + Compile / resourceGenerators += buildClientDist.taskValue.map { distDir => + val targetDir = (Compile / resourceManaged).value / "public" + IO.copyDirectory(distDir, targetDir) + (targetDir ** "*").get + }, inThisBuild( List( scalaVersion := scala3Version, @@ -144,13 +147,11 @@ lazy val server = project ThisBuild / buildClientDist := { Process("npm install", client.base).! Process("npm run build", client.base).! - new java.io.File(client.base.getPath + "/dist") + client.base / "dist" } buildImages := { - (client / Docker / publishLocal).value (server / Docker / publishLocal).value } pushImages := { - (client / Docker / publish).value (server / Docker / publish).value } diff --git a/client/src/main/scala/client/NavBar.scala b/client/src/main/scala/client/NavBar.scala index 712cd12..f5c99be 100644 --- a/client/src/main/scala/client/NavBar.scala +++ b/client/src/main/scala/client/NavBar.scala @@ -8,7 +8,6 @@ import be.doeraene.webcomponents.ui5.UList import be.doeraene.webcomponents.ui5.configkeys.IconName import be.doeraene.webcomponents.ui5.configkeys.ListSeparator import be.doeraene.webcomponents.ui5.configkeys.PopoverPlacementType -import client.AppConfig.BASE_URI import client.NetworkUtils.{responseDecoder} import com.raquo.airstream.eventbus.EventBus import com.raquo.laminar.api.L.* @@ -84,6 +83,6 @@ object NavBar { private def refreshFeedsRequest(): EventStream[Unit] = FetchStream .withDecoder(responseDecoder[Unit]) - .post(s"$BASE_URI/api/channels/refresh") + .post("/api/channels/refresh") .mapTo(()) } diff --git a/client/src/main/scala/client/NetworkUtils.scala b/client/src/main/scala/client/NetworkUtils.scala index 41d1379..9e32e79 100644 --- a/client/src/main/scala/client/NetworkUtils.scala +++ b/client/src/main/scala/client/NetworkUtils.scala @@ -17,7 +17,7 @@ import scala.util.Try object NetworkUtils { - def HOST: String = AppConfig.BASE_URI + def HOST: String = "" val JSON_ACCEPT: (String, String) = "Accept" -> "application/json" val JSON_CONTENT_TYPE: (String, String) = "Content-Type" -> "application/json" diff --git a/scripts/local-dev/Caddyfile b/scripts/local-dev/Caddyfile index b5ecdf7..48a82c4 100644 --- a/scripts/local-dev/Caddyfile +++ b/scripts/local-dev/Caddyfile @@ -8,11 +8,11 @@ localhost { path /signup_callback } - reverse_proxy host.docker.internal:8081 { - header_up X-Real-IP {remote_host} - } + # reverse_proxy host.docker.internal:8081 { + # header_up X-Real-IP {remote_host} + #} - handle @apipath { + handle { reverse_proxy host.docker.internal:8080 { header_up X-Real-IP {remote_host} } diff --git a/scripts/local-dev/docker-compose.yml b/scripts/local-dev/docker-compose.yml index d1b6a5b..59fb3f6 100644 --- a/scripts/local-dev/docker-compose.yml +++ b/scripts/local-dev/docker-compose.yml @@ -12,15 +12,3 @@ services: POSTGRES_DB: rss POSTGRES_USER: rss_user - caddy: - image: caddy:2-alpine - restart: unless-stopped - depends_on: - - db - ports: - - "80:80" - - "443:443" - volumes: - - $PWD/Caddyfile:/etc/caddy/Caddyfile - extra_hosts: - - host.docker.internal:host-gateway diff --git a/scripts/local-docker/Caddyfile b/scripts/local-docker/Caddyfile index ea42a32..e6afee9 100644 --- a/scripts/local-docker/Caddyfile +++ b/scripts/local-docker/Caddyfile @@ -1,22 +1,9 @@ localhost { - @apipath { - path /api/* - path /error - path /signin - path /signup - path /signin_callback - path /signup_callback - } - handle @apipath { + handle { reverse_proxy { to rss_server:8080 } } - handle { - reverse_proxy { - to rss_client - } - } } diff --git a/scripts/local-docker/docker-compose.yml b/scripts/local-docker/docker-compose.yml index d293083..cdf24e6 100644 --- a/scripts/local-docker/docker-compose.yml +++ b/scripts/local-docker/docker-compose.yml @@ -41,9 +41,3 @@ services: CLIENT_SECRET: ${CLIENT_SECRET} GOOGLE_API_KEY: ${GOOGLE_API_KEY} - client: - image: client:2.2.8 - container_name: rss_client - restart: always - depends_on: - - server diff --git a/server/src/main/resources/application.conf b/server/src/main/resources/application.conf index 9fc4dba..76fc97d 100644 --- a/server/src/main/resources/application.conf +++ b/server/src/main/resources/application.conf @@ -1,5 +1,5 @@ server { - port = 8080 + port = 80 port = ${?SERVER_PORT} host = "0.0.0.0" } @@ -17,12 +17,12 @@ db { oauth { client-id = ${CLIENT_ID} client-secret = ${CLIENT_SECRET} - redirect-uri = "https://localhost" + redirect-uri = "http://localhost" redirect-uri = ${?SERVER_URL} } cors { - allowed-origin = "https://localhost" + allowed-origin = "http://localhost" allowed-origin = ${?CORS_URL} allow-credentials = false max-age = 24h diff --git a/server/src/main/scala/ru/trett/rss/server/Server.scala b/server/src/main/scala/ru/trett/rss/server/Server.scala index 5a45829..14ceb42 100644 --- a/server/src/main/scala/ru/trett/rss/server/Server.scala +++ b/server/src/main/scala/ru/trett/rss/server/Server.scala @@ -6,36 +6,47 @@ import cats.implicits.* import com.comcast.ip4s.* import com.zaxxer.hikari.HikariConfig import doobie.hikari.* -import doobie.util.log.{LogEvent, LogHandler} +import doobie.util.log.LogEvent +import doobie.util.log.LogHandler +import org.http4s.AuthedRoutes +import org.http4s.HttpRoutes +import org.http4s.StaticFile +import org.http4s.Uri import org.http4s.client.Client +import org.http4s.dsl.io.* import org.http4s.ember.client.EmberClientBuilder import org.http4s.ember.server.* import org.http4s.implicits.* -import org.http4s.server.middleware.{CORS, CORSPolicy, ErrorAction, ErrorHandling} -import org.http4s.{AuthedRoutes, HttpRoutes, Uri} +import org.http4s.server.middleware.CORS +import org.http4s.server.middleware.CORSPolicy +import org.http4s.server.middleware.ErrorAction +import org.http4s.server.middleware.ErrorHandling +import org.http4s.server.staticcontent.* import org.typelevel.log4cats.* import org.typelevel.log4cats.slf4j.* import pureconfig.ConfigSource -import ru.trett.rss.server.authorization.{AuthFilter, SessionManager} -import ru.trett.rss.server.config.{AppConfig, CorsConfig, DbConfig, OAuthConfig} -import ru.trett.rss.server.controllers.{ - ChannelController, - FeedController, - LoginController, - LogoutController, - SummarizeController, - UserController -} +import ru.trett.rss.server.authorization.AuthFilter +import ru.trett.rss.server.authorization.SessionManager +import ru.trett.rss.server.config.AppConfig +import ru.trett.rss.server.config.CorsConfig +import ru.trett.rss.server.config.DbConfig +import ru.trett.rss.server.config.OAuthConfig +import ru.trett.rss.server.controllers.ChannelController +import ru.trett.rss.server.controllers.FeedController +import ru.trett.rss.server.controllers.LoginController +import ru.trett.rss.server.controllers.LogoutController +import ru.trett.rss.server.controllers.SummarizeController +import ru.trett.rss.server.controllers.UserController import ru.trett.rss.server.db.FlywayMigration import ru.trett.rss.server.models.User -import ru.trett.rss.server.repositories.{ChannelRepository, FeedRepository, UserRepository} -import ru.trett.rss.server.services.{ - ChannelService, - FeedService, - SummarizeService, - UpdateTask, - UserService -} +import ru.trett.rss.server.repositories.ChannelRepository +import ru.trett.rss.server.repositories.FeedRepository +import ru.trett.rss.server.repositories.UserRepository +import ru.trett.rss.server.services.ChannelService +import ru.trett.rss.server.services.FeedService +import ru.trett.rss.server.services.SummarizeService +import ru.trett.rss.server.services.UpdateTask +import ru.trett.rss.server.services.UserService object Server extends IOApp: @@ -162,6 +173,15 @@ object Server extends IOApp: logoutController ) ) + private def resourceRoutes: HttpRoutes[IO] = + val indexRoute = HttpRoutes.of[IO] { + case request @ GET -> Root => + StaticFile.fromResource("/public/index.html", Some(request)).getOrElseF(NotFound()) + + case request @ GET -> Root / "" => + StaticFile.fromResource("/public/index.html", Some(request)).getOrElseF(NotFound()) + } + indexRoute <+> resourceServiceBuilder[IO]("/public").toRoutes private def unprotectedRoutes( sessionManager: SessionManager[IO], @@ -169,7 +189,7 @@ object Server extends IOApp: userService: UserService, client: Client[IO] ): HttpRoutes[IO] = - LoginController.routes(sessionManager, oauthConfig, userService, client) + LoginController.routes(sessionManager, oauthConfig, userService, client) <+> resourceRoutes private def authedRoutes( channelService: ChannelService, From 95c7bfc999f1ae09358e9ebcbab99d717774667c Mon Sep 17 00:00:00 2001 From: trett Date: Mon, 1 Dec 2025 17:41:15 +0100 Subject: [PATCH 2/8] fix links --- client/src/main/scala/client/Home.scala | 6 +++--- client/src/main/scala/client/LoginPage.scala | 5 ++--- client/src/main/scala/client/NetworkUtils.scala | 4 +--- client/src/main/scala/client/SettingsPage.scala | 12 ++++++------ client/src/main/scala/client/SummaryPage.scala | 3 +-- client/vite.config.js | 2 +- .../src/main/scala/ru/trett/rss/server/Server.scala | 2 +- 7 files changed, 15 insertions(+), 19 deletions(-) diff --git a/client/src/main/scala/client/Home.scala b/client/src/main/scala/client/Home.scala index 0c6c05d..b6ad44b 100644 --- a/client/src/main/scala/client/Home.scala +++ b/client/src/main/scala/client/Home.scala @@ -184,7 +184,7 @@ object Home: private def getChannelsAndFeedsRequest(page: Int): EventStream[Try[FeedItemList]] = FetchStream .withDecoder(responseDecoder[FeedItemList]) - .get(s"$HOST/api/channels/feeds?page=${page}&limit=${pageLimit}") + .get(s"/api/channels/feeds?page=${page}&limit=${pageLimit}") .mapSuccess(_.get) private def updateFeedRequest(links: List[String]): EventStream[Try[List[String]]] = @@ -195,7 +195,7 @@ object Home: FetchStream .withDecoder(responseDecoder[String]) .post( - s"$HOST/api/feeds/read", + "/api/feeds/read", _.body(links.asJson.toString), _.headers(JSON_ACCEPT, JSON_CONTENT_TYPE) ) @@ -204,5 +204,5 @@ object Home: private def getUnreadCountRequest(): EventStream[Try[Int]] = FetchStream .withDecoder(responseDecoder[Int]) - .get(s"$HOST/api/feeds/unread/total") + .get("/api/feeds/unread/total") .mapSuccess(_.get) diff --git a/client/src/main/scala/client/LoginPage.scala b/client/src/main/scala/client/LoginPage.scala index 5523921..cfc62b4 100644 --- a/client/src/main/scala/client/LoginPage.scala +++ b/client/src/main/scala/client/LoginPage.scala @@ -2,7 +2,6 @@ package client import be.doeraene.webcomponents.ui5.Link import be.doeraene.webcomponents.ui5.configkeys.IconName -import client.NetworkUtils.HOST import com.raquo.laminar.api.L.* import scala.scalajs.js import scala.scalajs.js.annotation.JSImport @@ -46,14 +45,14 @@ object LoginPage { Link( cls("google-button", "signup-button"), "Sign Up", - _.href := s"$HOST/signup", + _.href := "/signup", typ("button"), _.icon := IconName.`sys-add` ), Link( cls("google-button"), "Sign In", - _.href := s"$HOST/signin", + _.href := "/signin", typ("button"), _.icon := IconName.`sys-enter` ) diff --git a/client/src/main/scala/client/NetworkUtils.scala b/client/src/main/scala/client/NetworkUtils.scala index 9e32e79..54fa434 100644 --- a/client/src/main/scala/client/NetworkUtils.scala +++ b/client/src/main/scala/client/NetworkUtils.scala @@ -17,8 +17,6 @@ import scala.util.Try object NetworkUtils { - def HOST: String = "" - val JSON_ACCEPT: (String, String) = "Accept" -> "application/json" val JSON_CONTENT_TYPE: (String, String) = "Content-Type" -> "application/json" @@ -55,5 +53,5 @@ object NetworkUtils { ) def logout(): EventStream[Unit] = - FetchStream.post(s"$HOST/api/logout", _.body("")).mapTo(()) + FetchStream.post("/api/logout", _.body("")).mapTo(()) } diff --git a/client/src/main/scala/client/SettingsPage.scala b/client/src/main/scala/client/SettingsPage.scala index 2f6ee25..9fcefee 100644 --- a/client/src/main/scala/client/SettingsPage.scala +++ b/client/src/main/scala/client/SettingsPage.scala @@ -161,7 +161,7 @@ object SettingsPage { FetchStream .withDecoder(responseDecoder[String]) .post( - s"$HOST/api/user/settings", + "/api/user/settings", _.body(s.asJson.toString), _.headers(JSON_ACCEPT, JSON_CONTENT_TYPE) ) @@ -172,7 +172,7 @@ object SettingsPage { private def getSettingsRequest: EventStream[Try[Option[UserSettings]]] = FetchStream .withDecoder(responseDecoder[Option[UserSettings]]) - .get(s"$HOST/api/user/settings") + .get("/api/user/settings") .mapSuccess(_.get) private def renderChannel( @@ -227,7 +227,7 @@ object SettingsPage { FetchStream .withDecoder(responseDecoder[String]) .put( - s"$HOST/api/channels/$id/highlight", + s"/api/channels/$id/highlight", _.body(highlighted.asJson.toString), _.headers(JSON_ACCEPT, JSON_CONTENT_TYPE) ) @@ -236,7 +236,7 @@ object SettingsPage { private def deleteChannelRequest(id: String): EventStream[Try[Long]] = FetchStream .withDecoder(responseDecoder[Long]) - .apply(_.DELETE, s"$HOST/api/channels/$id") + .apply(_.DELETE, s"/api/channels/$id") .mapSuccess(_.get) private def newChannelDialog(): HtmlElement = div( @@ -279,14 +279,14 @@ object SettingsPage { private def getChannelsRequest: EventStream[Try[ChannelList]] = FetchStream .withDecoder(responseDecoder[ChannelList]) - .get(s"$HOST/api/channels") + .get("/api/channels") .mapSuccess(_.get) private def updateChannelRequest(link: String): EventStream[Try[Unit]] = FetchStream .withDecoder(responseDecoder[String]) .post( - s"$HOST/api/channels", + "/api/channels", _.body(link.asJson.toString), _.headers(JSON_ACCEPT, JSON_CONTENT_TYPE) ) diff --git a/client/src/main/scala/client/SummaryPage.scala b/client/src/main/scala/client/SummaryPage.scala index eed36d1..4dfe2c6 100644 --- a/client/src/main/scala/client/SummaryPage.scala +++ b/client/src/main/scala/client/SummaryPage.scala @@ -3,14 +3,13 @@ package client import com.raquo.laminar.api.L.* import client.NetworkUtils.unsafeParseToHtmlFragment import be.doeraene.webcomponents.ui5.Panel -import client.NetworkUtils.HOST import be.doeraene.webcomponents.ui5.BusyIndicator import be.doeraene.webcomponents.ui5.UList object SummaryPage { def render: Element = { - val response = EventStream.fromValue(s"$HOST/api/summarize").flatMapWithStatus { req => + val response = EventStream.fromValue("/api/summarize").flatMapWithStatus { req => FetchStream.get(req) } val isLoading = response.map(_.isPending) diff --git a/client/vite.config.js b/client/vite.config.js index 0fbf4b7..42629ba 100644 --- a/client/vite.config.js +++ b/client/vite.config.js @@ -5,5 +5,5 @@ export default defineConfig({ plugins: [scalaJSPlugin({ cwd: '../', projectID: 'client', - })], + })] }); diff --git a/server/src/main/scala/ru/trett/rss/server/Server.scala b/server/src/main/scala/ru/trett/rss/server/Server.scala index 14ceb42..87a24f6 100644 --- a/server/src/main/scala/ru/trett/rss/server/Server.scala +++ b/server/src/main/scala/ru/trett/rss/server/Server.scala @@ -175,7 +175,7 @@ object Server extends IOApp: ) private def resourceRoutes: HttpRoutes[IO] = val indexRoute = HttpRoutes.of[IO] { - case request @ GET -> Root => + case request @ GET -> Root => StaticFile.fromResource("/public/index.html", Some(request)).getOrElseF(NotFound()) case request @ GET -> Root / "" => From 6661f848e907c66ebc88f41da5621074a29ab688 Mon Sep 17 00:00:00 2001 From: trett Date: Thu, 4 Dec 2025 17:08:08 +0100 Subject: [PATCH 3/8] remove obsolete files --- build.sbt | 22 ++++------------------ scripts/local-dev/Caddyfile | 20 -------------------- scripts/local-dev/docker-compose.yml | 14 -------------- scripts/local-docker/Caddyfile | 2 +- scripts/local-docker/docker-compose.yml | 4 +--- 5 files changed, 6 insertions(+), 56 deletions(-) delete mode 100644 scripts/local-dev/Caddyfile delete mode 100644 scripts/local-dev/docker-compose.yml diff --git a/build.sbt b/build.sbt index 53e9a0f..d73e3b4 100644 --- a/build.sbt +++ b/build.sbt @@ -13,8 +13,8 @@ lazy val logs4catVersion = "2.7.1" lazy val customScalaOptions = Seq("-Wunused:imports", "-rewrite", "-source:3.4-migration") lazy val buildClientDist = taskKey[File]("Build client optimized package") -lazy val buildImages = taskKey[Unit]("Build docker images") -lazy val pushImages = taskKey[Unit]("Push docker images to remote repository") +lazy val buildImage = taskKey[Unit]("Build docker image") +lazy val pushImage = taskKey[Unit]("Push docker image to remote repository") lazy val shared = crossProject(JSPlatform, JVMPlatform) .crossType(CrossType.Pure) @@ -42,20 +42,6 @@ lazy val client = project _.withModuleKind(ModuleKind.ESModule) .withModuleSplitStyle(ModuleSplitStyle.SmallModulesFor(List("client"))) }, - //Compile / sourceGenerators += Def.task { - //val out = - //(Compile / sourceManaged).value / "scala/client/AppConfig.scala" - //IO.write( - //out, - //s""" - //package client - //object AppConfig { - //val BASE_URI="${sys.env.getOrElse("SERVER_URL", "http://localhost")}" - //} - //""" - //) - //Seq(out) - //}, Universal / mappings ++= directory(buildClientDist.value), libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "2.8.1", libraryDependencies += "com.raquo" %%% "laminar" % "17.2.1", @@ -149,9 +135,9 @@ ThisBuild / buildClientDist := { Process("npm run build", client.base).! client.base / "dist" } -buildImages := { +buildImage := { (server / Docker / publishLocal).value } -pushImages := { +pushImage := { (server / Docker / publish).value } diff --git a/scripts/local-dev/Caddyfile b/scripts/local-dev/Caddyfile deleted file mode 100644 index 48a82c4..0000000 --- a/scripts/local-dev/Caddyfile +++ /dev/null @@ -1,20 +0,0 @@ -localhost { - @apipath { - path /api/* - path /error - path /signin - path /signup - path /signin_callback - path /signup_callback - } - - # reverse_proxy host.docker.internal:8081 { - # header_up X-Real-IP {remote_host} - #} - - handle { - reverse_proxy host.docker.internal:8080 { - header_up X-Real-IP {remote_host} - } - } -} diff --git a/scripts/local-dev/docker-compose.yml b/scripts/local-dev/docker-compose.yml deleted file mode 100644 index 59fb3f6..0000000 --- a/scripts/local-dev/docker-compose.yml +++ /dev/null @@ -1,14 +0,0 @@ -version: "3.7" - -services: - db: - image: postgres - container_name: postgresdb - restart: always - ports: - - "5432:5432" - environment: - POSTGRES_PASSWORD: 123456 - POSTGRES_DB: rss - POSTGRES_USER: rss_user - diff --git a/scripts/local-docker/Caddyfile b/scripts/local-docker/Caddyfile index e6afee9..ff46334 100644 --- a/scripts/local-docker/Caddyfile +++ b/scripts/local-docker/Caddyfile @@ -1,4 +1,4 @@ -localhost { +localhost:80 { handle { reverse_proxy { diff --git a/scripts/local-docker/docker-compose.yml b/scripts/local-docker/docker-compose.yml index cdf24e6..379bf88 100644 --- a/scripts/local-docker/docker-compose.yml +++ b/scripts/local-docker/docker-compose.yml @@ -1,5 +1,3 @@ -version: "3.7" - services: db: image: postgres @@ -36,7 +34,7 @@ services: DATASOURCE_URL: jdbc:postgresql://postgresdb:5432/rss DATASOURCE_USER: rss_user DATASOURCE_PASS: 123456 - CORS_URL: https://localhost + CORS_URL: http://localhost CLIENT_ID: ${CLIENT_ID} CLIENT_SECRET: ${CLIENT_SECRET} GOOGLE_API_KEY: ${GOOGLE_API_KEY} From 12477b0d0f3218aa387012a5ed1ec6c02dfdae68 Mon Sep 17 00:00:00 2001 From: trett Date: Thu, 4 Dec 2025 17:19:30 +0100 Subject: [PATCH 4/8] update workflow --- .github/workflows/build.yml | 6 +++--- README.md | 34 +++++++++++++--------------------- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a621c62..4f8e541 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,8 +25,8 @@ jobs: - name: Login to registry run: echo ${{ secrets.TOKEN }} | docker login --username oauth --password-stdin cr.yandex - - name: Push images - run: sbt pushImages + - name: Push image + run: sbt pushImage env: SERVER_URL: ${{ secrets.SERVER_URL }} - REGISTRY: ${{ secrets.REGISTRY }} \ No newline at end of file + REGISTRY: ${{ secrets.REGISTRY }} diff --git a/README.md b/README.md index 8348fdc..be845f1 100644 --- a/README.md +++ b/README.md @@ -54,9 +54,9 @@ This is the easiest way to run the application. ``` 2. **Build Docker images**: - This command will build the Docker images for the server and the client. + This command will build the Docker image. ```bash - sbt buildImages + sbt buildImage ``` 3. **Run the application**: @@ -71,10 +71,8 @@ This is the easiest way to run the application. This setup is for actively developing the application. 1. **Start the database**: - This will start a PostgreSQL database instance. - ```bash - docker-compose -f scripts/local-dev/docker-compose.yml up - ``` + Prepare and start a PostgreSQL database instance. + 2. **Run the backend server**: In a new terminal, start the backend server using sbt. You need to set the required environment variables. @@ -84,19 +82,8 @@ This setup is for actively developing the application. export GOOGLE_API_KEY=your_google_ai_api_key sbt server/run ``` - The server will be running on `http://localhost:8080`. + The server will be running on `http://localhost`. -3. **Run the frontend development server**: - - In new terminal, navigate to the `client` directory, install dependencies, and build JS bundle. - ```bash - cd client - sbt ~fastLinkJs - ``` - - start one more new terminal from the `client` folder and start Vite development server - ```bash - npm run dev - ``` - The frontend will be available at `http://localhost:5173`. ## Configuration @@ -113,12 +100,17 @@ The application is configured using environment variables. | `SERVER_URL` | The public URL of the server. Used for OAuth redirect URI. | `https://localhost` | No | | `CORS_URL` | The allowed origin for CORS requests. | `https://localhost` | No | | `GOOGLE_API_KEY` | The API key for Google's Generative AI. | - | For summary feature | -| `REGISTRY` | The Docker registry to push images to (used with `sbt pushImages`). | - | No | +| `REGISTRY` | The Docker registry to push the image to | - | No | ## Deployment -The Docker images built with `sbt buildImages` can be used for production deployment. You can adapt the `scripts/local-docker/docker-compose.yml` file for your production environment. Remember to configure all the necessary environment variables. +The Docker image built with `sbt buildImage` can be used for production deployment. The image includes both the backend server and frontend assets. You can adapt the `scripts/local-docker/docker-compose.yml` file for your production environment. Remember to configure all the necessary environment variables. + +To push the image to a registry, set the `REGISTRY` environment variable and run: +```bash +sbt pushImage +``` ## License -This project is licensed under the MIT License. \ No newline at end of file +This project is licensed under the MIT License. From b0b9c281442a5bfbd416c2e277fdacc84f355c60 Mon Sep 17 00:00:00 2001 From: Roman Tretyakov Date: Thu, 4 Dec 2025 19:49:13 +0100 Subject: [PATCH 5/8] Update server/src/main/scala/ru/trett/rss/server/Server.scala Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- server/src/main/scala/ru/trett/rss/server/Server.scala | 3 --- 1 file changed, 3 deletions(-) diff --git a/server/src/main/scala/ru/trett/rss/server/Server.scala b/server/src/main/scala/ru/trett/rss/server/Server.scala index 87a24f6..678bb51 100644 --- a/server/src/main/scala/ru/trett/rss/server/Server.scala +++ b/server/src/main/scala/ru/trett/rss/server/Server.scala @@ -177,9 +177,6 @@ object Server extends IOApp: val indexRoute = HttpRoutes.of[IO] { case request @ GET -> Root => StaticFile.fromResource("/public/index.html", Some(request)).getOrElseF(NotFound()) - - case request @ GET -> Root / "" => - StaticFile.fromResource("/public/index.html", Some(request)).getOrElseF(NotFound()) } indexRoute <+> resourceServiceBuilder[IO]("/public").toRoutes From b3975a0f57477d3fb307fbeb27276e224755d555 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Dec 2025 20:43:05 +0100 Subject: [PATCH 6/8] [WIP] Consolidate client and server into single deployable unit (#127) * Initial plan * chore: update project version to 2.9.9 Co-authored-by: trett <1980024+trett@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: trett <1980024+trett@users.noreply.github.com> --- build.sbt | 2 +- scripts/local-docker/docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index d73e3b4..628fb69 100644 --- a/build.sbt +++ b/build.sbt @@ -4,7 +4,7 @@ import org.scalajs.linker.interface.ModuleSplitStyle import scala.sys.process.* -lazy val projectVersion = "2.2.8" +lazy val projectVersion = "2.9.9" lazy val organizationName = "ru.trett" lazy val scala3Version = "3.7.4" lazy val circeVersion = "0.14.15" diff --git a/scripts/local-docker/docker-compose.yml b/scripts/local-docker/docker-compose.yml index 379bf88..aaa74f7 100644 --- a/scripts/local-docker/docker-compose.yml +++ b/scripts/local-docker/docker-compose.yml @@ -24,7 +24,7 @@ services: - host.docker.internal:host-gateway server: - image: server:2.2.8 + image: server:2.9.9 container_name: rss_server restart: always depends_on: From 2b60abbb51e076f9ced616f7c71d1a38e4c2471a Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Dec 2025 20:59:47 +0100 Subject: [PATCH 7/8] [WIP] Consolidate client into server resources (#128) * Initial plan * fix: Update project version to 2.2.9 Co-authored-by: trett <1980024+trett@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: trett <1980024+trett@users.noreply.github.com> --- build.sbt | 2 +- scripts/local-docker/docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 628fb69..7dad8d5 100644 --- a/build.sbt +++ b/build.sbt @@ -4,7 +4,7 @@ import org.scalajs.linker.interface.ModuleSplitStyle import scala.sys.process.* -lazy val projectVersion = "2.9.9" +lazy val projectVersion = "2.2.9" lazy val organizationName = "ru.trett" lazy val scala3Version = "3.7.4" lazy val circeVersion = "0.14.15" diff --git a/scripts/local-docker/docker-compose.yml b/scripts/local-docker/docker-compose.yml index aaa74f7..9b2497b 100644 --- a/scripts/local-docker/docker-compose.yml +++ b/scripts/local-docker/docker-compose.yml @@ -24,7 +24,7 @@ services: - host.docker.internal:host-gateway server: - image: server:2.9.9 + image: server:2.2.9 container_name: rss_server restart: always depends_on: From 067aae0b8f39451d65bf92e7ab65a335ae054737 Mon Sep 17 00:00:00 2001 From: trett Date: Fri, 5 Dec 2025 18:20:55 +0100 Subject: [PATCH 8/8] fix reloading --- build.sbt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index 7dad8d5..951c6b0 100644 --- a/build.sbt +++ b/build.sbt @@ -75,8 +75,8 @@ lazy val server = project dockerBaseImage := "eclipse-temurin:17-jre-noble", dockerRepository := sys.env.get("REGISTRY"), dockerExposedPorts := Seq(8080), - // trigger on compile for hot reloading, e.g. ~reStart - buildClientDist := buildClientDist.triggeredBy(Compile / compile).value, + watchSources ++= (client / Compile / watchSources).value, + Compile / compile := ((Compile / compile) dependsOn (client / Compile / fastLinkJS)).value, libraryDependencies ++= Seq( "org.typelevel" %% "cats-effect" % "3.6.3", "org.slf4j" % "slf4j-api" % "2.0.17", @@ -117,11 +117,13 @@ lazy val server = project scalacOptions ++= customScalaOptions, Compile / run / fork := true, Compile / packageDoc / mappings := Seq(), - Compile / resourceGenerators += buildClientDist.taskValue.map { distDir => + Compile / resourceGenerators += Def.task { + val _ = (client / Compile / fastLinkJS).value + val distDir = buildClientDist.value val targetDir = (Compile / resourceManaged).value / "public" IO.copyDirectory(distDir, targetDir) (targetDir ** "*").get - }, + }.taskValue, inThisBuild( List( scalaVersion := scala3Version,