~jan0sch/smederee

Showing details for patch e45f55e4bc59fdb6f1fbce591c0c25f5c445d433.
2022-08-14 (Sun), 11:57 AM - Jens Grassel - e45f55e4bc59fdb6f1fbce591c0c25f5c445d433

Refactoring: extract common logic into helper functions

Showing all repositories and individual repositories for guests and logged 
on users shares common logic which is now placed inside a helper functions.
Summary of changes
2 files modified with 148 lines added and 120 lines removed
  • modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala with 146 added and 118 removed lines
  • modules/hub/src/main/twirl/de/smederee/hub/views/showRepository.scala.html with 2 added and 2 removed lines
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-02-02 16:52:51.162545340 +0000
+++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala	2025-02-02 16:52:51.162545340 +0000
@@ -48,6 +48,134 @@
   private val createRepoPath  = uri"/repo/create"
   private val MaximumFileSize = 131072L // TODO Move to configuration directive.
 
+  /** Logic for rendering a list of all repositories visible to the given user account.
+    *
+    * @param csrf
+    *   An optional CSRF-Token that shall be used.
+    * @param user
+    *   An optional user account for whom the list of repositories shall be rendered.
+    * @return
+    *   An HTTP response containing the rendered HTML.
+    */
+  private def doShowAllRepositories(csrf: Option[CsrfToken])(user: Option[Account]): F[Response[F]] =
+    for {
+      repos <- vcsMetadataRepo
+        .listAllRepositories(user)(VcsMetadataRepositoriesOrdering.NameAscending)
+        .compile
+        .toList
+      actionBaseUri <- Sync[F].delay(uri"projects")
+      resp <- Ok.apply(
+        views.html.showAllRepositories()(actionBaseUri, csrf, s"Smederee - Projects".some, user)(repos)
+      )
+    } yield resp
+
+  /** Logic for rendering the content of a repository directory or file visible to the given user account.
+    *
+    * @param csrf
+    *   An optional CSRF-Token that shall be used.
+    * @param user
+    *   An optional user account for whom the list of repositories shall be rendered.
+    * @param repositoryOwnerName
+    *   The name of the user who owns the repository.
+    * @param repositoryName
+    *   The actual name of the repository.
+    * @param filePath
+    *   An URI path which describes the path the the requested part of the repository (empty or `/` for the
+    *   root directory of the repo).
+    * @return
+    *   An HTTP response containing the rendered HTML.
+    */
+  private def doShowRepositoryFiles(csrf: Option[CsrfToken])(
+      user: Option[Account]
+  )(repositoryOwnerName: Username, repositoryName: VcsRepositoryName)(filePath: Uri.Path): F[Response[F]] =
+    for {
+      owner <- vcsMetadataRepo.findVcsRepositoryOwner(repositoryOwnerName)
+      loadedRepo <- owner match {
+        case None        => Sync[F].pure(None)
+        case Some(owner) => vcsMetadataRepo.findVcsRepository(owner, repositoryName)
+      }
+      // TODO Replace with whatever we implement as proper permission model. ;-)
+      repo = user match {
+        case None => loadedRepo.filter(r => r.isPrivate === false)
+        case Some(user) =>
+          loadedRepo.filter(r => r.isPrivate === false || r.owner === user.toVcsRepositoryOwner)
+      }
+      requestedFilePath <- repo
+        .traverse(_ =>
+          Sync[F].delay(
+            os.Path(
+              Paths.get(
+                config.repositoriesDirectory.toPath.toString,
+                repositoryOwnerName.toString,
+                repositoryName.toString,
+                filePath.segments.mkString("/")
+              )
+            )
+          )
+        )
+      viewFile <- Sync[F].delay(requestedFilePath.map(os.isFile).getOrElse(false))
+      listing <-
+        requestedFilePath match {
+          case None                => Sync[F].delay(IndexedSeq.empty)
+          case Some(_) if viewFile => Sync[F].delay(IndexedSeq.empty)
+          case Some(path)          => doListFiles(path)
+        }
+      content <-
+        requestedFilePath match {
+          case None => Sync[F].delay(IndexedSeq.empty)
+          case Some(path) =>
+            if (viewFile)
+              for {
+                size <- Sync[F].delay(os.size(path))
+                stream <-
+                  if (size <= MaximumFileSize)
+                    Sync[F].delay(os.read.lines.stream(path))
+                  else
+                    Sync[F].delay(os.Generator("File is too big!"))
+                lines <- Sync[F].delay(stream.toVector)
+              } yield lines
+            else
+              Sync[F].delay(IndexedSeq.empty)
+        }
+      fileContent <- Sync[F].delay {
+        if (content.isEmpty)
+          None
+        else
+          Option(content.mkString("\n"))
+      }
+      repositoryBaseUri <- Sync[F].delay(
+        Uri(path =
+          Uri.Path.Root |+| Uri.Path(
+            Vector(Uri.Path.Segment(s"~$repositoryOwnerName"), Uri.Path.Segment(repositoryName.toString))
+          )
+        )
+      )
+      actionBaseUri <- Sync[F].delay(Uri(path = repositoryBaseUri.path |+| filePath))
+      goBackUri <- Sync[F].delay(
+        Uri(path = repositoryBaseUri.path |+| Uri.Path(filePath.segments.reverse.drop(1).reverse))
+      )
+      resp <-
+        repo match {
+          case None => NotFound("Repository not found!")
+          case Some(_) =>
+            if (
+              filePath.segments.mkString
+                .startsWith("_darcs") || filePath.segments.mkString.startsWith("/_darcs")
+            )
+              NotFound("File not found!")
+            else
+              Ok(
+                views.html.showRepository()(
+                  actionBaseUri,
+                  csrf,
+                  Option(goBackUri),
+                  s"Smederee/~$repositoryOwnerName/$repositoryName".some,
+                  user
+                )(fileContent, listing, repositoryBaseUri, repositoryName)
+              )
+        }
+    } yield resp
+
   /** List walk the given directory at the first level and return all found files and directories and their
     * stats sorted by directory first and by name second. If the given path is _not_ a directory then no
     * traversal is done and an empty list is returned.
@@ -57,7 +185,7 @@
     * @return
     *   A list of tuples containing a relative path and the related stats.
     */
