~jan0sch/smederee

Showing details for patch 1748634ccfae207a0c1d69f656f5244ef644091e.
2023-02-24 (Fri), 7:53 PM - Jens Grassel - 1748634ccfae207a0c1d69f656f5244ef644091e

Refactoring: Move html utils into separate module.

Summary of changes
4 files added
  • modules/html-utils/src/main/scala/de/smederee/html/ExternalUrlConfiguration.scala
  • modules/html-utils/src/main/scala/de/smederee/html/LinkTools.scala
  • modules/html-utils/src/main/scala/de/smederee/html/MetaTags.scala
  • modules/html-utils/src/test/scala/de/smederee/html/LinkToolsTest.scala
1 files modified with 20 lines added and 2 lines removed
  • build.sbt with 20 added and 2 removed lines
diff -rN -u old-smederee/build.sbt new-smederee/build.sbt
--- old-smederee/build.sbt	2025-01-31 13:55:45.818185516 +0000
+++ new-smederee/build.sbt	2025-01-31 13:55:45.822185522 +0000
@@ -50,7 +50,7 @@
       publish := {},
       publishLocal := {}
     )
-    .aggregate(darcs, email, hub, i18n, security, twirl)
+    .aggregate(darcs, email, htmlUtils, hub, i18n, security, twirl)
 
 lazy val darcs =
   project
@@ -119,10 +119,28 @@
       )
     )
 
+lazy val htmlUtils =
+  project
+    .in(file("modules/html-utils"))
+    .enablePlugins(AutomateHeaderPlugin)
+    .settings(commonSettings)
+    .settings(
+      name := "html-utils",
+      version := "0.5.0-SNAPSHOT",
+      libraryDependencies ++= Seq(
+        library.catsCore,
+        library.http4sCore,
+        library.ip4sCore,
+        library.munit             % Test,
+        library.munitScalaCheck   % Test,
+        library.scalaCheck        % Test
+      )
+    )
+
 lazy val hub =
   project
     .in(file("modules/hub"))
