~jan0sch/smederee

Showing details for patch 8c73e70847c00fb2b9d2747392dda224cd49af81.
2022-07-29 (Fri), 12:22 PM - Jens Grassel - 8c73e70847c00fb2b9d2747392dda224cd49af81

darcs: use darcs module in hub, PoC

Summary of changes
3 files added
  • modules/hub/src/main/scala/de/smederee/hub/NewVcsRepositoryForm.scala
  • modules/hub/src/main/scala/de/smederee/hub/VcsRepository.scala
  • modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala
7 files modified with 22 lines added and 29 lines removed
  • build.sbt with 1 added and 1 removed lines
  • modules/hub/src/main/resources/messages_en.properties with 1 added and 0 removed lines
  • modules/hub/src/main/scala/de/smederee/hub/HubServer.scala with 7 added and 4 removed lines
  • modules/hub/src/main/scala/de/smederee/hub/config/SmedereeHubConfig.scala with 4 added and 0 removed lines
  • modules/hub/src/main/twirl/de/smederee/hub/views/index.scala.html with 1 added and 17 removed lines
  • modules/hub/src/main/twirl/de/smederee/hub/views/main.scala.html with 1 added and 1 removed lines
  • modules/hub/src/main/twirl/de/smederee/hub/views/navbar.scala.html with 7 added and 6 removed lines
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>