feat(pull_requests): make it possible to see a Pull Request and its diff
+ 177
- 26
@@ -1,5 +1,5 @@
 {
-  "_generatedAtUnix": 1665674159579,
+  "_generatedAtUnix": 1665676834248,
   "_hashAlgorithm": "sha1",
   "_version": 2,
   "islands": {

...
@@ -134,7 +134,7 @@
       "pathSource": "./app/views/repositoryPullRequests/RepositoryPullRequestCreateView.tsx"
     },
     "RepositoryPullRequestDetailsView": {
-      "hash": "1ecb2513fa8cee3e027d71aea376466657bb1d56",
+      "hash": "6c311e13f7cbbc00b502e0d4c1af30493433eb65",
       "pathSource": "./app/views/repositoryPullRequests/RepositoryPullRequestDetailsView.tsx"
     },
     "RepositoryPullRequestsView": {

app/controllers/repositoryPullRequests/getRepositoryPullRequestDetailsView.ts
@@ -2,6 +2,7 @@
 import type { ReqHandler } from "@ethicdevs/react-monolith";
 import { ResourceVisibility } from "@prisma/client";
 // app
+import type { RepositoryFileDiff } from "../../types";
 import { AppRoute, AppRoutesParams } from "../../routes";
 // app services
 import { makeOrganizationService } from "../../services/organization";

...
@@ -25,47 +26,104 @@ const getRepositoryPullRequestDetailsView: ReqHandler = async (
   const repoService = makeRepositoryService({ request });
   const usersService = makeUsersService({ request });
 
+  const pullRequest = await prService.getPullRequestByUid(
+    orgSlug,
+    repoSlug,
+    pullUid
+  );
+
+  if (pullRequest == null) {
+    return reply.status(404).callNotFound();
+  }
+
   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);
+  const sourceRepo = await repoService.getRepositoryById(
+    pullRequest.sourceRepositoryId
+  );
+  const targetRepo = await repoService.getRepositoryById(
+    pullRequest.targetRepositoryId
+  );
 
-  if (parentOrg == null) {
+  if (sourceRepo == null || targetRepo == null) {
     return reply.status(404).callNotFound();
   }
 
-  const repo = await repoService.getRepository(orgSlug, repoSlug);
+  const sourceParentOrg = await orgService.getOrganizationById(
+    sourceRepo.organizationId
+  );
 
-  if (repo == null) {
+  const targetParentOrg = await orgService.getOrganizationById(
+    targetRepo.organizationId
+  );
+
+  if (sourceParentOrg == null || targetParentOrg == null) {
     return reply.status(404).callNotFound();
   }
 
-  if (repo.visibility === ResourceVisibility.PRIVATE) {
+  if (sourceRepo.visibility === ResourceVisibility.PRIVATE) {
     if (currentUser == null) {
       return reply.status(404).callNotFound();
     } else if (
-      (await repoService.canUserAccessRepository(currentUser, repo)) === false
+      (await repoService.canUserAccessRepository(currentUser, sourceRepo)) ===
+      false
     ) {
       return reply.status(404).callNotFound();
     }
   }
 
-  const pullRequest = await prService.getPullRequestByUid(
-    orgSlug,
-    repoSlug,
-    pullUid
+  if (targetRepo.visibility === ResourceVisibility.PRIVATE) {
+    if (currentUser == null) {
+      return reply.status(404).callNotFound();
+    } else if (
+      (await repoService.canUserAccessRepository(currentUser, targetRepo)) ===
+      false
+    ) {
+      return reply.status(404).callNotFound();
+    }
+  }
+
+  const commitLogs = await repoService.getRepositoryCommitLog(
+    sourceRepo,
+    "",
+    pullRequest.sourceBranch,
+    true
   );
 
+  const lastCommit = commitLogs.length >= 1 ? commitLogs[0] : null;
+
+  let filesDiffs = [] as RepositoryFileDiff[];
+
+  if (sourceParentOrg.slug === targetParentOrg.slug) {
+    filesDiffs = await repoService.getRepositoryRefDiff(
+      sourceRepo,
+      pullRequest.targetBranch,
+      pullRequest.sourceBranch
+    );
+  } else {
+    filesDiffs = await repoService.getRepositoryRemoteRefDiff(
+      sourceRepo,
+      pullRequest.sourceBranch,
+      targetRepo,
+      pullRequest.targetBranch
+    );
+  }
+
   const reqHandler = reply.makeRequestHandler(request, reply);
   return reqHandler<RepositoryPullRequestDetailsViewProps>(
     RepositoryPullRequestDetailsView.name,
     {
-      parentOrg,
+      filesDiffs,
+      lastCommit,
       pullRequest,
-      repo,
+      sourceParentOrg,
+      sourceRepo,
+      targetParentOrg,
+      targetRepo,
     }
   );
 };

new file
app/services/repository/getRepositoryById.ts
@@ -0,0 +1,44 @@
+// 1st-party
+import type { ServiceMethodFactory } from "@ethicdevs/react-monolith";
+// app
+import { RepositoryWithForkedFromRepo } from "../../types";
+// service
+import type { RepositoryServiceDeps } from "./types";
+
+const makeGetRepositoryById: ServiceMethodFactory<
+  RepositoryServiceDeps,
+  [string],
+  Promise<RepositoryWithForkedFromRepo | null>
+> = ({ request }) => {
+  return async (repoId) => {
+    const repository = await request.prisma.repository.findFirst({
+      include: {
+        forkedFromRepo: {
+          select: {
+            id: true,
+            slug: true,
+            displayName: true,
+            organization: {
+              select: {
+                id: true,
+                slug: true,
+                displayName: true,
+              },
+            },
+          },
+        },
+        forks: {
+          select: {
+            _count: true,
+          },
+        },
+      },
+      where: {
+        id: repoId,
+      },
+    });
+    return repository;
+  };
+};
+
+export default makeGetRepositoryById;

app/services/repository/index.ts
@@ -9,6 +9,7 @@ import { default as makeForkRepository } from "./forkRepository";
 import { default as makeGetCurrentUserRepositoryForks } from "./getCurrentUserRepositoryForks";
 import { default as makeGetRepository } from "./getRepository";
 import { default as makeGetRepositoryBranches } from "./getRepositoryBranches";
+import { default as makeGetRepositoryById } from "./getRepositoryById";
 import { default as makeGetRepositoryCommitLog } from "./getRepositoryCommitLog";
 import { default as makeGetRepositoryExploreCollection } from "./getRepositoryExploreCollection";
 import { default as makeGetRepositoryFileContent } from "./getRepositoryFileContent";

...
@@ -33,6 +34,7 @@ export const makeRepositoryService = makeService<
   getCurrentUserRepositoryForks: makeGetCurrentUserRepositoryForks,
   getRepository: makeGetRepository,
   getRepositoryBranches: makeGetRepositoryBranches,
+  getRepositoryById: makeGetRepositoryById,
   getRepositoryCommitLog: makeGetRepositoryCommitLog,
   getRepositoryExploreCollection: makeGetRepositoryExploreCollection,
   getRepositoryFileContent: makeGetRepositoryFileContent,

app/services/repository/types.ts
@@ -51,17 +51,20 @@ export interface RepositoryServiceAPI extends ServiceApiContract {
   canUserAccessRepository(user: User, repo: Repository): Promise<boolean>;
   createRepository(dto: CreateRepositoryDTO): Promise<Repository>;
   forkRepository(dto: ForkRepositoryDTO): Promise<Repository>;
+  getCurrentUserRepositoryForks(
+    repository: Repository
+  ): Promise<RepositoryWithParentAndForkedFromRepos[]>;
   getRepository(
     orgSlug: string,
     repoSlug: string
   ): Promise<RepositoryWithForkedFromRepo | null>;
-  getCurrentUserRepositoryForks(
-    repository: Repository
-  ): Promise<RepositoryWithParentAndForkedFromRepos[]>;
   getRepositoryBranches(
     repository: Repository,
     onlyLocalBranches?: boolean
   ): Promise<string[]>;
+  getRepositoryById(
+    repoId: string
+  ): Promise<RepositoryWithForkedFromRepo | null>;
   getRepositoryCommitLog(
     repository: Repository,
     path?: string,

app/views/repositoryPullRequests/RepositoryPullRequestDetailsView.tsx
@@ -5,25 +5,46 @@ import React from "react";
 // generated via script[generate:prisma]
 import type { Organization, PullRequest } from "@prisma/client";
 // app
-import type { CommonProps, RepositoryWithForkedFromRepo } from "../../types";
+import type {
+  CommonProps,
+  RepositoryFileDiff,
+  RepositoryObject,
+  RepositoryWithForkedFromRepo,
+} from "../../types";
 import {
+  Card,
   Grid,
   InlineCode,
   IslandWrapper,
   Layout,
+  MarkdownToJsx,
   PageWrapper,
 } from "../../components";
 // app islands
+import RepositoryFilesDiffsList from "../../islands/RepositoryFilesDiffsList";
 import RepositoryHero from "../../islands/RepositoryHero";
 
 export interface RepositoryPullRequestDetailsViewProps extends CommonProps {
-  parentOrg: Organization;
+  filesDiffs: RepositoryFileDiff[];
+  lastCommit: RepositoryObject | null;
   pullRequest: PullRequest;
-  repo: RepositoryWithForkedFromRepo;
+  sourceParentOrg: Organization;
+  sourceRepo: RepositoryWithForkedFromRepo;
+  targetParentOrg: Organization;
+  targetRepo: RepositoryWithForkedFromRepo;
 }
 
 const RepositoryPullRequestDetailsView: ReactView<RepositoryPullRequestDetailsViewProps> =
-  ({ commonProps, parentOrg, pullRequest: pr, repo }) => {
+  ({
+    commonProps,
+    filesDiffs,
+    lastCommit,
+    pullRequest: pr,
+    sourceParentOrg,
+    sourceRepo,
+    targetParentOrg: parentOrg,
+    targetRepo: repo,
+  }) => {
     return (
       <Layout {...commonProps}>
         <PageWrapper>

...
@@ -49,7 +70,7 @@ const RepositoryPullRequestDetailsView: ReactView<RepositoryPullRequestDetailsVi
               <span style={{ opacity: 0.67, marginTop: 8 }}>
                 wants to merge{" "}
                 <InlineCode themeScheme={commonProps.themeScheme}>
-                  {pr.sourceBranch}
+                  {`${sourceParentOrg.slug}/${sourceRepo.slug}@${pr.sourceBranch}`}
                 </InlineCode>{" "}
                 into{" "}
                 <InlineCode themeScheme={commonProps.themeScheme}>

...
@@ -97,10 +118,33 @@ const RepositoryPullRequestDetailsView: ReactView<RepositoryPullRequestDetailsVi
                 Delete PR
               </a>
             </Grid.Col>
-            <Grid.Col fluid>
-              <pre>
-                <code>{JSON.stringify(pr, null, 2)}</code>
-              </pre>
+            <Grid.Col fluid style={{ marginTop: 24 }}>
+              <Card
+                style={{ width: "100%" }}
+                themeScheme={commonProps.themeScheme}
+              >
+                <MarkdownToJsx
+                  markdown={
+                    pr.textMd == null || pr.textMd.trim() === ""
+                      ? "> &lt;no_description_yet /&gt;"
+                      : pr.textMd
+                  }
+                  themeScheme={commonProps.themeScheme}
+                />
+              </Card>
+            </Grid.Col>
+            <Grid.Col
+              fluid
+              data-islandid={`${RepositoryFilesDiffsList.name}$$0`}
+              style={{ marginTop: 24 }}
+            >
+              <RepositoryFilesDiffsList
+                filesDiffs={filesDiffs}
+                themeScheme={commonProps.themeScheme}
+                orgSlug={parentOrg.slug}
+                repoSlug={repo.slug}
+                commitHash={lastCommit != null ? lastCommit.commit : "HEAD"}
+              />
             </Grid.Col>
           </Grid.Col>
         </PageWrapper>