~jan0sch/smederee
Showing details for patch 9c753d0270a7b42c6d3e87d0773e3b47b220d9b3.
diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/AccountManagementRoutes.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/AccountManagementRoutes.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/AccountManagementRoutes.scala 2025-01-11 12:01:12.692589499 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/AccountManagementRoutes.scala 2025-01-11 12:01:12.696589506 +0000 @@ -40,7 +40,6 @@ import de.smederee.tickets.TicketServiceApi import de.smederee.tickets.TicketsUser import org.http4s.* -import org.http4s.dsl.* import org.http4s.headers.Location import org.http4s.implicits.* import org.http4s.twirl.TwirlInstances.* @@ -73,7 +72,7 @@ organisationsRepo: OrganisationRepository[F], signAndValidate: SignAndValidate, ticketServiceApi: TicketServiceApi[F] -) extends Http4sDsl[F] { +) extends HttpBaseRoute[F] { private val log = LoggerFactory.getLogger(getClass) /** Delete the given directory recursively. It is checked if the given directory is a direct sub directory of the diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/AuthenticationRoutes.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/AuthenticationRoutes.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/AuthenticationRoutes.scala 2025-01-11 12:01:12.692589499 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/AuthenticationRoutes.scala 2025-01-11 12:01:12.696589506 +0000 @@ -32,7 +32,6 @@ import de.smederee.hub.forms.types.FormFieldError import de.smederee.security.* import org.http4s.* -import org.http4s.dsl.Http4sDsl import org.http4s.headers.Location import org.http4s.implicits.* import org.http4s.twirl.TwirlInstances.* @@ -79,7 +78,7 @@ external: ExternalUrlConfiguration, repo: AuthenticationRepository[F], signAndValidate: SignAndValidate -) extends Http4sDsl[F] { +) extends HttpBaseRoute[F] { private val log = LoggerFactory.getLogger(getClass) private val loginPath = uri"/login" diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/HttpBaseRoute.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/HttpBaseRoute.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/HttpBaseRoute.scala 1970-01-01 00:00:00.000000000 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/HttpBaseRoute.scala 2025-01-11 12:01:12.696589506 +0000 @@ -0,0 +1,66 @@ +/* + * 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 cats.effect.* +import de.smederee.security.Username +import de.smederee.tickets.ProjectName +import org.http4s.AuthedRoutes +import org.http4s.HttpRoutes +import org.http4s.dsl.Http4sDsl + +/** A base class for HTTP routes to ease consistent structuring of them. This class should be extended if HTTP endpoints + * are implemented via http4s. + * + * @tparam F + * A higher kinded type providing needed functionality, which is usually an IO monad like Async or Sync. + */ +abstract class HttpBaseRoute[F[_]: Async] extends Http4sDsl[F] with HttpBaseRouteHelpers { + + /** All protected (i.e. AuthedRoutes) that are implemented by the routing class. + * + * @return + * A collection of protected routes which may be empty (i.e. `AuthedRoutes.empty`). + */ + def protectedRoutes: AuthedRoutes[Account, F] + + /** All public (unprotected) routes that are implemented by the routing class. + * + * @return + * A collection of routes that might by empty (i.e. `HttpRoutes.empty`). + */ + def routes: HttpRoutes[F] +} + +trait HttpBaseRouteHelpers { + + /** A helper function to provide a consistent web page title generator. It generates the base of a page title that + * may be extended further. + * + * @param ownerName + * The name of the owner of a resource (project, repo, etc.). + * @param An + * optional resource name (project, repo, etc.). + * @return + * A base string for the web page title header. + */ + def genPageTitleBase(ownerName: Username)(repoOrProject: Option[ProjectName | VcsRepositoryName]): String = { + val prefix = s"~$ownerName" + repoOrProject.fold(prefix)(name => prefix + s"/$name") + } +} diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/LandingPageRoutes.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/LandingPageRoutes.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/LandingPageRoutes.scala 2025-01-11 12:01:12.692589499 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/LandingPageRoutes.scala 2025-01-11 12:01:12.696589506 +0000 @@ -24,7 +24,6 @@ import de.smederee.hub.config.* import de.smederee.i18n.LanguageCode import org.http4s.* -import org.http4s.dsl.Http4sDsl import org.http4s.implicits.* import org.http4s.twirl.TwirlInstances.* @@ -38,7 +37,7 @@ * @tparam F * A higher kinded type providing needed functionality, which is usually an IO monad like Async or Sync. */ -final class LandingPageRoutes[F[_]: Async](configuration: ServiceConfig) extends Http4sDsl[F] { +final class LandingPageRoutes[F[_]: Async](configuration: ServiceConfig) extends HttpBaseRoute[F] { private val linkConfig = configuration.external // The base URI for our site which that be passed into some templates which create links themselfes. private val baseUri = linkConfig.createFullUri(Uri()) diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/OrganisationRoutes.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/OrganisationRoutes.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/OrganisationRoutes.scala 2025-01-11 12:01:12.696589506 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/OrganisationRoutes.scala 2025-01-11 12:01:12.696589506 +0000 @@ -34,7 +34,6 @@ import de.smederee.i18n.LanguageCode import de.smederee.security.* import org.http4s.* -import org.http4s.dsl.Http4sDsl import org.http4s.headers.* import org.http4s.implicits.* import org.http4s.twirl.TwirlInstances.* @@ -50,7 +49,7 @@ * A higher kinded type providing needed functionality, which is usually an IO monad like Async or Sync. */ final class OrganisationRoutes[F[_]: Async](configuration: ServiceConfig, orgRepo: OrganisationRepository[F]) - extends Http4sDsl[F] { + extends HttpBaseRoute[F] { private val log = LoggerFactory.getLogger(getClass) given org.typelevel.log4cats.LoggerFactory[F] = org.typelevel.log4cats.slf4j.Slf4jFactory.create[F] @@ -525,4 +524,5 @@ val protectedRoutes = showCreateOrganisationForm <+> createOrganisation <+> showEditOrganisationAdminsForm <+> showEditOrganisationForm <+> deleteOrganisation <+> editOrganisationAdmins <+> editOrganisation + val routes = HttpRoutes.empty } diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/ResetPasswordRoutes.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/ResetPasswordRoutes.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/ResetPasswordRoutes.scala 2025-01-11 12:01:12.696589506 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/ResetPasswordRoutes.scala 2025-01-11 12:01:12.696589506 +0000 @@ -33,7 +33,6 @@ import de.smederee.hub.forms.types.FormFieldError import de.smederee.i18n.LanguageCode import org.http4s.* -import org.http4s.dsl.Http4sDsl import org.http4s.headers.Location import org.http4s.implicits.* import org.http4s.twirl.TwirlInstances.* @@ -47,7 +46,7 @@ emailMiddleware: EmailMiddleware[F], external: ExternalUrlConfiguration, resetPasswordRepo: ResetPasswordRepository[F] -) extends Http4sDsl[F] { +) extends HttpBaseRoute[F] { private val log = LoggerFactory.getLogger(getClass) private val loginPath = uri"/login" diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/SignupRoutes.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/SignupRoutes.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/SignupRoutes.scala 2025-01-11 12:01:12.696589506 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/SignupRoutes.scala 2025-01-11 12:01:12.696589506 +0000 @@ -27,7 +27,6 @@ import de.smederee.hub.forms.types.FormFieldError import de.smederee.security.* import org.http4s.* -import org.http4s.dsl.Http4sDsl import org.http4s.headers.Location import org.http4s.implicits.* import org.http4s.twirl.TwirlInstances.* @@ -42,7 +41,8 @@ * @tparam F * A higher kinded type providing needed functionality, which is usually an IO monad like Async or Sync. */ -final class SignupRoutes[F[_]: Async](configuration: ServiceConfig, repo: SignupRepository[F]) extends Http4sDsl[F] { +final class SignupRoutes[F[_]: Async](configuration: ServiceConfig, repo: SignupRepository[F]) + extends HttpBaseRoute[F] { private val log = LoggerFactory.getLogger(getClass) private val linkConfig = configuration.external private val signupConfig = configuration.signup 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-11 12:01:12.696589506 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala 2025-01-11 12:01:12.696589506 +0000 @@ -37,7 +37,6 @@ import de.smederee.tickets.ProjectRepository import org.fusesource.jansi.utils.UtilsAnsiHtml import org.http4s.* -import org.http4s.dsl.Http4sDsl import org.http4s.dsl.impl.* import org.http4s.headers.* import org.http4s.implicits.* @@ -66,7 +65,7 @@ vcsMetadataRepo: VcsMetadataRepository[F], ticketsProjectRepo: ProjectRepository[F], orgRepo: OrganisationRepository[F] -) extends Http4sDsl[F] { +) extends HttpBaseRoute[F] { private val log = LoggerFactory.getLogger(getClass) given org.typelevel.log4cats.LoggerFactory[F] = org.typelevel.log4cats.slf4j.Slf4jFactory.create[F] @@ -325,6 +324,7 @@ organisationAdmins = organisationOwner.flatten .map(account => List(account)) .getOrElse(Nil) ::: possibleOrgaAdmins.getOrElse(Nil) + pageTitle = genPageTitleBase(repositoriesOwnerName)(None) resp <- (owner, organisation) match { case (Some(owner), organisation) => loadRepos(owner).compile.toList.flatMap { repos => @@ -332,7 +332,7 @@ views.html.showRepositories(lang = language)( actionBaseUri, csrf, - s"Smederee/~$repositoriesOwnerName".some, + pageTitle.some, user )(repos, repositoriesOwnerName, organisation, organisationActionBaseUri, organisationAdmins) ) @@ -342,7 +342,7 @@ views.html.showRepositories(lang = language)( actionBaseUri, csrf, - s"Smederee/~$repositoriesOwnerName".some, + pageTitle.some, user )(Nil, repositoriesOwnerName, organisation.some, organisationActionBaseUri, organisationAdmins) ) @@ -350,7 +350,7 @@ NotFound( views.html.errors.userOrOrganisationNotFound(lang = language)( csrf, - s"Smederee/~$repositoriesOwnerName".some, + pageTitle.some, user )(repositoriesOwnerName) ) @@ -397,6 +397,7 @@ case None => Sync[F].pure(None) case Some(repo) => vcsMetadataRepo.findVcsRepositoryParentFork(repo.owner, repo.name) } + pageTitle = genPageTitleBase(repositoryOwnerName)(repositoryName.some) resp <- repo match { case None => NotFound("Repository not found!") case Some(repo) => @@ -405,7 +406,7 @@ actionBaseUri, csrf, linkToTicketService, - s"Smederee/~$repositoryOwnerName/$repositoryName".some, + pageTitle.some, user )(repo, branches) ) @@ -504,6 +505,7 @@ case None => Sync[F].pure(None) case Some(repo) => vcsMetadataRepo.findVcsRepositoryParentFork(repo.owner, repo.name) } + pageTitle = genPageTitleBase(repositoryOwnerName)(repositoryName.some) resp <- repo match { case None => NotFound("Repository not found!") case Some(repo) => @@ -512,7 +514,7 @@ actionBaseUri, csrf, linkToTicketService, - s"Smederee/~$repositoryOwnerName/$repositoryName".some, + pageTitle.some, user )( repo, @@ -645,9 +647,8 @@ case Some(repoId) => vcsMetadataRepo.findVcsRepositoryBranches(repoId).compile.toList case _ => Sync[F].delay(List.empty) } - title <- Sync[F].delay( - s"Smederee/~$repositoryOwnerName/$repositoryName/${filePath.segments.map(_.decoded()).mkString("/")}" - ) + titleBase = genPageTitleBase(repositoryOwnerName)(repositoryName.some) + pageTitle <- Sync[F].delay(s"$titleBase/${filePath.segments.map(_.decoded()).mkString("/")}") resp <- repo match { case None => NotFound("Repository not found!") @@ -664,7 +665,7 @@ csrf, goBackUri.some, linkToTicketService, - title.some, + pageTitle.some, user )(fileContent, renderableContent, listing, repositoryBaseUri, repo, branches) ) @@ -758,6 +759,7 @@ ) ) ) + pageTitle = genPageTitleBase(repositoryOwnerName)(repositoryName.some) |+| " - History of changes" resp <- repo match { case None => NotFound() case Some(repo) => @@ -767,7 +769,7 @@ csrf, goBackUri.some, linkToTicketService, - s"Smederee - History of ~$repositoryOwnerName/$repositoryName".some, + pageTitle.some, user )(patches, next, repositoryBaseUri, repo, branches) ) @@ -912,6 +914,8 @@ ) ) ) + titleBase = genPageTitleBase(repositoryOwnerName)(repositoryName.some) + pageTitle = patch.map(_.name).map(patchName => titleBase |+| " " |+| patchName.toString) resp <- repo match { case None => NotFound() case Some(repo) => @@ -921,7 +925,7 @@ actionBaseUri, csrf, linkToTicketService, - patch.map(_.name.toString), + pageTitle, user )( patch, diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/tickets/LabelRoutes.scala new-smederee/modules/hub/src/main/scala/de/smederee/tickets/LabelRoutes.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/tickets/LabelRoutes.scala 2025-01-11 12:01:12.696589506 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/tickets/LabelRoutes.scala 2025-01-11 12:01:12.696589506 +0000 @@ -24,6 +24,7 @@ import de.smederee.html.* import de.smederee.html.LinkTools.* import de.smederee.hub.Account +import de.smederee.hub.HttpBaseRoute import de.smederee.hub.RequestHelpers.instances.given import de.smederee.i18n.LanguageCode import de.smederee.security.CsrfToken @@ -31,7 +32,6 @@ import de.smederee.tickets.config.* import de.smederee.tickets.forms.types.* import org.http4s.* -import org.http4s.dsl.Http4sDsl import org.http4s.headers.Location import org.http4s.twirl.TwirlInstances.* import org.slf4j.LoggerFactory @@ -51,7 +51,7 @@ configuration: SmedereeTicketsConfiguration, labelRepo: LabelRepository[F], projectRepo: ProjectRepository[F] -) extends Http4sDsl[F] { +) extends HttpBaseRoute[F] { private val log = LoggerFactory.getLogger(getClass) private val linkToHubService = configuration.hub.baseUri @@ -93,6 +93,7 @@ ) ) ) + pageTitle = genPageTitleBase(projectOwnerName)(projectName.some) |+| " Manage your labels." resp <- Ok( views.html.editLabels(lang = language)( projectBaseUri.addSegment("labels"), @@ -100,7 +101,7 @@ linkToHubService, labels, projectBaseUri, - "Manage your project labels.".some, + pageTitle.some, user, project )() @@ -182,6 +183,9 @@ ) ) ) + pageTitle = genPageTitleBase(projectOwnerName)( + projectName.some + ) |+| " Manage your labels." resp <- form match { case Validated.Invalid(errors) => BadRequest( @@ -191,7 +195,7 @@ linkToHubService, labels.getOrElse(List.empty), projectBaseUri, - "Manage your project labels.".some, + pageTitle.some, user.some, project )(formData.withDefaultValue(Chain.empty), FormErrors.fromNec(errors)) @@ -214,7 +218,7 @@ linkToHubService, labels.getOrElse(List.empty), projectBaseUri, - "Manage your project labels.".some, + pageTitle.some, user.some, project )( @@ -367,6 +371,9 @@ // } // ) form <- Sync[F].delay(LabelForm.validate(formData)) + pageTitle = genPageTitleBase(projectOwnerName)( + projectName.some + ) |+| s" - Edit label ${label.name}" resp <- form match { case Validated.Invalid(errors) => BadRequest( @@ -376,7 +383,7 @@ linkToHubService, label, projectBaseUri, - s"Edit label ${label.name}".some, + pageTitle.some, user, project )( @@ -406,7 +413,7 @@ linkToHubService, label, projectBaseUri, - s"Edit label ${label.name}".some, + pageTitle.some, user, project )( @@ -470,6 +477,9 @@ projectBaseUri.addSegment("labels").addSegment(label.name.toString) ) formData <- Sync[F].delay(LabelForm.fromLabel(label)) + pageTitle = genPageTitleBase(projectOwnerName)( + projectName.some + ) |+| s" - Edit label ${label.name}" resp <- Ok( views.html .editLabel()( @@ -478,7 +488,7 @@ linkToHubService, label, projectBaseUri, - s"Edit label ${label.name}".some, + pageTitle.some, user, project )( diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/tickets/MilestoneRoutes.scala new-smederee/modules/hub/src/main/scala/de/smederee/tickets/MilestoneRoutes.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/tickets/MilestoneRoutes.scala 2025-01-11 12:01:12.696589506 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/tickets/MilestoneRoutes.scala 2025-01-11 12:01:12.696589506 +0000 @@ -24,6 +24,7 @@ import de.smederee.html.* import de.smederee.html.LinkTools.* import de.smederee.hub.Account +import de.smederee.hub.HttpBaseRoute import de.smederee.hub.RequestHelpers.instances.given import de.smederee.i18n.LanguageCode import de.smederee.security.CsrfToken @@ -31,7 +32,6 @@ import de.smederee.tickets.config.* import de.smederee.tickets.forms.types.* import org.http4s.* -import org.http4s.dsl.Http4sDsl import org.http4s.headers.Location import org.http4s.twirl.TwirlInstances.* import org.slf4j.LoggerFactory @@ -51,7 +51,7 @@ configuration: SmedereeTicketsConfiguration, milestoneRepo: MilestoneRepository[F], projectRepo: ProjectRepository[F] -) extends Http4sDsl[F] { +) extends HttpBaseRoute[F] { private val log = LoggerFactory.getLogger(getClass) private val linkToHubService = configuration.hub.baseUri @@ -110,6 +110,9 @@ ContentRenderer.render(None)(RenderableContent.Markdown)(content).toOption ) ) + pageTitle = genPageTitleBase(projectOwnerName)( + projectName.some + ) |+| s" Milestone ${milestone.title}" resp <- Ok( views.html.showMilestone(lang = language)( actionUri, @@ -119,7 +122,7 @@ renderedDescription, projectBaseUri, tickets.getOrElse(Nil), - s"Milestone ${milestone.title}".some, + pageTitle.some, user, project ) @@ -164,6 +167,9 @@ ) ) ) + pageTitle = genPageTitleBase(projectOwnerName)( + projectName.some + ) |+| " - Manage your project milestones." resp <- Ok( views.html.editMilestones(lang = language)( projectBaseUri.addSegment("milestones"), @@ -171,7 +177,7 @@ linkToHubService, milestones, projectBaseUri, - "Manage your project milestones.".some, + pageTitle.some, user, project )() @@ -252,6 +258,9 @@ ) ) ) + pageTitle = genPageTitleBase(projectOwnerName)( + projectName.some + ) |+| " - Manage your project milestones." resp <- form match { case Validated.Invalid(errors) => BadRequest( @@ -261,7 +270,7 @@ linkToHubService, milestones.getOrElse(List.empty), projectBaseUri, - "Manage your project milestones.".some, + pageTitle.some, user.some, project )(formData, FormErrors.fromNec(errors)) @@ -292,7 +301,7 @@ linkToHubService, milestones.getOrElse(List.empty), projectBaseUri, - "Manage your project milestones.".some, + pageTitle.some, user.some, project )( @@ -510,6 +519,9 @@ )(_ => milestone.id.validNec) ) form <- Sync[F].delay(MilestoneForm.validate(formData)) + pageTitle = genPageTitleBase(projectOwnerName)( + projectName.some + ) |+| s" - Edit milestone ${milestone.title}" resp <- form match { case Validated.Invalid(errors) => BadRequest( @@ -519,7 +531,7 @@ linkToHubService, milestone, projectBaseUri, - s"Edit milestone ${milestone.title}".some, + pageTitle.some, user, project )( @@ -551,7 +563,7 @@ linkToHubService, milestone, projectBaseUri, - s"Edit milestone ${milestone.title}".some, + pageTitle.some, user, project )( @@ -678,6 +690,9 @@ projectBaseUri.addSegment("milestones").addSegment(milestone.title.toString) ) formData <- Sync[F].delay(MilestoneForm.fromMilestone(milestone)) + pageTitle = genPageTitleBase(projectOwnerName)( + projectName.some + ) |+| s" - Edit milestone ${milestone.title}" resp <- Ok( views.html.editMilestone(lang = language)( actionUri, @@ -685,7 +700,7 @@ linkToHubService, milestone, projectBaseUri, - s"Edit milestone ${milestone.title}".some, + pageTitle.some, user, project )( 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-11 12:01:12.696589506 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/tickets/TicketRoutes.scala 2025-01-11 12:01:12.696589506 +0000 @@ -27,6 +27,7 @@ import de.smederee.html.* import de.smederee.html.LinkTools.* import de.smederee.hub.Account +import de.smederee.hub.HttpBaseRoute import de.smederee.hub.RequestHelpers.instances.given import de.smederee.i18n.LanguageCode import de.smederee.security.CsrfToken @@ -34,7 +35,6 @@ import de.smederee.tickets.config.* import de.smederee.tickets.forms.types.* import org.http4s.* -import org.http4s.dsl.Http4sDsl import org.http4s.headers.Location import org.http4s.twirl.TwirlInstances.* import org.slf4j.LoggerFactory @@ -60,7 +60,7 @@ milestoneRepo: MilestoneRepository[F], projectRepo: ProjectRepository[F], ticketRepo: TicketRepository[F] -) extends Http4sDsl[F] { +) extends HttpBaseRoute[F] { private val log = LoggerFactory.getLogger(getClass) private val linkToHubService = configuration.hub.baseUri @@ -152,6 +152,9 @@ ContentRenderer.render(None)(RenderableContent.Markdown)(content).toOption ) ) + pageTitle = genPageTitleBase(projectOwnerName)( + projectName.some + ) |+| s" #${ticket.number} ${ticket.title}" resp <- Ok( views.html.showTicket(lang = language)( projectBaseUri.addSegment("tickets"), @@ -162,7 +165,7 @@ ticket, renderedTicketContent, projectBaseUri, - ticket.title.toString.some, + pageTitle.some, user, project ) @@ -215,7 +218,9 @@ .filter(_.nonEmpty) .map(stati => s" (${stati.mkString(", ")})") .getOrElse("") - title = s"Smederee/~$projectOwnerName/$projectName - Tickets" |+| ticketStati + pageTitle = genPageTitleBase(projectOwnerName)( + projectName.some + ) |+| " - Tickets" |+| ticketStati resp <- Ok( views.html.showTickets(lang = language)( projectBaseUri.addSegment("tickets"), @@ -224,7 +229,7 @@ tickets, filter, projectBaseUri, - title.some, + pageTitle.some, user, project ) @@ -266,6 +271,9 @@ ) ) ) + pageTitle = genPageTitleBase(projectOwnerName)( + projectName.some + ) |+| " Create a new ticket." resp <- form match { case Validated.Invalid(errors) => BadRequest( @@ -276,7 +284,7 @@ labels, milestones, projectBaseUri, - "Create a new ticket.".some, + pageTitle.some, user.some, project )(formData.withDefaultValue(Chain.empty), FormErrors.fromNec(errors)) @@ -364,6 +372,9 @@ ) ) ) + pageTitle = genPageTitleBase(projectOwnerName)( + projectName.some + ) |+| " Create a new ticket." resp <- form match { case Validated.Invalid(errors) => BadRequest( @@ -374,7 +385,7 @@ labels, milestones, projectBaseUri, - "Create a new ticket.".some, + pageTitle.some, user.some, project )(formData.withDefaultValue(Chain.empty), FormErrors.fromNec(errors)) @@ -466,6 +477,9 @@ ) ) ) + pageTitle = genPageTitleBase(projectOwnerName)( + projectName.some + ) |+| " Create a new ticket." resp <- Ok( views.html.createTicket(lang = language)( projectBaseUri.addSegment("tickets"), @@ -474,7 +488,7 @@ labels, milestones, projectBaseUri, - "Create a new ticket.".some, + pageTitle.some, user.some, project )() @@ -532,6 +546,9 @@ ) ) ) + pageTitle = genPageTitleBase(projectOwnerName)( + projectName.some + ) |+| s"Edit ticket ${ticket.number}" resp <- Ok( views.html.editTicket(lang = language)( projectBaseUri.addSegment("tickets"), @@ -541,7 +558,7 @@ milestones, projectBaseUri, ticket.number, - s"Edit ticket ${ticket.number}".some, + pageTitle.some, user.some, project )(formData.withDefaultValue(Chain.empty), FormErrors.empty) diff -rN -u old-smederee/modules/hub/src/test/scala/de/smederee/hub/HttpBaseRouteTest.scala new-smederee/modules/hub/src/test/scala/de/smederee/hub/HttpBaseRouteTest.scala --- old-smederee/modules/hub/src/test/scala/de/smederee/hub/HttpBaseRouteTest.scala 1970-01-01 00:00:00.000000000 +0000 +++ new-smederee/modules/hub/src/test/scala/de/smederee/hub/HttpBaseRouteTest.scala 2025-01-11 12:01:12.700589515 +0000 @@ -0,0 +1,55 @@ +/* + * 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 cats.syntax.all.* +import de.smederee.hub.Generators.genValidVcsRepository +import de.smederee.hub.Generators.given +import de.smederee.tickets.Generators.genProject +import de.smederee.tickets.Project + +import munit.* + +import org.scalacheck.* +import org.scalacheck.Prop.* + +final class HttpBaseRouteTest extends ScalaCheckSuite with HttpBaseRouteHelpers { + private given Arbitrary[Project] = Arbitrary(genProject) + private given Arbitrary[VcsRepository] = Arbitrary(genValidVcsRepository) + + property("genPageTitleBase must work correctly for only usernames") { + forAll { (account: Account) => + val expected = s"~${account.name}" + assertEquals(genPageTitleBase(account.name)(None), expected) + } + } + + property("genPageTitleBase must work correctly for user and repository names") { + forAll { (account: Account, repo: VcsRepository) => + val expected = s"~${account.name}/${repo.name}" + assertEquals(genPageTitleBase(account.name)(repo.name.some), expected) + } + } + + property("genPageTitleBase must work correctly for user and project names") { + forAll { (account: Account, project: Project) => + val expected = s"~${account.name}/${project.name}" + assertEquals(genPageTitleBase(account.name)(project.name.some), expected) + } + } +}