Feature/4 pilots add #20

Merged
noahspannbauer merged 64 commits from feature/4-pilots---add into main 2024-09-23 21:52:59 -04:00
11 changed files with 143 additions and 53 deletions
Showing only changes of commit 09fb9ebdf2 - Show all commits

View File

@@ -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",

View File

@@ -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
]
})

View File

@@ -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();

View File

@@ -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<void> {
async create(pilotInfoData: PilotInfoDto): Promise<TableInsertEntityHeaders> {
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
);
}
}

View File

@@ -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<void> {
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
// });
}
}
}

View File

@@ -15,7 +15,7 @@ type EventPayloadExtended = EventPayload & { accessToken: string };
const App: React.FC<unknown> = () => {
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<unknown> = () => {
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<unknown> = () => {
return (
<div className="container mx-auto">
<SiteNav handleSignIn={handleSignIn} handleSignOut={handleSignOut} />
<SiteNav
handleSignIn={handleSignIn}
handleSignOut={handleSignOut}
inProgress={inProgress}
/>
<Routes>
{useFeatureFlag('flying-pilots')?.enabled && (
<Route path="/" element={<Pilots />} />

View File

@@ -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<IPilotFormProps> = ({
const [peoplePickerResults, setPeoplePickerResults] = useState<Person[]>([]);
const [isPeoplePickerLoading, setIsPeoplePickerLoading] =
useState<boolean>(false);
const [isLoading, setIsLoading] = useState<boolean>(false);
const { getAccessToken } = useAccessToken();
const methods = useForm();
@@ -82,14 +83,31 @@ const PilotForm: React.FC<IPilotFormProps> = ({
};
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<IPilotFormProps> = ({
<div>
<Button
className="flex items-center gap-3"
loading={isLoading}
variant="filled"
type="submit"
>

View File

@@ -4,7 +4,11 @@ import {
Button,
Card,
PlusIcon,
Typography
Typography,
Menu,
MenuItem,
MenuHandler,
MenuList
} from '@noahspan/noahspan-components';
const Pilots: React.FC<unknown> = () => {
@@ -14,26 +18,37 @@ const Pilots: React.FC<unknown> = () => {
};
return (
<Card className="mt-6 p-6">
<div className="grid grid-cols-1 gap-4">
// <Card className="mt-6 p-6">
<div className="grid grid-cols-1 gap-4">
<div className="col-span-1">
<Typography variant="h2">Pilots</Typography>
</div>
<div className="col-span-1 justify-self-end">
<Button
className="flex items-center gap-3"
className="flex justify-center gap-3"
variant="filled"
onClick={onOpenCloseDrawer}
data-testid="add-pilot-button"
fullWidth={true}
>
<PlusIcon size="lg" />
Add Pilot
</Button>
<PilotForm
isDrawerOpen={isDrawerOpen}
onOpenCloseDrawer={onOpenCloseDrawer}
/>
<Menu>
<MenuHandler>
<Button>Menu</Button>
</MenuHandler>
<MenuList>
<MenuItem>List Item 1</MenuItem>
</MenuList>
</Menu>
</div>
</Card>
<PilotForm
isDrawerOpen={isDrawerOpen}
onOpenCloseDrawer={onOpenCloseDrawer}
/>
</div>
//</Card>
);
};

View File

@@ -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<SiteNavProps> = ({
const SiteNav: React.FC<ISiteNavProps> = ({
handleSignIn,
handleSignOut
}: SiteNavProps) => {
handleSignOut,
inProgress
}: ISiteNavProps) => {
const appContext = useAppContext();
const isAuthenticated = useIsAuthenticated();
const navItems: NavbarItemProps[] = [
@@ -47,22 +46,25 @@ const SiteNav: React.FC<SiteNavProps> = ({
<Menu placement="bottom-end">
<MenuHandler>
<div>
<Typography>
<Button variant="text" size="sm">
{appContext.state.userProfile.displayName}
</Typography>
</Button>
</div>
</MenuHandler>
<MenuList>
<MenuItem>
<Button variant="text" size="sm" onClick={handleSignOut}>
Sign Out
</Button>
<MenuItem onClick={handleSignOut}>
<Typography variant="small">Sign Out</Typography>
</MenuItem>
</MenuList>
</Menu>
)}
{!isAuthenticated && (
<Button variant="text" size="sm" onClick={handleSignIn}>
<Button
variant="text"
size="sm"
onClick={handleSignIn}
loading={inProgress === InteractionStatus.Login ? true : false}
>
Sign In
</Button>
)}

View File

@@ -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) {

11
package-lock.json generated
View File

@@ -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",