diff --git a/apps/web/package.json b/apps/web/package.json index 8dd5c441d60f7f287683060569365d39409ec378..c31bb9017f2ea078ef797e79174aa40e8c948b67 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -4,7 +4,8 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite", + "dev": "vite --host local.graphpolaris.com --port 4200", + "dev2": "vite --host local.graphpolaris.com --port 4200", "build": "tsc && vite build", "preview": "vite preview" }, @@ -29,6 +30,7 @@ "@types/react-dom": "^18.0.11", "@types/react-grid-layout": "^1.3.2", "@types/styled-components": "^5.1.26", + "@vitejs/plugin-basic-ssl": "^1.0.1", "@vitejs/plugin-react-swc": "^3.0.0", "graphology-types": "^0.24.7", "react-is": "^18.2.0", diff --git a/apps/web/public/assets/icons/ExportIcon.png b/apps/web/public/assets/icons/ExportIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..62976b8a7902bc100cad532959e3d9e0d5fea42c Binary files /dev/null and b/apps/web/public/assets/icons/ExportIcon.png differ diff --git a/apps/web/public/assets/login-screen/github.png b/apps/web/public/assets/login-screen/github.png new file mode 100644 index 0000000000000000000000000000000000000000..55f90676824d4901f6fe7a50ed11f6d4af3d6fef Binary files /dev/null and b/apps/web/public/assets/login-screen/github.png differ diff --git a/apps/web/public/assets/login-screen/google.png b/apps/web/public/assets/login-screen/google.png new file mode 100644 index 0000000000000000000000000000000000000000..f27bb2433042aea5fc34e19fcf90944430ec331b Binary files /dev/null and b/apps/web/public/assets/login-screen/google.png differ diff --git a/apps/web/src/app/app.tsx b/apps/web/src/app/app.tsx index bb7fca8f820d6b54ee66ac325297234873a8f57c..dcdc35b0784678e3a90e1e6f79b796f2fe0a1c2d 100644 --- a/apps/web/src/app/app.tsx +++ b/apps/web/src/app/app.tsx @@ -5,47 +5,35 @@ import Panel from '../components/panels/panel'; import { RawJSONVis } from '@graphpolaris/shared/lib/vis/rawjsonvis/rawjsonvis'; import SemanticSubstrates from '@graphpolaris/shared/lib/vis/semanticsubstrates/semanticsubstrates'; import { Schema } from '@graphpolaris/shared/lib/schema/panel'; -import { GetUserInfo } from '@graphpolaris/shared/lib/data-access/api'; +import { + GetAllDatabases, + GetUserInfo, +} from '@graphpolaris/shared/lib/data-access/api'; import { QueryBuilder } from '@graphpolaris/shared/lib/querybuilder'; import { assignNewGraphQueryResult, useAppDispatch, } from '@graphpolaris/shared/lib/data-access/store'; -import { AuthorizationHandler } from '@graphpolaris/shared/lib/data-access/authorization'; - -function useIsAuthorized() { - const [userAuthorized, setUserAuthorized] = useState(false); - - const authCallback = async () => { - setUserAuthorized(true); - - // Print the user that is currently logged in - const user = await GetUserInfo(); - console.log(user); - }; - - AuthorizationHandler.instance().setCallback(authCallback); - - // Attempt to Authorize the user - const authorize = async () => { - const authorized = await AuthorizationHandler.instance().Authorize(); - setUserAuthorized(authorized); - }; - - useEffect(() => { - authorize(); - }, []); - - return userAuthorized; -} +import { + AuthorizationHandler, + useIsAuthorized, +} from '@graphpolaris/shared/lib/data-access/authorization'; export function App() { const dispatch = useAppDispatch(); const userIsAuthorized = useIsAuthorized(); + useEffect(() => { + if (userIsAuthorized) { + GetAllDatabases().then((d) => { + console.log(d); + }); + } + }, [userIsAuthorized]); + return ( <> - {/* {!userIsAuthorized && <LoginScreen />} */} + {!userIsAuthorized && <LoginScreen />} <GridLayout className="layout" cols={10} diff --git a/apps/web/src/components/navbar/AddDatabaseForm/AddDatabaseFormComponent.tsx b/apps/web/src/components/navbar/AddDatabaseForm/AddDatabaseFormComponent.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c4682516870ec05ee2438e538142e626d7147952 --- /dev/null +++ b/apps/web/src/components/navbar/AddDatabaseForm/AddDatabaseFormComponent.tsx @@ -0,0 +1,217 @@ +/** + * This program has been developed by students from the bachelor Computer Science at + * Utrecht University within the Software Project course. + * © Copyright Utrecht University (Department of Information and Computing Sciences) + */ + +/* istanbul ignore file */ +/* The comment above was added so the code coverage wouldn't count this file towards code coverage. + * We do not test components/renderfunctions/styling files. + * See testing plan for more details.*/ +import React from 'react'; +import { ClassNameMap } from '@material-ui/styles'; +import { TextField, Button, WithStyles, withStyles, NativeSelect } from '@material-ui/core'; +import { useStyles } from './AddDatabaseFormUIStylesheet'; + +/** AddDatabaseFormProps is an interface containing the AuthViewModel. */ +export interface AddDatabaseFormProps extends WithStyles<typeof useStyles> { + open: boolean; + onClose(): void; + onSubmit( + username: string, + password: string, + hostname: string, + port: number, + databaseName: string, + internalDatabase: string, + databaseType: string, + ): void; +} + +/** AddDatabaseFormState is an interface containing the databasehost information. */ +export interface AddDatabaseFormState { + username: string; + password: string; + hostname: string; + port: number; + databaseName: string; + internalDatabase: string; + databaseType: string; + + styles: ClassNameMap; +} + +/** AddDatabaseForm is the View implementation for the connect screen that will be rendered. */ +class AddDatabaseForm extends React.Component<AddDatabaseFormProps, AddDatabaseFormState> { + public constructor(props: AddDatabaseFormProps) { + super(props); + + this.state = { + username: 'root', + password: 'DikkeDraak', + hostname: 'https://datastrophe.science.uu.nl/', + port: 8529, + databaseName: 'Tweede Kamer Dataset', + internalDatabase: 'TweedeKamer', + styles: this.props.classes, + databaseType: 'arangodb', + }; + } + + /** + * Validates if the port value is numerical. Only then will the state be updated. + * @param port The new port value. + */ + private handlePortChanged(port: string): void { + if (!isNaN(Number(port))) this.setState({ ...this.state, port: Number(port) }); + } + + /** Handles the submit button click. Calls the onSubmit in the props with all the fields. */ + private handleSubmitClicked(): void { + this.props.onSubmit( + this.state.username, + this.state.password, + this.state.hostname, + this.state.port, + this.state.databaseName, + this.state.internalDatabase, + this.state.databaseType, + ); + } + + public render(): JSX.Element { + const { + username, + password, + port, + hostname, + databaseName, + internalDatabase, + styles, + databaseType, + } = this.state; + + return this.props.open ? ( + <div + className={styles.wrapper} + onMouseDown={() => { + this.props.onClose(); + }} + > + <div + className={styles.authWrapper} + onMouseDown={(e) => { + e.stopPropagation(); + }} + > + <h1 className={styles.header}>Database Connect</h1> + <form + className={styles.formWrapper} + onSubmit={(event: React.FormEvent) => { + event.preventDefault(); + this.handleSubmitClicked(); + }} + > + <div className={styles.loginContainer}> + <TextField + className={styles.passLabel} + label="Database name" + type="databaseName" + value={databaseName} + onChange={(event) => + this.setState({ ...this.state, databaseName: event.currentTarget.value }) + } + required + /> + </div> + <div className={styles.loginContainer}> + <NativeSelect + className={styles.passLabel} + value={databaseType} + onChange={(event) => { + this.setState({ ...this.state, databaseType: event.currentTarget.value }); + }} + > + <option value="arangodb">arangodb</option> + <option value="neo4j">neo4j</option> + </NativeSelect> + </div> + <div className={styles.loginContainerRow}> + <TextField + className={styles.hostLabel} + label="Hostname/IP" + type="hostname" + value={hostname} + onChange={(event) => + this.setState({ ...this.state, hostname: event.currentTarget.value }) + } + required + /> + <TextField + className={styles.portLabel} + label="Port" + type="port" + value={port} + onChange={(event) => this.handlePortChanged(event.currentTarget.value)} + required + /> + </div> + <div className={styles.loginContainer}> + <TextField + className={styles.userLabel} + label="Username" + type="username" + value={username} + onChange={(event) => + this.setState({ ...this.state, username: event.currentTarget.value }) + } + required + /> + </div> + <div className={styles.loginContainer}> + <TextField + className={styles.passLabel} + label="Password" + type="password" + value={password} + onChange={(event) => + this.setState({ ...this.state, password: event.currentTarget.value }) + } + required + /> + </div> + <div className={styles.loginContainer}> + <TextField + className={styles.passLabel} + label="Internal database" + type="internalDatabaseName" + value={internalDatabase} + onChange={(event) => + this.setState({ ...this.state, internalDatabase: event.currentTarget.value }) + } + required + /> + </div> + <div className={styles.loginContainerButton}> + <Button variant="outlined" type="submit"> + Submit + </Button> + <Button + className={styles.cancelButton} + variant="outlined" + onClick={() => { + this.props.onClose(); + }} + > + Cancel + </Button> + </div> + </form> + </div> + </div> + ) : ( + <></> + ); + } +} +export default withStyles(useStyles)(AddDatabaseForm); diff --git a/apps/web/src/components/navbar/AddDatabaseForm/add-database-form.scss b/apps/web/src/components/navbar/AddDatabaseForm/add-database-form.scss new file mode 100644 index 0000000000000000000000000000000000000000..97642ae3758a844a1c36c7afcb9185993e8c419b --- /dev/null +++ b/apps/web/src/components/navbar/AddDatabaseForm/add-database-form.scss @@ -0,0 +1,75 @@ +.wrapper { + height: 100vh; + display: flex; + align-items: center; + width: 100vw; + background-color: rgba(0, 0, 0, 0.87); + position: absolute; + z-index: 1225; +} + +.authWrapper { + font-family: Poppins, sans-serif; + display: flex; + flex-direction: column; + flex-wrap: wrap; + justify-content: center; + max-width: 400px; + margin: auto; + overflow: auto; + min-height: 300px; + background-color: #f7f8fc; + padding: 30px; + border-radius: 5px; + padding-bottom: 300px; +} + +.header { + text-align: center; +} + +.formWrapper { +} + +.loginContainer { + margin: 5px 0px !important; +} + +.loginContainerRow { + display: flex; + flex-direction: row; + margin: 5px 0px; +} + +.loginContainerButton { + margin-top: 10px !important; + + & button { + width: 100%; + } +} + +.hostLabel { + flex: 1 0 66.66667%; + + & div { + width: 95%; + } +} + +.portLabel { + flex: 1 0 33.33334%; +} + +.userLabel { + width: 100%; +} + +passLabel { + width: 100%; +} + +cancelButton { + margin-top: 2.5%; +} + diff --git a/apps/web/src/components/navbar/MenuList.scss b/apps/web/src/components/navbar/MenuList.scss new file mode 100644 index 0000000000000000000000000000000000000000..af7f9f647753753c0c3077e7a1994ada4454fcbb --- /dev/null +++ b/apps/web/src/components/navbar/MenuList.scss @@ -0,0 +1,17 @@ +/* This file is dependent on src/data/domain/entity/customization/colours.tsx +* You can match a class name with a colour palette by naming the class 'menuList-' + colour palette name +*/ + +.menuList-default{ + .MuiPaper-root{ + background-color: #fffdfa; + color: #000000; + } +} + +.menuList-dark{ + .MuiPaper-root{ + background-color: #171721; + color: #ffffff; + } +} \ No newline at end of file diff --git a/apps/web/src/components/navbar/logogp.png b/apps/web/src/components/navbar/logogp.png new file mode 100644 index 0000000000000000000000000000000000000000..cebd2d5f6a7e895c4858d7b9b0153a245fd0d7f9 Binary files /dev/null and b/apps/web/src/components/navbar/logogp.png differ diff --git a/apps/web/src/components/navbar/logogpwhite.png b/apps/web/src/components/navbar/logogpwhite.png new file mode 100644 index 0000000000000000000000000000000000000000..c83f02998f92e8b99c7c51416ac62968fe5fb302 Binary files /dev/null and b/apps/web/src/components/navbar/logogpwhite.png differ diff --git a/apps/web/src/components/navbar/navbar.module.scss b/apps/web/src/components/navbar/navbar.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..e1faa9a7863fcca1b4c52135300e3951de0e5b8e --- /dev/null +++ b/apps/web/src/components/navbar/navbar.module.scss @@ -0,0 +1,44 @@ +.root { + display: flex; + overflow: hidden; +} + +.appBar { + display: flex; + flex-direction: column; + justify-content: center; + background-color: #fff; + height: 50px; +} + +.menubox { + display: flex; + flex-direction: row; + justify-content: flex-end; + width: 100%; +} + +.menuText { + margin: 5px; + color: #181520; + font-family: Poppins, sans-serif; + font-weight: 400; + font-size: 15px; + align-self: flex-end; + text-transform: none; +} + +.logo { + margin: auto; + display: block; +} + +.loginButton { + margin: 6px; +} + +.loginButtonText { + color: black; + font-weight: bold; +} + diff --git a/apps/web/src/components/navbar/navbar.tsx b/apps/web/src/components/navbar/navbar.tsx new file mode 100644 index 0000000000000000000000000000000000000000..325670160bbf99d4d3512eba328e00d63d9a94a6 --- /dev/null +++ b/apps/web/src/components/navbar/navbar.tsx @@ -0,0 +1,343 @@ +/** + * This program has been developed by students from the bachelor Computer Science at + * Utrecht University within the Software Project course. + * © Copyright Utrecht University (Department of Information and Computing Sciences) + */ + +/* istanbul ignore file */ +/* The comment above was added so the code coverage wouldn't count this file towards code coverage. + * We do not test components/renderfunctions/styling files. + * See testing plan for more details.*/ +import React from 'react'; +import { ClassNameMap } from '@material-ui/styles'; +import NavbarViewModel from '../../view-model/navbar/NavbarViewModel'; +import { + AppBar, + Toolbar, + CssBaseline, + Typography, + MenuItem, + ListItemText, + Menu, + IconButton, + Button, +} from '@material-ui/core/'; +import { AccountCircle } from '@material-ui/icons'; +import logo from './logogp.png'; +import logo2 from './logogpwhite.png'; +import AddDatabaseForm from './AddDatabaseForm/AddDatabaseFormComponent'; + +/** NavbarComponentProps is an interface containing the NavbarViewModel. */ +export interface NavbarComponentProps { + navbarViewModel: NavbarViewModel; + currentColours: any; + changeColourPalette: () => void; +} + +/** NavbarComponentState is an interface containing the type of visualisations. */ +export interface NavbarComponentState { + styles: ClassNameMap; + + isAuthorized: boolean; + clientID: string; + sessionID: string; + databases: string[]; + currentDatabase: string; + + // The anchor for rendering the user data (clientID, sessionID, add database, etc) menu + userMenuAnchor?: Element; + // The anchor for rendering the menu for selecting a database to use + selectDatabaseMenuAnchor?: Element; + // Determines if the addDatabaseForm will be shown + showAddDatabaseForm: boolean; +} + +/** NavbarComponent is the View implementation for Navbar */ +export default class Navbar + extends React.Component<NavbarComponentProps, NavbarComponentState> + implements BaseView +{ + private navbarViewModel: NavbarViewModel; + + public constructor(props: NavbarComponentProps) { + super(props); + + const { navbarViewModel, currentColours } = props; + this.navbarViewModel = navbarViewModel; + + this.state = { + isAuthorized: false, + styles: navbarViewModel.styles, + clientID: navbarViewModel.clientID, + sessionID: navbarViewModel.sessionID, + databases: [], + currentDatabase: navbarViewModel.currentDatabase, + + userMenuAnchor: undefined, + selectDatabaseMenuAnchor: undefined, + showAddDatabaseForm: false, + }; + } + + public componentDidMount(): void { + this.navbarViewModel.attachView(this); + } + + public componentWillUnmount(): void { + this.navbarViewModel.detachView(); + } + + /** onViewModelChanged updates the NavbarComponent each time the navbarViewModel changes */ + public onViewModelChanged(): void { + this.setState({ + isAuthorized: this.navbarViewModel.isAuthorized, + clientID: this.navbarViewModel.clientID, + sessionID: this.navbarViewModel.sessionID, + databases: this.navbarViewModel.databases, + currentDatabase: this.navbarViewModel.currentDatabase, + }); + } + + /** Closes the user menu. Also closes all nested menu's. */ + private closeUserMenu(): void { + // If a nested window is open, close the main user menu a bit later + if (this.state.selectDatabaseMenuAnchor != undefined) { + this.setState({ selectDatabaseMenuAnchor: undefined }); + setTimeout(() => this.setState({ userMenuAnchor: undefined }), 100); + } else this.setState({ userMenuAnchor: undefined }); + } + + /** + * Render will render the navigation bar + * @return {JSX.Element} The TypeScript code of the navigation bar + */ + public render(): JSX.Element { + const { styles } = this.state; + const currentLogo = + this.props.currentColours.logo == 'white' ? logo2 : logo; + + return ( + <div className={styles.root}> + <CssBaseline /> + <AppBar + title="GraphPolaris" + style={{ + zIndex: 1250, + backgroundColor: '#' + this.props.currentColours.background, + }} + position="fixed" + className={styles.appBar} + > + <Toolbar style={{ marginLeft: -5 }}> + <a href="https://graphpolaris.com/" className={styles.logo}> + <img src={currentLogo} className={styles.logo} /> + </a> + <div className={styles.menubox}> + <Button + className={styles.menuText} + style={{ color: '#' + this.props.currentColours.menuText }} + onClick={this.props.changeColourPalette} + > + Change Palette + </Button> + <Button + href="https://graphpolaris.com/" + className={styles.menuText} + style={{ color: '#' + this.props.currentColours.menuText }} + > + Home + </Button> + <Button + href="https://graphpolaris.com/index.php/products/" + className={styles.menuText} + style={{ color: '#' + this.props.currentColours.menuText }} + > + Products + </Button> + <Button + href="https://graphpolaris.com#usecases" + className={styles.menuText} + style={{ color: '#' + this.props.currentColours.menuText }} + > + Use Cases + </Button> + <Button + href="https://graphpolaris.com#earlyadoption" + className={styles.menuText} + style={{ color: '#' + this.props.currentColours.menuText }} + > + Contact + </Button> + {this.state.isAuthorized ? ( + <div> + <IconButton + color="inherit" + onClick={(event) => + this.setState({ + ...this.state, + userMenuAnchor: event.currentTarget, + }) + } + > + <AccountCircle + htmlColor={'#' + this.props.currentColours.menuText} + /> + </IconButton> + <Menu + id="user-menus" + getContentAnchorEl={null} + anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }} + transformOrigin={{ vertical: 'top', horizontal: 'left' }} + anchorEl={this.state.userMenuAnchor} + keepMounted + open={Boolean(this.state.userMenuAnchor)} + onClose={() => this.closeUserMenu()} + > + <MenuItem> + <ListItemText + primary={'clientID: ' + this.state.clientID} + /> + </MenuItem> + <MenuItem> + <ListItemText + primary={'sessionID: ' + this.state.sessionID} + /> + </MenuItem> + <MenuItem + onClick={() => + this.navbarViewModel.handleLoginButtonClicked('google') + } + > + <ListItemText primary={'request new'} /> + </MenuItem> + {/*<MenuItem onClick={() => LocalStorage.instance().Reset()}>*/} + {/* <ListItemText primary={'Empty localstorage'} />*/} + {/*</MenuItem>*/} + <MenuItem + onClick={() => + this.setState({ + ...this.state, + userMenuAnchor: undefined, + showAddDatabaseForm: true, + }) + } + > + <ListItemText primary={'Add database'} /> + </MenuItem> + <MenuItem + onClick={(event) => + this.setState({ + ...this.state, + selectDatabaseMenuAnchor: event.currentTarget, + }) + } + > + <ListItemText primary={'Change database'} /> + </MenuItem> + <Menu + id="databases-menus" + getContentAnchorEl={null} + anchorOrigin={{ vertical: 'top', horizontal: 'left' }} + transformOrigin={{ vertical: 'top', horizontal: 'right' }} + anchorEl={this.state.selectDatabaseMenuAnchor} + keepMounted + disableAutoFocusItem + open={Boolean(this.state.selectDatabaseMenuAnchor)} + onClose={() => this.closeUserMenu()} + > + {this.state.databases.length > 0 ? ( + this.state.databases.map((database) => ( + <MenuItem + key={database} + selected={database == this.state.currentDatabase} + onClick={() => { + if (this.state.currentDatabase != database) { + this.navbarViewModel.updateCurrentDatabase( + database + ); + this.closeUserMenu(); + } + }} + > + <ListItemText primary={database} /> + </MenuItem> + )) + ) : ( + <MenuItem key="placeholder" value="" disabled> + no databases connected + </MenuItem> + )} + </Menu> + </Menu> + </div> + ) : ( + <div> + <Button + className={styles.loginButton} + onClick={(event) => + this.setState({ + ...this.state, + userMenuAnchor: event.currentTarget, + }) + } + > + <span + className={styles.loginButtonText} + style={{ + color: '#' + this.props.currentColours.menuText, + }} + > + Login + </span> + </Button> + <Menu + id="user-menus" + getContentAnchorEl={null} + anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }} + transformOrigin={{ vertical: 'top', horizontal: 'left' }} + anchorEl={this.state.userMenuAnchor} + keepMounted + open={Boolean(this.state.userMenuAnchor)} + onClose={() => this.closeUserMenu()} + > + <MenuItem + onClick={() => + this.navbarViewModel.handleLoginButtonClicked('google') + } + > + <ListItemText primary={'Login with Google'} /> + </MenuItem> + <MenuItem + onClick={() => + this.navbarViewModel.handleLoginButtonClicked('github') + } + > + <ListItemText primary={'Login with GitHub'} /> + </MenuItem> + <MenuItem + onClick={() => + this.navbarViewModel.handleLoginButtonClicked('free') + } + > + <ListItemText primary={'Login with free (debug)'} /> + </MenuItem> + </Menu> + </div> + )} + </div> + </Toolbar> + </AppBar> + <AddDatabaseForm + open={this.state.showAddDatabaseForm} + onClose={() => + this.setState({ ...this.state, showAddDatabaseForm: false }) + } + onSubmit={(...params) => { + this.navbarViewModel.onAddDatabaseFormSubmit(...params); + this.setState({ ...this.state, showAddDatabaseForm: false }); + }} + /> + </div> + ); + } +} diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index 05136727df33393019f3b7a458b6f4b08148f23c..8a74eed5864975dc493fc05b64a4acc6bb632e89 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -1,14 +1,18 @@ /// <reference types="vitest" /> -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react-swc' -import path from 'path' +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react-swc'; +import path from 'path'; +import basicSsl from '@vitejs/plugin-basic-ssl'; // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [react(), basicSsl()], resolve: { alias: { - '@graphpolaris/shared/lib': path.resolve(__dirname, '../../libs/shared/lib') + '@graphpolaris/shared/lib': path.resolve( + __dirname, + '../../libs/shared/lib' + ), }, - } -}) + }, +}); diff --git a/libs/shared/lib/data-access/api/database.ts b/libs/shared/lib/data-access/api/database.ts index deabbe0f590ed058772363cb224fabd758425a88..e583b7f98b94c453b605250846a7efc44ca9d224 100644 --- a/libs/shared/lib/data-access/api/database.ts +++ b/libs/shared/lib/data-access/api/database.ts @@ -34,22 +34,24 @@ export function AddDatabase(request: AddDatabaseRequest): Promise<void> { export function GetAllDatabases(): Promise<Array<string>> { return new Promise<Array<string>>((resolve, reject) => { + console.log(AuthorizationHandler.instance().AccessToken()); fetch('https://api.graphpolaris.com/user/database', { method: 'GET', - credentials: 'same-origin', + // credentials: 'same-origin', headers: new Headers({ Authorization: 'Bearer ' + AuthorizationHandler.instance().AccessToken(), }), }) .then((response: Response) => { - if (!response.ok) { - reject(response.statusText); - } + // if (!response.ok) { + // reject(response.statusText); + // } return response.json(); }) .then((json: any) => { + console.log(json); resolve(json.databases); }); }); diff --git a/libs/shared/lib/data-access/api/user.ts b/libs/shared/lib/data-access/api/user.ts index 06296ae3a3d7547cb1eaaa4ec9f85fe081ab2bed..825538a8278a9d8d0234c64d01a6e7d074a5c2e4 100644 --- a/libs/shared/lib/data-access/api/user.ts +++ b/libs/shared/lib/data-access/api/user.ts @@ -10,12 +10,12 @@ export type User = { export function GetUserInfo(): Promise<User> { return new Promise<User>((resolve, reject) => { + const auth = AuthorizationHandler.instance(); fetch('https://api.graphpolaris.com/user/', { method: 'GET', credentials: 'same-origin', headers: new Headers({ - Authorization: - 'Bearer ' + AuthorizationHandler.instance().AccessToken(), + Authorization: 'Bearer ' + auth.AccessToken(), }), }) .then((response: Response) => { diff --git a/libs/shared/lib/data-access/authorization/authorizationHandler.ts b/libs/shared/lib/data-access/authorization/authorizationHandler.ts index bc6001fd2aed8d24d60abf15411167af64629e81..4571c3b053f55a273620e386a8dcc3940cb343cd 100644 --- a/libs/shared/lib/data-access/authorization/authorizationHandler.ts +++ b/libs/shared/lib/data-access/authorization/authorizationHandler.ts @@ -36,6 +36,7 @@ export class AuthorizationHandler { // Store them this.userID = authResponse.userID ?? ''; this.sessionID = authResponse.sessionID ?? ''; + this.SetAccessToken(authResponse.accessToken ?? ''); } @@ -93,8 +94,10 @@ export class AuthorizationHandler { // Set the new access token this.accessToken = authResponse.accessToken ?? ''; - // Initialise the new refresh token - this.initialiseRefreshToken(); + if (this.accessToken !== '') { + // Initialise the new refresh token + this.initialiseRefreshToken(); + } } } @@ -102,10 +105,14 @@ export class AuthorizationHandler { * initialiseRefreshToken attempts to initialise a refresh token */ private async initialiseRefreshToken() { - fetch('https://api.graphpolaris.com/auth/refresh', { - method: 'POST', - credentials: 'include', - }) + fetch( + 'https://api.graphpolaris.com/auth/refresh?access_token=' + + this.accessToken, + { + method: 'GET', + credentials: 'include', + } + ) .then((response) => { if (!response.ok) { throw Error(response.statusText); @@ -158,8 +165,6 @@ export class AuthorizationHandler { async SetAccessToken(accessToken: string) { this.accessToken = accessToken; - console.log(this.accessToken); - // Activate the refresh token this.initialiseRefreshToken(); diff --git a/libs/shared/lib/data-access/authorization/authorizationHook.tsx b/libs/shared/lib/data-access/authorization/authorizationHook.tsx new file mode 100644 index 0000000000000000000000000000000000000000..81d0d4fae7a880f127edaafcad32243725fb9795 --- /dev/null +++ b/libs/shared/lib/data-access/authorization/authorizationHook.tsx @@ -0,0 +1,28 @@ +import { useEffect, useState } from 'react'; +import { AuthorizationHandler } from '@graphpolaris/shared/lib/data-access'; + +export function useIsAuthorized() { + const [userAuthorized, setUserAuthorized] = useState(false); + + const authCallback = async () => { + setUserAuthorized(true); + + // Print the user that is currently logged in + // const user = await GetUserInfo(); + // console.log(user); + }; + + AuthorizationHandler.instance().setCallback(authCallback); + + // Attempt to Authorize the user + const authorize = async () => { + const authorized = await AuthorizationHandler.instance().Authorize(); + setUserAuthorized(authorized); + }; + + useEffect(() => { + authorize(); + }, []); + + return userAuthorized; +} diff --git a/libs/shared/lib/data-access/authorization/index.ts b/libs/shared/lib/data-access/authorization/index.ts index 40d36480605c0faae067cdd01ace9cba3252d171..0cf2dd57d11433d17c7556f071f558b458c14c2d 100644 --- a/libs/shared/lib/data-access/authorization/index.ts +++ b/libs/shared/lib/data-access/authorization/index.ts @@ -1 +1,2 @@ export { AuthorizationHandler } from './authorizationHandler'; +export * from './authorizationHook'; diff --git a/libs/shared/lib/data-access/store/schemaSlice.ts b/libs/shared/lib/data-access/store/schemaSlice.ts index 4634eb33c99a27ba59f310a5d88bfba57321174a..bd3d0b4b70a92774e5fd947a83228e8f9b88d7f3 100644 --- a/libs/shared/lib/data-access/store/schemaSlice.ts +++ b/libs/shared/lib/data-access/store/schemaSlice.ts @@ -82,7 +82,7 @@ export const { readInSchemaFromBackend, setSchema } = schemaSlice.actions; export const schemaGraphology = (state: RootState) => { // This is really weird but for some reason all the attributes appeared as read-only otherwise let ret = new MultiGraph(); - ret.import(MultiGraph.from(state.querybuilder.graphologySerialized).export()); + ret.import(MultiGraph.from(state.schema.graphologySerialized).export()); return ret; }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 651a2726f31790745e63edd0f8268351d01308af..c0bd7f0cc8610cba4f060887d3e5d7c273fc4dfc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -83,6 +83,9 @@ importers: '@types/styled-components': specifier: ^5.1.26 version: 5.1.26 + '@vitejs/plugin-basic-ssl': + specifier: ^1.0.1 + version: 1.0.1(vite@4.2.1) '@vitejs/plugin-react-swc': specifier: ^3.0.0 version: 3.2.0(vite@4.2.1) @@ -683,7 +686,7 @@ packages: resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 /@babel/helper-module-transforms@7.21.2: resolution: {integrity: sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==} @@ -1777,20 +1780,12 @@ packages: '@babel/helper-hoist-variables': 7.18.6 '@babel/helper-split-export-declaration': 7.18.6 '@babel/parser': 7.21.3 - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 debug: 4.3.4(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: - supports-color - /@babel/types@7.21.3: - resolution: {integrity: sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-string-parser': 7.19.4 - '@babel/helper-validator-identifier': 7.19.1 - to-fast-properties: 2.0.0 - /@babel/types@7.21.4: resolution: {integrity: sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==} engines: {node: '>=6.9.0'} @@ -4174,6 +4169,17 @@ packages: telejson: 7.0.4 dev: true + /@storybook/channel-postmessage@7.0.6: + resolution: {integrity: sha512-xBsh/+85GS4bJ08r7z1iRn26EI6hGmMgNpjpFztRigMhsq5SkD9FJb+Nh9bbaHm+yPOCqJcaHQ2aQpuJNT8dHA==} + dependencies: + '@storybook/channels': 7.0.6 + '@storybook/client-logger': 7.0.6 + '@storybook/core-events': 7.0.6 + '@storybook/global': 5.0.0 + qs: 6.11.1 + telejson: 7.0.4 + dev: true + /@storybook/channel-websocket@7.0.5: resolution: {integrity: sha512-QgvxAZjEdRzPZveUibErJbaqqe97DLscPeK5YHA1/xDCPqMKo0HaQKTyT0YSsSkeE3oKXbdz9IXFXEaPmIpjzw==} dependencies: @@ -4199,6 +4205,10 @@ packages: resolution: {integrity: sha512-WiSPXgOK63jAlDDmbTs1sVXoYe3r/4VjpfwhEcxSPU544YQVARF1ePtiGjlp8HVFhZh1Q7afbVGJ9w96++u98A==} dev: true + /@storybook/channels@7.0.6: + resolution: {integrity: sha512-+34cVmrXZ3lb1s5tDK+OWd5HLtEPSUMas0VKFJ0k9LBpFlVl9aiCZBJRvSYmWL7beauUfa+HSmJgjlD6228ChQ==} + dev: true + /@storybook/cli@7.0.5: resolution: {integrity: sha512-VRrf4XG9H29FycNqthT6r4MjT0f4ynpwQAj039vUrt95rosV8ytuLFIrTwww1x/2o/VNpkWyL7MJwu6dejeZgw==} hasBin: true @@ -4267,6 +4277,12 @@ packages: '@storybook/global': 5.0.0 dev: true + /@storybook/client-logger@7.0.6: + resolution: {integrity: sha512-TC/E5BBkY+WNldNw5p5Ffr9x4UgMe48GmC50ikBpQFk6og1B7XpFGMMbj40EBB0R5cpZkQNEVQh4OvunEygNzg==} + dependencies: + '@storybook/global': 5.0.0 + dev: true + /@storybook/codemod@7.0.5: resolution: {integrity: sha512-Hu9CiVBHhaPJHMVpiAjr7pEtL7/AUsKT/Xxn3xUM7Ngy7TYMa62XTIMkt2Z+tAAud0HzAz/6Wv+2q+IqPr7BeQ==} dependencies: @@ -4422,6 +4438,10 @@ packages: resolution: {integrity: sha512-bYQFZlJR3n5gFk5GVIemuL3m6aYPF6DVnzj6n9UcMZDlHcOZ2B2WbTmAUrGy0bmtj/Fd6ZJKDpBhh3cRRsYkbA==} dev: true + /@storybook/core-events@7.0.6: + resolution: {integrity: sha512-kGrtjlYtjd4iTVk+Phb4CymZaVkB+MGscKAgcO8gfgJ/Q/gq8HQLVZSIzeoCDcDSHOGlBzbg2WVtdHIHhCKlOQ==} + dev: true + /@storybook/core-server@7.0.5: resolution: {integrity: sha512-h3SVzwepHTyDxS7ZPuYfHStnWC0EC05axSPKb3yeO6bCsowf+CEXgY5VayUqP8GkgLBez859m172y6B+wVXZ3g==} dependencies: @@ -4588,14 +4608,14 @@ packages: '@storybook/preview-api': 7.0.0-rc.5 dev: true - /@storybook/instrumenter@7.0.5: - resolution: {integrity: sha512-A+uPQjA8JqR23efQbMHKnmeoltAJGYEV+855X3X27aie2B4mUo3KHELyeioaqTVuh1KZ/K0dTvjpfbGSQGscvg==} + /@storybook/instrumenter@7.0.6: + resolution: {integrity: sha512-JUcDas1cYCE+ZMVOw5CKc5g6PxDe3HH+IGdh/W9wL5vmdOUvAs858m7NLxkjkQGufof+Ohbmf/Yz5gyXaZ5+Yg==} dependencies: - '@storybook/channels': 7.0.5 - '@storybook/client-logger': 7.0.5 - '@storybook/core-events': 7.0.5 + '@storybook/channels': 7.0.6 + '@storybook/client-logger': 7.0.6 + '@storybook/core-events': 7.0.6 '@storybook/global': 5.0.0 - '@storybook/preview-api': 7.0.5 + '@storybook/preview-api': 7.0.6 dev: true /@storybook/manager-api@7.0.0-rc.5(react-dom@18.2.0)(react@18.2.0): @@ -4735,6 +4755,26 @@ packages: util-deprecate: 1.0.2 dev: true + /@storybook/preview-api@7.0.6: + resolution: {integrity: sha512-uNsedNyiEccBV2EDUC/xcKTbmiNCYuVHbgOoWTmBz0ZqFo9bX0jxkpyYWHEhJM79qqVqmrpiQ5jbS8QKn8TIxQ==} + dependencies: + '@storybook/channel-postmessage': 7.0.6 + '@storybook/channels': 7.0.6 + '@storybook/client-logger': 7.0.6 + '@storybook/core-events': 7.0.6 + '@storybook/csf': 0.1.0 + '@storybook/global': 5.0.0 + '@storybook/types': 7.0.6 + '@types/qs': 6.9.7 + dequal: 2.0.3 + lodash: 4.17.21 + memoizerific: 1.11.3 + qs: 6.11.1 + synchronous-promise: 2.0.17 + ts-dedent: 2.2.0 + util-deprecate: 1.0.2 + dev: true + /@storybook/preview@7.0.5: resolution: {integrity: sha512-N1IDKzmqnF+XAdACGnaWw22dmSUQHuHKyyQ/vV9upMf0hA+4gk9pc5RFEHOQO/sTbxblgfKm9Q1fIYkxgPVFxg==} dev: true @@ -4933,8 +4973,8 @@ packages: /@storybook/testing-library@0.0.14-next.1: resolution: {integrity: sha512-1CAl40IKIhcPaCC4pYCG0b9IiYNymktfV/jTrX7ctquRY3akaN7f4A1SippVHosksft0M+rQTFE0ccfWW581fw==} dependencies: - '@storybook/client-logger': 7.0.5 - '@storybook/instrumenter': 7.0.5 + '@storybook/client-logger': 7.0.6 + '@storybook/instrumenter': 7.0.6 '@testing-library/dom': 8.20.0 '@testing-library/user-event': 13.5.0(@testing-library/dom@8.20.0) ts-dedent: 2.2.0 @@ -5000,6 +5040,15 @@ packages: file-system-cache: 2.0.2 dev: true + /@storybook/types@7.0.6: + resolution: {integrity: sha512-dFASQxzvldU2Nx/eJG+oL4wCchUWAKOmOSYJYhKgtGpx99oXOiWUyC0SgCpTveBJ7AppoiseyasQ9Gd/Ccycdw==} + dependencies: + '@storybook/channels': 7.0.6 + '@types/babel__core': 7.20.0 + '@types/express': 4.17.17 + file-system-cache: 2.0.2 + dev: true + /@swc/core-darwin-arm64@1.3.42: resolution: {integrity: sha512-hM6RrZFyoCM9mX3cj/zM5oXwhAqjUdOCLXJx7KTQps7NIkv/Qjvobgvyf2gAb89j3ARNo9NdIoLjTjJ6oALtiA==} engines: {node: '>=10'} @@ -5851,6 +5900,15 @@ packages: '@typescript-eslint/types': 5.52.0 eslint-visitor-keys: 3.3.0 + /@vitejs/plugin-basic-ssl@1.0.1(vite@4.2.1): + resolution: {integrity: sha512-pcub+YbFtFhaGRTo1832FQHQSHvMrlb43974e2eS8EKleR3p1cDdkJFPci1UhwkEf1J9Bz+wKBSzqpKp7nNj2A==} + engines: {node: '>=14.6.0'} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 + dependencies: + vite: 4.2.1(@types/node@18.13.0)(sass@1.59.3) + dev: true + /@vitejs/plugin-react-swc@3.2.0(vite@4.2.1): resolution: {integrity: sha512-IcBoXL/mcH7JdQr/nfDlDwTdIaH8Rg7LpfQDF4nAht+juHWIuv6WhpKPCSfY4+zztAaB07qdBoFz1XCZsgo3pQ==} peerDependencies: