feat(repository): continue work on Repository*View to display usefull data@@ -1,5 +1,5 @@
{
- "_generatedAtUnix": 1663515923444,
+ "_generatedAtUnix": 1663685370478,
"_hashAlgorithm": "sha1",
"_version": 2,
"islands": {
@@ -15,6 +15,18 @@
"pathBundle": "./public/.islands/RepositoryCreateForm.bundle.js",
"pathSourceMap": "./public/.islands/RepositoryCreateForm.bundle.js.map"
},
+ "RepositoryInitialSetup": {
+ "hash": "4916d0555f98e2c8438fea37315212d6d538c9a8",
+ "pathSource": "./app/islands/RepositoryInitialSetup.tsx",
+ "pathBundle": "./public/.islands/RepositoryInitialSetup.bundle.js",
+ "pathSourceMap": "./public/.islands/RepositoryInitialSetup.bundle.js.map"
+ },
+ "RepositoryTreeView": {
+ "hash": "e5a3555080f0a865e31011f45a376c4a0e78a2c7",
+ "pathSource": "./app/islands/RepositoryTreeView.tsx",
+ "pathBundle": "./public/.islands/RepositoryTreeView.bundle.js",
+ "pathSourceMap": "./public/.islands/RepositoryTreeView.bundle.js.map"
+ },
"SideMenu": {
"hash": "5d01374da1cbee58e081b9022aa56f0624209e27",
"pathSource": "./app/islands/SideMenu.tsx",
@@ -48,11 +60,11 @@
"pathSource": "./app/views/repository/RepositoryCreateView.tsx"
},
"RepositoryDetailsView": {
- "hash": "671bd0202822bb20af517af1bfee98e138cd61ac",
+ "hash": "43ee50554a5b3adaf6ab010a62816f4bb9d32d68",
"pathSource": "./app/views/repository/RepositoryDetailsView.tsx"
},
"RepositoryExploreView": {
- "hash": "53c3c318d1f6dd198a9627e9024dfc5737091a80",
+ "hash": "c4c1d08c876232054af3318739a6d1091510afb2",
"pathSource": "./app/views/repository/RepositoryExploreView.tsx"
}
}
@@ -11,5 +11,25 @@ export const PageWrapper = styled.div`
margin: 0 auto;
padding: 24px 16px 64px 16px;
- gap: 24px;
+
+ & > h1 {
+ margin: 0;
+ &:not(:first-of-type) {
+ margin-top: 32px;
+ }
+ }
+
+ & > h2 {
+ margin: 0;
+ &:not(:first-of-type) {
+ margin-top: 24px;
+ }
+ }
+
+ & > p {
+ margin: 0;
+ &:not(:first-of-type) {
+ margin-top: 16px;
+ }
+ }
`;
@@ -3,6 +3,7 @@ import { ReqHandler } from "@ethicdevs/react-monolith";
// app
import { AppRoute, AppRoutesParams } from "../../routes";
// app services
+import { makeOrganizationService } from "../../services/organization";
import { makeRepositoryService } from "../../services/repository";
import { makeUsersService } from "../../services/user";
// app views
@@ -14,8 +15,9 @@ const getRepositoryDetailsView: ReqHandler = async (request, reply) => {
const { orgSlug, repoSlug } =
request.params as AppRoutesParams[AppRoute.REPOSITORY_DETAILS]["params"];
- const usersService = makeUsersService({ request });
+ const orgService = makeOrganizationService({ request });
const repoService = makeRepositoryService({ request });
+ const usersService = makeUsersService({ request });
const currentUser =
request.session.data.authenticated &&
@@ -23,6 +25,7 @@ const getRepositoryDetailsView: ReqHandler = async (request, reply) => {
? await usersService.getUserById(request.session.data.curr_user_uid)
: null;
+ const parentOrg = await orgService.getOrganizationBySlug(orgSlug);
const ref = "HEAD";
const repo = await repoService.getRepository(orgSlug, repoSlug);
if (repo == null) {
@@ -38,6 +41,7 @@ const getRepositoryDetailsView: ReqHandler = async (request, reply) => {
http: await repoService.getRepositoryHTTPCloneUrl(repo),
ssh: await repoService.getRepositorySSHCloneUrl(repo),
},
+ parentOrg,
ref,
repo,
repoHead: await repoService.getRepositoryHead(repo, ref),
@@ -54,6 +58,7 @@ const getRepositoryDetailsView: ReqHandler = async (request, reply) => {
http: await repoService.getRepositoryHTTPCloneUrl(repo),
ssh: await repoService.getRepositorySSHCloneUrl(repo),
},
+ parentOrg,
ref,
repo,
repoHead: null,
@@ -0,0 +1,65 @@
+// 1st-party
+import type { ReactIsland } from "@ethicdevs/react-monolith";
+// 3rd-party
+import React from "react";
+import styled from "styled-components";
+// generated via script[generate:prisma]
+import { Repository, User } from "@prisma/client";
+
+export interface RepositoryInitialSetupProps {
+ currentUser: null | User;
+ cloneUrl: {
+ http: string;
+ ssh: string;
+ };
+ ref: string;
+ repo: Repository;
+}
+
+const RepositoryInitialSetup: ReactIsland<RepositoryInitialSetupProps> = ({
+ cloneUrl,
+ currentUser,
+ repo,
+}) => {
+ return (
+ <StyledRepositoryInitialSetupContainer>
+ <p>It looks like this repository is empty.</p>
+ <p>Get started easily:</p>
+ <h3>Clone and initialize</h3>
+ <code>
+ <pre
+ style={{ maxWidth: 600 }}
+ >{`# Clone and enter the repository directory
+ $ git clone ${cloneUrl.http}
+ $ cd ${repo.slug}/
+ ${
+ currentUser != null
+ ? `
+ # Setup committer identity for this project
+ $ git config user.name "${
+ currentUser.displayName || currentUser.username
+ }"
+ $ git config user.email "${currentUser.email}"`
+ : ""
+ }
+
+ # Create some base files
+ $ echo "# ${repo.displayName || repo.slug}" > README.md
+ $ echo "The MIT License" > LICENSE
+
+ # Track files, commit and send to GitFOSS remote repository
+ $ git add .
+ $ git commit -am 'feat: initial commit'
+ $ git push
+ `}</pre>
+ </code>
+ </StyledRepositoryInitialSetupContainer>
+ );
+};
+
+const StyledRepositoryInitialSetupContainer = styled.div`
+ width: 100%;
+`;
+
+RepositoryInitialSetup.displayName = "RepositoryInitialSetup";
+export default RepositoryInitialSetup;
@@ -0,0 +1,75 @@
+// 1st-party
+import type { ReactIsland } from "@ethicdevs/react-monolith";
+// 3rd-party
+import React, { useCallback } from "react";
+import styled from "styled-components";
+// app
+import { RepositoryFile, RepositoryHead } from "../types";
+
+export interface RepositoryTreeViewProps {
+ currPath: string;
+ orgSlug: string;
+ repoHead: RepositoryHead;
+ repoFiles: RepositoryFile[];
+ repoSlug: string;
+}
+
+const RepositoryTreeView: ReactIsland<RepositoryTreeViewProps> = ({
+ currPath,
+ orgSlug,
+ repoFiles,
+ repoHead,
+ repoSlug,
+}) => {
+ const buildRepoFileLink = useCallback(
+ (file: RepositoryFile) => {
+ const fileName = `${file.name}${file.type === "tree" ? "/" : ""}`;
+ return {
+ text: fileName,
+ href:
+ currPath === "/"
+ ? `/${orgSlug}/${repoSlug}/main/tree/${fileName}`
+ : `/${orgSlug}/${repoSlug}/main/tree/${currPath}/${fileName}`,
+ };
+ },
+ [orgSlug, repoSlug, currPath]
+ );
+
+ return (
+ <StyledRepositoryTreeViewContainer>
+ <div>
+ <strong>{repoHead.author.name}</strong>
+ {" ∙ "}
+ <span>{repoHead.commitMessage}</span>
+ {" - "}
+ <span>
+ {repoHead.treeId.substring(0, 8)}
+ {repoHead.parentId
+ ? ` ∙ parent ${repoHead.parentId.substring(0, 8)}`
+ : ""}
+ </span>
+ {" ∙ "}
+ <span>{new Date(repoHead.author.timestamp * 1000).toUTCString()}</span>
+ </div>
+ <div>
+ <ul>
+ {repoFiles.map((file) => {
+ const fileLink = buildRepoFileLink(file);
+ return (
+ <li key={file.id}>
+ <a href={fileLink.href}>{fileLink.text}</a>
+ </li>
+ );
+ })}
+ </ul>
+ </div>
+ </StyledRepositoryTreeViewContainer>
+ );
+};
+
+const StyledRepositoryTreeViewContainer = styled.div`
+ width: 100%;
+`;
+
+RepositoryTreeView.displayName = "RepositoryTreeView";
+export default RepositoryTreeView;
@@ -0,0 +1,25 @@
+// 1st-party
+import type { ServiceMethodFactory } from "@ethicdevs/react-monolith";
+// generated via script[generate:prisma]
+import { Organization, Prisma } from "@prisma/client";
+// service
+import { OrganizationServiceDeps } from "./types";
+
+const makeGetOrganizationById: ServiceMethodFactory<
+ OrganizationServiceDeps,
+ [string, Prisma.OrganizationInclude | undefined],
+ Promise<Organization | null>
+> = ({ request }) => {
+ return async (orgId, include = undefined) => {
+ const organization = await request.prisma.organization.findUnique({
+ include,
+ where: {
+ id: orgId,
+ },
+ });
+
+ return organization;
+ };
+};
+
+export default makeGetOrganizationById;
@@ -0,0 +1,25 @@
+// 1st-party
+import type { ServiceMethodFactory } from "@ethicdevs/react-monolith";
+// generated via script[generate:prisma]
+import { Organization, Prisma } from "@prisma/client";
+// service
+import { OrganizationServiceDeps } from "./types";
+
+const makeGetOrganizationBySlug: ServiceMethodFactory<
+ OrganizationServiceDeps,
+ [string, Prisma.OrganizationInclude | undefined],
+ Promise<Organization | null>
+> = ({ request }) => {
+ return async (orgSlug, include = undefined) => {
+ const organization = await request.prisma.organization.findUnique({
+ include,
+ where: {
+ slug: orgSlug,
+ },
+ });
+
+ return organization;
+ };
+};
+
+export default makeGetOrganizationBySlug;
@@ -0,0 +1,15 @@
+// 1st-party
+import { makeService } from "@ethicdevs/react-monolith";
+// service
+import type { OrganizationServiceAPI, OrganizationServiceDeps } from "./types";
+// service methods
+import { default as makeGetOrganizationById } from "./getOrganizationById";
+import { default as makeGetOrganizationBySlug } from "./getOrganizationBySlug";
+
+export const makeOrganizationService = makeService<
+ OrganizationServiceAPI,
+ OrganizationServiceDeps
+>({
+ getOrganizationById: makeGetOrganizationById,
+ getOrganizationBySlug: makeGetOrganizationBySlug,
+});
@@ -0,0 +1,22 @@
+// 1st-party
+import { ServiceApiContract } from "@ethicdevs/react-monolith";
+// 3rd-party
+import { FastifyRequest } from "fastify";
+// generated via script[generate:prisma]
+import { Organization, Prisma } from "@prisma/client";
+
+// service
+export interface OrganizationServiceAPI extends ServiceApiContract {
+ getOrganizationById(
+ orgId: string,
+ include?: Prisma.OrganizationInclude
+ ): Promise<Organization | null>;
+ getOrganizationBySlug(
+ orgSlug: string,
+ include?: Prisma.OrganizationInclude
+ ): Promise<Organization | null>;
+}
+
+export interface OrganizationServiceDeps {
+ request: FastifyRequest;
+}
@@ -1,7 +1,7 @@
// 1st-party
import type { ServiceMethodFactory } from "@ethicdevs/react-monolith";
// generated via script[generate:prisma]
-import { Repository, ResourceVisibility } from "@prisma/client";
+import { Organization, Repository, ResourceVisibility } from "@prisma/client";
// service
import type { RepositoryServiceDeps } from "./types";
import { default as makeGetRepositoryHTTPCloneUrl } from "./getRepositoryHTTPCloneUrl";
@@ -10,21 +10,25 @@ import { default as makeGetRepositorySSHCloneUrl } from "./getRepositorySSHClone
const makeGetRepositoryExploreCollection: ServiceMethodFactory<
RepositoryServiceDeps,
void[],
- Promise<Repository[]>
+ Promise<(Repository & { parentOrg: Organization })[]>
> = (deps) => {
const { request } = deps;
const getRepositoryHTTPCloneUrl = makeGetRepositoryHTTPCloneUrl(deps);
const getRepositorySSHCloneUrl = makeGetRepositorySSHCloneUrl(deps);
return async () => {
const repositories = await request.prisma.repository.findMany({
+ include: {
+ organization: true,
+ },
where: {
visibility: ResourceVisibility.PUBLIC,
},
});
const repositoriesWithMetas = await Promise.all(
- repositories.map(async (repo) => ({
+ repositories.map(async ({ organization: parentOrg, ...repo }) => ({
...repo,
+ parentOrg,
httpCloneUrl: await getRepositoryHTTPCloneUrl(repo),
sshCloneUrl: await getRepositorySSHCloneUrl(repo),
}))
@@ -5,7 +5,7 @@ import type { ServiceApiContract } from "@ethicdevs/react-monolith";
// 3rd-party
import type { FastifyRequest } from "fastify";
// generated via script[generate:prisma]
-import type { Repository } from "@prisma/client";
+import type { Organization, Repository } from "@prisma/client";
// app
import type { RepositoryFile, RepositoryHead } from "../../types";
@@ -27,7 +27,9 @@ export interface CreateRepositoryDTO {
export interface RepositoryServiceAPI extends ServiceApiContract {
createRepository(dto: CreateRepositoryDTO): Promise<Repository>;
getRepository(orgSlug: string, repoSlug: string): Promise<Repository | null>;
- getRepositoryExploreCollection(): Promise<Repository[]>;
+ getRepositoryExploreCollection(): Promise<
+ (Repository & { parentOrg: Organization })[]
+ >;
getRepositoryFiles(
repository: Repository,
ref?: string
@@ -3,10 +3,13 @@ import type { ReactView } from "@ethicdevs/react-monolith";
// 3rd-party
import React from "react";
// generated via script[prisma:generate]
-import { Repository, User } from "@prisma/client";
+import type { Organization, Repository, User } from "@prisma/client";
// app
import type { CommonProps, RepositoryHead, RepositoryFile } from "../../types";
import { Layout, PageWrapper } from "../../components";
+// app islands
+import RepositoryInitialSetup from "../../islands/RepositoryInitialSetup";
+import RepositoryTreeView from "../../islands/RepositoryTreeView";
export interface RepositoryDetailsViewProps extends CommonProps {
currentUser: null | User;
@@ -14,6 +17,7 @@ export interface RepositoryDetailsViewProps extends CommonProps {
http: string;
ssh: string;
};
+ parentOrg: Organization;
ref: string;
repo: Repository;
repoHead: null | RepositoryHead;
@@ -24,6 +28,7 @@ const RepositoryDetailsView: ReactView<RepositoryDetailsViewProps> = ({
currentUser,
commonProps,
cloneUrl,
+ parentOrg,
ref,
repo,
repoHead,
@@ -32,57 +37,64 @@ const RepositoryDetailsView: ReactView<RepositoryDetailsViewProps> = ({
return (
<Layout {...commonProps} showSideMenu={false}>
<PageWrapper>
- <h1>{ref}</h1>
- <code>
- <pre style={{ maxWidth: 600 }}>{JSON.stringify(repo, null, 2)}</pre>
- </code>
+ <h1>
+ {parentOrg.displayName || parentOrg.slug}
+ {" / "}
+ {repo.displayName || repo.slug}
+ {" ∙ "}
+ <span style={{ textTransform: "capitalize" }}>
+ ({repo.visibility.toLowerCase()})
+ </span>
+ </h1>
+ <div>
+ <p>{repo.shortDescription}</p>
+ {repo.websiteUrl != null && (
+ <p>
+ <a
+ href={repo.websiteUrl}
+ target={"_blank"}
+ rel={"noopener noreferer noreferrer"}
+ >
+ {repo.websiteUrl}
+ </a>
+ </p>
+ )}
+ </div>
+ <div>
+ {repo.keywords.map((keyword, idx, arr) => (
+ <React.Fragment key={[idx, keyword].join(":")}>
+ <span>{keyword}</span>
+ {idx < arr.length - 1 ? ", " : "."}
+ </React.Fragment>
+ ))}
+ </div>
+ <div>
+ <p>
+ HTTP Clone: <code>{cloneUrl.http}</code>
+ </p>
+ <p>
+ SSH Clone: <code>{cloneUrl.ssh}</code>
+ </p>
+ </div>
{repoHead == null ? (
- <div>
- <p>It looks like this repository is empty.</p>
- <p>Get started easily:</p>
- <h3>Clone and initialize</h3>
- <code>
- <pre
- style={{ maxWidth: 600 }}
- >{`# Clone and enter the repository directory
-$ git clone ${cloneUrl.http}
-$ cd ${repo.slug}/
-${
- currentUser != null
- ? `
-# Setup committer identity for this project
-$ git config user.name "${currentUser.displayName || currentUser.username}"
-$ git config user.email "${currentUser.email}"`
- : ""
-}
-
-# Create some base files
-$ echo "# ${repo.displayName || repo.slug}" > README.md
-$ echo "The MIT License" > LICENSE
-
-# Track files, commit and send to GitFOSS remote repository
-$ git add .
-$ git commit -am 'feat: initial commit'
-$ git push
-`}</pre>
- </code>
+ <div data-islandid={`${RepositoryInitialSetup.name}$$0`}>
+ <RepositoryInitialSetup
+ cloneUrl={cloneUrl}
+ currentUser={currentUser}
+ ref={ref}
+ repo={repo}
+ />
</div>
) : (
- <>
- <code>
- <pre style={{ maxWidth: 600 }}>
- {JSON.stringify(repoHead, null, 2)}
- </pre>
- </code>
- {repoFiles.map((file) => (
- <div key={file.id}>
- <code>
- {file.name}
- {file.type === "tree" ? "/" : ""}
- </code>
- </div>
- ))}
- </>
+ <div data-islandid={`${RepositoryTreeView.name}$$0`}>
+ <RepositoryTreeView
+ currPath={"/"}
+ orgSlug={parentOrg.slug}
+ repoHead={repoHead}
+ repoFiles={repoFiles}
+ repoSlug={repo.slug}
+ />
+ </div>
)}
</PageWrapper>
</Layout>
@@ -3,13 +3,13 @@ import type { ReactView } from "@ethicdevs/react-monolith";
// 3rd-party
import React from "react";
// generated via script[generate:prisma]
-import type { Repository } from "@prisma/client";
+import type { Organization, Repository } from "@prisma/client";
// app
import type { CommonProps } from "../../types";
import { Layout, PageWrapper } from "../../components";
export interface RepositoryExploreViewProps extends CommonProps {
- repositories: Repository[];
+ repositories: (Repository & { parentOrg: Organization })[];
}
const RepositoryExploreView: ReactView<RepositoryExploreViewProps> = ({
@@ -21,12 +21,20 @@ const RepositoryExploreView: ReactView<RepositoryExploreViewProps> = ({
<PageWrapper>
{repositories.map((repo) => (
<div key={repo.id}>
- <h2>{repo.displayName}</h2>
- <code>
- <pre style={{ maxWidth: 600 }}>
- {JSON.stringify(repo, null, 2)}
- </pre>
- </code>
+ <h1>
+ <a href={`/${repo.parentOrg.slug}/${repo.slug}`}>
+ {repo.parentOrg.displayName || repo.parentOrg.slug}
+ {" / "}
+ {repo.displayName || repo.slug}
+ {" ∙ "}
+ <span style={{ textTransform: "capitalize" }}>
+ ({repo.visibility.toLowerCase()})
+ </span>
+ </a>
+ </h1>
+ <div>
+ <p>{repo.shortDescription}</p>
+ </div>
</div>
))}
</PageWrapper>