From 5754bf42f7f730b4991af70b23010e0e9756bdd1 Mon Sep 17 00:00:00 2001 From: TechQuery Date: Sat, 7 Sep 2024 08:19:16 +0800 Subject: [PATCH] [refactor] simplify Page Authorization based on MobX RESTful persistence [remove] Strapi session API --- .github/workflows/main.yml | 5 ++ .github/workflows/pull-request.yml | 5 ++ .../PlatformAdmin/PlatformAdminFrame.tsx | 18 ++---- components/Team/TeamManageFrame.tsx | 4 +- models/Activity/Team.ts | 16 ++++- models/Activity/index.ts | 13 +++- models/Base/index.ts | 7 +- models/User/Session.ts | 64 ++++++++----------- models/User/index.ts | 8 --- pages/_app.tsx | 2 +- pages/activity/[name]/index.tsx | 3 +- .../activity/[name]/manage/questionnaire.tsx | 2 +- pages/activity/[name]/team/[tid]/index.tsx | 6 +- pages/connect/[provider]/redirect.tsx | 21 ------ pages/index.tsx | 10 +-- pages/user/[id].tsx | 5 +- 16 files changed, 83 insertions(+), 106 deletions(-) delete mode 100644 pages/connect/[provider]/redirect.tsx diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 710456f5..64e9c430 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -46,6 +46,11 @@ jobs: - tag: a text: ${{ github.ref }} href: ${{ github.server_url }}/${{ github.repository }}/tree/${{ github.ref_name }} + - - tag: text + text: 提交作者: + - tag: a + text: ${{ github.actor }} + href: ${{ github.server_url }}/${{ github.actor }} - - tag: text text: 预览链接: - tag: a diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index ede12ff2..21ac43e9 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -45,6 +45,11 @@ jobs: - tag: a text: ${{ github.ref }} href: ${{ github.server_url }}/${{ github.repository }}/tree/${{ github.ref_name }} + - - tag: text + text: 提交作者: + - tag: a + text: ${{ github.actor }} + href: ${{ github.server_url }}/${{ github.actor }} - - tag: text text: 预览链接: - tag: a diff --git a/components/PlatformAdmin/PlatformAdminFrame.tsx b/components/PlatformAdmin/PlatformAdminFrame.tsx index 39353520..bb338312 100644 --- a/components/PlatformAdmin/PlatformAdminFrame.tsx +++ b/components/PlatformAdmin/PlatformAdminFrame.tsx @@ -14,14 +14,13 @@ import { } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Loading } from 'idea-react'; -import { observable } from 'mobx'; import { observer } from 'mobx-react'; import { Component, Fragment, PropsWithChildren } from 'react'; import { Container, Nav } from 'react-bootstrap'; import { adminMenus } from '../../configuration/menu'; import { i18n } from '../../models/Base/Translation'; -import platformAdminStore from '../../models/User/PlatformAdmin'; +import sessionStore from '../../models/User/Session'; import { findDeep } from '../../utils/data'; import { MainBreadcrumb } from '../layout/MainBreadcrumb'; import { PageHead } from '../layout/PageHead'; @@ -49,13 +48,8 @@ export type PlatformAdminFrameProps = PropsWithChildren<{ @observer export class PlatformAdminFrame extends Component { - @observable - accessor loading = false; - - async componentDidMount() { - this.loading = true; - await platformAdminStore.checkAuthorization(); - this.loading = false; + componentDidMount() { + sessionStore.getProfile(); } get currentRoute() { @@ -106,9 +100,9 @@ export class PlatformAdminFrame extends Component { } render() { - const { currentRoute, loading } = this, + const { currentRoute } = this, { children, title } = this.props, - { isPlatformAdmin } = platformAdminStore; + { downloading, isPlatformAdmin } = sessionStore; return (
{ > - {loading ? ( + {downloading > 0 ? ( ) : isPlatformAdmin ? ( <> diff --git a/components/Team/TeamManageFrame.tsx b/components/Team/TeamManageFrame.tsx index 59b85f41..5d3c0dda 100644 --- a/components/Team/TeamManageFrame.tsx +++ b/components/Team/TeamManageFrame.tsx @@ -8,6 +8,7 @@ import { import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { StaffType, TeamMemberRole } from '@kaiyuanshe/openhackathon-service'; import { Loading } from 'idea-react'; +import { HTTPError } from 'koajax'; import { computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import { JWTProps, RouteProps } from 'next-ssr-middleware'; @@ -16,7 +17,6 @@ import { Col, Nav } from 'react-bootstrap'; import { activityTeamMenus } from '../../configuration/menu'; import activityStore from '../../models/Activity'; -import { ErrorBaseData } from '../../models/Base'; import { i18n } from '../../models/Base/Translation'; import sessionStore from '../../models/User/Session'; import { findDeep } from '../../utils/data'; @@ -59,7 +59,7 @@ export class TeamManageFrame extends PureComponent { ? currentUserInThisTeam.role : undefined; } catch (error: any) { - const { status } = error as ErrorBaseData; + const { status } = (error as HTTPError).response; if (status !== 404) this.teamMemberRole = undefined; } finally { diff --git a/models/Activity/Team.ts b/models/Activity/Team.ts index 98d0243f..d143d725 100644 --- a/models/Activity/Team.ts +++ b/models/Activity/Team.ts @@ -7,12 +7,12 @@ import { TeamWorkFilter, } from '@kaiyuanshe/openhackathon-service'; import { action, computed, observable } from 'mobx'; -import { ListModel, Stream, toggle } from 'mobx-restful'; +import { ListModel, persist, restore, Stream, toggle } from 'mobx-restful'; import { buildURLData } from 'web-utility'; import { createListStream, Filter, InputData, TableModel } from '../Base'; import { WorkspaceModel } from '../Git'; -import sessionStore from '../User/Session'; +import sessionStore, { isServer } from '../User/Session'; import { AwardAssignment } from './Award'; export type TeamFilter = Filter & BaseFilter; @@ -110,8 +110,18 @@ export class TeamMemberModel extends TableModel { this.baseURI = `${baseURI}/member`; } + restored = !isServer() && restore(this, 'TeamMember'); + + @persist() @observable - accessor sessionOne: TeamMember | undefined; + accessor currentOne = {} as TeamMember; + + @toggle('downloading') + async getOne(id: number) { + await this.restored; + + return this.currentOne.id === id ? this.currentOne : super.getOne(id); + } @toggle('uploading') async joinTeam(data: JoinTeamReqBody) { diff --git a/models/Activity/index.ts b/models/Activity/index.ts index 33273998..26d0ded1 100644 --- a/models/Activity/index.ts +++ b/models/Activity/index.ts @@ -6,12 +6,13 @@ import { Questionnaire, } from '@kaiyuanshe/openhackathon-service'; import { action, observable } from 'mobx'; -import { toggle } from 'mobx-restful'; +import { persist, restore, toggle } from 'mobx-restful'; import { buildURLData } from 'web-utility'; import { createListStream, Filter, InputData, TableModel } from '../Base'; import { GitModel } from '../Git'; import platformAdmin from '../User/PlatformAdmin'; +import { isServer } from '../User/Session'; import { AwardModel } from './Award'; import { EnrollmentModel } from './Enrollment'; import { LogModel } from './Log'; @@ -37,6 +38,12 @@ export class ActivityModel extends TableModel { baseURI = 'hackathon'; indexKey = 'name' as const; + restored = !isServer() && restore(this, 'Activity'); + + @persist() + @observable + accessor currentOne = {} as Hackathon; + currentStaff?: StaffModel; currentAward?: AwardModel; @@ -114,6 +121,10 @@ export class ActivityModel extends TableModel { @action @toggle('downloading') async getOne(name: string) { + await this.restored; + + if (this.currentOne.name === name) return this.currentOne; + const { detail, ...data } = await super.getOne(name); this.staffOf(name); diff --git a/models/Base/index.ts b/models/Base/index.ts index eb83f659..b3693029 100644 --- a/models/Base/index.ts +++ b/models/Base/index.ts @@ -1,9 +1,8 @@ import { Base, ListChunk } from '@kaiyuanshe/openhackathon-service'; -import { HTTPError } from 'koajax'; import { Filter as BaseFilter, ListModel, RESTClient } from 'mobx-restful'; import { buildURLData } from 'web-utility'; -import sessionStore from '../User/Session'; +import { ownClient } from '../User/Session'; export interface UploadUrl extends Record<'filename' | 'uploadUrl' | 'url', string> { @@ -30,8 +29,6 @@ export interface ListData { value: T[]; } -export const isServer = () => typeof window === 'undefined'; - export async function* createListStream( path: string, client: RESTClient, @@ -60,7 +57,7 @@ export abstract class TableModel< D extends Base, F extends InputData = InputData, > extends ListModel { - client = sessionStore.client; + client = ownClient; async loadPage(pageIndex: number, pageSize: number, filter: F) { const { body } = await this.client.get>( diff --git a/models/User/Session.ts b/models/User/Session.ts index 8bd6481f..793aced8 100644 --- a/models/User/Session.ts +++ b/models/User/Session.ts @@ -2,34 +2,26 @@ import { Base, User } from '@kaiyuanshe/openhackathon-service'; import { HTTPClient } from 'koajax'; import { computed, observable } from 'mobx'; import { parseCookie, setCookie } from 'mobx-i18n'; -import { BaseModel, toggle } from 'mobx-restful'; -import { buildURLData, sleep } from 'web-utility'; +import { BaseModel, persist, restore, toggle } from 'mobx-restful'; +import { buildURLData } from 'web-utility'; -import { AuthingUserBase } from '.'; +export const isServer = () => typeof window === 'undefined'; -const { localStorage, document } = globalThis; +const { token, JWT } = (globalThis.document ? parseCookie() : {}) as Record< + 'token' | 'JWT', + string +>; -const { token } = (document ? parseCookie() : {}) as { token: string }; - -export const strapiClient = new HTTPClient({ - baseURI: `${ - process.env.NODE_ENV === 'development' - ? 'http://127.0.0.1:1337' - : 'https://hackathon-server.kaiyuanshe.cn' - }/api/`, +export const ownClient = new HTTPClient({ + baseURI: process.env.NEXT_PUBLIC_API_HOST, responseType: 'json', }).use(({ request }, next) => { - if (token) - request.headers = { ...request.headers, Authorization: `Bearer ${token}` }; + if (JWT) + request.headers = { ...request.headers, Authorization: `Bearer ${JWT}` }; return next(); }); -export const ownClient = new HTTPClient({ - baseURI: process.env.NEXT_PUBLIC_API_HOST, - responseType: 'json', -}); - export interface SessionUser extends Base, Record<'username' | 'email', string>, @@ -39,36 +31,31 @@ export interface SessionUser } export class SessionModel extends BaseModel { - client = ownClient.use(({ request }, next) => { - const { token } = this.user || {}; + client = ownClient; - if (token) - request.headers = { - ...request.headers, - Authorization: `Bearer ${token}`, - }; - return next(); - }); + restored = !isServer() && restore(this, 'Session'); + @persist() @observable - accessor user: User | undefined = - localStorage?.user && JSON.parse(localStorage.user); + accessor user: User | undefined; @computed get metaOAuth() { - const { token } = parseCookie(globalThis.document?.cookie || ''); - return { github: { accessToken: token } }; } - @toggle('uploading') - async signIn(profile: AuthingUserBase, reload = false) { - const { body } = await this.client.post('login', profile); + @computed + get isPlatformAdmin() { + return !!this.user?.roles.includes(0); + } + + @toggle('downloading') + async getProfile() { + await this.restored; - setCookie('token', body!.token!, { path: '/' }); - localStorage.user = JSON.stringify(body); + if (this.user) return this.user; - if (reload) sleep().then(() => location.reload()); + const { body } = await this.client.get('user/session'); return (this.user = body); } @@ -83,7 +70,6 @@ export class SessionModel extends BaseModel { signOut(reload = false) { setCookie('token', '', { path: '/', expires: new Date() }); setCookie('JWT', '', { path: '/', expires: new Date() }); - localStorage?.clear(); this.user = undefined; diff --git a/models/User/index.ts b/models/User/index.ts index 2dbea2ff..4f9cd194 100644 --- a/models/User/index.ts +++ b/models/User/index.ts @@ -2,14 +2,6 @@ import { User, UserRankListChunk } from '@kaiyuanshe/openhackathon-service'; import { Filter, TableModel } from '../Base'; -export interface AuthingUserBase { - _id?: string; - openid: string; - unionid?: string; - userId: string; - userPoolId: string; -} - export interface UserFilter extends Filter { keywords?: string; } diff --git a/pages/_app.tsx b/pages/_app.tsx index eccf5ce9..f1e7e88f 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -10,8 +10,8 @@ import { FC } from 'react'; import { Col, Container, Image, Row } from 'react-bootstrap'; import { MainNavigation } from '../components/layout/MainNavigation'; -import { ErrorBaseData, isServer } from '../models/Base'; import { i18n } from '../models/Base/Translation'; +import { isServer } from '../models/User/Session'; configure({ enforceActions: 'never' }); diff --git a/pages/activity/[name]/index.tsx b/pages/activity/[name]/index.tsx index 77a17fa6..bd236a3a 100644 --- a/pages/activity/[name]/index.tsx +++ b/pages/activity/[name]/index.tsx @@ -40,9 +40,8 @@ import { TeamCard } from '../../../components/Team/TeamCard'; import { TeamCreateModal } from '../../../components/Team/TeamCreateModal'; import { TeamListLayout } from '../../../components/Team/TeamList'; import activityStore, { ActivityModel } from '../../../models/Activity'; -import { isServer } from '../../../models/Base'; import { i18n } from '../../../models/Base/Translation'; -import sessionStore from '../../../models/User/Session'; +import sessionStore, { isServer } from '../../../models/User/Session'; import { convertDatetime } from '../../../utils/time'; const { t } = i18n; diff --git a/pages/activity/[name]/manage/questionnaire.tsx b/pages/activity/[name]/manage/questionnaire.tsx index 6c27cf61..cfb9c16a 100644 --- a/pages/activity/[name]/manage/questionnaire.tsx +++ b/pages/activity/[name]/manage/questionnaire.tsx @@ -10,8 +10,8 @@ import { QuestionnaireCreate } from '../../../../components/Activity/Questionnai import { QuestionnaireForm } from '../../../../components/Activity/QuestionnairePreview'; import { QuestionnaireTable } from '../../../../components/Activity/QuestionnaireTable'; import activityStore from '../../../../models/Activity'; -import { isServer } from '../../../../models/Base'; import { i18n } from '../../../../models/Base/Translation'; +import { isServer } from '../../../../models/User/Session'; import { sessionGuard } from '../../../api/core'; const { t } = i18n; diff --git a/pages/activity/[name]/team/[tid]/index.tsx b/pages/activity/[name]/team/[tid]/index.tsx index 1f18d4a7..6ecfa4c1 100644 --- a/pages/activity/[name]/team/[tid]/index.tsx +++ b/pages/activity/[name]/team/[tid]/index.tsx @@ -5,6 +5,7 @@ import { TeamWork, } from '@kaiyuanshe/openhackathon-service'; import { Icon } from 'idea-react'; +import { HTTPError } from 'koajax'; import { computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import { observePropsState } from 'mobx-react-helper'; @@ -21,9 +22,8 @@ import { JoinTeamModal } from '../../../../../components/Team/JoinTeamModal'; import { TeamMemberListLayout } from '../../../../../components/Team/TeamMemberList'; import { TeamWorkList } from '../../../../../components/Team/TeamWorkList'; import activityStore, { ActivityModel } from '../../../../../models/Activity'; -import { ErrorBaseData, isServer } from '../../../../../models/Base'; import { i18n } from '../../../../../models/Base/Translation'; -import sessionStore from '../../../../../models/User/Session'; +import sessionStore, { isServer } from '../../../../../models/User/Session'; const { t } = i18n; @@ -132,7 +132,7 @@ export default class TeamPage extends PureComponent { : undefined; this.teamMemberRole = this.currentUserInThisTeam?.role || ''; } catch (error: any) { - const { status } = error as ErrorBaseData; + const { status } = (error as HTTPError).response; if (status !== 404) this.teamMemberRole = ''; } diff --git a/pages/connect/[provider]/redirect.tsx b/pages/connect/[provider]/redirect.tsx deleted file mode 100644 index 9a24d5bb..00000000 --- a/pages/connect/[provider]/redirect.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { compose, errorLogger } from 'next-ssr-middleware'; -import { FC } from 'react'; -import { buildURLData } from 'web-utility'; - -import { SessionUser, strapiClient } from '../../../models/User/Session'; - -export const getServerSideProps = compose<{ provider: string }>( - errorLogger, - async ({ params, query, res }) => { - const { body } = await strapiClient.get<{ jwt: string; user: SessionUser }>( - `auth/${params!.provider}/callback?${buildURLData(query)}`, - ); - res.setHeader('Set-Cookie', `token=${body!.jwt}; Path=/`); - - return { props: {}, redirect: { statusCode: 302, destination: '/' } }; - }, -); - -const ProviderRedirection: FC = () => <>; - -export default ProviderRedirection; diff --git a/pages/index.tsx b/pages/index.tsx index 964ba45d..d8138bf5 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,6 +1,6 @@ -import type { InferGetServerSidePropsType } from 'next'; +import { Hackathon, UserRank } from '@kaiyuanshe/openhackathon-service'; import { cache, compose, errorLogger, translator } from 'next-ssr-middleware'; -import { Fragment } from 'react'; +import { FC, Fragment } from 'react'; import { Button, Carousel, Col, Container, Image, Row } from 'react-bootstrap'; import { ActivityListLayout } from '../components/Activity/ActivityList'; @@ -23,14 +23,14 @@ export const getServerSideProps = compose( new UserModel().getUserTopList(), ]); - return { props: { activities, topUsers } }; + return { props: JSON.parse(JSON.stringify({ activities, topUsers })) }; }, ); -const HomePage = ({ +const HomePage: FC<{ activities: Hackathon[]; topUsers: UserRank[] }> = ({ activities, topUsers, -}: InferGetServerSidePropsType) => ( +}) => ( <> diff --git a/pages/user/[id].tsx b/pages/user/[id].tsx index f5c2c791..0999789d 100644 --- a/pages/user/[id].tsx +++ b/pages/user/[id].tsx @@ -25,9 +25,8 @@ export const getServerSideProps = compose<{ id?: string }, User>( cache(), errorLogger, translator(i18n), - async ({ params: { id = '' } = {} }) => ({ - props: await userStore.getOne(id), - }), + async ({ params: { id = '' } = {} }) => + JSON.parse(JSON.stringify({ props: await userStore.getOne(id) })), ); const UserDetailPage: FC = ({ id, name, avatar }) => (