From 6c04a0e607e65e134619cb42f1c03343c72fdfc9 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Fri, 31 Oct 2025 16:54:54 +0900 Subject: [PATCH 01/21] fix: normalize UI RepositoryData types and props --- src/ui/services/repo.ts | 2 +- src/ui/types.ts | 16 ++++++++++++++++ src/ui/views/RepoDetails/RepoDetails.tsx | 19 ++++--------------- src/ui/views/RepoList/Components/NewRepo.tsx | 14 +------------- .../RepoList/Components/RepoOverview.tsx | 7 ++++++- .../RepoList/Components/Repositories.tsx | 3 ++- src/ui/views/RepoList/repositories.types.ts | 15 --------------- 7 files changed, 30 insertions(+), 46 deletions(-) create mode 100644 src/ui/types.ts delete mode 100644 src/ui/views/RepoList/repositories.types.ts diff --git a/src/ui/services/repo.ts b/src/ui/services/repo.ts index 5b168e882..5224e0f1a 100644 --- a/src/ui/services/repo.ts +++ b/src/ui/services/repo.ts @@ -1,7 +1,7 @@ import axios from 'axios'; import { getAxiosConfig, processAuthError } from './auth.js'; import { API_BASE } from '../apiBase'; -import { RepositoryData, RepositoryDataWithId } from '../views/RepoList/Components/NewRepo'; +import { RepositoryData, RepositoryDataWithId } from '../types'; const API_V1_BASE = `${API_BASE}/api/v1`; diff --git a/src/ui/types.ts b/src/ui/types.ts new file mode 100644 index 000000000..19d7c3fb8 --- /dev/null +++ b/src/ui/types.ts @@ -0,0 +1,16 @@ +export interface RepositoryData { + _id?: string; + project: string; + name: string; + url: string; + maxUser: number; + lastModified?: string; + dateCreated?: string; + proxyURL?: string; + users?: { + canPush?: string[]; + canAuthorise?: string[]; + }; +} + +export type RepositoryDataWithId = Required> & RepositoryData; diff --git a/src/ui/views/RepoDetails/RepoDetails.tsx b/src/ui/views/RepoDetails/RepoDetails.tsx index cb62e8008..a3175f203 100644 --- a/src/ui/views/RepoDetails/RepoDetails.tsx +++ b/src/ui/views/RepoDetails/RepoDetails.tsx @@ -23,18 +23,7 @@ import CodeActionButton from '../../components/CustomButtons/CodeActionButton'; import { trimTrailingDotGit } from '../../../db/helper'; import { fetchRemoteRepositoryData } from '../../utils'; import { SCMRepositoryMetadata } from '../../../types/models'; - -interface RepoData { - _id: string; - project: string; - name: string; - proxyURL: string; - url: string; - users: { - canAuthorise: string[]; - canPush: string[]; - }; -} +import { RepositoryDataWithId } from '../../types'; export interface UserContextType { user: { @@ -57,7 +46,7 @@ const useStyles = makeStyles((theme) => ({ const RepoDetails: React.FC = () => { const navigate = useNavigate(); const classes = useStyles(); - const [data, setData] = useState(null); + const [data, setData] = useState(null); const [, setAuth] = useState(true); const [isLoading, setIsLoading] = useState(true); const [isError, setIsError] = useState(false); @@ -197,7 +186,7 @@ const RepoDetails: React.FC = () => { - {data.users.canAuthorise.map((row) => ( + {data.users?.canAuthorise?.map((row) => ( {row} @@ -240,7 +229,7 @@ const RepoDetails: React.FC = () => { - {data.users.canPush.map((row) => ( + {data.users?.canPush?.map((row) => ( {row} diff --git a/src/ui/views/RepoList/Components/NewRepo.tsx b/src/ui/views/RepoList/Components/NewRepo.tsx index 6758a1bb1..fa12355d6 100644 --- a/src/ui/views/RepoList/Components/NewRepo.tsx +++ b/src/ui/views/RepoList/Components/NewRepo.tsx @@ -15,6 +15,7 @@ import { addRepo } from '../../../services/repo'; import { makeStyles } from '@material-ui/core/styles'; import styles from '../../../assets/jss/material-dashboard-react/views/dashboardStyle'; import { RepoIcon } from '@primer/octicons-react'; +import { RepositoryData, RepositoryDataWithId } from '../../../types'; interface AddRepositoryDialogProps { open: boolean; @@ -22,19 +23,6 @@ interface AddRepositoryDialogProps { onSuccess: (data: RepositoryDataWithId) => void; } -export interface RepositoryData { - _id?: string; - project: string; - name: string; - url: string; - maxUser: number; - lastModified?: string; - dateCreated?: string; - proxyURL?: string; -} - -export type RepositoryDataWithId = Required> & RepositoryData; - interface NewRepoProps { onSuccess: (data: RepositoryDataWithId) => Promise; } diff --git a/src/ui/views/RepoList/Components/RepoOverview.tsx b/src/ui/views/RepoList/Components/RepoOverview.tsx index 2191c05db..671a5cb92 100644 --- a/src/ui/views/RepoList/Components/RepoOverview.tsx +++ b/src/ui/views/RepoList/Components/RepoOverview.tsx @@ -5,10 +5,15 @@ import GridItem from '../../../components/Grid/GridItem'; import { CodeReviewIcon, LawIcon, PeopleIcon } from '@primer/octicons-react'; import CodeActionButton from '../../../components/CustomButtons/CodeActionButton'; import { languageColors } from '../../../../constants/languageColors'; -import { RepositoriesProps } from '../repositories.types'; +import { RepositoryDataWithId } from '../../../types'; import { fetchRemoteRepositoryData } from '../../../utils'; import { SCMRepositoryMetadata } from '../../../../types/models'; +export interface RepositoriesProps { + data: RepositoryDataWithId; + [key: string]: unknown; +} + const Repositories: React.FC = (props) => { const [remoteRepoData, setRemoteRepoData] = React.useState(null); const [errorMessage] = React.useState(''); diff --git a/src/ui/views/RepoList/Components/Repositories.tsx b/src/ui/views/RepoList/Components/Repositories.tsx index fe93eb766..44d63fe28 100644 --- a/src/ui/views/RepoList/Components/Repositories.tsx +++ b/src/ui/views/RepoList/Components/Repositories.tsx @@ -8,7 +8,8 @@ import styles from '../../../assets/jss/material-dashboard-react/views/dashboard import { getRepos } from '../../../services/repo'; import GridContainer from '../../../components/Grid/GridContainer'; import GridItem from '../../../components/Grid/GridItem'; -import NewRepo, { RepositoryDataWithId } from './NewRepo'; +import NewRepo from './NewRepo'; +import { RepositoryDataWithId } from '../../../types'; import RepoOverview from './RepoOverview'; import { UserContext } from '../../../../context'; import Search from '../../../components/Search/Search'; diff --git a/src/ui/views/RepoList/repositories.types.ts b/src/ui/views/RepoList/repositories.types.ts deleted file mode 100644 index 2e7660147..000000000 --- a/src/ui/views/RepoList/repositories.types.ts +++ /dev/null @@ -1,15 +0,0 @@ -export interface RepositoriesProps { - data: { - _id: string; - project: string; - name: string; - url: string; - proxyURL: string; - users?: { - canPush?: string[]; - canAuthorise?: string[]; - }; - }; - - [key: string]: unknown; -} From 4e91205b5fea40d50740f1e28b1003b8b30cebc0 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Fri, 31 Oct 2025 17:25:23 +0900 Subject: [PATCH 02/21] refactor: remove duplicate commitTs and CommitData --- packages/git-proxy-cli/index.ts | 1 - packages/git-proxy-cli/test/testCliUtils.ts | 1 - src/types/models.ts | 13 +------------ src/ui/utils.tsx | 2 +- .../OpenPushRequests/components/PushesTable.tsx | 5 ++--- src/ui/views/PushDetails/PushDetails.tsx | 4 ++-- 6 files changed, 6 insertions(+), 20 deletions(-) diff --git a/packages/git-proxy-cli/index.ts b/packages/git-proxy-cli/index.ts index 5536785f0..1a3bf3443 100644 --- a/packages/git-proxy-cli/index.ts +++ b/packages/git-proxy-cli/index.ts @@ -141,7 +141,6 @@ async function getGitPushes(filters: Partial) { commitTimestamp: pushCommitDataRecord.commitTimestamp, tree: pushCommitDataRecord.tree, parent: pushCommitDataRecord.parent, - commitTs: pushCommitDataRecord.commitTs, }); }); record.commitData = commitData; diff --git a/packages/git-proxy-cli/test/testCliUtils.ts b/packages/git-proxy-cli/test/testCliUtils.ts index fd733f7e4..a99f33bec 100644 --- a/packages/git-proxy-cli/test/testCliUtils.ts +++ b/packages/git-proxy-cli/test/testCliUtils.ts @@ -221,7 +221,6 @@ async function addGitPushToDb( parent: 'parent', author: 'author', committer: 'committer', - commitTs: 'commitTs', message: 'message', authorEmail: 'authorEmail', committerEmail: 'committerEmail', diff --git a/src/types/models.ts b/src/types/models.ts index d583ebd76..3f199cd6c 100644 --- a/src/types/models.ts +++ b/src/types/models.ts @@ -1,5 +1,6 @@ import { StepData } from '../proxy/actions/Step'; import { AttestationData } from '../ui/views/PushDetails/attestation.types'; +import { CommitData } from '../proxy/processors/types'; export interface UserData { id: string; @@ -12,18 +13,6 @@ export interface UserData { admin?: boolean; } -export interface CommitData { - commitTs?: number; - message: string; - committer: string; - committerEmail: string; - tree?: string; - parent?: string; - author: string; - authorEmail: string; - commitTimestamp?: number; -} - export interface PushData { id: string; url: string; diff --git a/src/ui/utils.tsx b/src/ui/utils.tsx index 20740013f..0ae7e2167 100644 --- a/src/ui/utils.tsx +++ b/src/ui/utils.tsx @@ -1,11 +1,11 @@ import axios from 'axios'; import React from 'react'; import { - CommitData, GitHubRepositoryMetadata, GitLabRepositoryMetadata, SCMRepositoryMetadata, } from '../types/models'; +import { CommitData } from '../proxy/processors/types'; import moment from 'moment'; /** diff --git a/src/ui/views/OpenPushRequests/components/PushesTable.tsx b/src/ui/views/OpenPushRequests/components/PushesTable.tsx index 8a15469d0..e8f6f45a7 100644 --- a/src/ui/views/OpenPushRequests/components/PushesTable.tsx +++ b/src/ui/views/OpenPushRequests/components/PushesTable.tsx @@ -106,13 +106,12 @@ const PushesTable: React.FC = (props) => { // may be used to resolve users to profile links in future // const gitProvider = getGitProvider(repoUrl); // const hostname = new URL(repoUrl).hostname; - const commitTimestamp = - row.commitData[0]?.commitTs || row.commitData[0]?.commitTimestamp; + const commitTimestamp = row.commitData[0]?.commitTimestamp; return ( - {commitTimestamp ? moment.unix(commitTimestamp).toString() : 'N/A'} + {commitTimestamp ? moment.unix(Number(commitTimestamp)).toString() : 'N/A'} diff --git a/src/ui/views/PushDetails/PushDetails.tsx b/src/ui/views/PushDetails/PushDetails.tsx index 32fa31610..54f82ead2 100644 --- a/src/ui/views/PushDetails/PushDetails.tsx +++ b/src/ui/views/PushDetails/PushDetails.tsx @@ -309,9 +309,9 @@ const Dashboard: React.FC = () => { {data.commitData.map((c) => ( - + - {moment.unix(c.commitTs || c.commitTimestamp || 0).toString()} + {moment.unix(Number(c.commitTimestamp || 0)).toString()} {generateEmailLink(c.committer, c.committerEmail)} {generateEmailLink(c.author, c.authorEmail)} From b5356ac38fc737f03d446fc73c6c21454d181196 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Fri, 31 Oct 2025 18:20:11 +0900 Subject: [PATCH 03/21] refactor: unify attestation-related types --- src/types/models.ts | 4 +-- src/ui/services/config.ts | 4 +-- src/ui/types.ts | 27 +++++++++++++++++++ src/ui/views/PushDetails/attestation.types.ts | 21 --------------- .../PushDetails/components/Attestation.tsx | 5 ++-- .../components/AttestationForm.tsx | 21 +++------------ .../components/AttestationView.tsx | 8 +++++- 7 files changed, 44 insertions(+), 46 deletions(-) delete mode 100644 src/ui/views/PushDetails/attestation.types.ts diff --git a/src/types/models.ts b/src/types/models.ts index 3f199cd6c..6f30fec94 100644 --- a/src/types/models.ts +++ b/src/types/models.ts @@ -1,6 +1,6 @@ import { StepData } from '../proxy/actions/Step'; -import { AttestationData } from '../ui/views/PushDetails/attestation.types'; import { CommitData } from '../proxy/processors/types'; +import { AttestationFormData } from '../ui/types'; export interface UserData { id: string; @@ -29,7 +29,7 @@ export interface PushData { rejected?: boolean; blocked?: boolean; authorised?: boolean; - attestation?: AttestationData; + attestation?: AttestationFormData; autoApproved?: boolean; timestamp: string | Date; allowPush?: boolean; diff --git a/src/ui/services/config.ts b/src/ui/services/config.ts index 3ececdc0f..ae5ae0203 100644 --- a/src/ui/services/config.ts +++ b/src/ui/services/config.ts @@ -1,11 +1,11 @@ import axios from 'axios'; import { API_BASE } from '../apiBase'; -import { FormQuestion } from '../views/PushDetails/components/AttestationForm'; +import { QuestionFormData } from '../types'; import { UIRouteAuth } from '../../config/generated/config'; const API_V1_BASE = `${API_BASE}/api/v1`; -const setAttestationConfigData = async (setData: (data: FormQuestion[]) => void) => { +const setAttestationConfigData = async (setData: (data: QuestionFormData[]) => void) => { const url = new URL(`${API_V1_BASE}/config/attestation`); await axios(url.toString()).then((response) => { setData(response.data.questions); diff --git a/src/ui/types.ts b/src/ui/types.ts index 19d7c3fb8..6fbc1bef6 100644 --- a/src/ui/types.ts +++ b/src/ui/types.ts @@ -14,3 +14,30 @@ export interface RepositoryData { } export type RepositoryDataWithId = Required> & RepositoryData; + +interface QuestionTooltipLink { + text: string; + url: string; +} + +interface QuestionTooltip { + text: string; + links?: QuestionTooltipLink[]; +} + +export interface QuestionFormData { + label: string; + checked: boolean; + tooltip: QuestionTooltip; +} + +interface Reviewer { + username: string; + gitAccount: string; +} + +export interface AttestationFormData { + reviewer: Reviewer; + timestamp: string | Date; + questions: QuestionFormData[]; +} diff --git a/src/ui/views/PushDetails/attestation.types.ts b/src/ui/views/PushDetails/attestation.types.ts deleted file mode 100644 index 47efe9de6..000000000 --- a/src/ui/views/PushDetails/attestation.types.ts +++ /dev/null @@ -1,21 +0,0 @@ -interface Question { - label: string; - checked: boolean; -} - -interface Reviewer { - username: string; - gitAccount: string; -} - -export interface AttestationData { - reviewer: Reviewer; - timestamp: string | Date; - questions: Question[]; -} - -export interface AttestationViewProps { - attestation: boolean; - setAttestation: (value: boolean) => void; - data: AttestationData; -} diff --git a/src/ui/views/PushDetails/components/Attestation.tsx b/src/ui/views/PushDetails/components/Attestation.tsx index dc68bf5d2..c405eb2cf 100644 --- a/src/ui/views/PushDetails/components/Attestation.tsx +++ b/src/ui/views/PushDetails/components/Attestation.tsx @@ -4,12 +4,13 @@ import DialogContent from '@material-ui/core/DialogContent'; import DialogActions from '@material-ui/core/DialogActions'; import { CheckCircle, ErrorOutline } from '@material-ui/icons'; import Button from '../../../components/CustomButtons/Button'; -import AttestationForm, { FormQuestion } from './AttestationForm'; +import AttestationForm from './AttestationForm'; import { setAttestationConfigData, setURLShortenerData, setEmailContactData, } from '../../../services/config'; +import { QuestionFormData } from '../../../types'; interface AttestationProps { approveFn: (data: { label: string; checked: boolean }[]) => void; @@ -17,7 +18,7 @@ interface AttestationProps { const Attestation: React.FC = ({ approveFn }) => { const [open, setOpen] = useState(false); - const [formData, setFormData] = useState([]); + const [formData, setFormData] = useState([]); const [urlShortener, setURLShortener] = useState(''); const [contactEmail, setContactEmail] = useState(''); diff --git a/src/ui/views/PushDetails/components/AttestationForm.tsx b/src/ui/views/PushDetails/components/AttestationForm.tsx index 04f794f99..162e34fa9 100644 --- a/src/ui/views/PushDetails/components/AttestationForm.tsx +++ b/src/ui/views/PushDetails/components/AttestationForm.tsx @@ -4,26 +4,11 @@ import { green } from '@material-ui/core/colors'; import { Help } from '@material-ui/icons'; import { Grid, Tooltip, Checkbox, FormGroup, FormControlLabel } from '@material-ui/core'; import { Theme } from '@material-ui/core/styles'; - -interface TooltipLink { - text: string; - url: string; -} - -interface TooltipContent { - text: string; - links?: TooltipLink[]; -} - -export interface FormQuestion { - label: string; - checked: boolean; - tooltip: TooltipContent; -} +import { QuestionFormData } from '../../../types'; interface AttestationFormProps { - formData: FormQuestion[]; - passFormData: (data: FormQuestion[]) => void; + formData: QuestionFormData[]; + passFormData: (data: QuestionFormData[]) => void; } const styles = (theme: Theme) => ({ diff --git a/src/ui/views/PushDetails/components/AttestationView.tsx b/src/ui/views/PushDetails/components/AttestationView.tsx index 60f348a1c..69f790d7d 100644 --- a/src/ui/views/PushDetails/components/AttestationView.tsx +++ b/src/ui/views/PushDetails/components/AttestationView.tsx @@ -11,7 +11,13 @@ import Checkbox from '@material-ui/core/Checkbox'; import { withStyles } from '@material-ui/core/styles'; import { green } from '@material-ui/core/colors'; import { setURLShortenerData } from '../../../services/config'; -import { AttestationViewProps } from '../attestation.types'; +import { AttestationFormData } from '../../../types'; + +export interface AttestationViewProps { + attestation: boolean; + setAttestation: (value: boolean) => void; + data: AttestationFormData; +} const StyledFormControlLabel = withStyles({ root: { From 642de69771bd321ee79249887d0d999d66cbfc19 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Fri, 31 Oct 2025 18:29:26 +0900 Subject: [PATCH 04/21] chore: remove unused UserType and replace with Partial --- src/ui/layouts/Dashboard.tsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/ui/layouts/Dashboard.tsx b/src/ui/layouts/Dashboard.tsx index 777358f42..a788ffd92 100644 --- a/src/ui/layouts/Dashboard.tsx +++ b/src/ui/layouts/Dashboard.tsx @@ -11,18 +11,12 @@ import styles from '../assets/jss/material-dashboard-react/layouts/dashboardStyl import logo from '../assets/img/git-proxy.png'; import { UserContext } from '../../context'; import { getUser } from '../services/user'; -import { Route as RouteType } from '../../types/models'; +import { Route as RouteType, UserData } from '../../types/models'; interface DashboardProps { [key: string]: any; } -interface UserType { - id?: string; - name?: string; - email?: string; -} - let ps: PerfectScrollbar | undefined; let refresh = false; @@ -33,7 +27,7 @@ const Dashboard: React.FC = ({ ...rest }) => { const mainPanel = useRef(null); const [color] = useState<'purple' | 'blue' | 'green' | 'orange' | 'red'>('blue'); const [mobileOpen, setMobileOpen] = useState(false); - const [user, setUser] = useState({}); + const [user, setUser] = useState>({}); const { id } = useParams<{ id?: string }>(); const handleDrawerToggle = () => setMobileOpen((prev) => !prev); From 99ddef17ea773ae10b01968dc50fa791cfe6cfc0 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Sat, 1 Nov 2025 13:40:40 +0900 Subject: [PATCH 05/21] chore: move ui-only types (UserData, PushData, Route) from src/types/models.ts to ui/types.ts --- src/routes.tsx | 2 +- src/types/models.ts | 48 ------------------- src/ui/auth/AuthProvider.tsx | 2 +- .../Navbars/DashboardNavbarLinks.tsx | 2 +- src/ui/components/Navbars/Navbar.tsx | 2 +- src/ui/components/Sidebar/Sidebar.tsx | 2 +- src/ui/layouts/Dashboard.tsx | 2 +- src/ui/services/auth.ts | 2 +- src/ui/services/user.ts | 2 +- src/ui/types.ts | 47 ++++++++++++++++++ .../components/PushesTable.tsx | 2 +- src/ui/views/PushDetails/PushDetails.tsx | 2 +- .../views/RepoDetails/Components/AddUser.tsx | 2 +- src/ui/views/User/UserProfile.tsx | 2 +- src/ui/views/UserList/Components/UserList.tsx | 2 +- 15 files changed, 60 insertions(+), 61 deletions(-) diff --git a/src/routes.tsx b/src/routes.tsx index 43a2ac41c..feb2664de 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -30,7 +30,7 @@ import SettingsView from './ui/views/Settings/Settings'; import { RepoIcon } from '@primer/octicons-react'; import { Group, AccountCircle, Dashboard, Settings } from '@material-ui/icons'; -import { Route } from './types/models'; +import { Route } from './ui/types'; const dashboardRoutes: Route[] = [ { diff --git a/src/types/models.ts b/src/types/models.ts index 6f30fec94..c2b9e94fc 100644 --- a/src/types/models.ts +++ b/src/types/models.ts @@ -1,51 +1,3 @@ -import { StepData } from '../proxy/actions/Step'; -import { CommitData } from '../proxy/processors/types'; -import { AttestationFormData } from '../ui/types'; - -export interface UserData { - id: string; - name: string; - username: string; - email?: string; - displayName?: string; - title?: string; - gitAccount?: string; - admin?: boolean; -} - -export interface PushData { - id: string; - url: string; - repo: string; - branch: string; - commitFrom: string; - commitTo: string; - commitData: CommitData[]; - diff: { - content: string; - }; - error: boolean; - canceled?: boolean; - rejected?: boolean; - blocked?: boolean; - authorised?: boolean; - attestation?: AttestationFormData; - autoApproved?: boolean; - timestamp: string | Date; - allowPush?: boolean; - lastStep?: StepData; -} - -export interface Route { - path: string; - layout: string; - name: string; - rtlName?: string; - component: React.ComponentType; - icon?: string | React.ComponentType; - visible?: boolean; -} - export interface GitHubRepositoryMetadata { description?: string; language?: string; diff --git a/src/ui/auth/AuthProvider.tsx b/src/ui/auth/AuthProvider.tsx index a2409da60..9982ef1e9 100644 --- a/src/ui/auth/AuthProvider.tsx +++ b/src/ui/auth/AuthProvider.tsx @@ -1,6 +1,6 @@ import React, { createContext, useContext, useState, useEffect } from 'react'; import { getUserInfo } from '../services/auth'; -import { UserData } from '../../types/models'; +import { UserData } from '../types'; interface AuthContextType { user: UserData | null; diff --git a/src/ui/components/Navbars/DashboardNavbarLinks.tsx b/src/ui/components/Navbars/DashboardNavbarLinks.tsx index b69cd61c9..7e1dfb982 100644 --- a/src/ui/components/Navbars/DashboardNavbarLinks.tsx +++ b/src/ui/components/Navbars/DashboardNavbarLinks.tsx @@ -16,7 +16,7 @@ import { AccountCircle } from '@material-ui/icons'; import { getUser } from '../../services/user'; import axios from 'axios'; import { getAxiosConfig } from '../../services/auth'; -import { UserData } from '../../../types/models'; +import { UserData } from '../../types'; import { API_BASE } from '../../apiBase'; diff --git a/src/ui/components/Navbars/Navbar.tsx b/src/ui/components/Navbars/Navbar.tsx index 59dc2e110..859b01a50 100644 --- a/src/ui/components/Navbars/Navbar.tsx +++ b/src/ui/components/Navbars/Navbar.tsx @@ -8,7 +8,7 @@ import Hidden from '@material-ui/core/Hidden'; import Menu from '@material-ui/icons/Menu'; import DashboardNavbarLinks from './DashboardNavbarLinks'; import styles from '../../assets/jss/material-dashboard-react/components/headerStyle'; -import { Route } from '../../../types/models'; +import { Route } from '../../types'; const useStyles = makeStyles(styles as any); diff --git a/src/ui/components/Sidebar/Sidebar.tsx b/src/ui/components/Sidebar/Sidebar.tsx index a2f745948..ad698f0b2 100644 --- a/src/ui/components/Sidebar/Sidebar.tsx +++ b/src/ui/components/Sidebar/Sidebar.tsx @@ -9,7 +9,7 @@ import ListItem from '@material-ui/core/ListItem'; import ListItemText from '@material-ui/core/ListItemText'; import Icon from '@material-ui/core/Icon'; import styles from '../../assets/jss/material-dashboard-react/components/sidebarStyle'; -import { Route } from '../../../types/models'; +import { Route } from '../../types'; const useStyles = makeStyles(styles as any); diff --git a/src/ui/layouts/Dashboard.tsx b/src/ui/layouts/Dashboard.tsx index a788ffd92..fffcf6dfc 100644 --- a/src/ui/layouts/Dashboard.tsx +++ b/src/ui/layouts/Dashboard.tsx @@ -11,7 +11,7 @@ import styles from '../assets/jss/material-dashboard-react/layouts/dashboardStyl import logo from '../assets/img/git-proxy.png'; import { UserContext } from '../../context'; import { getUser } from '../services/user'; -import { Route as RouteType, UserData } from '../../types/models'; +import { Route as RouteType, UserData } from '../types'; interface DashboardProps { [key: string]: any; diff --git a/src/ui/services/auth.ts b/src/ui/services/auth.ts index b855a26f8..74af4b713 100644 --- a/src/ui/services/auth.ts +++ b/src/ui/services/auth.ts @@ -1,5 +1,5 @@ import { getCookie } from '../utils'; -import { UserData } from '../../types/models'; +import { UserData } from '../types'; import { API_BASE } from '../apiBase'; import { AxiosError } from 'axios'; diff --git a/src/ui/services/user.ts b/src/ui/services/user.ts index 5896b60ea..b847fe51e 100644 --- a/src/ui/services/user.ts +++ b/src/ui/services/user.ts @@ -1,6 +1,6 @@ import axios, { AxiosError, AxiosResponse } from 'axios'; import { getAxiosConfig, processAuthError } from './auth'; -import { UserData } from '../../types/models'; +import { UserData } from '../types'; import { API_BASE } from '../apiBase'; diff --git a/src/ui/types.ts b/src/ui/types.ts index 6fbc1bef6..2d0f4dc4b 100644 --- a/src/ui/types.ts +++ b/src/ui/types.ts @@ -1,3 +1,40 @@ +import { StepData } from '../proxy/actions/Step'; +import { CommitData } from '../proxy/processors/types'; + +export interface UserData { + id: string; + name: string; + username: string; + email?: string; + displayName?: string; + title?: string; + gitAccount?: string; + admin?: boolean; +} + +export interface PushData { + id: string; + url: string; + repo: string; + branch: string; + commitFrom: string; + commitTo: string; + commitData: CommitData[]; + diff: { + content: string; + }; + error: boolean; + canceled?: boolean; + rejected?: boolean; + blocked?: boolean; + authorised?: boolean; + attestation?: AttestationFormData; + autoApproved?: boolean; + timestamp: string | Date; + allowPush?: boolean; + lastStep?: StepData; +} + export interface RepositoryData { _id?: string; project: string; @@ -41,3 +78,13 @@ export interface AttestationFormData { timestamp: string | Date; questions: QuestionFormData[]; } + +export interface Route { + path: string; + layout: string; + name: string; + rtlName?: string; + component: React.ComponentType; + icon?: string | React.ComponentType; + visible?: boolean; +} diff --git a/src/ui/views/OpenPushRequests/components/PushesTable.tsx b/src/ui/views/OpenPushRequests/components/PushesTable.tsx index e8f6f45a7..f5e06398f 100644 --- a/src/ui/views/OpenPushRequests/components/PushesTable.tsx +++ b/src/ui/views/OpenPushRequests/components/PushesTable.tsx @@ -15,7 +15,7 @@ import { getPushes } from '../../../services/git-push'; import { KeyboardArrowRight } from '@material-ui/icons'; import Search from '../../../components/Search/Search'; import Pagination from '../../../components/Pagination/Pagination'; -import { PushData } from '../../../../types/models'; +import { PushData } from '../../../types'; import { trimPrefixRefsHeads, trimTrailingDotGit } from '../../../../db/helper'; import { generateAuthorLinks, generateEmailLink } from '../../../utils'; diff --git a/src/ui/views/PushDetails/PushDetails.tsx b/src/ui/views/PushDetails/PushDetails.tsx index 54f82ead2..05f275406 100644 --- a/src/ui/views/PushDetails/PushDetails.tsx +++ b/src/ui/views/PushDetails/PushDetails.tsx @@ -22,7 +22,7 @@ import { getPush, authorisePush, rejectPush, cancelPush } from '../../services/g import { CheckCircle, Visibility, Cancel, Block } from '@material-ui/icons'; import Snackbar from '@material-ui/core/Snackbar'; import Tooltip from '@material-ui/core/Tooltip'; -import { PushData } from '../../../types/models'; +import { PushData } from '../../types'; import { trimPrefixRefsHeads, trimTrailingDotGit } from '../../../db/helper'; import { generateEmailLink, getGitProvider } from '../../utils'; diff --git a/src/ui/views/RepoDetails/Components/AddUser.tsx b/src/ui/views/RepoDetails/Components/AddUser.tsx index 93231f81a..1b64a570d 100644 --- a/src/ui/views/RepoDetails/Components/AddUser.tsx +++ b/src/ui/views/RepoDetails/Components/AddUser.tsx @@ -16,7 +16,7 @@ import Snackbar from '@material-ui/core/Snackbar'; import { addUser } from '../../../services/repo'; import { getUsers } from '../../../services/user'; import { PersonAdd } from '@material-ui/icons'; -import { UserData } from '../../../../types/models'; +import { UserData } from '../../../types'; import Danger from '../../../components/Typography/Danger'; interface AddUserDialogProps { diff --git a/src/ui/views/User/UserProfile.tsx b/src/ui/views/User/UserProfile.tsx index 89b8a1bf9..f10a6f3b3 100644 --- a/src/ui/views/User/UserProfile.tsx +++ b/src/ui/views/User/UserProfile.tsx @@ -9,7 +9,7 @@ import FormLabel from '@material-ui/core/FormLabel'; import { getUser, updateUser } from '../../services/user'; import { UserContext } from '../../../context'; -import { UserData } from '../../../types/models'; +import { UserData } from '../../types'; import { makeStyles } from '@material-ui/core/styles'; import { LogoGithubIcon } from '@primer/octicons-react'; diff --git a/src/ui/views/UserList/Components/UserList.tsx b/src/ui/views/UserList/Components/UserList.tsx index 68a4e6a0f..c150c5861 100644 --- a/src/ui/views/UserList/Components/UserList.tsx +++ b/src/ui/views/UserList/Components/UserList.tsx @@ -17,7 +17,7 @@ import Pagination from '../../../components/Pagination/Pagination'; import { CloseRounded, Check, KeyboardArrowRight } from '@material-ui/icons'; import Search from '../../../components/Search/Search'; import Danger from '../../../components/Typography/Danger'; -import { UserData } from '../../../../types/models'; +import { UserData } from '../../../types'; const useStyles = makeStyles(styles as any); From b5ddbd962d9fa6d538ad9464cb9ca3aed2473b9a Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Sat, 1 Nov 2025 15:09:18 +0900 Subject: [PATCH 06/21] refactor: duplicate ContextData types --- src/context.ts | 2 +- src/ui/types.ts | 6 ++++++ src/ui/views/RepoDetails/RepoDetails.tsx | 9 +-------- src/ui/views/RepoList/Components/Repositories.tsx | 7 ------- src/ui/views/User/UserProfile.tsx | 2 +- 5 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/context.ts b/src/context.ts index d8302c7cb..de73cfb20 100644 --- a/src/context.ts +++ b/src/context.ts @@ -1,5 +1,5 @@ import { createContext } from 'react'; -import { UserContextType } from './ui/views/RepoDetails/RepoDetails'; +import { UserContextType } from './ui/types'; export const UserContext = createContext({ user: { diff --git a/src/ui/types.ts b/src/ui/types.ts index 2d0f4dc4b..b518296c6 100644 --- a/src/ui/types.ts +++ b/src/ui/types.ts @@ -88,3 +88,9 @@ export interface Route { icon?: string | React.ComponentType; visible?: boolean; } + +export interface UserContextType { + user: { + admin: boolean; + }; +} diff --git a/src/ui/views/RepoDetails/RepoDetails.tsx b/src/ui/views/RepoDetails/RepoDetails.tsx index a3175f203..04f74fe2f 100644 --- a/src/ui/views/RepoDetails/RepoDetails.tsx +++ b/src/ui/views/RepoDetails/RepoDetails.tsx @@ -22,14 +22,7 @@ import { UserContext } from '../../../context'; import CodeActionButton from '../../components/CustomButtons/CodeActionButton'; import { trimTrailingDotGit } from '../../../db/helper'; import { fetchRemoteRepositoryData } from '../../utils'; -import { SCMRepositoryMetadata } from '../../../types/models'; -import { RepositoryDataWithId } from '../../types'; - -export interface UserContextType { - user: { - admin: boolean; - }; -} +import { RepositoryDataWithId, SCMRepositoryMetadata, UserContextType } from '../../types'; const useStyles = makeStyles((theme) => ({ root: { diff --git a/src/ui/views/RepoList/Components/Repositories.tsx b/src/ui/views/RepoList/Components/Repositories.tsx index 44d63fe28..c50f9fd1e 100644 --- a/src/ui/views/RepoList/Components/Repositories.tsx +++ b/src/ui/views/RepoList/Components/Repositories.tsx @@ -32,13 +32,6 @@ interface GridContainerLayoutProps { key: string; } -interface UserContextType { - user: { - admin: boolean; - [key: string]: any; - }; -} - export default function Repositories(): React.ReactElement { const useStyles = makeStyles(styles as any); const classes = useStyles(); diff --git a/src/ui/views/User/UserProfile.tsx b/src/ui/views/User/UserProfile.tsx index f10a6f3b3..a36a26b63 100644 --- a/src/ui/views/User/UserProfile.tsx +++ b/src/ui/views/User/UserProfile.tsx @@ -9,7 +9,7 @@ import FormLabel from '@material-ui/core/FormLabel'; import { getUser, updateUser } from '../../services/user'; import { UserContext } from '../../../context'; -import { UserData } from '../../types'; +import { UserContextType, UserData } from '../../types'; import { makeStyles } from '@material-ui/core/styles'; import { LogoGithubIcon } from '@primer/octicons-react'; From 8740d6210b3ebda7d47a91bc89394ce1690865ac Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Sat, 1 Nov 2025 15:11:03 +0900 Subject: [PATCH 07/21] chore: move repo metadata types and fix isAdminUser typings --- src/service/routes/utils.ts | 8 +-- src/types/models.ts | 57 ------------------ src/ui/types.ts | 58 +++++++++++++++++++ src/ui/utils.tsx | 6 +- .../RepoList/Components/RepoOverview.tsx | 3 +- .../RepoList/Components/Repositories.tsx | 2 +- src/ui/views/User/UserProfile.tsx | 1 - 7 files changed, 64 insertions(+), 71 deletions(-) delete mode 100644 src/types/models.ts diff --git a/src/service/routes/utils.ts b/src/service/routes/utils.ts index 3c72064ce..a9c501801 100644 --- a/src/service/routes/utils.ts +++ b/src/service/routes/utils.ts @@ -1,10 +1,8 @@ -interface User { +interface User extends Express.User { username: string; admin?: boolean; } -export function isAdminUser(user: any): user is User & { admin: true } { - return ( - typeof user === 'object' && user !== null && user !== undefined && (user as User).admin === true - ); +export function isAdminUser(user?: Express.User): user is User & { admin: true } { + return user !== null && user !== undefined && (user as User).admin === true; } diff --git a/src/types/models.ts b/src/types/models.ts deleted file mode 100644 index c2b9e94fc..000000000 --- a/src/types/models.ts +++ /dev/null @@ -1,57 +0,0 @@ -export interface GitHubRepositoryMetadata { - description?: string; - language?: string; - license?: { - spdx_id: string; - }; - html_url: string; - parent?: { - full_name: string; - html_url: string; - }; - created_at?: string; - updated_at?: string; - pushed_at?: string; - owner?: { - avatar_url: string; - html_url: string; - }; -} - -export interface GitLabRepositoryMetadata { - description?: string; - primary_language?: string; - license?: { - nickname: string; - }; - web_url: string; - forked_from_project?: { - full_name: string; - web_url: string; - }; - last_activity_at?: string; - avatar_url?: string; - namespace?: { - name: string; - path: string; - full_path: string; - avatar_url?: string; - web_url: string; - }; -} - -export interface SCMRepositoryMetadata { - description?: string; - language?: string; - license?: string; - htmlUrl?: string; - parentName?: string; - parentUrl?: string; - lastUpdated?: string; - created_at?: string; - updated_at?: string; - pushed_at?: string; - - profileUrl?: string; - avatarUrl?: string; -} diff --git a/src/ui/types.ts b/src/ui/types.ts index b518296c6..08ef42057 100644 --- a/src/ui/types.ts +++ b/src/ui/types.ts @@ -89,6 +89,64 @@ export interface Route { visible?: boolean; } +export interface GitHubRepositoryMetadata { + description?: string; + language?: string; + license?: { + spdx_id: string; + }; + html_url: string; + parent?: { + full_name: string; + html_url: string; + }; + created_at?: string; + updated_at?: string; + pushed_at?: string; + owner?: { + avatar_url: string; + html_url: string; + }; +} + +export interface GitLabRepositoryMetadata { + description?: string; + primary_language?: string; + license?: { + nickname: string; + }; + web_url: string; + forked_from_project?: { + full_name: string; + web_url: string; + }; + last_activity_at?: string; + avatar_url?: string; + namespace?: { + name: string; + path: string; + full_path: string; + avatar_url?: string; + web_url: string; + }; +} + +export interface SCMRepositoryMetadata { + description?: string; + language?: string; + license?: string; + htmlUrl?: string; + parentName?: string; + parentUrl?: string; + lastUpdated?: string; + created_at?: string; + updated_at?: string; + pushed_at?: string; + + profileUrl?: string; + avatarUrl?: string; +} + export interface UserContextType { user: { admin: boolean; diff --git a/src/ui/utils.tsx b/src/ui/utils.tsx index 0ae7e2167..6a8abfc17 100644 --- a/src/ui/utils.tsx +++ b/src/ui/utils.tsx @@ -1,10 +1,6 @@ import axios from 'axios'; import React from 'react'; -import { - GitHubRepositoryMetadata, - GitLabRepositoryMetadata, - SCMRepositoryMetadata, -} from '../types/models'; +import { GitHubRepositoryMetadata, GitLabRepositoryMetadata, SCMRepositoryMetadata } from './types'; import { CommitData } from '../proxy/processors/types'; import moment from 'moment'; diff --git a/src/ui/views/RepoList/Components/RepoOverview.tsx b/src/ui/views/RepoList/Components/RepoOverview.tsx index 671a5cb92..731e843a2 100644 --- a/src/ui/views/RepoList/Components/RepoOverview.tsx +++ b/src/ui/views/RepoList/Components/RepoOverview.tsx @@ -5,9 +5,8 @@ import GridItem from '../../../components/Grid/GridItem'; import { CodeReviewIcon, LawIcon, PeopleIcon } from '@primer/octicons-react'; import CodeActionButton from '../../../components/CustomButtons/CodeActionButton'; import { languageColors } from '../../../../constants/languageColors'; -import { RepositoryDataWithId } from '../../../types'; +import { RepositoryDataWithId, SCMRepositoryMetadata } from '../../../types'; import { fetchRemoteRepositoryData } from '../../../utils'; -import { SCMRepositoryMetadata } from '../../../../types/models'; export interface RepositoriesProps { data: RepositoryDataWithId; diff --git a/src/ui/views/RepoList/Components/Repositories.tsx b/src/ui/views/RepoList/Components/Repositories.tsx index c50f9fd1e..08e72b3eb 100644 --- a/src/ui/views/RepoList/Components/Repositories.tsx +++ b/src/ui/views/RepoList/Components/Repositories.tsx @@ -9,7 +9,7 @@ import { getRepos } from '../../../services/repo'; import GridContainer from '../../../components/Grid/GridContainer'; import GridItem from '../../../components/Grid/GridItem'; import NewRepo from './NewRepo'; -import { RepositoryDataWithId } from '../../../types'; +import { RepositoryDataWithId, UserContextType } from '../../../types'; import RepoOverview from './RepoOverview'; import { UserContext } from '../../../../context'; import Search from '../../../components/Search/Search'; diff --git a/src/ui/views/User/UserProfile.tsx b/src/ui/views/User/UserProfile.tsx index a36a26b63..ebaab2807 100644 --- a/src/ui/views/User/UserProfile.tsx +++ b/src/ui/views/User/UserProfile.tsx @@ -16,7 +16,6 @@ import { LogoGithubIcon } from '@primer/octicons-react'; import CloseRounded from '@material-ui/icons/CloseRounded'; import { Check, Save } from '@material-ui/icons'; import { TextField, Theme } from '@material-ui/core'; -import { UserContextType } from '../RepoDetails/RepoDetails'; const useStyles = makeStyles((theme: Theme) => ({ root: { From 2db6d4edbd1cfa16e7aa937b681ff3cd990acf0d Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Sat, 1 Nov 2025 20:05:47 +0900 Subject: [PATCH 08/21] refactor: extra config types into own types file --- src/config/ConfigLoader.ts | 49 +------------------------------- src/config/env.ts | 9 +----- src/config/index.ts | 3 +- src/config/types.ts | 58 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 57 deletions(-) create mode 100644 src/config/types.ts diff --git a/src/config/ConfigLoader.ts b/src/config/ConfigLoader.ts index e09ce81f6..22dd6abfd 100644 --- a/src/config/ConfigLoader.ts +++ b/src/config/ConfigLoader.ts @@ -6,57 +6,10 @@ import { promisify } from 'util'; import { EventEmitter } from 'events'; import envPaths from 'env-paths'; import { GitProxyConfig, Convert } from './generated/config'; +import { Configuration, ConfigurationSource, FileSource, HttpSource, GitSource } from './types'; const execFileAsync = promisify(execFile); -interface GitAuth { - type: 'ssh'; - privateKeyPath: string; -} - -interface HttpAuth { - type: 'bearer'; - token: string; -} - -interface BaseSource { - type: 'file' | 'http' | 'git'; - enabled: boolean; -} - -interface FileSource extends BaseSource { - type: 'file'; - path: string; -} - -interface HttpSource extends BaseSource { - type: 'http'; - url: string; - headers?: Record; - auth?: HttpAuth; -} - -interface GitSource extends BaseSource { - type: 'git'; - repository: string; - branch?: string; - path: string; - auth?: GitAuth; -} - -type ConfigurationSource = FileSource | HttpSource | GitSource; - -export interface ConfigurationSources { - enabled: boolean; - sources: ConfigurationSource[]; - reloadIntervalSeconds: number; - merge?: boolean; -} - -export interface Configuration extends GitProxyConfig { - configurationSources?: ConfigurationSources; -} - // Add path validation helper function isValidPath(filePath: string): boolean { if (!filePath || typeof filePath !== 'string') return false; diff --git a/src/config/env.ts b/src/config/env.ts index 3adb7d2f9..14b63a7f6 100644 --- a/src/config/env.ts +++ b/src/config/env.ts @@ -1,11 +1,4 @@ -export type ServerConfig = { - GIT_PROXY_SERVER_PORT: string | number; - GIT_PROXY_HTTPS_SERVER_PORT: string | number; - GIT_PROXY_UI_HOST: string; - GIT_PROXY_UI_PORT: string | number; - GIT_PROXY_COOKIE_SECRET: string | undefined; - GIT_PROXY_MONGO_CONNECTION_STRING: string; -}; +import { ServerConfig } from './types'; const { GIT_PROXY_SERVER_PORT = 8000, diff --git a/src/config/index.ts b/src/config/index.ts index 6c108d3fc..8f40ac3b1 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -2,7 +2,8 @@ import { existsSync, readFileSync } from 'fs'; import defaultSettings from '../../proxy.config.json'; import { GitProxyConfig, Convert } from './generated/config'; -import { ConfigLoader, Configuration } from './ConfigLoader'; +import { ConfigLoader } from './ConfigLoader'; +import { Configuration } from './types'; import { serverConfig } from './env'; import { configFile } from './file'; diff --git a/src/config/types.ts b/src/config/types.ts new file mode 100644 index 000000000..49c7f811b --- /dev/null +++ b/src/config/types.ts @@ -0,0 +1,58 @@ +import { GitProxyConfig } from './generated/config'; + +export type ServerConfig = { + GIT_PROXY_SERVER_PORT: string | number; + GIT_PROXY_HTTPS_SERVER_PORT: string | number; + GIT_PROXY_UI_HOST: string; + GIT_PROXY_UI_PORT: string | number; + GIT_PROXY_COOKIE_SECRET: string | undefined; + GIT_PROXY_MONGO_CONNECTION_STRING: string; +}; + +interface GitAuth { + type: 'ssh'; + privateKeyPath: string; +} + +interface HttpAuth { + type: 'bearer'; + token: string; +} + +interface BaseSource { + type: 'file' | 'http' | 'git'; + enabled: boolean; +} + +export interface FileSource extends BaseSource { + type: 'file'; + path: string; +} + +export interface HttpSource extends BaseSource { + type: 'http'; + url: string; + headers?: Record; + auth?: HttpAuth; +} + +export interface GitSource extends BaseSource { + type: 'git'; + repository: string; + branch?: string; + path: string; + auth?: GitAuth; +} + +export type ConfigurationSource = FileSource | HttpSource | GitSource; + +interface ConfigurationSources { + enabled: boolean; + sources: ConfigurationSource[]; + reloadIntervalSeconds: number; + merge?: boolean; +} + +export interface Configuration extends GitProxyConfig { + configurationSources?: ConfigurationSources; +} From 311a10326b2ccbdd0a0fe5eda17e2a7a3f1f3ceb Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Sat, 1 Nov 2025 21:27:33 +0900 Subject: [PATCH 09/21] chore: generate config types for JWT roleMapping --- config.schema.json | 9 ++++++++- src/config/generated/config.ts | 10 ++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/config.schema.json b/config.schema.json index dafb93c3f..f75ca0d19 100644 --- a/config.schema.json +++ b/config.schema.json @@ -466,7 +466,14 @@ "description": "Additional JWT configuration.", "properties": { "clientID": { "type": "string" }, - "authorityURL": { "type": "string" } + "authorityURL": { "type": "string" }, + "expectedAudience": { "type": "string" }, + "roleMapping": { + "type": "object", + "properties": { + "admin": { "type": "object" } + } + } }, "required": ["clientID", "authorityURL"] } diff --git a/src/config/generated/config.ts b/src/config/generated/config.ts index 4d3493e1a..6f87f0cd1 100644 --- a/src/config/generated/config.ts +++ b/src/config/generated/config.ts @@ -225,6 +225,13 @@ export interface AdConfig { export interface JwtConfig { authorityURL: string; clientID: string; + expectedAudience?: string; + roleMapping?: RoleMapping; + [property: string]: any; +} + +export interface RoleMapping { + admin?: { [key: string]: any }; [property: string]: any; } @@ -754,9 +761,12 @@ const typeMap: any = { [ { json: 'authorityURL', js: 'authorityURL', typ: '' }, { json: 'clientID', js: 'clientID', typ: '' }, + { json: 'expectedAudience', js: 'expectedAudience', typ: u(undefined, '') }, + { json: 'roleMapping', js: 'roleMapping', typ: u(undefined, r('RoleMapping')) }, ], 'any', ), + RoleMapping: o([{ json: 'admin', js: 'admin', typ: u(undefined, m('any')) }], 'any'), OidcConfig: o( [ { json: 'callbackURL', js: 'callbackURL', typ: '' }, From 91b87501a78e59b4a92d9c8664cb23a6cab9773b Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Sat, 1 Nov 2025 21:28:53 +0900 Subject: [PATCH 10/21] refactor: remove duplicate RoleMapping type --- src/service/passport/jwtAuthHandler.ts | 3 +-- src/service/passport/jwtUtils.ts | 11 ++++++++++- src/service/passport/types.ts | 16 ---------------- 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/src/service/passport/jwtAuthHandler.ts b/src/service/passport/jwtAuthHandler.ts index bb312e40f..2bcb4ae4c 100644 --- a/src/service/passport/jwtAuthHandler.ts +++ b/src/service/passport/jwtAuthHandler.ts @@ -1,8 +1,7 @@ import { assignRoles, validateJwt } from './jwtUtils'; import type { Request, Response, NextFunction } from 'express'; import { getAPIAuthMethods } from '../../config'; -import { JwtConfig, AuthenticationElement, Type } from '../../config/generated/config'; -import { RoleMapping } from './types'; +import { AuthenticationElement, JwtConfig, RoleMapping, Type } from '../../config/generated/config'; export const type = 'jwt'; diff --git a/src/service/passport/jwtUtils.ts b/src/service/passport/jwtUtils.ts index 8fcf214e4..5fc3a1901 100644 --- a/src/service/passport/jwtUtils.ts +++ b/src/service/passport/jwtUtils.ts @@ -2,7 +2,8 @@ import axios from 'axios'; import jwt, { type JwtPayload } from 'jsonwebtoken'; import jwkToPem from 'jwk-to-pem'; -import { JwkKey, JwksResponse, JwtValidationResult, RoleMapping } from './types'; +import { JwkKey, JwksResponse, JwtValidationResult } from './types'; +import { RoleMapping } from '../../config/generated/config'; /** * Obtain the JSON Web Key Set (JWKS) from the OIDC authority. @@ -80,6 +81,14 @@ export async function validateJwt( * Assign roles to the user based on the role mappings provided in the jwtConfig. * * If no role mapping is provided, the user will not have any roles assigned (i.e. user.admin = false). + * + * For example, the following role mapping will assign the "admin" role to users whose "name" claim is "John Doe": + * + * { + * "admin": { + * "name": "John Doe" + * } + * } * @param {RoleMapping} roleMapping the role mapping configuration * @param {JwtPayload} payload the JWT payload * @param {Record} user the req.user object to assign roles to diff --git a/src/service/passport/types.ts b/src/service/passport/types.ts index d433c782f..59b02deca 100644 --- a/src/service/passport/types.ts +++ b/src/service/passport/types.ts @@ -19,22 +19,6 @@ export type JwtValidationResult = { error: string | null; }; -/** - * The JWT role mapping configuration. - * - * The key is the in-app role name (e.g. "admin"). - * The value is a pair of claim name and expected value. - * - * For example, the following role mapping will assign the "admin" role to users whose "name" claim is "John Doe": - * - * { - * "admin": { - * "name": "John Doe" - * } - * } - */ -export type RoleMapping = Record>; - export type ADProfile = { id?: string; username?: string; From 1bc75bae8a14e9bc75d0aef71e25f0397456fdc7 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Thu, 6 Nov 2025 21:45:26 +0900 Subject: [PATCH 11/21] refactor: remove duplicate Commit interface in Action.ts --- src/proxy/actions/Action.ts | 21 ++++--------------- .../push-action/checkAuthorEmails.ts | 4 ++-- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/src/proxy/actions/Action.ts b/src/proxy/actions/Action.ts index c576bb0e1..bfc80c37e 100644 --- a/src/proxy/actions/Action.ts +++ b/src/proxy/actions/Action.ts @@ -1,20 +1,7 @@ import { processGitURLForNameAndOrg, processUrlPath } from '../routes/helper'; import { Step } from './Step'; - -/** - * Represents a commit. - */ -export interface Commit { - message: string; - committer: string; - committerEmail: string; - tree: string; - parent: string; - author: string; - authorEmail: string; - commitTS?: string; // TODO: Normalize this to commitTimestamp - commitTimestamp?: string; -} +import { CommitData } from '../processors/types'; +import { AttestationFormData } from '../../ui/types'; /** * Class representing a Push. @@ -39,7 +26,7 @@ class Action { rejected: boolean = false; autoApproved: boolean = false; autoRejected: boolean = false; - commitData?: Commit[] = []; + commitData?: CommitData[] = []; commitFrom?: string; commitTo?: string; branch?: string; @@ -47,7 +34,7 @@ class Action { author?: string; user?: string; userEmail?: string; - attestation?: string; + attestation?: AttestationFormData; lastStep?: Step; proxyGitPath?: string; newIdxFiles?: string[]; diff --git a/src/proxy/processors/push-action/checkAuthorEmails.ts b/src/proxy/processors/push-action/checkAuthorEmails.ts index 3c7cbb89c..ab45123d0 100644 --- a/src/proxy/processors/push-action/checkAuthorEmails.ts +++ b/src/proxy/processors/push-action/checkAuthorEmails.ts @@ -1,6 +1,6 @@ import { Action, Step } from '../../actions'; import { getCommitConfig } from '../../../config'; -import { Commit } from '../../actions/Action'; +import { CommitData } from '../types'; import { isEmail } from 'validator'; const commitConfig = getCommitConfig(); @@ -33,7 +33,7 @@ const exec = async (req: any, action: Action): Promise => { const step = new Step('checkAuthorEmails'); const uniqueAuthorEmails = [ - ...new Set(action.commitData?.map((commit: Commit) => commit.authorEmail)), + ...new Set(action.commitData?.map((commitData: CommitData) => commitData.authorEmail)), ]; console.log({ uniqueAuthorEmails }); From 276da564db2dce21c3654e9a7fe6bada635fdafb Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Thu, 6 Nov 2025 21:52:44 +0900 Subject: [PATCH 12/21] refactor: replace PushData type with PushActionView --- src/ui/services/git-push.ts | 9 ++++--- src/ui/types.ts | 26 +++---------------- .../components/PushesTable.tsx | 26 +++++++++---------- src/ui/views/PushDetails/PushDetails.tsx | 8 +++--- 4 files changed, 26 insertions(+), 43 deletions(-) diff --git a/src/ui/services/git-push.ts b/src/ui/services/git-push.ts index 2b0420680..37a8f21b0 100644 --- a/src/ui/services/git-push.ts +++ b/src/ui/services/git-push.ts @@ -1,6 +1,7 @@ import axios from 'axios'; import { getAxiosConfig, processAuthError } from './auth'; import { API_BASE } from '../apiBase'; +import { Action, Step } from '../../proxy/actions'; const API_V1_BASE = `${API_BASE}/api/v1`; @@ -15,9 +16,9 @@ const getPush = async ( setIsLoading(true); try { - const response = await axios(url, getAxiosConfig()); - const data = response.data; - data.diff = data.steps.find((x: any) => x.stepName === 'diff'); + const response = await axios(url, getAxiosConfig()); + const data: Action & { diff?: Step } = response.data; + data.diff = data.steps.find((x: Step) => x.stepName === 'diff'); setData(data); } catch (error: any) { if (error.response?.status === 401) setAuth(false); @@ -46,7 +47,7 @@ const getPushes = async ( setIsLoading(true); try { - const response = await axios(url.toString(), getAxiosConfig()); + const response = await axios(url.toString(), getAxiosConfig()); setData(response.data); } catch (error: any) { setIsError(true); diff --git a/src/ui/types.ts b/src/ui/types.ts index 08ef42057..4eda5d85e 100644 --- a/src/ui/types.ts +++ b/src/ui/types.ts @@ -1,4 +1,5 @@ -import { StepData } from '../proxy/actions/Step'; +import { Action } from '../proxy/actions'; +import { Step, StepData } from '../proxy/actions/Step'; import { CommitData } from '../proxy/processors/types'; export interface UserData { @@ -12,27 +13,8 @@ export interface UserData { admin?: boolean; } -export interface PushData { - id: string; - url: string; - repo: string; - branch: string; - commitFrom: string; - commitTo: string; - commitData: CommitData[]; - diff: { - content: string; - }; - error: boolean; - canceled?: boolean; - rejected?: boolean; - blocked?: boolean; - authorised?: boolean; - attestation?: AttestationFormData; - autoApproved?: boolean; - timestamp: string | Date; - allowPush?: boolean; - lastStep?: StepData; +export interface PushActionView extends Action { + diff: Step; } export interface RepositoryData { diff --git a/src/ui/views/OpenPushRequests/components/PushesTable.tsx b/src/ui/views/OpenPushRequests/components/PushesTable.tsx index f5e06398f..c8c1c1319 100644 --- a/src/ui/views/OpenPushRequests/components/PushesTable.tsx +++ b/src/ui/views/OpenPushRequests/components/PushesTable.tsx @@ -15,7 +15,7 @@ import { getPushes } from '../../../services/git-push'; import { KeyboardArrowRight } from '@material-ui/icons'; import Search from '../../../components/Search/Search'; import Pagination from '../../../components/Pagination/Pagination'; -import { PushData } from '../../../types'; +import { PushActionView } from '../../../types'; import { trimPrefixRefsHeads, trimTrailingDotGit } from '../../../../db/helper'; import { generateAuthorLinks, generateEmailLink } from '../../../utils'; @@ -27,8 +27,8 @@ const useStyles = makeStyles(styles as any); const PushesTable: React.FC = (props) => { const classes = useStyles(); - const [data, setData] = useState([]); - const [filteredData, setFilteredData] = useState([]); + const [data, setData] = useState([]); + const [filteredData, setFilteredData] = useState([]); const [isLoading, setIsLoading] = useState(false); const [, setIsError] = useState(false); const navigate = useNavigate(); @@ -59,8 +59,8 @@ const PushesTable: React.FC = (props) => { ? data.filter( (item) => item.repo.toLowerCase().includes(lowerCaseTerm) || - item.commitTo.toLowerCase().includes(lowerCaseTerm) || - item.commitData[0]?.message.toLowerCase().includes(lowerCaseTerm), + item.commitTo?.toLowerCase().includes(lowerCaseTerm) || + item.commitData?.[0]?.message.toLowerCase().includes(lowerCaseTerm), ) : data; setFilteredData(filtered); @@ -100,13 +100,13 @@ const PushesTable: React.FC = (props) => { {[...currentItems].reverse().map((row) => { const repoFullName = trimTrailingDotGit(row.repo); - const repoBranch = trimPrefixRefsHeads(row.branch); + const repoBranch = trimPrefixRefsHeads(row.branch ?? ''); const repoUrl = row.url; const repoWebUrl = trimTrailingDotGit(repoUrl); // may be used to resolve users to profile links in future // const gitProvider = getGitProvider(repoUrl); // const hostname = new URL(repoUrl).hostname; - const commitTimestamp = row.commitData[0]?.commitTimestamp; + const commitTimestamp = row.commitData?.[0]?.commitTimestamp; return ( @@ -129,7 +129,7 @@ const PushesTable: React.FC = (props) => { rel='noreferrer' target='_blank' > - {row.commitTo.substring(0, 8)} + {row.commitTo?.substring(0, 8)} @@ -137,18 +137,18 @@ const PushesTable: React.FC = (props) => { {getUserProfileLink(row.commitData[0].committerEmail, gitProvider, hostname)} */} {generateEmailLink( - row.commitData[0].committer, - row.commitData[0]?.committerEmail, + row.commitData?.[0]?.committer ?? '', + row.commitData?.[0]?.committerEmail ?? '', )} {/* render github/gitlab profile links in future {getUserProfileLink(row.commitData[0].authorEmail, gitProvider, hostname)} */} - {generateAuthorLinks(row.commitData)} + {generateAuthorLinks(row.commitData ?? [])} - {row.commitData[0]?.message || 'N/A'} - {row.commitData.length} + {row.commitData?.[0]?.message || 'N/A'} + {row.commitData?.length ?? 0} From 33ee86beecd02a0a4705ced8c4e9117ae13135ce Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Fri, 7 Nov 2025 16:48:26 +0900 Subject: [PATCH 15/21] fix: missing user errors --- src/ui/layouts/Dashboard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/layouts/Dashboard.tsx b/src/ui/layouts/Dashboard.tsx index 84f45d673..6017a1715 100644 --- a/src/ui/layouts/Dashboard.tsx +++ b/src/ui/layouts/Dashboard.tsx @@ -28,7 +28,7 @@ const Dashboard: React.FC = ({ ...rest }) => { const mainPanel = useRef(null); const [color] = useState<'purple' | 'blue' | 'green' | 'orange' | 'red'>('blue'); const [mobileOpen, setMobileOpen] = useState(false); - const [user, setUser] = useState(null); + const [user, setUser] = useState({} as PublicUser); const { id } = useParams<{ id?: string }>(); const handleDrawerToggle = () => setMobileOpen((prev) => !prev); From 35ecfb0ee054bf8253e6eba0f99d1e48967e605e Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Fri, 7 Nov 2025 17:57:21 +0900 Subject: [PATCH 16/21] refactor: replace RepositoryData and related types with RepoView Replace generic data/setData with repo versions --- src/ui/services/repo.ts | 33 +++++------ src/ui/types.ts | 16 +----- src/ui/views/RepoDetails/RepoDetails.tsx | 52 +++++++++--------- src/ui/views/RepoList/Components/NewRepo.tsx | 23 ++++---- .../RepoList/Components/RepoOverview.tsx | 22 ++++---- .../RepoList/Components/Repositories.tsx | 55 ++++++++++--------- 6 files changed, 98 insertions(+), 103 deletions(-) diff --git a/src/ui/services/repo.ts b/src/ui/services/repo.ts index 5224e0f1a..59c68342d 100644 --- a/src/ui/services/repo.ts +++ b/src/ui/services/repo.ts @@ -1,20 +1,21 @@ import axios from 'axios'; import { getAxiosConfig, processAuthError } from './auth.js'; import { API_BASE } from '../apiBase'; -import { RepositoryData, RepositoryDataWithId } from '../types'; +import { Repo } from '../../db/types'; +import { RepoView } from '../types'; const API_V1_BASE = `${API_BASE}/api/v1`; const canAddUser = (repoId: string, user: string, action: string) => { const url = new URL(`${API_V1_BASE}/repo/${repoId}`); return axios - .get(url.toString(), getAxiosConfig()) + .get(url.toString(), getAxiosConfig()) .then((response) => { - const data = response.data; + const repo = response.data; if (action === 'authorise') { - return !data.users.canAuthorise.includes(user); + return !repo.users.canAuthorise.includes(user); } else { - return !data.users.canPush.includes(user); + return !repo.users.canPush.includes(user); } }) .catch((error: any) => { @@ -31,7 +32,7 @@ class DupUserValidationError extends Error { const getRepos = async ( setIsLoading: (isLoading: boolean) => void, - setData: (data: any) => void, + setRepos: (repos: RepoView[]) => void, setAuth: (auth: boolean) => void, setIsError: (isError: boolean) => void, setErrorMessage: (errorMessage: string) => void, @@ -40,12 +41,12 @@ const getRepos = async ( const url = new URL(`${API_V1_BASE}/repo`); url.search = new URLSearchParams(query as any).toString(); setIsLoading(true); - await axios(url.toString(), getAxiosConfig()) + await axios(url.toString(), getAxiosConfig()) .then((response) => { - const sortedRepos = response.data.sort((a: RepositoryData, b: RepositoryData) => + const sortedRepos = response.data.sort((a: RepoView, b: RepoView) => a.name.localeCompare(b.name), ); - setData(sortedRepos); + setRepos(sortedRepos); }) .catch((error: any) => { setIsError(true); @@ -63,17 +64,17 @@ const getRepos = async ( const getRepo = async ( setIsLoading: (isLoading: boolean) => void, - setData: (data: any) => void, + setRepo: (repo: RepoView) => void, setAuth: (auth: boolean) => void, setIsError: (isError: boolean) => void, id: string, ): Promise => { const url = new URL(`${API_V1_BASE}/repo/${id}`); setIsLoading(true); - await axios(url.toString(), getAxiosConfig()) + await axios(url.toString(), getAxiosConfig()) .then((response) => { - const data = response.data; - setData(data); + const repo = response.data; + setRepo(repo); }) .catch((error: any) => { if (error.response && error.response.status === 401) { @@ -88,12 +89,12 @@ const getRepo = async ( }; const addRepo = async ( - data: RepositoryData, -): Promise<{ success: boolean; message?: string; repo: RepositoryDataWithId | null }> => { + repo: RepoView, +): Promise<{ success: boolean; message?: string; repo: RepoView | null }> => { const url = new URL(`${API_V1_BASE}/repo`); try { - const response = await axios.post(url.toString(), data, getAxiosConfig()); + const response = await axios.post(url.toString(), repo, getAxiosConfig()); return { success: true, repo: response.data, diff --git a/src/ui/types.ts b/src/ui/types.ts index ddd7fbccf..8cac38f65 100644 --- a/src/ui/types.ts +++ b/src/ui/types.ts @@ -1,27 +1,17 @@ import { Action } from '../proxy/actions'; import { Step } from '../proxy/actions/Step'; +import { Repo } from '../db/types'; export interface PushActionView extends Action { diff: Step; } -export interface RepositoryData { - _id?: string; - project: string; - name: string; - url: string; - maxUser: number; +export interface RepoView extends Repo { + proxyURL: string; lastModified?: string; dateCreated?: string; - proxyURL?: string; - users?: { - canPush?: string[]; - canAuthorise?: string[]; - }; } -export type RepositoryDataWithId = Required> & RepositoryData; - interface QuestionTooltipLink { text: string; url: string; diff --git a/src/ui/views/RepoDetails/RepoDetails.tsx b/src/ui/views/RepoDetails/RepoDetails.tsx index 04f74fe2f..f74e0cbf5 100644 --- a/src/ui/views/RepoDetails/RepoDetails.tsx +++ b/src/ui/views/RepoDetails/RepoDetails.tsx @@ -22,7 +22,7 @@ import { UserContext } from '../../../context'; import CodeActionButton from '../../components/CustomButtons/CodeActionButton'; import { trimTrailingDotGit } from '../../../db/helper'; import { fetchRemoteRepositoryData } from '../../utils'; -import { RepositoryDataWithId, SCMRepositoryMetadata, UserContextType } from '../../types'; +import { RepoView, SCMRepositoryMetadata, UserContextType } from '../../types'; const useStyles = makeStyles((theme) => ({ root: { @@ -39,7 +39,7 @@ const useStyles = makeStyles((theme) => ({ const RepoDetails: React.FC = () => { const navigate = useNavigate(); const classes = useStyles(); - const [data, setData] = useState(null); + const [repo, setRepo] = useState(null); const [, setAuth] = useState(true); const [isLoading, setIsLoading] = useState(true); const [isError, setIsError] = useState(false); @@ -49,20 +49,20 @@ const RepoDetails: React.FC = () => { useEffect(() => { if (repoId) { - getRepo(setIsLoading, setData, setAuth, setIsError, repoId); + getRepo(setIsLoading, setRepo, setAuth, setIsError, repoId); } }, [repoId]); useEffect(() => { - if (data) { - fetchRemoteRepositoryData(data.project, data.name, data.url).then(setRemoteRepoData); + if (repo) { + fetchRemoteRepositoryData(repo.project, repo.name, repo.url).then(setRemoteRepoData); } - }, [data]); + }, [repo]); const removeUser = async (userToRemove: string, action: 'authorise' | 'push') => { if (!repoId) return; await deleteUser(userToRemove, repoId, action); - getRepo(setIsLoading, setData, setAuth, setIsError, repoId); + getRepo(setIsLoading, setRepo, setAuth, setIsError, repoId); }; const removeRepository = async (id: string) => { @@ -72,15 +72,15 @@ const RepoDetails: React.FC = () => { const refresh = () => { if (repoId) { - getRepo(setIsLoading, setData, setAuth, setIsError, repoId); + getRepo(setIsLoading, setRepo, setAuth, setIsError, repoId); } }; if (isLoading) return
Loading...
; if (isError) return
Something went wrong ...
; - if (!data) return
No repository data found
; + if (!repo) return
No repository data found
; - const { url: remoteUrl, proxyURL } = data || {}; + const { url: remoteUrl, proxyURL } = repo || {}; const parsedUrl = new URL(remoteUrl); const cloneURL = `${proxyURL}/${parsedUrl.host}${parsedUrl.port ? `:${parsedUrl.port}` : ''}${parsedUrl.pathname}`; @@ -102,7 +102,7 @@ const RepoDetails: React.FC = () => { variant='contained' color='secondary' data-testid='delete-repo-button' - onClick={() => removeRepository(data._id)} + onClick={() => removeRepository(repo._id!)} > @@ -120,7 +120,7 @@ const RepoDetails: React.FC = () => { width='75px' style={{ borderRadius: '5px' }} src={remoteRepoData.avatarUrl} - alt={`${data.project} logo`} + alt={`${repo.project} logo`} /> )} @@ -130,29 +130,29 @@ const RepoDetails: React.FC = () => {

{remoteRepoData?.profileUrl && ( - {data.project} + {repo.project} )} - {!remoteRepoData?.profileUrl && {data.project}} + {!remoteRepoData?.profileUrl && {repo.project}}

Name

- {data.name} + {repo.name}

URL

- - {trimTrailingDotGit(data.url)} + + {trimTrailingDotGit(repo.url)}

@@ -179,17 +179,17 @@ const RepoDetails: React.FC = () => {
- {data.users?.canAuthorise?.map((row) => ( - + {repo.users?.canAuthorise?.map((username) => ( + - {row} + {username} {user.admin && ( @@ -222,17 +222,17 @@ const RepoDetails: React.FC = () => { - {data.users?.canPush?.map((row) => ( - + {repo.users?.canPush?.map((username) => ( + - {row} + {username} {user.admin && ( diff --git a/src/ui/views/RepoList/Components/NewRepo.tsx b/src/ui/views/RepoList/Components/NewRepo.tsx index fa12355d6..e29f8244f 100644 --- a/src/ui/views/RepoList/Components/NewRepo.tsx +++ b/src/ui/views/RepoList/Components/NewRepo.tsx @@ -15,16 +15,16 @@ import { addRepo } from '../../../services/repo'; import { makeStyles } from '@material-ui/core/styles'; import styles from '../../../assets/jss/material-dashboard-react/views/dashboardStyle'; import { RepoIcon } from '@primer/octicons-react'; -import { RepositoryData, RepositoryDataWithId } from '../../../types'; +import { RepoView } from '../../../types'; interface AddRepositoryDialogProps { open: boolean; onClose: () => void; - onSuccess: (data: RepositoryDataWithId) => void; + onSuccess: (repo: RepoView) => void; } interface NewRepoProps { - onSuccess: (data: RepositoryDataWithId) => Promise; + onSuccess: (repo: RepoView) => Promise; } const useStyles = makeStyles(styles as any); @@ -43,8 +43,8 @@ const AddRepositoryDialog: React.FC = ({ open, onClose onClose(); }; - const handleSuccess = (data: RepositoryDataWithId) => { - onSuccess(data); + const handleSuccess = (repo: RepoView) => { + onSuccess(repo); setTip(true); }; @@ -55,25 +55,26 @@ const AddRepositoryDialog: React.FC = ({ open, onClose }; const add = async () => { - const data: RepositoryData = { + const repo: RepoView = { project: project.trim(), name: name.trim(), url: url.trim(), - maxUser: 1, + proxyURL: '', + users: { canPush: [], canAuthorise: [] }, }; - if (data.project.length === 0 || data.project.length > 100) { + if (repo.project.length === 0 || repo.project.length > 100) { setError('Project name length must be between 1 and 100 characters'); return; } - if (data.name.length === 0 || data.name.length > 100) { + if (repo.name.length === 0 || repo.name.length > 100) { setError('Repository name length must be between 1 and 100 characters'); return; } try { - const parsedUrl = new URL(data.url); + const parsedUrl = new URL(repo.url); if (!parsedUrl.pathname.endsWith('.git')) { setError('Invalid git URL - Git URLs should end with .git'); return; @@ -83,7 +84,7 @@ const AddRepositoryDialog: React.FC = ({ open, onClose return; } - const result = await addRepo(data); + const result = await addRepo(repo); if (result.success && result.repo) { handleSuccess(result.repo); handleClose(); diff --git a/src/ui/views/RepoList/Components/RepoOverview.tsx b/src/ui/views/RepoList/Components/RepoOverview.tsx index 731e843a2..4c647fb8a 100644 --- a/src/ui/views/RepoList/Components/RepoOverview.tsx +++ b/src/ui/views/RepoList/Components/RepoOverview.tsx @@ -5,11 +5,11 @@ import GridItem from '../../../components/Grid/GridItem'; import { CodeReviewIcon, LawIcon, PeopleIcon } from '@primer/octicons-react'; import CodeActionButton from '../../../components/CustomButtons/CodeActionButton'; import { languageColors } from '../../../../constants/languageColors'; -import { RepositoryDataWithId, SCMRepositoryMetadata } from '../../../types'; +import { RepoView, SCMRepositoryMetadata } from '../../../types'; import { fetchRemoteRepositoryData } from '../../../utils'; export interface RepositoriesProps { - data: RepositoryDataWithId; + repo: RepoView; [key: string]: unknown; } @@ -20,24 +20,24 @@ const Repositories: React.FC = (props) => { useEffect(() => { prepareRemoteRepositoryData(); - }, [props.data.project, props.data.name, props.data.url]); + }, [props.repo.project, props.repo.name, props.repo.url]); const prepareRemoteRepositoryData = async () => { try { - const { url: remoteUrl } = props.data; + const { url: remoteUrl } = props.repo; if (!remoteUrl) return; setRemoteRepoData( - await fetchRemoteRepositoryData(props.data.project, props.data.name, remoteUrl), + await fetchRemoteRepositoryData(props.repo.project, props.repo.name, remoteUrl), ); } catch (error: any) { console.warn( - `Unable to fetch repository data for ${props.data.project}/${props.data.name} from '${remoteUrl}' - this may occur if the project is private or from an SCM vendor that is not supported.`, + `Unable to fetch repository data for ${props.repo.project}/${props.repo.name} from '${remoteUrl}' - this may occur if the project is private or from an SCM vendor that is not supported.`, ); } }; - const { url: remoteUrl, proxyURL } = props?.data || {}; + const { url: remoteUrl, proxyURL } = props?.repo || {}; const parsedUrl = new URL(remoteUrl); const cloneURL = `${proxyURL}/${parsedUrl.host}${parsedUrl.port ? `:${parsedUrl.port}` : ''}${parsedUrl.pathname}`; @@ -45,9 +45,9 @@ const Repositories: React.FC = (props) => {
- + - {props.data.project}/{props.data.name} + {props.repo.project}/{props.repo.name} {remoteRepoData?.parentName && ( @@ -97,12 +97,12 @@ const Repositories: React.FC = (props) => { )} {' '} - {props.data?.users?.canPush?.length || 0} + {props.repo?.users?.canPush?.length || 0} {' '} - {props.data?.users?.canAuthorise?.length || 0} + {props.repo?.users?.canAuthorise?.length || 0} {remoteRepoData?.lastUpdated && ( diff --git a/src/ui/views/RepoList/Components/Repositories.tsx b/src/ui/views/RepoList/Components/Repositories.tsx index 08e72b3eb..5104c31e4 100644 --- a/src/ui/views/RepoList/Components/Repositories.tsx +++ b/src/ui/views/RepoList/Components/Repositories.tsx @@ -9,7 +9,7 @@ import { getRepos } from '../../../services/repo'; import GridContainer from '../../../components/Grid/GridContainer'; import GridItem from '../../../components/Grid/GridItem'; import NewRepo from './NewRepo'; -import { RepositoryDataWithId, UserContextType } from '../../../types'; +import { RepoView, UserContextType } from '../../../types'; import RepoOverview from './RepoOverview'; import { UserContext } from '../../../../context'; import Search from '../../../components/Search/Search'; @@ -20,7 +20,7 @@ import Danger from '../../../components/Typography/Danger'; interface GridContainerLayoutProps { classes: any; openRepo: (repo: string) => void; - data: RepositoryDataWithId[]; + repos: RepoView[]; repoButton: React.ReactNode; onSearch: (query: string) => void; currentPage: number; @@ -35,8 +35,8 @@ interface GridContainerLayoutProps { export default function Repositories(): React.ReactElement { const useStyles = makeStyles(styles as any); const classes = useStyles(); - const [data, setData] = useState([]); - const [filteredData, setFilteredData] = useState([]); + const [repos, setRepos] = useState([]); + const [filteredRepos, setFilteredRepos] = useState([]); const [, setAuth] = useState(true); const [isLoading, setIsLoading] = useState(false); const [isError, setIsError] = useState(false); @@ -51,9 +51,9 @@ export default function Repositories(): React.ReactElement { useEffect(() => { getRepos( setIsLoading, - (data: RepositoryDataWithId[]) => { - setData(data); - setFilteredData(data); + (repos: RepoView[]) => { + setRepos(repos); + setFilteredRepos(repos); }, setAuth, setIsError, @@ -61,20 +61,20 @@ export default function Repositories(): React.ReactElement { ); }, []); - const refresh = async (repo: RepositoryDataWithId): Promise => { - const updatedData = [...data, repo]; - setData(updatedData); - setFilteredData(updatedData); + const refresh = async (repo: RepoView): Promise => { + const updatedRepos = [...repos, repo]; + setRepos(updatedRepos); + setFilteredRepos(updatedRepos); }; const handleSearch = (query: string): void => { setCurrentPage(1); if (!query) { - setFilteredData(data); + setFilteredRepos(repos); } else { const lowercasedQuery = query.toLowerCase(); - setFilteredData( - data.filter( + setFilteredRepos( + repos.filter( (repo) => repo.name.toLowerCase().includes(lowercasedQuery) || repo.project.toLowerCase().includes(lowercasedQuery), @@ -84,35 +84,35 @@ export default function Repositories(): React.ReactElement { }; const handleFilterChange = (filterOption: FilterOption, sortOrder: SortOrder): void => { - const sortedData = [...data]; + const sortedRepos = [...repos]; switch (filterOption) { case 'Date Modified': - sortedData.sort( + sortedRepos.sort( (a, b) => new Date(a.lastModified || 0).getTime() - new Date(b.lastModified || 0).getTime(), ); break; case 'Date Created': - sortedData.sort( + sortedRepos.sort( (a, b) => new Date(a.dateCreated || 0).getTime() - new Date(b.dateCreated || 0).getTime(), ); break; case 'Alphabetical': - sortedData.sort((a, b) => a.name.localeCompare(b.name)); + sortedRepos.sort((a, b) => a.name.localeCompare(b.name)); break; default: break; } if (sortOrder === 'desc') { - sortedData.reverse(); + sortedRepos.reverse(); } - setFilteredData(sortedData); + setFilteredRepos(sortedRepos); }; const handlePageChange = (page: number): void => setCurrentPage(page); const startIdx = (currentPage - 1) * itemsPerPage; - const paginatedData = filteredData.slice(startIdx, startIdx + itemsPerPage); + const paginatedRepos = filteredRepos.slice(startIdx, startIdx + itemsPerPage); if (isLoading) return
Loading...
; if (isError) return {errorMessage}; @@ -129,11 +129,11 @@ export default function Repositories(): React.ReactElement { key: 'x', classes: classes, openRepo: openRepo, - data: paginatedData, + repos: paginatedRepos, repoButton: addrepoButton, onSearch: handleSearch, currentPage: currentPage, - totalItems: filteredData.length, + totalItems: filteredRepos.length, itemsPerPage: itemsPerPage, onPageChange: handlePageChange, onFilterChange: handleFilterChange, @@ -153,10 +153,13 @@ function getGridContainerLayOut(props: GridContainerLayoutProps): React.ReactEle > - {props.data.map((row) => { - if (row.url) { + {props.repos.map((repo) => { + if (repo.url) { return ( - + ); } return null; From 7396564782e803cf350352837cae6c95f5429e55 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Fri, 7 Nov 2025 23:53:37 +0900 Subject: [PATCH 17/21] refactor: replace generic data/setData variables with push versions --- src/ui/services/git-push.ts | 9 +-- .../components/PushesTable.tsx | 14 ++-- src/ui/views/PushDetails/PushDetails.tsx | 64 +++++++++---------- 3 files changed, 44 insertions(+), 43 deletions(-) diff --git a/src/ui/services/git-push.ts b/src/ui/services/git-push.ts index 37a8f21b0..3de0dac4d 100644 --- a/src/ui/services/git-push.ts +++ b/src/ui/services/git-push.ts @@ -2,13 +2,14 @@ import axios from 'axios'; import { getAxiosConfig, processAuthError } from './auth'; import { API_BASE } from '../apiBase'; import { Action, Step } from '../../proxy/actions'; +import { PushActionView } from '../types'; const API_V1_BASE = `${API_BASE}/api/v1`; const getPush = async ( id: string, setIsLoading: (isLoading: boolean) => void, - setData: (data: any) => void, + setPush: (push: PushActionView) => void, setAuth: (auth: boolean) => void, setIsError: (isError: boolean) => void, ): Promise => { @@ -19,7 +20,7 @@ const getPush = async ( const response = await axios(url, getAxiosConfig()); const data: Action & { diff?: Step } = response.data; data.diff = data.steps.find((x: Step) => x.stepName === 'diff'); - setData(data); + setPush(data as PushActionView); } catch (error: any) { if (error.response?.status === 401) setAuth(false); else setIsError(true); @@ -30,7 +31,7 @@ const getPush = async ( const getPushes = async ( setIsLoading: (isLoading: boolean) => void, - setData: (data: any) => void, + setPushes: (pushes: PushActionView[]) => void, setAuth: (auth: boolean) => void, setIsError: (isError: boolean) => void, setErrorMessage: (errorMessage: string) => void, @@ -48,7 +49,7 @@ const getPushes = async ( try { const response = await axios(url.toString(), getAxiosConfig()); - setData(response.data); + setPushes(response.data as PushActionView[]); } catch (error: any) { setIsError(true); diff --git a/src/ui/views/OpenPushRequests/components/PushesTable.tsx b/src/ui/views/OpenPushRequests/components/PushesTable.tsx index c8c1c1319..83cc90be9 100644 --- a/src/ui/views/OpenPushRequests/components/PushesTable.tsx +++ b/src/ui/views/OpenPushRequests/components/PushesTable.tsx @@ -27,7 +27,7 @@ const useStyles = makeStyles(styles as any); const PushesTable: React.FC = (props) => { const classes = useStyles(); - const [data, setData] = useState([]); + const [pushes, setPushes] = useState([]); const [filteredData, setFilteredData] = useState([]); const [isLoading, setIsLoading] = useState(false); const [, setIsError] = useState(false); @@ -46,26 +46,26 @@ const PushesTable: React.FC = (props) => { authorised: props.authorised ?? false, rejected: props.rejected ?? false, }; - getPushes(setIsLoading, setData, setAuth, setIsError, props.handleError, query); + getPushes(setIsLoading, setPushes, setAuth, setIsError, props.handleError, query); }, [props]); useEffect(() => { - setFilteredData(data); - }, [data]); + setFilteredData(pushes); + }, [pushes]); useEffect(() => { const lowerCaseTerm = searchTerm.toLowerCase(); const filtered = searchTerm - ? data.filter( + ? pushes.filter( (item) => item.repo.toLowerCase().includes(lowerCaseTerm) || item.commitTo?.toLowerCase().includes(lowerCaseTerm) || item.commitData?.[0]?.message.toLowerCase().includes(lowerCaseTerm), ) - : data; + : pushes; setFilteredData(filtered); setCurrentPage(1); - }, [searchTerm, data]); + }, [searchTerm, pushes]); const handleSearch = (term: string) => setSearchTerm(term.trim()); diff --git a/src/ui/views/PushDetails/PushDetails.tsx b/src/ui/views/PushDetails/PushDetails.tsx index 2dff212d9..fc584f476 100644 --- a/src/ui/views/PushDetails/PushDetails.tsx +++ b/src/ui/views/PushDetails/PushDetails.tsx @@ -28,7 +28,7 @@ import { generateEmailLink, getGitProvider } from '../../utils'; const Dashboard: React.FC = () => { const { id } = useParams<{ id: string }>(); - const [data, setData] = useState(null); + const [push, setPush] = useState(null); const [, setAuth] = useState(true); const [isLoading, setIsLoading] = useState(true); const [isError, setIsError] = useState(false); @@ -51,7 +51,7 @@ const Dashboard: React.FC = () => { useEffect(() => { if (id) { - getPush(id, setIsLoading, setData, setAuth, setIsError); + getPush(id, setIsLoading, setPush, setAuth, setIsError); } }, [id]); @@ -79,37 +79,37 @@ const Dashboard: React.FC = () => { if (isLoading) return
Loading...
; if (isError) return
Something went wrong ...
; - if (!data) return
No data found
; + if (!push) return
No push data found
; let headerData: { title: string; color: CardHeaderColor } = { title: 'Pending', color: 'warning', }; - if (data.canceled) { + if (push.canceled) { headerData = { color: 'warning', title: 'Canceled', }; } - if (data.rejected) { + if (push.rejected) { headerData = { color: 'danger', title: 'Rejected', }; } - if (data.authorised) { + if (push.authorised) { headerData = { color: 'success', title: 'Approved', }; } - const repoFullName = trimTrailingDotGit(data.repo); - const repoBranch = trimPrefixRefsHeads(data.branch ?? ''); - const repoUrl = data.url; + const repoFullName = trimTrailingDotGit(push.repo); + const repoBranch = trimPrefixRefsHeads(push.branch ?? ''); + const repoUrl = push.url; const repoWebUrl = trimTrailingDotGit(repoUrl); const gitProvider = getGitProvider(repoUrl); const isGitHub = gitProvider == 'github'; @@ -149,7 +149,7 @@ const Dashboard: React.FC = () => { {generateIcon(headerData.title)}

{headerData.title}

- {!(data.canceled || data.rejected || data.authorised) && ( + {!(push.canceled || push.rejected || push.authorised) && (
)} - {data.attestation && data.authorised && ( + {push.attestation && push.authorised && (
{ { - if (!data.autoApproved) { + if (!push.autoApproved) { setAttestation(true); } }} @@ -189,7 +189,7 @@ const Dashboard: React.FC = () => { /> - {data.autoApproved ? ( + {push.autoApproved ? (

Auto-approved by system @@ -198,23 +198,23 @@ const Dashboard: React.FC = () => { ) : ( <> {isGitHub && ( - + )}

{isGitHub && ( - - {data.attestation.reviewer.gitAccount} + + {push.attestation.reviewer.gitAccount} )} {!isGitHub && ( - - {data.attestation.reviewer.username} + + {push.attestation.reviewer.username} )}{' '} approved this contribution @@ -224,19 +224,19 @@ const Dashboard: React.FC = () => { )} - {moment(data.attestation.timestamp).fromNow()} + {moment(push.attestation.timestamp).fromNow()} - {!data.autoApproved && ( + {!push.autoApproved && ( @@ -248,17 +248,17 @@ const Dashboard: React.FC = () => {

Timestamp

-

{moment(data.timestamp).toString()}

+

{moment(push.timestamp).toString()}

Remote Head

- {data.commitFrom} + {push.commitFrom}

@@ -266,11 +266,11 @@ const Dashboard: React.FC = () => {

Commit SHA

- {data.commitTo} + {push.commitTo}

@@ -308,7 +308,7 @@ const Dashboard: React.FC = () => { - {data.commitData?.map((c) => ( + {push.commitData?.map((c) => ( {moment.unix(Number(c.commitTimestamp || 0)).toString()} @@ -327,7 +327,7 @@ const Dashboard: React.FC = () => { - + From c3e4116523ea85c3dbfd21614befcbbf5e325f22 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Sat, 8 Nov 2025 10:53:55 +0900 Subject: [PATCH 18/21] chore: simplify unexported UI types --- src/ui/types.ts | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/src/ui/types.ts b/src/ui/types.ts index 8cac38f65..cbbc505ee 100644 --- a/src/ui/types.ts +++ b/src/ui/types.ts @@ -12,29 +12,23 @@ export interface RepoView extends Repo { dateCreated?: string; } -interface QuestionTooltipLink { - text: string; - url: string; -} - -interface QuestionTooltip { - text: string; - links?: QuestionTooltipLink[]; -} - export interface QuestionFormData { label: string; checked: boolean; - tooltip: QuestionTooltip; -} - -interface Reviewer { - username: string; - gitAccount: string; + tooltip: { + text: string; + links?: { + text: string; + url: string; + }[]; + }; } export interface AttestationFormData { - reviewer: Reviewer; + reviewer: { + username: string; + gitAccount: string; + }; timestamp: string | Date; questions: QuestionFormData[]; } @@ -106,9 +100,3 @@ export interface SCMRepositoryMetadata { profileUrl?: string; avatarUrl?: string; } - -export interface UserContextType { - user: { - admin: boolean; - }; -} From c92c649d0ba2cbf1e3d448c8f5fa6dd702be7a30 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Sat, 8 Nov 2025 11:00:37 +0900 Subject: [PATCH 19/21] refactor: duplicate TabConfig/TabItem --- src/ui/components/CustomTabs/CustomTabs.tsx | 7 ++++--- src/ui/views/OpenPushRequests/OpenPushRequests.tsx | 10 ++-------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/ui/components/CustomTabs/CustomTabs.tsx b/src/ui/components/CustomTabs/CustomTabs.tsx index 8cd0c2d81..6a9211ecf 100644 --- a/src/ui/components/CustomTabs/CustomTabs.tsx +++ b/src/ui/components/CustomTabs/CustomTabs.tsx @@ -7,16 +7,17 @@ import Card from '../Card/Card'; import CardBody from '../Card/CardBody'; import CardHeader from '../Card/CardHeader'; import styles from '../../assets/jss/material-dashboard-react/components/customTabsStyle'; +import { SvgIconProps } from '@material-ui/core'; const useStyles = makeStyles(styles as any); type HeaderColor = 'warning' | 'success' | 'danger' | 'info' | 'primary' | 'rose'; -interface TabItem { +export type TabItem = { tabName: string; - tabIcon?: React.ComponentType; + tabIcon?: React.ComponentType; tabContent: React.ReactNode; -} +}; interface CustomTabsProps { headerColor?: HeaderColor; diff --git a/src/ui/views/OpenPushRequests/OpenPushRequests.tsx b/src/ui/views/OpenPushRequests/OpenPushRequests.tsx index a778e08ab..41c2672a8 100644 --- a/src/ui/views/OpenPushRequests/OpenPushRequests.tsx +++ b/src/ui/views/OpenPushRequests/OpenPushRequests.tsx @@ -5,13 +5,7 @@ import PushesTable from './components/PushesTable'; import CustomTabs from '../../components/CustomTabs/CustomTabs'; import Danger from '../../components/Typography/Danger'; import { Visibility, CheckCircle, Cancel, Block } from '@material-ui/icons'; -import { SvgIconProps } from '@material-ui/core'; - -interface TabConfig { - tabName: string; - tabIcon: React.ComponentType; - tabContent: React.ReactNode; -} +import { TabItem } from '../../components/CustomTabs/CustomTabs'; const Dashboard: React.FC = () => { const [errorMessage, setErrorMessage] = useState(null); @@ -20,7 +14,7 @@ const Dashboard: React.FC = () => { setErrorMessage(errorMessage); }; - const tabs: TabConfig[] = [ + const tabs: TabItem[] = [ { tabName: 'Pending', tabIcon: Visibility, From 23fbc4e04549ef088d0413ad50f964e5aa4a40b9 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Sat, 8 Nov 2025 11:02:02 +0900 Subject: [PATCH 20/21] chore: move UserContext and AuthContext to ui/context.ts --- src/context.ts | 8 ------- src/ui/auth/AuthProvider.tsx | 12 ++-------- src/ui/context.ts | 23 +++++++++++++++++++ src/ui/layouts/Dashboard.tsx | 2 +- src/ui/views/RepoDetails/RepoDetails.tsx | 5 ++-- .../RepoList/Components/Repositories.tsx | 4 ++-- src/ui/views/User/UserProfile.tsx | 3 +-- 7 files changed, 32 insertions(+), 25 deletions(-) delete mode 100644 src/context.ts create mode 100644 src/ui/context.ts diff --git a/src/context.ts b/src/context.ts deleted file mode 100644 index de73cfb20..000000000 --- a/src/context.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { createContext } from 'react'; -import { UserContextType } from './ui/types'; - -export const UserContext = createContext({ - user: { - admin: false, - }, -}); diff --git a/src/ui/auth/AuthProvider.tsx b/src/ui/auth/AuthProvider.tsx index 4a9c77bfa..57e6913c0 100644 --- a/src/ui/auth/AuthProvider.tsx +++ b/src/ui/auth/AuthProvider.tsx @@ -1,15 +1,7 @@ -import React, { createContext, useContext, useState, useEffect } from 'react'; +import React, { useContext, useState, useEffect } from 'react'; import { getUserInfo } from '../services/auth'; import { PublicUser } from '../../db/types'; - -interface AuthContextType { - user: PublicUser | null; - setUser: React.Dispatch; - refreshUser: () => Promise; - isLoading: boolean; -} - -const AuthContext = createContext(undefined); +import { AuthContext } from '../context'; export const AuthProvider: React.FC> = ({ children }) => { const [user, setUser] = useState(null); diff --git a/src/ui/context.ts b/src/ui/context.ts new file mode 100644 index 000000000..fcf7a7da5 --- /dev/null +++ b/src/ui/context.ts @@ -0,0 +1,23 @@ +import { createContext } from 'react'; +import { PublicUser } from '../db/types'; + +export const UserContext = createContext({ + user: { + admin: false, + }, +}); + +export interface UserContextType { + user: { + admin: boolean; + }; +} + +export interface AuthContextType { + user: PublicUser | null; + setUser: React.Dispatch; + refreshUser: () => Promise; + isLoading: boolean; +} + +export const AuthContext = createContext(undefined); diff --git a/src/ui/layouts/Dashboard.tsx b/src/ui/layouts/Dashboard.tsx index 6017a1715..3666a2bd1 100644 --- a/src/ui/layouts/Dashboard.tsx +++ b/src/ui/layouts/Dashboard.tsx @@ -9,7 +9,7 @@ import Sidebar from '../components/Sidebar/Sidebar'; import routes from '../../routes'; import styles from '../assets/jss/material-dashboard-react/layouts/dashboardStyle'; import logo from '../assets/img/git-proxy.png'; -import { UserContext } from '../../context'; +import { UserContext } from '../context'; import { getUser } from '../services/user'; import { Route as RouteType } from '../types'; import { PublicUser } from '../../db/types'; diff --git a/src/ui/views/RepoDetails/RepoDetails.tsx b/src/ui/views/RepoDetails/RepoDetails.tsx index f74e0cbf5..a6f785b12 100644 --- a/src/ui/views/RepoDetails/RepoDetails.tsx +++ b/src/ui/views/RepoDetails/RepoDetails.tsx @@ -18,11 +18,12 @@ import { makeStyles } from '@material-ui/core/styles'; import AddUser from './Components/AddUser'; import { Code, Delete, RemoveCircle, Visibility } from '@material-ui/icons'; import { useNavigate, useParams } from 'react-router-dom'; -import { UserContext } from '../../../context'; +import { UserContext } from '../../context'; import CodeActionButton from '../../components/CustomButtons/CodeActionButton'; import { trimTrailingDotGit } from '../../../db/helper'; import { fetchRemoteRepositoryData } from '../../utils'; -import { RepoView, SCMRepositoryMetadata, UserContextType } from '../../types'; +import { RepoView, SCMRepositoryMetadata } from '../../types'; +import { UserContextType } from '../../context'; const useStyles = makeStyles((theme) => ({ root: { diff --git a/src/ui/views/RepoList/Components/Repositories.tsx b/src/ui/views/RepoList/Components/Repositories.tsx index 5104c31e4..a72cd2fc5 100644 --- a/src/ui/views/RepoList/Components/Repositories.tsx +++ b/src/ui/views/RepoList/Components/Repositories.tsx @@ -9,9 +9,9 @@ import { getRepos } from '../../../services/repo'; import GridContainer from '../../../components/Grid/GridContainer'; import GridItem from '../../../components/Grid/GridItem'; import NewRepo from './NewRepo'; -import { RepoView, UserContextType } from '../../../types'; +import { RepoView } from '../../../types'; import RepoOverview from './RepoOverview'; -import { UserContext } from '../../../../context'; +import { UserContext, UserContextType } from '../../../context'; import Search from '../../../components/Search/Search'; import Pagination from '../../../components/Pagination/Pagination'; import Filtering, { FilterOption, SortOrder } from '../../../components/Filtering/Filtering'; diff --git a/src/ui/views/User/UserProfile.tsx b/src/ui/views/User/UserProfile.tsx index 50883e913..93d468980 100644 --- a/src/ui/views/User/UserProfile.tsx +++ b/src/ui/views/User/UserProfile.tsx @@ -7,9 +7,8 @@ import CardBody from '../../components/Card/CardBody'; import Button from '../../components/CustomButtons/Button'; import FormLabel from '@material-ui/core/FormLabel'; import { getUser, updateUser } from '../../services/user'; -import { UserContext } from '../../../context'; +import { UserContext, UserContextType } from '../../context'; -import { UserContextType } from '../../types'; import { PublicUser } from '../../../db/types'; import { makeStyles } from '@material-ui/core/styles'; From f14d9378ec9acecf6d1a89931416d84f6cd05390 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Sat, 8 Nov 2025 14:06:40 +0900 Subject: [PATCH 21/21] fix: cli type import error --- package.json | 21 +++-- packages/git-proxy-cli/index.ts | 131 ++++++++++++++++++-------------- 2 files changed, 85 insertions(+), 67 deletions(-) diff --git a/package.json b/package.json index 56c5679dd..5d6890e98 100644 --- a/package.json +++ b/package.json @@ -20,20 +20,25 @@ "require": "./dist/src/db/index.js", "types": "./dist/src/db/index.d.ts" }, + "./plugin": { + "import": "./dist/src/plugin.js", + "require": "./dist/src/plugin.js", + "types": "./dist/src/plugin.d.ts" + }, "./proxy": { "import": "./dist/src/proxy/index.js", "require": "./dist/src/proxy/index.js", "types": "./dist/src/proxy/index.d.ts" }, - "./types": { - "import": "./dist/src/types/models.js", - "require": "./dist/src/types/models.js", - "types": "./dist/src/types/models.d.ts" + "./proxy/actions": { + "import": "./dist/src/proxy/actions/index.js", + "require": "./dist/src/proxy/actions/index.js", + "types": "./dist/src/proxy/actions/index.d.ts" }, - "./plugin": { - "import": "./dist/src/plugin.js", - "require": "./dist/src/plugin.js", - "types": "./dist/src/plugin.d.ts" + "./ui": { + "import": "./dist/src/ui/index.js", + "require": "./dist/src/ui/index.js", + "types": "./dist/src/ui/index.d.ts" } }, "scripts": { diff --git a/packages/git-proxy-cli/index.ts b/packages/git-proxy-cli/index.ts index 1a3bf3443..31ebc8a4c 100644 --- a/packages/git-proxy-cli/index.ts +++ b/packages/git-proxy-cli/index.ts @@ -5,8 +5,8 @@ import { hideBin } from 'yargs/helpers'; import fs from 'fs'; import util from 'util'; -import { CommitData, PushData } from '@finos/git-proxy/types'; import { PushQuery } from '@finos/git-proxy/db'; +import { Action } from '@finos/git-proxy/proxy/actions'; const GIT_PROXY_COOKIE_FILE = 'git-proxy-cookie'; // GitProxy UI HOST and PORT (configurable via environment variable) @@ -88,73 +88,86 @@ async function getGitPushes(filters: Partial) { try { const cookies = JSON.parse(fs.readFileSync(GIT_PROXY_COOKIE_FILE, 'utf8')); - - const response = await axios.get(`${baseUrl}/api/v1/push/`, { + const { data } = await axios.get(`${baseUrl}/api/v1/push/`, { headers: { Cookie: cookies }, params: filters, }); - const records: PushData[] = []; - response.data.forEach((push: PushData) => { - const record: PushData = { - id: push.id, - repo: push.repo, - branch: push.branch, - commitFrom: push.commitFrom, - commitTo: push.commitTo, - commitData: push.commitData, - diff: push.diff, - error: push.error, - canceled: push.canceled, - rejected: push.rejected, - blocked: push.blocked, - authorised: push.authorised, - attestation: push.attestation, - autoApproved: push.autoApproved, - timestamp: push.timestamp, - url: push.url, - allowPush: push.allowPush, + const records = data.map((push: Action) => { + const { + id, + repo, + branch, + commitFrom, + commitTo, + commitData, + error, + canceled, + rejected, + blocked, + authorised, + attestation, + autoApproved, + timestamp, + url, + allowPush, + lastStep, + } = push; + + return { + id, + repo, + branch, + commitFrom, + commitTo, + commitData: commitData?.map( + ({ + message, + committer, + committerEmail, + author, + authorEmail, + commitTimestamp, + tree, + parent, + }) => ({ + message, + committer, + committerEmail, + author, + authorEmail, + commitTimestamp, + tree, + parent, + }), + ), + error, + canceled, + rejected, + blocked, + authorised, + attestation, + autoApproved, + timestamp, + url, + allowPush, + lastStep: lastStep && { + id: lastStep.id, + content: lastStep.content, + logs: lastStep.logs, + stepName: lastStep.stepName, + error: lastStep.error, + errorMessage: lastStep.errorMessage, + blocked: lastStep.blocked, + blockedMessage: lastStep.blockedMessage, + }, }; - - if (push.lastStep) { - record.lastStep = { - id: push.lastStep?.id, - content: push.lastStep?.content, - logs: push.lastStep?.logs, - stepName: push.lastStep?.stepName, - error: push.lastStep?.error, - errorMessage: push.lastStep?.errorMessage, - blocked: push.lastStep?.blocked, - blockedMessage: push.lastStep?.blockedMessage, - }; - } - - if (push.commitData) { - const commitData: CommitData[] = []; - push.commitData.forEach((pushCommitDataRecord: CommitData) => { - commitData.push({ - message: pushCommitDataRecord.message, - committer: pushCommitDataRecord.committer, - committerEmail: pushCommitDataRecord.committerEmail, - author: pushCommitDataRecord.author, - authorEmail: pushCommitDataRecord.authorEmail, - commitTimestamp: pushCommitDataRecord.commitTimestamp, - tree: pushCommitDataRecord.tree, - parent: pushCommitDataRecord.parent, - }); - }); - record.commitData = commitData; - } - - records.push(record); }); - console.log(`${util.inspect(records, false, null, false)}`); + console.log(util.inspect(records, false, null, false)); } catch (error: any) { - // default error - const errorMessage = `Error: List: '${error.message}'`; + console.error(`Error: List: '${error.message}'`); process.exitCode = 2; - console.error(errorMessage); } }