From 5a90a7d0de9ae8410ef8c23a6994fdba7657666d Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 30 Jul 2020 23:57:13 +0800 Subject: ... --- Timeline/ClientApp/src/app/data/common.ts | 6 ++ Timeline/ClientApp/src/app/data/timeline.ts | 51 ++++++++++++++-- .../src/app/timeline/TimelinePageTemplate.tsx | 67 +++++++--------------- 3 files changed, 74 insertions(+), 50 deletions(-) (limited to 'Timeline/ClientApp') diff --git a/Timeline/ClientApp/src/app/data/common.ts b/Timeline/ClientApp/src/app/data/common.ts index e9b56970..9f985ce6 100644 --- a/Timeline/ClientApp/src/app/data/common.ts +++ b/Timeline/ClientApp/src/app/data/common.ts @@ -10,3 +10,9 @@ export interface BlobWithUrl { blob: Blob; url: string; } + +export class ForbiddenError extends Error { + constructor(message?: string) { + super(message); + } +} diff --git a/Timeline/ClientApp/src/app/data/timeline.ts b/Timeline/ClientApp/src/app/data/timeline.ts index 50d45aa5..88a13381 100644 --- a/Timeline/ClientApp/src/app/data/timeline.ts +++ b/Timeline/ClientApp/src/app/data/timeline.ts @@ -6,7 +6,7 @@ import { pull } from 'lodash'; import { convertError } from '../utilities/rxjs'; -import { BlobWithUrl, dataStorage } from './common'; +import { BlobWithUrl, dataStorage, ForbiddenError } from './common'; import { SubscriptionHub, ISubscriptionHub } from './SubscriptionHub'; import { UserAuthInfo, checkLogin, userService } from './user'; @@ -69,6 +69,7 @@ export interface PostKey { export interface TimelinePostListState { state: | 'loading' // Loading posts from cache. `posts` is empty array. + | 'forbid' // The list is forbidden to see. | 'syncing' // Cache loaded and syncing now. | 'synced' // Sync succeeded. | 'offline'; // Sync failed and use cache. @@ -177,6 +178,12 @@ export class TimelineService { timelineName: string ): Promise { const timeline = await this.getTimeline(timelineName).toPromise(); + if (!this.hasReadPermission(userService.currentUser, timeline)) { + throw new ForbiddenError( + 'You are not allowed to get posts of this timeline.' + ); + } + const postListInfo = await dataStorage.getItem( this.getPostListInfoKey(timeline.uniqueId) ); @@ -197,6 +204,18 @@ export class TimelineService { async syncPostList(timelineName: string): Promise { const timeline = await this.getTimeline(timelineName).toPromise(); + if (!this.hasReadPermission(userService.currentUser, timeline)) { + this._postListSubscriptionHub.update(timelineName, () => + Promise.resolve({ + state: 'forbid', + posts: [], + }) + ); + throw new ForbiddenError( + 'You are not allowed to get posts of this timeline.' + ); + } + const postListInfoKey = this.getPostListInfoKey(timeline.uniqueId); const postListInfo = await dataStorage.getItem( postListInfoKey @@ -325,10 +344,7 @@ export class TimelineService { } ); - get postListSubscriptionHub(): ISubscriptionHub< - string, - TimelinePostListState - > { + get postListHub(): ISubscriptionHub { return this._postListSubscriptionHub; } @@ -513,6 +529,31 @@ export function validateTimelineName(name: string): boolean { return timelineNameReg.test(name); } +export function usePostList( + timelineName: string | null | undefined +): TimelinePostListState | undefined { + const [state, setState] = React.useState( + undefined + ); + React.useEffect(() => { + if (timelineName == null) { + setState(undefined); + return; + } + + const subscription = timelineService.postListHub.subscribe( + timelineName, + (data) => { + setState(data); + } + ); + return () => { + subscription.unsubscribe(); + }; + }, [timelineName]); + return state; +} + export function usePostDataUrl( enable: boolean, timelineName: string, diff --git a/Timeline/ClientApp/src/app/timeline/TimelinePageTemplate.tsx b/Timeline/ClientApp/src/app/timeline/TimelinePageTemplate.tsx index 9be7f305..88066b76 100644 --- a/Timeline/ClientApp/src/app/timeline/TimelinePageTemplate.tsx +++ b/Timeline/ClientApp/src/app/timeline/TimelinePageTemplate.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { concat, without } from 'lodash'; import { of } from 'rxjs'; -import { catchError, switchMap, map } from 'rxjs/operators'; +import { catchError, map } from 'rxjs/operators'; import { ExcludeKey } from '../utilities/type'; import { pushAlert } from '../common/alert-service'; @@ -11,6 +11,7 @@ import { timelineService, TimelineInfo, TimelineNotExistError, + usePostList, } from '../data/timeline'; import { TimelinePostInfoEx, TimelineDeleteCallback } from './Timeline'; @@ -22,7 +23,7 @@ import { UiLogicError } from '../common'; export interface TimelinePageTemplateProps< TManageItem, - TTimeline extends TimelineInfo + TTimeline extends TimelineInfo // TODO: Remove this. > { name: string; onManage: (item: TManageItem) => void; @@ -53,53 +54,27 @@ export default function TimelinePageTemplate< const [timeline, setTimeline] = React.useState( undefined ); - const [posts, setPosts] = React.useState< - TimelinePostInfoEx[] | 'forbid' | undefined - >(undefined); + + const rawPosts = usePostList(timeline?.name); + const [error, setError] = React.useState(undefined); React.useEffect(() => { - const subscription = service - .getTimeline(name) - .pipe( - switchMap((ti) => { - setTimeline(ti); - if (!service.hasReadPermission(user, ti)) { - setPosts('forbid'); - return of(null); - } else { - return service - .getPosts(name) - .pipe(map((ps) => ({ timeline: ti, posts: ps }))); - } - }) - ) - .subscribe( - (data) => { - if (data != null) { - setPosts( - data.posts.map((post) => ({ - ...post, - deletable: service.hasModifyPostPermission( - user, - data.timeline, - post - ), - })) - ); - } - }, - (error) => { - if (error instanceof TimelineNotExistError) { - setError(t(props.notFoundI18nKey)); - } else { - setError( - // TODO: Convert this to a function. - (error as { message?: string })?.message ?? 'Unknown error' - ); - } + const subscription = service.getTimeline(name).subscribe( + (ti) => { + setTimeline(ti); + }, + (error) => { + if (error instanceof TimelineNotExistError) { + setError(t(props.notFoundI18nKey)); + } else { + setError( + // TODO: Convert this to a function. + (error as { message?: string })?.message ?? 'Unknown error' + ); } - ); + } + ); return () => { subscription.unsubscribe(); }; @@ -209,6 +184,7 @@ export default function TimelinePageTemplate< (index, id) => { service.deletePost(name, id).subscribe( () => { + // TODO: Remove this. setPosts((oldPosts) => without( oldPosts as TimelinePostInfoEx[], @@ -233,6 +209,7 @@ export default function TimelinePageTemplate< .createPost(name, req) .pipe( map((newPost) => { + // TODO: Remove this. setPosts((oldPosts) => concat(oldPosts as TimelinePostInfoEx[], { ...newPost, -- cgit v1.2.3