-  private def listFiles(directory: os.Path): F[IndexedSeq[(os.RelPath, os.StatInfo)]] =
+  private def doListFiles(directory: os.Path): F[IndexedSeq[(os.RelPath, os.StatInfo)]] =
     for {
       isDirectory <- Sync[F].delay(os.isDir(directory))
       listing <-
@@ -173,14 +301,7 @@
     case ar @ GET -> Root / "projects" as user =>
       for {
         csrf <- Sync[F].delay(ar.req.getCsrfToken)
-        repos <- vcsMetadataRepo
-          .listAllRepositories(user.some)(VcsMetadataRepositoriesOrdering.NameAscending)
-          .compile
-          .toList
-        actionBaseUri <- Sync[F].delay(uri"projects")
-        resp <- Ok.apply(
-          views.html.showAllRepositories()(actionBaseUri, csrf, s"Smederee - Projects".some, user.some)(repos)
-        )
+        resp <- doShowAllRepositories(csrf)(user.some)
       } yield resp
   }
 
@@ -188,14 +309,7 @@
     case req @ GET -> Root / "projects" =>
       for {
         csrf <- Sync[F].delay(req.getCsrfToken)
-        repos <- vcsMetadataRepo
-          .listAllRepositories(None)(VcsMetadataRepositoriesOrdering.NameAscending)
-          .compile
-          .toList
-        actionBaseUri <- Sync[F].delay(uri"projects")
-        resp <- Ok.apply(
-          views.html.showAllRepositories()(actionBaseUri, csrf, s"Smederee - Projects".some, None)(repos)
-        )
+        resp <- doShowAllRepositories(csrf)(None)
       } yield resp
   }
 
@@ -246,109 +360,23 @@
       } yield resp
   }
 
