~jan0sch/smederee
Showing details for patch 078d0d60bff85cbadb1163fea15ae3887215d4e4.
diff -rN -u old-smederee/build.sbt new-smederee/build.sbt --- old-smederee/build.sbt 2025-01-16 08:07:45.415306550 +0000 +++ new-smederee/build.sbt 2025-01-16 08:07:45.419306557 +0000 @@ -139,6 +139,10 @@ version := "0.7.0-SNAPSHOT", libraryDependencies ++= Seq( library.catsCore, + library.commonMark, + library.commonMarkExtHeadingAnchor, + library.commonMarkExtTables, + library.commonMarkExtTaskListItems, library.http4sCore, library.ip4sCore, library.munit % Test, @@ -180,10 +184,6 @@ library.circeCore, library.circeGeneric, library.circeParser, - library.commonMark, - library.commonMarkExtHeadingAnchor, - library.commonMarkExtTables, - library.commonMarkExtTaskListItems, library.doobieCore, library.doobieHikari, library.doobiePostgres, @@ -230,7 +230,7 @@ Seq( daemonUser := "smederee", daemonGroup := "smederee", - Debian / debianPackageProvides += "smederee", + Debian / debianPackageProvides += "smederee-hub", Debian / debianPackageDependencies += "openjdk-17-jre-headless", defaultLinuxInstallLocation := "/opt", maintainer := "Wegtam GmbH <devops@wegtam.com>", @@ -258,8 +258,8 @@ Rpm / maintainerScripts := maintainerScriptsAppend((Rpm / maintainerScripts).value)( RpmConstants.Post -> s"restartService ${normalizedName.value}" ), - packageSummary := "Smederee - Software collaboration platform.", - packageDescription := "Leverage the power of the darcs vcs to handle your projects with ease and confidence", + packageSummary := "Smederee Hub Service - Software collaboration platform.", + packageDescription := "Leverage the power of the darcs vcs to handle your projects with ease and confidence, this is the central hub service", Debian / requiredStartFacilities := Option("$local_fs $remote_fs $network $postgresql"), // Do not package API docs. Compile / packageDoc / publishArtifact := false, @@ -324,8 +324,16 @@ lazy val tickets = project .in(file("modules/tickets")) - .dependsOn(email, htmlUtils, i18n, security) - .enablePlugins(AutomateHeaderPlugin) + .dependsOn(email, htmlUtils, i18n, security, twirl) + .enablePlugins( + AutomateHeaderPlugin, + DebianPlugin, + JavaServerAppPackaging, + JDebPackaging, + RpmPlugin, + SbtTwirl, + SystemdPlugin + ) .configs(IntegrationTest) .settings(commonSettings) .settings( @@ -342,10 +350,6 @@ library.circeCore, library.circeGeneric, library.circeParser, - library.commonMark, - library.commonMarkExtHeadingAnchor, - library.commonMarkExtTables, - library.commonMarkExtTaskListItems, library.doobieCore, library.doobieHikari, library.doobiePostgres, @@ -372,6 +376,77 @@ library.scalaCheck % Test ) ) + .settings( + libraryDependencies := libraryDependencies.value.map { + case module if module.name == "twirl-api" => + module.cross(CrossVersion.for3Use2_13).exclude("org.scala-lang.modules", "scala-xml_2.13") + case module => module + } ++ Seq("org.scala-lang.modules" %% "scala-xml" % "2.1.0"), + TwirlKeys.templateImports ++= Seq( + "cats._", + "cats.data._", + "cats.syntax.all._", + "de.smederee.html._", + "de.smederee.i18n._", + "de.smederee.security.{ CsrfToken, UserId, Username }", + "org.http4s.Uri" + ) + ) + .settings( + Seq( + daemonUser := "smederee", + daemonGroup := "smederee", + Debian / debianPackageProvides += "smederee-tickets", + Debian / debianPackageDependencies += "openjdk-17-jre-headless", + defaultLinuxInstallLocation := "/opt", + maintainer := "Wegtam GmbH <devops@wegtam.com>", + rpmLicense := Option("AGPL-3.0 or later"), + rpmVendor := "Wegtam GmbH <devops@wegtam.com>", + // Create an empty `conf/production.conf` file if it does not exist. + Debian / maintainerScripts := maintainerScriptsAppend((Debian / maintainerScripts).value)( + DebianConstants.Postinst -> Seq( + s"touch ${defaultLinuxInstallLocation.value}/${normalizedName.value}/conf/production.conf", + s"chown ${daemonUser.value}:${daemonGroup.value} ${defaultLinuxInstallLocation.value}/${normalizedName.value}/conf/production.conf" + ).mkString(" && ") // Chain both commands together in the shell. + ), + // Require a service restart after installation / update. + Debian / maintainerScripts := maintainerScriptsAppend((Debian / maintainerScripts).value)( + DebianConstants.Postinst -> s"restartService ${normalizedName.value}" + ), + // Create an empty `conf/production.conf` file if it does not exist. + Rpm / maintainerScripts := maintainerScriptsAppend((Rpm / maintainerScripts).value)( + RpmConstants.Post -> Seq( + s"touch ${defaultLinuxInstallLocation.value}/${normalizedName.value}/conf/production.conf", + s"chown ${daemonUser.value}:${daemonGroup.value} ${defaultLinuxInstallLocation.value}/${normalizedName.value}/conf/production.conf" + ).mkString(" && ") // Chain both commands together in the shell. + ), + // Require a service restart after installation / update. + Rpm / maintainerScripts := maintainerScriptsAppend((Rpm / maintainerScripts).value)( + RpmConstants.Post -> s"restartService ${normalizedName.value}" + ), + packageSummary := "Smederee Ticket Service - Software collaboration platform.", + packageDescription := "Leverage the power of the darcs vcs to handle your projects with ease and confidence, this is the ticket service", + Debian / requiredStartFacilities := Option("$local_fs $remote_fs $network $postgresql"), + // Do not package API docs. + Compile / packageDoc / publishArtifact := false, + Compile / doc / sources := Seq.empty, + // Require tests to be run before building a debian package. + Debian / packageBin := ((Debian / packageBin) dependsOn (Test / test) dependsOn (IntegrationTest / test)).value, + // Require tests to be run before building a RPM package. + Rpm / packageBin := ((Rpm / packageBin) dependsOn (Test / test) dependsOn (IntegrationTest / test)).value, + // Require tests to be run before building a universal package. + Universal / packageBin := ((Universal / packageBin) dependsOn (Test / test) dependsOn (IntegrationTest / test)).value, + Universal / packageOsxDmg := ((Universal / packageOsxDmg) dependsOn (Test / test) dependsOn (IntegrationTest / test)).value, + Universal / packageXzTarball := ((Universal / packageXzTarball) dependsOn (Test / test) dependsOn (IntegrationTest / test)).value, + Universal / packageZipTarball := ((Universal / packageZipTarball) dependsOn (Test / test) dependsOn (IntegrationTest / test)).value, + // Prevent a customised local application.conf file to be packaged! + Compile / packageBin / mappings ~= { files => + files.filterNot { + case (_, name) => name == "application.conf" + } + } + ) + ) // FIXME: This is a workaround until http4s-twirl gets published properly for Scala 3! lazy val twirl = diff -rN -u old-smederee/modules/html-utils/src/main/scala/de/smederee/html/MarkdownRenderer.scala new-smederee/modules/html-utils/src/main/scala/de/smederee/html/MarkdownRenderer.scala --- old-smederee/modules/html-utils/src/main/scala/de/smederee/html/MarkdownRenderer.scala 1970-01-01 00:00:00.000000000 +0000 +++ new-smederee/modules/html-utils/src/main/scala/de/smederee/html/MarkdownRenderer.scala 2025-01-16 08:07:45.419306557 +0000 @@ -0,0 +1,184 @@ +/* + * 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 java.util.Locale + +import cats.syntax.all._ +import org.commonmark.ext.gfm.tables.TablesExtension +import org.commonmark.ext.heading.anchor.HeadingAnchorExtension +import org.commonmark.ext.task.list.items.TaskListItemsExtension +import org.commonmark.node._ +import org.commonmark.parser.Parser +import org.commonmark.renderer.NodeRenderer +import org.commonmark.renderer.html._ +import org.http4s.Uri +import org.slf4j.{ Logger, LoggerFactory } + +import scala.annotation.nowarn +import scala.jdk.CollectionConverters._ +import scala.util.matching.Regex + +object MarkdownRenderer { + private val log = LoggerFactory.getLogger(getClass()) + + private val MarkdownExtensions = + List(TablesExtension.create(), HeadingAnchorExtension.create(), TaskListItemsExtension.create()) + + /** Render the given ticket content using markdown. + * + * @param markdownSource + * The content of a ticket description. + * @return + * A string containing the rendered markdown (HTML). + */ + def renderTicketContent(markdownSource: String): String = { + val parser = Parser.builder().extensions(MarkdownExtensions.asJava).build() + val markdown = parser.parse(markdownSource) + val renderer = HtmlRenderer + .builder() + .escapeHtml(true) + .extensions(MarkdownExtensions.asJava) + .sanitizeUrls(true) + .build() + renderer.render(markdown) + } + + /** Render the given markdown sources and adjust all relative links by prefixing them with the path for the repostiory + * file browsing (`repo-name/files`). + * + * @param repositoryName + * The name of the repository which contains the markdown sources. If the option is empty then no URI path prefix + * will be set. + * @param markdownSource + * A string containing the markdown sources to be rendered (usually the content of the README.md file in the + * repository root). + * @return + * A string containing the rendered markdown (HTML). + */ + def renderRepositoryMarkdownFile(repositoryName: Option[String])(markdownSource: String): String = { + val parser = Parser.builder().extensions(MarkdownExtensions.asJava).build() + val markdown = parser.parse(markdownSource) + val renderer = HtmlRenderer + .builder() + .attributeProviderFactory(new AttributeProviderFactory { + override def create(context: AttributeProviderContext): AttributeProvider = + new LinkHrefCorrector(repositoryName) + }) + .escapeHtml(true) + .extensions(MarkdownExtensions.asJava) + .nodeRendererFactory(new HtmlNodeRendererFactory { + override def create(context: HtmlNodeRendererContext): NodeRenderer = new ToDoTextRenderer(context) + }) + .sanitizeUrls(true) + .build() + renderer.render(markdown) + } + + /** A helper class used by the `renderRepositoryOverviewReadme` function to adjust the `href` attribute of links that + * are not absolute. + * + * @param repositoryName + * The name of the repository which contains the markdown sources. If the option is empty then no URI path prefix + * will be set. + */ + @SuppressWarnings( + Array("scalafix:DisableSyntax.isInstanceOf") + ) // TODO: Find a way to get rid of the isInstanceOf below. + @nowarn("msg=discarded non-Unit value.*") + class LinkHrefCorrector(private val repositoryName: Option[String]) extends AttributeProvider { + override def setAttributes(node: Node, tagName: String, attributes: java.util.Map[String, String]): Unit = + if (node.isInstanceOf[Link]) { + (repositoryName, attributes.asScala.get("href").flatMap(href => Uri.fromString(href).toOption)).mapN { + case (repositoryName, uri) => + if (uri.scheme.isEmpty) { + val pathPrefix = Uri.Path(Vector(Uri.Path.Segment(repositoryName), Uri.Path.Segment("files"))) + val correctedUri = + if (uri.path.startsWith(pathPrefix)) + uri + else + uri.copy(path = pathPrefix |+| uri.path) + log.debug(s"Corrected URI for repository overview README rendering: $uri -> $correctedUri") + attributes.put("href", correctedUri.toString) + } + } + } + } + + /** A custom text node renderer which is supposed to highlight several words which are considered "todo items". This + * is currently a very limited approach with a fixed list of matching words. + * + * @param context + * A context for an html node renderer that is needed to extract the html writer from it. + */ + class ToDoTextRenderer(context: HtmlNodeRendererContext) extends NodeRenderer { + import ToDoTextCssMapping._ + + private final val htmlWriter: HtmlWriter = context.getWriter() + private final val log: Logger = LoggerFactory.getLogger(getClass()) + + override def getNodeTypes(): java.util.Set[Class[? <: Node]] = Set(classOf[Text]).asJava + + @SuppressWarnings(Array("scalafix:DisableSyntax.null", "scalafix:DisableSyntax.asInstanceOf")) + override def render(node: Node): Unit = { + val text = node.asInstanceOf[Text] // We only receive text nodes (see `getNodeTypes`). + isToDoItem.findFirstMatchIn(text.getLiteral()) match { + case Some(matchedItem) => + log.debug(s"Matched TODO item: ${text.getLiteral()} (${matchedItem.groupCount})") + if (matchedItem.group(4) === null) { + val prefix = matchedItem.group(1) + val item = matchedItem.group(2) + val suffix = matchedItem.group(3) + val cssClass = todoCssClasses(item.toLowerCase(Locale.ROOT)) + htmlWriter.text(prefix) + htmlWriter.tag("span", Map("class" -> cssClass).asJava) + htmlWriter.text(item + ":") + htmlWriter.tag("/span") + htmlWriter.text(suffix) + } else { + val item = matchedItem.group(4) + val cssClass = todoCssClasses(item.toLowerCase(Locale.ROOT)) + htmlWriter.tag("span", Map("class" -> cssClass).asJava) + htmlWriter.text(item) + htmlWriter.tag("/span") + } + case _ => + htmlWriter.text(text.getLiteral()) + } + } + + } +} + +object ToDoTextCssMapping { + /* We want to match either on specific words followed by a colon (`:`) and also on complete text nodes + * which can be created if markdown rendering is involved. + * For example a `**WORD** some text` will produce two text nodes (`WORD` and ` some text`) + * while `WORD: some text` will only produce one. + */ + val isToDoItem: Regex = "(?i)(.*)(DEBUG|FIXME|HACK|TODO):(.*)|^(DEBUG|FIXME|HACK|TODO)$".r + + // Mapping of todo item (words) to css classes for highlighting. + val todoCssClasses: Map[String, String] = Map( + "todo" -> "todo-default", + "fixme" -> "todo-error", + "debug" -> "todo-info", + "hack" -> "todo-warning" + ).withDefaultValue("todo-default") + +} diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/HubServer.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/HubServer.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/HubServer.scala 2025-01-16 08:07:45.415306550 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/HubServer.scala 2025-01-16 08:07:45.419306557 +0000 @@ -23,6 +23,7 @@ import java.util.Locale import cats.arrow.FunctionK +import cats.data._ import cats.effect._ import cats.syntax.all._ import com.typesafe.config._ @@ -62,8 +63,11 @@ * @return * A function which will check the correct origin of requests / cookies inside the CSRF middleware. */ - private def createCsrfOriginCheck(linkConfig: ExternalUrlConfiguration): Request[IO] => Boolean = { request => - CSRF.defaultOriginCheck(request, linkConfig.host.toString, linkConfig.scheme, linkConfig.port.map(_.value)) + private def createCsrfOriginCheck(allowedOrigins: NonEmptyList[ExternalUrlConfiguration]): Request[IO] => Boolean = { + request => + allowedOrigins.exists { linkConfig => + CSRF.defaultOriginCheck(request, linkConfig.host.toString, linkConfig.scheme, linkConfig.port.map(_.value)) + } } /** Try to load the CSRF key from the given path. If it doesn't exist or fails then a new key is generated and stored @@ -171,8 +175,10 @@ ticketMilestoneRoutes = new MilestoneRoutes[IO](ticketsConfiguration, ticketMilestonesRepo, ticketProjectsRepo) cryptoClock = java.time.Clock.systemUTC csrfKey <- loadOrCreateCsrfKey(hubConfiguration.service.csrfKeyFile) - csrfOriginCheck = createCsrfOriginCheck(hubConfiguration.service.external) - csrfBuilder = CSRF[IO, IO](csrfKey, csrfOriginCheck) + csrfOriginCheck = createCsrfOriginCheck( + NonEmptyList(hubConfiguration.service.external, List(ticketsConfiguration.externalUrl)) + ) + csrfBuilder = CSRF[IO, IO](csrfKey, csrfOriginCheck) /* The idea behind the `onFailure` part of the CSRF protection middleware is * that we simply remove the CSRF cookie and redirect the user to the frontpage. * This is done to avoid frustration for users after a server restart because diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/MarkdownRenderer.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/MarkdownRenderer.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/MarkdownRenderer.scala 2025-01-16 08:07:45.415306550 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/MarkdownRenderer.scala 1970-01-01 00:00:00.000000000 +0000 @@ -1,182 +0,0 @@ -/* - * 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.hub - -import java.util.Locale - -import cats.syntax.all._ -import de.smederee.tickets.TicketContent -import org.commonmark.ext.gfm.tables.TablesExtension -import org.commonmark.ext.heading.anchor.HeadingAnchorExtension -import org.commonmark.ext.task.list.items.TaskListItemsExtension -import org.commonmark.node._ -import org.commonmark.parser.Parser -import org.commonmark.renderer.NodeRenderer -import org.commonmark.renderer.html._ -import org.http4s.Uri -import org.slf4j.{ Logger, LoggerFactory } - -import scala.annotation.nowarn -import scala.jdk.CollectionConverters._ -import scala.util.matching.Regex - -object MarkdownRenderer { - private val log = LoggerFactory.getLogger(getClass()) - - private val MarkdownExtensions = - List(TablesExtension.create(), HeadingAnchorExtension.create(), TaskListItemsExtension.create()) - - /** Render the given ticket content using markdown. - * - * @param markdownSource - * The content of a ticket description. - * @return - * A string containing the rendered markdown (HTML). - */ - def renderTicketContent(markdownSource: TicketContent): String = { - val parser = Parser.builder().extensions(MarkdownExtensions.asJava).build() - val markdown = parser.parse(markdownSource.toString) - val renderer = HtmlRenderer - .builder() - .escapeHtml(true) - .extensions(MarkdownExtensions.asJava) - .sanitizeUrls(true) - .build() - renderer.render(markdown) - } - - /** Render the given markdown sources and adjust all relative links by prefixing them with the path for the repostiory - * file browsing (`repo-name/files`). - * - * @param repo - * The repository which contains the markdown sources. If the option is empty then no URI path prefix will be set. - * @param markdownSource - * A string containing the markdown sources to be rendered (usually the content of the README.md file in the - * repository root). - * @return - * A string containing the rendered markdown (HTML). - */ - def renderRepositoryMarkdownFile(repo: Option[VcsRepository])(markdownSource: String): String = { - val parser = Parser.builder().extensions(MarkdownExtensions.asJava).build() - val markdown = parser.parse(markdownSource) - val renderer = HtmlRenderer - .builder() - .attributeProviderFactory(new AttributeProviderFactory { - override def create(context: AttributeProviderContext): AttributeProvider = new LinkHrefCorrector(repo) - }) - .escapeHtml(true) - .extensions(MarkdownExtensions.asJava) - .nodeRendererFactory(new HtmlNodeRendererFactory { - override def create(context: HtmlNodeRendererContext): NodeRenderer = new ToDoTextRenderer(context) - }) - .sanitizeUrls(true) - .build() - renderer.render(markdown) - } - - /** A helper class used by the `renderRepositoryOverviewReadme` function to adjust the `href` attribute of links that - * are not absolute. - * - * @param repo - * The repository which contains the markdown sources. If the option is empty then no URI path prefix will be set. - */ - @SuppressWarnings( - Array("scalafix:DisableSyntax.isInstanceOf") - ) // TODO: Find a way to get rid of the isInstanceOf below. - @nowarn("msg=discarded non-Unit value.*") - class LinkHrefCorrector(private val repo: Option[VcsRepository]) extends AttributeProvider { - override def setAttributes(node: Node, tagName: String, attributes: java.util.Map[String, String]): Unit = - if (node.isInstanceOf[Link]) { - (repo, attributes.asScala.get("href").flatMap(href => Uri.fromString(href).toOption)).mapN { - case (repository, uri) => - if (uri.scheme.isEmpty) { - val pathPrefix = Uri.Path(Vector(Uri.Path.Segment(repository.name.toString), Uri.Path.Segment("files"))) - val correctedUri = - if (uri.path.startsWith(pathPrefix)) - uri - else - uri.copy(path = pathPrefix |+| uri.path) - log.debug(s"Corrected URI for repository overview README rendering: $uri -> $correctedUri") - attributes.put("href", correctedUri.toString) - } - } - } - } - - /** A custom text node renderer which is supposed to highlight several words which are considered "todo items". This - * is currently a very limited approach with a fixed list of matching words. - * - * @param context - * A context for an html node renderer that is needed to extract the html writer from it. - */ - class ToDoTextRenderer(context: HtmlNodeRendererContext) extends NodeRenderer { - import ToDoTextCssMapping._ - - private final val htmlWriter: HtmlWriter = context.getWriter() - private final val log: Logger = LoggerFactory.getLogger(getClass()) - - override def getNodeTypes(): java.util.Set[Class[? <: Node]] = Set(classOf[Text]).asJava - - @SuppressWarnings(Array("scalafix:DisableSyntax.null", "scalafix:DisableSyntax.asInstanceOf")) - override def render(node: Node): Unit = { - val text = node.asInstanceOf[Text] // We only receive text nodes (see `getNodeTypes`). - isToDoItem.findFirstMatchIn(text.getLiteral()) match { - case Some(matchedItem) => - log.debug(s"Matched TODO item: ${text.getLiteral()} (${matchedItem.groupCount})") - if (matchedItem.group(4) === null) { - val prefix = matchedItem.group(1) - val item = matchedItem.group(2) - val suffix = matchedItem.group(3) - val cssClass = todoCssClasses(item.toLowerCase(Locale.ROOT)) - htmlWriter.text(prefix) - htmlWriter.tag("span", Map("class" -> cssClass).asJava) - htmlWriter.text(item + ":") - htmlWriter.tag("/span") - htmlWriter.text(suffix) - } else { - val item = matchedItem.group(4) - val cssClass = todoCssClasses(item.toLowerCase(Locale.ROOT)) - htmlWriter.tag("span", Map("class" -> cssClass).asJava) - htmlWriter.text(item) - htmlWriter.tag("/span") - } - case _ => - htmlWriter.text(text.getLiteral()) - } - } - - } -} - -object ToDoTextCssMapping { - /* We want to match either on specific words followed by a colon (`:`) and also on complete text nodes - * which can be created if markdown rendering is involved. - * For example a `**WORD** some text` will produce two text nodes (`WORD` and ` some text`) - * while `WORD: some text` will only produce one. - */ - val isToDoItem: Regex = "(?i)(.*)(DEBUG|FIXME|HACK|TODO):(.*)|^(DEBUG|FIXME|HACK|TODO)$".r - - // Mapping of todo item (words) to css classes for highlighting. - val todoCssClasses: Map[String, String] = Map( - "todo" -> "todo-default", - "fixme" -> "todo-error", - "debug" -> "todo-info", - "hack" -> "todo-warning" - ).withDefaultValue("todo-default") - -} diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala 2025-01-16 08:07:45.415306550 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala 2025-01-16 08:07:45.419306557 +0000 @@ -27,8 +27,9 @@ import cats.syntax.all._ import de.smederee.darcs._ import de.smederee.html.LinkTools._ -import de.smederee.hub.RequestHelpers.instances.given +import de.smederee.html.MarkdownRenderer import de.smederee.hub.RelatedTypesConverter.given +import de.smederee.hub.RequestHelpers.instances.given import de.smederee.hub.config._ import de.smederee.hub.forms.types.FormErrors import de.smederee.i18n.LanguageCode @@ -410,7 +411,7 @@ case Some((lines, Some(filename))) => if (filename.matches("(?iu).*\\.(md|markdown)$")) { Sync[F] - .delay(MarkdownRenderer.renderRepositoryMarkdownFile(repo)(lines.mkString("\n"))) + .delay(MarkdownRenderer.renderRepositoryMarkdownFile(repo.map(_.name.toString))(lines.mkString("\n"))) .map(_.some) } else { Sync[F].delay(lines.mkString("\n").some) diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/tickets/TicketRoutes.scala new-smederee/modules/hub/src/main/scala/de/smederee/tickets/TicketRoutes.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/tickets/TicketRoutes.scala 2025-01-16 08:07:45.415306550 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/tickets/TicketRoutes.scala 2025-01-16 08:07:45.419306557 +0000 @@ -36,7 +36,6 @@ import org.http4s.headers.Location import org.http4s.twirl.TwirlInstances._ import org.slf4j.LoggerFactory -import de.smederee.hub.MarkdownRenderer /** Routes for managing tickets. * @@ -143,7 +142,9 @@ ) ) ) - renderedTicketContent <- Sync[F].delay(ticket.content.map(MarkdownRenderer.renderTicketContent)) + renderedTicketContent <- Sync[F].delay( + ticket.content.map(_.toString).map(MarkdownRenderer.renderTicketContent) + ) resp <- Ok( views.html.showTicket(lang = language)( projectBaseUri.addSegment("tickets"), diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryFiles.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryFiles.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryFiles.scala.html 2025-01-16 08:07:45.415306550 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryFiles.scala.html 2025-01-16 08:07:45.419306557 +0000 @@ -1,5 +1,5 @@ @import java.util.Locale -@import de.smederee.hub.ToDoTextCssMapping._ +@import de.smederee.html.ToDoTextCssMapping._ @import de.smederee.hub._ @(baseUri: Uri,