Skip to content

Commit

Permalink
Merge branch 'feature/plugin-token-frontend' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
dotFionn committed Mar 30, 2024
2 parents 83541fc + 2becf03 commit fe93613
Show file tree
Hide file tree
Showing 16 changed files with 379 additions and 39 deletions.
9 changes: 9 additions & 0 deletions src/backend/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import cookieParser from 'cookie-parser';
import { Request } from 'express';
import mongoose from 'mongoose';
import morgan from 'morgan';
import { WinstonModule } from 'nest-winston';

Expand Down Expand Up @@ -37,6 +38,14 @@ async function bootstrap() {
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);

(async () => {
try {
await mongoose.syncIndexes();
logger.info('Synced indexes to MongoDB!');
} catch (error) {
logger.warn('Failed to sync indexes to MongoDB: %o', error);
}
})();

await app.listen(3000);
}
Expand Down
33 changes: 32 additions & 1 deletion src/backend/plugin-token/plugin-token.controller.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { BadRequestException, Body, Controller, Get, HttpCode, NotFoundException, Param, Post, Res, UnauthorizedException } from '@nestjs/common';
import { BadRequestException, Body, Controller, Delete, Get, HttpCode, NotFoundException, Param, Post, Query, Res, UnauthorizedException, UseGuards } from '@nestjs/common';
import { ApiExcludeController } from '@nestjs/swagger';
import { Response } from 'express';
import { FilterQuery } from 'mongoose';

import PluginToken from '../../shared/interfaces/plugin-token.interface';
import { User } from '../auth/auth.decorator';
import { AuthGuard } from '../auth/auth.guard';
import getAppConfig from '../config';
import logger from '../logger';
import { UserDocument } from '../user/user.model';
Expand Down Expand Up @@ -96,4 +99,32 @@ export class PluginTokenController {
token,
};
}

@Get('')
@UseGuards(AuthGuard)
async getAllTokens(@User() user: UserDocument, @Query('scope') scope = 'own') {
let filter: FilterQuery<PluginToken> = { user: user._id };

if (scope == 'all' && user.admin) {
filter = {};
}

const tokens = await this.pluginTokenService.findTokens(filter);

return tokens;
}

@Delete('/:id')
@UseGuards(AuthGuard)
async revokeToken(@User() user: UserDocument, @Param('id') id: string) {
const token = await this.pluginTokenService.findToken({ user: user._id, _id: id });

if (!token) {
throw new NotFoundException();
}

this.pluginTokenService.deleteTokenById(token._id);

return token;
}
}
4 changes: 2 additions & 2 deletions src/backend/plugin-token/plugin-token.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export type PluginTokenDocument = HydratedDocument<PluginToken>;
const PluginTokenSchema = new mongoose.Schema<PluginToken>({
user: { type: String },
label: { type: String, default: 'New token' },
pollingSecret: { type: String, required: true },
token: { type: String, required: true },
pollingSecret: { type: String, select: false },
token: { type: String, required: true, select: false },
lastUsed: { type: Date, default: Date.now },
}, { timestamps: true });

Expand Down
21 changes: 20 additions & 1 deletion src/backend/plugin-token/plugin-token.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Inject, Injectable } from '@nestjs/common';
import mongoose from 'mongoose';
import mongoose, { FilterQuery } from 'mongoose';

import PluginToken from '../../shared/interfaces/plugin-token.interface';
import { UtilsService } from '../utils/utils.service';

import { PLUGINTOKEN_MODEL, PluginTokenDocument, PluginTokenModel } from './plugin-token.model';
Expand Down Expand Up @@ -80,4 +81,22 @@ export class PluginTokenService {

return count > 0;
}

async findTokens(filter: FilterQuery<PluginToken> = {}): Promise<PluginTokenDocument[]> {
const tokens = await this.pluginTokenModel.find(filter);

return tokens;
}

async findToken(filter: FilterQuery<PluginToken> = {}): Promise<PluginTokenDocument | null> {
const token = await this.pluginTokenModel.findOne(filter);

return token;
}

