diff options
author | crupest <crupest@outlook.com> | 2020-11-16 16:30:32 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2020-11-16 16:30:32 +0800 |
commit | e7854c1ed8facc2955ef9ad96f0bb2513041bba9 (patch) | |
tree | 81ff02d3efa5704f7263170385caf60753ec210e /FrontEnd/src | |
parent | e0785b385138057a23ffd1703a7265c371aef45d (diff) | |
download | timeline-e7854c1ed8facc2955ef9ad96f0bb2513041bba9.tar.gz timeline-e7854c1ed8facc2955ef9ad96f0bb2513041bba9.tar.bz2 timeline-e7854c1ed8facc2955ef9ad96f0bb2513041bba9.zip |
...
Diffstat (limited to 'FrontEnd/src')
-rw-r--r-- | FrontEnd/src/app/App.tsx | 2 | ||||
-rw-r--r-- | FrontEnd/src/app/http/user.ts | 46 | ||||
-rw-r--r-- | FrontEnd/src/app/services/timeline.ts | 22 | ||||
-rw-r--r-- | FrontEnd/src/app/services/user.ts | 57 | ||||
-rw-r--r-- | FrontEnd/src/app/views/admin/Admin.tsx | 4 | ||||
-rw-r--r-- | FrontEnd/src/app/views/admin/UserAdmin.tsx | 4 | ||||
-rw-r--r-- | FrontEnd/src/app/views/common/AppBar.tsx | 4 | ||||
-rw-r--r-- | FrontEnd/src/app/views/home/BoardWithUser.tsx | 4 |
8 files changed, 101 insertions, 42 deletions
diff --git a/FrontEnd/src/app/App.tsx b/FrontEnd/src/app/App.tsx index 01b1883b..6cdf2434 100644 --- a/FrontEnd/src/app/App.tsx +++ b/FrontEnd/src/app/App.tsx @@ -58,7 +58,7 @@ const App: React.FC = () => { <Route path="/users/:username"> <User /> </Route> - {user && user.administrator && ( + {user && user.hasAdministrationPermission && ( <Route path="/admin"> <LazyAdmin user={user} /> </Route> diff --git a/FrontEnd/src/app/http/user.ts b/FrontEnd/src/app/http/user.ts index a0a02cce..9ba6508f 100644 --- a/FrontEnd/src/app/http/user.ts +++ b/FrontEnd/src/app/http/user.ts @@ -12,10 +12,18 @@ import { convertToNotModified, } from "./common"; +export const kUserPermissionList = [ + "UserManagement", + "AllTimelineManagement", + "HighlightTimelineManagement", +] as const; + +export type UserPermission = typeof kUserPermissionList[number]; + export interface HttpUser { uniqueId: string; username: string; - administrator: boolean; + permissions: UserPermission[]; nickname: string; } @@ -54,6 +62,16 @@ export interface IHttpUserClient { ): Promise<BlobWithEtag | NotModified>; putAvatar(username: string, data: Blob, token: string): Promise<void>; changePassword(req: HttpChangePasswordRequest, token: string): Promise<void>; + putUserPermission( + username: string, + permission: UserPermission, + token: string + ): Promise<void>; + deleteUserPermission( + username: string, + permission: UserPermission, + token: string + ): Promise<void>; } export class HttpUserClient implements IHttpUserClient { @@ -119,6 +137,32 @@ export class HttpUserClient implements IHttpUserClient { .catch(convertToNetworkError) .then(); } + + putUserPermission( + username: string, + permission: UserPermission, + token: string + ): Promise<void> { + return axios + .put( + `${apiBaseUrl}/users/${username}/permissions/${permission}?token=${token}` + ) + .catch(convertToNetworkError) + .then(); + } + + deleteUserPermission( + username: string, + permission: UserPermission, + token: string + ): Promise<void> { + return axios + .delete( + `${apiBaseUrl}/users/${username}/permissions/${permission}?token=${token}` + ) + .catch(convertToNetworkError) + .then(); + } } let client: IHttpUserClient = new HttpUserClient(); diff --git a/FrontEnd/src/app/services/timeline.ts b/FrontEnd/src/app/services/timeline.ts index 2cbbffab..c58516fc 100644 --- a/FrontEnd/src/app/services/timeline.ts +++ b/FrontEnd/src/app/services/timeline.ts @@ -29,11 +29,11 @@ export type { TimelineVisibility } from "@/http/timeline"; import { dataStorage, throwIfNotNetworkError, BlobOrStatus } from "./common"; import { DataHub, WithSyncStatus } from "./DataHub"; import { - UserAuthInfo, checkLogin, userService, userInfoService, User, + AuthUser, } from "./user"; export type TimelineInfo = HttpTimelineInfo; @@ -608,10 +608,11 @@ export class TimelineService { } hasReadPermission( - user: UserAuthInfo | null | undefined, + user: AuthUser | null | undefined, timeline: TimelineInfo ): boolean { - if (user != null && user.administrator) return true; + if (user != null && user.hasAllTimelineAdministrationPermission) + return true; const { visibility } = timeline; if (visibility === "Public") { @@ -631,10 +632,11 @@ export class TimelineService { } hasPostPermission( - user: UserAuthInfo | null | undefined, + user: AuthUser | null | undefined, timeline: TimelineInfo ): boolean { - if (user != null && user.administrator) return true; + if (user != null && user.hasAllTimelineAdministrationPermission) + return true; return ( user != null && @@ -644,20 +646,22 @@ export class TimelineService { } hasManagePermission( - user: UserAuthInfo | null | undefined, + user: AuthUser | null | undefined, timeline: TimelineInfo ): boolean { - if (user != null && user.administrator) return true; + if (user != null && user.hasAllTimelineAdministrationPermission) + return true; return user != null && user.username == timeline.owner.username; } hasModifyPostPermission( - user: UserAuthInfo | null | undefined, + user: AuthUser | null | undefined, timeline: TimelineInfo, post: TimelinePostInfo ): boolean { - if (user != null && user.administrator) return true; + if (user != null && user.hasAllTimelineAdministrationPermission) + return true; return ( user != null && diff --git a/FrontEnd/src/app/services/user.ts b/FrontEnd/src/app/services/user.ts index cd6d1c15..0166bce0 100644 --- a/FrontEnd/src/app/services/user.ts +++ b/FrontEnd/src/app/services/user.ts @@ -14,6 +14,7 @@ import { getHttpUserClient, HttpUserNotExistError, HttpUser, + UserPermission, } from "@/http/user"; import { dataStorage, throwIfNotNetworkError } from "./common"; @@ -22,13 +23,26 @@ import { pushAlert } from "./alert"; export type User = HttpUser; -export interface UserAuthInfo { +export class AuthUser implements User { + constructor(user: User, public token: string) { + this.uniqueId = user.uniqueId; + this.username = user.username; + this.permissions = user.permissions; + this.nickname = user.nickname; + } + + uniqueId: string; username: string; - administrator: boolean; -} + permissions: UserPermission[]; + nickname: string; -export interface UserWithToken extends User { - token: string; + get hasAdministrationPermission(): boolean { + return this.permissions.length !== 0; + } + + get hasAllTimelineAdministrationPermission(): boolean { + return this.permissions.includes("AllTimelineManagement"); + } } export interface LoginCredentials { @@ -43,24 +57,24 @@ export class BadCredentialError { const USER_STORAGE_KEY = "currentuser"; export class UserService { - private userSubject = new BehaviorSubject<UserWithToken | null | undefined>( + private userSubject = new BehaviorSubject<AuthUser | null | undefined>( undefined ); - get user$(): Observable<UserWithToken | null | undefined> { + get user$(): Observable<AuthUser | null | undefined> { return this.userSubject; } - get currentUser(): UserWithToken | null | undefined { + get currentUser(): AuthUser | null | undefined { return this.userSubject.value; } - async checkLoginState(): Promise<UserWithToken | null> { + async checkLoginState(): Promise<AuthUser | null> { if (this.currentUser !== undefined) { console.warn("Already checked user. Can't check twice."); } - const savedUser = await dataStorage.getItem<UserWithToken | null>( + const savedUser = await dataStorage.getItem<AuthUser | null>( USER_STORAGE_KEY ); @@ -74,8 +88,8 @@ export class UserService { const savedToken = savedUser.token; try { const res = await getHttpTokenClient().verify({ token: savedToken }); - const user: UserWithToken = { ...res.user, token: savedToken }; - await dataStorage.setItem<UserWithToken>(USER_STORAGE_KEY, user); + const user = new AuthUser(res.user, savedToken); + await dataStorage.setItem<AuthUser>(USER_STORAGE_KEY, user); this.userSubject.next(user); pushAlert({ type: "success", @@ -116,12 +130,9 @@ export class UserService { ...credentials, expire: 30, }); - const user: UserWithToken = { - ...res.user, - token: res.token, - }; + const user = new AuthUser(res.user, res.token); if (rememberMe) { - await dataStorage.setItem<UserWithToken>(USER_STORAGE_KEY, user); + await dataStorage.setItem<AuthUser>(USER_STORAGE_KEY, user); } this.userSubject.next(user); } catch (e) { @@ -169,8 +180,8 @@ export class UserService { export const userService = new UserService(); -export function useRawUser(): UserWithToken | null | undefined { - const [user, setUser] = useState<UserWithToken | null | undefined>( +export function useRawUser(): AuthUser | null | undefined { + const [user, setUser] = useState<AuthUser | null | undefined>( userService.currentUser ); useEffect(() => { @@ -182,8 +193,8 @@ export function useRawUser(): UserWithToken | null | undefined { return user; } -export function useUser(): UserWithToken | null { - const [user, setUser] = useState<UserWithToken | null>(() => { +export function useUser(): AuthUser | null { + const [user, setUser] = useState<AuthUser | null>(() => { const initUser = userService.currentUser; if (initUser === undefined) { throw new UiLogicError( @@ -208,7 +219,7 @@ export function useUser(): UserWithToken | null { return user; } -export function useUserLoggedIn(): UserWithToken { +export function useUserLoggedIn(): AuthUser { const user = useUser(); if (user == null) { throw new UiLogicError("You assert user has logged in but actually not."); @@ -216,7 +227,7 @@ export function useUserLoggedIn(): UserWithToken { return user; } -export function checkLogin(): UserWithToken { +export function checkLogin(): AuthUser { const user = userService.currentUser; if (user == null) { throw new UiLogicError("You must login to perform the operation."); diff --git a/FrontEnd/src/app/views/admin/Admin.tsx b/FrontEnd/src/app/views/admin/Admin.tsx index 9c0250e7..a64a9bc0 100644 --- a/FrontEnd/src/app/views/admin/Admin.tsx +++ b/FrontEnd/src/app/views/admin/Admin.tsx @@ -8,12 +8,12 @@ import { } from "react-router"; import { Nav } from "react-bootstrap"; -import { UserWithToken } from "@/services/user"; +import { AuthUser } from "@/services/user"; import UserAdmin from "./UserAdmin"; interface AdminProps { - user: UserWithToken; + user: AuthUser; } const Admin: React.FC<AdminProps> = (props) => { diff --git a/FrontEnd/src/app/views/admin/UserAdmin.tsx b/FrontEnd/src/app/views/admin/UserAdmin.tsx index 0f5f8796..4ad9ed09 100644 --- a/FrontEnd/src/app/views/admin/UserAdmin.tsx +++ b/FrontEnd/src/app/views/admin/UserAdmin.tsx @@ -10,7 +10,7 @@ import { } from "react-bootstrap"; import OperationDialog from "../common/OperationDialog"; -import { User, UserWithToken } from "@/services/user"; +import { User, AuthUser } from "@/services/user"; const apiBaseUrl = "/api"; @@ -285,7 +285,7 @@ const UserChangePermissionDialog: React.FC<UserChangePermissionDialogProps> = ( }; interface UserAdminProps { - user: UserWithToken; + user: AuthUser; } const UserAdmin: React.FC<UserAdminProps> = (props) => { diff --git a/FrontEnd/src/app/views/common/AppBar.tsx b/FrontEnd/src/app/views/common/AppBar.tsx index 8f35b482..11d3de04 100644 --- a/FrontEnd/src/app/views/common/AppBar.tsx +++ b/FrontEnd/src/app/views/common/AppBar.tsx @@ -15,7 +15,7 @@ const AppBar: React.FC = (_) => { const { t } = useTranslation(); - const isAdministrator = user && user.administrator; + const hasAdministrationPermission = user && user.hasAdministrationPermission; const [expand, setExpand] = React.useState<boolean>(false); const collapse = (): void => setExpand(false); @@ -56,7 +56,7 @@ const AppBar: React.FC = (_) => { {t("nav.about")} </NavLink> - {isAdministrator && ( + {hasAdministrationPermission && ( <NavLink to="/admin" className="nav-link" diff --git a/FrontEnd/src/app/views/home/BoardWithUser.tsx b/FrontEnd/src/app/views/home/BoardWithUser.tsx index fbe1dd89..bbef835a 100644 --- a/FrontEnd/src/app/views/home/BoardWithUser.tsx +++ b/FrontEnd/src/app/views/home/BoardWithUser.tsx @@ -2,14 +2,14 @@ import React from "react"; import { Row, Col } from "react-bootstrap"; import { useTranslation } from "react-i18next"; -import { UserWithToken } from "@/services/user"; +import { AuthUser } from "@/services/user"; import { TimelineInfo } from "@/services/timeline"; import { getHttpTimelineClient } from "@/http/timeline"; import TimelineBoard from "./TimelineBoard"; import OfflineBoard from "./OfflineBoard"; -const BoardWithUser: React.FC<{ user: UserWithToken }> = ({ user }) => { +const BoardWithUser: React.FC<{ user: AuthUser }> = ({ user }) => { const { t } = useTranslation(); const [ownTimelines, setOwnTimelines] = React.useState< |