diff --git a/api/package.json b/api/package.json index af1ee42..91511e0 100644 --- a/api/package.json +++ b/api/package.json @@ -22,6 +22,7 @@ "dependencies": { "@azure/data-tables": "^13.2.2", "@azure/functions": "^1.0.3", + "@nestjs/axios": "^3.0.3", "@nestjs/azure-database": "^3.0.0", "@nestjs/azure-func-http": "^0.10.0", "@nestjs/common": "^10.0.0", diff --git a/api/src/app.module.ts b/api/src/app.module.ts index 1d94683..e44f786 100644 --- a/api/src/app.module.ts +++ b/api/src/app.module.ts @@ -10,6 +10,8 @@ import { import { ConfigModule } from '@nestjs/config'; import { APP_GUARD } from '@nestjs/core'; import { PilotModule } from './pilot/pilot.module'; +import { APP_FILTER } from '@nestjs/core'; +import { HttpExceptionFilter } from './filters/http-exception.filter'; @Module({ imports: [ @@ -38,6 +40,10 @@ import { PilotModule } from './pilot/pilot.module'; provide: APP_GUARD, useClass: AuthGuard }, + { + provide: APP_FILTER, + useClass: HttpExceptionFilter + }, AppService ] }) diff --git a/api/src/main.ts b/api/src/main.ts index 13cad38..fc8500d 100644 --- a/api/src/main.ts +++ b/api/src/main.ts @@ -1,8 +1,25 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; +import { HttpService } from '@nestjs/axios'; +import { HttpExceptionFilter } from './filters/http-exception.filter'; +import { InternalServerErrorException } from '@nestjs/common'; async function bootstrap() { + const httpService = new HttpService(); const app = await NestFactory.create(AppModule); + + app.useGlobalFilters(new HttpExceptionFilter()); + httpService.axiosRef.interceptors.response.use( + (response) => { + return response; + }, + (error) => { + console.error('Internal server error exception', error); + + throw new InternalServerErrorException(); + } + ); + await app.listen(3000); } bootstrap(); diff --git a/api/src/pilot/info/pilot-info.service.ts b/api/src/pilot/info/pilot-info.service.ts index 18818fa..b87304c 100644 --- a/api/src/pilot/info/pilot-info.service.ts +++ b/api/src/pilot/info/pilot-info.service.ts @@ -1,8 +1,10 @@ -import { Injectable } from '@nestjs/common'; +import { HttpException, Injectable } from '@nestjs/common'; // import { Repository, InjectRepository } from '@nestjs/azure-database'; import { PilotInfoDto } from './pilot-info.dto'; import { PilotInfoEntity } from './pilot-info.entity'; import { TableClient, TableService } from '@noahspan/noahspan-modules'; +import { RestError, TableInsertEntityHeaders } from '@azure/data-tables'; +import { CustomError } from '../../customError/CustomError'; @Injectable() export class PilotInfoService { @@ -22,7 +24,7 @@ export class PilotInfoService { // return await this.pilotInfoRepository.findAll(); // } - async create(pilotInfoData: PilotInfoDto): Promise { + async create(pilotInfoData: PilotInfoDto): Promise { const client: TableClient = await this.tableService.getTableClient('Pilots'); const pilotInfo: PilotInfoEntity = new PilotInfoEntity(); @@ -30,11 +32,17 @@ export class PilotInfoService { Object.assign(pilotInfo, pilotInfoData); pilotInfo.partitionKey = 'pilot'; pilotInfo.rowKey = pilotInfo.id; - console.log(pilotInfo); + try { - await client.createEntity(pilotInfo); + return await client.createEntity(pilotInfo); } catch (error) { - return error; + const restError: RestError = error as RestError; + + throw new CustomError( + restError.details['odataError']['message']['value'], + restError.details['odataError']['code'], + restError.statusCode + ); } } diff --git a/api/src/pilot/pilot.controller.ts b/api/src/pilot/pilot.controller.ts index 8776ced..df58987 100644 --- a/api/src/pilot/pilot.controller.ts +++ b/api/src/pilot/pilot.controller.ts @@ -1,14 +1,8 @@ -import { - Body, - Controller, - Get, - Post, - Put, - Query, - UnprocessableEntityException -} from '@nestjs/common'; +import { Body, Controller, HttpException, Post } from '@nestjs/common'; import { PilotInfoService } from './info/pilot-info.service'; import { PilotInfoDto } from './info/pilot-info.dto'; +import { TableInsertEntityHeaders } from '@azure/data-tables'; +import { CustomError } from '../customError/CustomError'; @Controller('pilots') export class PilotController { @@ -17,9 +11,23 @@ export class PilotController { @Post() async createPilot(@Body() pilotInfoData: PilotInfoDto): Promise { try { - return await this.pilotInfoService.create(pilotInfoData); + const response: TableInsertEntityHeaders = + await this.pilotInfoService.create(pilotInfoData); + + console.log(`Not Broken: ${response}`); } catch (error) { - throw new UnprocessableEntityException(error); + const customError = error as CustomError; + console.log(customError.name); + throw new HttpException(customError.message, customError.statusCode, { + cause: customError.name + }); + + // throw new HttpException({ + // status: customError.statusCode, + // error: customError.message + // }, customError.statusCode, { + // cause: customError.name + // }); } } } diff --git a/app/src/App.tsx b/app/src/App.tsx index 0e8b7e3..0926359 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -15,7 +15,7 @@ type EventPayloadExtended = EventPayload & { accessToken: string }; const App: React.FC = () => { const httpClient: AxiosInstance = useHttpClient(); const appContext = useAppContext(); - const { instance } = useMsal(); + const { inProgress, instance } = useMsal(); const handleSignIn = async () => { await instance.loginRedirect({ scopes: [`api://${import.meta.env.VITE_CLIENT_ID}/user_impersonation`] @@ -32,7 +32,6 @@ const App: React.FC = () => { const response: AxiosResponse = await httpClient.get( `api/featureFlags?keys=${featureFlagKeys}&label=${import.meta.env.MODE}` ); - console.log(response.data); const featureFlags: { key: string; enabled: boolean }[] = response.data; if (featureFlags.length > 0) { @@ -87,7 +86,11 @@ const App: React.FC = () => { return (
- + {useFeatureFlag('flying-pilots')?.enabled && ( } /> diff --git a/app/src/components/pilotForm/PilotForm.tsx b/app/src/components/pilotForm/PilotForm.tsx index f01b34b..224b887 100644 --- a/app/src/components/pilotForm/PilotForm.tsx +++ b/app/src/components/pilotForm/PilotForm.tsx @@ -25,7 +25,7 @@ import { IPilotFormProps } from './IPilotFormProps'; import PilotFormCertificates from '../pilotFormCertificates/PilotFormCertificates'; import PilotFormEndorsements from '../pilotFormEndorsements/PilotFormEndorsements'; import { Person } from '@microsoft/microsoft-graph-types'; -import { AxiosInstance, AxiosResponse } from 'axios'; +import axios, { AxiosError, AxiosInstance, AxiosResponse } from 'axios'; import { useHttpClient } from '../../hooks/httpClient/UseHttpClient'; import { useAccessToken } from '../../hooks/accessToken/UseAcessToken'; import { IPilotFormCertificates } from '../pilotFormCertificates/IPilotFormCertificates'; @@ -41,6 +41,7 @@ const PilotForm: React.FC = ({ const [peoplePickerResults, setPeoplePickerResults] = useState([]); const [isPeoplePickerLoading, setIsPeoplePickerLoading] = useState(false); + const [isLoading, setIsLoading] = useState(false); const { getAccessToken } = useAccessToken(); const methods = useForm(); @@ -82,14 +83,31 @@ const PilotForm: React.FC = ({ }; const onSubmit = async (data: unknown) => { - console.log(data); + try { + setIsLoading(true); - const accessToken: string = await getAccessToken(); - const response: AxiosResponse = await httpClient.post(`api/pilots`, data, { - headers: { - Authorization: accessToken + const accessToken: string = await getAccessToken(); + const response: AxiosResponse = await httpClient.post( + `api/pilots`, + data, + { + headers: { + Authorization: accessToken + } + } + ); + + if (response) console.log(response); + } catch (error) { + if (axios.isAxiosError(error)) { + const errResp = error.response; + + console.log(errResp?.data.message); + } else { } - }); + } finally { + setIsLoading(false); + } }; useEffect(() => { @@ -428,6 +446,7 @@ const PilotForm: React.FC = ({
- - + + + + + + List Item 1 + +
- + + +
+ // ); }; diff --git a/app/src/components/siteNav/SiteNav.tsx b/app/src/components/siteNav/SiteNav.tsx index 5a303f4..90d5e04 100644 --- a/app/src/components/siteNav/SiteNav.tsx +++ b/app/src/components/siteNav/SiteNav.tsx @@ -1,3 +1,4 @@ +import { ISiteNavProps } from './ISiteNavProps'; // import { Link as ReactRouterLink } from 'react-router-dom'; import { useAppContext } from '../../hooks/appContext/UseAppContext'; import { @@ -12,19 +13,17 @@ import { NavbarMenu, NavbarItemProps, PlaneIcon, + Spinner, Typography } from '@noahspan/noahspan-components'; import { useIsAuthenticated } from '@azure/msal-react'; +import { InteractionStatus } from '@azure/msal-browser'; -interface SiteNavProps { - handleSignIn: () => void; - handleSignOut: () => void; -} - -const SiteNav: React.FC = ({ +const SiteNav: React.FC = ({ handleSignIn, - handleSignOut -}: SiteNavProps) => { + handleSignOut, + inProgress +}: ISiteNavProps) => { const appContext = useAppContext(); const isAuthenticated = useIsAuthenticated(); const navItems: NavbarItemProps[] = [ @@ -47,22 +46,25 @@ const SiteNav: React.FC = ({
- +
- - + + Sign Out
)} {!isAuthenticated && ( - )} diff --git a/app/src/hooks/accessToken/UseAcessToken.tsx b/app/src/hooks/accessToken/UseAcessToken.tsx index bc37731..336d9e5 100644 --- a/app/src/hooks/accessToken/UseAcessToken.tsx +++ b/app/src/hooks/accessToken/UseAcessToken.tsx @@ -15,7 +15,7 @@ export const useAccessToken = () => { try { const response: AuthenticationResult = await instance.acquireTokenSilent(tokenRequest); - console.log(response.accessToken); + return `Bearer ${response.accessToken}`; } catch (error) { if (error instanceof InteractionRequiredAuthError) { diff --git a/package-lock.json b/package-lock.json index ea8978d..c94dddd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "dependencies": { "@azure/data-tables": "^13.2.2", "@azure/functions": "^1.0.3", + "@nestjs/axios": "^3.0.3", "@nestjs/azure-database": "^3.0.0", "@nestjs/azure-func-http": "^0.10.0", "@nestjs/common": "^10.0.0", @@ -3065,6 +3066,16 @@ "tslib": "^2.3.1" } }, + "node_modules/@nestjs/axios": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-3.0.3.tgz", + "integrity": "sha512-h6TCn3yJwD6OKqqqfmtRS5Zo4E46Ip2n+gK1sqwzNBC+qxQ9xpCu+ODVRFur6V3alHSCSBxb3nNtt73VEdluyA==", + "peerDependencies": { + "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", + "axios": "^1.3.1", + "rxjs": "^6.0.0 || ^7.0.0" + } + }, "node_modules/@nestjs/azure-database": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@nestjs/azure-database/-/azure-database-3.0.0.tgz",