-    .dependsOn(darcs, email, i18n, security, twirl)
+    .dependsOn(darcs, email, htmlUtils, i18n, security, twirl)
     .enablePlugins(
       AutomateHeaderPlugin,
       DebianPlugin,
diff -rN -u old-smederee/modules/html-utils/src/main/scala/de/smederee/html/ExternalUrlConfiguration.scala new-smederee/modules/html-utils/src/main/scala/de/smederee/html/ExternalUrlConfiguration.scala
--- old-smederee/modules/html-utils/src/main/scala/de/smederee/html/ExternalUrlConfiguration.scala	1970-01-01 00:00:00.000000000 +0000
+++ new-smederee/modules/html-utils/src/main/scala/de/smederee/html/ExternalUrlConfiguration.scala	2025-01-31 13:55:45.822185522 +0000
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022  Contributors as noted in the AUTHORS.md file
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package de.smederee.html
+
+import com.comcast.ip4s.{ Host, Port }
+import org.http4s.Uri
+
+/** A wrapper for bundling values to configure a possible "external url mapping", the idea is that a service might be
+  * listening on a server and port which is behind a reverse proxy. The value configured here may have a multitude of
+  * applications generation of valid urls and adjusting of CSRF protection only being examples.
+  *
+  * @param host
+  *   The official hostname of the service which could be used for the CSRF protection, generation of links in e-mails
+  *   etc.
+  * @param path
+  *   A possible path prefix that will be prepended to any paths used in link generation.
+  * @param port
+  *   The port number which defaults to the port the service is listening on. Please note that this is also relevant for
+  *   CSRF protection! It should not be defined if the service is running behind a reverse proxy listening on the
+  *   standard port for the given URL scheme (http/https).
+  * @param scheme
+  *   The URL scheme which is used for links and will also determine if cookies will have the secure flag enabled.
+  */
+final case class ExternalUrlConfiguration(host: Host, path: Option[Uri], port: Option[Port], scheme: Uri.Scheme)
diff -rN -u old-smederee/modules/html-utils/src/main/scala/de/smederee/html/LinkTools.scala new-smederee/modules/html-utils/src/main/scala/de/smederee/html/LinkTools.scala
--- old-smederee/modules/html-utils/src/main/scala/de/smederee/html/LinkTools.scala	1970-01-01 00:00:00.000000000 +0000
+++ new-smederee/modules/html-utils/src/main/scala/de/smederee/html/LinkTools.scala	2025-01-31 13:55:45.822185522 +0000
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022  Contributors as noted in the AUTHORS.md file
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package de.smederee.html
+
+import cats.syntax.all._
+import org.http4s.Uri
+
+object LinkTools {
+
+  extension (linkConfig: ExternalUrlConfiguration) {
+
+    /** Take the given URI path and create a full URI using the specified configuration with a possible path prefix and
+      * append the given path to it.
+      *
+      * @param path
+      *   An URI containing a path with possible URL fragment and query parameters which will be used to construct the
+      *   full URI.
+      * @return
+      *   A full URI created from the values of the ExternalUrlConfiguration (scheme, host, port, possible path prefix)
+      *   and the path data from the given URI.
+      */
+    def createFullUri(path: Uri): Uri = {
+      val completePath = linkConfig.path match {
+        case None             => path.path
+        case Some(pathPrefix) => pathPrefix.path |+| path.path
+      }
+      val baseUri = Uri(
+        scheme = Option(linkConfig.scheme),
+        authority = Option(
+          Uri.Authority(
+            userInfo = None,
+            host = Uri.Host.fromIp4sHost(linkConfig.host),
+            port = linkConfig.port.map(_.value)
+          )
+        ),
+        path = completePath
+      ).withQueryParams(path.params)
+      path.fragment.fold(baseUri)(fragment => baseUri.withFragment(fragment))
+    }
+  }
+
+}
diff -rN -u old-smederee/modules/html-utils/src/main/scala/de/smederee/html/MetaTags.scala new-smederee/modules/html-utils/src/main/scala/de/smederee/html/MetaTags.scala
--- old-smederee/modules/html-utils/src/main/scala/de/smederee/html/MetaTags.scala	1970-01-01 00:00:00.000000000 +0000
+++ new-smederee/modules/html-utils/src/main/scala/de/smederee/html/MetaTags.scala	2025-01-31 13:55:45.822185522 +0000
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022  Contributors as noted in the AUTHORS.md file
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package de.smederee.html
+
+enum MetaRobotsDirective(val tag: String) {
+  case Follow   extends MetaRobotsDirective("follow")
+  case Index    extends MetaRobotsDirective("index")
+  case NoFollow extends MetaRobotsDirective("nofollow")
+  case NoIndex  extends MetaRobotsDirective("noindex")
+}
+
+opaque type MetaDescription = String
+object MetaDescription {
+
+  /** Create an instance of MetaDescription from the given String type.
+    *
+    * @param source
+    *   An instance of type String which will be returned as a MetaDescription.
+    * @return
+    *   The appropriate instance of MetaDescription.
+    */
+  def apply(source: String): MetaDescription = source
+
+  /** Try to create an instance of MetaDescription from the given String.
+    *
+    * @param source
+    *   A String that should fulfil the requirements to be converted into a MetaDescription.
+    * @return
+    *   An option to the successfully converted MetaDescription.
+    */
+  def from(source: String): Option[MetaDescription] = Option(source)
+
+}
+
+opaque type MetaKeyWords = List[String]
+object MetaKeyWords {
+
+  /** Create an instance of MetaKeyWords from the given List[String] type.
+    *
+    * @param source
+    *   An instance of type List[String] which will be returned as a MetaKeyWords.
+    * @return
+    *   The appropriate instance of MetaKeyWords.
+    */
+  def apply(source: List[String]): MetaKeyWords = source
+
+  /** Return an empty instance of MetaKeyWords.
+    *
+    * @return
+    *   An empty list.
+    */
+  def empty: MetaKeyWords = List.empty
+
+  /** Try to create an instance of MetaKeyWords from the given List[String].
+    *
+    * @param source
+    *   A List[String] that should fulfil the requirements to be converted into a MetaKeyWords.
+    * @return
+    *   An option to the successfully converted MetaKeyWords.
+    */
+  def from(source: List[String]): Option[MetaKeyWords] =
+    source.flatMap(string => Option(string)) match {
+      case Nil      => None
+      case keywords => Option(keywords)
+    }
+
+  extension (keywords: MetaKeyWords) {
+    def isEmpty: Boolean  = keywords.isEmpty
+    def mkString: String  = keywords.toList.mkString(", ")
+    def nonEmpty: Boolean = keywords.nonEmpty
+  }
+
+}
+
+/** HTML meta attributes which can be written into the header part of an HTML page.
+  *
+  * @param description
+  *   An optional description for the related meta tag which should not be too long (max. 160/200 characters).
+  * @param keywords
+  *   A list of keywords which can be empty and should not be too long.
+  */
+final case class MetaTags(description: Option[MetaDescription], keywords: MetaKeyWords)
+
+object MetaTags {
+
+  /** Return an empty meta tags instance.
+    *
+    * @return
+    *   An instance of meta tags containing no values, resulting in no tags being rendered.
+    */
+  def empty: MetaTags = MetaTags(description = None, keywords = MetaKeyWords.empty)
+}
diff -rN -u old-smederee/modules/html-utils/src/test/scala/de/smederee/html/LinkToolsTest.scala new-smederee/modules/html-utils/src/test/scala/de/smederee/html/LinkToolsTest.scala
--- old-smederee/modules/html-utils/src/test/scala/de/smederee/html/LinkToolsTest.scala	1970-01-01 00:00:00.000000000 +0000
+++ new-smederee/modules/html-utils/src/test/scala/de/smederee/html/LinkToolsTest.scala	2025-01-31 13:55:45.822185522 +0000
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022  Contributors as noted in the AUTHORS.md file
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package de.smederee.html
+
+import com.comcast.ip4s._
+import org.http4s.Uri
+import org.http4s.implicits._
+
+import munit._
+import org.scalacheck._
+import de.smederee.html.LinkTools.createFullUri
+
+final class LinkToolsTest extends ScalaCheckSuite {
+
+  val externalUrlConfig = ExternalUrlConfiguration(
+    host"proxy.example.com",
+    Uri.fromString("sub-path/with/children").toOption,
+    None,
+    Uri.Scheme.https
+  )
+
+  test("createFullUri must create a corrent full Uri") {
+    val uri     = uri"/another/path#fragment?parameter=value&another=one"
+    val fullUri = externalUrlConfig.createFullUri(uri)
+    assertEquals(
+      fullUri.renderString,
+      "https://proxy.example.com/sub-path/with/children/another/path#fragment?parameter=value&another=one"
+    )
+  }
+}