GitFOSS
refactor(repository): move magic/duplicated strings into constants file
+ 93
- 42
@@ -4,6 +4,8 @@ import type { AppThemeScheme } from "./types";
 
 type Const = {
   APP_NAME: string;
+  PRIMARY_BRANCH_REF: string;
+  DEFAULT_HEAD_REF: string;
   DEFAULT_THEME_SCHEME: AppThemeScheme;
   README_FILE_NAMES: string[];
   SESSION_TTL_SECONDS: number;

...
@@ -12,6 +14,8 @@ type Const = {
 
 export const Const: Const = {
   APP_NAME: "GitFOSS",
+  PRIMARY_BRANCH_REF: "main",
+  DEFAULT_HEAD_REF: "HEAD",
   DEFAULT_THEME_SCHEME: "dark",
   README_FILE_NAMES: [
     "READ_ME",

app/controllers/repository/getRepositoryBrowserView.ts
@@ -20,7 +20,7 @@ import RepositoryDetailsView, {
 const getRepositoryBrowserView: ReqHandler = async (request, reply) => {
   const params =
     request.params as AppRoutesParams[AppRoute.REPOSITORY_BROWSER]["params"];
-  const { orgSlug, repoSlug, ref } = params;
+  const { orgSlug, repoSlug, currentRef } = params;
   const path = params["*"];
 
   const orgService = makeOrganizationService({ request });

...
@@ -57,7 +57,9 @@ const getRepositoryBrowserView: ReqHandler = async (request, reply) => {
       repo,
       path,
       Const.README_FILE_NAMES,
-      ref === "main" ? "HEAD" : ref
+      currentRef === Const.PRIMARY_BRANCH_REF
+        ? Const.DEFAULT_HEAD_REF
+        : currentRef
     );
 
     const readmeFileContent =

...
@@ -65,14 +67,16 @@ const getRepositoryBrowserView: ReqHandler = async (request, reply) => {
         ? await repoService.getRepositoryFileContent(
             repo,
             `${path}${readmeFiles[0]}`,
-            ref === "main" ? "HEAD" : ref
+            currentRef === Const.PRIMARY_BRANCH_REF
+              ? Const.DEFAULT_HEAD_REF
+              : currentRef
           )
         : null;
 
     const commitLogs = await repoService.getRepositoryCommitLog(
       repo,
       path,
-      ref,
+      currentRef,
       true
     );
 

...
@@ -83,7 +87,7 @@ const getRepositoryBrowserView: ReqHandler = async (request, reply) => {
 
     return reqHandler<RepositoryDetailsViewProps>(RepositoryDetailsView.name, {
       branches,
-      currentRef: ref,
+      currentRef,
       currentUser,
       cloneUrl: {
         http: await repoService.getRepositoryHTTPCloneUrl(repo),

...
@@ -94,8 +98,8 @@ const getRepositoryBrowserView: ReqHandler = async (request, reply) => {
       path,
       readmeFileContent,
       repo,
-      repoHead: await repoService.getRepositoryHead(repo, ref),
-      repoFiles: await repoService.getRepositoryFiles(repo, path, ref),
+      repoHead: await repoService.getRepositoryHead(repo, currentRef),
+      repoFiles: await repoService.getRepositoryFiles(repo, path, currentRef),
       tags,
     });
   }

...
@@ -106,8 +110,8 @@ const getRepositoryBrowserView: ReqHandler = async (request, reply) => {
 
   let fileContent =
     linguistInfos.type === "image"
-      ? await repoService.getRepositoryFileContentBase64(repo, path, ref)
-      : await repoService.getRepositoryFileContent(repo, path, ref);
+      ? await repoService.getRepositoryFileContentBase64(repo, path, currentRef)
+      : await repoService.getRepositoryFileContent(repo, path, currentRef);
 
   if (fileContent == null) {
     return reply.status(404).callNotFound();

...
@@ -119,7 +123,7 @@ const getRepositoryBrowserView: ReqHandler = async (request, reply) => {
   );
 
   return reqHandler<RepositoryBrowserViewProps>(RepositoryBrowserView.name, {
-    currentRef: ref,
+    currentRef,
     currentUser,
     fileContent,
     linguistInfos,

app/controllers/repository/getRepositoryCommitsLogView.ts
@@ -13,8 +13,8 @@ import RepositoryCommitsLogView, {
 } from "../../views/repository/RepositoryCommitsLogView";
 
 const getRepositoryCommitsLogView: ReqHandler = async (request, reply) => {
-  const { orgSlug, repoSlug } =
-    request.params as AppRoutesParams[AppRoute.REPOSITORY_BROWSER]["params"];
+  const { orgSlug, repoSlug, currentRef } =
+    request.params as AppRoutesParams[AppRoute.REPOSITORY_COMMITS_LOG]["params"];
 
   const orgService = makeOrganizationService({ request });
   const repoService = makeRepositoryService({ request });

...
@@ -49,6 +49,7 @@ const getRepositoryCommitsLogView: ReqHandler = async (request, reply) => {
   return reqHandler<RepositoryCommitsLogViewProps>(
     RepositoryCommitsLogView.name,
     {
+      currentRef,
       parentOrg,
       repo,
       history,

app/controllers/repository/getRepositoryDetailsView.ts
@@ -32,7 +32,7 @@ const getRepositoryDetailsView: ReqHandler = async (request, reply) => {
       : null;
 
   const path = "/";
-  const ref = "HEAD";
+  const currentRef = Const.DEFAULT_HEAD_REF;
 
   const parentOrg = (await orgService.getOrganizationBySlug(orgSlug, {
     memberships: true,

...
@@ -69,7 +69,7 @@ const getRepositoryDetailsView: ReqHandler = async (request, reply) => {
   const commitLogs = await repoService.getRepositoryCommitLog(
     repo,
     "",
-    ref,
+    currentRef,
     true
   );
 

...
@@ -83,7 +83,7 @@ const getRepositoryDetailsView: ReqHandler = async (request, reply) => {
   try {
     return reqHandler<RepositoryDetailsViewProps>(RepositoryDetailsView.name, {
       branches,
-      currentRef: ref,
+      currentRef,
       currentUser,
       cloneUrl: {
         http: await repoService.getRepositoryHTTPCloneUrl(repo),

...
@@ -94,18 +94,18 @@ const getRepositoryDetailsView: ReqHandler = async (request, reply) => {
       path,
       readmeFileContent,
       repo,
-      repoHead: await repoService.getRepositoryHead(repo, ref),
-      repoFiles: await repoService.getRepositoryFiles(repo, "", ref),
+      repoHead: await repoService.getRepositoryHead(repo, currentRef),
+      repoFiles: await repoService.getRepositoryFiles(repo, "", currentRef),
       tags,
     });
   } catch (err) {
     const error = err as Error;
-    if (error.message.includes("Not a valid object name HEAD")) {
+    if (error.message.includes(`${currentRef} is not a valid object name`)) {
       return reqHandler<RepositoryDetailsViewProps>(
         RepositoryDetailsView.name,
         {
           branches,
-          currentRef: ref,
+          currentRef,
           currentUser,
           cloneUrl: {
             http: await repoService.getRepositoryHTTPCloneUrl(repo),

app/controllers/repository/getRepositoryPullRequestsView.ts
@@ -1,11 +1,13 @@
 // 1st-party
 import type { ReqHandler } from "@ethicdevs/react-monolith";
+import { ResourceVisibility } from "@prisma/client";
 // app
 import { AppRoute, AppRoutesParams } from "../../routes";
 // app services
 import { makeOrganizationService } from "../../services/organization";
 import { makePullRequestService } from "../../services/pullRequest";
 import { makeRepositoryService } from "../../services/repository";
+import { makeUsersService } from "../../services/user";
 // app views
 import RepositoryPullRequestsView, {
   RepositoryPullRequestsViewProps,

...
@@ -18,6 +20,13 @@ const getRepositoryPullRequestsView: ReqHandler = async (request, reply) => {
   const orgService = makeOrganizationService({ request });
   const prService = makePullRequestService({ request });
   const repoService = makeRepositoryService({ request });
+  const usersService = makeUsersService({ request });
+
+  const currentUser =
+    request.session.data.authenticated &&
+    request.session.data.curr_user_uid != null
+      ? await usersService.getUserById(request.session.data.curr_user_uid)
+      : null;
 
   const parentOrg = await orgService.getOrganizationBySlug(orgSlug);
 

...
@@ -31,6 +40,16 @@ const getRepositoryPullRequestsView: ReqHandler = async (request, reply) => {
     return reply.status(404).callNotFound();
   }
 
+  if (repo.visibility === ResourceVisibility.PRIVATE) {
+    if (currentUser == null) {
+      return reply.status(404).callNotFound();
+    } else if (
+      (await repoService.canUserAccessRepository(currentUser, repo)) === false
+    ) {
+      return reply.status(404).callNotFound();
+    }
+  }
+
   const pullRequests = await prService.getPullRequestsInRepository(repo);
 
   const reqHandler = reply.makeRequestHandler(request, reply);

app/islands/RepositoryCommitSummaryLine.tsx
@@ -7,9 +7,10 @@ import type { RepositoryObject } from "../types";
 import { Grid } from "../components/Grid";
 
 export interface RepositoryCommitSummaryLineProps {
+  commit: RepositoryObject;
+  currentRef: string;
   orgSlug: string;
   repoSlug: string;
-  commit: RepositoryObject;
 }
 
 const MAX_COMMIT_LINE_LENGTH = 60;

app/islands/RepositoryFilesDiffsList.tsx
@@ -8,6 +8,7 @@ import type {
   RepositoryFileDiffChunk,
   WithThemeSchemeProp,
 } from "../types";
+import { Const } from "../const";
 import { Card, Grid } from "../components";
 // app islands
 import Code, { getThemedCodeCss } from "../islands/Code";

...
@@ -63,10 +64,10 @@ const RepositoryFilesDiffsList: ReactIsland<
                   View file (current ref)
                 </a>
                 <a
-                  href={`/${orgSlug}/${repoSlug}/main/tree/${diff.to}`}
+                  href={`/${orgSlug}/${repoSlug}/${Const.PRIMARY_BRANCH_REF}/tree/${diff.to}`}
                   style={{ marginLeft: 16 }}
                 >
-                  View file (main)
+                  View file (${Const.PRIMARY_BRANCH_REF})
                 </a>
               </div>
             </Grid.Row>

@@ -77,6 +77,7 @@ export interface AppRoutesParams extends IRouteParams {
     params: {
       orgSlug: string;
       repoSlug: string;
+      currentRef: string;
     };
   };
   [AppRoute.REPOSITORY_COMPARE]: {

...
@@ -107,13 +108,14 @@ export interface AppRoutesParams extends IRouteParams {
     params: {
       orgSlug: string;
       repoSlug: string;
+      currentRef: string;
     };
   };
   [AppRoute.REPOSITORY_BROWSER]: {
     params: {
       orgSlug: string;
       repoSlug: string;
-      ref: string;
+      currentRef: string;
       "*": string;
     };
   };

...
@@ -202,7 +204,7 @@ export const AppRoutesSchemas: Record<AppRoute, undefined | FastifySchema> = {
   [AppRoute.REPOSITORY_COMMITS_LOG]: {
     params: {
       type: "object",
-      required: ["orgSlug", "repoSlug"],
+      required: ["orgSlug", "repoSlug", "currentRef"],
       additionalProperties: false,
       properties: {
         orgSlug: {

...
@@ -211,6 +213,9 @@ export const AppRoutesSchemas: Record<AppRoute, undefined | FastifySchema> = {
         repoSlug: {
           type: "string",
         },
+        currentRef: {
+          type: "string"
+        },
       },
     },
   },

...
@@ -318,7 +323,7 @@ export const AppRoutesSchemas: Record<AppRoute, undefined | FastifySchema> = {
   [AppRoute.REPOSITORY_BROWSER]: {
     params: {
       type: "object",
-      required: ["orgSlug", "repoSlug", "ref", "*"],
+      required: ["orgSlug", "repoSlug", "currentRef", "*"],
       additionalProperties: false,
       properties: {
         orgSlug: {

...
@@ -327,7 +332,7 @@ export const AppRoutesSchemas: Record<AppRoute, undefined | FastifySchema> = {
         repoSlug: {
           type: "string",
         },
-        ref: {
+        currentRef: {
           type: "string",
         },
         "*": {

...
@@ -465,7 +470,7 @@ const RootAppRouter: AppRouter = () => {
         <Router.Route
           name={AppRoute.REPOSITORY_COMMITS_LOG}
           method={"GET"}
-          path={"/:orgSlug/:repoSlug/commits"} // TODO: add :ref before /commits + adaptations to links
+          path={"/:orgSlug/:repoSlug/:ref/commits"}
           schema={AppRoutesSchemas[AppRoute.REPOSITORY_COMMITS_LOG]}
           handler={RepositoryController.getRepositoryCommitsLogView}
         />

app/services/repository/createRepository.ts
@@ -1,13 +1,15 @@
 // std
 import { existsSync } from "node:fs";
+import { mkdir } from "node:fs/promises";
 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 { Const } from "../../const";
 // service
 import type { CreateRepositoryDTO, RepositoryServiceDeps } from "./types";
-import { mkdir } from "node:fs/promises";
 
 const makeCreateRepository: ServiceMethodFactory<
   RepositoryServiceDeps,

...
@@ -69,7 +71,7 @@ const makeCreateRepository: ServiceMethodFactory<
       [
         "init",
         "--bare",
-        "--initial-branch=main",
+        `--initial-branch=${Const.PRIMARY_BRANCH_REF}`,
         "--shared=group",
         `${newRepo.slug}.git`,
       ],

app/services/repository/getRepositoryCommitLog.ts
@@ -6,9 +6,10 @@ import type { ServiceMethodFactory } from "@ethicdevs/react-monolith";
 // generated via script[generate:prisma]
 import type { Repository } from "@prisma/client";
 // app
+import type { RepositoryLog } from "../../types";
+import { Const } from "../../const";
 import { Env } from "../../env";
 // service
-import type { RepositoryLog } from "../../types";
 import type { RepositoryServiceDeps } from "./types";
 
 const GIT_LOG_NEWLINEW_FINDER_REGEXP =

...
@@ -19,7 +20,12 @@ const makeGetRepositoryCommitLog: ServiceMethodFactory<
   [Repository, string | undefined, string | undefined, boolean | undefined],
   Promise<RepositoryLog[]>
 > = ({ request }) => {
-  return async (repo, path = "", ref = "HEAD", onlyLast = false) => {
+  return async (
+    repo,
+    path = "",
+    ref = Const.DEFAULT_HEAD_REF,
+    onlyLast = false
+  ) => {
     const parentOrg = await request.prisma.organization.findUnique({
       where: {
         id: repo.organizationId,

app/services/repository/getRepositoryFileContent.ts
@@ -6,9 +6,10 @@ import type { ServiceMethodFactory } from "@ethicdevs/react-monolith";
 // generated via script[generate:prisma]
 import type { Repository } from "@prisma/client";
 // app
+import type { RepositoryFileContent } from "../../types";
+import { Const } from "../../const";
 import { Env } from "../../env";
 // service
-import type { RepositoryFileContent } from "../../types";
 import type { RepositoryServiceDeps } from "./types";
 
 const makeGetRepositoryFileContent: ServiceMethodFactory<

...
@@ -16,7 +17,7 @@ const makeGetRepositoryFileContent: ServiceMethodFactory<
   [Repository, string, string | undefined],
   Promise<null | RepositoryFileContent>
 > = ({ request }) => {
-  return async (repo, path, ref = "HEAD") => {
+  return async (repo, path, ref = Const.DEFAULT_HEAD_REF) => {
     if (path.endsWith("/")) {
       throw new Error("Could not retrieve file content for a folder.");
     }

app/services/repository/getRepositoryFileContentBase64.ts
@@ -6,9 +6,10 @@ import type { ServiceMethodFactory } from "@ethicdevs/react-monolith";
 // generated via script[generate:prisma]
 import type { Repository } from "@prisma/client";
 // app
+import type { RepositoryFileContent } from "../../types";
+import { Const } from "../../const";
 import { Env } from "../../env";
 // service
-import type { RepositoryFileContent } from "../../types";
 import type { RepositoryServiceDeps } from "./types";
 
 const makeGetRepositoryFileContentBase64: ServiceMethodFactory<

...
@@ -16,7 +17,7 @@ const makeGetRepositoryFileContentBase64: ServiceMethodFactory<
   [Repository, string, string | undefined],
   Promise<null | RepositoryFileContent>
 > = ({ request }) => {
-  return async (repo, path, ref = "HEAD") => {
+  return async (repo, path, ref = Const.DEFAULT_HEAD_REF) => {
     if (path.endsWith("/")) {
       throw new Error("Could not retrieve file content for a folder.");
     }

app/services/repository/getRepositoryFiles.ts
@@ -6,9 +6,10 @@ import type { ServiceMethodFactory } from "@ethicdevs/react-monolith";
 // generated via script[generate:prisma]
 import type { Repository } from "@prisma/client";
 // app
+import type { RepositoryFile } from "../../types";
+import { Const } from "../../const";
 import { Env } from "../../env";
 // service
-import type { RepositoryFile } from "../../types";
 import type { RepositoryServiceDeps } from "./types";
 
 const GIT_LS_TREE_REGEXP =

...
@@ -19,7 +20,7 @@ const makeGetRepositoryFiles: ServiceMethodFactory<
   [Repository, string | undefined, string | undefined],
   Promise<RepositoryFile[]>
 > = ({ request }) => {
-  return async (repo, path = "", ref = "HEAD") => {
+  return async (repo, path = "", ref = Const.DEFAULT_HEAD_REF) => {
     const parentOrg = await request.prisma.organization.findUnique({
       where: {
         id: repo.organizationId,

app/services/repository/getRepositoryHead.ts
@@ -6,9 +6,10 @@ import type { ServiceMethodFactory } from "@ethicdevs/react-monolith";
 // generated via script[generate:prisma]
 import type { Repository } from "@prisma/client";
 // app
+import type { RepositoryHead } from "../../types";
+import { Const } from "../../const";
 import { Env } from "../../env";
 // service
-import type { RepositoryHead } from "../../types";
 import type { RepositoryServiceDeps } from "./types";
 
 const GIT_TREE_ID_REGEXP = /tree[\s]+(.*)\n/im;

...
@@ -25,7 +26,7 @@ const makeGetRepositoryHead: ServiceMethodFactory<
   [Repository, string | undefined],
   Promise<RepositoryHead>
 > = ({ request }) => {
-  return async (repo, ref = "HEAD") => {
+  return async (repo, ref = Const.DEFAULT_HEAD_REF) => {
     const parentOrg = await request.prisma.organization.findUnique({
       where: {
         id: repo.organizationId,

app/services/repository/isFileInRepositoryPath.ts
@@ -6,6 +6,7 @@ import type { ServiceMethodFactory } from "@ethicdevs/react-monolith";
 // generated via script[generate:prisma]
 import type { Repository } from "@prisma/client";
 // app
+import { Const } from "../../const";
 import { Env } from "../../env";
 // service
 import type { RepositoryServiceDeps } from "./types";

...
@@ -15,7 +16,7 @@ const makeIsFileInRepositoryPath: ServiceMethodFactory<
   [Repository, string, string[], string | undefined],
   Promise<string[]>
 > = ({ request }) => {
-  return async (repo, path, filesToMatch, ref = "HEAD") => {
+  return async (repo, path, filesToMatch, ref = Const.DEFAULT_HEAD_REF) => {
     if (path !== "" && path.endsWith("/") === false) {
       throw new Error("Could not search for files in a non-tree blob.");
     }

app/views/repository/RepositoryCommitsLogView.tsx
@@ -11,6 +11,7 @@ import { Card, Layout, PageWrapper } from "../../components";
 import RepositoryCommitSummaryLine from "../../islands/RepositoryCommitSummaryLine";
 
 export interface RepositoryCommitsLogViewProps extends CommonProps {
+  currentRef: string;
   history: RepositoryLog[];
   parentOrg: Organization;
   repo: Repository;

...
@@ -18,6 +19,7 @@ export interface RepositoryCommitsLogViewProps extends CommonProps {
 
 const RepositoryCommitsLogView: ReactView<RepositoryCommitsLogViewProps> = ({
   commonProps,
+  currentRef,
   history,
   parentOrg,
   repo,

...
@@ -33,7 +35,7 @@ const RepositoryCommitsLogView: ReactView<RepositoryCommitsLogViewProps> = ({
           <a href={`/${parentOrg.slug}/${repo.slug}`}>
             {repo.displayName || repo.slug}
           </a>
-          {" / Commits"}
+          {`/ ${currentRef} / Commits`}
         </h1>
         <Card
           style={{ width: "100%", marginTop: 32 }}

...
@@ -47,9 +49,10 @@ const RepositoryCommitsLogView: ReactView<RepositoryCommitsLogViewProps> = ({
               themeScheme={commonProps.themeScheme}
             >
               <RepositoryCommitSummaryLine
+                commit={commit}
+                currentRef={currentRef}
                 orgSlug={parentOrg.slug}
                 repoSlug={repo.slug}
-                commit={commit}
               />
             </Card>
           ))}