From 25d876d22159830ec74703e1c19b44cd2208138b Mon Sep 17 00:00:00 2001 From: Kacper Donat Date: Thu, 10 Sep 2020 18:42:57 +0200 Subject: [PATCH 1/3] Add support for PG SSO --- src/api/user.ts | 19 ++++++++++++++++--- src/pages/user/login.tsx | 32 ++++++++++++++++++++++++-------- src/routing.tsx | 8 +++++++- webpack.config.js | 5 +++-- 4 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/api/user.ts b/src/api/user.ts index ba70d5f..e930190 100644 --- a/src/api/user.ts +++ b/src/api/user.ts @@ -1,9 +1,22 @@ import { axios } from "@/api/index"; +import { query, route } from "@/routing"; -const AUTHORIZE_ENDPOINT = "/access/login" +const LOGIN_ENDPOINT = "/access/login" -export async function authorize(code: string): Promise { - const response = await axios.get(AUTHORIZE_ENDPOINT, { params: { code }}); +const CLIENT_ID = process.env.LOGIN_CLIENT_ID || "PraktykiClientId"; +const AUTHORIZE_URL = process.env.AUTHORIZE || "https://logowanie.pg.edu.pl/oauth2.0/authorize"; + +export async function login(code: string): Promise { + const response = await axios.get(LOGIN_ENDPOINT, { params: { code }}); return response.data; } + +export function getAuthorizeUrl() { + return query(AUTHORIZE_URL, { + response_type: "code", + scope: "user_details", + client_id: CLIENT_ID, + redirect_uri: window.location.origin + route("user_login") + "/check/pg", + }) +} diff --git a/src/pages/user/login.tsx b/src/pages/user/login.tsx index 89fa966..f7a9099 100644 --- a/src/pages/user/login.tsx +++ b/src/pages/user/login.tsx @@ -1,8 +1,8 @@ import React, { Dispatch } from "react"; import { Page } from "@/pages/base"; -import { Button, Container, Typography } from "@material-ui/core"; +import { Button, Container } from "@material-ui/core"; import { Action, useDispatch } from "@/state/actions"; -import { useHistory } from "react-router-dom"; +import { Route, Switch, useHistory, useLocation, useRouteMatch } from "react-router-dom"; import { route } from "@/routing"; import { useVerticalSpacing } from "@/styles"; import { AppState } from "@/state/reducer"; @@ -10,9 +10,10 @@ import { AppState } from "@/state/reducer"; import api from "@/api"; import { UserActions } from "@/state/actions/user"; import { sampleStudent } from "@/provider/dummy"; +import { getAuthorizeUrl } from "@/api/user"; const authorizeUser = async (dispatch: Dispatch, getState: () => AppState): Promise => { - const token = await api.user.authorize("test"); + const token = await api.user.login("test"); dispatch({ type: UserActions.Login, @@ -24,6 +25,8 @@ const authorizeUser = async (dispatch: Dispatch, getState: () => AppStat export const UserLoginPage = () => { const dispatch = useDispatch(); const history = useHistory(); + const match = useRouteMatch(); + const query = new URLSearchParams(useLocation().search); const handleSampleLogin = async () => { await dispatch(authorizeUser); @@ -31,16 +34,29 @@ export const UserLoginPage = () => { history.push(route("home")); } + const handlePgLogin = async () => { + history.push(route("user_login") + "/pg"); + } + const classes = useVerticalSpacing(3); return - Tu miało być przekierowanie do logowania PG... + Zaloguj się - - ... ale wciąż czekamy na dostęp :( - - + + + + + + + + + (window.location.href = getAuthorizeUrl())} /> + + Kod: { query.get("code") } + + ; } diff --git a/src/routing.tsx b/src/routing.tsx index f57249d..f4f5fd1 100644 --- a/src/routing.tsx +++ b/src/routing.tsx @@ -25,7 +25,7 @@ export const routes: Route[] = [ { name: "internship_plan", path: "/internship/plan", exact: true, content: () => }, // user - { name: "user_login", path: "/user/login", exact: true, content: () => }, + { name: "user_login", path: "/user/login", content: () => }, // fallback route for 404 pages { name: "fallback", path: "*", content: () => } @@ -44,3 +44,9 @@ export function route(name: string, params: URLParams = {}) { return prepare(url, params) } + +export const query = (url: string, params: URLParams) => { + const query = Object.entries(params).map(([ name, value ]) => `${name}=${encodeURIComponent(value)}`).join("&"); + + return url + (query.length > 0 ? `?${query}` : ''); +} diff --git a/webpack.config.js b/webpack.config.js index c3636bf..94454fc 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -51,11 +51,12 @@ const config = { ], devServer: { contentBase: path.resolve("./public/"), - port: 3000, - host: 'system-praktyk-front.localhost', + port: 443, + host: 'system-praktyk.stg.kadet.net', disableHostCheck: true, historyApiFallback: true, overlay: true, + https: true, proxy: { "/api": { target: "http://system-praktyk-front.localhost:8080/", From fa04dd26cce37ec7240d1918d0e1aad992d2593e Mon Sep 17 00:00:00 2001 From: Kacper Donat Date: Thu, 17 Sep 2020 21:02:27 +0200 Subject: [PATCH 2/3] Add integration with api for static pages --- src/api/dto/page.ts | 40 ++++++++++++++++++++++++++++++++++++++++ src/api/page.tsx | 28 +++++++--------------------- src/data/page.ts | 1 + webpack.config.js | 5 ++--- 4 files changed, 50 insertions(+), 24 deletions(-) create mode 100644 src/api/dto/page.ts diff --git a/src/api/dto/page.ts b/src/api/dto/page.ts new file mode 100644 index 0000000..390fa03 --- /dev/null +++ b/src/api/dto/page.ts @@ -0,0 +1,40 @@ +import { Identifiable } from "@/data"; +import { Page } from "@/data/page"; +import { Transformer } from "@/serialization"; + +export interface PageDTO extends Identifiable { + accessName: string; + title: string; + titleEng: string; + content: string; + contentEng: string; +} + +export const pageDtoTransformer: Transformer = { + reverseTransform(subject: Page, context: undefined): PageDTO { + return { + id: subject.id, + accessName: subject.slug, + content: subject.content.pl, + contentEng: subject.content.en, + title: subject.title.pl, + titleEng: subject.title.en, + } + }, + transform(subject: PageDTO, context: undefined): Page { + return { + slug: subject.accessName, + id: subject.id, + content: { + pl: subject.content, + en: subject.contentEng + }, + title: { + pl: subject.title, + en: subject.titleEng + }, + }; + } +} + +export default pageDtoTransformer; diff --git a/src/api/page.tsx b/src/api/page.tsx index f1c69ee..1a5cd02 100644 --- a/src/api/page.tsx +++ b/src/api/page.tsx @@ -1,27 +1,13 @@ -// MOCK import { Page } from "@/data/page"; +import { PageDTO, pageDtoTransformer } from "./dto/page" +import { axios } from "@/api/index"; +import { prepare } from "@/routing"; -const tos = `

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Bestiarum vero nullum iudicium puto. Quare ad ea primum, si videtur; Duo Reges: constructio interrete. Eam tum adesse, cum dolor omnis absit; Sed ad bona praeterita redeamus. Facillimum id quidem est, inquam. Apud ceteros autem philosophos, qui quaesivit aliquid, tacet;

- -

Quorum altera prosunt, nocent altera. Eam stabilem appellas. Sed nimis multa. Quo plebiscito decreta a senatu est consuli quaestio Cn. Sin laboramus, quis est, qui alienae modum statuat industriae? Quod quidem nobis non saepe contingit. Si autem id non concedatur, non continuo vita beata tollitur. Illum mallem levares, quo optimum atque humanissimum virum, Cn. Id est enim, de quo quaerimus.

- -

Ille vero, si insipiens-quo certe, quoniam tyrannus -, numquam beatus; Sin dicit obscurari quaedam nec apparere, quia valde parva sint, nos quoque concedimus; Et quod est munus, quod opus sapientiae? Ab hoc autem quaedam non melius quam veteres, quaedam omnino relicta.

-` +const STATIC_PAGE_ENDPOINT = "/staticPage/:slug" export async function get(slug: string): Promise { - if (slug === "/regulamin" || slug === "/rules") { - return { - id: "tak", - content: { - pl: tos, - en: tos, - }, - title: { - pl: "Regulamin Praktyk", - en: "Terms of Internship", - }, - } - } + const response = await axios.get(prepare(STATIC_PAGE_ENDPOINT, { slug })) + const page = response.data; - throw new Error(); + return pageDtoTransformer.transform(page); } diff --git a/src/data/page.ts b/src/data/page.ts index a474e82..ca7e3c9 100644 --- a/src/data/page.ts +++ b/src/data/page.ts @@ -3,4 +3,5 @@ import { Identifiable, Multilingual } from "@/data/common"; export interface Page extends Identifiable { title: Multilingual; content: Multilingual; + slug: string; } diff --git a/webpack.config.js b/webpack.config.js index 94454fc..c98251d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -51,12 +51,11 @@ const config = { ], devServer: { contentBase: path.resolve("./public/"), - port: 443, - host: 'system-praktyk.stg.kadet.net', + host: process.env.APP_HOST || 'system-praktyk-front.localhost', disableHostCheck: true, historyApiFallback: true, overlay: true, - https: true, + https: !!process.env.APP_HTTPS || false, proxy: { "/api": { target: "http://system-praktyk-front.localhost:8080/", From 379acad8c5a5a2d9a29b5e89f9324f53df54d6d1 Mon Sep 17 00:00:00 2001 From: Kacper Donat Date: Sun, 27 Sep 2020 22:06:53 +0200 Subject: [PATCH 3/3] Login and stuff --- src/api/dto/course.ts | 23 +++++++++++ src/api/dto/edition.ts | 57 +++++++++++++++++++++++++++ src/api/dto/student.ts | 34 +++++++++++++++++ src/api/edition.ts | 18 ++++----- src/api/index.ts | 4 +- src/api/student.ts | 13 +++++++ src/app.tsx | 20 ++++------ src/components/section.tsx | 2 + src/data/edition.ts | 5 ++- src/data/student.ts | 2 +- src/helpers.ts | 2 +- src/hooks/useAsync.ts | 10 ++++- src/middleware.tsx | 15 ++++++++ src/pages/edition/pick.tsx | 72 +++++++++++++++++++++++++++++++++++ src/pages/main.tsx | 2 +- src/pages/user/login.tsx | 32 ++++++++++++---- src/provider/dummy/student.ts | 2 +- src/routing.tsx | 29 +++++++++++--- src/serialization/types.ts | 7 +++- src/state/actions/edition.ts | 2 +- src/state/actions/index.ts | 21 +++++++++- src/state/actions/student.ts | 13 +++++++ src/state/actions/user.ts | 2 - src/state/reducer/student.ts | 5 ++- translations/pl.yaml | 5 +++ webpack.config.js | 1 + 26 files changed, 344 insertions(+), 54 deletions(-) create mode 100644 src/api/dto/course.ts create mode 100644 src/api/dto/edition.ts create mode 100644 src/api/dto/student.ts create mode 100644 src/api/student.ts create mode 100644 src/middleware.tsx create mode 100644 src/pages/edition/pick.tsx create mode 100644 src/state/actions/student.ts diff --git a/src/api/dto/course.ts b/src/api/dto/course.ts new file mode 100644 index 0000000..8b13f1f --- /dev/null +++ b/src/api/dto/course.ts @@ -0,0 +1,23 @@ +import { Course, Identifiable } from "@/data"; +import { Transformer } from "@/serialization"; + +export interface CourseDTO extends Identifiable { + name: string; +} + +export const courseDtoTransformer: Transformer = { + reverseTransform(subject: Course, context: undefined): CourseDTO { + return { + id: subject.id, + name: subject.name, + }; + }, + transform(subject: CourseDTO, context: undefined): Course { + return { + id: subject.id, + name: subject.name, + desiredSemesters: [], + possibleProgramEntries: [], // todo + }; + } +} diff --git a/src/api/dto/edition.ts b/src/api/dto/edition.ts new file mode 100644 index 0000000..94c3101 --- /dev/null +++ b/src/api/dto/edition.ts @@ -0,0 +1,57 @@ +import { Identifiable } from "@/data"; +import { CourseDTO, courseDtoTransformer } from "@/api/dto/course"; +import { OneWayTransformer, Transformer } from "@/serialization"; +import { Edition } from "@/data/edition"; +import moment from "moment"; +import { Subset } from "@/helpers"; + +export interface EditionDTO extends Identifiable { + editionStart: string, + editionFinish: string, + reportingStart: string, + course: CourseDTO, +} + +export interface EditionTeaserDTO extends Identifiable { + editionStart: string, + editionFinish: string, + courseName: string, +} + +export const editionTeaserDtoTransformer: OneWayTransformer> = { + transform(subject: EditionTeaserDTO, context?: undefined): Subset { + return { + id: subject.id, + startDate: moment(subject.editionStart), + endDate: moment(subject.editionFinish), + course: { + name: subject.courseName, + } + } + } +} + +export const editionDtoTransformer: Transformer = { + reverseTransform(subject: Edition, context: undefined): EditionDTO { + return { + id: subject.id, + editionFinish: subject.endDate.toISOString(), + editionStart: subject.startDate.toISOString(), + course: courseDtoTransformer.reverseTransform(subject.course), + reportingStart: subject.reportingStart.toISOString(), + }; + }, + transform(subject: EditionDTO, context: undefined): Edition { + return { + id: subject.id, + course: courseDtoTransformer.transform(subject.course), + startDate: moment(subject.editionStart), + endDate: moment(subject.editionFinish), + minimumInternshipHours: 40, + maximumInternshipHours: 160, + proposalDeadline: moment(subject.reportingStart), + reportingStart: moment(subject.reportingStart), + reportingEnd: moment(subject.reportingStart).add(1, 'month'), + }; + } +} diff --git a/src/api/dto/student.ts b/src/api/dto/student.ts new file mode 100644 index 0000000..269e1aa --- /dev/null +++ b/src/api/dto/student.ts @@ -0,0 +1,34 @@ +import { Identifiable, Student } from "@/data"; +import { Transformer } from "@/serialization"; + +export interface StudentDTO extends Identifiable { + albumNumber: number, + course: any, + email: string, + firstName: string, + lastName: string, + semester: number, +} + +export const studentDtoTransfer: Transformer = { + reverseTransform(subject: Student, context: undefined): StudentDTO { + return { + albumNumber: subject.albumNumber, + course: subject.course, + email: subject.email, + firstName: subject.name, + lastName: subject.surname, + semester: subject.semester + }; + }, + transform(subject: StudentDTO, context: undefined): Student { + return { + albumNumber: subject.albumNumber, + course: subject.course, + email: subject.email, + name: subject.firstName, + semester: subject.semester, + surname: subject.lastName + }; + } +} diff --git a/src/api/edition.ts b/src/api/edition.ts index bce8982..6164634 100644 --- a/src/api/edition.ts +++ b/src/api/edition.ts @@ -1,16 +1,16 @@ import { axios } from "@/api/index"; import { Edition } from "@/data/edition"; -import { sampleEdition } from "@/provider/dummy"; -import { delay } from "@/helpers"; +import { prepare } from "@/routing"; +import { EditionDTO, editionDtoTransformer, editionTeaserDtoTransformer } from "@/api/dto/edition"; const EDITIONS_ENDPOINT = "/editions"; const EDITION_INFO_ENDPOINT = "/editions/:key"; const REGISTER_ENDPOINT = "/register"; -export async function editions() { +export async function available() { const response = await axios.get(EDITIONS_ENDPOINT); - return response.data; + return (response.data || []).map(editionTeaserDtoTransformer.transform); } export async function join(key: string): Promise { @@ -23,13 +23,9 @@ export async function join(key: string): Promise { } } -// MOCK export async function get(key: string): Promise { - await delay(Math.random() * 200 + 100); + const response = await axios.get(prepare(EDITION_INFO_ENDPOINT, { key })); + const dto = response.data; - if (key == "inf2020") { - return sampleEdition; - } - - return null; + return editionDtoTransformer.transform(dto); } diff --git a/src/api/index.ts b/src/api/index.ts index 6d9942d..3432330 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -6,6 +6,7 @@ import { UserState } from "@/state/reducer/user"; import * as user from "./user"; import * as edition from "./edition"; import * as page from "./page" +import * as student from "./student" export const axios = Axios.create({ baseURL: process.env.API_BASE_URL || "https://system-praktyk.stg.kadet.net/api/", @@ -31,7 +32,8 @@ axios.interceptors.request.use(config => { const api = { user, edition, - page + page, + student } export default api; diff --git a/src/api/student.ts b/src/api/student.ts new file mode 100644 index 0000000..e93a861 --- /dev/null +++ b/src/api/student.ts @@ -0,0 +1,13 @@ +import { axios } from "@/api/index"; +import { Student } from "@/data/student"; +import { StudentDTO, studentDtoTransfer } from "@/api/dto/student"; + +export const CURRENT_STUDENT_ENDPOINT = '/students/current'; + +export async function current(): Promise { + const response = await axios.get(CURRENT_STUDENT_ENDPOINT); + const dto = response.data; + + return studentDtoTransfer.transform(dto); +} + diff --git a/src/app.tsx b/src/app.tsx index c05b38c..a3bb940 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,16 +1,14 @@ import React, { HTMLProps, useEffect } from 'react'; import { Link, Route, Switch } from "react-router-dom" -import { route, routes } from "@/routing"; +import { processMiddlewares, route, routes } from "@/routing"; import { useSelector } from "react-redux"; -import { AppState, isReady } from "@/state/reducer"; +import { AppState } from "@/state/reducer"; import { Trans, useTranslation } from "react-i18next"; import { Student } from "@/data"; import '@/styles/overrides.scss' import '@/styles/header.scss' import '@/styles/footer.scss' import classNames from "classnames"; -import { EditionActions } from "@/state/actions/edition"; -import { sampleEdition } from "@/provider/dummy/edition"; import { Edition } from "@/data/edition"; import { SettingActions } from "@/state/actions/settings"; import { useDispatch, UserActions } from "@/state/actions"; @@ -68,20 +66,12 @@ function App() { const { t } = useTranslation(); const locale = useSelector(state => getLocale(state.settings)); - useEffect(() => { - if (!edition) { - dispatch({ type: EditionActions.Set, edition: sampleEdition }); - } - }) - useEffect(() => { i18n.changeLanguage(locale); document.documentElement.lang = locale; moment.locale(locale) }, [ locale ]) - const ready = useSelector(isReady); - return <>
- { ready && { routes.map(({ name, content, ...route }) => { content() }) } } + { + { routes.map(({ name, content, middlewares = [], ...route }) => + { processMiddlewares([ ...middlewares, content ]) } + ) } + }