Move to axios
This commit is contained in:
parent
9bf2dfd6fc
commit
2f4c74b8da
|
@ -38,6 +38,7 @@
|
|||
"@types/resize-observer-browser": "^0.1.7",
|
||||
"@types/validator": "^13.7.10",
|
||||
"ackee-tracker": "^5.1.0",
|
||||
"axios": "^1.2.1",
|
||||
"bootstrap": "^5.2.3",
|
||||
"caniuse-lite": "^1.0.30001441",
|
||||
"fuse.js": "^6.6.2",
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
Input,
|
||||
OnChanges,
|
||||
SimpleChanges,
|
||||
ViewChild,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { FileType, ImageFileType } from 'picsur-shared/dist/dto/mimes.dto';
|
||||
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
|
||||
|
@ -85,7 +85,7 @@ export class PicsurImgComponent implements OnChanges {
|
|||
|
||||
this.state = PicsurImgState.Canvas;
|
||||
} else {
|
||||
const result = await this.apiService.getBuffer(url);
|
||||
const result = await this.apiService.getBuffer(url).result;
|
||||
if (HasFailed(result)) return result;
|
||||
|
||||
const img = this.img.nativeElement;
|
||||
|
@ -99,12 +99,12 @@ export class PicsurImgComponent implements OnChanges {
|
|||
}
|
||||
|
||||
private async getFileType(url: string): AsyncFailable<FileType> {
|
||||
const response = await this.apiService.head(url);
|
||||
const response = await this.apiService.head(url).result;
|
||||
if (HasFailed(response)) {
|
||||
return response;
|
||||
}
|
||||
|
||||
const mimeHeader = response.get('content-type') ?? '';
|
||||
const mimeHeader = response['content-type'] ?? '';
|
||||
const mime = mimeHeader.split(';')[0];
|
||||
|
||||
return ParseMime2FileType(mime);
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import { MultiPartRequest } from './multi-part-request.dto';
|
||||
|
||||
export class ImagesUploadRequest implements MultiPartRequest {
|
||||
constructor(private images: File[]) {}
|
||||
|
||||
public createFormData(): FormData {
|
||||
const data = new FormData();
|
||||
for (let i = 0; i < this.images.length; i++) {
|
||||
data.append(`images[${i}]`, this.images[i]);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
|
@ -1,17 +1,23 @@
|
|||
import { Inject, Injectable } from '@angular/core';
|
||||
import { WINDOW } from '@ng-web-apis/common';
|
||||
import axios, {
|
||||
AxiosRequestConfig,
|
||||
AxiosResponse,
|
||||
AxiosResponseHeaders
|
||||
} from 'axios';
|
||||
import { ApiResponseSchema } from 'picsur-shared/dist/dto/api/api.dto';
|
||||
import { FileType2Ext } from 'picsur-shared/dist/dto/mimes.dto';
|
||||
import {
|
||||
AsyncFailable,
|
||||
Fail,
|
||||
Failure,
|
||||
FT,
|
||||
HasFailed,
|
||||
HasSuccess,
|
||||
HasSuccess
|
||||
} from 'picsur-shared/dist/types';
|
||||
import { ZodDtoStatic } from 'picsur-shared/dist/util/create-zod-dto';
|
||||
import { ParseMime2FileType } from 'picsur-shared/dist/util/parse-mime';
|
||||
import { Subject } from 'rxjs';
|
||||
import { BehaviorSubject, Observable, Subject } from 'rxjs';
|
||||
import { ApiBuffer } from 'src/app/models/dto/api-buffer.dto';
|
||||
import { ApiError } from 'src/app/models/dto/api-error.dto';
|
||||
import { z } from 'zod';
|
||||
|
@ -23,6 +29,37 @@ import { KeyStorageService } from '../storage/key-storage.service';
|
|||
Proud of this, it works so smoooth
|
||||
*/
|
||||
|
||||
interface RunningRequest<R> {
|
||||
uploadProgress: Observable<number>;
|
||||
downloadProgress: Observable<number>;
|
||||
result: AsyncFailable<R>;
|
||||
cancel: () => void;
|
||||
}
|
||||
|
||||
function MapRunningRequest<R, T>(
|
||||
runningRequest: RunningRequest<R>,
|
||||
map: (r: R) => AsyncFailable<T>,
|
||||
): RunningRequest<T> {
|
||||
return {
|
||||
...runningRequest,
|
||||
result: runningRequest.result.then(async (result) => {
|
||||
if (HasFailed(result)) return result;
|
||||
return map(result);
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function CreateFailedRunningRequest<R>(failure: Failure) {
|
||||
const subject = new Subject<number>();
|
||||
subject.complete();
|
||||
return {
|
||||
uploadProgress: subject.asObservable(),
|
||||
downloadProgress: subject.asObservable(),
|
||||
result: Promise.resolve(failure),
|
||||
cancel: () => {},
|
||||
} as RunningRequest<R>;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
|
@ -40,177 +77,196 @@ export class ApiService {
|
|||
@Inject(WINDOW) private readonly windowRef: Window,
|
||||
) {}
|
||||
|
||||
public async get<T extends z.AnyZodObject>(
|
||||
public get<T extends z.AnyZodObject>(
|
||||
type: ZodDtoStatic<T>,
|
||||
url: string,
|
||||
): AsyncFailable<z.infer<T>> {
|
||||
): RunningRequest<z.infer<T>> {
|
||||
return this.fetchSafeJson(type, url, { method: 'GET' });
|
||||
}
|
||||
|
||||
public async head(url: string): AsyncFailable<Headers> {
|
||||
public head(url: string): RunningRequest<AxiosResponseHeaders> {
|
||||
return this.fetchHead(url, { method: 'HEAD' });
|
||||
}
|
||||
|
||||
public async getBuffer(url: string): AsyncFailable<ApiBuffer> {
|
||||
public getBuffer(url: string): RunningRequest<ApiBuffer> {
|
||||
return this.fetchBuffer(url, { method: 'GET' });
|
||||
}
|
||||
|
||||
public async post<T extends z.AnyZodObject, W extends z.AnyZodObject>(
|
||||
public post<T extends z.AnyZodObject, W extends z.AnyZodObject>(
|
||||
sendType: ZodDtoStatic<T>,
|
||||
receiveType: ZodDtoStatic<W>,
|
||||
url: string,
|
||||
data: z.infer<T>,
|
||||
): AsyncFailable<z.infer<W>> {
|
||||
): RunningRequest<z.infer<W>> {
|
||||
const sendSchema = sendType.zodSchema;
|
||||
|
||||
const validateResult = sendSchema.safeParse(data);
|
||||
if (!validateResult.success) {
|
||||
return Fail(
|
||||
FT.SysValidation,
|
||||
'Something went wrong',
|
||||
validateResult.error,
|
||||
return CreateFailedRunningRequest(
|
||||
Fail(FT.SysValidation, 'Something went wrong', validateResult.error),
|
||||
);
|
||||
}
|
||||
|
||||
return this.fetchSafeJson(receiveType, url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(validateResult.data),
|
||||
data: validateResult.data,
|
||||
});
|
||||
}
|
||||
|
||||
public async postEmpty<T extends z.AnyZodObject>(
|
||||
public postEmpty<T extends z.AnyZodObject>(
|
||||
type: ZodDtoStatic<T>,
|
||||
url: string,
|
||||
): AsyncFailable<z.infer<T>> {
|
||||
): RunningRequest<z.infer<T>> {
|
||||
return this.fetchSafeJson(type, url, { method: 'POST' });
|
||||
}
|
||||
|
||||
public async postForm<T extends z.AnyZodObject>(
|
||||
public postForm<T extends z.AnyZodObject>(
|
||||
receiveType: ZodDtoStatic<T>,
|
||||
url: string,
|
||||
data: MultiPartRequest,
|
||||
): AsyncFailable<z.infer<T>> {
|
||||
): RunningRequest<z.infer<T>> {
|
||||
return this.fetchSafeJson(receiveType, url, {
|
||||
method: 'POST',
|
||||
body: data.createFormData(),
|
||||
data: data.createFormData(),
|
||||
});
|
||||
}
|
||||
|
||||
private async fetchSafeJson<T extends z.AnyZodObject>(
|
||||
private fetchSafeJson<T extends z.AnyZodObject>(
|
||||
type: ZodDtoStatic<T>,
|
||||
url: RequestInfo,
|
||||
options: RequestInit,
|
||||
): AsyncFailable<z.infer<T>> {
|
||||
url: string,
|
||||
options: AxiosRequestConfig,
|
||||
): RunningRequest<z.infer<T>> {
|
||||
const resultSchema = ApiResponseSchema(type.zodSchema as z.AnyZodObject);
|
||||
type resultType = z.infer<typeof resultSchema>;
|
||||
|
||||
let result = await this.fetchJsonAs<resultType>(url, options);
|
||||
if (HasFailed(result)) return result;
|
||||
let result = this.fetchJsonAs<resultType>(url, options);
|
||||
|
||||
const validateResult = resultSchema.safeParse(result);
|
||||
if (!validateResult.success) {
|
||||
return Fail(
|
||||
FT.SysValidation,
|
||||
'Something went wrong',
|
||||
validateResult.error,
|
||||
);
|
||||
}
|
||||
|
||||
if (validateResult.data.success === false)
|
||||
return Fail(FT.Unknown, result.data.message);
|
||||
|
||||
return validateResult.data.data;
|
||||
}
|
||||
|
||||
private async fetchJsonAs<T>(
|
||||
url: RequestInfo,
|
||||
options: RequestInit,
|
||||
): AsyncFailable<T> {
|
||||
const response = await this.fetch(url, options);
|
||||
if (HasFailed(response)) {
|
||||
return response;
|
||||
}
|
||||
try {
|
||||
return await response.json();
|
||||
} catch (e) {
|
||||
return Fail(FT.Internal, e);
|
||||
}
|
||||
}
|
||||
|
||||
private async fetchBuffer(
|
||||
url: RequestInfo,
|
||||
options: RequestInit,
|
||||
): AsyncFailable<ApiBuffer> {
|
||||
const response = await this.fetch(url, options);
|
||||
if (HasFailed(response)) return response;
|
||||
|
||||
if (!response.ok) return Fail(FT.Network, 'Recieved a non-ok response');
|
||||
|
||||
const mimeType = response.headers.get('Content-Type') ?? 'other/unknown';
|
||||
let name = response.headers.get('Content-Disposition');
|
||||
if (!name) {
|
||||
if (typeof url === 'string') {
|
||||
name = url.split('/').pop() ?? 'unnamed';
|
||||
} else {
|
||||
name = url.url.split('/').pop() ?? 'unnamed';
|
||||
return MapRunningRequest(result, async (r) => {
|
||||
const validateResult = resultSchema.safeParse(r);
|
||||
if (!validateResult.success) {
|
||||
return Fail(
|
||||
FT.SysValidation,
|
||||
'Something went wrong',
|
||||
validateResult.error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const filetype = ParseMime2FileType(mimeType);
|
||||
if (HasSuccess(filetype)) {
|
||||
const ext = FileType2Ext(filetype.identifier);
|
||||
if (HasSuccess(ext)) {
|
||||
if (!name.endsWith(ext)) {
|
||||
name += '.' + ext;
|
||||
if (validateResult.data.success === false)
|
||||
return Fail(FT.Unknown, r.data.message);
|
||||
|
||||
return validateResult.data.data;
|
||||
});
|
||||
}
|
||||
|
||||
private fetchJsonAs<T>(
|
||||
url: string,
|
||||
options: AxiosRequestConfig,
|
||||
): RunningRequest<T> {
|
||||
const response = this.fetch(url, {
|
||||
...options,
|
||||
responseType: 'json',
|
||||
});
|
||||
|
||||
return MapRunningRequest(response, async (r) => r.data);
|
||||
}
|
||||
|
||||
private fetchBuffer(
|
||||
url: string,
|
||||
options: AxiosRequestConfig,
|
||||
): RunningRequest<ApiBuffer> {
|
||||
const response = this.fetch(url, {
|
||||
...options,
|
||||
responseType: 'arraybuffer',
|
||||
});
|
||||
|
||||
return MapRunningRequest(response, async (r) => {
|
||||
const mimeType = r.headers['Content-Type'] ?? 'other/unknown';
|
||||
let name = r.headers['Content-Disposition'];
|
||||
if (!name) {
|
||||
name = url.split('/').pop() ?? 'unnamed';
|
||||
}
|
||||
|
||||
const filetype = ParseMime2FileType(mimeType);
|
||||
if (HasSuccess(filetype)) {
|
||||
const ext = FileType2Ext(filetype.identifier);
|
||||
if (HasSuccess(ext)) {
|
||||
if (!name.endsWith(ext)) {
|
||||
name += '.' + ext;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
return {
|
||||
buffer: arrayBuffer,
|
||||
buffer: r.data,
|
||||
mimeType,
|
||||
name,
|
||||
};
|
||||
} catch (e) {
|
||||
return Fail(FT.Internal, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async fetchHead(
|
||||
url: RequestInfo,
|
||||
options: RequestInit,
|
||||
): AsyncFailable<Headers> {
|
||||
const response = await this.fetch(url, options);
|
||||
if (HasFailed(response)) return response;
|
||||
private fetchHead(
|
||||
url: string,
|
||||
options: AxiosRequestConfig,
|
||||
): RunningRequest<AxiosResponseHeaders> {
|
||||
const response = this.fetch(url, options);
|
||||
|
||||
if (!response.ok) return Fail(FT.Network, 'Recieved a non-ok response');
|
||||
|
||||
return response.headers;
|
||||
return MapRunningRequest(response, async (r) => {
|
||||
return r.headers as AxiosResponseHeaders;
|
||||
});
|
||||
}
|
||||
|
||||
private async fetch(
|
||||
url: RequestInfo,
|
||||
options: RequestInit,
|
||||
): AsyncFailable<Response> {
|
||||
try {
|
||||
const key = this.keyService.get();
|
||||
const isJSON = typeof options.body === 'string';
|
||||
private fetch(
|
||||
url: string,
|
||||
options: AxiosRequestConfig,
|
||||
): RunningRequest<AxiosResponse> {
|
||||
const key = this.keyService.get();
|
||||
const isJSON = typeof options.data === 'string';
|
||||
|
||||
const headers: any = options.headers || {};
|
||||
if (key !== null)
|
||||
headers['Authorization'] = `Bearer ${this.keyService.get()}`;
|
||||
if (isJSON) headers['Content-Type'] = 'application/json';
|
||||
options.headers = headers;
|
||||
const headers: any = options.headers || {};
|
||||
if (key !== null)
|
||||
headers['Authorization'] = `Bearer ${this.keyService.get()}`;
|
||||
if (isJSON) headers['Content-Type'] = 'application/json';
|
||||
options.headers = headers;
|
||||
|
||||
return await this.windowRef.fetch(url, options);
|
||||
} catch (e) {
|
||||
this.errorSubject.next({
|
||||
error: e,
|
||||
url,
|
||||
});
|
||||
return Fail(FT.Network, e);
|
||||
}
|
||||
const uploadProgress = new BehaviorSubject<number>(0);
|
||||
const downloadProgress = new BehaviorSubject<number>(0);
|
||||
const abortController = new AbortController();
|
||||
|
||||
const resultPromise: AsyncFailable<AxiosResponse> = (async () => {
|
||||
try {
|
||||
const result = await axios.request({
|
||||
url,
|
||||
onDownloadProgress: (e) => {
|
||||
downloadProgress.next(e.loaded / (e.total ?? 1000000) * 100);
|
||||
},
|
||||
onUploadProgress: (e) => {
|
||||
uploadProgress.next(e.loaded / (e.total ?? 1000000) * 100);
|
||||
},
|
||||
signal: abortController.signal,
|
||||
...options,
|
||||
});
|
||||
|
||||
uploadProgress.complete();
|
||||
downloadProgress.complete();
|
||||
|
||||
if (result.status < 200 || result.status >= 300) {
|
||||
return Fail(FT.Network, 'Recieved a non-ok response');
|
||||
}
|
||||
return result;
|
||||
} catch (e) {
|
||||
return Fail(FT.Network, e);
|
||||
}
|
||||
})();
|
||||
|
||||
return {
|
||||
result: resultPromise,
|
||||
uploadProgress,
|
||||
downloadProgress,
|
||||
cancel: () => {
|
||||
abortController.abort();
|
||||
uploadProgress.complete();
|
||||
downloadProgress.complete();
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
ApiKeyListRequest,
|
||||
ApiKeyListResponse,
|
||||
ApiKeyUpdateRequest,
|
||||
ApiKeyUpdateResponse,
|
||||
ApiKeyUpdateResponse
|
||||
} from 'picsur-shared/dist/dto/api/apikeys.dto';
|
||||
import { EApiKey } from 'picsur-shared/dist/entities/apikey.entity';
|
||||
import { AsyncFailable } from 'picsur-shared/dist/types';
|
||||
|
@ -26,7 +26,7 @@ export class ApiKeysService {
|
|||
page: number,
|
||||
userID?: string,
|
||||
): AsyncFailable<FindResult<EApiKey>> {
|
||||
const response = await this.api.post(
|
||||
return await this.api.post(
|
||||
ApiKeyListRequest,
|
||||
ApiKeyListResponse,
|
||||
'/api/apikeys/list',
|
||||
|
@ -35,9 +35,7 @@ export class ApiKeysService {
|
|||
page,
|
||||
user_id: userID,
|
||||
},
|
||||
);
|
||||
|
||||
return response;
|
||||
).result;
|
||||
}
|
||||
|
||||
public async getApiKey(id: string): AsyncFailable<EApiKey> {
|
||||
|
@ -48,14 +46,12 @@ export class ApiKeysService {
|
|||
{
|
||||
id,
|
||||
},
|
||||
);
|
||||
).result;
|
||||
}
|
||||
|
||||
public async createApiKey(): AsyncFailable<EApiKey> {
|
||||
return await this.api.postEmpty(
|
||||
ApiKeyCreateResponse,
|
||||
'/api/apikeys/create',
|
||||
);
|
||||
return await this.api.postEmpty(ApiKeyCreateResponse, '/api/apikeys/create')
|
||||
.result;
|
||||
}
|
||||
|
||||
public async updateApiKey(id: string, name: string): AsyncFailable<EApiKey> {
|
||||
|
@ -67,7 +63,7 @@ export class ApiKeysService {
|
|||
id,
|
||||
name,
|
||||
},
|
||||
);
|
||||
).result;
|
||||
}
|
||||
|
||||
public async deleteApiKey(id: string): AsyncFailable<Omit<EApiKey, 'id'>> {
|
||||
|
@ -78,6 +74,6 @@ export class ApiKeysService {
|
|||
{
|
||||
id,
|
||||
},
|
||||
);
|
||||
).result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
HasSuccess,
|
||||
Open
|
||||
} from 'picsur-shared/dist/types/failable';
|
||||
import { ImagesUploadRequest } from 'src/app/models/dto/images-upload-request.dto';
|
||||
import { ImageUploadRequest } from '../../models/dto/image-upload-request.dto';
|
||||
import { ApiService } from './api.service';
|
||||
import { InfoService } from './info.service';
|
||||
|
@ -43,13 +44,28 @@ export class ImageService {
|
|||
ImageUploadResponse,
|
||||
'/api/image/upload',
|
||||
new ImageUploadRequest(image),
|
||||
);
|
||||
).result;
|
||||
|
||||
return Open(result, 'id');
|
||||
}
|
||||
|
||||
public async UploadImages(images: File[]): AsyncFailable<string[]> {
|
||||
console.log('Uploading images', images);
|
||||
|
||||
// Split into chunks of 20
|
||||
const groups = this.chunks(images, 20);
|
||||
|
||||
const result = await this.api.postForm(
|
||||
ImageUploadResponse,
|
||||
'/api/image/upload/bulk',
|
||||
new ImagesUploadRequest(images),
|
||||
);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public async GetImageMeta(image: string): AsyncFailable<ImageMetaResponse> {
|
||||
return await this.api.get(ImageMetaResponse, `/i/meta/${image}`);
|
||||
return await this.api.get(ImageMetaResponse, `/i/meta/${image}`).result;
|
||||
}
|
||||
|
||||
public async ListAllImages(
|
||||
|
@ -66,7 +82,7 @@ export class ImageService {
|
|||
page,
|
||||
user_id: userID,
|
||||
},
|
||||
);
|
||||
).result;
|
||||
}
|
||||
|
||||
public async ListMyImages(
|
||||
|
@ -93,7 +109,7 @@ export class ImageService {
|
|||
id,
|
||||
...settings,
|
||||
},
|
||||
);
|
||||
).result;
|
||||
}
|
||||
|
||||
public async DeleteImages(
|
||||
|
@ -106,7 +122,7 @@ export class ImageService {
|
|||
{
|
||||
ids: images,
|
||||
},
|
||||
);
|
||||
).result;
|
||||
}
|
||||
|
||||
public async DeleteImage(image: string): AsyncFailable<EImage> {
|
||||
|
@ -192,4 +208,13 @@ export class ImageService {
|
|||
): ImageLinks {
|
||||
return this.CreateImageLinks(this.GetImageURL(imageID, mime, true), name);
|
||||
}
|
||||
|
||||
private chunks<T>(arr: T[], size: number): T[][] {
|
||||
let result = [];
|
||||
for (let i = 0; i < arr.length; i += size) {
|
||||
result.push(arr.slice(i, size + i));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@ export class InfoService {
|
|||
}
|
||||
|
||||
private async updateInfo(): AsyncFailable<ServerInfo> {
|
||||
const response = await this.api.get(InfoResponse, '/api/info');
|
||||
const response = await this.api.get(InfoResponse, '/api/info').result;
|
||||
if (HasFailed(response)) return response;
|
||||
|
||||
this.infoSubject.next(response);
|
||||
|
|
|
@ -69,7 +69,7 @@ export class PermissionService {
|
|||
const got = await this.api.get(
|
||||
UserMePermissionsResponse,
|
||||
'/api/user/me/permissions',
|
||||
);
|
||||
).result;
|
||||
if (HasFailed(got)) return got;
|
||||
|
||||
this.permissionsSubject.next(got.permissions);
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
RoleInfoResponse,
|
||||
RoleListResponse,
|
||||
RoleUpdateRequest,
|
||||
RoleUpdateResponse,
|
||||
RoleUpdateResponse
|
||||
} from 'picsur-shared/dist/dto/api/roles.dto';
|
||||
import { ERole } from 'picsur-shared/dist/entities/role.entity';
|
||||
import { AsyncFailable, Open } from 'picsur-shared/dist/types';
|
||||
|
@ -22,7 +22,8 @@ export class RolesService {
|
|||
constructor(private readonly api: ApiService) {}
|
||||
|
||||
public async getRoles(): AsyncFailable<ERole[]> {
|
||||
const response = await this.api.get(RoleListResponse, '/api/roles/list');
|
||||
const response = await this.api.get(RoleListResponse, '/api/roles/list')
|
||||
.result;
|
||||
|
||||
return Open(response, 'results');
|
||||
}
|
||||
|
@ -35,7 +36,7 @@ export class RolesService {
|
|||
{
|
||||
name,
|
||||
},
|
||||
);
|
||||
).result;
|
||||
}
|
||||
|
||||
public async createRole(role: RoleModel): AsyncFailable<ERole> {
|
||||
|
@ -44,7 +45,7 @@ export class RolesService {
|
|||
RoleCreateResponse,
|
||||
'/api/roles/create',
|
||||
role,
|
||||
);
|
||||
).result;
|
||||
}
|
||||
|
||||
public async updateRole(role: RoleModel): AsyncFailable<ERole> {
|
||||
|
@ -53,7 +54,7 @@ export class RolesService {
|
|||
RoleUpdateResponse,
|
||||
'/api/roles/update',
|
||||
role,
|
||||
);
|
||||
).result;
|
||||
}
|
||||
|
||||
public async deleteRole(name: string): AsyncFailable<ERole> {
|
||||
|
@ -64,6 +65,6 @@ export class RolesService {
|
|||
{
|
||||
name,
|
||||
},
|
||||
);
|
||||
).result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ export class StaticInfoService {
|
|||
SoulBoundRoles: [],
|
||||
UndeletableRoles: [],
|
||||
},
|
||||
() => this.api.get(SpecialRolesResponse, '/api/roles/special'),
|
||||
() => this.api.get(SpecialRolesResponse, '/api/roles/special').result,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ export class StaticInfoService {
|
|||
LockedLoginUsersList: [],
|
||||
UndeletableUsersList: [],
|
||||
},
|
||||
() => this.api.get(GetSpecialUsersResponse, '/api/user/special'),
|
||||
() => this.api.get(GetSpecialUsersResponse, '/api/user/special').result,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,7 @@ export class StaticInfoService {
|
|||
const res = await this.api.get(
|
||||
AllPermissionsResponse,
|
||||
'/api/info/permissions',
|
||||
);
|
||||
).result;
|
||||
return Open(res, 'permissions');
|
||||
},
|
||||
);
|
||||
|
|
|
@ -4,19 +4,19 @@ import {
|
|||
GetPreferenceResponse,
|
||||
MultiplePreferencesResponse,
|
||||
UpdatePreferenceRequest,
|
||||
UpdatePreferenceResponse,
|
||||
UpdatePreferenceResponse
|
||||
} from 'picsur-shared/dist/dto/api/pref.dto';
|
||||
import { Permission } from 'picsur-shared/dist/dto/permissions.enum';
|
||||
import {
|
||||
DecodedPref,
|
||||
PrefValueType,
|
||||
PrefValueType
|
||||
} from 'picsur-shared/dist/dto/preferences.dto';
|
||||
import {
|
||||
AsyncFailable,
|
||||
Fail,
|
||||
FT,
|
||||
HasFailed,
|
||||
Map,
|
||||
Map
|
||||
} from 'picsur-shared/dist/types';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { ErrorService } from 'src/app/util/error-manager/error.service';
|
||||
|
@ -69,7 +69,7 @@ export class SysPrefService {
|
|||
const response = await this.api.get(
|
||||
MultiplePreferencesResponse,
|
||||
'/api/pref/sys',
|
||||
);
|
||||
).result;
|
||||
|
||||
return Map(response, (pref) => {
|
||||
this.sysprefObservable.next(pref.results);
|
||||
|
@ -89,7 +89,7 @@ export class SysPrefService {
|
|||
const response = await this.api.get(
|
||||
GetPreferenceResponse,
|
||||
`/api/pref/sys/${key}`,
|
||||
);
|
||||
).result;
|
||||
|
||||
if (!HasFailed(response)) this.updatePrefArray(response);
|
||||
return response;
|
||||
|
@ -110,7 +110,7 @@ export class SysPrefService {
|
|||
UpdatePreferenceResponse,
|
||||
`/api/pref/sys/${key}`,
|
||||
{ value },
|
||||
);
|
||||
).result;
|
||||
|
||||
if (!HasFailed(response)) this.updatePrefArray(response);
|
||||
return response;
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
UserListRequest,
|
||||
UserListResponse,
|
||||
UserUpdateRequest,
|
||||
UserUpdateResponse,
|
||||
UserUpdateResponse
|
||||
} from 'picsur-shared/dist/dto/api/user-manage.dto';
|
||||
import { EUser } from 'picsur-shared/dist/entities/user.entity';
|
||||
import { AsyncFailable } from 'picsur-shared/dist/types';
|
||||
|
@ -27,7 +27,7 @@ export class UserAdminService {
|
|||
UserInfoResponse,
|
||||
'api/user/info',
|
||||
{ id },
|
||||
);
|
||||
).result;
|
||||
}
|
||||
|
||||
public async getUsers(
|
||||
|
@ -42,7 +42,7 @@ export class UserAdminService {
|
|||
count,
|
||||
page,
|
||||
},
|
||||
);
|
||||
).result;
|
||||
}
|
||||
|
||||
public async createUser(user: UserCreateRequest): AsyncFailable<EUser> {
|
||||
|
@ -51,7 +51,7 @@ export class UserAdminService {
|
|||
UserCreateResponse,
|
||||
'/api/user/create',
|
||||
user,
|
||||
);
|
||||
).result;
|
||||
}
|
||||
|
||||
public async updateUser(user: UserUpdateRequest): AsyncFailable<EUser> {
|
||||
|
@ -60,7 +60,7 @@ export class UserAdminService {
|
|||
UserUpdateResponse,
|
||||
'/api/user/update',
|
||||
user,
|
||||
);
|
||||
).result;
|
||||
}
|
||||
|
||||
public async deleteUser(id: string): AsyncFailable<Omit<EUser, 'id'>> {
|
||||
|
@ -69,6 +69,6 @@ export class UserAdminService {
|
|||
UserDeleteResponse,
|
||||
'/api/user/delete',
|
||||
{ id },
|
||||
);
|
||||
).result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
UserLoginResponse,
|
||||
UserMeResponse,
|
||||
UserRegisterRequest,
|
||||
UserRegisterResponse,
|
||||
UserRegisterResponse
|
||||
} from 'picsur-shared/dist/dto/api/user.dto';
|
||||
import { JwtDataSchema } from 'picsur-shared/dist/dto/jwt.dto';
|
||||
import { EUser } from 'picsur-shared/dist/entities/user.entity';
|
||||
|
@ -16,7 +16,7 @@ import {
|
|||
Fail,
|
||||
FT,
|
||||
HasFailed,
|
||||
Open,
|
||||
Open
|
||||
} from 'picsur-shared/dist/types';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { Logger } from '../logger/logger.service';
|
||||
|
@ -72,7 +72,7 @@ export class UserService {
|
|||
username,
|
||||
password,
|
||||
},
|
||||
);
|
||||
).result;
|
||||
if (HasFailed(response)) return response;
|
||||
|
||||
// Set the key so the apiservice can use it
|
||||
|
@ -94,7 +94,7 @@ export class UserService {
|
|||
{
|
||||
username,
|
||||
},
|
||||
),
|
||||
).result,
|
||||
'available',
|
||||
);
|
||||
}
|
||||
|
@ -111,7 +111,7 @@ export class UserService {
|
|||
username,
|
||||
password,
|
||||
},
|
||||
);
|
||||
).result;
|
||||
}
|
||||
|
||||
public async logout(): AsyncFailable<EUser> {
|
||||
|
@ -147,7 +147,7 @@ export class UserService {
|
|||
|
||||
// This actually fetches up to date information from the server
|
||||
private async fetchUser(): AsyncFailable<EUser> {
|
||||
const got = await this.api.get(UserMeResponse, '/api/user/me');
|
||||
const got = await this.api.get(UserMeResponse, '/api/user/me').result;
|
||||
if (HasFailed(got)) return got;
|
||||
|
||||
this.key.set(got.token);
|
||||
|
|
|
@ -4,19 +4,19 @@ import {
|
|||
GetPreferenceResponse,
|
||||
MultiplePreferencesResponse,
|
||||
UpdatePreferenceRequest,
|
||||
UpdatePreferenceResponse,
|
||||
UpdatePreferenceResponse
|
||||
} from 'picsur-shared/dist/dto/api/pref.dto';
|
||||
import { Permission } from 'picsur-shared/dist/dto/permissions.enum';
|
||||
import {
|
||||
DecodedPref,
|
||||
PrefValueType,
|
||||
PrefValueType
|
||||
} from 'picsur-shared/dist/dto/preferences.dto';
|
||||
import {
|
||||
AsyncFailable,
|
||||
Fail,
|
||||
FT,
|
||||
HasFailed,
|
||||
Map,
|
||||
Map
|
||||
} from 'picsur-shared/dist/types';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { ErrorService } from 'src/app/util/error-manager/error.service';
|
||||
|
@ -69,7 +69,7 @@ export class UsrPrefService {
|
|||
const response = await this.api.get(
|
||||
MultiplePreferencesResponse,
|
||||
'/api/pref/usr',
|
||||
);
|
||||
).result;
|
||||
|
||||
return Map(response, (pref) => {
|
||||
this.usrprefObservable.next(pref.results);
|
||||
|
@ -89,7 +89,7 @@ export class UsrPrefService {
|
|||
const response = await this.api.get(
|
||||
GetPreferenceResponse,
|
||||
`/api/pref/usr/${key}`,
|
||||
);
|
||||
).result;
|
||||
|
||||
if (!HasFailed(response)) this.updatePrefArray(response);
|
||||
return response;
|
||||
|
@ -110,7 +110,7 @@ export class UsrPrefService {
|
|||
UpdatePreferenceResponse,
|
||||
`/api/pref/usr/${key}`,
|
||||
{ value },
|
||||
);
|
||||
).result;
|
||||
|
||||
if (!HasFailed(response)) this.updatePrefArray(response);
|
||||
return response;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="dialog-text">
|
||||
<h2>Downloading {{ data.name }}...</h2>
|
||||
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
|
||||
<mat-progress-bar mode="determinate" [value]="progress | async"></mat-progress-bar>
|
||||
</div>
|
||||
<div class="dialog-buttons"></div>
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import { Component, Inject } from '@angular/core';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
export interface DownloadDialogData {
|
||||
name: string;
|
||||
progress?: Observable<number>;
|
||||
}
|
||||
|
||||
@Component({
|
||||
|
@ -10,8 +12,12 @@ export interface DownloadDialogData {
|
|||
templateUrl: './download-dialog.component.html',
|
||||
})
|
||||
export class DownloadDialogComponent {
|
||||
public progress: Observable<number>;
|
||||
|
||||
constructor(
|
||||
public readonly dialogRef: MatDialogRef<DownloadDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public readonly data: DownloadDialogData,
|
||||
) {}
|
||||
) {
|
||||
this.progress = data.progress ?? new Observable<number>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Fail, FT, HasFailed } from 'picsur-shared/dist/types';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ApiService } from 'src/app/services/api/api.service';
|
||||
import { Logger } from 'src/app/services/logger/logger.service';
|
||||
import { DownloadDialogComponent } from '../dialog-manager/download-dialog/download-dialog.component';
|
||||
|
@ -20,9 +21,12 @@ export class DownloadService {
|
|||
private readonly errorService: ErrorService,
|
||||
) {}
|
||||
|
||||
public showDownloadDialog(filename: string): () => void {
|
||||
public showDownloadDialog(
|
||||
filename: string,
|
||||
progress?: Observable<number>,
|
||||
): () => void {
|
||||
const ref = this.dialog.open(DownloadDialogComponent, {
|
||||
data: { name: filename },
|
||||
data: { name: filename, progress: progress },
|
||||
disableClose: true,
|
||||
closeOnNavigation: false,
|
||||
});
|
||||
|
@ -31,11 +35,17 @@ export class DownloadService {
|
|||
}
|
||||
|
||||
public async downloadFile(url: string) {
|
||||
const closeDialog = this.showDownloadDialog('image');
|
||||
|
||||
const file = await this.api.getBuffer(url);
|
||||
if (HasFailed(file))
|
||||
|
||||
const request = this.api.getBuffer(url);
|
||||
const closeDialog = this.showDownloadDialog('image', request.downloadProgress);
|
||||
|
||||
const file = await request.result;
|
||||
|
||||
if (HasFailed(file)){
|
||||
closeDialog();
|
||||
return this.errorService.showFailure(file, this.logger);
|
||||
}
|
||||
|
||||
this.util.downloadBuffer(file.buffer, file.name, file.mimeType);
|
||||
|
||||
|
@ -80,7 +90,7 @@ export class DownloadService {
|
|||
url,
|
||||
};
|
||||
} else {
|
||||
const image = await this.api.getBuffer(url);
|
||||
const image = await this.api.getBuffer(url).result;
|
||||
if (HasFailed(image))
|
||||
return this.errorService.showFailure(image, this.logger);
|
||||
|
||||
|
|
65
yarn.lock
65
yarn.lock
|
@ -4354,6 +4354,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"asynckit@npm:^0.4.0":
|
||||
version: 0.4.0
|
||||
resolution: "asynckit@npm:0.4.0"
|
||||
checksum: 7b78c451df768adba04e2d02e63e2d0bf3b07adcd6e42b4cf665cb7ce899bedd344c69a1dcbce355b5f972d597b25aaa1c1742b52cffd9caccb22f348114f6be
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"atomic-sleep@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "atomic-sleep@npm:1.0.0"
|
||||
|
@ -4390,6 +4397,17 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"axios@npm:^1.2.1":
|
||||
version: 1.2.1
|
||||
resolution: "axios@npm:1.2.1"
|
||||
dependencies:
|
||||
follow-redirects: ^1.15.0
|
||||
form-data: ^4.0.0
|
||||
proxy-from-env: ^1.1.0
|
||||
checksum: c4dc4e119064c9aed09a3de309bedb797a139a6fb372223aafe3e0c10a7d4a14e4d3e9c9d309467fadb9d2b490b891ee3df96ef5b55716bb971910466ff9f0c5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"babel-loader@npm:9.1.0":
|
||||
version: 9.1.0
|
||||
resolution: "babel-loader@npm:9.1.0"
|
||||
|
@ -5041,6 +5059,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"combined-stream@npm:^1.0.8":
|
||||
version: 1.0.8
|
||||
resolution: "combined-stream@npm:1.0.8"
|
||||
dependencies:
|
||||
delayed-stream: ~1.0.0
|
||||
checksum: 49fa4aeb4916567e33ea81d088f6584749fc90c7abec76fd516bf1c5aa5c79f3584b5ba3de6b86d26ddd64bae5329c4c7479343250cfe71c75bb366eae53bb7c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"commander@npm:4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "commander@npm:4.1.1"
|
||||
|
@ -5411,6 +5438,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"delayed-stream@npm:~1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "delayed-stream@npm:1.0.0"
|
||||
checksum: 46fe6e83e2cb1d85ba50bd52803c68be9bd953282fa7096f51fc29edd5d67ff84ff753c51966061e5ba7cb5e47ef6d36a91924eddb7f3f3483b1c560f77a0020
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"delegates@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "delegates@npm:1.0.0"
|
||||
|
@ -6546,6 +6580,16 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"follow-redirects@npm:^1.15.0":
|
||||
version: 1.15.2
|
||||
resolution: "follow-redirects@npm:1.15.2"
|
||||
peerDependenciesMeta:
|
||||
debug:
|
||||
optional: true
|
||||
checksum: faa66059b66358ba65c234c2f2a37fcec029dc22775f35d9ad6abac56003268baf41e55f9ee645957b32c7d9f62baf1f0b906e68267276f54ec4b4c597c2b190
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fork-ts-checker-webpack-plugin@npm:7.2.13":
|
||||
version: 7.2.13
|
||||
resolution: "fork-ts-checker-webpack-plugin@npm:7.2.13"
|
||||
|
@ -6573,6 +6617,17 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"form-data@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "form-data@npm:4.0.0"
|
||||
dependencies:
|
||||
asynckit: ^0.4.0
|
||||
combined-stream: ^1.0.8
|
||||
mime-types: ^2.1.12
|
||||
checksum: 01135bf8675f9d5c61ff18e2e2932f719ca4de964e3be90ef4c36aacfc7b9cb2fceb5eca0b7e0190e3383fe51c5b37f4cb80b62ca06a99aaabfcfd6ac7c9328c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"formdata-polyfill@npm:^4.0.10":
|
||||
version: 4.0.10
|
||||
resolution: "formdata-polyfill@npm:4.0.10"
|
||||
|
@ -8199,7 +8254,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mime-types@npm:^2.1.27, mime-types@npm:^2.1.31, mime-types@npm:~2.1.17, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34":
|
||||
"mime-types@npm:^2.1.12, mime-types@npm:^2.1.27, mime-types@npm:^2.1.31, mime-types@npm:~2.1.17, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34":
|
||||
version: 2.1.35
|
||||
resolution: "mime-types@npm:2.1.35"
|
||||
dependencies:
|
||||
|
@ -9532,6 +9587,7 @@ __metadata:
|
|||
"@types/resize-observer-browser": ^0.1.7
|
||||
"@types/validator": ^13.7.10
|
||||
ackee-tracker: ^5.1.0
|
||||
axios: ^1.2.1
|
||||
bootstrap: ^5.2.3
|
||||
caniuse-lite: ^1.0.30001441
|
||||
fuse.js: ^6.6.2
|
||||
|
@ -9905,6 +9961,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"proxy-from-env@npm:^1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "proxy-from-env@npm:1.1.0"
|
||||
checksum: ed7fcc2ba0a33404958e34d95d18638249a68c430e30fcb6c478497d72739ba64ce9810a24f53a7d921d0c065e5b78e3822759800698167256b04659366ca4d4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"prr@npm:~1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "prr@npm:1.0.1"
|
||||
|
|
Loading…
Reference in New Issue