aboutsummaryrefslogtreecommitdiff
path: root/Timeline/ClientApp/src/app/http/mock/timeline.ts
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2020-07-26 15:02:55 +0800
committercrupest <crupest@outlook.com>2020-07-26 15:02:55 +0800
commitf5d10683a1edeba4dabe148ff7aa682c044f7496 (patch)
treed8f7edae96baa26823dee80ccc9329a23ac04c3c /Timeline/ClientApp/src/app/http/mock/timeline.ts
parent7753c9cad23b06c2acdd908a5a7cc3863bfa6b61 (diff)
downloadtimeline-f5d10683a1edeba4dabe148ff7aa682c044f7496.tar.gz
timeline-f5d10683a1edeba4dabe148ff7aa682c044f7496.tar.bz2
timeline-f5d10683a1edeba4dabe148ff7aa682c044f7496.zip
Merge front end repo
Diffstat (limited to 'Timeline/ClientApp/src/app/http/mock/timeline.ts')
-rw-r--r--Timeline/ClientApp/src/app/http/mock/timeline.ts600
1 files changed, 600 insertions, 0 deletions
diff --git a/Timeline/ClientApp/src/app/http/mock/timeline.ts b/Timeline/ClientApp/src/app/http/mock/timeline.ts
new file mode 100644
index 00000000..2a34ef10
--- /dev/null
+++ b/Timeline/ClientApp/src/app/http/mock/timeline.ts
@@ -0,0 +1,600 @@
+import { random, without, range } from 'lodash';
+
+import { BlobWithEtag, NotModified } from '../common';
+import {
+ IHttpTimelineClient,
+ HttpTimelineInfo,
+ TimelineVisibility,
+ HttpTimelineListQuery,
+ HttpTimelineNotExistError,
+ HttpTimelinePostRequest,
+ HttpTimelineNameConflictError,
+ HttpTimelinePatchRequest,
+ HttpTimelinePostInfo,
+ HttpTimelinePostContent,
+ HttpTimelinePostPostRequest,
+ HttpTimelinePostNotExistError,
+ HttpTimelineGenericPostInfo,
+} from '../timeline';
+import { HttpUser } from '../user';
+
+import { mockStorage, sha1, mockPrepare } from './common';
+import { getUser, MockUserNotExistError, checkToken } from './user';
+
+async function getTimelineNameList(): Promise<string[]> {
+ return (await mockStorage.getItem<string[]>('timelines')) ?? [];
+}
+
+async function setTimelineNameList(newOne: string[]): Promise<void> {
+ await mockStorage.setItem<string[]>('timelines', newOne);
+}
+
+type TimelinePropertyKey =
+ | 'uniqueId'
+ | 'owner'
+ | 'description'
+ | 'visibility'
+ | 'members'
+ | 'currentPostId';
+
+function getTimelinePropertyKey(
+ name: string,
+ property: TimelinePropertyKey
+): string {
+ return `timeline.${name}.${property}`;
+}
+
+function getTimelinePropertyValue<T>(
+ name: string,
+ property: TimelinePropertyKey
+): Promise<T> {
+ return mockStorage.getItem<T>(getTimelinePropertyKey(name, property));
+}
+
+function setTimelinePropertyValue<T>(
+ name: string,
+ property: TimelinePropertyKey,
+ value: T
+): Promise<void> {
+ return mockStorage
+ .setItem<T>(getTimelinePropertyKey(name, property), value)
+ .then();
+}
+
+interface HttpTimelineInfoEx extends HttpTimelineInfo {
+ memberUsernames: string[];
+}
+
+function createUniqueId(): string {
+ const s = 'abcdefghijklmnopqrstuvwxz0123456789';
+ let result = '';
+ for (let i = 0; i < 16; i++) {
+ result += s[random(0, s.length - 1)];
+ }
+ return result;
+}
+
+class MockTimelineNotExistError extends Error {
+ constructor() {
+ super('Timeline not exist.');
+ }
+}
+
+class MockTimelineAlreadyExistError extends Error {
+ constructor() {
+ super('Timeline already exist.');
+ }
+}
+
+async function getTimelineInfo(name: string): Promise<HttpTimelineInfoEx> {
+ let owner: HttpUser;
+ if (name.startsWith('@')) {
+ const ownerUsername = name.substr(1);
+ owner = await getUser(ownerUsername);
+ const optionalUniqueId = await getTimelinePropertyValue<string | null>(
+ name,
+ 'uniqueId'
+ );
+ if (optionalUniqueId == null) {
+ await setTimelineNameList([...(await getTimelineNameList()), name]);
+ await setTimelinePropertyValue(name, 'uniqueId', createUniqueId());
+ }
+ } else {
+ const optionalOwnerUsername = await getTimelinePropertyValue<string | null>(
+ name,
+ 'owner'
+ );
+ if (optionalOwnerUsername == null) {
+ throw new MockTimelineNotExistError();
+ } else {
+ owner = await getUser(optionalOwnerUsername);
+ }
+ }
+
+ const memberUsernames =
+ (await getTimelinePropertyValue<string[] | null>(name, 'members')) ?? [];
+ const members = await Promise.all(
+ memberUsernames.map(async (username) => {
+ return await getUser(username);
+ })
+ );
+
+ return {
+ name,
+ uniqueId: await getTimelinePropertyValue<string>(name, 'uniqueId'),
+ owner,
+ description:
+ (await getTimelinePropertyValue<string | null>(name, 'description')) ??
+ '',
+ visibility:
+ (await getTimelinePropertyValue<TimelineVisibility | null>(
+ name,
+ 'visibility'
+ )) ?? 'Register',
+ members,
+ memberUsernames,
+ };
+}
+
+async function createTimeline(name: string, owner: string): Promise<void> {
+ const optionalOwnerUsername = await getTimelinePropertyValue<string | null>(
+ name,
+ 'owner'
+ );
+ if (optionalOwnerUsername != null) {
+ throw new MockTimelineAlreadyExistError();
+ }
+
+ await setTimelineNameList([...(await getTimelineNameList()), name]);
+ await setTimelinePropertyValue(name, 'uniqueId', createUniqueId());
+ await setTimelinePropertyValue(name, 'owner', owner);
+}
+
+type TimelinePostPropertyKey =
+ | 'type'
+ | 'data'
+ | 'etag'
+ | 'author'
+ | 'time'
+ | 'lastUpdated';
+
+function getTimelinePostPropertyKey(
+ timelineName: string,
+ id: number,
+ propertyKey: TimelinePostPropertyKey
+): string {
+ return `timeline.${timelineName}.posts.${id}.${propertyKey}`;
+}
+
+function getTimelinePostPropertyValue<T>(
+ timelineName: string,
+ id: number,
+ propertyKey: TimelinePostPropertyKey
+): Promise<T> {
+ return mockStorage.getItem<T>(
+ getTimelinePostPropertyKey(timelineName, id, propertyKey)
+ );
+}
+
+function setTimelinePostPropertyValue<T>(
+ timelineName: string,
+ id: number,
+ propertyKey: TimelinePostPropertyKey,
+ value: T
+): Promise<T> {
+ return mockStorage.setItem(
+ getTimelinePostPropertyKey(timelineName, id, propertyKey),
+ value
+ );
+}
+
+function removeTimelinePostProperty(
+ timelineName: string,
+ id: number,
+ propertyKey: TimelinePostPropertyKey
+): Promise<void> {
+ return mockStorage.removeItem(
+ getTimelinePostPropertyKey(timelineName, id, propertyKey)
+ );
+}
+
+async function getTimelinePostInfo(
+ timelineName: string,
+ id: number
+): Promise<HttpTimelineGenericPostInfo> {
+ const currentPostId = await getTimelinePropertyValue<number | null>(
+ timelineName,
+ 'currentPostId'
+ );
+ if (currentPostId == null || id > currentPostId) {
+ throw new HttpTimelinePostNotExistError();
+ }
+
+ const type = await getTimelinePostPropertyValue<string | null>(
+ timelineName,
+ id,
+ 'type'
+ );
+
+ if (type == null) {
+ return {
+ id,
+ author: await getUser(
+ await getTimelinePostPropertyValue<string>(timelineName, id, 'author')
+ ),
+ time: new Date(
+ await getTimelinePostPropertyValue<string>(timelineName, id, 'time')
+ ),
+ lastUpdated: new Date(
+ await getTimelinePostPropertyValue<string>(
+ timelineName,
+ id,
+ 'lastUpdated'
+ )
+ ),
+ deleted: true,
+ };
+ } else {
+ let content: HttpTimelinePostContent;
+ if (type === 'text') {
+ content = {
+ type: 'text',
+ text: await getTimelinePostPropertyValue(timelineName, id, 'data'),
+ };
+ } else {
+ content = {
+ type: 'image',
+ };
+ }
+
+ return {
+ id,
+ author: await getUser(
+ await getTimelinePostPropertyValue<string>(timelineName, id, 'author')
+ ),
+ time: new Date(
+ await getTimelinePostPropertyValue<string>(timelineName, id, 'time')
+ ),
+ lastUpdated: new Date(
+ await getTimelinePostPropertyValue<string>(
+ timelineName,
+ id,
+ 'lastUpdated'
+ )
+ ),
+ content,
+ deleted: false,
+ };
+ }
+}
+
+export class MockHttpTimelineClient implements IHttpTimelineClient {
+ async listTimeline(
+ query: HttpTimelineListQuery
+ ): Promise<HttpTimelineInfo[]> {
+ await mockPrepare();
+ return (
+ await Promise.all(
+ (await getTimelineNameList()).map((name) => getTimelineInfo(name))
+ )
+ ).filter((timeline) => {
+ if (
+ query.visibility != null &&
+ query.visibility !== timeline.visibility
+ ) {
+ return false;
+ }
+ if (query.relate != null) {
+ if (query.relateType === 'own') {
+ if (timeline.owner.username !== query.relate) {
+ return false;
+ }
+ } else if (query.relateType === 'join') {
+ if (!timeline.memberUsernames.includes(query.relate)) {
+ return false;
+ }
+ } else if (
+ timeline.owner.username !== query.relate &&
+ !timeline.memberUsernames.includes(query.relate)
+ ) {
+ return false;
+ }
+ }
+ return true;
+ });
+ }
+
+ async getTimeline(timelineName: string): Promise<HttpTimelineInfo> {
+ await mockPrepare();
+ try {
+ return await getTimelineInfo(timelineName);
+ } catch (e) {
+ if (
+ e instanceof MockTimelineNotExistError ||
+ e instanceof MockUserNotExistError
+ ) {
+ throw new HttpTimelineNotExistError();
+ }
+ throw e;
+ }
+ }
+
+ async postTimeline(
+ req: HttpTimelinePostRequest,
+ token: string
+ ): Promise<HttpTimelineInfo> {
+ await mockPrepare();
+ const user = checkToken(token);
+ try {
+ await createTimeline(req.name, user);
+ } catch (e) {
+ if (e instanceof MockTimelineAlreadyExistError) {
+ throw new HttpTimelineNameConflictError();
+ }
+ throw e;
+ }
+ return await getTimelineInfo(req.name);
+ }
+
+ async patchTimeline(
+ timelineName: string,
+ req: HttpTimelinePatchRequest,
+ _token: string
+ ): Promise<HttpTimelineInfo> {
+ await mockPrepare();
+ if (req.description != null) {
+ await setTimelinePropertyValue(
+ timelineName,
+ 'description',
+ req.description
+ );
+ }
+ if (req.visibility != null) {
+ await setTimelinePropertyValue(
+ timelineName,
+ 'visibility',
+ req.visibility
+ );
+ }
+ return await getTimelineInfo(timelineName);
+ }
+
+ async deleteTimeline(timelineName: string, _token: string): Promise<void> {
+ await mockPrepare();
+ await setTimelineNameList(
+ without(await getTimelineNameList(), timelineName)
+ );
+ await mockStorage.removeItem(
+ getTimelinePropertyKey(timelineName, 'uniqueId')
+ );
+
+ // TODO: remove other things
+ }
+
+ async memberPut(
+ timelineName: string,
+ username: string,
+ _token: string
+ ): Promise<void> {
+ await mockPrepare();
+ const oldMembers =
+ (await getTimelinePropertyValue<string[] | null>(
+ timelineName,
+ 'members'
+ )) ?? [];
+ if (!oldMembers.includes(username)) {
+ await setTimelinePropertyValue(timelineName, 'members', [
+ ...oldMembers,
+ username,
+ ]);
+ }
+ }
+
+ async memberDelete(
+ timelineName: string,
+ username: string,
+ _token: string
+ ): Promise<void> {
+ await mockPrepare();
+ const oldMembers =
+ (await getTimelinePropertyValue<string[] | null>(
+ timelineName,
+ 'members'
+ )) ?? [];
+ if (oldMembers.includes(username)) {
+ await setTimelinePropertyValue(
+ timelineName,
+ 'members',
+ without(oldMembers, username)
+ );
+ }
+ }
+
+ listPost(
+ timelineName: string,
+ token?: string
+ ): Promise<HttpTimelinePostInfo[]>;
+ listPost(
+ timelineName: string,
+ token: string | undefined,
+ query: {
+ modifiedSince?: Date;
+ includeDeleted?: false;
+ }
+ ): Promise<HttpTimelinePostInfo[]>;
+ listPost(
+ timelineName: string,
+ token: string | undefined,
+ query: {
+ modifiedSince?: Date;
+ includeDeleted: true;
+ }
+ ): Promise<HttpTimelineGenericPostInfo[]>;
+ async listPost(
+ timelineName: string,
+ _token?: string,
+ query?: {
+ modifiedSince?: Date;
+ includeDeleted?: boolean;
+ }
+ ): Promise<HttpTimelineGenericPostInfo[]> {
+ await mockPrepare();
+ // TODO: Permission check.
+
+ const currentPostId = await getTimelinePropertyValue<number | null>(
+ timelineName,
+ 'currentPostId'
+ );
+
+ return (
+ await Promise.all(
+ range(1, currentPostId == null ? 1 : currentPostId + 1).map(
+ async (id) => {
+ return await getTimelinePostInfo(timelineName, id);
+ }
+ )
+ )
+ )
+ .filter((post) => {
+ if (query?.includeDeleted !== true && post.deleted) {
+ return false;
+ }
+ return true;
+ })
+ .filter((post) => {
+ if (query?.modifiedSince != null) {
+ return post.lastUpdated >= query.modifiedSince;
+ }
+ return true;
+ });
+ }
+
+ getPostData(
+ timelineName: string,
+ postId: number,
+ token: string
+ ): Promise<BlobWithEtag>;
+ async getPostData(
+ timelineName: string,
+ postId: number,
+ _token?: string,
+ etag?: string
+ ): Promise<BlobWithEtag | NotModified> {
+ await mockPrepare();
+ // TODO: Permission check.
+
+ const optionalSavedEtag = await getTimelinePostPropertyValue<string>(
+ timelineName,
+ postId,
+ 'etag'
+ );
+
+ if (optionalSavedEtag == null) {
+ const optionalType = await getTimelinePostPropertyValue<string>(
+ timelineName,
+ postId,
+ 'type'
+ );
+
+ if (optionalType != null) {
+ throw new Error('Post of this type has no data.');
+ } else {
+ throw new HttpTimelinePostNotExistError();
+ }
+ }
+
+ if (etag === optionalSavedEtag) {
+ return new NotModified();
+ }
+
+ return {
+ data: await getTimelinePostPropertyValue<Blob>(
+ timelineName,
+ postId,
+ 'data'
+ ),
+ etag: optionalSavedEtag,
+ };
+ }
+
+ async postPost(
+ timelineName: string,
+ req: HttpTimelinePostPostRequest,
+ token: string
+ ): Promise<HttpTimelinePostInfo> {
+ await mockPrepare();
+ const user = checkToken(token);
+
+ const savedId = await getTimelinePropertyValue<number | null>(
+ timelineName,
+ 'currentPostId'
+ );
+ const id = savedId ? savedId + 1 : 1;
+ await setTimelinePropertyValue(timelineName, 'currentPostId', id);
+
+ await setTimelinePostPropertyValue(timelineName, id, 'author', user);
+
+ const currentTimeString = new Date().toISOString();
+ await setTimelinePostPropertyValue(
+ timelineName,
+ id,
+ 'lastUpdated',
+ currentTimeString
+ );
+
+ await setTimelinePostPropertyValue(
+ timelineName,
+ id,
+ 'time',
+ req.time != null ? req.time.toISOString() : currentTimeString
+ );
+
+ const { content } = req;
+ if (content.type === 'text') {
+ await setTimelinePostPropertyValue(timelineName, id, 'type', 'text');
+ await setTimelinePostPropertyValue(
+ timelineName,
+ id,
+ 'data',
+ content.text
+ );
+ } else {
+ await setTimelinePostPropertyValue(timelineName, id, 'type', 'image');
+ await setTimelinePostPropertyValue(
+ timelineName,
+ id,
+ 'data',
+ content.data
+ );
+ await setTimelinePostPropertyValue(
+ timelineName,
+ id,
+ 'etag',
+ await sha1(content.data)
+ );
+ }
+
+ return (await getTimelinePostInfo(
+ timelineName,
+ id
+ )) as HttpTimelinePostInfo;
+ }
+
+ async deletePost(
+ timelineName: string,
+ postId: number,
+ _token: string
+ ): Promise<void> {
+ await mockPrepare();
+ // TODO: permission check
+ await removeTimelinePostProperty(timelineName, postId, 'type');
+ await removeTimelinePostProperty(timelineName, postId, 'data');
+ await removeTimelinePostProperty(timelineName, postId, 'etag');
+ await setTimelinePostPropertyValue(
+ timelineName,
+ postId,
+ 'lastUpdated',
+ new Date().toISOString()
+ );
+ }
+}