Updating api auth and ms graph endpoint (#19)

* Updating api auth and ms graph endpoint

* updating feature flag value

* Updating get feature flags functions

* Updating get feature flags functions
This commit was merged in pull request #19.
This commit is contained in:
2024-06-06 07:31:15 -05:00
committed by GitHub
parent dd73d48ffd
commit 80b4fb8c1d
16 changed files with 967 additions and 251 deletions

BIN
.DS_Store vendored

Binary file not shown.

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
.vscode/* .vscode/*
node_modules node_modules
**/.env **/.env
.DS_Store

1
api/.gitignore vendored
View File

@@ -55,4 +55,5 @@ pids
# Diagnostic reports (https://nodejs.org/api/report.html) # Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
local.settings.json local.settings.json

View File

@@ -24,9 +24,11 @@
"@nestjs/azure-database": "^3.0.0", "@nestjs/azure-database": "^3.0.0",
"@nestjs/azure-func-http": "^0.10.0", "@nestjs/azure-func-http": "^0.10.0",
"@nestjs/common": "^10.0.0", "@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.2.2",
"@nestjs/core": "^10.0.0", "@nestjs/core": "^10.0.0",
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.0.0", "@nestjs/platform-express": "^10.0.0",
"@noahspan/noahspan-modules": "^0.1.2", "@noahspan/noahspan-modules": "^0.2.7",
"@schematics/angular": "^17.3.7", "@schematics/angular": "^17.3.7",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"reflect-metadata": "0.1.13", "reflect-metadata": "0.1.13",
@@ -39,6 +41,7 @@
"@types/express": "^4.17.17", "@types/express": "^4.17.17",
"@types/jest": "^29.5.2", "@types/jest": "^29.5.2",
"@types/node": "^20.3.1", "@types/node": "^20.3.1",
"@types/passport-azure-ad": "^4.3.6",
"@types/supertest": "^6.0.0", "@types/supertest": "^6.0.0",
"jest": "^29.5.0", "jest": "^29.5.0",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",

View File

@@ -1,34 +1,57 @@
import { Controller, Get, Query } from '@nestjs/common'; import { Controller, Get, Query } from '@nestjs/common';
import { AppService } from './app.service'; import {
import { AppConfigService, AuthService } from '@noahspan/noahspan-modules'; AppConfigService,
MsGraphService,
MsGraphClient
} from '@noahspan/noahspan-modules';
import { FeatureFlagValue } from '@azure/app-configuration'; import { FeatureFlagValue } from '@azure/app-configuration';
import { Public } from '@noahspan/noahspan-modules';
@Controller() @Controller()
export class AppController { export class AppController {
constructor( constructor(
private readonly appConfigService: AppConfigService, private readonly appConfigService: AppConfigService,
private readonly authService: AuthService private readonly msGraphService: MsGraphService
) {} ) {}
@Public()
@Get('featureFlags') @Get('featureFlags')
async getFeatureFlags(@Query() query: any): Promise<FeatureFlagValue[]> { async getFeatureFlags(
@Query() query: any
): Promise<{ key: string; enabled: boolean }[]> {
try { try {
const token: string = await this.authService.getToken( const featureFlagKeys: string[] =
'client_credentials', query.keys && query.keys.toString().includes(';')
process.env.CLIENT_ID, ? query.keys.split(';')
process.env.CLIENT_SECRET, : [query.keys];
'https://azconfig.io' const featureFlagLabel: string = query.label;
); const featureFlags: { key: string; enabled: boolean }[] =
const featureFlags: FeatureFlagValue[] =
await this.appConfigService.getFeatureFlags( await this.appConfigService.getFeatureFlags(
token, featureFlagKeys,
`${query.key}`, featureFlagLabel
query.label
); );
return featureFlags; return featureFlags;
} catch (error) { } catch (error) {
console.log(error); return error;
}
}
@Public()
@Get('profilePhoto')
async getProfilePhoto(@Query() query: any): Promise<any> {}
@Get('userProfile')
async getUserProfile(@Query() query: any) {
try {
const username: string = query.username;
const client: MsGraphClient =
await this.msGraphService.getMsGraphClient();
const userProfile = await client.api(`users/${username}`).get();
return userProfile;
} catch (error) {
return error;
} }
} }
} }

View File

@@ -1,18 +1,42 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { AppController } from './app.controller'; import { AppController } from './app.controller';
import { AppService } from './app.service'; import { AppService } from './app.service';
import { AppConfigModule, AuthModule } from '@noahspan/noahspan-modules'; import {
AppConfigModule,
AuthModule,
AuthGuard,
MsGraphModule
} from '@noahspan/noahspan-modules';
import { ConfigModule } from '@nestjs/config';
import { APP_GUARD } from '@nestjs/core';
@Module({ @Module({
imports: [ imports: [
ConfigModule.forRoot(),
AppConfigModule.register({ AppConfigModule.register({
url: process.env.APP_CONFIG_URL url: process.env.APP_CONFIG_URL,
tenantId: process.env.TENANT_ID,
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET
}), }),
AuthModule.register({ AuthModule.register({
tenantId: process.env.TENANT_ID tenantId: process.env.TENANT_ID,
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET
}),
MsGraphModule.register({
tenantId: process.env.TENANT_ID,
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET
}) })
], ],
controllers: [AppController], controllers: [AppController],
providers: [AppService] providers: [
{
provide: APP_GUARD,
useClass: AuthGuard
},
AppService
]
}) })
export class AppModule {} export class AppModule {}

View File

@@ -16,7 +16,7 @@
"@fortawesome/free-solid-svg-icons": "^6.5.2", "@fortawesome/free-solid-svg-icons": "^6.5.2",
"@fortawesome/react-fontawesome": "^0.2.2", "@fortawesome/react-fontawesome": "^0.2.2",
"@nextui-org/react": "^2.3.6", "@nextui-org/react": "^2.3.6",
"@noahspan/noahspan-components": "^0.2.4", "@noahspan/noahspan-components": "^0.2.5",
"axios": "^1.7.2", "axios": "^1.7.2",
"framer-motion": "^11.1.7", "framer-motion": "^11.1.7",
"react": "^18.2.0", "react": "^18.2.0",

View File

@@ -11,10 +11,12 @@ const App: React.FC<unknown> = () => {
useEffect(() => { useEffect(() => {
const getFeatureFlags = async () => { const getFeatureFlags = async () => {
try { try {
const featureFlagKeys: string = 'flying-pilots';
const response: AxiosResponse = await axios.get( const response: AxiosResponse = await axios.get(
`http://localhost:7071/api/featureFlags?key=flying*&label=${process.env.NODE_ENV}` `http://localhost:7071/api/featureFlags?keys=${featureFlagKeys}&label=${process.env.NODE_ENV}`
); );
const featureFlags: unknown[] = response.data; const featureFlags: { key: string; enabled: boolean }[] = response.data;
console.log(featureFlags);
if (featureFlags.length > 0) { if (featureFlags.length > 0) {
appContext.dispatch({ appContext.dispatch({

View File

@@ -1,122 +1,122 @@
import React from 'react'; // import React from 'react';
import { // import {
Accordion, // Accordion,
AccordionItem, // AccordionItem,
Button, // Button,
DatePicker, // DatePicker,
Input // Input
} from '@nextui-org/react'; // } from '@nextui-org/react';
import { useForm, Controller, SubmitHandler } from 'react-hook-form'; // import { useForm, Controller, SubmitHandler } from 'react-hook-form';
const LogbookEntryForm: React.FC<unknown> = () => { // const LogbookEntryForm: React.FC<unknown> = () => {
const { control, handleSubmit } = useForm(); // const { control, handleSubmit } = useForm();
const onSubmit = (data: unknown) => { // const onSubmit = (data: unknown) => {
console.log(data); // console.log(data);
}; // };
return ( // return (
<form className="m-10" onSubmit={handleSubmit(onSubmit)}> // <form className="m-10" onSubmit={handleSubmit(onSubmit)}>
<div className="grid grid-cols-2 gap-4"> // <div className="grid grid-cols-2 gap-4">
<div className="self-center"> // <div className="self-center">
<label>Date</label> // <label>Date</label>
</div> // </div>
<div> // <div>
<Controller // <Controller
name="date" // name="date"
control={control} // control={control}
render={({ field }) => <DatePicker labelPlacement="outside-left" />} // render={({ field }) => <DatePicker labelPlacement="outside-left" />}
/> // />
</div> // </div>
<div className="self-center"> // <div className="self-center">
<label>Aircraft Type</label> // <label>Aircraft Type</label>
</div> // </div>
<div> // <div>
<Controller // <Controller
name="aircraftType" // name="aircraftType"
control={control} // control={control}
render={({ field }) => ( // render={({ field }) => (
<Input fullWidth={true} labelPlacement="outside-left" /> // <Input fullWidth={true} labelPlacement="outside-left" />
)} // )}
/> // />
</div> // </div>
<div className="self-center"> // <div className="self-center">
<label>Aircraft Identity</label> // <label>Aircraft Identity</label>
</div> // </div>
<div> // <div>
<Controller // <Controller
name="aircraftIdent" // name="aircraftIdent"
control={control} // control={control}
render={({ field }) => ( // render={({ field }) => (
<Input fullWidth={true} labelPlacement="outside-left" /> // <Input fullWidth={true} labelPlacement="outside-left" />
)} // )}
/> // />
</div> // </div>
<div className="self-center"> // <div className="self-center">
<label>Route To</label> // <label>Route To</label>
</div> // </div>
<div> // <div>
<Controller // <Controller
name="routeTo" // name="routeTo"
control={control} // control={control}
render={({ field }) => <Input />} // render={({ field }) => <Input />}
/> // />
</div> // </div>
<div className="self-center"> // <div className="self-center">
<label>Route From</label> // <label>Route From</label>
</div> // </div>
<div> // <div>
<Controller // <Controller
name="routeFrom" // name="routeFrom"
control={control} // control={control}
render={({ field }) => <Input />} // render={({ field }) => <Input />}
/> // />
</div> // </div>
<div className="self-center"> // <div className="self-center">
<label>Duration of Flight</label> // <label>Duration of Flight</label>
</div> // </div>
<div> // <div>
<Controller // <Controller
name="flightDuration" // name="flightDuration"
control={control} // control={control}
render={({ field }) => <Input />} // render={({ field }) => <Input />}
/> // />
</div> // </div>
<div className="self-center"> // <div className="self-center">
<label>Single Engine Land</label> // <label>Single Engine Land</label>
</div> // </div>
<div> // <div>
<Controller // <Controller
name="aircraftSEL" // name="aircraftSEL"
control={control} // control={control}
render={({ field }) => <Input />} // render={({ field }) => <Input />}
/> // />
</div> // </div>
<div className="col-span-2"> // <div className="col-span-2">
<Accordion> // <Accordion>
<AccordionItem title="Aircraft Category and Class"> // <AccordionItem title="Aircraft Category and Class">
<div className="self-center"> // <div className="self-center">
<label>Single Engine Land</label> // <label>Single Engine Land</label>
</div> // </div>
<div> // <div>
<Controller // <Controller
name="aircraftSEL" // name="aircraftSEL"
control={control} // control={control}
render={({ field }) => <Input />} // render={({ field }) => <Input />}
/> // />
</div> // </div>
</AccordionItem> // </AccordionItem>
</Accordion> // </Accordion>
</div> // </div>
<div className="col-span-2 justify-self-end"> // <div className="col-span-2 justify-self-end">
<Button color="default">Cancel</Button> // <Button color="default">Cancel</Button>
<Button className="ml-10" color="primary"> // <Button className="ml-10" color="primary">
Save // Save
</Button> // </Button>
</div> // </div>
</div> // </div>
</form> // </form>
); // );
}; // };
export default LogbookEntryForm; // export default LogbookEntryForm;

View File

@@ -1,85 +1,85 @@
import { // import {
Accordion, // Accordion,
AccordionItem, // AccordionItem,
Button, // Button,
DatePicker, // DatePicker,
Input, // Input,
Select, // Select,
SelectItem // SelectItem
} from '@nextui-org/react'; // } from '@nextui-org/react';
import { useForm, Controller, SubmitHandler } from 'react-hook-form'; // import { useForm, Controller, SubmitHandler } from 'react-hook-form';
const PilotForm: React.FC<unknown> = () => { // const PilotForm: React.FC<unknown> = () => {
const { control, handleSubmit } = useForm(); // const { control, handleSubmit } = useForm();
const onSubmit = (data: unknown) => { // const onSubmit = (data: unknown) => {
console.log(data); // console.log(data);
}; // };
return ( // return (
<form className="m-10" onSubmit={handleSubmit(onSubmit)}> // <form className="m-10" onSubmit={handleSubmit(onSubmit)}>
<div className="grid grid-cols-2 gap-4"> // <div className="grid grid-cols-2 gap-4">
<div className="self-center"> // <div className="self-center">
<label>First Name</label> // <label>First Name</label>
</div> // </div>
<div> // <div>
<Controller // <Controller
name="firstName" // name="firstName"
control={control} // control={control}
render={({ field }) => <Input />} // render={({ field }) => <Input />}
/> // />
</div> // </div>
<div className="self-center"> // <div className="self-center">
<label>Last Name</label> // <label>Last Name</label>
</div> // </div>
<div> // <div>
<Controller // <Controller
name="lastName" // name="lastName"
control={control} // control={control}
render={({ field }) => <Input />} // render={({ field }) => <Input />}
/> // />
</div> // </div>
</div> // </div>
<Accordion> // <Accordion>
<AccordionItem title="Medical Certificate"> // <AccordionItem title="Medical Certificate">
<div className="grid grid-cols-2 gap-4"> // <div className="grid grid-cols-2 gap-4">
<div className="self-center"> // <div className="self-center">
<label>Class</label> // <label>Class</label>
</div> // </div>
<div> // <div>
<Controller // <Controller
name="medicalClass" // name="medicalClass"
control={control} // control={control}
render={({ field }) => ( // render={({ field }) => (
<Select> // <Select>
<SelectItem key="first" value="First"> // <SelectItem key="first" value="First">
First // First
</SelectItem> // </SelectItem>
<SelectItem key="second" value="Second"> // <SelectItem key="second" value="Second">
Second // Second
</SelectItem> // </SelectItem>
<SelectItem key="Third" value="Third"> // <SelectItem key="Third" value="Third">
Third // Third
</SelectItem> // </SelectItem>
</Select> // </Select>
)} // )}
/> // />
</div> // </div>
<div className="self-center"> // <div className="self-center">
<label>Expires</label> // <label>Expires</label>
</div> // </div>
<div> // <div>
<Controller // <Controller
name="medicalExpiration" // name="medicalExpiration"
control={control} // control={control}
render={({ field }) => <DatePicker />} // render={({ field }) => <DatePicker />}
/> // />
</div> // </div>
</div> // </div>
</AccordionItem> // </AccordionItem>
</Accordion> // </Accordion>
</form> // </form>
); // );
}; // };
export default PilotForm; // export default PilotForm;

View File

@@ -1,6 +1,6 @@
import { useState } from 'react'; import { useState } from 'react';
import SiteNav from '../siteNav/SiteNav'; import SiteNav from '../siteNav/SiteNav';
import PilotForm from '../pilotForm/PilotForm'; // import PilotForm from '../pilotForm/PilotForm';
import { Button } from '@nextui-org/react'; import { Button } from '@nextui-org/react';
import { import {
Drawer, Drawer,
@@ -37,9 +37,7 @@ const Pilots: React.FC<unknown> = () => {
<DrawerHeader> <DrawerHeader>
<h2>Add Pilot</h2> <h2>Add Pilot</h2>
</DrawerHeader> </DrawerHeader>
<DrawerBody> <DrawerBody>{/* <PilotForm /> */}</DrawerBody>
<PilotForm />
</DrawerBody>
<DrawerFooter> <DrawerFooter>
<div className="flex gap-4 justify-end justify-self-center"> <div className="flex gap-4 justify-end justify-self-center">
<div> <div>

View File

@@ -38,7 +38,7 @@ const SiteNav: React.FC<unknown> = () => {
<NavbarContent justify="center"> <NavbarContent justify="center">
<NavbarItem> <NavbarItem>
{appContext.state.featureFlags.find( {appContext.state.featureFlags.find(
(featureFlag) => featureFlag.id === 'flying-pilots' (featureFlag) => featureFlag.key === 'flying-pilots'
)?.enabled && ( )?.enabled && (
<Link> <Link>
<ReactRouterLink to="/">Pilots</ReactRouterLink> <ReactRouterLink to="/">Pilots</ReactRouterLink>

View File

@@ -1,3 +1,3 @@
export interface IAppContextState { export interface IAppContextState {
featureFlags: unknown[]; featureFlags: { key: string; enabled: boolean }[];
} }

View File

@@ -1,6 +1,9 @@
import { IAppContextState } from './IAppContextState'; import { IAppContextState } from './IAppContextState';
export type Action = { type: 'SET_FEATURE_FLAGS'; payload: unknown[] }; export type Action = {
type: 'SET_FEATURE_FLAGS';
payload: { key: string; enabled: boolean }[];
};
export const reducer = ( export const reducer = (
state: IAppContextState, state: IAppContextState,

View File

@@ -1,9 +1,9 @@
import { useAppContext } from '../appContext/UseAppContext'; import { useAppContext } from '../appContext/UseAppContext';
export const useFeatureFlag = (featureFlagId: string) => { export const useFeatureFlag = (featureFlagKey: string) => {
const appContext = useAppContext(); const appContext = useAppContext();
const featureFlag = appContext.state.featureFlags.find( const featureFlag = appContext.state.featureFlags.find(
(featureFlag) => featureFlag.id === featureFlagId (featureFlag) => featureFlag.key === featureFlagKey
); );
return featureFlag; return featureFlag;

699
package-lock.json generated

File diff suppressed because it is too large Load Diff