diff --git a/angular.json b/angular.json index 808e19ed..a57388f2 100644 --- a/angular.json +++ b/angular.json @@ -7,39 +7,60 @@ "projectType": "application", "architect": { "build": { - "builder": "@nrwl/node:build", + "builder": "@nrwl/web:build", "outputs": ["{options.outputPath}"], + "defaultConfiguration": "production", "options": { "outputPath": "dist/apps/admin-panel", + "index": "apps/admin-panel/src/index.html", + "baseHref": "/", "main": "apps/admin-panel/src/main.js", + "polyfills": "apps/admin-panel/src/polyfills.js", "tsConfig": "apps/admin-panel/tsconfig.app.json", - "assets": ["apps/admin-panel/src/assets"] + "assets": [ + "apps/admin-panel/src/favicon.ico", + "apps/admin-panel/src/assets" + ], + "styles": ["apps/admin-panel/src/styles.css"], + "scripts": [], + "webpackConfig": "@nrwl/react/plugins/webpack" }, "configurations": { "production": { - "optimization": true, - "extractLicenses": true, - "inspect": false, "fileReplacements": [ { "replace": "apps/admin-panel/src/environments/environment.js", "with": "apps/admin-panel/src/environments/environment.prod.js" } - ] + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "namedChunks": false, + "extractLicenses": true, + "vendorChunk": false } } }, "serve": { - "builder": "@nrwl/node:execute", + "builder": "@nrwl/web:dev-server", "options": { - "buildTarget": "admin-panel:build" + "buildTarget": "admin-panel:build", + "hmr": true, + "port": 4300 + }, + "configurations": { + "production": { + "buildTarget": "admin-panel:build:production", + "hmr": false + } } }, "lint": { "builder": "@nrwl/linter:eslint", "outputs": ["{options.outputFile}"], "options": { - "lintFilePatterns": ["apps/admin-panel/**/*.js"] + "lintFilePatterns": ["apps/admin-panel/**/*.{ts,tsx,js,jsx}"] } }, "test": { @@ -53,6 +74,34 @@ }, "tags": [] }, + "admin-panel-e2e": { + "root": "apps/admin-panel-e2e", + "sourceRoot": "apps/admin-panel-e2e/src", + "projectType": "application", + "architect": { + "e2e": { + "builder": "@nrwl/cypress:cypress", + "options": { + "cypressConfig": "apps/admin-panel-e2e/cypress.json", + "devServerTarget": "admin-panel:serve" + }, + "configurations": { + "production": { + "devServerTarget": "admin-panel:serve:production" + } + } + }, + "lint": { + "builder": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["apps/admin-panel-e2e/**/*.js"] + } + } + }, + "tags": [], + "implicitDependencies": ["admin-panel"] + }, "e-learning-app": { "root": "apps/e-learning-app", "sourceRoot": "apps/e-learning-app/src", diff --git a/apps/admin-panel-e2e/.eslintrc.json b/apps/admin-panel-e2e/.eslintrc.json new file mode 100644 index 00000000..696cb8b1 --- /dev/null +++ b/apps/admin-panel-e2e/.eslintrc.json @@ -0,0 +1,10 @@ +{ + "extends": ["plugin:cypress/recommended", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/apps/admin-panel-e2e/cypress.json b/apps/admin-panel-e2e/cypress.json new file mode 100644 index 00000000..e760d6fb --- /dev/null +++ b/apps/admin-panel-e2e/cypress.json @@ -0,0 +1,12 @@ +{ + "fileServerFolder": ".", + "fixturesFolder": "./src/fixtures", + "integrationFolder": "./src/integration", + "modifyObstructiveCode": false, + "supportFile": "./src/support/index.js", + "pluginsFile": false, + "video": true, + "videosFolder": "../../dist/cypress/apps/admin-panel-e2e/videos", + "screenshotsFolder": "../../dist/cypress/apps/admin-panel-e2e/screenshots", + "chromeWebSecurity": false +} diff --git a/apps/admin-panel-e2e/src/fixtures/example.json b/apps/admin-panel-e2e/src/fixtures/example.json new file mode 100644 index 00000000..294cbed6 --- /dev/null +++ b/apps/admin-panel-e2e/src/fixtures/example.json @@ -0,0 +1,4 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io" +} diff --git a/apps/admin-panel-e2e/src/integration/app.spec.js b/apps/admin-panel-e2e/src/integration/app.spec.js new file mode 100644 index 00000000..b6d4d7d5 --- /dev/null +++ b/apps/admin-panel-e2e/src/integration/app.spec.js @@ -0,0 +1,10 @@ +import { getGreeting } from '../support/app.po'; +describe('admin-panel', () => { + beforeEach(() => cy.visit('/')); + it('should display welcome message', () => { + // Custom command example, see `../support/commands.ts` file + cy.login('my-email@something.com', 'myPassword'); + // Function helper example, see `../support/app.po.ts` file + getGreeting().contains('Welcome to admin-panel!'); + }); +}); diff --git a/apps/admin-panel-e2e/src/support/app.po.js b/apps/admin-panel-e2e/src/support/app.po.js new file mode 100644 index 00000000..32934246 --- /dev/null +++ b/apps/admin-panel-e2e/src/support/app.po.js @@ -0,0 +1 @@ +export const getGreeting = () => cy.get('h1'); diff --git a/apps/admin-panel-e2e/src/support/commands.js b/apps/admin-panel-e2e/src/support/commands.js new file mode 100644 index 00000000..43864090 --- /dev/null +++ b/apps/admin-panel-e2e/src/support/commands.js @@ -0,0 +1,25 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// -- This is a parent command -- +Cypress.Commands.add('login', (email, password) => { + console.log('Custom command example: Login', email, password); +}); +// +// -- This is a child command -- +// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) diff --git a/apps/admin-panel-e2e/src/support/index.js b/apps/admin-panel-e2e/src/support/index.js new file mode 100644 index 00000000..fad13015 --- /dev/null +++ b/apps/admin-panel-e2e/src/support/index.js @@ -0,0 +1,16 @@ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** +// Import commands.js using ES2015 syntax: +import './commands'; diff --git a/apps/admin-panel-e2e/tsconfig.json b/apps/admin-panel-e2e/tsconfig.json new file mode 100644 index 00000000..c4f818ec --- /dev/null +++ b/apps/admin-panel-e2e/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "sourceMap": false, + "outDir": "../../dist/out-tsc", + "allowJs": true, + "types": ["cypress", "node"] + }, + "include": ["src/**/*.ts", "src/**/*.js"] +} diff --git a/apps/admin-panel/.babelrc b/apps/admin-panel/.babelrc new file mode 100644 index 00000000..61641ec8 --- /dev/null +++ b/apps/admin-panel/.babelrc @@ -0,0 +1,11 @@ +{ + "presets": [ + [ + "@nrwl/react/babel", + { + "runtime": "automatic" + } + ] + ], + "plugins": [] +} diff --git a/apps/admin-panel/.browserslistrc b/apps/admin-panel/.browserslistrc new file mode 100644 index 00000000..f1d12df4 --- /dev/null +++ b/apps/admin-panel/.browserslistrc @@ -0,0 +1,16 @@ +# This file is used by: +# 1. autoprefixer to adjust CSS to support the below specified browsers +# 2. babel preset-env to adjust included polyfills +# +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries +# +# If you need to support different browsers in production, you may tweak the list below. + +last 1 Chrome version +last 1 Firefox version +last 2 Edge major versions +last 2 Safari major version +last 2 iOS major versions +Firefox ESR +not IE 9-11 # For IE 9-11 support, remove 'not'. \ No newline at end of file diff --git a/apps/admin-panel/.env-template b/apps/admin-panel/.env-template index 93ef95c2..eccb41f1 100644 --- a/apps/admin-panel/.env-template +++ b/apps/admin-panel/.env-template @@ -1,3 +1,3 @@ # The general .env file should contain +NX_ENVIRONMENT= NX_API_URL= -NX_SERVER_PORT= diff --git a/apps/admin-panel/.eslintrc.json b/apps/admin-panel/.eslintrc.json index 9d9c0db5..734ddace 100644 --- a/apps/admin-panel/.eslintrc.json +++ b/apps/admin-panel/.eslintrc.json @@ -1,5 +1,5 @@ { - "extends": ["../../.eslintrc.json"], + "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], "ignorePatterns": ["!**/*"], "overrides": [ { diff --git a/apps/admin-panel/jest.config.js b/apps/admin-panel/jest.config.js index 300ed795..3e7eb600 100644 --- a/apps/admin-panel/jest.config.js +++ b/apps/admin-panel/jest.config.js @@ -1,14 +1,9 @@ module.exports = { displayName: 'admin-panel', preset: '../../jest.preset.js', - globals: { - 'ts-jest': { - tsconfig: '/tsconfig.spec.json', - }, - }, - testEnvironment: 'node', transform: { - '^.+\\.[tj]sx?$': 'ts-jest', + '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest', + '^.+\\.[tj]sx?$': 'babel-jest', }, moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], coverageDirectory: '../../coverage/apps/admin-panel', diff --git a/apps/admin-panel/src/app/.gitkeep b/apps/admin-panel/src/app/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/admin-panel/src/app/app.js b/apps/admin-panel/src/app/app.js new file mode 100644 index 00000000..4a498db7 --- /dev/null +++ b/apps/admin-panel/src/app/app.js @@ -0,0 +1,51 @@ +import { BrowserRouter as Router, Switch } from 'react-router-dom'; +import LoginRoute from './components/loginRoute/LoginRoute'; +import PrivateRoute from './components/privateRoute/PrivateRoute'; +import JwtVerification from './services/jwtVerification.service'; +import HomePage from './pages/home/HomePage'; +import LoginPage from './pages/login/LoginPage'; +import GradesPage from './pages/resources/grades/GradesPage'; +import SubjectsPage from './pages/resources/subjects/SubjectsPage'; +import LecturesPage from './pages/resources/lectures/LecturesPage'; +import LectureMaterialsPage from './pages/resources/lecture-materials/LectureMaterialsPage'; +import VideosPage from './pages/resources/videos/VideosPage'; +import RolesPage from './pages/resources/roles/RolesPage'; +import UsersPage from './pages/resources/users/UsersPage'; + +export function App() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} +export default App; diff --git a/apps/admin-panel/src/app/app.module.css b/apps/admin-panel/src/app/app.module.css new file mode 100644 index 00000000..b4f3ac0b --- /dev/null +++ b/apps/admin-panel/src/app/app.module.css @@ -0,0 +1,3 @@ +body { + background-color: #b4c6a6; +} diff --git a/apps/admin-panel/src/app/app.spec.js b/apps/admin-panel/src/app/app.spec.js new file mode 100644 index 00000000..ffb27df6 --- /dev/null +++ b/apps/admin-panel/src/app/app.spec.js @@ -0,0 +1,13 @@ +import { render } from '@testing-library/react'; +import { BrowserRouter } from 'react-router-dom'; +import App from './app'; +describe('App', () => { + it('should render successfully', () => { + const { baseElement } = render( + + + + ); + expect(baseElement).toBeTruthy(); + }); +}); diff --git a/apps/admin-panel/src/app/components/customCard/CustomCard.js b/apps/admin-panel/src/app/components/customCard/CustomCard.js new file mode 100644 index 00000000..bfc67a71 --- /dev/null +++ b/apps/admin-panel/src/app/components/customCard/CustomCard.js @@ -0,0 +1,29 @@ +import { Card } from 'react-bootstrap'; + +/** + * Renders a Card with a custom Title, Description, and link button. + * @param {*} props + * > + * - `String` title + * - `String` description + * - `String` buttonText + * - `String` buttonLink - default: `#` + * - `Object` style - custom styling options for the card + */ +export default function CustomCard(props) { + const title = props.title; + const description = props.description; + const buttonText = props.buttonText; + const buttonLink = props.buttonLink || '#'; + const customStyle = props.style; + + return ( + + + {title} + {description} + {buttonText} + + + ); +} diff --git a/apps/admin-panel/src/app/components/customTitle/CustomTitle.js b/apps/admin-panel/src/app/components/customTitle/CustomTitle.js new file mode 100644 index 00000000..8419f836 --- /dev/null +++ b/apps/admin-panel/src/app/components/customTitle/CustomTitle.js @@ -0,0 +1,21 @@ +import { Container, Row } from 'react-bootstrap'; +import style from './CustomTitleStyle'; + +/** + * Renders a page title with an optional divider underneath. + * @param {*} props + * > + * - `String` title + * - `boolean` hasDivider - toggles the divider on or off. + */ +export default function CustomTitle(props) { + const title = props.value; + const hasDivider = props.hasDivider || true; + + return ( + +

{title}

+ {hasDivider &&
} +
+ ); +} diff --git a/apps/admin-panel/src/app/components/customTitle/CustomTitleStyle.js b/apps/admin-panel/src/app/components/customTitle/CustomTitleStyle.js new file mode 100644 index 00000000..e00d2705 --- /dev/null +++ b/apps/admin-panel/src/app/components/customTitle/CustomTitleStyle.js @@ -0,0 +1,18 @@ +const customTitleStyle = { + containerStyle: { + marginTop: '20px', + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + }, + + dividerStyle: { + borderTop: '3px solid #bbb', + borderRadius: '5px', + backgroundColor: 'grey', + width: '90%', + }, +}; + +export default customTitleStyle; diff --git a/apps/admin-panel/src/app/components/loginRoute/LoginRoute.js b/apps/admin-panel/src/app/components/loginRoute/LoginRoute.js new file mode 100644 index 00000000..f70f5001 --- /dev/null +++ b/apps/admin-panel/src/app/components/loginRoute/LoginRoute.js @@ -0,0 +1,26 @@ +import { Route, Redirect } from 'react-router-dom'; +import AuthService from '../../services/auth.service'; + +// A wrapper for that redirects to the login +// screen if you're not yet authenticated. +export default function LoginRoute({ children, ...rest }) { + const isAuthenticated = AuthService.isAuthenticated(); + + return ( + + !isAuthenticated ? ( + children + ) : ( + + ) + } + /> + ); +} diff --git a/apps/admin-panel/src/app/components/privateRoute/PrivateRoute.js b/apps/admin-panel/src/app/components/privateRoute/PrivateRoute.js new file mode 100644 index 00000000..cde79fdf --- /dev/null +++ b/apps/admin-panel/src/app/components/privateRoute/PrivateRoute.js @@ -0,0 +1,26 @@ +import { Route, Redirect } from 'react-router-dom'; +import AuthService from '../../services/auth.service'; + +// A wrapper for that redirects to the login +// screen if you're not yet authenticated. +export default function PrivateRoute({ children, ...rest }) { + const isAuthenticated = AuthService.isAuthenticated(); + + return ( + + isAuthenticated ? ( + children + ) : ( + + ) + } + /> + ); +} diff --git a/apps/admin-panel/src/app/pages/home/HomePage.js b/apps/admin-panel/src/app/pages/home/HomePage.js new file mode 100644 index 00000000..bdaf18ad --- /dev/null +++ b/apps/admin-panel/src/app/pages/home/HomePage.js @@ -0,0 +1,92 @@ +import { useHistory } from 'react-router-dom'; +import { Button, Container, Row, Col } from 'react-bootstrap'; +import AuthService from '../../services/auth.service'; +import CustomCard from '../../components/customCard/CustomCard'; + +export default function HomePage(props) { + const history = useHistory(); + + const logoutHandler = () => { + AuthService.logout(); + history.push('/login'); + }; + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/apps/admin-panel/src/app/pages/login/LoginPage.js b/apps/admin-panel/src/app/pages/login/LoginPage.js new file mode 100644 index 00000000..3cd2f15d --- /dev/null +++ b/apps/admin-panel/src/app/pages/login/LoginPage.js @@ -0,0 +1,106 @@ +import { useState } from 'react'; +import { useHistory } from 'react-router-dom'; +import { Container, Form, Button, Spinner } from 'react-bootstrap'; + +import AuthService from '../../services/auth.service'; + +import style from './LoginPageStyle'; + +export default function LoginPage(props) { + const [isLoading, setIsLoading] = useState(false); + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [validated, setValidated] = useState(false); + + const history = useHistory(); + + const onChangeUsername = (e) => { + const formUsername = e.target.value; + setUsername(formUsername); + }; + + const onChangePassword = (e) => { + const formPassword = e.target.value; + setPassword(formPassword); + }; + + const loginHandler = (e) => { + const form = e.currentTarget; + setIsLoading(true); + //TODO: properly validate inputs + if (form.checkValidity() === false) { + e.preventDefault(); + e.stopPropagation(); + } + + AuthService.login(username, password).then( + () => { + history.push('/home'); + }, + (error) => { + setIsLoading(false); + } + ); + setValidated(true); + }; + + return ( + +
+ + Email address: + + + + Password: + + + + + + +
+
+ ); +} diff --git a/apps/admin-panel/src/app/pages/login/LoginPageStyle.js b/apps/admin-panel/src/app/pages/login/LoginPageStyle.js new file mode 100644 index 00000000..15eeb5bf --- /dev/null +++ b/apps/admin-panel/src/app/pages/login/LoginPageStyle.js @@ -0,0 +1,38 @@ +const loginPageStyle = { + containerStyle: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + width: '100vw', + height: '100vh', + }, + formStyle: { + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + width: '45vmin', + minWidth: '250px', + height: '45vmin', + minHeight: '250px', + padding: '25px', + border: '2px solid darkgray', + borderRadius: '10px', + boxShadow: '0px 0px 35px rgba(0, 0, 0, 0.15)', + }, + inputStyle: { + width: '100%', + }, + buttonContainerStyle: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + width: '100%', + height: '100%', + }, + buttonStyle: { + width: '60%', + minWidth: '125px', + }, +}; + +export default loginPageStyle; diff --git a/apps/admin-panel/src/app/pages/resources/grades/GradesPage.js b/apps/admin-panel/src/app/pages/resources/grades/GradesPage.js new file mode 100644 index 00000000..159551c6 --- /dev/null +++ b/apps/admin-panel/src/app/pages/resources/grades/GradesPage.js @@ -0,0 +1,74 @@ +import { useEffect, useState } from 'react'; +import { Table, Container, Col, Row, Spinner, Button } from 'react-bootstrap'; +import { useHistory } from 'react-router-dom'; +import CustomTitle from '../../../components/customTitle/CustomTitle.js'; +import httpService from '../../../services/http.service.js'; + +export default function GradesPage() { + // State + const [grades, setGrades] = useState(undefined); + const [isLoading, setIsLoading] = useState(false); + + const history = useHistory(); + + /** + * Retrieves the grades from the database. + */ + useEffect(() => { + setIsLoading(true); + httpService.getAll('/grades', (response) => { + setGrades(response.data); + setIsLoading(false); + }); + }, []); + + const historyBack = () => history.goBack(); + + /** + * Builds a table row for each of the grades retrieved from the database. + */ + const buildGrades = () => { + if (grades) { + return grades.map((grade) => ( + + {grade.id} + {grade.value} + + )); + } + return []; + }; + + return ( + + + + + + + + + + + + + + + + + + + + + {isLoading ? : buildGrades()} + +
IDValue
+
+
+ ); +} diff --git a/apps/admin-panel/src/app/pages/resources/lecture-materials/LectureMaterialsPage.js b/apps/admin-panel/src/app/pages/resources/lecture-materials/LectureMaterialsPage.js new file mode 100644 index 00000000..1b174367 --- /dev/null +++ b/apps/admin-panel/src/app/pages/resources/lecture-materials/LectureMaterialsPage.js @@ -0,0 +1,74 @@ +import { useEffect, useState } from 'react'; +import { Table, Container, Col, Row, Spinner, Button } from 'react-bootstrap'; +import { useHistory } from 'react-router-dom'; +import CustomTitle from '../../../components/customTitle/CustomTitle.js'; +import httpService from '../../../services/http.service.js'; + +export default function LectureMaterialsPage() { + // State + const [lectureMaterials, setLectureMaterials] = useState(undefined); + const [isLoading, setIsLoading] = useState(false); + + const history = useHistory(); + + /** + * Retrieves the lecture materials from the database. + */ + useEffect(() => { + setIsLoading(true); + httpService.getAll('/lecture-materials', (response) => { + setLectureMaterials(response.data); + setIsLoading(false); + }); + }, []); + + const historyBack = () => history.goBack(); + + /** + * Builds a table row for each of the lecture materials retrieved from the database. + */ + const buildLectureMaterials = () => { + if (lectureMaterials) { + return lectureMaterials.map((lectureMaterial) => ( + + {lectureMaterial.id} + {lectureMaterial.document} + + )); + } + return []; + }; + + return ( + + + + + + + + + + + + + + + + + + + + + {isLoading ? : buildLectureMaterials()} + +
IDDocument
+
+
+ ); +} diff --git a/apps/admin-panel/src/app/pages/resources/lectures/LecturesPage.js b/apps/admin-panel/src/app/pages/resources/lectures/LecturesPage.js new file mode 100644 index 00000000..b65880a2 --- /dev/null +++ b/apps/admin-panel/src/app/pages/resources/lectures/LecturesPage.js @@ -0,0 +1,74 @@ +import { useEffect, useState } from 'react'; +import { Table, Container, Col, Row, Spinner, Button } from 'react-bootstrap'; +import { useHistory } from 'react-router-dom'; +import CustomTitle from '../../../components/customTitle/CustomTitle.js'; +import httpService from '../../../services/http.service.js'; + +export default function LecturesPage() { + // State + const [lectures, setLectures] = useState(undefined); + const [isLoading, setIsLoading] = useState(false); + + const history = useHistory(); + + /** + * Retrieves the lectures from the database. + */ + useEffect(() => { + setIsLoading(true); + httpService.getAll('/lectures', (response) => { + setLectures(response.data); + setIsLoading(false); + }); + }, []); + + const historyBack = () => history.goBack(); + + /** + * Builds a table row for each of the lectures retrieved from the database. + */ + const buildLectures = () => { + if (lectures) { + return lectures.map((lecture) => ( + + {lecture.id} + {lecture.title} + + )); + } + return []; + }; + + return ( + + + + + + + + + + + + + + + + + + + + + {isLoading ? : buildLectures()} + +
IDTitle
+
+
+ ); +} diff --git a/apps/admin-panel/src/app/pages/resources/roles/RolesPage.js b/apps/admin-panel/src/app/pages/resources/roles/RolesPage.js new file mode 100644 index 00000000..ae772783 --- /dev/null +++ b/apps/admin-panel/src/app/pages/resources/roles/RolesPage.js @@ -0,0 +1,74 @@ +import { useEffect, useState } from 'react'; +import { Table, Container, Col, Row, Spinner, Button } from 'react-bootstrap'; +import { useHistory } from 'react-router-dom'; +import CustomTitle from '../../../components/customTitle/CustomTitle.js'; +import httpService from '../../../services/http.service.js'; + +export default function RolesPage() { + // State + const [roles, setRoles] = useState(undefined); + const [isLoading, setIsLoading] = useState(false); + + const history = useHistory(); + + /** + * Retrieves the roles from the database. + */ + useEffect(() => { + setIsLoading(true); + httpService.getAll('/roles', (response) => { + setRoles(response.data); + setIsLoading(false); + }); + }, []); + + const historyBack = () => history.goBack(); + + /** + * Builds a table row for each of the roles retrieved from the database. + */ + const buildRoles = () => { + if (roles) { + return roles.map((role) => ( + + {role.id} + {role.name} + + )); + } + return []; + }; + + return ( + + + + + + + + + + + + + + + + + + + + + {isLoading ? : buildRoles()} + +
IDName
+
+
+ ); +} diff --git a/apps/admin-panel/src/app/pages/resources/subjects/SubjectsPage.js b/apps/admin-panel/src/app/pages/resources/subjects/SubjectsPage.js new file mode 100644 index 00000000..56acc78f --- /dev/null +++ b/apps/admin-panel/src/app/pages/resources/subjects/SubjectsPage.js @@ -0,0 +1,74 @@ +import { useEffect, useState } from 'react'; +import { Table, Container, Col, Row, Spinner, Button } from 'react-bootstrap'; +import { useHistory } from 'react-router-dom'; +import CustomTitle from '../../../components/customTitle/CustomTitle.js'; +import httpService from '../../../services/http.service.js'; + +export default function SubjectsPage() { + // State + const [subjects, setSubjects] = useState(undefined); + const [isLoading, setIsLoading] = useState(false); + + const history = useHistory(); + + /** + * Retrieves the subjects from the database. + */ + useEffect(() => { + setIsLoading(true); + httpService.getAll('/subjects', (response) => { + setSubjects(response.data); + setIsLoading(false); + }); + }, []); + + const historyBack = () => history.goBack(); + + /** + * Builds a table row for each of the subjects retrieved from the database. + */ + const buildSubjects = () => { + if (subjects) { + return subjects.map((subject) => ( + + {subject.id} + {subject.value} + + )); + } + return []; + }; + + return ( + + + + + + + + + + + + + + + + + + + + + {isLoading ? : buildSubjects()} + +
IDValue
+
+
+ ); +} diff --git a/apps/admin-panel/src/app/pages/resources/users/UsersPage.js b/apps/admin-panel/src/app/pages/resources/users/UsersPage.js new file mode 100644 index 00000000..a0328cd9 --- /dev/null +++ b/apps/admin-panel/src/app/pages/resources/users/UsersPage.js @@ -0,0 +1,78 @@ +import { useEffect, useState } from 'react'; +import { Table, Container, Col, Row, Spinner, Button } from 'react-bootstrap'; +import { useHistory } from 'react-router-dom'; +import CustomTitle from '../../../components/customTitle/CustomTitle.js'; +import httpService from '../../../services/http.service.js'; + +export default function UsersPage() { + // State + const [users, setUsers] = useState(undefined); + const [isLoading, setIsLoading] = useState(false); + + const history = useHistory(); + + /** + * Retrieves the users from the database. + */ + useEffect(() => { + setIsLoading(true); + httpService.getAll('/users', (response) => { + setUsers(response.data); + setIsLoading(false); + }); + }, []); + + const historyBack = () => history.goBack(); + + /** + * Builds a table row for each of the users retrieved from the database. + */ + const buildUsers = () => { + if (users) { + return users.map((user) => ( + + {user.id} + {user.username} + {user.name} + {user.validated ? 'True' : 'False'} + + )); + } + return []; + }; + + return ( + + + + + + + + + + + + + + + + + + + + + + + {isLoading ? : buildUsers()} + +
IDUsernameNameValidated
+
+
+ ); +} diff --git a/apps/admin-panel/src/app/pages/resources/videos/VideosPage.js b/apps/admin-panel/src/app/pages/resources/videos/VideosPage.js new file mode 100644 index 00000000..cb283ec4 --- /dev/null +++ b/apps/admin-panel/src/app/pages/resources/videos/VideosPage.js @@ -0,0 +1,76 @@ +import { useEffect, useState } from 'react'; +import { Table, Container, Col, Row, Spinner, Button } from 'react-bootstrap'; +import { useHistory } from 'react-router-dom'; +import CustomTitle from '../../../components/customTitle/CustomTitle.js'; +import httpService from '../../../services/http.service.js'; + +export default function VideosPage() { + // State + const [videos, setVideos] = useState(undefined); + const [isLoading, setIsLoading] = useState(false); + + const history = useHistory(); + + /** + * Retrieves the videos from the database. + */ + useEffect(() => { + setIsLoading(true); + httpService.getAll('/videos', (response) => { + setVideos(response.data); + setIsLoading(false); + }); + }, []); + + const historyBack = () => history.goBack(); + + /** + * Builds a table row for each of the videos retrieved from the database. + */ + const buildVideos = () => { + if (videos) { + return videos.map((video) => ( + + {video.id} + {video.videoTitle} + {video.videoLength} + + )); + } + return []; + }; + + return ( + + + + + + + + + + + + + + + + + + + + + + {isLoading ? : buildVideos()} + +
IDTitleLength
+
+
+ ); +} diff --git a/apps/admin-panel/src/app/public/app.css b/apps/admin-panel/src/app/public/app.css deleted file mode 100644 index 477d635f..00000000 --- a/apps/admin-panel/src/app/public/app.css +++ /dev/null @@ -1,3 +0,0 @@ -.main { - min-height: calc(100vh - 9vh - 6vh); -} diff --git a/apps/admin-panel/src/app/public/endpoints/endpoints.css b/apps/admin-panel/src/app/public/endpoints/endpoints.css deleted file mode 100644 index e66ea01c..00000000 --- a/apps/admin-panel/src/app/public/endpoints/endpoints.css +++ /dev/null @@ -1,56 +0,0 @@ -.body { - background-color: #cce3de; - background-position: center; - background-repeat: no-repeat; - background-size: cover; -} - -.main { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -} - -.main .header { - margin-top: 5vh; - display: flex; - justify-content: center; - align-items: center; -} - -.main .content { - margin-top: 3vh; - margin-bottom: 5vh; - display: flex; - flex-direction: column; - justify-content: center; - width: 50vw; -} - -.main .content .accordion-button { - background-color: white; -} - -.main .content .accordion-body { - background-color: whitesmoke; - display: flex; - justify-content: space-between; - align-items: center; -} - -.main .content .accordion-body button { - display: flex; - justify-content: center; - align-items: center; - width: 8vw; - height: 4vh; -} - -.main .content .actions { - display: flex; - flex-direction: column; - justify-content: space-between; - align-items: center; - height: 14vh; -} diff --git a/apps/admin-panel/src/app/public/endpoints/endpoints.js b/apps/admin-panel/src/app/public/endpoints/endpoints.js deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/admin-panel/src/app/public/footer/footer.css b/apps/admin-panel/src/app/public/footer/footer.css deleted file mode 100644 index 24f50860..00000000 --- a/apps/admin-panel/src/app/public/footer/footer.css +++ /dev/null @@ -1,27 +0,0 @@ -.footer { - display: flex; - justify-content: space-evenly; - align-items: center; - height: 6vh; - background: rgba(245, 245, 245, 0.7); -} - -.footer .left { - display: flex; - margin-left: 1vw; - justify-content: flex-start; - align-items: center; -} - -.footer .right { - display: flex; - margin-right: 2vw; - justify-content: flex-end; - align-items: center; -} - -.footer a { - display: flex; - justify-content: center; - align-items: center; -} diff --git a/apps/admin-panel/src/app/public/footer/footer.html b/apps/admin-panel/src/app/public/footer/footer.html deleted file mode 100644 index 120f7a47..00000000 --- a/apps/admin-panel/src/app/public/footer/footer.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - diff --git a/apps/admin-panel/src/app/public/footer/footer.js b/apps/admin-panel/src/app/public/footer/footer.js deleted file mode 100644 index 4503280c..00000000 --- a/apps/admin-panel/src/app/public/footer/footer.js +++ /dev/null @@ -1,7 +0,0 @@ -const copyrightSpan = document - .getElementsByClassName('footer')[0] - .getElementsByClassName('right')[0] - .getElementsByClassName('copyright')[0]; -const year = new Date().getFullYear(); - -copyrightSpan.innerText = '@Copyright ' + year; diff --git a/apps/admin-panel/src/app/public/home/home.css b/apps/admin-panel/src/app/public/home/home.css deleted file mode 100644 index 7b386dd4..00000000 --- a/apps/admin-panel/src/app/public/home/home.css +++ /dev/null @@ -1,32 +0,0 @@ -.body { - background-color: #cce3de; - background-position: center; - background-repeat: no-repeat; - background-size: cover; -} - -.main { - display: flex; - justify-content: center; -} - -.main .content { - margin-top: 3vh; - display: flex; - flex-direction: column; - justify-content: flex-start; - width: 96vw; -} - -.main .content .row { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - width: 100%; -} - -.main .content .list-group-item { - display: flex; - justify-content: space-between; -} diff --git a/apps/admin-panel/src/app/public/home/home.html b/apps/admin-panel/src/app/public/home/home.html deleted file mode 100644 index d91837aa..00000000 --- a/apps/admin-panel/src/app/public/home/home.html +++ /dev/null @@ -1,54 +0,0 @@ - - -
-
- -
-
Endpoints
- - - -
-
-
diff --git a/apps/admin-panel/src/app/public/images/logo.png b/apps/admin-panel/src/app/public/images/logo.png deleted file mode 100644 index 4564d1d9..00000000 Binary files a/apps/admin-panel/src/app/public/images/logo.png and /dev/null differ diff --git a/apps/admin-panel/src/app/public/images/logo.svg b/apps/admin-panel/src/app/public/images/logo.svg deleted file mode 100644 index 32b89817..00000000 --- a/apps/admin-panel/src/app/public/images/logo.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/apps/admin-panel/src/app/public/navbar/navbar.css b/apps/admin-panel/src/app/public/navbar/navbar.css deleted file mode 100644 index 8509a388..00000000 --- a/apps/admin-panel/src/app/public/navbar/navbar.css +++ /dev/null @@ -1,10 +0,0 @@ -.navbar { - height: 9vh; - max-height: 9vh; - background: #006466b2; -} - -.navbar .logo { - display: flex; - align-items: center; -} diff --git a/apps/admin-panel/src/app/public/navbar/navbar.html b/apps/admin-panel/src/app/public/navbar/navbar.html deleted file mode 100644 index a3c30594..00000000 --- a/apps/admin-panel/src/app/public/navbar/navbar.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - Admin Panel - Scoala-Online - - - - - - - - - - - - - - - - - - - - - - - diff --git a/apps/admin-panel/src/app/services/auth.service.js b/apps/admin-panel/src/app/services/auth.service.js new file mode 100644 index 00000000..efd3390f --- /dev/null +++ b/apps/admin-panel/src/app/services/auth.service.js @@ -0,0 +1,56 @@ +import axios from 'axios'; + +axios.defaults.baseURL = process.env.NX_API_URL; + +axios.interceptors.request.use( + function (config) { + const user = getUser(); + if (user && user.access_token) { + config.headers.Authorization = `Bearer ${user.access_token}`; + } + return config; + }, + function (error) { + return Promise.reject(error); + } +); + +const isAuthenticated = () => { + const user = getUser(); + + if (user && user.access_token) { + return true; + } + return false; +}; + +const login = async (username, password) => { + const response = await axios.post('/users/login', { + username, + password, + }); + if (response.data.access_token) { + localStorage.setItem('user', JSON.stringify(response.data)); + } + + return response.data; +}; + +const logout = () => { + localStorage.removeItem('user'); +}; + +const getUser = () => { + const user = localStorage.getItem('user'); + if (user) { + return JSON.parse(user); + } + return null; +}; + +export default { + login, + logout, + isAuthenticated, + getUser, +}; diff --git a/apps/admin-panel/src/app/services/http.service.js b/apps/admin-panel/src/app/services/http.service.js new file mode 100644 index 00000000..84c2c517 --- /dev/null +++ b/apps/admin-panel/src/app/services/http.service.js @@ -0,0 +1,185 @@ +import axios from 'axios'; + +import httpUtil from '../utils/http.util'; + +// Handlers +const responseHandler = (response, onResponse) => { + if (onResponse) { + onResponse(response); + } +}; + +const errorHandler = (error) => { + if (error.response) { + // The request was made and the server responded with a status code + // that falls out of the range of 2xx + console.log(error.response.data); + console.log(error.response.status); + console.log(error.response.headers); + } else if (error.request) { + // The request was made but no response was received + // `error.request` is an instance of XMLHttpRequest in the browser and an instance of + // http.ClientRequest in node.js + console.log(error.request); + } else { + // Something happened in setting up the request that triggered an Error + console.log('Error', error.message); + } + console.log(error.config); +}; + +// Http Service +const http = { + /** + * Gets all resources of a type.\ + * \ + * Calls the api at the `resourceEndpoint` provided and executes the + * `onResponse` callback when the response is received.\ + * \ + * The `resourceEndpoint` should contain the name of the resource + * that is being requested.\ + * Example: + * - '/students' + * - '/notebooks' + * + * The `onResponse` callback is a `Function` that should take the + * ***response*** `Object` as a parameter. + * @param {String} resourceEndpoint + * @param {Function} onResponse + */ + getAll: (resourceEndpoint, onResponse) => { + axios + .get(resourceEndpoint) + .then((response) => responseHandler(response, onResponse)) + .catch((error) => errorHandler(error)); + }, + + /** + * Gets a single resource.\ + * \ + * Calls the api at the `resourceEndpoint` combined with the + * `resourceId` as a *path parameter* and executes the + * `onResponse` callback when the response is received.\ + * \ + * The `resourceEndpoint` should contain the name of the resource + * that is being requested.\ + * Example: + * - '/students' + * - '/notebooks' + * + * The `resourceId` should contain the id of the resource + * that is being requested.\ + * Example: + * - 'cc327880f-9252-444b-94c2-b3b28c006' + * + * The `onResponse` callback is a `Function` that should take the + * ***response*** `Object` as a parameter. + * @param {String} resourceEndpoint + * @param {String} resourceId + * @param {Function} onResponse + */ + getById: (resourceEndpoint, resourceId, onResponse) => { + const endpoint = httpUtil.addPathParameter(resourceEndpoint, resourceId); + axios + .get(endpoint) + .then((response) => responseHandler(response, onResponse)) + .catch((error) => errorHandler(error)); + }, + + /** + * Creates a new resource.\ + * \ + * Calls the api at the `resourceEndpoint` with the provided `data` as a body, + * and executes the `onResponse` callback when the response is received.\ + * \ + * The `resourceEndpoint` should contain the name of the resource + * that is being requested.\ + * Example: + * - '/students' + * - '/notebooks' + * + * The `resourceId` should contain the id of the resource + * that is being requested.\ + * Example: + * - 'cc327880f-9252-444b-94c2-b3b28c006' + * + * The `onResponse` callback is a `Function` that should take the + * ***response*** `Object` as a parameter. + * @param {String} resourceEndpoint + * @param {Object} data + * @param {Function} onResponse + */ + add: (resourceEndpoint, data, onResponse) => { + axios + .post(resourceEndpoint, data) + .then((response) => responseHandler(response, onResponse)) + .catch((error) => errorHandler(error)); + }, + + /** + * Updates a resource.\ + * \ + * Calls the api at the `resourceEndpoint` combined with the + * `resourceId` as a *path parameter*, and the provided `data` as a body; + * it then executes the `onResponse` callback when the response is received.\ + * \ + * The `resourceEndpoint` should contain the name of the resource + * that is being requested.\ + * Example: + * - '/students' + * - '/notebooks' + * + * The `onResponse` callback is a `Function` that should take the + * ***response*** `Object` as a parameter. + * @param {String} resourceEndpoint + * @param {Object} data + * @param {Function} onResponse + */ + update: (resourceEndpoint, resourceId, data, onResponse) => { + const endpoint = httpUtil.addPathParameter(resourceEndpoint, resourceId); + axios + .patch(endpoint, data) + .then((response) => responseHandler(response, onResponse)) + .catch((error) => errorHandler(error)); + }, + + /** + * Deletes a single resource.\ + * \ + * Calls the api at the `resourceEndpoint` combined with the + * `resourceId` as a *path parameter* and executes the + * `onResponse` callback when the response is received.\ + * \ + * The `resourceEndpoint` should contain the name of the resource + * that is being requested.\ + * Example: + * - '/students' + * - '/notebooks' + * + * The `resourceId` should contain the id of the resource + * that is being requested.\ + * Example: + * - 'cc327880f-9252-444b-94c2-b3b28c006' + * + * The `onResponse` callback is a `Function` that should take the + * ***response*** `Object` as a parameter. + * @param {String} resourceEndpoint + * @param {String} resourceId + * @param {Function} onResponse + */ + delete: (resourceEndpoint, resourceId, onResponse) => { + const endpoint = httpUtil.addPathParameter(resourceEndpoint, resourceId); + axios + .delete(endpoint) + .then((response) => responseHandler(response, onResponse)) + .catch((error) => errorHandler(error)); + }, + + // Base axios functions + customGet: axios.get, + customPost: axios.post, + customPatch: axios.patch, + customDelete: axios.delete, +}; + +export default http; diff --git a/apps/admin-panel/src/app/services/jwtVerification.service.js b/apps/admin-panel/src/app/services/jwtVerification.service.js new file mode 100644 index 00000000..27c7f7fa --- /dev/null +++ b/apps/admin-panel/src/app/services/jwtVerification.service.js @@ -0,0 +1,34 @@ +import { useEffect } from 'react'; +import { withRouter, useLocation, useHistory } from 'react-router-dom'; +import AuthService from './auth.service'; + +const parseJwt = (token) => { + try { + return JSON.parse(atob(token.split('.')[1])); + } catch (e) { + console.error('Error parsing the token.', e); + } +}; + +const JwtVerification = (props) => { + const location = useLocation(); + const history = useHistory(); + + useEffect(() => { + const user = AuthService.getUser(); + + if (user && user.access_token) { + const decodedJwt = parseJwt(user.access_token); + + console.log(new Date(decodedJwt.exp * 1000)); + if (decodedJwt.exp * 1000 < Date.now()) { + AuthService.logout(); + history.push('/login'); + } + } + }, [location]); + + return
; +}; + +export default withRouter(JwtVerification); diff --git a/apps/admin-panel/src/app/util/endpoints.util.js b/apps/admin-panel/src/app/util/endpoints.util.js deleted file mode 100644 index 51a22509..00000000 --- a/apps/admin-panel/src/app/util/endpoints.util.js +++ /dev/null @@ -1,91 +0,0 @@ -const axios = require('axios').default; - -// Axios default configuration -axios.defaults.baseURL = process.env.NX_API_URL; -axios.defaults.responseType = 'json'; - -/** - * Takes in the name of a resource, queries the API for the given resource, and builds an HTML - * body with the data received from the API. - * - * @param {String} resource The name of a resource endpoint. - * @returns {String} the HTML body showcasing all the API entries for the given resource. - */ -const buildBody = async (resource) => { - const response = await axios.get(resource); - const items = response.data; - - let content = '
'; - items.map((element, index) => { - content += buildItem(element, index); - }); - content += '
'; - return content; -}; - -/** - * Takes in an **element** and constructs a '*Bootstrap Accordion item*' with its data. - * - * @param {Object} element an entry received from the API. - * @param {Number} index - * @returns {String} the HTML accordion item representing the provided **element**. - */ -const buildItem = (element, index) => { - const itemTitle = getItemTitle(element); - - return ` -
-

- -

-
-
-
- ${displayItem(element)} -
-
- - - -
-
-
-
- `; -}; - -/** - * Takes in an **element** and constructs an HTML preview of its contents. - * - * @param {Object} element an entry received from the API. - * @returns {String} a series of HTML spans, displaying the content of the **element**. - */ -const displayItem = (element) => { - let content = ''; - for (const key of Object.keys(element)) { - content += ` - ${key}: ${element[key]}
- `; - } - return content; -}; - -/** - * Takes in an **element** `Object`, searches through its keys and returns the first one that is not the ***id***. - * - * @param {Object} element an entry received from the API. - * @returns {String} the key which represents the title of the given `Object`. - */ -const getItemTitle = (element) => { - for (const key of Object.keys(element)) { - if (key !== 'id') { - return key; - } - } -}; - -module.exports = { - buildBody, -}; diff --git a/apps/admin-panel/src/app/utils/http.util.js b/apps/admin-panel/src/app/utils/http.util.js new file mode 100644 index 00000000..4cd5e2a5 --- /dev/null +++ b/apps/admin-panel/src/app/utils/http.util.js @@ -0,0 +1,21 @@ +const httpUtil = { + /** + * Adds a path parameter to a given path. + * @param {String} path - the current path. + * @param {String} pathParam - the path parameter to be added to the path. + * @returns {String} the path with the added parameter. + */ + addPathParameter: (path, pathParam) => { + if (path.endsWith('/')) { + path = path.substr(0, path.length - 1); + } + + if (pathParam.startsWith('/')) { + pathParam = pathParam.substr(1, pathParam.length); + } + + return `${path}/${pathParam}`; + }, +}; + +export default httpUtil; diff --git a/apps/admin-panel/src/environments/environment.js b/apps/admin-panel/src/environments/environment.js index a20cfe55..9d74e357 100644 --- a/apps/admin-panel/src/environments/environment.js +++ b/apps/admin-panel/src/environments/environment.js @@ -1,3 +1,5 @@ +// This file can be replaced during build by using the `fileReplacements` array. +// When building for production, this file is replaced with `environment.prod.ts`. export const environment = { production: false, }; diff --git a/apps/admin-panel/src/favicon.ico b/apps/admin-panel/src/favicon.ico new file mode 100644 index 00000000..317ebcb2 Binary files /dev/null and b/apps/admin-panel/src/favicon.ico differ diff --git a/apps/admin-panel/src/index.html b/apps/admin-panel/src/index.html new file mode 100644 index 00000000..bab0fee8 --- /dev/null +++ b/apps/admin-panel/src/index.html @@ -0,0 +1,38 @@ + + + + + AdminPanel + + + + + + + + + +
+ + + + + + + + + diff --git a/apps/admin-panel/src/main.js b/apps/admin-panel/src/main.js index 3046da06..79571ec3 100644 --- a/apps/admin-panel/src/main.js +++ b/apps/admin-panel/src/main.js @@ -1,82 +1,12 @@ -const chalk = require('chalk'); -const dotenv = require('dotenv'); -const fs = require('fs'); -const path = require('path'); -const express = require('express'); - -dotenv.config(); -const app = express(); - -const endpointsUtil = require('./app/util/endpoints.util'); - -// Constants -const SERVER_PORT = process.env.NX_SERVER_PORT || 8080; - -// Static directory -app.use( - express.static( - path.join(__dirname, '/../../../apps/admin-panel/src/app/public/') - ) +import { StrictMode } from 'react'; +import * as ReactDOM from 'react-dom'; +import { BrowserRouter } from 'react-router-dom'; +import App from './app/app'; +ReactDOM.render( + + + + + , + document.getElementById('root') ); - -// Files -const navbar = fs.readFileSync( - path.join( - __dirname, - '/../../../apps/admin-panel/src/app/public/navbar/navbar.html' - ), - 'utf-8' -); -const footer = fs.readFileSync( - path.join( - __dirname, - '/../../../apps/admin-panel/src/app/public/footer/footer.html' - ), - 'utf-8' -); -const home = fs.readFileSync( - path.join( - __dirname, - '/../../../apps/admin-panel/src/app/public/home/home.html' - ), - 'utf-8' -); -const endpoints = async (resource) => { - let endpointPage = fs.readFileSync( - path.join( - __dirname, - '/../../../apps/admin-panel/src/app/public/endpoints/endpoints.html' - ), - 'utf-8' - ); - endpointPage = endpointPage.replace('{{ Resource }}', resource); - endpointPage = endpointPage.replace( - '{{ Data }}', - await endpointsUtil.buildBody(resource) - ); - - return endpointPage; -}; - -app.get('/', (req, res) => { - res.send(navbar + home + footer); -}); - -app.get('/home', (req, res) => { - res.redirect('/'); -}); - -app.get('/endpoint/:resource', async (req, res) => { - const requestedResource = req.params.resource; - const content = await endpoints(requestedResource); - res.send(navbar + content + footer); -}); - -const server = app.listen(SERVER_PORT, (error) => { - if (error) console.log(error); - - console.log( - chalk.green('Admin Panel Server started on port:'), - server.address().port - ); -}); diff --git a/apps/admin-panel/src/polyfills.js b/apps/admin-panel/src/polyfills.js new file mode 100644 index 00000000..2adf3d05 --- /dev/null +++ b/apps/admin-panel/src/polyfills.js @@ -0,0 +1,7 @@ +/** + * Polyfill stable language features. These imports will be optimized by `@babel/preset-env`. + * + * See: https://github.com/zloirock/core-js#babel + */ +import 'core-js/stable'; +import 'regenerator-runtime/runtime'; diff --git a/apps/admin-panel/src/styles.css b/apps/admin-panel/src/styles.css new file mode 100644 index 00000000..90d4ee00 --- /dev/null +++ b/apps/admin-panel/src/styles.css @@ -0,0 +1 @@ +/* You can add global styles to this file, and also import other style files */ diff --git a/apps/admin-panel/tsconfig.app.json b/apps/admin-panel/tsconfig.app.json index 6b02b129..71adee65 100644 --- a/apps/admin-panel/tsconfig.app.json +++ b/apps/admin-panel/tsconfig.app.json @@ -2,9 +2,12 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", "types": ["node"] }, - "exclude": ["**/*.spec.ts", "**/*.spec.js"], - "include": ["**/*.ts", "**/*.js"] + "files": [ + "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../node_modules/@nrwl/react/typings/image.d.ts" + ], + "exclude": ["**/*.spec.ts", "**/*.spec.tsx"], + "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] } diff --git a/apps/admin-panel/tsconfig.json b/apps/admin-panel/tsconfig.json index cba0fd39..3cef3ded 100644 --- a/apps/admin-panel/tsconfig.json +++ b/apps/admin-panel/tsconfig.json @@ -1,5 +1,15 @@ { "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, "files": [], "include": [], "references": [ @@ -9,8 +19,5 @@ { "path": "./tsconfig.spec.json" } - ], - "compilerOptions": { - "allowJs": true - } + ] } diff --git a/apps/admin-panel/tsconfig.spec.json b/apps/admin-panel/tsconfig.spec.json index 559410b9..fee141bb 100644 --- a/apps/admin-panel/tsconfig.spec.json +++ b/apps/admin-panel/tsconfig.spec.json @@ -11,5 +11,9 @@ "**/*.spec.js", "**/*.spec.jsx", "**/*.d.ts" + ], + "files": [ + "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../node_modules/@nrwl/react/typings/image.d.ts" ] } diff --git a/apps/java-api/src/main/java/org/scoalaonline/api/service/JWTService.java b/apps/java-api/src/main/java/org/scoalaonline/api/service/JWTService.java index 91b459f0..4aba3373 100644 --- a/apps/java-api/src/main/java/org/scoalaonline/api/service/JWTService.java +++ b/apps/java-api/src/main/java/org/scoalaonline/api/service/JWTService.java @@ -19,8 +19,8 @@ @Slf4j public class JWTService { private Algorithm algorithm = Algorithm.HMAC256("secret".getBytes()); - private Date accessTimer = new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000); - private Date refreshTimer = new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000); + private int accessTimer = 15 * 60 * 1000; + private int refreshTimer = 24 * 60 * 60 * 1000; /** * Generates an access token based on the username, requestURL and the list of roles, using the algorithm @@ -32,7 +32,7 @@ public class JWTService { public String getAccessToken(String username, String requestURL, List roles) { return JWT.create() .withSubject(username) - .withExpiresAt(accessTimer) + .withExpiresAt(new Date(System.currentTimeMillis() + accessTimer)) .withIssuer(requestURL) .withClaim("roles", roles) .sign(algorithm); @@ -47,7 +47,7 @@ public String getAccessToken(String username, String requestURL, List ro public String getRefreshToken(String username, String requestURL) { return JWT.create() .withSubject(username) - .withExpiresAt(accessTimer) + .withExpiresAt(new Date(System.currentTimeMillis() + refreshTimer)) .withIssuer(requestURL) .sign(algorithm); }