async deleteTokenById(id: string | mongoose.Types.ObjectId): Promise<PluginTokenDocument | null> {
const token = await this.pluginTokenModel.findOneAndDelete({ _id: id });

return token;
}
}
8 changes: 7 additions & 1 deletion src/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,18 @@ import Debug from './pages/Debug';
import Delivery from './pages/Delivery';
import FlowManagement from './pages/FlowManagement';
import Landingpage from './pages/Landingpage';
import ProfilePage from './pages/Profile';
import Vdgs from './pages/VdgsNew';
import { button } from './utils/ui/customDesign/button';
import { card } from './utils/ui/customDesign/card';
import { datatable } from './utils/ui/customDesign/datatable';
import { dataview } from './utils/ui/customDesign/dataview';
import { dialog } from './utils/ui/customDesign/dialog';
import { dropdown } from './utils/ui/customDesign/dropdown';
import { global } from './utils/ui/customDesign/global';
import { inputnumber } from './utils/ui/customDesign/inputnumber';
import { inputtext } from './utils/ui/customDesign/inputtext';
import { menu } from './utils/ui/customDesign/menu';
import { selectbutton } from './utils/ui/customDesign/selectbutton';
import { toast } from './utils/ui/customDesign/toast';
import { toolbar } from './utils/ui/customDesign/toolbar';
Expand All @@ -55,6 +58,8 @@ function App() {
inputnumber: inputnumber,
inputtext: inputtext,
selectbutton: selectbutton,
menu: menu,
dataview: dataview,

},
{ mergeSections: true, mergeProps: false },
Expand All @@ -65,7 +70,7 @@ function App() {
<>
<Router>
<DarkModeProvider>
<PrimeReactProvider value={{ unstyled: true, pt: CustomTailwind, ripple: true }}>
<PrimeReactProvider value={{ unstyled: true, pt: CustomTailwind, ripple: false }}>
<AuthProvider>
<Navbar />
<div className="mt-2">
Expand All @@ -85,6 +90,7 @@ function App() {
<Route path="/delivery" element={<Delivery />} />
<Route path="/auth-failure" element={<AuthFailurePage />} />
<Route path='/authorize-plugin/:id' element={<AuthorizePluginPage />} />
<Route path='/profile' element={<ProfilePage />} />
<Route path="/" element={<Landingpage />} />
<Route path="*" element={<Navigate to="/" />} />
</Routes>
Expand Down
60 changes: 30 additions & 30 deletions src/frontend/src/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import { Disclosure, Menu, Transition } from '@headlessui/react';
import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline';
import { Button } from 'primereact/button';
import { Fragment, useContext, useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Link, useNavigate } from 'react-router-dom';

import logo from '../assets/cdm_logo.png';
import AuthContext from '../contexts/AuthProvider';
import DarkModeContext from '../contexts/DarkModeProvider';
import AuthService from '../services/AuthService';

import { FrontendSettings } from '@/shared/interfaces/config.interface';
// import { FrontendSettings } from '@/shared/interfaces/config.interface';
import ProfilePicture from './ProfilePicture';

import User from '@/shared/interfaces/user.interface';

interface NavItemDefinition {
Expand All @@ -24,18 +25,12 @@ function classNames(...classes: string[]) {
}

export default function NavbarWithDropdown() {
const [config, setConfig] = useState<FrontendSettings>();
// const [config, setConfig] = useState<FrontendSettings>();
const navigate = useNavigate();
const [items, setItems] = useState<NavItemDefinition[]>([]);
const { auth } = useContext(AuthContext);
const { auth, authenticate, logout } = useContext(AuthContext);
const { darkMode, changeDarkMode } = useContext(DarkModeContext);

async function logout() {
await AuthService.logout();

window.location.reload();
}

const navItems: NavItemDefinition[] = [
{
label: 'Delivery',
Expand Down Expand Up @@ -63,24 +58,20 @@ export default function NavbarWithDropdown() {
},
];

const redirectToVatsimAuth = () => {
window.location.replace('/api/auth');
};

useEffect(() => {
setItems(
navItems.filter((item) =>
(item.permission ? item.permission : () => true)(auth.user),
),
);

AuthService.getConfig()
.then((data) => {
setConfig(data);
})
.catch((e) => {
console.error(e);
});
// AuthService.getConfig()
// .then((data) => {
// setConfig(data);
// })
// .catch((e) => {
// console.error(e);
// });
return () => {};
}, [auth]);

Expand Down Expand Up @@ -137,7 +128,7 @@ export default function NavbarWithDropdown() {
</div>
</div>
<div className="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
{config?.vaccLogoUrl && <img alt="vacc-logo" src={config?.vaccLogoUrl} className='hidden md:block max-h-[40px] mr-3' />}
{/* {config?.vaccLogoUrl && <img alt="vacc-logo" src={config?.vaccLogoUrl} className='hidden md:block max-h-[40px] mr-3' />} */}
<button
onClick={changeDarkMode}
type="button"
Expand Down Expand Up @@ -177,7 +168,7 @@ export default function NavbarWithDropdown() {
</button>

<div className={`${auth.user ? 'hidden' : ''} ml-2`}>
<Button size='small' onClick={() => redirectToVatsimAuth()}>Login</Button>
<Button size='small' onClick={authenticate}>Login</Button>
</div>

{/* Profile dropdown */}
Expand All @@ -186,11 +177,7 @@ export default function NavbarWithDropdown() {
<div className={!auth.user ? 'hidden' : ''}>
<Menu.Button className="flex rounded-full bg-zinc-900 text-white hover:text-white">
<span className="sr-only">Open user menu</span>
<img
className="h-8 w-8 rounded-full"
src={`https://ui-avatars.com/api/?name=${auth.user?.firstName.charAt(0)}+${auth.user?.lastName.charAt(0)}&color=FFFFFF&background=18181B&format=svg`}
alt="##"
/>
<ProfilePicture user={auth.user} className='h-8 w-8 rounded-full' />
</Menu.Button>
</div>
<Transition
Expand All @@ -203,10 +190,23 @@ export default function NavbarWithDropdown() {
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
<Menu.Item>
{({ active }) => (
<Link
to="/profile"
className={classNames(
active ? 'bg-gray-100' : '',
'block px-4 py-2 text-sm text-gray-700 cursor-pointer',
)}
>
Profile
</Link>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<span
onClick={() => logout()}
onClick={logout}
className={classNames(
active ? 'bg-gray-100' : '',
'block px-4 py-2 text-sm text-gray-700 cursor-pointer',
Expand Down
39 changes: 39 additions & 0 deletions src/frontend/src/components/ProfilePicture.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import User from '../../../shared/interfaces/user.interface';

export interface IProfilePictureProps {
user: User | undefined;
className?: string | void;
size?: number | void;
}

export default function ProfilePicture({ user, className = 'rounded-full border-2', size = 64 }: IProfilePictureProps) {
return <svg
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
width={`${size}px`}
height={`${size}px`}
viewBox={`0 0 ${size} ${size}`}
version="1.1"
className={className || ''}>
<rect
fill="#18181B"
cx={size / 2}
width={size}
height={size}
cy={size / 2}
r={size / 2}
/>
<text
x="50%"
y="50%"
style={{ color: '#FFF', lineHeight: 1, fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif" }}
alignmentBaseline="middle"
textAnchor="middle"
fontSize={size / 2.3}
fontWeight="400"
dy=".1em"
dominantBaseline="middle"
fill="#FFFFFF"
>{user ? `${user.firstName.charAt(0)}${user.lastName.charAt(0)}` : '?'}</text>
</svg>;
}
19 changes: 16 additions & 3 deletions src/frontend/src/contexts/AuthProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,41 @@ const AuthContext = createContext<{
user: User | undefined
};
setAuth: Dispatch<SetStateAction<{ user: User | undefined }>>;
}>({ auth: { user: undefined }, setAuth: () => {} });
authenticate: () => void;
logout: () => void;
}>({ auth: { user: undefined }, setAuth() {}, authenticate() {}, logout() {} });

export const AuthProvider = ({ children }: PropsWithChildren) => {
const [auth, setAuth] = useState<{ user: User | undefined }>({ user: undefined });

const navigate = useNavigate();

function authenticate() {
window.location.replace('/api/auth');
}

async function logout() {
await authService.logout();

window.location.reload();
}

useEffect(() => {
authService
.getProfile()
.then((user) => {
setAuth({ user });
})
.catch(() => {
.catch((error) => {
console.error(error);
setAuth({ user: undefined });
navigate('/auth-failure');
});
}, []);

return (
<>
<AuthContext.Provider value={{ auth, setAuth }}>
<AuthContext.Provider value={{ auth, setAuth, authenticate, logout }}>
{children}
</AuthContext.Provider>
</>
Expand Down
Loading

0 comments on commit fe93613

Please sign in to comment.