From f69e455996543317e83f07f5e9213f45580d231d Mon Sep 17 00:00:00 2001 From: rubikscraft Date: Sun, 25 Dec 2022 23:25:39 +0100 Subject: [PATCH] Finish hostname override --- .../view-speeddial.component.ts | 10 +--- frontend/src/app/services/api/api.service.ts | 6 +-- .../src/app/services/api/image.service.ts | 30 +++++------ frontend/src/app/services/api/info.service.ts | 41 ++++++++------- frontend/src/app/services/api/user.service.ts | 8 +-- .../src/app/services/storage/cache.service.ts | 32 ++++++++++++ .../services/storage/info-storage.service.ts | 50 +++++++++++++++++++ ...{key.service.ts => key-storage.service.ts} | 2 +- .../src/app/workers/qoi-worker.service.ts | 4 +- shared/src/dto/sys-preferences.enum.ts | 7 +-- 10 files changed, 136 insertions(+), 54 deletions(-) create mode 100644 frontend/src/app/services/storage/info-storage.service.ts rename frontend/src/app/services/storage/{key.service.ts => key-storage.service.ts} (94%) diff --git a/frontend/src/app/routes/view/view-speeddial/view-speeddial.component.ts b/frontend/src/app/routes/view/view-speeddial/view-speeddial.component.ts index cd6125b..0cc646d 100644 --- a/frontend/src/app/routes/view/view-speeddial/view-speeddial.component.ts +++ b/frontend/src/app/routes/view/view-speeddial/view-speeddial.component.ts @@ -93,10 +93,7 @@ export class ViewSpeeddialComponent implements OnInit { if (this.image === null) return; this.downloadService.downloadFile( - this.imageService.CreateImageLinksFromID( - this.image?.id, - this.selectedFormat, - ).source, + this.imageService.GetImageURL(this.image?.id, this.selectedFormat), ); } @@ -104,10 +101,7 @@ export class ViewSpeeddialComponent implements OnInit { if (this.image === null) return; this.downloadService.shareFile( - this.imageService.CreateImageLinksFromID( - this.image?.id, - this.selectedFormat, - ).source, + this.imageService.GetImageURL(this.image?.id, this.selectedFormat), ); } diff --git a/frontend/src/app/services/api/api.service.ts b/frontend/src/app/services/api/api.service.ts index 831e147..f093848 100644 --- a/frontend/src/app/services/api/api.service.ts +++ b/frontend/src/app/services/api/api.service.ts @@ -7,7 +7,7 @@ import { Fail, 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'; @@ -17,7 +17,7 @@ import { ApiError } from 'src/app/models/dto/api-error.dto'; import { z } from 'zod'; import { MultiPartRequest } from '../../models/dto/multi-part-request.dto'; import { Logger } from '../logger/logger.service'; -import { KeyService } from '../storage/key.service'; +import { KeyStorageService } from '../storage/key-storage.service'; /* Proud of this, it works so smoooth @@ -36,7 +36,7 @@ export class ApiService { } constructor( - private readonly keyService: KeyService, + private readonly keyService: KeyStorageService, @Inject(WINDOW) private readonly windowRef: Window, ) {} diff --git a/frontend/src/app/services/api/image.service.ts b/frontend/src/app/services/api/image.service.ts index 32c86ba..85bcfb0 100644 --- a/frontend/src/app/services/api/image.service.ts +++ b/frontend/src/app/services/api/image.service.ts @@ -125,8 +125,9 @@ export class ImageService { // Non api calls - public GetImageURL(image: string, filetype: string | null): string { - const baseURL = this.infoService.getHostname(); + // Use for native images + public GetImageURL(image: string, filetype: string | null, allowOverride = false): string { + const baseURL = this.infoService.getHostname(allowOverride); const extension = FileType2Ext(filetype ?? ''); return `${baseURL}/i/${image}${ @@ -134,12 +135,23 @@ export class ImageService { }`; } + // Use for user facing urls + public CreateImageLinks(imageURL: string): ImageLinks { + return { + source: imageURL, + markdown: `![image](${imageURL})`, + html: `image`, + rst: `.. image:: ${imageURL}`, + bbcode: `[img]${imageURL}[/img]`, + }; + } + public GetImageURLCustomized( image: string, filetype: string | null, options: ImageRequestParams, ): string { - const baseURL = this.GetImageURL(image, filetype); + const baseURL = this.GetImageURL(image, filetype, true); const betterOptions = ImageRequestParams.zodSchema.safeParse(options); if (!betterOptions.success) return baseURL; @@ -169,20 +181,10 @@ export class ImageService { return baseURL + '?' + queryParams.join('&'); } - public CreateImageLinks(imageURL: string): ImageLinks { - return { - source: imageURL, - markdown: `![image](${imageURL})`, - html: `image`, - rst: `.. image:: ${imageURL}`, - bbcode: `[img]${imageURL}[/img]`, - }; - } - public CreateImageLinksFromID( imageID: string, mime: string | null, ): ImageLinks { - return this.CreateImageLinks(this.GetImageURL(imageID, mime)); + return this.CreateImageLinks(this.GetImageURL(imageID, mime, true)); } } diff --git a/frontend/src/app/services/api/info.service.ts b/frontend/src/app/services/api/info.service.ts index 5902383..8eff4fa 100644 --- a/frontend/src/app/services/api/info.service.ts +++ b/frontend/src/app/services/api/info.service.ts @@ -7,6 +7,7 @@ import { BehaviorSubject, filter, Observable, take } from 'rxjs'; import pkg from '../../../../package.json'; import { ServerInfo } from '../../models/dto/server-info.dto'; import { Logger } from '../logger/logger.service'; +import { InfoStorageService } from '../storage/info-storage.service'; import { ApiService } from './api.service'; @Injectable({ @@ -23,21 +24,16 @@ export class InfoService { return this.infoSubject.value; } - private infoSubject = new BehaviorSubject(new ServerInfo()); + private infoSubject = new BehaviorSubject( + this.infoStorage.get() ?? new ServerInfo(), + ); constructor( - private readonly api: ApiService, @Inject(LOCATION) private readonly location: Location, + private readonly api: ApiService, + private readonly infoStorage: InfoStorageService, ) { - this.pollInfo().catch((e) => this.logger.warn(e)); - } - - public async pollInfo(): AsyncFailable { - const response = await this.api.get(InfoResponse, '/api/info'); - if (HasFailed(response)) return response; - - this.infoSubject.next(response); - return response; + this.updateInfo().catch((e) => this.logger.warn(e)); } public async getLoadedSnapshot(): Promise { @@ -58,12 +54,14 @@ export class InfoService { return pkg.version; } - public getHostname(): string { - // const info = await this.getLoadedSnapshot(); + public getHostname(allowOverride = false): string { + if (allowOverride) { + const info = this.snapshot; - // if (info.host_override !== undefined) { - // return info.host_override; - // } + if (info.host_override !== undefined) { + return info.host_override; + } + } return this.location.protocol + '//' + this.location.host; } @@ -71,7 +69,7 @@ export class InfoService { // If either version starts with 0. it has to be exactly the same // If both versions start with something else, they have to match the first part public async isCompatibleWithServer(): AsyncFailable { - const info = await this.pollInfo(); + const info = await this.getLoadedSnapshot(); if (HasFailed(info)) return info; const serverVersion = info.version; @@ -101,4 +99,13 @@ export class InfoService { public isLoaded(): boolean { return this.snapshot.version !== '0.0.0'; } + + private async updateInfo(): AsyncFailable { + const response = await this.api.get(InfoResponse, '/api/info'); + if (HasFailed(response)) return response; + + this.infoSubject.next(response); + this.infoStorage.set(response); + return response; + } } diff --git a/frontend/src/app/services/api/user.service.ts b/frontend/src/app/services/api/user.service.ts index 9a8ab90..3b5ad79 100644 --- a/frontend/src/app/services/api/user.service.ts +++ b/frontend/src/app/services/api/user.service.ts @@ -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,11 +16,11 @@ import { Fail, FT, HasFailed, - Open, + Open } from 'picsur-shared/dist/types'; import { BehaviorSubject } from 'rxjs'; import { Logger } from '../logger/logger.service'; -import { KeyService } from '../storage/key.service'; +import { KeyStorageService } from '../storage/key-storage.service'; import { ApiService } from './api.service'; @Injectable({ @@ -44,7 +44,7 @@ export class UserService { constructor( private readonly api: ApiService, - private readonly key: KeyService, + private readonly key: KeyStorageService, ) { this.init().catch(this.logger.error); } diff --git a/frontend/src/app/services/storage/cache.service.ts b/frontend/src/app/services/storage/cache.service.ts index 766467c..7803b34 100644 --- a/frontend/src/app/services/storage/cache.service.ts +++ b/frontend/src/app/services/storage/cache.service.ts @@ -1,6 +1,7 @@ import { Inject, Injectable } from '@angular/core'; import { SESSION_STORAGE } from '@ng-web-apis/common'; import { AsyncFailable, Failable, HasFailed } from 'picsur-shared/dist/types'; +import { Logger } from '../logger/logger.service'; interface dataWrapper { data: T; @@ -11,6 +12,8 @@ interface dataWrapper { providedIn: 'root', }) export class CacheService { + private readonly logger = new Logger(CacheService.name); + private readonly cacheExpiresMS = 1000 * 60 * 60; private cacheVersion = '0.0.0'; @@ -76,6 +79,35 @@ export class CacheService { return finalFallback; } + public getFallbackSync( + key: string, + finalFallback: T, + ...fallbacks: Array<(key: string) => AsyncFailable | Failable> + ): T { + const cached = this.get(key); + if (cached !== null) { + return cached; + } + + const background = async () => { + for (const fallback of fallbacks) { + const result = await fallback(key); + if (HasFailed(result)) { + continue; + } + + this.set(key, result); + return result; + } + + return finalFallback; + }; + + background().catch((e) => this.logger.error(e)); + + return finalFallback; + } + private transformKey(key: string): string { return `${this.cacheVersion}-${key}`; } diff --git a/frontend/src/app/services/storage/info-storage.service.ts b/frontend/src/app/services/storage/info-storage.service.ts new file mode 100644 index 0000000..7f678d6 --- /dev/null +++ b/frontend/src/app/services/storage/info-storage.service.ts @@ -0,0 +1,50 @@ +import { Injectable } from '@angular/core'; +import { ServerInfo } from 'src/app/models/dto/server-info.dto'; +import { Logger } from '../logger/logger.service'; + +@Injectable({ + providedIn: 'root', +}) +export class InfoStorageService { + private readonly logger = new Logger(InfoStorageService.name); + + private readonly storageKey = 'server-info'; + private info: ServerInfo | null = null; + + constructor() { + this.load(); + } + + private load() { + try { + const hasRead = localStorage.getItem(this.storageKey); + if (hasRead === null) return; + + this.info = JSON.parse(hasRead); + } catch (e) { + this.logger.warn(e); + localStorage.removeItem(this.storageKey); + } + } + + private store() { + if (this.info) + localStorage.setItem(this.storageKey, JSON.stringify(this.info)); + else localStorage.removeItem(this.storageKey); + } + + public get() { + setTimeout(this.load.bind(this), 0); + return this.info; + } + + public set(info: ServerInfo) { + this.info = info; + this.store(); + } + + public clear() { + this.info = null; + this.store(); + } +} diff --git a/frontend/src/app/services/storage/key.service.ts b/frontend/src/app/services/storage/key-storage.service.ts similarity index 94% rename from frontend/src/app/services/storage/key.service.ts rename to frontend/src/app/services/storage/key-storage.service.ts index 50d0b15..d60f1ed 100644 --- a/frontend/src/app/services/storage/key.service.ts +++ b/frontend/src/app/services/storage/key-storage.service.ts @@ -3,7 +3,7 @@ import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root', }) -export class KeyService { +export class KeyStorageService { private key: string | null = null; constructor() { diff --git a/frontend/src/app/workers/qoi-worker.service.ts b/frontend/src/app/workers/qoi-worker.service.ts index f3efe0c..733510b 100644 --- a/frontend/src/app/workers/qoi-worker.service.ts +++ b/frontend/src/app/workers/qoi-worker.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { AsyncFailable, Failure, HasFailed } from 'picsur-shared/dist/types'; -import { KeyService } from '../services/storage/key.service'; +import { KeyStorageService } from '../services/storage/key-storage.service'; import { QOIImage, QOIJob, QOIWorkerOut } from './qoi-worker.dto'; @Injectable({ @@ -10,7 +10,7 @@ export class QoiWorkerService { private worker: Worker | null = null; private job: Promise | null = null; - constructor(private readonly keyService: KeyService) { + constructor(private readonly keyService: KeyStorageService) { if (typeof Worker !== 'undefined') { this.worker = new Worker(new URL('./qoi.worker', import.meta.url)); } else { diff --git a/shared/src/dto/sys-preferences.enum.ts b/shared/src/dto/sys-preferences.enum.ts index 5bc4536..f75f650 100644 --- a/shared/src/dto/sys-preferences.enum.ts +++ b/shared/src/dto/sys-preferences.enum.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { HostNameRegex, URLRegex } from '../util/common-regex'; +import { URLRegex } from '../util/common-regex'; import { IsEntityID } from '../validators/entity-id.validator'; import { IsValidMS } from '../validators/ms.validator'; import { IsPosInt } from '../validators/positive-int.validator'; @@ -57,10 +57,7 @@ export const SysPreferenceValueTypes: { export const SysPreferenceValidators: { [key in SysPreference]: z.ZodTypeAny; } = { - [SysPreference.HostOverride]: z - .string() - .regex(HostNameRegex) - .or(z.literal('')), + [SysPreference.HostOverride]: z.string().regex(URLRegex).or(z.literal('')), [SysPreference.JwtSecret]: z.boolean(), [SysPreference.JwtExpiresIn]: IsValidMS(),