aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2022-04-25 19:27:44 +0800
committercrupest <crupest@outlook.com>2022-04-25 19:27:44 +0800
commitf2ead327344fdacdf3fb1e761b4fb8ec89330f1e (patch)
treec04e46a1517191440a72932f4314bd1f64503fab
parent60ec53aa2ad9687f5d8661322fb3e1e03e291eba (diff)
downloadtimeline-f2ead327344fdacdf3fb1e761b4fb8ec89330f1e.tar.gz
timeline-f2ead327344fdacdf3fb1e761b4fb8ec89330f1e.tar.bz2
timeline-f2ead327344fdacdf3fb1e761b4fb8ec89330f1e.zip
...
-rw-r--r--BackEnd/Timeline.Tests/IntegratedTests/BookmarkTimelineTest.cs2
-rw-r--r--BackEnd/Timeline.Tests/IntegratedTests/HighlightTimelineTest.cs2
-rw-r--r--BackEnd/Timeline.Tests/IntegratedTests/SearchTest.cs2
-rw-r--r--BackEnd/Timeline.Tests/IntegratedTests/TimelineTest.cs1
-rw-r--r--BackEnd/Timeline.Tests/IntegratedTests2/SelfTest.cs41
-rw-r--r--BackEnd/Timeline.Tests/IntegratedTests2/TimelineTest.cs2
-rw-r--r--BackEnd/Timeline.Tests/IntegratedTests2/TimelineTest2.cs2
-rw-r--r--BackEnd/Timeline/Controllers/V2/SelfController.cs40
-rw-r--r--BackEnd/Timeline/Models/Http/HttpTimeline.cs9
-rw-r--r--BackEnd/Timeline/Models/Http/HttpTimelinePost.cs9
-rw-r--r--FrontEnd/src/http/bookmark.ts11
-rw-r--r--FrontEnd/src/http/common.ts132
-rw-r--r--FrontEnd/src/http/timeline.ts10
-rw-r--r--FrontEnd/src/http/token.ts24
-rw-r--r--FrontEnd/src/http/user.ts60
-rw-r--r--FrontEnd/src/utilities/base64.ts15
-rw-r--r--FrontEnd/src/utilities/url.ts2
17 files changed, 181 insertions, 183 deletions
diff --git a/BackEnd/Timeline.Tests/IntegratedTests/BookmarkTimelineTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/BookmarkTimelineTest.cs
index 4bad700b..eb3e878d 100644
--- a/BackEnd/Timeline.Tests/IntegratedTests/BookmarkTimelineTest.cs
+++ b/BackEnd/Timeline.Tests/IntegratedTests/BookmarkTimelineTest.cs
@@ -1,4 +1,5 @@
using FluentAssertions;
+using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Timeline.Models.Http;
@@ -7,6 +8,7 @@ using Xunit.Abstractions;
namespace Timeline.Tests.IntegratedTests
{
+ [Obsolete("Old test.")]
public class BookmarkTimelineTest : IntegratedTestBase
{
public BookmarkTimelineTest(ITestOutputHelper testOutputHelper) : base(testOutputHelper)
diff --git a/BackEnd/Timeline.Tests/IntegratedTests/HighlightTimelineTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/HighlightTimelineTest.cs
index c52ac907..f05497fd 100644
--- a/BackEnd/Timeline.Tests/IntegratedTests/HighlightTimelineTest.cs
+++ b/BackEnd/Timeline.Tests/IntegratedTests/HighlightTimelineTest.cs
@@ -1,4 +1,5 @@
using FluentAssertions;
+using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Timeline.Models.Http;
@@ -7,6 +8,7 @@ using Xunit.Abstractions;
namespace Timeline.Tests.IntegratedTests
{
+ [Obsolete("Old test.")]
public class HighlightTimelineTest : IntegratedTestBase
{
public HighlightTimelineTest(ITestOutputHelper testOutputHelper) : base(testOutputHelper)
diff --git a/BackEnd/Timeline.Tests/IntegratedTests/SearchTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/SearchTest.cs
index 499eabbe..c9d1cb58 100644
--- a/BackEnd/Timeline.Tests/IntegratedTests/SearchTest.cs
+++ b/BackEnd/Timeline.Tests/IntegratedTests/SearchTest.cs
@@ -1,4 +1,5 @@
using FluentAssertions;
+using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Timeline.Models.Http;
@@ -7,6 +8,7 @@ using Xunit.Abstractions;
namespace Timeline.Tests.IntegratedTests
{
+ [Obsolete("Old test.")]
public class SearchTest : IntegratedTestBase
{
public SearchTest(ITestOutputHelper testOutputHelper) : base(testOutputHelper)
diff --git a/BackEnd/Timeline.Tests/IntegratedTests/TimelineTest.cs b/BackEnd/Timeline.Tests/IntegratedTests/TimelineTest.cs
index 5fab2bdb..4abcdb92 100644
--- a/BackEnd/Timeline.Tests/IntegratedTests/TimelineTest.cs
+++ b/BackEnd/Timeline.Tests/IntegratedTests/TimelineTest.cs
@@ -10,6 +10,7 @@ using Xunit.Abstractions;
namespace Timeline.Tests.IntegratedTests
{
+ [Obsolete("Old test.")]
public class TimelineTest : BaseTimelineTest
{
public TimelineTest(ITestOutputHelper testOutputHelper) : base(testOutputHelper)
diff --git a/BackEnd/Timeline.Tests/IntegratedTests2/SelfTest.cs b/BackEnd/Timeline.Tests/IntegratedTests2/SelfTest.cs
new file mode 100644
index 00000000..9698551a
--- /dev/null
+++ b/BackEnd/Timeline.Tests/IntegratedTests2/SelfTest.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Timeline.Models.Http;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Timeline.Tests.IntegratedTests2
+{
+ public class SelfTest : IntegratedTestBase
+ {
+ public SelfTest(ITestOutputHelper testOutput) : base(testOutput)
+ {
+ }
+
+ [Fact]
+ public async Task ChangePassword()
+ {
+ await DefaultClient.TestJsonSendAsync(HttpMethod.Post, "v2/self/changepassword", new HttpChangePasswordRequest
+ {
+ OldPassword = "abc",
+ NewPassword = "def"
+ }, expectedStatusCode: HttpStatusCode.Unauthorized);
+
+
+ await UserClient.TestJsonSendAsync(HttpMethod.Post, "v2/self/changepassword", new HttpChangePasswordRequest
+ {
+ OldPassword = "abc",
+ NewPassword = "def"
+ }, expectedStatusCode: HttpStatusCode.UnprocessableEntity);
+
+ await UserClient.TestJsonSendAsync(HttpMethod.Post, "v2/self/changepassword", new HttpChangePasswordRequest
+ {
+ OldPassword = "userpw",
+ NewPassword = "def"
+ }, expectedStatusCode: HttpStatusCode.NoContent);
+ }
+ }
+}
+
diff --git a/BackEnd/Timeline.Tests/IntegratedTests2/TimelineTest.cs b/BackEnd/Timeline.Tests/IntegratedTests2/TimelineTest.cs
index 807314f4..84bd5264 100644
--- a/BackEnd/Timeline.Tests/IntegratedTests2/TimelineTest.cs
+++ b/BackEnd/Timeline.Tests/IntegratedTests2/TimelineTest.cs
@@ -25,7 +25,7 @@ namespace Timeline.Tests.IntegratedTests2
var b = await client.TestJsonSendAsync<HttpTimeline>(HttpMethod.Get, "v2/timelines/user/hello");
- a.Name.Should().Be(b.Name);
+ a.NameV2.Should().Be(b.NameV2);
a.UniqueId.Should().Be(b.UniqueId);
}
diff --git a/BackEnd/Timeline.Tests/IntegratedTests2/TimelineTest2.cs b/BackEnd/Timeline.Tests/IntegratedTests2/TimelineTest2.cs
index b5566ba0..a97ee6d6 100644
--- a/BackEnd/Timeline.Tests/IntegratedTests2/TimelineTest2.cs
+++ b/BackEnd/Timeline.Tests/IntegratedTests2/TimelineTest2.cs
@@ -38,7 +38,7 @@ namespace Timeline.Tests.IntegratedTests2
Color = "#FFFFFF"
});
- b.Name.Should().Be("hello2");
+ b.NameV2.Should().Be("hello2");
b.Title.Should().Be("Hello");
b.Description.Should().Be("A Description.");
b.Visibility.Should().Be(TimelineVisibility.Public);
diff --git a/BackEnd/Timeline/Controllers/V2/SelfController.cs b/BackEnd/Timeline/Controllers/V2/SelfController.cs
new file mode 100644
index 00000000..1604bc67
--- /dev/null
+++ b/BackEnd/Timeline/Controllers/V2/SelfController.cs
@@ -0,0 +1,40 @@
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Timeline.Models.Http;
+using Timeline.Services.User;
+
+namespace Timeline.Controllers.V2
+{
+ [ApiController]
+ [Route("v2/self")]
+ public class SelfController : V2ControllerBase
+ {
+ private readonly IUserService _userService;
+
+ public SelfController(IUserService userService)
+ {
+ _userService = userService;
+ }
+
+ [HttpPost("changepassword")]
+ [Authorize]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+ [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
+ public async Task<ActionResult> ChangePasswordAsync([FromBody] HttpChangePasswordRequest body)
+ {
+ try
+ {
+ await _userService.ChangePassword(GetAuthUserId(), body.OldPassword, body.NewPassword);
+ return NoContent();
+ }
+ catch (BadPasswordException)
+ {
+ return UnprocessableEntity(new ErrorResponse(ErrorResponse.InvalidRequest, "Old password is wrong."));
+ }
+ }
+ }
+}
+
diff --git a/BackEnd/Timeline/Models/Http/HttpTimeline.cs b/BackEnd/Timeline/Models/Http/HttpTimeline.cs
index 83398baf..401d84a2 100644
--- a/BackEnd/Timeline/Models/Http/HttpTimeline.cs
+++ b/BackEnd/Timeline/Models/Http/HttpTimeline.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
namespace Timeline.Models.Http
@@ -13,8 +13,10 @@ namespace Timeline.Models.Http
public HttpTimeline(string uniqueId, string title, string name, string nameV2, DateTime nameLastModifed, string description, HttpUser owner, TimelineVisibility visibility, List<HttpUser> members, string? color, DateTime createTime, DateTime lastModified, bool isHighlight, bool isBookmark, bool manageable, bool postable, HttpTimelineLinks links)
{
UniqueId = uniqueId;
- Title = title;
- Name = name;
+ Title = title;
+#pragma warning disable CS0618 // Type or member is obsolete
+ Name = name;
+#pragma warning restore CS0618 // Type or member is obsolete
NameV2 = nameV2;
NameLastModifed = nameLastModifed;
Description = description;
@@ -42,6 +44,7 @@ namespace Timeline.Models.Http
/// <summary>
/// Name of timeline.
/// </summary>
+ [Obsolete("Use NameV2")]
public string Name { get; set; } = default!;
/// <summary>
/// Name of timeline.
diff --git a/BackEnd/Timeline/Models/Http/HttpTimelinePost.cs b/BackEnd/Timeline/Models/Http/HttpTimelinePost.cs
index 5c6a7167..677c486f 100644
--- a/BackEnd/Timeline/Models/Http/HttpTimelinePost.cs
+++ b/BackEnd/Timeline/Models/Http/HttpTimelinePost.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
namespace Timeline.Models.Http
@@ -21,8 +21,10 @@ namespace Timeline.Models.Http
Color = color;
LastUpdated = lastUpdated;
TimelineOwnerV2 = timelineOwnerV2;
- TimelineNameV2 = timelineNameV2;
- TimelineName = timelineName;
+ TimelineNameV2 = timelineNameV2;
+#pragma warning disable CS0618 // Type or member is obsolete
+ TimelineName = timelineName;
+#pragma warning restore CS0618 // Type or member is obsolete
Editable = editable;
}
@@ -67,6 +69,7 @@ namespace Timeline.Models.Http
/// <summary>
/// Timeline name.
/// </summary>
+ [Obsolete("Use TimelineNameV2.")]
public string TimelineName { get; set; } = default!;
/// <summary>
/// True if you can edit this post.
diff --git a/FrontEnd/src/http/bookmark.ts b/FrontEnd/src/http/bookmark.ts
index 382543ff..40e121cc 100644
--- a/FrontEnd/src/http/bookmark.ts
+++ b/FrontEnd/src/http/bookmark.ts
@@ -1,4 +1,5 @@
-import { applyQueryParameters } from "@/utilities/url";
+import { withQuery } from "@/utilities/url";
+
import { axios, apiBaseUrl, extractResponseData, Page } from "./common";
export interface TimelineBookmark {
@@ -37,10 +38,10 @@ export class HttpHighlightClient implements IHttpBookmarkClient {
page?: number,
pageSize?: number
): Promise<Page<TimelineBookmark>> {
- const url = applyQueryParameters(
- `${apiBaseUrl}/v2/users/${username}/bookmarks`,
- { page, pageSize }
- );
+ const url = withQuery(`${apiBaseUrl}/v2/users/${username}/bookmarks`, {
+ page,
+ pageSize,
+ });
return axios.get<Page<TimelineBookmark>>(url).then(extractResponseData);
}
diff --git a/FrontEnd/src/http/common.ts b/FrontEnd/src/http/common.ts
index 5a8c0346..25c69012 100644
--- a/FrontEnd/src/http/common.ts
+++ b/FrontEnd/src/http/common.ts
@@ -1,7 +1,6 @@
import axios, { Axios, AxiosError, AxiosResponse } from "axios";
-import { Base64 } from "js-base64";
-import { identity } from "lodash";
import { BehaviorSubject, Observable } from "rxjs";
+import { identity } from "lodash";
export { axios };
@@ -14,7 +13,10 @@ export class HttpNetworkError extends Error {
}
export class HttpForbiddenError extends Error {
- constructor(public innerError?: AxiosError) {
+ constructor(
+ public type: "unauthorized" | "forbidden",
+ public innerError?: AxiosError
+ ) {
super();
}
}
@@ -34,23 +36,20 @@ function convertNetworkError(error: AxiosError): never {
}
function convertForbiddenError(error: AxiosError): never {
- if (
- error.isAxiosError &&
- error.response != null &&
- (error.response.status == 401 || error.response.status == 403)
- ) {
- throw new HttpForbiddenError(error);
+ const statusCode = error.response?.status;
+ if (statusCode === 401 || statusCode === 403) {
+ throw new HttpForbiddenError(
+ statusCode === 401 ? "unauthorized" : "forbidden",
+ error
+ );
} else {
throw error;
}
}
function convertNotFoundError(error: AxiosError): never {
- if (
- error.isAxiosError &&
- error.response != null &&
- error.response.status == 404
- ) {
+ const statusCode = error.response?.status;
+ if (statusCode === 404) {
throw new HttpNotFoundError(error);
} else {
throw error;
@@ -85,47 +84,6 @@ export function setHttpToken(token: string | null): void {
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 NotModified {}
export interface BlobWithEtag {
@@ -137,67 +95,9 @@ 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
- ) {
+export function convertToNotModified(error: AxiosError): NotModified {
+ const statusCode = error.response?.status;
+ if (statusCode == 304) {
return new NotModified();
} else {
throw error;
diff --git a/FrontEnd/src/http/timeline.ts b/FrontEnd/src/http/timeline.ts
index d9e679ea..e95d52a8 100644
--- a/FrontEnd/src/http/timeline.ts
+++ b/FrontEnd/src/http/timeline.ts
@@ -1,6 +1,6 @@
import { AxiosError } from "axios";
-import { applyQueryParameters } from "../utilities/url";
+import { withQuery } from "@/utilities/url";
import {
axios,
@@ -168,9 +168,7 @@ export interface IHttpTimelineClient {
export class HttpTimelineClient implements IHttpTimelineClient {
listTimeline(query: HttpTimelineListQuery): Promise<HttpTimelineInfo[]> {
return axios
- .get<HttpTimelineInfo[]>(
- applyQueryParameters(`${apiBaseUrl}/timelines`, query)
- )
+ .get<HttpTimelineInfo[]>(withQuery(`${apiBaseUrl}/timelines`, query))
.then(extractResponseData);
}
@@ -243,7 +241,7 @@ export class HttpTimelineClient implements IHttpTimelineClient {
): Promise<Page<HttpTimelineGenericPostInfo>> {
return axios
.get<Page<HttpTimelineGenericPostInfo>>(
- applyQueryParameters(
+ withQuery(
`${apiBaseUrl}/v2/timelines/${ownerUsername}/${timelineName}/posts`,
{
page,
@@ -259,7 +257,7 @@ export class HttpTimelineClient implements IHttpTimelineClient {
timelineName: string,
postId: number
): string {
- return applyQueryParameters(
+ return withQuery(
`${apiBaseUrl}/v2/timelines/${ownerUsername}/${timelineName}/posts/${postId}/data`,
{ token: getHttpToken() }
);
diff --git a/FrontEnd/src/http/token.ts b/FrontEnd/src/http/token.ts
index 3de42d21..2eba9566 100644
--- a/FrontEnd/src/http/token.ts
+++ b/FrontEnd/src/http/token.ts
@@ -2,12 +2,8 @@
// authorization header, which shouldn't be used in token apis.
import originalAxios, { AxiosError } from "axios";
-import {
- apiBaseUrl,
- convertToIfErrorCodeIs,
- extractResponseData,
- configureAxios,
-} from "./common";
+import { apiBaseUrl, extractResponseData, configureAxios } from "./common";
+
import { HttpUser } from "./user";
const axios = originalAxios.create();
@@ -46,16 +42,20 @@ export interface IHttpTokenClient {
export class HttpTokenClient implements IHttpTokenClient {
create(req: HttpCreateTokenRequest): Promise<HttpCreateTokenResponse> {
return axios
- .post<HttpCreateTokenResponse>(`${apiBaseUrl}/token/create`, req, {})
- .then(extractResponseData)
- .catch(
- convertToIfErrorCodeIs(11010101, HttpCreateTokenBadCredentialError)
- );
+ .post<HttpCreateTokenResponse>(`${apiBaseUrl}/v2/token/create`, req, {})
+ .then(extractResponseData, (error: AxiosError) => {
+ const statusCode = error.response?.status;
+ if (statusCode === 422) {
+ throw new HttpCreateTokenBadCredentialError(error);
+ } else {
+ throw error;
+ }
+ });
}
verify(req: HttpVerifyTokenRequest): Promise<HttpVerifyTokenResponse> {
return axios
- .post<HttpVerifyTokenResponse>(`${apiBaseUrl}/token/verify`, req)
+ .post<HttpVerifyTokenResponse>(`${apiBaseUrl}/v2/token/verify`, req)
.then(extractResponseData);
}
}
diff --git a/FrontEnd/src/http/user.ts b/FrontEnd/src/http/user.ts
index dcf24cba..7313e484 100644
--- a/FrontEnd/src/http/user.ts
+++ b/FrontEnd/src/http/user.ts
@@ -1,13 +1,6 @@
import { AxiosError } from "axios";
-import {
- axios,
- apiBaseUrl,
- extractResponseData,
- convertToIfStatusCodeIs,
- convertToIfErrorCodeIs,
- extractEtag,
-} from "./common";
+import { axios, apiBaseUrl, extractResponseData, extractEtag } from "./common";
export const kUserManagement = "UserManagement";
export const kAllTimelineManagement = "AllTimelineManagement";
@@ -44,12 +37,6 @@ export interface HttpCreateUserRequest {
password: string;
}
-export class HttpUserNotExistError extends Error {
- constructor(public innerError?: AxiosError) {
- super();
- }
-}
-
export class HttpChangePasswordBadCredentialError extends Error {
constructor(public innerError?: AxiosError) {
super();
@@ -64,7 +51,6 @@ export interface IHttpUserClient {
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
@@ -73,46 +59,46 @@ export interface IHttpUserClient {
username: string,
permission: UserPermission
): Promise<void>;
+ changePassword(req: HttpChangePasswordRequest): Promise<void>;
}
export class HttpUserClient implements IHttpUserClient {
list(): Promise<HttpUser[]> {
return axios
- .get<HttpUser[]>(`${apiBaseUrl}/users`)
+ .get<HttpUser[]>(`${apiBaseUrl}/v2/users`)
.then(extractResponseData);
}
get(username: string): Promise<HttpUser> {
return axios
- .get<HttpUser>(`${apiBaseUrl}/users/${username}`)
- .then(extractResponseData)
- .catch(convertToIfStatusCodeIs(404, HttpUserNotExistError));
+ .get<HttpUser>(`${apiBaseUrl}/v2/users/${username}`)
+ .then(extractResponseData);
}
post(req: HttpCreateUserRequest): Promise<HttpUser> {
return axios
- .post<HttpUser>(`${apiBaseUrl}/users`, req)
+ .post<HttpUser>(`${apiBaseUrl}/v2/users`, req)
.then(extractResponseData)
.then();
}
patch(username: string, req: HttpUserPatchRequest): Promise<HttpUser> {
return axios
- .patch<HttpUser>(`${apiBaseUrl}/users/${username}`, req)
+ .patch<HttpUser>(`${apiBaseUrl}/v2/users/${username}`, req)
.then(extractResponseData);
}
delete(username: string): Promise<void> {
- return axios.delete(`${apiBaseUrl}/users/${username}`).then();
+ return axios.delete(`${apiBaseUrl}/v2/users/${username}`).then();
}
generateAvatarUrl(username: string): string {
- return `${apiBaseUrl}/users/${username}/avatar`;
+ return `${apiBaseUrl}/v2/users/${username}/avatar`;
}
putAvatar(username: string, data: Blob): Promise<string> {
return axios
- .put(`${apiBaseUrl}/users/${username}/avatar`, data, {
+ .put(`${apiBaseUrl}/v2/users/${username}/avatar`, data, {
headers: {
"Content-Type": data.type,
},
@@ -120,21 +106,12 @@ export class HttpUserClient implements IHttpUserClient {
.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}`)
+ .put(`${apiBaseUrl}/v2/users/${username}/permissions/${permission}`)
.then();
}
@@ -143,9 +120,22 @@ export class HttpUserClient implements IHttpUserClient {
permission: UserPermission
): Promise<void> {
return axios
- .delete(`${apiBaseUrl}/users/${username}/permissions/${permission}`)
+ .delete(`${apiBaseUrl}/v2/users/${username}/permissions/${permission}`)
.then();
}
+
+ changePassword(req: HttpChangePasswordRequest): Promise<void> {
+ return axios
+ .post(`${apiBaseUrl}/v2/self/changepassword`, req)
+ .then(undefined, (error: AxiosError) => {
+ const statusCode = error.response?.status;
+ if (statusCode === 422) {
+ throw new HttpChangePasswordBadCredentialError(error);
+ } else {
+ throw error;
+ }
+ });
+ }
}
let client: IHttpUserClient = new HttpUserClient();
diff --git a/FrontEnd/src/utilities/base64.ts b/FrontEnd/src/utilities/base64.ts
new file mode 100644
index 00000000..5f12414e
--- /dev/null
+++ b/FrontEnd/src/utilities/base64.ts
@@ -0,0 +1,15 @@
+import { Base64 } from "js-base64";
+
+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);
+ });
+}
diff --git a/FrontEnd/src/utilities/url.ts b/FrontEnd/src/utilities/url.ts
index 45ad0ab7..9700d861 100644
--- a/FrontEnd/src/utilities/url.ts
+++ b/FrontEnd/src/utilities/url.ts
@@ -1,4 +1,4 @@
-export function applyQueryParameters<T>(url: string, query: T): string {
+export function withQuery<T>(url: string, query: T): string {
if (query == null) return url;
const params = new URLSearchParams();