aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2020-07-31 23:13:03 +0800
committercrupest <crupest@outlook.com>2020-07-31 23:13:03 +0800
commitc53e51d28bbaa4fb294eb335001719f82474244f (patch)
treeb030e79f8a896f15a6cc11f9dedd004ea242debe
parentc5cd6aebf8516b485180ae5cadc8fed34e74291d (diff)
downloadtimeline-c53e51d28bbaa4fb294eb335001719f82474244f.tar.gz
timeline-c53e51d28bbaa4fb294eb335001719f82474244f.tar.bz2
timeline-c53e51d28bbaa4fb294eb335001719f82474244f.zip
Add http get timeline api query params.
-rw-r--r--Timeline/ClientApp/src/app/http/common.ts10
-rw-r--r--Timeline/ClientApp/src/app/http/mock/timeline.ts62
-rw-r--r--Timeline/ClientApp/src/app/http/timeline.ts74
-rw-r--r--Timeline/ClientApp/src/app/utilities/url.ts13
4 files changed, 150 insertions, 9 deletions
diff --git a/Timeline/ClientApp/src/app/http/common.ts b/Timeline/ClientApp/src/app/http/common.ts
index 8fb8eb69..3c2f2ba6 100644
--- a/Timeline/ClientApp/src/app/http/common.ts
+++ b/Timeline/ClientApp/src/app/http/common.ts
@@ -119,6 +119,16 @@ export function convertToNetworkError(
}
}
+export function extractDataOrConvert304ToNotModified<T>(
+ res: AxiosResponse<T>
+): T | NotModified {
+ if (res.status === 304) {
+ return new NotModified();
+ } else {
+ return res.data;
+ }
+}
+
export function convertToBlobWithEtag(res: AxiosResponse<Blob>): BlobWithEtag {
return {
data: res.data,
diff --git a/Timeline/ClientApp/src/app/http/mock/timeline.ts b/Timeline/ClientApp/src/app/http/mock/timeline.ts
index 2a34ef10..911da2f2 100644
--- a/Timeline/ClientApp/src/app/http/mock/timeline.ts
+++ b/Timeline/ClientApp/src/app/http/mock/timeline.ts
@@ -31,6 +31,7 @@ async function setTimelineNameList(newOne: string[]): Promise<void> {
type TimelinePropertyKey =
| 'uniqueId'
+ | 'lastModified'
| 'owner'
| 'description'
| 'visibility'
@@ -61,6 +62,14 @@ function setTimelinePropertyValue<T>(
.then();
}
+function updateTimelineLastModified(name: string): Promise<void> {
+ return setTimelinePropertyValue(
+ name,
+ 'lastModified',
+ new Date().toISOString()
+ );
+}
+
interface HttpTimelineInfoEx extends HttpTimelineInfo {
memberUsernames: string[];
}
@@ -98,6 +107,7 @@ async function getTimelineInfo(name: string): Promise<HttpTimelineInfoEx> {
if (optionalUniqueId == null) {
await setTimelineNameList([...(await getTimelineNameList()), name]);
await setTimelinePropertyValue(name, 'uniqueId', createUniqueId());
+ await updateTimelineLastModified(name);
}
} else {
const optionalOwnerUsername = await getTimelinePropertyValue<string | null>(
@@ -131,6 +141,9 @@ async function getTimelineInfo(name: string): Promise<HttpTimelineInfoEx> {
name,
'visibility'
)) ?? 'Register',
+ lastModified: new Date(
+ await getTimelinePropertyValue<string>(name, 'lastModified')
+ ),
members,
memberUsernames,
};
@@ -148,6 +161,7 @@ async function createTimeline(name: string, owner: string): Promise<void> {
await setTimelineNameList([...(await getTimelineNameList()), name]);
await setTimelinePropertyValue(name, 'uniqueId', createUniqueId());
await setTimelinePropertyValue(name, 'owner', owner);
+ await updateTimelineLastModified(name);
}
type TimelinePostPropertyKey =
@@ -304,10 +318,46 @@ export class MockHttpTimelineClient implements IHttpTimelineClient {
});
}
- async getTimeline(timelineName: string): Promise<HttpTimelineInfo> {
+ getTimeline(timelineName: string): Promise<HttpTimelineInfo>;
+ getTimeline(
+ timelineName: string,
+ query: {
+ checkUniqueId?: string;
+ }
+ ): Promise<HttpTimelineInfo>;
+ getTimeline(
+ timelineName: string,
+ query: {
+ checkUniqueId?: string;
+ ifModifiedSince: Date;
+ }
+ ): Promise<HttpTimelineInfo | NotModified>;
+ async getTimeline(
+ timelineName: string,
+ query?: {
+ checkUniqueId?: string;
+ ifModifiedSince?: Date;
+ }
+ ): Promise<HttpTimelineInfo | NotModified> {
await mockPrepare();
try {
- return await getTimelineInfo(timelineName);
+ const timeline = await getTimelineInfo(timelineName);
+ if (query != null && query.ifModifiedSince != null) {
+ if (timeline.lastModified >= query.ifModifiedSince) {
+ return timeline;
+ } else {
+ if (
+ query.checkUniqueId != null &&
+ timeline.uniqueId != query.checkUniqueId
+ ) {
+ return timeline;
+ } else {
+ return new NotModified();
+ }
+ }
+ }
+
+ return timeline;
} catch (e) {
if (
e instanceof MockTimelineNotExistError ||
@@ -342,7 +392,9 @@ export class MockHttpTimelineClient implements IHttpTimelineClient {
_token: string
): Promise<HttpTimelineInfo> {
await mockPrepare();
+ let modified = false;
if (req.description != null) {
+ modified = true;
await setTimelinePropertyValue(
timelineName,
'description',
@@ -350,12 +402,16 @@ export class MockHttpTimelineClient implements IHttpTimelineClient {
);
}
if (req.visibility != null) {
+ modified = true;
await setTimelinePropertyValue(
timelineName,
'visibility',
req.visibility
);
}
+ if (modified) {
+ await updateTimelineLastModified(timelineName);
+ }
return await getTimelineInfo(timelineName);
}
@@ -387,6 +443,7 @@ export class MockHttpTimelineClient implements IHttpTimelineClient {
...oldMembers,
username,
]);
+ await updateTimelineLastModified(timelineName);
}
}
@@ -407,6 +464,7 @@ export class MockHttpTimelineClient implements IHttpTimelineClient {
'members',
without(oldMembers, username)
);
+ await updateTimelineLastModified(timelineName);
}
}
diff --git a/Timeline/ClientApp/src/app/http/timeline.ts b/Timeline/ClientApp/src/app/http/timeline.ts
index 458ea6e6..bfe0d1ad 100644
--- a/Timeline/ClientApp/src/app/http/timeline.ts
+++ b/Timeline/ClientApp/src/app/http/timeline.ts
@@ -24,6 +24,7 @@ export interface HttpTimelineInfo {
description: string;
owner: HttpUser;
visibility: TimelineVisibility;
+ lastModified: Date;
members: HttpUser[];
}
@@ -115,6 +116,16 @@ export class HttpTimelineNameConflictError extends Error {
//-------------------- begin: internal model --------------------
+interface RawTimelineInfo {
+ uniqueId: string;
+ name: string;
+ description: string;
+ owner: HttpUser;
+ visibility: TimelineVisibility;
+ lastModified: string;
+ members: HttpUser[];
+}
+
interface RawTimelinePostTextContent {
type: 'text';
text: string;
@@ -171,6 +182,13 @@ interface RawTimelinePostPostRequest {
//-------------------- end: internal model --------------------
+function processRawTimelineInfo(raw: RawTimelineInfo): HttpTimelineInfo {
+ return {
+ ...raw,
+ lastModified: new Date(raw.lastModified),
+ };
+}
+
function processRawTimelinePostInfo(
raw: RawTimelinePostInfo
): HttpTimelinePostInfo;
@@ -190,6 +208,19 @@ function processRawTimelinePostInfo(
export interface IHttpTimelineClient {
listTimeline(query: HttpTimelineListQuery): Promise<HttpTimelineInfo[]>;
getTimeline(timelineName: string): Promise<HttpTimelineInfo>;
+ getTimeline(
+ timelineName: string,
+ query: {
+ checkUniqueId?: string;
+ }
+ ): Promise<HttpTimelineInfo>;
+ getTimeline(
+ timelineName: string,
+ query: {
+ checkUniqueId?: string;
+ ifModifiedSince: Date;
+ }
+ ): Promise<HttpTimelineInfo | NotModified>;
postTimeline(
req: HttpTimelinePostRequest,
token: string
@@ -256,17 +287,46 @@ export interface IHttpTimelineClient {
export class HttpTimelineClient implements IHttpTimelineClient {
listTimeline(query: HttpTimelineListQuery): Promise<HttpTimelineInfo[]> {
return axios
- .get<HttpTimelineInfo[]>(
+ .get<RawTimelineInfo[]>(
applyQueryParameters(`${apiBaseUrl}/timelines`, query)
)
.then(extractResponseData)
+ .then((list) => list.map(processRawTimelineInfo))
.catch(convertToNetworkError);
}
- getTimeline(timelineName: string): Promise<HttpTimelineInfo> {
+ getTimeline(timelineName: string): Promise<HttpTimelineInfo>;
+ getTimeline(
+ timelineName: string,
+ query: {
+ checkUniqueId?: string;
+ }
+ ): Promise<HttpTimelineInfo>;
+ getTimeline(
+ timelineName: string,
+ query: {
+ checkUniqueId?: string;
+ ifModifiedSince: Date;
+ }
+ ): Promise<HttpTimelineInfo | NotModified>;
+ getTimeline(
+ timelineName: string,
+ query?: {
+ checkUniqueId?: string;
+ ifModifiedSince?: Date;
+ }
+ ): Promise<HttpTimelineInfo | NotModified> {
return axios
- .get<HttpTimelineInfo>(`${apiBaseUrl}/timelines/${timelineName}`)
- .then(extractResponseData)
+ .get<RawTimelineInfo>(
+ applyQueryParameters(`${apiBaseUrl}/timelines/${timelineName}`, query)
+ )
+ .then((res) => {
+ if (res.status === 304) {
+ return new NotModified();
+ } else {
+ return processRawTimelineInfo(res.data);
+ }
+ })
.catch(convertToIfStatusCodeIs(404, HttpTimelineNotExistError))
.catch(convertToNetworkError);
}
@@ -276,8 +336,9 @@ export class HttpTimelineClient implements IHttpTimelineClient {
token: string
): Promise<HttpTimelineInfo> {
return axios
- .post<HttpTimelineInfo>(`${apiBaseUrl}/timelines?token=${token}`, req)
+ .post<RawTimelineInfo>(`${apiBaseUrl}/timelines?token=${token}`, req)
.then(extractResponseData)
+ .then(processRawTimelineInfo)
.catch(convertToIfErrorCodeIs(11040101, HttpTimelineNameConflictError))
.catch(convertToNetworkError);
}
@@ -288,11 +349,12 @@ export class HttpTimelineClient implements IHttpTimelineClient {
token: string
): Promise<HttpTimelineInfo> {
return axios
- .patch<HttpTimelineInfo>(
+ .patch<RawTimelineInfo>(
`${apiBaseUrl}/timelines/${timelineName}?token=${token}`,
req
)
.then(extractResponseData)
+ .then(processRawTimelineInfo)
.catch(convertToNetworkError);
}
diff --git a/Timeline/ClientApp/src/app/utilities/url.ts b/Timeline/ClientApp/src/app/utilities/url.ts
index 0b8623a2..90923fd2 100644
--- a/Timeline/ClientApp/src/app/utilities/url.ts
+++ b/Timeline/ClientApp/src/app/utilities/url.ts
@@ -34,8 +34,19 @@ export function updateQueryString(
}
export function applyQueryParameters<T>(url: string, query: T): string {
+ if (query == null) return url;
+
for (const [key, value] of Object.entries(query)) {
- url = updateQueryString(key, String(value), url);
+ if (typeof value === 'string') url = updateQueryString(key, value, url);
+ else if (typeof value === 'number')
+ url = updateQueryString(key, String(value), url);
+ else if (typeof value === 'boolean')
+ url = updateQueryString(key, value ? 'true' : 'false', url);
+ else if (value instanceof Date)
+ url = updateQueryString(key, value.toISOString(), url);
+ else {
+ console.error('Unknown query parameter type. Param: ', value);
+ }
}
return url;
}