aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src/http
diff options
context:
space:
mode:
Diffstat (limited to 'FrontEnd/src/http')
-rw-r--r--FrontEnd/src/http/bookmark.ts49
-rw-r--r--FrontEnd/src/http/common.ts214
-rw-r--r--FrontEnd/src/http/highlight.ts49
-rw-r--r--FrontEnd/src/http/search.ts36
-rw-r--r--FrontEnd/src/http/timeline.ts234
-rw-r--r--FrontEnd/src/http/token.ts71
-rw-r--r--FrontEnd/src/http/user.ts161
7 files changed, 814 insertions, 0 deletions
diff --git a/FrontEnd/src/http/bookmark.ts b/FrontEnd/src/http/bookmark.ts
new file mode 100644
index 00000000..3e5be229
--- /dev/null
+++ b/FrontEnd/src/http/bookmark.ts
@@ -0,0 +1,49 @@
+import { axios, apiBaseUrl, extractResponseData } from "./common";
+
+import { HttpTimelineInfo } from "./timeline";
+
+export interface HttpHighlightMoveRequest {
+ timeline: string;
+ newPosition: number;
+}
+
+export interface IHttpBookmarkClient {
+ list(): Promise<HttpTimelineInfo[]>;
+ put(timeline: string): Promise<void>;
+ delete(timeline: string): Promise<void>;
+ move(req: HttpHighlightMoveRequest): Promise<void>;
+}
+
+export class HttpHighlightClient implements IHttpBookmarkClient {
+ list(): Promise<HttpTimelineInfo[]> {
+ return axios
+ .get<HttpTimelineInfo[]>(`${apiBaseUrl}/bookmarks`)
+ .then(extractResponseData);
+ }
+
+ put(timeline: string): Promise<void> {
+ return axios.put(`${apiBaseUrl}/bookmarks/${timeline}`).then();
+ }
+
+ delete(timeline: string): Promise<void> {
+ return axios.delete(`${apiBaseUrl}/bookmarks/${timeline}`).then();
+ }
+
+ move(req: HttpHighlightMoveRequest): Promise<void> {
+ return axios.post(`${apiBaseUrl}/bookmarkop/move`, req).then();
+ }
+}
+
+let client: IHttpBookmarkClient = new HttpHighlightClient();
+
+export function getHttpBookmarkClient(): IHttpBookmarkClient {
+ return client;
+}
+
+export function setHttpBookmarkClient(
+ newClient: IHttpBookmarkClient
+): IHttpBookmarkClient {
+ const old = client;
+ client = newClient;
+ return old;
+}
diff --git a/FrontEnd/src/http/common.ts b/FrontEnd/src/http/common.ts
new file mode 100644
index 00000000..e1672985
--- /dev/null
+++ b/FrontEnd/src/http/common.ts
@@ -0,0 +1,214 @@
+import rawAxios, { AxiosError, AxiosResponse } from "axios";
+import { Base64 } from "js-base64";
+import { BehaviorSubject, Observable } from "rxjs";
+
+export const apiBaseUrl = "/api";
+
+export const axios = rawAxios.create();
+
+function convertToNetworkError(error: AxiosError): never {
+ if (error.isAxiosError && error.response == null) {
+ throw new HttpNetworkError(error);
+ } else {
+ throw error;
+ }
+}
+
+function convertToForbiddenError(error: AxiosError): never {
+ if (
+ error.isAxiosError &&
+ error.response != null &&
+ (error.response.status == 401 || error.response.status == 403)
+ ) {
+ throw new HttpForbiddenError(error);
+ } else {
+ throw error;
+ }
+}
+
+function convertToNotFoundError(error: AxiosError): never {
+ if (
+ error.isAxiosError &&
+ error.response != null &&
+ error.response.status == 404
+ ) {
+ throw new HttpNotFoundError(error);
+ } else {
+ throw error;
+ }
+}
+
+rawAxios.interceptors.response.use(undefined, convertToNetworkError);
+rawAxios.interceptors.response.use(undefined, convertToForbiddenError);
+rawAxios.interceptors.response.use(undefined, convertToNotFoundError);
+axios.interceptors.response.use(undefined, convertToNetworkError);
+axios.interceptors.response.use(undefined, convertToForbiddenError);
+axios.interceptors.response.use(undefined, convertToNotFoundError);
+
+const tokenSubject = new BehaviorSubject<string | null>(null);
+
+export function getHttpToken(): string | null {
+ return tokenSubject.value;
+}
+
+export function setHttpToken(token: string | null): void {
+ tokenSubject.next(token);
+
+ if (token == null) {
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ delete axios.defaults.headers.common["Authorization"];
+ } else {
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
+ }
+}
+
+export const token$: Observable<string | null> = tokenSubject.asObservable();
+
+export function base64(blob: Blob | string): Promise<string> {
+ if (typeof blob === "string") {
+ return Promise.resolve(Base64.encode(blob));
+ }
+
+ return new Promise<string>((resolve) => {
+ const reader = new FileReader();
+ reader.onload = function () {
+ resolve((reader.result as string).replace(/^data:.*;base64,/, ""));
+ };
+ reader.readAsDataURL(blob);
+ });
+}
+
+export function extractStatusCode(error: AxiosError): number | null {
+ if (error.isAxiosError) {
+ const code = error?.response?.status;
+ if (typeof code === "number") {
+ return code;
+ }
+ }
+ return null;
+}
+
+export interface CommonErrorResponse {
+ code: number;
+ message: string;
+}
+
+export function extractErrorCode(
+ error: AxiosError<CommonErrorResponse>
+): number | null {
+ if (error.isAxiosError) {
+ const code = error.response?.data?.code;
+ if (typeof code === "number") {
+ return code;
+ }
+ }
+ return null;
+}
+
+export class HttpNetworkError extends Error {
+ constructor(public innerError?: AxiosError) {
+ super();
+ }
+}
+
+export class HttpForbiddenError extends Error {
+ constructor(public innerError?: AxiosError) {
+ super();
+ }
+}
+
+export class HttpNotFoundError extends Error {
+ constructor(public innerError?: AxiosError) {
+ super();
+ }
+}
+
+export class NotModified {}
+
+export interface BlobWithEtag {
+ data: Blob;
+ etag: string;
+}
+
+export function extractResponseData<T>(res: AxiosResponse<T>): T {
+ return res.data;
+}
+
+export function catchIfStatusCodeIs<
+ TResult,
+ TErrorHandlerResult extends TResult | PromiseLike<TResult> | null | undefined
+>(
+ statusCode: number,
+ errorHandler: (error: AxiosError<CommonErrorResponse>) => TErrorHandlerResult
+): (error: AxiosError<CommonErrorResponse>) => TErrorHandlerResult {
+ return (error: AxiosError<CommonErrorResponse>) => {
+ if (extractStatusCode(error) == statusCode) {
+ return errorHandler(error);
+ } else {
+ throw error;
+ }
+ };
+}
+
+export function convertToIfStatusCodeIs<NewError>(
+ statusCode: number,
+ newErrorType: {
+ new (innerError: AxiosError): NewError;
+ }
+): (error: AxiosError<CommonErrorResponse>) => never {
+ return catchIfStatusCodeIs(statusCode, (error) => {
+ throw new newErrorType(error);
+ });
+}
+
+export function catchIfErrorCodeIs<
+ TResult,
+ TErrorHandlerResult extends TResult | PromiseLike<TResult> | null | undefined
+>(
+ errorCode: number,
+ errorHandler: (error: AxiosError<CommonErrorResponse>) => TErrorHandlerResult
+): (error: AxiosError<CommonErrorResponse>) => TErrorHandlerResult {
+ return (error: AxiosError<CommonErrorResponse>) => {
+ if (extractErrorCode(error) == errorCode) {
+ return errorHandler(error);
+ } else {
+ throw error;
+ }
+ };
+}
+export function convertToIfErrorCodeIs<NewError>(
+ errorCode: number,
+ newErrorType: {
+ new (innerError: AxiosError): NewError;
+ }
+): (error: AxiosError<CommonErrorResponse>) => never {
+ return catchIfErrorCodeIs(errorCode, (error) => {
+ throw new newErrorType(error);
+ });
+}
+
+export function convertToNotModified(
+ error: AxiosError<CommonErrorResponse>
+): NotModified {
+ if (
+ error.isAxiosError &&
+ error.response != null &&
+ error.response.status == 304
+ ) {
+ return new NotModified();
+ } else {
+ throw error;
+ }
+}
+
+export function convertToBlobWithEtag(res: AxiosResponse<Blob>): BlobWithEtag {
+ return {
+ data: res.data,
+ etag: (res.headers as Record<"etag", string>)["etag"],
+ };
+}
+
+export function extractEtag(res: AxiosResponse): string {
+ return (res.headers as Record<"etag", string>)["etag"];
+}
diff --git a/FrontEnd/src/http/highlight.ts b/FrontEnd/src/http/highlight.ts
new file mode 100644
index 00000000..fddf0729
--- /dev/null
+++ b/FrontEnd/src/http/highlight.ts
@@ -0,0 +1,49 @@
+import { axios, apiBaseUrl, extractResponseData } from "./common";
+
+import { HttpTimelineInfo } from "./timeline";
+
+export interface HttpHighlightMoveRequest {
+ timeline: string;
+ newPosition: number;
+}
+
+export interface IHttpHighlightClient {
+ list(): Promise<HttpTimelineInfo[]>;
+ put(timeline: string): Promise<void>;
+ delete(timeline: string): Promise<void>;
+ move(req: HttpHighlightMoveRequest): Promise<void>;
+}
+
+export class HttpHighlightClient implements IHttpHighlightClient {
+ list(): Promise<HttpTimelineInfo[]> {
+ return axios
+ .get<HttpTimelineInfo[]>(`${apiBaseUrl}/highlights`)
+ .then(extractResponseData);
+ }
+
+ put(timeline: string): Promise<void> {
+ return axios.put(`${apiBaseUrl}/highlights/${timeline}`).then();
+ }
+
+ delete(timeline: string): Promise<void> {
+ return axios.delete(`${apiBaseUrl}/highlights/${timeline}`).then();
+ }
+
+ move(req: HttpHighlightMoveRequest): Promise<void> {
+ return axios.post(`${apiBaseUrl}/highlightop/move`, req).then();
+ }
+}
+
+let client: IHttpHighlightClient = new HttpHighlightClient();
+
+export function getHttpHighlightClient(): IHttpHighlightClient {
+ return client;
+}
+
+export function setHttpHighlightClient(
+ newClient: IHttpHighlightClient
+): IHttpHighlightClient {
+ const old = client;
+ client = newClient;
+ return old;
+}
diff --git a/FrontEnd/src/http/search.ts b/FrontEnd/src/http/search.ts
new file mode 100644
index 00000000..8ca48fe9
--- /dev/null
+++ b/FrontEnd/src/http/search.ts
@@ -0,0 +1,36 @@
+import { apiBaseUrl, axios, extractResponseData } from "./common";
+import { HttpTimelineInfo } from "./timeline";
+import { HttpUser } from "./user";
+
+export interface IHttpSearchClient {
+ searchTimelines(query: string): Promise<HttpTimelineInfo[]>;
+ searchUsers(query: string): Promise<HttpUser[]>;
+}
+
+export class HttpSearchClient implements IHttpSearchClient {
+ searchTimelines(query: string): Promise<HttpTimelineInfo[]> {
+ return axios
+ .get<HttpTimelineInfo[]>(`${apiBaseUrl}/search/timelines?q=${query}`)
+ .then(extractResponseData);
+ }
+
+ searchUsers(query: string): Promise<HttpUser[]> {
+ return axios
+ .get<HttpUser[]>(`${apiBaseUrl}/search/users?q=${query}`)
+ .then(extractResponseData);
+ }
+}
+
+let client: IHttpSearchClient = new HttpSearchClient();
+
+export function getHttpSearchClient(): IHttpSearchClient {
+ return client;
+}
+
+export function setHttpSearchClient(
+ newClient: IHttpSearchClient
+): IHttpSearchClient {
+ const old = client;
+ client = newClient;
+ return old;
+}
diff --git a/FrontEnd/src/http/timeline.ts b/FrontEnd/src/http/timeline.ts
new file mode 100644
index 00000000..9697c1a0
--- /dev/null
+++ b/FrontEnd/src/http/timeline.ts
@@ -0,0 +1,234 @@
+import { AxiosError } from "axios";
+
+import { applyQueryParameters } from "../utilities/url";
+
+import {
+ axios,
+ apiBaseUrl,
+ extractResponseData,
+ convertToIfErrorCodeIs,
+ getHttpToken,
+} from "./common";
+import { HttpUser } from "./user";
+
+export const kTimelineVisibilities = ["Public", "Register", "Private"] as const;
+
+export type TimelineVisibility = typeof kTimelineVisibilities[number];
+
+export interface HttpTimelineInfo {
+ uniqueId: string;
+ title: string;
+ name: string;
+ description: string;
+ owner: HttpUser;
+ visibility: TimelineVisibility;
+ color: string;
+ lastModified: string;
+ members: HttpUser[];
+ isHighlight: boolean;
+ isBookmark: boolean;
+ manageable: boolean;
+ postable: boolean;
+}
+
+export interface HttpTimelineListQuery {
+ visibility?: TimelineVisibility;
+ relate?: string;
+ relateType?: "own" | "join";
+}
+
+export interface HttpTimelinePostRequest {
+ name: string;
+}
+
+export interface HttpTimelinePostDataDigest {
+ kind: string;
+ eTag: string;
+ lastUpdated: string;
+}
+
+export interface HttpTimelinePostInfo {
+ id: number;
+ time: string;
+ author: HttpUser;
+ dataList: HttpTimelinePostDataDigest[];
+ color: string;
+ lastUpdated: string;
+ timelineName: string;
+ editable: boolean;
+}
+
+export interface HttpTimelinePostPostRequestData {
+ contentType: string;
+ data: string;
+}
+
+export interface HttpTimelinePostPostRequest {
+ time?: string;
+ color?: string;
+ dataList: HttpTimelinePostPostRequestData[];
+}
+
+export interface HttpTimelinePatchRequest {
+ name?: string;
+ title?: string;
+ color?: string;
+ visibility?: TimelineVisibility;
+ description?: string;
+}
+
+export interface HttpTimelinePostPatchRequest {
+ time?: string;
+ color?: string;
+}
+
+export class HttpTimelineNameConflictError extends Error {
+ constructor(public innerError?: AxiosError) {
+ super();
+ }
+}
+
+export interface IHttpTimelineClient {
+ listTimeline(query: HttpTimelineListQuery): Promise<HttpTimelineInfo[]>;
+ getTimeline(timelineName: string): Promise<HttpTimelineInfo>;
+ postTimeline(req: HttpTimelinePostRequest): Promise<HttpTimelineInfo>;
+ patchTimeline(
+ timelineName: string,
+ req: HttpTimelinePatchRequest
+ ): Promise<HttpTimelineInfo>;
+ deleteTimeline(timelineName: string): Promise<void>;
+ memberPut(timelineName: string, username: string): Promise<void>;
+ memberDelete(timelineName: string, username: string): Promise<void>;
+ listPost(timelineName: string): Promise<HttpTimelinePostInfo[]>;
+ generatePostDataUrl(timelineName: string, postId: number): string;
+ getPostDataAsString(timelineName: string, postId: number): Promise<string>;
+ postPost(
+ timelineName: string,
+ req: HttpTimelinePostPostRequest
+ ): Promise<HttpTimelinePostInfo>;
+ patchPost(
+ timelineName: string,
+ postId: number,
+ req: HttpTimelinePostPatchRequest
+ ): Promise<HttpTimelinePostInfo>;
+ deletePost(timelineName: string, postId: number): Promise<void>;
+}
+
+export class HttpTimelineClient implements IHttpTimelineClient {
+ listTimeline(query: HttpTimelineListQuery): Promise<HttpTimelineInfo[]> {
+ return axios
+ .get<HttpTimelineInfo[]>(
+ applyQueryParameters(`${apiBaseUrl}/timelines`, query)
+ )
+ .then(extractResponseData);
+ }
+
+ getTimeline(timelineName: string): Promise<HttpTimelineInfo> {
+ return axios
+ .get<HttpTimelineInfo>(`${apiBaseUrl}/timelines/${timelineName}`)
+ .then(extractResponseData);
+ }
+
+ postTimeline(req: HttpTimelinePostRequest): Promise<HttpTimelineInfo> {
+ return axios
+ .post<HttpTimelineInfo>(`${apiBaseUrl}/timelines`, req)
+ .then(extractResponseData)
+ .catch(convertToIfErrorCodeIs(11040101, HttpTimelineNameConflictError));
+ }
+
+ patchTimeline(
+ timelineName: string,
+ req: HttpTimelinePatchRequest
+ ): Promise<HttpTimelineInfo> {
+ return axios
+ .patch<HttpTimelineInfo>(`${apiBaseUrl}/timelines/${timelineName}`, req)
+ .then(extractResponseData);
+ }
+
+ deleteTimeline(timelineName: string): Promise<void> {
+ return axios.delete(`${apiBaseUrl}/timelines/${timelineName}`).then();
+ }
+
+ memberPut(timelineName: string, username: string): Promise<void> {
+ return axios
+ .put(`${apiBaseUrl}/timelines/${timelineName}/members/${username}`)
+ .then();
+ }
+
+ memberDelete(timelineName: string, username: string): Promise<void> {
+ return axios
+ .delete(`${apiBaseUrl}/timelines/${timelineName}/members/${username}`)
+ .then();
+ }
+
+ listPost(timelineName: string): Promise<HttpTimelinePostInfo[]> {
+ return axios
+ .get<HttpTimelinePostInfo[]>(
+ `${apiBaseUrl}/timelines/${timelineName}/posts`
+ )
+ .then(extractResponseData);
+ }
+
+ generatePostDataUrl(timelineName: string, postId: number): string {
+ return applyQueryParameters(
+ `${apiBaseUrl}/timelines/${timelineName}/posts/${postId}/data`,
+ { token: getHttpToken() }
+ );
+ }
+
+ getPostDataAsString(timelineName: string, postId: number): Promise<string> {
+ return axios
+ .get<string>(
+ `${apiBaseUrl}/timelines/${timelineName}/posts/${postId}/data`,
+ {
+ responseType: "text",
+ }
+ )
+ .then(extractResponseData);
+ }
+
+ postPost(
+ timelineName: string,
+ req: HttpTimelinePostPostRequest
+ ): Promise<HttpTimelinePostInfo> {
+ return axios
+ .post<HttpTimelinePostInfo>(
+ `${apiBaseUrl}/timelines/${timelineName}/posts`,
+ req
+ )
+ .then(extractResponseData);
+ }
+
+ patchPost(
+ timelineName: string,
+ postId: number,
+ req: HttpTimelinePostPatchRequest
+ ): Promise<HttpTimelinePostInfo> {
+ return axios
+ .patch<HttpTimelinePostInfo>(
+ `${apiBaseUrl}/timelines/${timelineName}/posts/${postId}`,
+ req
+ )
+ .then(extractResponseData);
+ }
+
+ deletePost(timelineName: string, postId: number): Promise<void> {
+ return axios
+ .delete(`${apiBaseUrl}/timelines/${timelineName}/posts/${postId}`)
+ .then();
+ }
+}
+
+let client: IHttpTimelineClient = new HttpTimelineClient();
+
+export function getHttpTimelineClient(): IHttpTimelineClient {
+ return client;
+}
+
+export function setHttpTimelineClient(
+ newClient: IHttpTimelineClient
+): IHttpTimelineClient {
+ const old = client;
+ client = newClient;
+ return old;
+}
diff --git a/FrontEnd/src/http/token.ts b/FrontEnd/src/http/token.ts
new file mode 100644
index 00000000..f8b09d63
--- /dev/null
+++ b/FrontEnd/src/http/token.ts
@@ -0,0 +1,71 @@
+// Don't use axios in common because it will contains
+// authorization header, which shouldn't be used in token apis.
+import axios, { AxiosError } from "axios";
+
+import {
+ apiBaseUrl,
+ convertToIfErrorCodeIs,
+ extractResponseData,
+} from "./common";
+import { HttpUser } from "./user";
+
+export interface HttpCreateTokenRequest {
+ username: string;
+ password: string;
+ expire: number;
+}
+
+export interface HttpCreateTokenResponse {
+ token: string;
+ user: HttpUser;
+}
+
+export interface HttpVerifyTokenRequest {
+ token: string;
+}
+
+export interface HttpVerifyTokenResponse {
+ user: HttpUser;
+}
+
+export class HttpCreateTokenBadCredentialError extends Error {
+ constructor(public innerError?: AxiosError) {
+ super();
+ }
+}
+
+export interface IHttpTokenClient {
+ create(req: HttpCreateTokenRequest): Promise<HttpCreateTokenResponse>;
+ verify(req: HttpVerifyTokenRequest): Promise<HttpVerifyTokenResponse>;
+}
+
+export class HttpTokenClient implements IHttpTokenClient {
+ create(req: HttpCreateTokenRequest): Promise<HttpCreateTokenResponse> {
+ return axios
+ .post<HttpCreateTokenResponse>(`${apiBaseUrl}/token/create`, req)
+ .then(extractResponseData)
+ .catch(
+ convertToIfErrorCodeIs(11010101, HttpCreateTokenBadCredentialError)
+ );
+ }
+
+ verify(req: HttpVerifyTokenRequest): Promise<HttpVerifyTokenResponse> {
+ return axios
+ .post<HttpVerifyTokenResponse>(`${apiBaseUrl}/token/verify`, req)
+ .then(extractResponseData);
+ }
+}
+
+let client: IHttpTokenClient = new HttpTokenClient();
+
+export function getHttpTokenClient(): IHttpTokenClient {
+ return client;
+}
+
+export function setHttpTokenClient(
+ newClient: IHttpTokenClient
+): IHttpTokenClient {
+ const old = client;
+ client = newClient;
+ return old;
+}
diff --git a/FrontEnd/src/http/user.ts b/FrontEnd/src/http/user.ts
new file mode 100644
index 00000000..dcf24cba
--- /dev/null
+++ b/FrontEnd/src/http/user.ts
@@ -0,0 +1,161 @@
+import { AxiosError } from "axios";
+
+import {
+ axios,
+ apiBaseUrl,
+ extractResponseData,
+ convertToIfStatusCodeIs,
+ convertToIfErrorCodeIs,
+ extractEtag,
+} from "./common";
+
+export const kUserManagement = "UserManagement";
+export const kAllTimelineManagement = "AllTimelineManagement";
+export const kHighlightTimelineManagement = "HighlightTimelineManagement";
+
+export const kUserPermissionList = [
+ kUserManagement,
+ kAllTimelineManagement,
+ kHighlightTimelineManagement,
+] as const;
+
+export type UserPermission = typeof kUserPermissionList[number];
+
+export interface HttpUser {
+ uniqueId: string;
+ username: string;
+ permissions: UserPermission[];
+ nickname: string;
+}
+
+export interface HttpUserPatchRequest {
+ username?: string;
+ password?: string;
+ nickname?: string;
+}
+
+export interface HttpChangePasswordRequest {
+ oldPassword: string;
+ newPassword: string;
+}
+
+export interface HttpCreateUserRequest {
+ username: string;
+ password: string;
+}
+
+export class HttpUserNotExistError extends Error {
+ constructor(public innerError?: AxiosError) {
+ super();
+ }
+}
+
+export class HttpChangePasswordBadCredentialError extends Error {
+ constructor(public innerError?: AxiosError) {
+ super();
+ }
+}
+
+export interface IHttpUserClient {
+ list(): Promise<HttpUser[]>;
+ get(username: string): Promise<HttpUser>;
+ post(req: HttpCreateUserRequest): Promise<HttpUser>;
+ patch(username: string, req: HttpUserPatchRequest): Promise<HttpUser>;
+ delete(username: string): Promise<void>;
+ generateAvatarUrl(username: string): string;
+ putAvatar(username: string, data: Blob): Promise<string>;
+ changePassword(req: HttpChangePasswordRequest): Promise<void>;
+ putUserPermission(
+ username: string,
+ permission: UserPermission
+ ): Promise<void>;
+ deleteUserPermission(
+ username: string,
+ permission: UserPermission
+ ): Promise<void>;
+}
+
+export class HttpUserClient implements IHttpUserClient {
+ list(): Promise<HttpUser[]> {
+ return axios
+ .get<HttpUser[]>(`${apiBaseUrl}/users`)
+ .then(extractResponseData);
+ }
+
+ get(username: string): Promise<HttpUser> {
+ return axios
+ .get<HttpUser>(`${apiBaseUrl}/users/${username}`)
+ .then(extractResponseData)
+ .catch(convertToIfStatusCodeIs(404, HttpUserNotExistError));
+ }
+
+ post(req: HttpCreateUserRequest): Promise<HttpUser> {
+ return axios
+ .post<HttpUser>(`${apiBaseUrl}/users`, req)
+ .then(extractResponseData)
+ .then();
+ }
+
+ patch(username: string, req: HttpUserPatchRequest): Promise<HttpUser> {
+ return axios
+ .patch<HttpUser>(`${apiBaseUrl}/users/${username}`, req)
+ .then(extractResponseData);
+ }
+
+ delete(username: string): Promise<void> {
+ return axios.delete(`${apiBaseUrl}/users/${username}`).then();
+ }
+
+ generateAvatarUrl(username: string): string {
+ return `${apiBaseUrl}/users/${username}/avatar`;
+ }
+
+ putAvatar(username: string, data: Blob): Promise<string> {
+ return axios
+ .put(`${apiBaseUrl}/users/${username}/avatar`, data, {
+ headers: {
+ "Content-Type": data.type,
+ },
+ })
+ .then(extractEtag);
+ }
+
+ changePassword(req: HttpChangePasswordRequest): Promise<void> {
+ return axios
+ .post(`${apiBaseUrl}/userop/changepassword`, req)
+ .catch(
+ convertToIfErrorCodeIs(11020201, HttpChangePasswordBadCredentialError)
+ )
+ .then();
+ }
+
+ putUserPermission(
+ username: string,
+ permission: UserPermission
+ ): Promise<void> {
+ return axios
+ .put(`${apiBaseUrl}/users/${username}/permissions/${permission}`)
+ .then();
+ }
+
+ deleteUserPermission(
+ username: string,
+ permission: UserPermission
+ ): Promise<void> {
+ return axios
+ .delete(`${apiBaseUrl}/users/${username}/permissions/${permission}`)
+ .then();
+ }
+}
+
+let client: IHttpUserClient = new HttpUserClient();
+
+export function getHttpUserClient(): IHttpUserClient {
+ return client;
+}
+
+export function setHttpUserClient(newClient: IHttpUserClient): IHttpUserClient {
+ const old = client;
+ client = newClient;
+ return old;
+}