GitFOSS
feat(repository): add support for all kind of images to RepositoryBrowserView
+ 115
- 13
@@ -1,5 +1,5 @@
 {
-  "_generatedAtUnix": 1663770947993,
+  "_generatedAtUnix": 1663773368816,
   "_hashAlgorithm": "sha1",
   "_version": 2,
   "islands": {

...
@@ -50,7 +50,7 @@
       "pathSource": "./app/views/auth/RegisterView.tsx"
     },
     "RepositoryBrowserView": {
-      "hash": "2d4e879f98bd3a8316e1e86bd97baee50e515b2e",
+      "hash": "932ee3dcdabd946d727876e494b58264c3043069",
       "pathSource": "./app/views/repository/RepositoryBrowserView.tsx"
     },
     "RepositoryCommitsLogView": {

app/controllers/repository/getRepositoryBrowserView.ts
@@ -82,19 +82,24 @@ const getRepositoryBrowserView: ReqHandler = async (request, reply) => {
     });
   }
 
-  let fileContent = await repoService.getRepositoryFileContent(repo, path, ref);
+  let linguistInfos = await request.codeAnalysisService.getLinguistFileInfos(
+    path
+  );
+
+  let fileContent =
+    linguistInfos.type === "image"
+      ? await repoService.getRepositoryFileContentBase64(repo, path, ref)
+      : await repoService.getRepositoryFileContent(repo, path, ref);
 
   if (fileContent == null) {
     return reply.status(404).callNotFound();
   }
 
-  const linguistInfos = await request.codeAnalysisService.getLinguistFileInfos(
+  linguistInfos = await request.codeAnalysisService.getLinguistFileInfos(
     path,
     fileContent.content
   );
 
-  fileContent.mimeType = linguistInfos.mimeType;
-
   return reqHandler<RepositoryBrowserViewProps>(RepositoryBrowserView.name, {
     currentUser,
     fileContent,

app/services/repository/getRepositoryFileContent.ts
@@ -56,7 +56,7 @@ const makeGetRepositoryFileContent: ServiceMethodFactory<
           reject(new Error(Buffer.from(data).toString("utf-8")));
         });
         gitCatFileProcess.stdout.on("close", () => {
-          resolve(buffer.join(""));
+          resolve(Buffer.from(buffer.join("")).toString("utf-8"));
         });
       });
 

new file
app/services/repository/getRepositoryFileContentBase64.ts
@@ -0,0 +1,86 @@
+// std
+import { existsSync } from "node:fs";
+import { spawn } from "node:child_process";
+// 1st-party
+import type { ServiceMethodFactory } from "@ethicdevs/react-monolith";
+// generated via script[generate:prisma]
+import type { Repository } from "@prisma/client";
+// app
+import { Env } from "../../env";
+// service
+import type { RepositoryFileContent } from "../../types";
+import type { RepositoryServiceDeps } from "./types";
+
+const makeGetRepositoryFileContentBase64: ServiceMethodFactory<
+  RepositoryServiceDeps,
+  [Repository, string, string | undefined],
+  Promise<null | RepositoryFileContent>
+> = ({ request }) => {
+  return async (repo, path, ref = "HEAD") => {
+    if (path.endsWith("/")) {
+      throw new Error("Could not retrieve file content for a folder.");
+    }
+
+    const parentOrg = await request.prisma.organization.findUnique({
+      where: {
+        id: repo.organizationId,
+      },
+    });
+
+    if (parentOrg == null) {
+      throw new Error(
+        `Could not find the parent organization for project "${repo.slug}".`
+      );
+    }
+
+    try {
+      const repoPath = `${Env.GIT_REPOSITORIES_ROOT}/${parentOrg.slug}/${repo.slug}.git`;
+      if (existsSync(repoPath) === false) {
+        throw new Error(
+          `Could not find a valid git repository at: ${repoPath}`
+        );
+      }
+
+      const gitCatFileProcess = spawn(
+        "git",
+        ["cat-file", "-p", `${ref}:${path}`],
+        {
+          cwd: repoPath,
+        }
+      );
+
+      // pipe gitCatFileProcess stdout into base64 stdin
+      const base64FileProcess = spawn("base64", [], {
+        cwd: repoPath,
+        stdio: [gitCatFileProcess.stdout, "pipe"],
+      });
+
+      const gitCatFileResult = await new Promise<string>((resolve, reject) => {
+        let buffer = [] as string[];
+        base64FileProcess.stdout?.on("data", (data) => buffer.push(data));
+        base64FileProcess.stderr?.on("data", (data) => {
+          reject(new Error(Buffer.from(data).toString("utf-8")));
+        });
+        base64FileProcess.stdout?.on("close", () => {
+          resolve(buffer.join(""));
+        });
+      });
+
+      const { mimeType } =
+        await request.codeAnalysisService.getLinguistFileInfos(
+          path,
+          gitCatFileResult
+        );
+
+      return {
+        content: `data:${mimeType};base64,${gitCatFileResult}`,
+        mimeType,
+      };
+    } catch (_) {
+      console.error("err:", _);
+      return null;
+    }
+  };
+};
+
+export default makeGetRepositoryFileContentBase64;

