~jan0sch/smederee
Showing details for patch 8c73e70847c00fb2b9d2747392dda224cd49af81.
diff -rN -u old-smederee/build.sbt new-smederee/build.sbt --- old-smederee/build.sbt 2025-02-03 02:00:50.923480775 +0000 +++ new-smederee/build.sbt 2025-02-03 02:00:50.923480775 +0000 @@ -136,7 +136,7 @@ lazy val hub = project .in(file("modules/hub")) - .dependsOn(email, i18n, security, twirl) + .dependsOn(darcs, email, i18n, security, twirl) .enablePlugins(AutomateHeaderPlugin, SbtTwirl) .configs(IntegrationTest) .settings(commonSettings) diff -rN -u old-smederee/modules/hub/src/main/resources/messages_en.properties new-smederee/modules/hub/src/main/resources/messages_en.properties --- old-smederee/modules/hub/src/main/resources/messages_en.properties 2025-02-03 02:00:50.923480775 +0000 +++ new-smederee/modules/hub/src/main/resources/messages_en.properties 2025-02-03 02:00:50.923480775 +0000 @@ -35,6 +35,7 @@ global.login=Login global.logout=Logout global.navbar.top.logo=Smederee +global.navbar.top.repository.new=New Repository global.privacy=Privacy Policy global.signup=Sign Up global.terms.of.use=Terms of Use diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/config/SmedereeHubConfig.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/config/SmedereeHubConfig.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/config/SmedereeHubConfig.scala 2025-02-03 02:00:50.923480775 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/config/SmedereeHubConfig.scala 2025-02-03 02:00:50.923480775 +0000 @@ -134,6 +134,10 @@ def fromString(source: String): Option[DirectoryPath] = Try(Paths.get(source)).toOption } +extension (dir: DirectoryPath) { + def toPath: Path = dir +} + opaque type FailedAttempts = Int object FailedAttempts { 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-02-03 02:00:50.923480775 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/HubServer.scala 2025-02-03 02:00:50.923480775 +0000 @@ -15,6 +15,7 @@ import cats.syntax.all._ import com.comcast.ip4s._ import com.typesafe.config._ +import de.smederee.darcs._ import de.smederee.email.SimpleJavaMailMiddleware import de.smederee.hub.config._ import de.smederee.security._ @@ -69,6 +70,7 @@ configuration.service.authentication.timeouts ) ) + darcsWrapper = new DarcsCommands[IO](configuration.service.darcs.executable) emailMiddleware = new SimpleJavaMailMiddleware(configuration.service.email) authenticationRoutes = new AuthenticationRoutes[IO]( cryptoClock, @@ -76,11 +78,12 @@ authenticationRepo, signAndValidate ) - signUpRepo = new DoobieSignupRepository[IO](transactor) - signUpRoutes = new SignupRoutes[IO](configuration.service.signup, signUpRepo) - landingPages = new LandingPageRoutes[IO]() + signUpRepo = new DoobieSignupRepository[IO](transactor) + signUpRoutes = new SignupRoutes[IO](configuration.service.signup, signUpRepo) + landingPages = new LandingPageRoutes[IO]() + vcsRepoRoutes = new VcsRepositoryRoutes[IO](configuration.service.darcs, darcsWrapper) protectedRoutesWithFallThrough = authenticationWithFallThrough( - authenticationRoutes.protectedRoutes <+> signUpRoutes.protectedRoutes <+> landingPages.protectedRoutes + authenticationRoutes.protectedRoutes <+> signUpRoutes.protectedRoutes <+> vcsRepoRoutes.protectedRoutes <+> landingPages.protectedRoutes ) globalRoutes = Router( Constants.assetsPath.path.toAbsolute.toString -> assetsRoutes, diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/NewVcsRepositoryForm.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/NewVcsRepositoryForm.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/NewVcsRepositoryForm.scala 1970-01-01 00:00:00.000000000 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/NewVcsRepositoryForm.scala 2025-02-03 02:00:50.923480775 +0000 @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022 Wegtam GmbH + * + * Business Source License 1.1 - See file LICENSE for details! + * + * Change Date: 2025-06-21 + * Change License: GNU AFFERO GENERAL PUBLIC LICENSE Version 3 + */ + +package de.smederee.hub + +import cats.data._ +import cats.syntax.all._ +import de.smederee.hub.forms._ +import de.smederee.hub.forms.types._ + +/** Data container for the form to create a new VCS repository. + * + * @param name + * The name of the repository which will be created as a folder on disk. It must not be a duplicate of an + * already existing one. + */ +final case class NewVcsRepositoryForm(name: VcsRepositoryName) + +object NewVcsRepositoryForm extends FormValidator[NewVcsRepositoryForm] { + val fieldName: FormField = FormField("name") + + override def validate(data: Map[String, String]): ValidatedNel[FormErrors, NewVcsRepositoryForm] = { + val name: ValidatedNel[FormErrors, VcsRepositoryName] = data + .get(fieldName) + .fold(FormFieldError("No repository name given!").invalidNel)(s => + VcsRepositoryName.from(s).fold(FormFieldError("Invalid repository name!").invalidNel)(_.validNel) + ) + .leftMap(es => NonEmptyList.of(Map(fieldName -> es.toList))) + name.map(NewVcsRepositoryForm.apply) + } +} 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 1970-01-01 00:00:00.000000000 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala 2025-02-03 02:00:50.923480775 +0000 @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2022 Wegtam GmbH + * + * Business Source License 1.1 - See file LICENSE for details! + * + * Change Date: 2025-06-21 + * Change License: GNU AFFERO GENERAL PUBLIC LICENSE Version 3 + */ + +package de.smederee.hub + +import cats.data._ +import cats.effect._ +import cats.syntax.all._ +import de.smederee.darcs._ +import de.smederee.hub.RequestHelpers.instances.given +import de.smederee.hub.config._ +import org.http4s._ +import org.http4s.dsl.Http4sDsl +import org.http4s.implicits._ +import org.http4s.twirl.TwirlInstances._ + +/** Routes for handling VCS repositories, including creation, management and public serving. + * + * @param config + * The configuration for darcs related operations. + * @param darcs + * A class providing darcs VCS operations. + * @tparam F + * A higher kinded type providing needed functionality, which is usually an IO monad like Async or Sync. + */ +final class VcsRepositoryRoutes[F[_]: Async](config: DarcsConfiguration, darcs: DarcsCommands[F]) + extends Http4sDsl[F] { + + private val parseCreateRepositoryForm: AuthedRoutes[Account, F] = AuthedRoutes.of { + case ar @ POST -> Root / "repo" / "create" as user => + for { + csrf <- Sync[F].delay(ar.req.getCsrfToken) + resp <- NotImplemented.apply("Not yet implemented!") + } yield resp + } + + private val showCreateRepositoryForm: AuthedRoutes[Account, F] = AuthedRoutes.of { + case ar @ GET -> Root / "repo" / "create" as user => + for { + csrf <- Sync[F].delay(ar.req.getCsrfToken) + output <- darcs.initialize(config.repositoriesDirectory.toPath)("TEST-REPO")(Chain.empty) + resp <- Ok(output.toString) + } yield resp + } + + val protectedRoutes = parseCreateRepositoryForm <+> showCreateRepositoryForm + +} diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepository.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepository.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepository.scala 1970-01-01 00:00:00.000000000 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepository.scala 2025-02-03 02:00:50.923480775 +0000 @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2022 Wegtam GmbH + * + * Business Source License 1.1 - See file LICENSE for details! + * + * Change Date: 2025-06-21 + * Change License: GNU AFFERO GENERAL PUBLIC LICENSE Version 3 + */ + +package de.smederee.hub + +opaque type VcsRepositoryName = String +object VcsRepositoryName { + + /** Create an instance of VcsRepositoryName from the given String type. + * + * @param source + * An instance of type String which will be returned as a VcsRepositoryName. + * @return + * The appropriate instance of VcsRepositoryName. + */ + def apply(source: String): VcsRepositoryName = source + + /** Try to create an instance of VcsRepositoryName from the given String. + * + * @param source + * A String that should fulfil the requirements to be converted into a VcsRepositoryName. + * @return + * An option to the successfully converted VcsRepositoryName. + */ + def from(source: String): Option[VcsRepositoryName] = { + val filterNull = Option(source) + // Check for not allowed characters. + if (filterNull.exists(_.contains("/")) || filterNull.exists(_.contains('\u0000'))) + None + else + filterNull + } +} + +/** Data about a VCS respository. + * + * @param name + * The name of the repository. + * @param owner + * The account that owns the repository. + */ +final case class VcsRepository(name: VcsRepositoryName, owner: Account) diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/index.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/index.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/index.scala.html 2025-02-03 02:00:50.923480775 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/index.scala.html 2025-02-03 02:00:50.923480775 +0000 @@ -18,23 +18,7 @@ </head> <body> <navbar class="header navbar" id="navbar-top"> - <nav class="home-menu pure-menu pure-menu-horizontal pure-menu-fixed"> - <a class="logo pure-menu-heading" href="@pathPrefix/">@Messages("global.navbar.top.logo")</a> - - <ul class="pure-menu-list"> - @if(user.nonEmpty) { - <li class="pure-menu-item"> - <form action="/logout" method="POST" accept-charset="UTF-8" class="inline"> - @csrfToken(csrf) - <button class="pure-menu-link" type="submit">@Messages("global.logout")</button> - </form> - </li> - } else { - <li class="pure-menu-item"><a href="/login" class="pure-menu-link">@Messages("global.login")</a></li> - <li class="pure-menu-item"><a href="/signup" class="pure-menu-link">@Messages("global.signup")</a></li> - } - </ul> - </nav> + @navbar(lang, pathPrefix)(csrf, Option("pure-menu-fixed"), user) </navbar> <div class="splash-container"> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/main.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/main.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/main.scala.html 2025-02-03 02:00:50.923480775 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/main.scala.html 2025-02-03 02:00:50.923480775 +0000 @@ -13,7 +13,7 @@ @customHeaders </head> <body> - <navbar class="header navbar" id="navbar-top">@navbar(lang, pathPrefix)(csrf, user)</navbar> + <navbar class="header navbar" id="navbar-top">@navbar(lang, pathPrefix)(csrf, None, user)</navbar> <main class="content-wrapper"> @content <footer class="footer">@customFooters</footer> diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/navbar.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/navbar.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/navbar.scala.html 2025-02-03 02:00:50.923480775 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/navbar.scala.html 2025-02-03 02:00:50.923480775 +0000 @@ -1,19 +1,20 @@ -@(lang: LanguageCode, pathPrefix: Option[Uri])(csrf: Option[CsrfToken] = None, user: Option[Account] = None) +@(lang: LanguageCode, pathPrefix: Option[Uri])(csrf: Option[CsrfToken] = None, extraCss: Option[String] = None, user: Option[Account] = None) @defining(lang.toLocale) { implicit locale => -<nav class="home-menu pure-menu pure-menu-horizontal"> +<nav class="home-menu pure-menu pure-menu-horizontal @extraCss"> <a class="logo pure-menu-heading" href="@pathPrefix/">@Messages("global.navbar.top.logo")</a> <ul class="pure-menu-list"> @if(user.nonEmpty) { + <li class="pure-menu-item"><a class="pure-menu-link" href="@pathPrefix/repo/create">+ @Messages("global.navbar.top.repository.new")</a></li> <li class="pure-menu-item"> - <form action="/logout" method="POST" accept-charset="UTF-8" class="inline"> + <form action="@pathPrefix/logout" method="POST" accept-charset="UTF-8" class="pure-form"> @csrfToken(csrf) - <button class="pure-menu-link" type="submit">@Messages("global.logout")</button> + <button class="pure-button pure-button-primary" type="submit">@Messages("global.logout")</button> </form> </li> } else { - <li class="pure-menu-item"><a href="/login" class="pure-menu-link">@Messages("global.login")</a></li> - <li class="pure-menu-item"><a href="/signup" class="pure-menu-link">@Messages("global.signup")</a></li> + <li class="pure-menu-item"><a class="pure-menu-link" href="@pathPrefix/login">@Messages("global.login")</a></li> + <li class="pure-menu-item"><a class="pure-menu-link" href="@pathPrefix/signup">@Messages("global.signup")</a></li> } </ul> </nav>