aboutsummaryrefslogtreecommitdiff
path: root/Timeline/ClientApp
diff options
context:
space:
mode:
Diffstat (limited to 'Timeline/ClientApp')
-rw-r--r--Timeline/ClientApp/src/app/data/common.ts6
-rw-r--r--Timeline/ClientApp/src/app/data/timeline.ts51
-rw-r--r--Timeline/ClientApp/src/app/timeline/TimelinePageTemplate.tsx67
3 files changed, 74 insertions, 50 deletions
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<TimelinePostInfo[]> {
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<PostListInfo | null>(
this.getPostListInfoKey(timeline.uniqueId)
);
@@ -197,6 +204,18 @@ export class TimelineService {
async syncPostList(timelineName: string): Promise<TimelinePostInfo[]> {
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<PostListInfo | null>(
postListInfoKey
@@ -325,10 +344,7 @@ export class TimelineService {
}
);
- get postListSubscriptionHub(): ISubscriptionHub<
- string,
- TimelinePostListState
- > {
+ get postListHub(): ISubscriptionHub<string, TimelinePostListState> {
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<TimelinePostListState | undefined>(
+ 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<TimelineInfo | undefined>(
undefined
);
- const [posts, setPosts] = React.useState<
- TimelinePostInfoEx[] | 'forbid' | undefined
- >(undefined);
+
+ const rawPosts = usePostList(timeline?.name);
+
const [error, setError] = React.useState<string | undefined>(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,