app/services/repository/index.ts
@@ -8,6 +8,7 @@ import { default as makeGetRepository } from "./getRepository";
 import { default as makeGetRepositoryCommitLog } from "./getRepositoryCommitLog";
 import { default as makeGetRepositoryExploreCollection } from "./getRepositoryExploreCollection";
 import { default as makeGetRepositoryFileContent } from "./getRepositoryFileContent";
+import { default as makeGetRepositoryFileContentBase64 } from "./getRepositoryFileContentBase64";
 import { default as makeGetRepositoryFiles } from "./getRepositoryFiles";
 import { default as makeGetRepositoryHead } from "./getRepositoryHead";
 import { default as makeGetRepositoryHTTPCloneUrl } from "./getRepositoryHTTPCloneUrl";

...
@@ -23,6 +24,7 @@ export const makeRepositoryService = makeService<
   getRepositoryCommitLog: makeGetRepositoryCommitLog,
   getRepositoryExploreCollection: makeGetRepositoryExploreCollection,
   getRepositoryFileContent: makeGetRepositoryFileContent,
+  getRepositoryFileContentBase64: makeGetRepositoryFileContentBase64,
   getRepositoryFiles: makeGetRepositoryFiles,
   getRepositoryHead: makeGetRepositoryHead,
   getRepositoryHTTPCloneUrl: makeGetRepositoryHTTPCloneUrl,

app/services/repository/types.ts
@@ -46,6 +46,11 @@ export interface RepositoryServiceAPI extends ServiceApiContract {
     path: string,
     ref?: string
   ): Promise<null | RepositoryFileContent>;
+  getRepositoryFileContentBase64(
+    repository: Repository,
+    path: string,
+    ref?: string
+  ): Promise<null | RepositoryFileContent>;
   getRepositoryFiles(
     repository: Repository,
     path?: string,

app/views/repository/RepositoryBrowserView.tsx
@@ -14,6 +14,7 @@ import {
   Layout,
   PageWrapper,
   getThemedCodeCss,
+  Card,
 } from "../../components";
 
 export interface RepositoryBrowserViewProps extends CommonProps {

...
@@ -62,12 +63,15 @@ const RepositoryBrowserView: ReactView<RepositoryBrowserViewProps> = ({
             </div>
           </Grid.Row>
           {linguistInfos.type === "image" ? (
-            <img
-              src={`data:${fileContent.mimeType};base64,${Buffer.from(
-                fileContent.content
-              ).toString("base64")}`}
-              style={{ width: "100%", height: "auto" }}
-            />
+            <Card
+              style={{ width: "100%" }}
+              themeScheme={commonProps.themeScheme}
+            >
+              <img
+                src={fileContent.content}
+                style={{ width: "100%", height: "auto" }}
+              />
+            </Card>
           ) : (
             <>
               {getThemedCodeCss(commonProps.themeScheme)}