diff options
author | crupest <crupest@outlook.com> | 2021-01-09 01:07:35 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2021-01-09 01:07:35 +0800 |
commit | 8edf6d566cecd94d251a4e29ae8c35b77f88d6db (patch) | |
tree | 0e4371f4a40fabaf5c87baca491d2631665cd2ae /FrontEnd/src | |
parent | 777efa6e0405f4e871de4da21b939e30ed07f754 (diff) | |
download | timeline-8edf6d566cecd94d251a4e29ae8c35b77f88d6db.tar.gz timeline-8edf6d566cecd94d251a4e29ae8c35b77f88d6db.tar.bz2 timeline-8edf6d566cecd94d251a4e29ae8c35b77f88d6db.zip |
...
Diffstat (limited to 'FrontEnd/src')
-rw-r--r-- | FrontEnd/src/app/http/bookmark.ts | 27 | ||||
-rw-r--r-- | FrontEnd/src/app/http/common.ts | 19 | ||||
-rw-r--r-- | FrontEnd/src/app/http/highlight.ts | 21 | ||||
-rw-r--r-- | FrontEnd/src/app/http/timeline.ts | 122 | ||||
-rw-r--r-- | FrontEnd/src/app/http/token.ts | 2 | ||||
-rw-r--r-- | FrontEnd/src/app/http/user.ts | 59 | ||||
-rw-r--r-- | FrontEnd/src/app/services/timeline.ts | 62 | ||||
-rw-r--r-- | FrontEnd/src/app/services/user.ts | 30 | ||||
-rw-r--r-- | FrontEnd/src/app/views/admin/UserAdmin.tsx | 31 | ||||
-rw-r--r-- | FrontEnd/src/app/views/home/BoardWithUser.tsx | 12 | ||||
-rw-r--r-- | FrontEnd/src/app/views/timeline-common/TimelineCardTemplate.tsx | 10 | ||||
-rw-r--r-- | FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx | 62 |
12 files changed, 210 insertions, 247 deletions
diff --git a/FrontEnd/src/app/http/bookmark.ts b/FrontEnd/src/app/http/bookmark.ts index 68de4d73..15e55d98 100644 --- a/FrontEnd/src/app/http/bookmark.ts +++ b/FrontEnd/src/app/http/bookmark.ts @@ -1,6 +1,5 @@ -import axios from "axios"; - import { + axios, apiBaseUrl, convertToNetworkError, extractResponseData, @@ -18,38 +17,38 @@ export interface HttpHighlightMoveRequest { } export interface IHttpBookmarkClient { - list(token: string): Promise<HttpTimelineInfo[]>; - put(timeline: string, token: string): Promise<void>; - delete(timeline: string, token: string): Promise<void>; - move(req: HttpHighlightMoveRequest, token: string): Promise<void>; + list(): Promise<HttpTimelineInfo[]>; + put(timeline: string): Promise<void>; + delete(timeline: string): Promise<void>; + move(req: HttpHighlightMoveRequest): Promise<void>; } export class HttpHighlightClient implements IHttpBookmarkClient { - list(token: string): Promise<HttpTimelineInfo[]> { + list(): Promise<HttpTimelineInfo[]> { return axios - .get<RawHttpTimelineInfo[]>(`${apiBaseUrl}/bookmarks?token=${token}`) + .get<RawHttpTimelineInfo[]>(`${apiBaseUrl}/bookmarks`) .then(extractResponseData) .then((list) => list.map(processRawTimelineInfo)) .catch(convertToNetworkError); } - put(timeline: string, token: string): Promise<void> { + put(timeline: string): Promise<void> { return axios - .put(`${apiBaseUrl}/bookmarks/${timeline}?token=${token}`) + .put(`${apiBaseUrl}/bookmarks/${timeline}`) .catch(convertToNetworkError) .then(); } - delete(timeline: string, token: string): Promise<void> { + delete(timeline: string): Promise<void> { return axios - .delete(`${apiBaseUrl}/bookmarks/${timeline}?token=${token}`) + .delete(`${apiBaseUrl}/bookmarks/${timeline}`) .catch(convertToNetworkError) .then(); } - move(req: HttpHighlightMoveRequest, token: string): Promise<void> { + move(req: HttpHighlightMoveRequest): Promise<void> { return axios - .post(`${apiBaseUrl}/bookmarkop/move?token=${token}`, req) + .post(`${apiBaseUrl}/bookmarkop/move`, req) .catch(convertToNetworkError) .then(); } diff --git a/FrontEnd/src/app/http/common.ts b/FrontEnd/src/app/http/common.ts index 54203d1a..2dd3677b 100644 --- a/FrontEnd/src/app/http/common.ts +++ b/FrontEnd/src/app/http/common.ts @@ -1,7 +1,24 @@ -import { AxiosError, AxiosResponse } from "axios"; +import rawAxios, { AxiosError, AxiosResponse } from "axios"; +import { BehaviorSubject } from "rxjs"; export const apiBaseUrl = "/api"; +export const axios = rawAxios.create(); + +export const tokenSubject: BehaviorSubject<string | null> = new BehaviorSubject< + string | null +>(null); + +tokenSubject.subscribe((token) => { + if (token == null) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + delete axios.defaults.headers.common["Authorization"]; + } else { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; + } +}); + export function base64(blob: Blob): Promise<string> { return new Promise<string>((resolve) => { const reader = new FileReader(); diff --git a/FrontEnd/src/app/http/highlight.ts b/FrontEnd/src/app/http/highlight.ts index 1f226c19..851d52ce 100644 --- a/FrontEnd/src/app/http/highlight.ts +++ b/FrontEnd/src/app/http/highlight.ts @@ -1,6 +1,5 @@ -import axios from "axios"; - import { + axios, apiBaseUrl, convertToNetworkError, extractResponseData, @@ -19,9 +18,9 @@ export interface HttpHighlightMoveRequest { export interface IHttpHighlightClient { list(): Promise<HttpTimelineInfo[]>; - put(timeline: string, token: string): Promise<void>; - delete(timeline: string, token: string): Promise<void>; - move(req: HttpHighlightMoveRequest, token: string): Promise<void>; + put(timeline: string): Promise<void>; + delete(timeline: string): Promise<void>; + move(req: HttpHighlightMoveRequest): Promise<void>; } export class HttpHighlightClient implements IHttpHighlightClient { @@ -33,23 +32,23 @@ export class HttpHighlightClient implements IHttpHighlightClient { .catch(convertToNetworkError); } - put(timeline: string, token: string): Promise<void> { + put(timeline: string): Promise<void> { return axios - .put(`${apiBaseUrl}/highlights/${timeline}?token=${token}`) + .put(`${apiBaseUrl}/highlights/${timeline}`) .catch(convertToNetworkError) .then(); } - delete(timeline: string, token: string): Promise<void> { + delete(timeline: string): Promise<void> { return axios - .delete(`${apiBaseUrl}/highlights/${timeline}?token=${token}`) + .delete(`${apiBaseUrl}/highlights/${timeline}`) .catch(convertToNetworkError) .then(); } - move(req: HttpHighlightMoveRequest, token: string): Promise<void> { + move(req: HttpHighlightMoveRequest): Promise<void> { return axios - .post(`${apiBaseUrl}/highlightop/move?token=${token}`, req) + .post(`${apiBaseUrl}/highlightop/move`, req) .catch(convertToNetworkError) .then(); } diff --git a/FrontEnd/src/app/http/timeline.ts b/FrontEnd/src/app/http/timeline.ts index 6be0a183..ed02a65b 100644 --- a/FrontEnd/src/app/http/timeline.ts +++ b/FrontEnd/src/app/http/timeline.ts @@ -1,8 +1,9 @@ -import axios, { AxiosError } from "axios"; +import { AxiosError } from "axios"; import { updateQueryString, applyQueryParameters } from "../utilities/url"; import { + axios, apiBaseUrl, extractResponseData, convertToNetworkError, @@ -30,6 +31,8 @@ export interface HttpTimelineInfo { visibility: TimelineVisibility; lastModified: Date; members: HttpUser[]; + isHighlight: boolean; + isBookmark: boolean; } export interface HttpTimelineListQuery { @@ -130,6 +133,8 @@ export interface RawHttpTimelineInfo { visibility: TimelineVisibility; lastModified: string; members: HttpUser[]; + isHighlight: boolean; + isBookmark: boolean; } interface RawTimelinePostTextContent { @@ -229,33 +234,17 @@ export interface IHttpTimelineClient { ifModifiedSince: Date; } ): Promise<HttpTimelineInfo | NotModified>; - postTimeline( - req: HttpTimelinePostRequest, - token: string - ): Promise<HttpTimelineInfo>; + postTimeline(req: HttpTimelinePostRequest): Promise<HttpTimelineInfo>; patchTimeline( timelineName: string, - req: HttpTimelinePatchRequest, - token: string + req: HttpTimelinePatchRequest ): Promise<HttpTimelineInfo>; - deleteTimeline(timelineName: string, token: string): Promise<void>; - memberPut( - timelineName: string, - username: string, - token: string - ): Promise<void>; - memberDelete( - timelineName: string, - username: string, - token: string - ): Promise<void>; + deleteTimeline(timelineName: string): Promise<void>; + memberPut(timelineName: string, username: string): Promise<void>; + memberDelete(timelineName: string, username: string): Promise<void>; + listPost(timelineName: string): Promise<HttpTimelinePostInfo[]>; listPost( timelineName: string, - token?: string - ): Promise<HttpTimelinePostInfo[]>; - listPost( - timelineName: string, - token: string | undefined, query: { modifiedSince?: Date; includeDeleted?: false; @@ -263,33 +252,22 @@ export interface IHttpTimelineClient { ): Promise<HttpTimelinePostInfo[]>; listPost( timelineName: string, - token: string | undefined, query: { modifiedSince?: Date; includeDeleted: true; } ): Promise<HttpTimelineGenericPostInfo[]>; + getPostData(timelineName: string, postId: number): Promise<BlobWithEtag>; getPostData( timelineName: string, postId: number, - token?: string - ): Promise<BlobWithEtag>; - getPostData( - timelineName: string, - postId: number, - token: string | undefined, etag: string ): Promise<BlobWithEtag | NotModified>; postPost( timelineName: string, - req: HttpTimelinePostPostRequest, - token: string + req: HttpTimelinePostPostRequest ): Promise<HttpTimelinePostInfo>; - deletePost( - timelineName: string, - postId: number, - token: string - ): Promise<void>; + deletePost(timelineName: string, postId: number): Promise<void>; } export class HttpTimelineClient implements IHttpTimelineClient { @@ -339,12 +317,9 @@ export class HttpTimelineClient implements IHttpTimelineClient { .catch(convertToNetworkError); } - postTimeline( - req: HttpTimelinePostRequest, - token: string - ): Promise<HttpTimelineInfo> { + postTimeline(req: HttpTimelinePostRequest): Promise<HttpTimelineInfo> { return axios - .post<RawHttpTimelineInfo>(`${apiBaseUrl}/timelines?token=${token}`, req) + .post<RawHttpTimelineInfo>(`${apiBaseUrl}/timelines`, req) .then(extractResponseData) .then(processRawTimelineInfo) .catch(convertToIfErrorCodeIs(11040101, HttpTimelineNameConflictError)) @@ -353,12 +328,11 @@ export class HttpTimelineClient implements IHttpTimelineClient { patchTimeline( timelineName: string, - req: HttpTimelinePatchRequest, - token: string + req: HttpTimelinePatchRequest ): Promise<HttpTimelineInfo> { return axios .patch<RawHttpTimelineInfo>( - `${apiBaseUrl}/timelines/${timelineName}?token=${token}`, + `${apiBaseUrl}/timelines/${timelineName}`, req ) .then(extractResponseData) @@ -366,46 +340,30 @@ export class HttpTimelineClient implements IHttpTimelineClient { .catch(convertToNetworkError); } - deleteTimeline(timelineName: string, token: string): Promise<void> { + deleteTimeline(timelineName: string): Promise<void> { return axios - .delete(`${apiBaseUrl}/timelines/${timelineName}?token=${token}`) + .delete(`${apiBaseUrl}/timelines/${timelineName}`) .catch(convertToNetworkError) .then(); } - memberPut( - timelineName: string, - username: string, - token: string - ): Promise<void> { + memberPut(timelineName: string, username: string): Promise<void> { return axios - .put( - `${apiBaseUrl}/timelines/${timelineName}/members/${username}?token=${token}` - ) + .put(`${apiBaseUrl}/timelines/${timelineName}/members/${username}`) .catch(convertToNetworkError) .then(); } - memberDelete( - timelineName: string, - username: string, - token: string - ): Promise<void> { + memberDelete(timelineName: string, username: string): Promise<void> { return axios - .delete( - `${apiBaseUrl}/timelines/${timelineName}/members/${username}?token=${token}` - ) + .delete(`${apiBaseUrl}/timelines/${timelineName}/members/${username}`) .catch(convertToNetworkError) .then(); } + listPost(timelineName: string): Promise<HttpTimelinePostInfo[]>; listPost( timelineName: string, - token?: string - ): Promise<HttpTimelinePostInfo[]>; - listPost( - timelineName: string, - token: string | undefined, query: { modifiedSince?: Date; includeDeleted?: false; @@ -413,7 +371,6 @@ export class HttpTimelineClient implements IHttpTimelineClient { ): Promise<HttpTimelinePostInfo[]>; listPost( timelineName: string, - token: string | undefined, query: { modifiedSince?: Date; includeDeleted: true; @@ -421,14 +378,12 @@ export class HttpTimelineClient implements IHttpTimelineClient { ): Promise<HttpTimelineGenericPostInfo[]>; listPost( timelineName: string, - token?: string, query?: { modifiedSince?: Date; includeDeleted?: boolean; } ): Promise<HttpTimelineGenericPostInfo[]> { let url = `${apiBaseUrl}/timelines/${timelineName}/posts`; - url = updateQueryString("token", token, url); if (query != null) { if (query.modifiedSince != null) { url = updateQueryString( @@ -457,15 +412,10 @@ export class HttpTimelineClient implements IHttpTimelineClient { ); } + getPostData(timelineName: string, postId: number): Promise<BlobWithEtag>; getPostData( timelineName: string, postId: number, - token: string - ): Promise<BlobWithEtag>; - getPostData( - timelineName: string, - postId: number, - token?: string, etag?: string ): Promise<BlobWithEtag | NotModified> { const headers = @@ -475,8 +425,7 @@ export class HttpTimelineClient implements IHttpTimelineClient { } : undefined; - let url = `${apiBaseUrl}/timelines/${timelineName}/posts/${postId}/data`; - url = updateQueryString("token", token, url); + const url = `${apiBaseUrl}/timelines/${timelineName}/posts/${postId}/data`; return axios .get(url, { @@ -491,8 +440,7 @@ export class HttpTimelineClient implements IHttpTimelineClient { async postPost( timelineName: string, - req: HttpTimelinePostPostRequest, - token: string + req: HttpTimelinePostPostRequest ): Promise<HttpTimelinePostInfo> { let content: RawTimelinePostPostRequestContent; if (req.content.type === "image") { @@ -512,7 +460,7 @@ export class HttpTimelineClient implements IHttpTimelineClient { } return await axios .post<RawTimelinePostInfo>( - `${apiBaseUrl}/timelines/${timelineName}/posts?token=${token}`, + `${apiBaseUrl}/timelines/${timelineName}/posts`, rawReq ) .then(extractResponseData) @@ -520,15 +468,9 @@ export class HttpTimelineClient implements IHttpTimelineClient { .then((rawPost) => processRawTimelinePostInfo(rawPost)); } - deletePost( - timelineName: string, - postId: number, - token: string - ): Promise<void> { + deletePost(timelineName: string, postId: number): Promise<void> { return axios - .delete( - `${apiBaseUrl}/timelines/${timelineName}/posts/${postId}?token=${token}` - ) + .delete(`${apiBaseUrl}/timelines/${timelineName}/posts/${postId}`) .catch(convertToNetworkError) .then(); } diff --git a/FrontEnd/src/app/http/token.ts b/FrontEnd/src/app/http/token.ts index ae0cf3f6..c0644515 100644 --- a/FrontEnd/src/app/http/token.ts +++ b/FrontEnd/src/app/http/token.ts @@ -1,3 +1,5 @@ +// Don't use axios in common because it will contains +// authorization header, which shouldn't be used in token apis. import axios, { AxiosError } from "axios"; import { diff --git a/FrontEnd/src/app/http/user.ts b/FrontEnd/src/app/http/user.ts index 929956d0..8345880e 100644 --- a/FrontEnd/src/app/http/user.ts +++ b/FrontEnd/src/app/http/user.ts @@ -1,6 +1,7 @@ -import axios, { AxiosError } from "axios"; +import { AxiosError } from "axios"; import { + axios, apiBaseUrl, convertToNetworkError, extractResponseData, @@ -62,28 +63,22 @@ export class HttpChangePasswordBadCredentialError extends Error { export interface IHttpUserClient { list(): Promise<HttpUser[]>; get(username: string): Promise<HttpUser>; - patch( - username: string, - req: HttpUserPatchRequest, - token: string - ): Promise<HttpUser>; - delete(username: string, token: string): Promise<void>; + patch(username: string, req: HttpUserPatchRequest): Promise<HttpUser>; + delete(username: string): Promise<void>; getAvatar(username: string): Promise<BlobWithEtag>; getAvatar( username: string, etag: string ): Promise<BlobWithEtag | NotModified>; - putAvatar(username: string, data: Blob, token: string): Promise<void>; - changePassword(req: HttpChangePasswordRequest, token: string): Promise<void>; + putAvatar(username: string, data: Blob): Promise<void>; + changePassword(req: HttpChangePasswordRequest): Promise<void>; putUserPermission( username: string, - permission: UserPermission, - token: string + permission: UserPermission ): Promise<void>; deleteUserPermission( username: string, - permission: UserPermission, - token: string + permission: UserPermission ): Promise<void>; createUser(req: HttpCreateUserRequest, token: string): Promise<HttpUser>; @@ -105,20 +100,16 @@ export class HttpUserClient implements IHttpUserClient { .catch(convertToNetworkError); } - patch( - username: string, - req: HttpUserPatchRequest, - token: string - ): Promise<HttpUser> { + patch(username: string, req: HttpUserPatchRequest): Promise<HttpUser> { return axios - .patch<HttpUser>(`${apiBaseUrl}/users/${username}?token=${token}`, req) + .patch<HttpUser>(`${apiBaseUrl}/users/${username}`, req) .then(extractResponseData) .catch(convertToNetworkError); } - delete(username: string, token: string): Promise<void> { + delete(username: string): Promise<void> { return axios - .delete(`${apiBaseUrl}/users/${username}?token=${token}`) + .delete(`${apiBaseUrl}/users/${username}`) .catch(convertToNetworkError) .then(); } @@ -146,9 +137,9 @@ export class HttpUserClient implements IHttpUserClient { .catch(convertToNetworkError); } - putAvatar(username: string, data: Blob, token: string): Promise<void> { + putAvatar(username: string, data: Blob): Promise<void> { return axios - .put(`${apiBaseUrl}/users/${username}/avatar?token=${token}`, data, { + .put(`${apiBaseUrl}/users/${username}/avatar`, data, { headers: { "Content-Type": data.type, }, @@ -157,9 +148,9 @@ export class HttpUserClient implements IHttpUserClient { .then(); } - changePassword(req: HttpChangePasswordRequest, token: string): Promise<void> { + changePassword(req: HttpChangePasswordRequest): Promise<void> { return axios - .post(`${apiBaseUrl}/userop/changepassword?token=${token}`, req) + .post(`${apiBaseUrl}/userop/changepassword`, req) .catch( convertToIfErrorCodeIs(11020201, HttpChangePasswordBadCredentialError) ) @@ -169,33 +160,27 @@ export class HttpUserClient implements IHttpUserClient { putUserPermission( username: string, - permission: UserPermission, - token: string + permission: UserPermission ): Promise<void> { return axios - .put( - `${apiBaseUrl}/users/${username}/permissions/${permission}?token=${token}` - ) + .put(`${apiBaseUrl}/users/${username}/permissions/${permission}`) .catch(convertToNetworkError) .then(); } deleteUserPermission( username: string, - permission: UserPermission, - token: string + permission: UserPermission ): Promise<void> { return axios - .delete( - `${apiBaseUrl}/users/${username}/permissions/${permission}?token=${token}` - ) + .delete(`${apiBaseUrl}/users/${username}/permissions/${permission}`) .catch(convertToNetworkError) .then(); } - createUser(req: HttpCreateUserRequest, token: string): Promise<HttpUser> { + createUser(req: HttpCreateUserRequest): Promise<HttpUser> { return axios - .post<HttpUser>(`${apiBaseUrl}/userop/createuser?token=${token}`, req) + .post<HttpUser>(`${apiBaseUrl}/userop/createuser`, req) .then(extractResponseData) .catch(convertToNetworkError) .then(); diff --git a/FrontEnd/src/app/services/timeline.ts b/FrontEnd/src/app/services/timeline.ts index c58516fc..3b9a9072 100644 --- a/FrontEnd/src/app/services/timeline.ts +++ b/FrontEnd/src/app/services/timeline.ts @@ -28,13 +28,7 @@ export type { TimelineVisibility } from "@/http/timeline"; import { dataStorage, throwIfNotNetworkError, BlobOrStatus } from "./common"; import { DataHub, WithSyncStatus } from "./DataHub"; -import { - checkLogin, - userService, - userInfoService, - User, - AuthUser, -} from "./user"; +import { userInfoService, User, AuthUser } from "./user"; export type TimelineInfo = HttpTimelineInfo; export type TimelineChangePropertyRequest = HttpTimelinePatchRequest; @@ -227,14 +221,10 @@ export class TimelineService { } createTimeline(timelineName: string): Observable<TimelineInfo> { - const user = checkLogin(); return from( - getHttpTimelineClient().postTimeline( - { - name: timelineName, - }, - user.token - ) + getHttpTimelineClient().postTimeline({ + name: timelineName, + }) ).pipe( convertError(HttpTimelineNameConflictError, TimelineNameConflictError) ); @@ -244,10 +234,9 @@ export class TimelineService { timelineName: string, req: TimelineChangePropertyRequest ): Observable<TimelineInfo> { - const user = checkLogin(); return from( getHttpTimelineClient() - .patchTimeline(timelineName, req, user.token) + .patchTimeline(timelineName, req) .then((timeline) => { void this.syncTimeline(timelineName); return timeline; @@ -256,17 +245,13 @@ export class TimelineService { } deleteTimeline(timelineName: string): Observable<unknown> { - const user = checkLogin(); - return from( - getHttpTimelineClient().deleteTimeline(timelineName, user.token) - ); + return from(getHttpTimelineClient().deleteTimeline(timelineName)); } addMember(timelineName: string, username: string): Observable<unknown> { - const user = checkLogin(); return from( getHttpTimelineClient() - .memberPut(timelineName, username, user.token) + .memberPut(timelineName, username) .then(() => { void this.syncTimeline(timelineName); }) @@ -274,10 +259,9 @@ export class TimelineService { } removeMember(timelineName: string, username: string): Observable<unknown> { - const user = checkLogin(); return from( getHttpTimelineClient() - .memberDelete(timelineName, username, user.token) + .memberDelete(timelineName, username) .then(() => { void this.syncTimeline(timelineName); }) @@ -344,10 +328,7 @@ export class TimelineService { try { if (lastUpdatedTime == null) { - const httpPosts = await getHttpTimelineClient().listPost( - key, - userService.currentUser?.token - ); + const httpPosts = await getHttpTimelineClient().listPost(key); userInfoService.saveUsers( uniqBy( @@ -362,14 +343,10 @@ export class TimelineService { line.next({ type: "synced", posts }); } else { - const httpPosts = await getHttpTimelineClient().listPost( - key, - userService.currentUser?.token, - { - modifiedSince: lastUpdatedTime, - includeDeleted: true, - } - ); + const httpPosts = await getHttpTimelineClient().listPost(key, { + modifiedSince: lastUpdatedTime, + includeDeleted: true, + }); const deletedIds = httpPosts .filter((p) => p.deleted) @@ -582,10 +559,9 @@ export class TimelineService { timelineName: string, request: TimelineCreatePostRequest ): Observable<unknown> { - const user = checkLogin(); return from( getHttpTimelineClient() - .postPost(timelineName, request, user.token) + .postPost(timelineName, request) .then(() => { void this.syncPosts(timelineName); }) @@ -593,10 +569,9 @@ export class TimelineService { } deletePost(timelineName: string, postId: number): Observable<unknown> { - const user = checkLogin(); return from( getHttpTimelineClient() - .deletePost(timelineName, postId, user.token) + .deletePost(timelineName, postId) .then(() => { void this.syncPosts(timelineName); }) @@ -681,7 +656,10 @@ export function validateTimelineName(name: string): boolean { export function useTimelineInfo( timelineName: string -): TimelineWithSyncStatus | undefined { +): [ + TimelineWithSyncStatus | undefined, + React.Dispatch<React.SetStateAction<TimelineWithSyncStatus | undefined>> +] { const [state, setState] = React.useState<TimelineWithSyncStatus | undefined>( undefined ); @@ -695,7 +673,7 @@ export function useTimelineInfo( subscription.unsubscribe(); }; }, [timelineName]); - return state; + return [state, setState]; } export function usePostList( diff --git a/FrontEnd/src/app/services/user.ts b/FrontEnd/src/app/services/user.ts index 7a60b474..c3343493 100644 --- a/FrontEnd/src/app/services/user.ts +++ b/FrontEnd/src/app/services/user.ts @@ -5,7 +5,12 @@ import { map, filter } from "rxjs/operators"; import { UiLogicError } from "@/common"; import { convertError } from "@/utilities/rxjs"; -import { HttpNetworkError, BlobWithEtag, NotModified } from "@/http/common"; +import { + HttpNetworkError, + BlobWithEtag, + NotModified, + tokenSubject, +} from "@/http/common"; import { getHttpTokenClient, HttpCreateTokenBadCredentialError, @@ -61,6 +66,12 @@ export class BadCredentialError { const USER_STORAGE_KEY = "currentuser"; export class UserService { + constructor() { + this.userSubject.subscribe((u) => { + tokenSubject.next(u?.token ?? null); + }); + } + private userSubject = new BehaviorSubject<AuthUser | null | undefined>( undefined ); @@ -167,13 +178,10 @@ export class UserService { throw new UiLogicError("Not login or checked now, can't log out."); } const $ = from( - getHttpUserClient().changePassword( - { - oldPassword, - newPassword, - }, - this.currentUser.token - ) + getHttpUserClient().changePassword({ + oldPassword, + newPassword, + }) ); $.subscribe(() => { void this.logout(); @@ -378,15 +386,13 @@ export class UserInfoService { } async setAvatar(username: string, blob: Blob): Promise<void> { - const user = checkLogin(); - await getHttpUserClient().putAvatar(username, blob, user.token); + await getHttpUserClient().putAvatar(username, blob); this._avatarHub.getLine(username)?.next({ data: blob, type: "synced" }); } async setNickname(username: string, nickname: string): Promise<void> { - const user = checkLogin(); return getHttpUserClient() - .patch(username, { nickname }, user.token) + .patch(username, { nickname }) .then((user) => { this.saveUser(user); }); diff --git a/FrontEnd/src/app/views/admin/UserAdmin.tsx b/FrontEnd/src/app/views/admin/UserAdmin.tsx index d66abbec..fbdfd5a3 100644 --- a/FrontEnd/src/app/views/admin/UserAdmin.tsx +++ b/FrontEnd/src/app/views/admin/UserAdmin.tsx @@ -62,7 +62,7 @@ const UsernameLabel: React.FC = (props) => { const UserDeleteDialog: React.FC< DialogProps<{ username: string }, unknown> -> = ({ open, close, token, data: { username }, onSuccess }) => { +> = ({ open, close, data: { username }, onSuccess }) => { return ( <OperationDialog open={open} @@ -74,7 +74,7 @@ const UserDeleteDialog: React.FC< 0<UsernameLabel>{username}</UsernameLabel>2 </Trans> )} - onProcess={() => getHttpUserClient().delete(username, token)} + onProcess={() => getHttpUserClient().delete(username)} onSuccessAndClose={onSuccess} /> ); @@ -87,7 +87,7 @@ const UserModifyDialog: React.FC< }, HttpUser > -> = ({ open, close, token, data: { oldUser }, onSuccess }) => { +> = ({ open, close, data: { oldUser }, onSuccess }) => { return ( <OperationDialog open={open} @@ -115,15 +115,11 @@ const UserModifyDialog: React.FC< ] as const } onProcess={([username, password, nickname]) => - getHttpUserClient().patch( - oldUser.username, - { - username: username !== oldUser.username ? username : undefined, - password: password !== "" ? password : undefined, - nickname: nickname !== oldUser.nickname ? nickname : undefined, - }, - token - ) + getHttpUserClient().patch(oldUser.username, { + username: username !== oldUser.username ? username : undefined, + password: password !== "" ? password : undefined, + nickname: nickname !== oldUser.nickname ? nickname : undefined, + }) } onSuccessAndClose={onSuccess} /> @@ -138,7 +134,7 @@ const UserPermissionModifyDialog: React.FC< }, UserPermission[] > -> = ({ open, close, token, data: { username, permissions }, onSuccess }) => { +> = ({ open, close, data: { username, permissions }, onSuccess }) => { const oldPermissionBoolList: boolean[] = kUserPermissionList.map( (permission) => permissions.includes(permission) ); @@ -168,16 +164,11 @@ const UserPermissionModifyDialog: React.FC< const permission = kUserPermissionList[index]; if (oldValue === newValue) continue; if (newValue) { - await getHttpUserClient().putUserPermission( - username, - permission, - token - ); + await getHttpUserClient().putUserPermission(username, permission); } else { await getHttpUserClient().deleteUserPermission( username, - permission, - token + permission ); } } diff --git a/FrontEnd/src/app/views/home/BoardWithUser.tsx b/FrontEnd/src/app/views/home/BoardWithUser.tsx index 8afe440b..ba22916c 100644 --- a/FrontEnd/src/app/views/home/BoardWithUser.tsx +++ b/FrontEnd/src/app/views/home/BoardWithUser.tsx @@ -20,11 +20,11 @@ const BoardWithUser: React.FC<{ user: AuthUser }> = ({ user }) => { <Col xs="12" md="6"> <TimelineBoard title={t("home.bookmarkTimeline")} - load={() => getHttpBookmarkClient().list(user.token)} + load={() => getHttpBookmarkClient().list()} editHandler={{ onDelete: (timeline) => { return getHttpBookmarkClient() - .delete(timeline, user.token) + .delete(timeline) .catch((e) => { pushAlert({ message: { @@ -39,8 +39,7 @@ const BoardWithUser: React.FC<{ user: AuthUser }> = ({ user }) => { onMove: (timeline, index, offset) => { return getHttpBookmarkClient() .move( - { timeline, newPosition: index + offset + 1 }, // +1 because backend contract: index starts at 1 - user.token + { timeline, newPosition: index + offset + 1 } // +1 because backend contract: index starts at 1 ) .catch((e) => { pushAlert({ @@ -75,7 +74,7 @@ const BoardWithUser: React.FC<{ user: AuthUser }> = ({ user }) => { ? { onDelete: (timeline) => { return getHttpHighlightClient() - .delete(timeline, user.token) + .delete(timeline) .catch((e) => { pushAlert({ message: { @@ -90,8 +89,7 @@ const BoardWithUser: React.FC<{ user: AuthUser }> = ({ user }) => { onMove: (timeline, index, offset) => { return getHttpHighlightClient() .move( - { timeline, newPosition: index + offset + 1 }, // +1 because backend contract: index starts at 1 - user.token + { timeline, newPosition: index + offset + 1 } // +1 because backend contract: index starts at 1 ) .catch((e) => { pushAlert({ diff --git a/FrontEnd/src/app/views/timeline-common/TimelineCardTemplate.tsx b/FrontEnd/src/app/views/timeline-common/TimelineCardTemplate.tsx index ece1942f..b2b349bc 100644 --- a/FrontEnd/src/app/views/timeline-common/TimelineCardTemplate.tsx +++ b/FrontEnd/src/app/views/timeline-common/TimelineCardTemplate.tsx @@ -56,13 +56,19 @@ function TimelineCardTemplate({ <div className="text-right mt-2"> {onHighlight != null ? ( <i - className="bi-star icon-button text-yellow mr-3" + className={clsx( + timeline.isHighlight ? "bi-star-fill" : "bi-star", + "icon-button text-yellow mr-3" + )} onClick={onHighlight} /> ) : null} {onBookmark != null ? ( <i - className="bi-bookmark icon-button text-yellow mr-3" + className={clsx( + timeline.isBookmark ? "bi-bookmark-fill" : "bi-bookmark", + "icon-button text-yellow mr-3" + )} onClick={onBookmark} /> ) : null} diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx index 7f5c8206..caced3b7 100644 --- a/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx +++ b/FrontEnd/src/app/views/timeline-common/TimelinePageTemplate.tsx @@ -45,7 +45,7 @@ export default function TimelinePageTemplate<TManageItem>( null ); - const timelineState = useTimelineInfo(name); + const [timelineState, setTimelineState] = useTimelineInfo(name); const postListState = usePostList(name); const onPost: TimelinePostSendCallback = React.useCallback( @@ -121,24 +121,64 @@ export default function TimelinePageTemplate<TManageItem>( onBookmark: user != null ? () => { - void getHttpBookmarkClient() - .put(name, user.token) - .then(() => { - pushAlert({ - message: { - type: "i18n", - key: "timeline.addBookmarkSuccess", + if (timeline.isBookmark) { + setTimelineState({ + ...timelineState, + timeline: { + ...timeline, + isBookmark: false, + }, + }); + void getHttpBookmarkClient() + .delete(name) + .then( + () => { + void timelineService.syncTimeline(name); }, - type: "success", - }); + () => { + pushAlert({ + message: { + type: "i18n", + key: "timeline.removeBookmarkFail", + }, + type: "danger", + }); + setTimelineState(timelineState); + } + ); + } else { + setTimelineState({ + ...timelineState, + timeline: { + ...timeline, + isBookmark: true, + }, }); + void getHttpBookmarkClient() + .put(name) + .then( + () => { + void timelineService.syncTimeline(name); + }, + () => { + pushAlert({ + message: { + type: "i18n", + key: "timeline.addBookmarkFail", + }, + type: "danger", + }); + setTimelineState(timelineState); + } + ); + } } : undefined, onHighlight: user != null && user.hasHighlightTimelineAdministrationPermission ? () => { void getHttpHighlightClient() - .put(name, user.token) + .put(name) .then(() => { pushAlert({ message: { |