From e46041380cb8824c830f50f54428b4c8a155136e Mon Sep 17 00:00:00 2001 From: LamboCreeper <12111454+LamboCreeper@users.noreply.github.com> Date: Sat, 4 May 2024 14:17:25 +0100 Subject: [PATCH 01/14] Add react-query --- hosting/package-lock.json | 30 +++++++++++++++++++++++++----- hosting/package.json | 1 + 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/hosting/package-lock.json b/hosting/package-lock.json index 81f61af..0f63ace 100644 --- a/hosting/package-lock.json +++ b/hosting/package-lock.json @@ -8,6 +8,7 @@ "name": "codesupport-code-review-tool-frontend", "version": "0.1.0", "dependencies": { + "@tanstack/react-query": "^5.34.1", "@types/node": "^16.18.96", "@types/react": "^18.3.0", "@types/react-dom": "^18.3.0", @@ -4309,6 +4310,30 @@ "url": "https://github.com/sponsors/gregberge" } }, + "node_modules/@tanstack/query-core": { + "version": "5.34.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.34.1.tgz", + "integrity": "sha512-/a4yTnX525QUl8rFULrDLIogjqqcY/CiVvS/vWMgl477mO4WIIDxwQsLvEgb7vzlW8FqX/9CiCmaiAUnYVgB1w==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.34.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.34.1.tgz", + "integrity": "sha512-5DWzrK+ou5maZqVoIY0S26TYNpnA7re6JlviXVOe/4OBPtlvd4WEHJmyeLZhE43piJvrsKMxemp/f6Q2RPfebA==", + "dependencies": { + "@tanstack/query-core": "5.34.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -6198,11 +6223,6 @@ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==" }, - "node_modules/class-transformer": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", - "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" - }, "node_modules/clean-css": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", diff --git a/hosting/package.json b/hosting/package.json index 7e6fde5..77bcf58 100644 --- a/hosting/package.json +++ b/hosting/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@tanstack/react-query": "^5.34.1", "@types/node": "^16.18.96", "@types/react": "^18.3.0", "@types/react-dom": "^18.3.0", From 40eed27400206bccc5af6d6fc68d6ab83b9d419b Mon Sep 17 00:00:00 2001 From: LamboCreeper <12111454+LamboCreeper@users.noreply.github.com> Date: Sat, 4 May 2024 14:17:42 +0100 Subject: [PATCH 02/14] Replace useGitHub hook with React Query implementation --- hosting/src/components/pages/HomePage.tsx | 28 +++++++++++------------ hosting/src/hooks/useGitHub.ts | 17 -------------- hosting/src/index.tsx | 7 +++++- hosting/src/services/GitHubService.ts | 24 +++++++++++++++++++ 4 files changed, 43 insertions(+), 33 deletions(-) delete mode 100644 hosting/src/hooks/useGitHub.ts create mode 100644 hosting/src/services/GitHubService.ts diff --git a/hosting/src/components/pages/HomePage.tsx b/hosting/src/components/pages/HomePage.tsx index 8a95bc2..ca21caf 100644 --- a/hosting/src/components/pages/HomePage.tsx +++ b/hosting/src/components/pages/HomePage.tsx @@ -1,23 +1,21 @@ -import React, { useState, useEffect } from "react"; +import { useQuery } from "@tanstack/react-query"; import { useUser } from "../../hooks/useUser"; -import { useGitHub } from "../../hooks/useGitHub"; -import { IGitHubUser } from "../../interfaces/github/IGitHubUser"; +import { githubService } from "../../services/GitHubService"; import { IGitHubRepository } from "../../interfaces/github/IGitHubRepository"; export function HomePage() { const [user] = useUser(); - const [request] = useGitHub(); - const [repositories, setRepositories] = useState([]); + const { data: gitUser } = useQuery({ + queryKey: ["github", "users", user?.providerData[0].uid], + queryFn: async () => githubService.getUserById(+user!.providerData[0].uid), + enabled: !!user + }); - useEffect(() => { - if (!user) return; - (async () => { - const [gitUser] = await request(`users?since=${+(user.providerData[0].uid) - 1}&per_page=1`); - const repos = await request(`users/${gitUser.login.toLowerCase()}/repos`); - - setRepositories(repos); - })(); - }, [user, request]); + const { data: gitRepositories } = useQuery({ + queryKey: ["github", "repositories", gitUser?.login.toLowerCase()], + queryFn: async () => githubService.getUsersRepositories(gitUser!.login), + enabled: !!gitUser + }); return (
@@ -30,7 +28,7 @@ export function HomePage() {
{JSON.stringify(user)}

Repositories

    - {repositories?.map(repo =>
  • {repo.name}
  • )} + {gitRepositories?.map((repo: IGitHubRepository) =>
  • {repo.name}
  • )}
)} diff --git a/hosting/src/hooks/useGitHub.ts b/hosting/src/hooks/useGitHub.ts deleted file mode 100644 index 7851f88..0000000 --- a/hosting/src/hooks/useGitHub.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { useCallback } from "react"; - -const GITHUB_BASE_URL = "https://api.github.com"; - -type UseGitHubHook = [(path?: string) => Promise]; - -export function useGitHub(): UseGitHubHook { - const request = useCallback(async (path?: string): Promise => { - const res = await fetch(`${GITHUB_BASE_URL}/${path}`, { - method: "GET" - }); - - return res.json(); - }, []); - - return [request]; -} diff --git a/hosting/src/index.tsx b/hosting/src/index.tsx index b05018b..a23a04a 100644 --- a/hosting/src/index.tsx +++ b/hosting/src/index.tsx @@ -1,12 +1,15 @@ import React from "react"; import ReactDOM from "react-dom/client"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { HomePage } from "./components/pages/HomePage"; import { ErrorPage } from "./components/pages/ErrorPage"; import { ProtectedPageTemplate } from "./components/templates/ProtectedPageTemplate"; import reportWebVitals from "./reportWebVitals"; import "./index.css"; +const queryClient = new QueryClient(); + const router = createBrowserRouter([ { path: "/", @@ -27,7 +30,9 @@ const root = ReactDOM.createRoot( root.render( - + + + ); diff --git a/hosting/src/services/GitHubService.ts b/hosting/src/services/GitHubService.ts new file mode 100644 index 0000000..9d866ee --- /dev/null +++ b/hosting/src/services/GitHubService.ts @@ -0,0 +1,24 @@ +import { IGitHubUser } from "../interfaces/github/IGitHubUser"; +import { IGitHubRepository } from "../interfaces/github/IGitHubRepository"; + +export class GitHubService { + private static BASE_URL = "https://api.github.com"; + + private async makeRequest(path: string): Promise { + const res = await fetch(`${GitHubService.BASE_URL}/${path}`); + + return res.json(); + } + + async getUserById(id: number): Promise { + const [user] = await this.makeRequest(`users?since=${id - 1}&per_page=1`); + + return user; + } + + async getUsersRepositories(username: string): Promise { + return this.makeRequest(`users/${username.toLowerCase()}/repos`); + } +} + +export const githubService = new GitHubService(); From fd4c59e6e1ee3911e38f9723278c37f3b8f3cc1c Mon Sep 17 00:00:00 2001 From: LamboCreeper <12111454+LamboCreeper@users.noreply.github.com> Date: Sat, 4 May 2024 14:33:59 +0100 Subject: [PATCH 03/14] Update README --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index dcf84ac..4a19283 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ # Code Review Tool +## Getting Started 1. Set the correct Node version via `nvm use` 2. Start the Firebase Emulators via `npm run dev` 3. In another Terminal, start the React development server by running `npm start` within the `hosting` directory - In order to authenticate locally, you will need to use `127.0.0.1` rather than `localhost` + +## Project Guidelines + +### React Query Usage +We use the [React Query](https://tanstack.com/query/latest/docs/framework/react/overview) library for handling caching and synchronisation of our API calls. For consistency, we use the format `[, , ]` for our [query keys](https://tanstack.com/query/latest/docs/framework/react/guides/query-keys) (for example: `["github", "repositories", "example-user"]`). We ask that you keep the implementation of [query functions](https://tanstack.com/query/latest/docs/framework/react/guides/query-functions) slim, placing most the logic in the service layer. From cc02ee9ddbbe198a53a79f8354dd427760f6efc9 Mon Sep 17 00:00:00 2001 From: LamboCreeper <12111454+LamboCreeper@users.noreply.github.com> Date: Sat, 4 May 2024 16:19:35 +0100 Subject: [PATCH 04/14] Set access token in session --- hosting/.env.example | 1 + hosting/src/hooks/useUser.ts | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/hosting/.env.example b/hosting/.env.example index 3aa0b9d..ac61adf 100644 --- a/hosting/.env.example +++ b/hosting/.env.example @@ -2,3 +2,4 @@ REACT_APP_ENV=local REACT_APP_FIREBASE_AUTH_EMULATOR_URL=http://127.0.0.1:9099 REACT_APP_FIREBASE_FIRESTORE_EMULATOR_HOST=127.0.0.1 REACT_APP_FIREBASE_FIRESTORE_EMULATOR_PORT=8080 +REACT_APP_LOCAL_GITHUB_TOKEN=ghp_asdfghjklmnbvcxzqwertyuiop diff --git a/hosting/src/hooks/useUser.ts b/hosting/src/hooks/useUser.ts index 40bf31f..46a9f52 100644 --- a/hosting/src/hooks/useUser.ts +++ b/hosting/src/hooks/useUser.ts @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import { getAuth, onAuthStateChanged, type User } from "firebase/auth"; +import { getAuth, onAuthStateChanged, getRedirectResult, GithubAuthProvider, type User } from "firebase/auth"; import { app } from "../firebase"; const auth = getAuth(app); @@ -8,6 +8,16 @@ export function useUser() { const [user, setUser] = useState(); useEffect(() => { + (async () => { + const result = await getRedirectResult(auth); + + if (!result) return; + + const credential = GithubAuthProvider.credentialFromResult(result); + + sessionStorage.setItem("github_token", process.env.REACT_APP_LOCAL_GITHUB_TOKEN ?? credential!.accessToken!); + })(); + const listener = onAuthStateChanged(auth, (user) => { setUser(user ?? undefined); }); From e082fda19b61dc75f4d4c81301c89e69143e0332 Mon Sep 17 00:00:00 2001 From: LamboCreeper <12111454+LamboCreeper@users.noreply.github.com> Date: Sat, 4 May 2024 16:19:51 +0100 Subject: [PATCH 05/14] Use GitHub token and add method to get branches --- hosting/src/services/GitHubService.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/hosting/src/services/GitHubService.ts b/hosting/src/services/GitHubService.ts index 9d866ee..4eb4ebb 100644 --- a/hosting/src/services/GitHubService.ts +++ b/hosting/src/services/GitHubService.ts @@ -5,7 +5,11 @@ export class GitHubService { private static BASE_URL = "https://api.github.com"; private async makeRequest(path: string): Promise { - const res = await fetch(`${GitHubService.BASE_URL}/${path}`); + const res = await fetch(`${GitHubService.BASE_URL}/${path}`, { + headers: { + "Authorization": `Bearer ${sessionStorage.getItem("github_token")}` + } + }); return res.json(); } @@ -19,6 +23,10 @@ export class GitHubService { async getUsersRepositories(username: string): Promise { return this.makeRequest(`users/${username.toLowerCase()}/repos`); } + + async getRepositoryBranches(username: string, repository: string): Promise { + return this.makeRequest(`repos/${username.toLowerCase()}/${repository.toLowerCase()}/branches`); + } } export const githubService = new GitHubService(); From 5f6dbb5a28199c2892a9753e3608c357d465a52f Mon Sep 17 00:00:00 2001 From: LamboCreeper <12111454+LamboCreeper@users.noreply.github.com> Date: Sat, 4 May 2024 16:21:38 +0100 Subject: [PATCH 06/14] Use GitHub token and add method to get branches --- hosting/src/interfaces/github/IGitHubBranch.ts | 8 ++++++++ hosting/src/services/GitHubService.ts | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 hosting/src/interfaces/github/IGitHubBranch.ts diff --git a/hosting/src/interfaces/github/IGitHubBranch.ts b/hosting/src/interfaces/github/IGitHubBranch.ts new file mode 100644 index 0000000..ee437d5 --- /dev/null +++ b/hosting/src/interfaces/github/IGitHubBranch.ts @@ -0,0 +1,8 @@ +export interface IGitHubBranch { + name: string; + protected: boolean; + commit: { + sha: string; + url: string; + } +} diff --git a/hosting/src/services/GitHubService.ts b/hosting/src/services/GitHubService.ts index 4eb4ebb..10a7bfb 100644 --- a/hosting/src/services/GitHubService.ts +++ b/hosting/src/services/GitHubService.ts @@ -1,5 +1,6 @@ import { IGitHubUser } from "../interfaces/github/IGitHubUser"; import { IGitHubRepository } from "../interfaces/github/IGitHubRepository"; +import { IGitHubBranch } from "../interfaces/github/IGitHubBranch"; export class GitHubService { private static BASE_URL = "https://api.github.com"; @@ -24,7 +25,7 @@ export class GitHubService { return this.makeRequest(`users/${username.toLowerCase()}/repos`); } - async getRepositoryBranches(username: string, repository: string): Promise { + async getRepositoryBranches(username: string, repository: string): Promise { return this.makeRequest(`repos/${username.toLowerCase()}/${repository.toLowerCase()}/branches`); } } From 3bf873b1120d6134e5afbf7f62d84b8b69c7a6ef Mon Sep 17 00:00:00 2001 From: LamboCreeper <12111454+LamboCreeper@users.noreply.github.com> Date: Sat, 4 May 2024 16:22:21 +0100 Subject: [PATCH 07/14] Add Dropdown components --- README.md | 3 ++ hosting/src/components/atoms/Dropdown.tsx | 49 +++++++++++++++++++ .../molecules/DropdownWithTitle.tsx | 19 +++++++ 3 files changed, 71 insertions(+) create mode 100644 hosting/src/components/atoms/Dropdown.tsx create mode 100644 hosting/src/components/molecules/DropdownWithTitle.tsx diff --git a/README.md b/README.md index 4a19283..2ae3522 100644 --- a/README.md +++ b/README.md @@ -8,5 +8,8 @@ ## Project Guidelines +### Components Structure +We follow the [Atomic Design](https://bradfrost.com/blog/post/atomic-web-design/) methodology for organising our components. It provides a clear way to structure consistent and scalable components which relates to their final context. + ### React Query Usage We use the [React Query](https://tanstack.com/query/latest/docs/framework/react/overview) library for handling caching and synchronisation of our API calls. For consistency, we use the format `[, , ]` for our [query keys](https://tanstack.com/query/latest/docs/framework/react/guides/query-keys) (for example: `["github", "repositories", "example-user"]`). We ask that you keep the implementation of [query functions](https://tanstack.com/query/latest/docs/framework/react/guides/query-functions) slim, placing most the logic in the service layer. diff --git a/hosting/src/components/atoms/Dropdown.tsx b/hosting/src/components/atoms/Dropdown.tsx new file mode 100644 index 0000000..fd73b3f --- /dev/null +++ b/hosting/src/components/atoms/Dropdown.tsx @@ -0,0 +1,49 @@ +import { useEffect, useState, type ChangeEventHandler } from "react"; + +export interface IDropdownOption { + id: string | number; + text: string | number; + value: string | number; + default?: boolean; + disabled?: boolean; +} + +interface IProps { + options: IDropdownOption[]; + handleOnChange: ChangeEventHandler; +} + +export function Dropdown({ options, handleOnChange }: IProps) { + const [modifiedOptions, setModifiedOptions] = useState(options); + + useEffect(() => { + const hasDefault = !!options.find(option => option.default); + + if (!hasDefault) { + setModifiedOptions([{ + id: "__", + text: "Select One", + value: "Select One", + default: true, + disabled: true + }, ...options]); + } else { + setModifiedOptions(options); + } + }, [options]); + + return ( + + ) +} diff --git a/hosting/src/components/molecules/DropdownWithTitle.tsx b/hosting/src/components/molecules/DropdownWithTitle.tsx new file mode 100644 index 0000000..5e984c0 --- /dev/null +++ b/hosting/src/components/molecules/DropdownWithTitle.tsx @@ -0,0 +1,19 @@ +import type { ChangeEventHandler } from "react"; +import { Dropdown, IDropdownOption } from "../atoms/Dropdown"; + +interface IProps { + title: string; + description: string; + options: IDropdownOption[]; + handleOnChange: ChangeEventHandler; +} + +export function DropdownWithTitle({ title, description, options, handleOnChange }: IProps) { + return ( +
+

{title}

+

{description}

+ +
+ ) +} From 3247fe9d6680d1db1b1dad47189aae96725a9694 Mon Sep 17 00:00:00 2001 From: LamboCreeper <12111454+LamboCreeper@users.noreply.github.com> Date: Sat, 4 May 2024 16:22:51 +0100 Subject: [PATCH 08/14] Select repository and branch page --- .../components/pages/ImportRepositoryPage.tsx | 65 +++++++++++++++++++ hosting/src/index.tsx | 5 ++ 2 files changed, 70 insertions(+) create mode 100644 hosting/src/components/pages/ImportRepositoryPage.tsx diff --git a/hosting/src/components/pages/ImportRepositoryPage.tsx b/hosting/src/components/pages/ImportRepositoryPage.tsx new file mode 100644 index 0000000..0023d06 --- /dev/null +++ b/hosting/src/components/pages/ImportRepositoryPage.tsx @@ -0,0 +1,65 @@ +import { DropdownWithTitle} from "../molecules/DropdownWithTitle"; +import { useEffect, useState } from "react"; +import { useUser } from "../../hooks/useUser"; +import { useQuery } from "@tanstack/react-query"; +import { githubService } from "../../services/GitHubService"; + +export function ImportRepositoryPage() { + const [user] = useUser(); + const [chosenRepository, setChosenRepository] = useState(); + const [chosenBranch, setChosenBranch] = useState(); + + const { data: gitUser } = useQuery({ + queryKey: ["github", "users", user?.providerData[0].uid], + queryFn: async () => githubService.getUserById(+user!.providerData[0].uid), + enabled: !!user + }); + + const { data: gitRepositories } = useQuery({ + queryKey: ["github", "repositories", gitUser?.login.toLowerCase()], + queryFn: async () => githubService.getUsersRepositories(gitUser!.login), + enabled: !!gitUser, + }); + + const { data: gitBranches } = useQuery({ + queryKey: ["github", "branches", gitUser?.login.toLowerCase(), chosenRepository], + queryFn: async () => githubService.getRepositoryBranches(gitUser!.login, chosenRepository!), + enabled: !!chosenRepository + }); + + useEffect(() => { + if (chosenRepository) { + setChosenBranch(gitRepositories?.find(repo => repo.name.toLowerCase() === chosenRepository.toLowerCase())?.default_branch) + } + }, [chosenRepository, gitRepositories]); + + return ( +
+

Import A Repository

+ ({ + id: repo.id, + text: repo.name, + value: repo.name + })) ?? []} + handleOnChange={selected => setChosenRepository(selected.target.value)} + /> + ({ + id: branch.name, + text: branch.name, + value: branch.name, + default: branch.name.toLowerCase() === chosenBranch?.toLowerCase() + })) ?? []} + handleOnChange={selected => setChosenBranch(selected.target.value)} + /> + +
+ ); +} diff --git a/hosting/src/index.tsx b/hosting/src/index.tsx index a23a04a..4f4c530 100644 --- a/hosting/src/index.tsx +++ b/hosting/src/index.tsx @@ -5,6 +5,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { HomePage } from "./components/pages/HomePage"; import { ErrorPage } from "./components/pages/ErrorPage"; import { ProtectedPageTemplate } from "./components/templates/ProtectedPageTemplate"; +import { ImportRepositoryPage } from "./components/pages/ImportRepositoryPage"; import reportWebVitals from "./reportWebVitals"; import "./index.css"; @@ -19,6 +20,10 @@ const router = createBrowserRouter([ { path: "/", element: , + }, + { + path: "/repositories/import", + element: } ] } From a9462dcbb50140f96dd1a6466b4ec28be2376432 Mon Sep 17 00:00:00 2001 From: LamboCreeper <12111454+LamboCreeper@users.noreply.github.com> Date: Fri, 10 May 2024 21:45:45 +0100 Subject: [PATCH 09/14] Replace temporary repository list with link to import --- hosting/src/components/pages/HomePage.tsx | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/hosting/src/components/pages/HomePage.tsx b/hosting/src/components/pages/HomePage.tsx index ca21caf..7e4e6f9 100644 --- a/hosting/src/components/pages/HomePage.tsx +++ b/hosting/src/components/pages/HomePage.tsx @@ -1,21 +1,8 @@ -import { useQuery } from "@tanstack/react-query"; +import { Link } from "react-router-dom"; import { useUser } from "../../hooks/useUser"; -import { githubService } from "../../services/GitHubService"; -import { IGitHubRepository } from "../../interfaces/github/IGitHubRepository"; export function HomePage() { const [user] = useUser(); - const { data: gitUser } = useQuery({ - queryKey: ["github", "users", user?.providerData[0].uid], - queryFn: async () => githubService.getUserById(+user!.providerData[0].uid), - enabled: !!user - }); - - const { data: gitRepositories } = useQuery({ - queryKey: ["github", "repositories", gitUser?.login.toLowerCase()], - queryFn: async () => githubService.getUsersRepositories(gitUser!.login), - enabled: !!gitUser - }); return (
@@ -27,9 +14,8 @@ export function HomePage() {

Hello {user.displayName}

{JSON.stringify(user)}

Repositories

-
    - {gitRepositories?.map((repo: IGitHubRepository) =>
  • {repo.name}
  • )} -
+

You have no yet imported any repositories.

+ Import One )}
From 063f44af20d2332747f70a0aff9ff652c3be80d7 Mon Sep 17 00:00:00 2001 From: LamboCreeper <12111454+LamboCreeper@users.noreply.github.com> Date: Fri, 10 May 2024 22:35:43 +0100 Subject: [PATCH 10/14] Add repository and model for GitRepository --- hosting/src/models/BaseModel.ts | 4 +++- hosting/src/models/GitRepositoryModel.ts | 13 +++++++++++++ hosting/src/repositories/BaseRepository.ts | 5 +++-- hosting/src/repositories/GitRepositoryRepository.ts | 10 ++++++++++ 4 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 hosting/src/models/GitRepositoryModel.ts create mode 100644 hosting/src/repositories/GitRepositoryRepository.ts diff --git a/hosting/src/models/BaseModel.ts b/hosting/src/models/BaseModel.ts index f05f7f9..62acfde 100644 --- a/hosting/src/models/BaseModel.ts +++ b/hosting/src/models/BaseModel.ts @@ -1,3 +1,5 @@ +import type { FieldValue } from "@firebase/firestore"; + export abstract class BaseModel { - created!: Date; + created!: FieldValue; } diff --git a/hosting/src/models/GitRepositoryModel.ts b/hosting/src/models/GitRepositoryModel.ts new file mode 100644 index 0000000..8654914 --- /dev/null +++ b/hosting/src/models/GitRepositoryModel.ts @@ -0,0 +1,13 @@ +import { BaseModel } from "./BaseModel"; + +export class GitRepositoryModel extends BaseModel { + git_url!: string; + + branch!: string; + + commit!: string; + + user!: string; + + reviewers!: string[]; +} diff --git a/hosting/src/repositories/BaseRepository.ts b/hosting/src/repositories/BaseRepository.ts index 7e7a23c..d88c04a 100644 --- a/hosting/src/repositories/BaseRepository.ts +++ b/hosting/src/repositories/BaseRepository.ts @@ -17,10 +17,11 @@ export abstract class BaseRepository { return snapshot.data(); } - async create(data: T): Promise { + async create(data: T): Promise { const ref = collection(this.firestore, this.collection); + const doc = await addDoc(ref, data); - await addDoc(ref, data); + return doc.id; } async createWithId(id: string, data: T): Promise { diff --git a/hosting/src/repositories/GitRepositoryRepository.ts b/hosting/src/repositories/GitRepositoryRepository.ts new file mode 100644 index 0000000..f19e813 --- /dev/null +++ b/hosting/src/repositories/GitRepositoryRepository.ts @@ -0,0 +1,10 @@ +import { BaseRepository } from "./BaseRepository"; +import { GitRepositoryModel } from "../models/GitRepositoryModel"; + +export class GitRepositoryRepository extends BaseRepository { + constructor() { + super("crt_repositories"); + } +} + +export const gitRepositoryRepository = new GitRepositoryRepository(); From 45b844914d5307a6cd0bcc83a579278aaa64a3e7 Mon Sep 17 00:00:00 2001 From: LamboCreeper <12111454+LamboCreeper@users.noreply.github.com> Date: Fri, 10 May 2024 22:36:03 +0100 Subject: [PATCH 11/14] Update Secuity Rules for crt_repositories collection --- firestore.rules | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/firestore.rules b/firestore.rules index cd1a434..ff7c448 100644 --- a/firestore.rules +++ b/firestore.rules @@ -5,5 +5,10 @@ service cloud.firestore { match /{document=**} { allow read, write: if false; } + + match /crt_repositories/{repository} { + allow read: if (request.auth.uid in request.resource.data.reviewers) || (request.auth.uid == request.resource.data.user); + allow write: if request.auth.uid == request.resource.data.user; + } } -} \ No newline at end of file +} From 5dff556e494431bab998eb02ccce26dca569af54 Mon Sep 17 00:00:00 2001 From: LamboCreeper <12111454+LamboCreeper@users.noreply.github.com> Date: Fri, 10 May 2024 22:36:30 +0100 Subject: [PATCH 12/14] Create repository when next is pressed --- .../components/pages/ImportRepositoryPage.tsx | 46 +++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/hosting/src/components/pages/ImportRepositoryPage.tsx b/hosting/src/components/pages/ImportRepositoryPage.tsx index 0023d06..4b711b5 100644 --- a/hosting/src/components/pages/ImportRepositoryPage.tsx +++ b/hosting/src/components/pages/ImportRepositoryPage.tsx @@ -1,13 +1,18 @@ -import { DropdownWithTitle} from "../molecules/DropdownWithTitle"; import { useEffect, useState } from "react"; +import { useMutation, useQuery } from "@tanstack/react-query"; +import { serverTimestamp } from "firebase/firestore"; +import { useNavigate } from "react-router-dom"; +import { DropdownWithTitle } from "../molecules/DropdownWithTitle"; import { useUser } from "../../hooks/useUser"; -import { useQuery } from "@tanstack/react-query"; import { githubService } from "../../services/GitHubService"; +import { gitRepositoryRepository } from "../../repositories/GitRepositoryRepository"; +import { GitRepositoryModel } from "../../models/GitRepositoryModel"; export function ImportRepositoryPage() { const [user] = useUser(); const [chosenRepository, setChosenRepository] = useState(); const [chosenBranch, setChosenBranch] = useState(); + const navigate = useNavigate(); const { data: gitUser } = useQuery({ queryKey: ["github", "users", user?.providerData[0].uid], @@ -27,12 +32,47 @@ export function ImportRepositoryPage() { enabled: !!chosenRepository }); + const repositoryMutation = useMutation({ + mutationFn: async (newRepository: GitRepositoryModel) => gitRepositoryRepository.create(newRepository), + onSuccess: (documentId) => { + navigate(`../review/${documentId}`); + } + }); + useEffect(() => { if (chosenRepository) { setChosenBranch(gitRepositories?.find(repo => repo.name.toLowerCase() === chosenRepository.toLowerCase())?.default_branch) } }, [chosenRepository, gitRepositories]); + const handleCreateRepository = async () => { + if (!chosenRepository && !chosenBranch) return; + + try { + const repo = gitRepositories?.find(({ name }) => name === chosenRepository); + const branch = gitBranches?.find(({ name }) => name === chosenBranch); + + if (!repo || !branch || !user) { + alert("Unable to create repository. Please be authenticated and select a repository and branch."); + + return; + } + + console.log("Creating..."); + + repositoryMutation.mutate({ + created: serverTimestamp(), + git_url: repo.git_url.toLowerCase(), + branch: branch.name, + commit: branch.commit.sha, + user: user.uid, + reviewers: [] + }); + } catch (error) { + console.error(error); + } + }; + return (

Import A Repository

@@ -57,7 +97,7 @@ export function ImportRepositoryPage() { })) ?? []} handleOnChange={selected => setChosenBranch(selected.target.value)} /> -
From c3d47da2cef16add36344713b4de31c277732ed0 Mon Sep 17 00:00:00 2001 From: LamboCreeper <12111454+LamboCreeper@users.noreply.github.com> Date: Sat, 11 May 2024 11:10:27 +0100 Subject: [PATCH 13/14] Remove useFirestore hook and fix typo --- hosting/src/components/pages/HomePage.tsx | 2 +- hosting/src/hooks/useFirestore.ts | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) delete mode 100644 hosting/src/hooks/useFirestore.ts diff --git a/hosting/src/components/pages/HomePage.tsx b/hosting/src/components/pages/HomePage.tsx index 7e4e6f9..8419f29 100644 --- a/hosting/src/components/pages/HomePage.tsx +++ b/hosting/src/components/pages/HomePage.tsx @@ -14,7 +14,7 @@ export function HomePage() {

Hello {user.displayName}

{JSON.stringify(user)}

Repositories

-

You have no yet imported any repositories.

+

You have not yet imported any repositories.

Import One )} diff --git a/hosting/src/hooks/useFirestore.ts b/hosting/src/hooks/useFirestore.ts deleted file mode 100644 index e8cf89e..0000000 --- a/hosting/src/hooks/useFirestore.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { BaseModel } from "../models/BaseModel"; -import { IRepository } from "../interfaces/IRepository"; -import { BaseRepository } from "../repositories/BaseRepository"; - -export function useFirestore(repository: IRepository): [BaseRepository] { - return [new repository()]; -} From 57c7d58505906930aeca69da18b7ec1f85327c6a Mon Sep 17 00:00:00 2001 From: LamboCreeper <12111454+LamboCreeper@users.noreply.github.com> Date: Fri, 24 May 2024 22:17:05 +0100 Subject: [PATCH 14/14] Sara's comments --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9a1bb92..694d787 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ 1. Navigate into the `hosting` directory 2. Run `npm i` 3. Create a `.env` file based off of `.env.sample` - - You can get a GitHub token from [here](https://github.com/settings/tokens) + - You can get a GitHub token from [here](https://github.com/settings/tokens) - please ensure it has the scopes `public_repo`, `read:user` and `user:email` 4. Start the React development server via `npm start` - In order to authenticate locally, you will need to use `127.0.0.1` rather than `localhost`