-  private val showRepository: AuthedRoutes[Account, F] = AuthedRoutes.of {
-    case ar @ GET -> Root / UsernamePathParameter(repositoryOwner) / VcsRepositoryNamePathParameter(
+  private val showRepositoryFiles: AuthedRoutes[Account, F] = AuthedRoutes.of {
+    case ar @ GET -> UsernamePathParameter(repositoryOwnerName) /: VcsRepositoryNamePathParameter(
           repositoryName
-        ) as user =>
+        ) /: filePath as user =>
       for {
         csrf <- Sync[F].delay(ar.req.getCsrfToken)
-        directory <- Sync[F].delay(
-          os.Path(
-            Paths.get(
-              config.repositoriesDirectory.toPath.toString,
-              repositoryOwner.toString,
-              repositoryName.toString
-            )
-          )
-        )
-        listing <- listFiles(directory)
-        repositoryBaseUri <- Sync[F].delay(
-          Uri(path =
-            Uri.Path.Root |+| Uri.Path(
-              Vector(Uri.Path.Segment(s"~$repositoryOwner"), Uri.Path.Segment(repositoryName.toString))
-            )
-          )
-        )
-        resp <- Ok(
-          views.html.showRepository()(
-            repositoryBaseUri,
-            csrf,
-            None,
-            s"Smederee/~$repositoryOwner/$repositoryName".some,
-            user
-          )(None, listing, repositoryBaseUri, repositoryName)
-        )
+        resp <- doShowRepositoryFiles(csrf)(user.some)(repositoryOwnerName, repositoryName)(filePath)
       } yield resp
   }
 
-  private val showRepositoryFiles: AuthedRoutes[Account, F] = AuthedRoutes.of {
-    case ar @ GET -> UsernamePathParameter(repositoryOwner) /: VcsRepositoryNamePathParameter(
+  private val showRepositoryFilesForGuests: HttpRoutes[F] = HttpRoutes.of {
+    case req @ GET -> UsernamePathParameter(repositoryOwnerName) /: VcsRepositoryNamePathParameter(
           repositoryName
-        ) /: filePath as user =>
+        ) /: filePath =>
       for {
-        csrf <- Sync[F].delay(ar.req.getCsrfToken)
-        requestedFilePath <- Sync[F].delay(
-          os.Path(
-            Paths.get(
-              config.repositoriesDirectory.toPath.toString,
-              repositoryOwner.toString,
-              repositoryName.toString,
-              filePath.segments.mkString("/")
-            )
-          )
-        )
-        viewFile <- Sync[F].delay(os.isFile(requestedFilePath))
-        listing <-
-          if (viewFile)
-            Sync[F].delay(IndexedSeq.empty)
-          else
-            listFiles(requestedFilePath)
-        content <-
-          if (viewFile)
-            for {
-              size <- Sync[F].delay(os.size(requestedFilePath))
-              stream <-
-                if (size <= MaximumFileSize)
-                  Sync[F].delay(os.read.lines.stream(requestedFilePath))
-                else
-                  Sync[F].delay(os.Generator("File is too big!"))
-              lines <- Sync[F].delay(stream.toVector)
-            } yield lines
-          else
-            Sync[F].delay(IndexedSeq.empty)
-        fileContent <- Sync[F].delay {
-          if (content.isEmpty)
-            None
-          else
-            Option(content.mkString("\n"))
-        }
-        repositoryBaseUri <- Sync[F].delay(
-          Uri(path =
-            Uri.Path.Root |+| Uri.Path(
-              Vector(Uri.Path.Segment(s"~$repositoryOwner"), Uri.Path.Segment(repositoryName.toString))
-            )
-          )
-        )
-        actionBaseUri <- Sync[F].delay(Uri(path = repositoryBaseUri.path |+| filePath))
-        goBackUri <- Sync[F].delay(
-          Uri(path = repositoryBaseUri.path |+| Uri.Path(filePath.segments.reverse.drop(1).reverse))
-        )
-        resp <-
-          if (
-            filePath.segments.mkString
-              .startsWith("_darcs") || filePath.segments.mkString.startsWith("/_darcs")
-          )
-            NotFound()
-          else
-            Ok(
-              views.html.showRepository()(
-                actionBaseUri,
-                csrf,
-                Option(goBackUri),
-                s"Smederee/~$repositoryOwner/$repositoryName".some,
-                user
-              )(fileContent, listing, repositoryBaseUri, repositoryName)
-            )
+        csrf <- Sync[F].delay(req.getCsrfToken)
+        resp <- doShowRepositoryFiles(csrf)(None)(repositoryOwnerName, repositoryName)(filePath)
       } yield resp
   }
 
@@ -400,8 +428,8 @@
   }
 
   val protectedRoutes =
-    showAllRepositories <+> showRepositories <+> parseCreateRepositoryForm <+> showCreateRepositoryForm <+> showRepositoryHistory <+> showRepositoryFiles <+> showRepository
+    showAllRepositories <+> showRepositories <+> parseCreateRepositoryForm <+> showCreateRepositoryForm <+> showRepositoryHistory <+> showRepositoryFiles
 
-  val routes = showAllRepositoriesForGuests
+  val routes = showAllRepositoriesForGuests <+> showRepositoryFilesForGuests
 
 }
diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepository.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepository.scala.html
--- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepository.scala.html	2025-02-02 16:52:51.162545340 +0000
+++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepository.scala.html	2025-02-02 16:52:51.162545340 +0000
@@ -1,5 +1,5 @@
-@(lang: LanguageCode = LanguageCode("en"), pathPrefix: Option[Uri] = None)(actionBaseUri: Uri, csrf: Option[CsrfToken] = None, goBackUri: Option[Uri] = None, title: Option[String] = None, user: Account)(fileContent: Option[String], listing: IndexedSeq[(os.RelPath, os.StatInfo)], repositoryBaseUri: Uri, repositoryName: VcsRepositoryName)
-@main(lang, pathPrefix)()(csrf, title, user.some) {
+@(lang: LanguageCode = LanguageCode("en"), pathPrefix: Option[Uri] = None)(actionBaseUri: Uri, csrf: Option[CsrfToken] = None, goBackUri: Option[Uri] = None, title: Option[String] = None, user: Option[Account])(fileContent: Option[String], listing: IndexedSeq[(os.RelPath, os.StatInfo)], repositoryBaseUri: Uri, repositoryName: VcsRepositoryName)
+@main(lang, pathPrefix)()(csrf, title, user) {
 @defining(lang.toLocale) { implicit locale =>
   <div class="content">
     <div class="pure-g">