~jan0sch/smederee

Showing details for patch 731a0117556ac2e26066eb52c1839d7c3bc71d95.
2022-08-15 (Mon), 2:00 PM - Jens Grassel - 731a0117556ac2e26066eb52c1839d7c3bc71d95

VCS: Proof of Concept for cloning via HTTP

The current approach enables cloning via URI.darcs but we should consider
changing this to support simply cloning via URI for project.
Summary of changes
1 files modified with 48 lines added and 1 lines removed
  • modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala with 48 added and 1 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 14:39:17.086094661 +0000
+++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala	2025-02-02 14:39:17.086094661 +0000
@@ -348,6 +348,39 @@
           Sync[F].delay(IndexedSeq.empty)
     } yield listing
 
+  private val cloneRepository: HttpRoutes[F] = HttpRoutes.of {
+    case req @ GET -> UsernamePathParameter(repositoryOwnerName) /: VcsRepositoryClonePathParameter(
+          repositoryName
+        ) /: filePath =>
+      for {
+        csrf  <- Sync[F].delay(req.getCsrfToken)
+        owner <- vcsMetadataRepo.findVcsRepositoryOwner(repositoryOwnerName)
+        loadedRepo <- owner match {
+          case None        => Sync[F].pure(None)
+          case Some(owner) => vcsMetadataRepo.findVcsRepository(owner, repositoryName)
+        }
+        // Cloning via HTTP protocol is public only, so we don't need more sophisticated checks here.
+        repo = loadedRepo.filter(r => r.isPrivate === false)
+        requestedFilePath <- Sync[F].delay(
+          repo.map(_ =>
+            fs2.io.file.Path.fromNioPath(
+              Paths.get(
+                config.repositoriesDirectory.toPath.toString,
+                repositoryOwnerName.toString,
+                repositoryName.toString,
+                filePath.segments.mkString("/")
+              )
+            )
+          )
+        )
+        _ <- Sync[F].delay(log.info(s"Repository $repo ($requestedFilePath) is cloned."))
+        resp <- (repo, requestedFilePath) match {
+          case (Some(repo), Some(path)) => StaticFile.fromPath(path, req.some).getOrElseF(NotFound())
+          case _                        => NotFound()
+        }
+      } yield resp
+  }
+
   private val parseCreateRepositoryForm: AuthedRoutes[Account, F] = AuthedRoutes.of {
     case ar @ POST -> Root / "repo" / "create" as user =>
       ar.req.decodeStrict[F, UrlForm] { urlForm =>
@@ -563,8 +596,22 @@
     showAllRepositories <+> showRepositories <+> parseCreateRepositoryForm <+> showCreateRepositoryForm <+> showRepositoryOverview <+> showRepositoryHistory <+> showRepositoryFiles
 
   val routes =
-    showAllRepositoriesForGuests <+> showRepositoryOverviewForGuests <+> showRepositoryHistoryForGuests <+> showRepositoryFilesForGuests
+    cloneRepository <+> showAllRepositoriesForGuests <+> showRepositoryOverviewForGuests <+> showRepositoryHistoryForGuests <+> showRepositoryFilesForGuests
 
 }
 
+/** Extractor for an optional query parameter we use in our history route.
+  */
 object HistoryFromQueryParameter extends OptionalQueryParamDecoderMatcher[Int]("from")
+
+/** A path parameter extractor to get the vcs repository name for a clone operation.
+  */
+object VcsRepositoryClonePathParameter {
+  def unapply(str: String): Option[VcsRepositoryName] =
+    Option(str).flatMap { string =>
+      if (string.endsWith(".darcs"))
+        VcsRepositoryName.from(string.reverse.drop(6).reverse)
+      else
+        None
+    }
+}