From f2ead327344fdacdf3fb1e761b4fb8ec89330f1e Mon Sep 17 00:00:00 2001 From: crupest Date: Mon, 25 Apr 2022 19:27:44 +0800 Subject: ... --- .../IntegratedTests/BookmarkTimelineTest.cs | 2 + .../IntegratedTests/HighlightTimelineTest.cs | 2 + .../Timeline.Tests/IntegratedTests/SearchTest.cs | 2 + .../Timeline.Tests/IntegratedTests/TimelineTest.cs | 1 + .../Timeline.Tests/IntegratedTests2/SelfTest.cs | 41 +++++++ .../IntegratedTests2/TimelineTest.cs | 2 +- .../IntegratedTests2/TimelineTest2.cs | 2 +- BackEnd/Timeline/Controllers/V2/SelfController.cs | 40 +++++++ BackEnd/Timeline/Models/Http/HttpTimeline.cs | 9 +- BackEnd/Timeline/Models/Http/HttpTimelinePost.cs | 9 +- FrontEnd/src/http/bookmark.ts | 11 +- FrontEnd/src/http/common.ts | 132 +++------------------ FrontEnd/src/http/timeline.ts | 10 +- FrontEnd/src/http/token.ts | 24 ++-- FrontEnd/src/http/user.ts | 60 ++++------ FrontEnd/src/utilities/base64.ts | 15 +++ FrontEnd/src/utilities/url.ts | 2 +- 17 files changed, 181 insertions(+), 183 deletions(-) create mode 100644 BackEnd/Timeline.Tests/IntegratedTests2/SelfTest.cs create mode 100644 BackEnd/Timeline/Controllers/V2/SelfController.cs create mode 100644 FrontEnd/src/utilities/base64.ts 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(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 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 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 /// /// Name of timeline. /// + [Obsolete("Use NameV2")] public string Name { get; set; } = default!; /// /// 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 /// /// Timeline name. /// + [Obsolete("Use TimelineNameV2.")] public string TimelineName { get; set; } = default!; /// /// 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> { - const url = applyQueryParameters( - `${apiBaseUrl}/v2/users/${username}/bookmarks`, - { page, pageSize } - ); + const url = withQuery(`${apiBaseUrl}/v2/users/${username}/bookmarks`, { + page, + pageSize, + }); return axios.get>(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 = tokenSubject.asObservable(); -export function base64(blob: Blob | string): Promise { - if (typeof blob === "string") { - return Promise.resolve(Base64.encode(blob)); - } - - return new Promise((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 -): 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(res: AxiosResponse): T { return res.data; } -export function catchIfStatusCodeIs< - TResult, - TErrorHandlerResult extends TResult | PromiseLike | null | undefined ->( - statusCode: number, - errorHandler: (error: AxiosError) => TErrorHandlerResult -): (error: AxiosError) => TErrorHandlerResult { - return (error: AxiosError) => { - if (extractStatusCode(error) == statusCode) { - return errorHandler(error); - } else { - throw error; - } - }; -} - -export function convertToIfStatusCodeIs( - statusCode: number, - newErrorType: { - new (innerError: AxiosError): NewError; - } -): (error: AxiosError) => never { - return catchIfStatusCodeIs(statusCode, (error) => { - throw new newErrorType(error); - }); -} - -export function catchIfErrorCodeIs< - TResult, - TErrorHandlerResult extends TResult | PromiseLike | null | undefined ->( - errorCode: number, - errorHandler: (error: AxiosError) => TErrorHandlerResult -): (error: AxiosError) => TErrorHandlerResult { - return (error: AxiosError) => { - if (extractErrorCode(error) == errorCode) { - return errorHandler(error); - } else { - throw error; - } - }; -} -export function convertToIfErrorCodeIs( - errorCode: number, - newErrorType: { - new (innerError: AxiosError): NewError; - } -): (error: AxiosError) => never { - return catchIfErrorCodeIs(errorCode, (error) => { - throw new newErrorType(error); - }); -} - -export function convertToNotModified( - error: AxiosError -): 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 { return axios - .get( - applyQueryParameters(`${apiBaseUrl}/timelines`, query) - ) + .get(withQuery(`${apiBaseUrl}/timelines`, query)) .then(extractResponseData); } @@ -243,7 +241,7 @@ export class HttpTimelineClient implements IHttpTimelineClient { ): Promise> { return axios .get>( - 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 { return axios - .post(`${apiBaseUrl}/token/create`, req, {}) - .then(extractResponseData) - .catch( - convertToIfErrorCodeIs(11010101, HttpCreateTokenBadCredentialError) - ); + .post(`${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 { return axios - .post(`${apiBaseUrl}/token/verify`, req) + .post(`${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; generateAvatarUrl(username: string): string; putAvatar(username: string, data: Blob): Promise; - changePassword(req: HttpChangePasswordRequest): Promise; putUserPermission( username: string, permission: UserPermission @@ -73,46 +59,46 @@ export interface IHttpUserClient { username: string, permission: UserPermission ): Promise; + changePassword(req: HttpChangePasswordRequest): Promise; } export class HttpUserClient implements IHttpUserClient { list(): Promise { return axios - .get(`${apiBaseUrl}/users`) + .get(`${apiBaseUrl}/v2/users`) .then(extractResponseData); } get(username: string): Promise { return axios - .get(`${apiBaseUrl}/users/${username}`) - .then(extractResponseData) - .catch(convertToIfStatusCodeIs(404, HttpUserNotExistError)); + .get(`${apiBaseUrl}/v2/users/${username}`) + .then(extractResponseData); } post(req: HttpCreateUserRequest): Promise { return axios - .post(`${apiBaseUrl}/users`, req) + .post(`${apiBaseUrl}/v2/users`, req) .then(extractResponseData) .then(); } patch(username: string, req: HttpUserPatchRequest): Promise { return axios - .patch(`${apiBaseUrl}/users/${username}`, req) + .patch(`${apiBaseUrl}/v2/users/${username}`, req) .then(extractResponseData); } delete(username: string): Promise { - 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 { 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 { - return axios - .post(`${apiBaseUrl}/userop/changepassword`, req) - .catch( - convertToIfErrorCodeIs(11020201, HttpChangePasswordBadCredentialError) - ) - .then(); - } - putUserPermission( username: string, permission: UserPermission ): Promise { 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 { return axios - .delete(`${apiBaseUrl}/users/${username}/permissions/${permission}`) + .delete(`${apiBaseUrl}/v2/users/${username}/permissions/${permission}`) .then(); } + + changePassword(req: HttpChangePasswordRequest): Promise { + 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 { + if (typeof blob === "string") { + return Promise.resolve(Base64.encode(blob)); + } + + return new Promise((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(url: string, query: T): string { +export function withQuery(url: string, query: T): string { if (query == null) return url; const params = new URLSearchParams(); -- cgit v1.2.3