aboutsummaryrefslogtreecommitdiff
path: root/Timeline/ClientApp/src/app/data
diff options
context:
space:
mode:
Diffstat (limited to 'Timeline/ClientApp/src/app/data')
-rw-r--r--Timeline/ClientApp/src/app/data/base64.ts9
-rw-r--r--Timeline/ClientApp/src/app/data/common.ts25
-rw-r--r--Timeline/ClientApp/src/app/data/timeline.ts346
-rw-r--r--Timeline/ClientApp/src/app/data/user.ts224
4 files changed, 0 insertions, 604 deletions
diff --git a/Timeline/ClientApp/src/app/data/base64.ts b/Timeline/ClientApp/src/app/data/base64.ts
deleted file mode 100644
index 7f7c6fcc..00000000
--- a/Timeline/ClientApp/src/app/data/base64.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-export function base64(blob: Blob): Promise<string> {
- return new Promise<string>(resolve => {
- const reader = new FileReader();
- reader.onload = function() {
- resolve((reader.result as string).replace(/^data:.+;base64,/, ''));
- };
- reader.readAsDataURL(blob);
- });
-}
diff --git a/Timeline/ClientApp/src/app/data/common.ts b/Timeline/ClientApp/src/app/data/common.ts
deleted file mode 100644
index 1cdf93f1..00000000
--- a/Timeline/ClientApp/src/app/data/common.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { AxiosError } from 'axios';
-
-export function extractStatusCode(error: AxiosError): number | null {
- const code = error.response && error.response.status;
- if (typeof code === 'number') {
- return code;
- } else {
- return null;
- }
-}
-
-export interface CommonErrorResponse {
- code: number;
- message: string;
-}
-
-export function extractErrorCode(error: AxiosError): number | null {
- const { response } = error as AxiosError<CommonErrorResponse>;
- const code = response && response.data && response.data.code;
- if (typeof code === 'number') {
- return code;
- } else {
- return null;
- }
-}
diff --git a/Timeline/ClientApp/src/app/data/timeline.ts b/Timeline/ClientApp/src/app/data/timeline.ts
deleted file mode 100644
index a8875701..00000000
--- a/Timeline/ClientApp/src/app/data/timeline.ts
+++ /dev/null
@@ -1,346 +0,0 @@
-import axios from 'axios';
-import XRegExp from 'xregexp';
-
-import { base64 } from './base64';
-import { apiBaseUrl } from '../config';
-import { User, UserAuthInfo, getCurrentUser, UserWithToken } from './user';
-import { UiLogicError } from '../common';
-
-export const kTimelineVisibilities = ['Public', 'Register', 'Private'] as const;
-
-export type TimelineVisibility = typeof kTimelineVisibilities[number];
-
-export const timelineVisibilityTooltipTranslationMap: Record<
- TimelineVisibility,
- string
-> = {
- Public: 'timeline.visibilityTooltip.public',
- Register: 'timeline.visibilityTooltip.register',
- Private: 'timeline.visibilityTooltip.private',
-};
-
-export interface TimelineInfo {
- uniqueId: string;
- name: string;
- description: string;
- owner: User;
- visibility: TimelineVisibility;
- members: User[];
- _links: {
- posts: string;
- };
-}
-
-export interface TimelinePostTextContent {
- type: 'text';
- text: string;
-}
-
-export interface TimelinePostImageContent {
- type: 'image';
- url: string;
-}
-
-export type TimelinePostContent =
- | TimelinePostTextContent
- | TimelinePostImageContent;
-
-export interface TimelinePostInfo {
- id: number;
- content: TimelinePostContent;
- time: Date;
- author: User;
-}
-
-export interface CreatePostRequestTextContent {
- type: 'text';
- text: string;
-}
-
-export interface CreatePostRequestImageContent {
- type: 'image';
- data: Blob;
-}
-
-export type CreatePostRequestContent =
- | CreatePostRequestTextContent
- | CreatePostRequestImageContent;
-
-export interface CreatePostRequest {
- content: CreatePostRequestContent;
- time?: Date;
-}
-
-// TODO: Remove in the future
-export interface TimelineChangePropertyRequest {
- visibility?: TimelineVisibility;
- description?: string;
-}
-
-export interface PersonalTimelineChangePropertyRequest {
- visibility?: TimelineVisibility;
- description?: string;
-}
-
-export interface OrdinaryTimelineChangePropertyRequest {
- // not supported by server now
- // name?: string;
- visibility?: TimelineVisibility;
- description?: string;
-}
-
-//-------------------- begin: internal model --------------------
-
-interface RawTimelinePostTextContent {
- type: 'text';
- text: string;
-}
-
-interface RawTimelinePostImageContent {
- type: 'image';
- url: string;
-}
-
-type RawTimelinePostContent =
- | RawTimelinePostTextContent
- | RawTimelinePostImageContent;
-
-interface RawTimelinePostInfo {
- id: number;
- content: RawTimelinePostContent;
- time: string;
- author: User;
-}
-
-interface RawCreatePostRequestTextContent {
- type: 'text';
- text: string;
-}
-
-interface RawCreatePostRequestImageContent {
- type: 'text';
- data: string;
-}
-
-type RawCreatePostRequestContent =
- | RawCreatePostRequestTextContent
- | RawCreatePostRequestImageContent;
-
-interface RawCreatePostRequest {
- content: RawCreatePostRequestContent;
- time?: string;
-}
-
-//-------------------- end: internal model --------------------
-
-function processRawTimelinePostInfo(
- raw: RawTimelinePostInfo,
- token?: string
-): TimelinePostInfo {
- return {
- ...raw,
- content: (() => {
- if (raw.content.type === 'image' && token != null) {
- return {
- ...raw.content,
- url: raw.content.url + '?token=' + token,
- };
- }
- return raw.content;
- })(),
- time: new Date(raw.time),
- };
-}
-
-type TimelineUrlResolver = (name: string) => string;
-
-export class TimelineServiceTemplate<
- TTimeline extends TimelineInfo,
- TChangePropertyRequest
-> {
- private checkUser(): UserWithToken {
- const user = getCurrentUser();
- if (user == null) {
- throw new UiLogicError('You must login to perform the operation.');
- }
- return user;
- }
-
- constructor(private urlResolver: TimelineUrlResolver) {}
-
- changeProperty(
- name: string,
- req: TChangePropertyRequest
- ): Promise<TTimeline> {
- const user = this.checkUser();
-
- return axios
- .patch<TTimeline>(`${this.urlResolver(name)}?token=${user.token}`, req)
- .then((res) => res.data);
- }
-
- fetch(name: string): Promise<TTimeline> {
- return axios
- .get<TTimeline>(`${this.urlResolver(name)}`)
- .then((res) => res.data);
- }
-
- fetchPosts(name: string): Promise<TimelinePostInfo[]> {
- const token = getCurrentUser()?.token;
- return axios
- .get<RawTimelinePostInfo[]>(
- token == null
- ? `${this.urlResolver(name)}/posts`
- : `${this.urlResolver(name)}/posts?token=${token}`
- )
- .then((res) => res.data.map((p) => processRawTimelinePostInfo(p, token)));
- }
-
- createPost(
- name: string,
- request: CreatePostRequest
- ): Promise<TimelinePostInfo> {
- const user = this.checkUser();
-
- const rawReq: Promise<RawCreatePostRequest> = new Promise<
- RawCreatePostRequestContent
- >((resolve) => {
- if (request.content.type === 'image') {
- void base64(request.content.data).then((d) =>
- resolve({
- ...request.content,
- data: d,
- } as RawCreatePostRequestImageContent)
- );
- } else {
- resolve(request.content);
- }
- }).then((content) => {
- const rawReq: RawCreatePostRequest = {
- content,
- };
- if (request.time != null) {
- rawReq.time = request.time.toISOString();
- }
- return rawReq;
- });
-
- return rawReq
- .then((req) =>
- axios.post<RawTimelinePostInfo>(
- `${this.urlResolver(name)}/posts?token=${user.token}`,
- req
- )
- )
- .then((res) => processRawTimelinePostInfo(res.data, user.token));
- }
-
- deletePost(name: string, id: number): Promise<void> {
- const user = this.checkUser();
-
- return axios.delete(
- `${this.urlResolver(name)}/posts/${id}?token=${user.token}`
- );
- }
-
- addMember(name: string, username: string): Promise<void> {
- const user = this.checkUser();
-
- return axios.put(
- `${this.urlResolver(name)}/members/${username}?token=${user.token}`
- );
- }
-
- removeMember(name: string, username: string): Promise<void> {
- const user = this.checkUser();
-
- return axios.delete(
- `${this.urlResolver(name)}/members/${username}?token=${user.token}`
- );
- }
-
- isMemberOf(username: string, timeline: TTimeline): boolean {
- return timeline.members.findIndex((m) => m.username == username) >= 0;
- }
-
- hasReadPermission(
- user: UserAuthInfo | null | undefined,
- timeline: TTimeline
- ): boolean {
- if (user != null && user.administrator) return true;
-
- const { visibility } = timeline;
- if (visibility === 'Public') {
- return true;
- } else if (visibility === 'Register') {
- if (user != null) return true;
- } else if (visibility === 'Private') {
- if (user != null && this.isMemberOf(user.username, timeline)) {
- return true;
- }
- }
- return false;
- }
-
- hasPostPermission(
- user: UserAuthInfo | null | undefined,
- timeline: TTimeline
- ): boolean {
- if (user != null && user.administrator) return true;
-
- return (
- user != null &&
- (timeline.owner.username === user.username ||
- this.isMemberOf(user.username, timeline))
- );
- }
-
- hasManagePermission(
- user: UserAuthInfo | null | undefined,
- timeline: TTimeline
- ): boolean {
- if (user != null && user.administrator) return true;
-
- return user != null && user.username == timeline.owner.username;
- }
-
- hasModifyPostPermission(
- user: UserAuthInfo | null | undefined,
- timeline: TTimeline,
- post: TimelinePostInfo
- ): boolean {
- if (user != null && user.administrator) return true;
-
- return (
- user != null &&
- (user.username === timeline.owner.username ||
- user.username === post.author.username)
- );
- }
-}
-
-export type PersonalTimelineService = TimelineServiceTemplate<
- TimelineInfo,
- PersonalTimelineChangePropertyRequest
->;
-
-export const personalTimelineService: PersonalTimelineService = new TimelineServiceTemplate<
- TimelineInfo,
- PersonalTimelineChangePropertyRequest
->((name) => `${apiBaseUrl}/timelines/@${name}`);
-
-export type OrdinaryTimelineService = TimelineServiceTemplate<
- TimelineInfo,
- OrdinaryTimelineChangePropertyRequest
->;
-
-export const ordinaryTimelineService: OrdinaryTimelineService = new TimelineServiceTemplate<
- TimelineInfo,
- TimelineChangePropertyRequest
->((name) => `${apiBaseUrl}/timelines/${name}`);
-
-const timelineNameReg = XRegExp('^[-_\\p{L}]*$', 'u');
-
-export function validateTimelineName(name: string): boolean {
- return timelineNameReg.test(name);
-}
diff --git a/Timeline/ClientApp/src/app/data/user.ts b/Timeline/ClientApp/src/app/data/user.ts
deleted file mode 100644
index 8f787478..00000000
--- a/Timeline/ClientApp/src/app/data/user.ts
+++ /dev/null
@@ -1,224 +0,0 @@
-import axios, { AxiosError } from 'axios';
-import { useState, useEffect } from 'react';
-import { BehaviorSubject, Observable } from 'rxjs';
-
-import { apiBaseUrl } from '../config';
-import { extractErrorCode } from './common';
-import { pushAlert } from '../common/alert-service';
-import { i18nPromise } from '../i18n';
-import { UiLogicError } from '../common';
-
-export interface UserAuthInfo {
- username: string;
- administrator: boolean;
-}
-
-export interface User {
- username: string;
- administrator: boolean;
- nickname: string;
- _links: {
- avatar: string;
- timeline: string;
- };
-}
-
-export interface UserWithToken extends User {
- token: string;
-}
-
-interface CreateTokenRequest {
- username: string;
- password: string;
-}
-
-interface CreateTokenResponse {
- token: string;
- user: User;
-}
-
-interface VerifyTokenRequest {
- token: string;
-}
-
-interface VerifyTokenResponse {
- user: User;
-}
-
-export type LoginCredentials = CreateTokenRequest;
-
-const userSubject = new BehaviorSubject<UserWithToken | null | undefined>(
- undefined
-);
-
-export const user$: Observable<UserWithToken | null | undefined> = userSubject;
-
-export function getCurrentUser(): UserWithToken | null | undefined {
- return userSubject.value;
-}
-
-const kCreateTokenUrl = '/token/create';
-const kVerifyTokenUrl = '/token/verify';
-const createTokenUrl = apiBaseUrl + kCreateTokenUrl;
-const verifyTokenUrl = apiBaseUrl + kVerifyTokenUrl;
-
-function verifyToken(token: string): Promise<User> {
- return axios
- .post<VerifyTokenResponse>(verifyTokenUrl, {
- token: token,
- } as VerifyTokenRequest)
- .then((res) => res.data.user);
-}
-
-const TOKEN_STORAGE_KEY = 'token';
-
-export function checkUserLoginState(): Promise<UserWithToken | null> {
- if (getCurrentUser() !== undefined)
- throw new UiLogicError("Already checked user. Can't check twice.");
-
- const savedToken = window.localStorage.getItem(TOKEN_STORAGE_KEY);
- if (savedToken) {
- return verifyToken(savedToken)
- .then(
- (u) => {
- const user: UserWithToken = {
- ...u,
- token: savedToken,
- };
- void i18nPromise.then((t) => {
- pushAlert({
- type: 'success',
- message: t('user.welcomeBack'),
- });
- });
- return user;
- },
- (e: AxiosError) => {
- if (e.response != null) {
- window.localStorage.removeItem(TOKEN_STORAGE_KEY);
- void i18nPromise.then((t) => {
- pushAlert({
- type: 'danger',
- message: t('user.verifyTokenFailed'),
- });
- });
- } else {
- void i18nPromise.then((t) => {
- pushAlert({
- type: 'danger',
- message: t('user.verifyTokenFailedNetwork'),
- });
- });
- }
-
- return null;
- }
- )
- .then((u) => {
- userSubject.next(u);
- return u;
- });
- }
- userSubject.next(null);
- return Promise.resolve(null);
-}
-
-export class BadCredentialError {
- constructor(public innerError: Error) {}
-
- message = 'login.badCredential';
-}
-
-export function userLogin(
- credentials: LoginCredentials,
- rememberMe: boolean
-): Promise<UserWithToken> {
- if (getCurrentUser()) {
- throw new UiLogicError('Already login.');
- }
- return axios
- .post<CreateTokenResponse>(createTokenUrl, { ...credentials, expire: 30 })
- .catch((e: AxiosError) => {
- if (extractErrorCode(e) === 11010101) {
- throw new BadCredentialError(e);
- }
- throw e;
- })
- .then((res) => {
- const body = res.data;
- const token = body.token;
- if (rememberMe) {
- window.localStorage.setItem(TOKEN_STORAGE_KEY, token);
- }
- const user = {
- ...body.user,
- token,
- };
- userSubject.next(user);
- return user;
- });
-}
-
-export function userLogout(): void {
- if (getCurrentUser() === undefined) {
- throw new UiLogicError('Please check user first.');
- }
- if (getCurrentUser() === null) {
- throw new UiLogicError('No login.');
- }
- window.localStorage.removeItem(TOKEN_STORAGE_KEY);
- userSubject.next(null);
-}
-
-export function useOptionalUser(): UserWithToken | null | undefined {
- const [user, setUser] = useState<UserWithToken | null | undefined>(
- userSubject.value
- );
- useEffect(() => {
- const sub = user$.subscribe((u) => setUser(u));
- return () => {
- sub.unsubscribe();
- };
- });
- return user;
-}
-
-export function useUser(): UserWithToken | null {
- const [user, setUser] = useState<UserWithToken | null>(() => {
- const initUser = userSubject.value;
- if (initUser === undefined) {
- throw new UiLogicError(
- "This is a logic error in user module. Current user can't be undefined in useUser."
- );
- }
- return initUser;
- });
- useEffect(() => {
- const sub = user$.subscribe((u) => {
- if (u === undefined) {
- throw new UiLogicError(
- "This is a logic error in user module. User emitted can't be undefined later."
- );
- }
- setUser(u);
- });
- return () => {
- sub.unsubscribe();
- };
- });
- return user;
-}
-
-export function useUserLoggedIn(): UserWithToken {
- const user = useUser();
- if (user == null) {
- throw new UiLogicError('You assert user has logged in but actually not.');
- }
- return user;
-}
-
-export function fetchUser(username: string): Promise<User> {
- return axios
- .get<User>(`${apiBaseUrl}/users/${username}`)
- .then((res) => res.data);
-}