fix bug in frontend caching

This commit is contained in:
rubikscraft 2022-09-11 16:09:19 +02:00
parent c866522888
commit ef255d6e66
No known key found for this signature in database
GPG key ID: 1463EBE9200A5CD4
10 changed files with 56 additions and 26 deletions

View file

@ -3,7 +3,7 @@ import multipart from '@fastify/multipart';
import { NestFactory, Reflector } from '@nestjs/core';
import {
FastifyAdapter,
NestFastifyApplication
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { AppModule } from './app.module';
import { UserDbService } from './collections/user-db/user-db.service';

View file

@ -4,7 +4,7 @@ import {
CanActivate,
CanActivateChild,
Router,
RouterStateSnapshot,
RouterStateSnapshot
} from '@angular/router';
import { isPermissionsArray } from 'picsur-shared/dist/validators/permissions.validator';
import { PRouteData } from '../models/dto/picsur-routes.dto';
@ -17,19 +17,12 @@ import { Logger } from '../services/logger/logger.service';
})
export class PermissionGuard implements CanActivate, CanActivateChild {
private readonly logger = new Logger(PermissionGuard.name);
private allPermissionsArray: string[] | null = null;
constructor(
private readonly permissionService: PermissionService,
private readonly staticInfo: StaticInfoService,
private readonly router: Router,
) {
this.setupAllPermissions().catch(this.logger.error);
}
private async setupAllPermissions() {
this.allPermissionsArray = await this.staticInfo.getAllPermissions();
}
) {}
async canActivateChild(
childRoute: ActivatedRouteSnapshot,
@ -44,15 +37,16 @@ export class PermissionGuard implements CanActivate, CanActivateChild {
private async can(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const requiredPermissions: string[] = this.nestedPermissions(route);
const allPermissionsArray = await this.staticInfo.getAllPermissions();
// Check if permissions array is valid
// But only if we actually have the data
if (
this.allPermissionsArray !== null &&
!isPermissionsArray(requiredPermissions, this.allPermissionsArray)
allPermissionsArray !== null &&
!isPermissionsArray(requiredPermissions, allPermissionsArray)
) {
this.logger.error(
`Permissions array is invalid: "${requiredPermissions}" (available: ${this.allPermissionsArray})`,
`Permissions array is invalid: "${requiredPermissions}" (available: ${allPermissionsArray})`,
);
return false;
}

View file

@ -11,7 +11,7 @@ import {
merge,
Observable,
switchMap,
timer
timer,
} from 'rxjs';
import { ImageService } from 'src/app/services/api/image.service';
import { UserService } from 'src/app/services/api/user.service';

View file

@ -17,11 +17,11 @@ import { ErrorService } from 'src/app/util/error-manager/error.service';
import { UtilService } from 'src/app/util/util.service';
import {
CustomizeDialogComponent,
CustomizeDialogData
CustomizeDialogData,
} from '../customize-dialog/customize-dialog.component';
import {
EditDialogComponent,
EditDialogData
EditDialogData,
} from '../edit-dialog/edit-dialog.component';
@Component({

View file

@ -3,7 +3,7 @@ import {
ChangeDetectorRef,
Component,
OnDestroy,
OnInit
OnInit,
} from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ImageMetaResponse } from 'picsur-shared/dist/dto/api/image.dto';
@ -11,7 +11,7 @@ import { ImageLinks } from 'picsur-shared/dist/dto/image-links.class';
import {
AnimFileType,
ImageFileType,
SupportedFileTypeCategory
SupportedFileTypeCategory,
} from 'picsur-shared/dist/dto/mimes.dto';
import { EImage } from 'picsur-shared/dist/entities/image.entity';
import { EUser } from 'picsur-shared/dist/entities/user.entity';

View file

@ -18,6 +18,10 @@ export class InfoService {
return this.infoSubject;
}
public get snapshot() {
return this.infoSubject.value;
}
private infoSubject = new BehaviorSubject<ServerInfo>(new ServerInfo());
constructor(private readonly api: ApiService) {}

View file

@ -1,10 +1,12 @@
import { Injectable } from '@angular/core';
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
import { AllPermissionsResponse } from 'picsur-shared/dist/dto/api/info.dto';
import { SpecialRolesResponse } from 'picsur-shared/dist/dto/api/roles.dto';
import { GetSpecialUsersResponse } from 'picsur-shared/dist/dto/api/user-manage.dto';
import { Open } from 'picsur-shared/dist/types';
import { CacheService } from '../storage/cache.service';
import { ApiService } from './api.service';
import { InfoService } from './info.service';
@Injectable({
providedIn: 'root',
@ -12,8 +14,11 @@ import { ApiService } from './api.service';
export class StaticInfoService {
constructor(
private readonly api: ApiService,
private readonly info: InfoService,
private readonly cache: CacheService,
) {}
) {
this.subscribeVersion();
}
public async getSpecialRoles(): Promise<SpecialRolesResponse> {
return this.cache.getFallback(
@ -54,4 +59,11 @@ export class StaticInfoService {
},
);
}
@AutoUnsubscribe()
private subscribeVersion() {
return this.info.live.subscribe((info) => {
this.cache.setVersion(info.version);
});
}
}

View file

@ -12,21 +12,36 @@ interface dataWrapper<T> {
})
export class CacheService {
private readonly cacheExpiresMS = 1000 * 60 * 60;
private cacheVersion = '0.0.0';
constructor(@Inject(SESSION_STORAGE) private readonly storage: Storage) {}
public setVersion(version: string): void {
if (version !== this.cacheVersion) this.clear();
this.cacheVersion = version;
}
public clear(): void {
this.storage.clear();
}
public set<T>(key: string, value: T): void {
const safeKey = this.transformKey(key);
const data: dataWrapper<T> = {
data: value,
expires: Date.now() + this.cacheExpiresMS,
};
this.storage.setItem(key, JSON.stringify(data));
this.storage.setItem(safeKey, JSON.stringify(data));
}
public get<T>(key: string): T | null {
const safeKey = this.transformKey(key);
try {
const data: dataWrapper<T> = JSON.parse(this.storage.getItem(key) ?? '');
const data: dataWrapper<T> = JSON.parse(this.storage.getItem(safeKey) ?? '');
if (data && data.data && data.expires > Date.now()) {
return data.data;
}
@ -41,21 +56,27 @@ export class CacheService {
finalFallback: T,
...fallbacks: Array<(key: string) => AsyncFailable<T> | Failable<T>>
): Promise<T> {
const cached = this.get<T>(key);
const safeKey = this.transformKey(key);
const cached = this.get<T>(safeKey);
if (cached !== null) {
return cached;
}
for (const fallback of fallbacks) {
const result = await fallback(key);
const result = await fallback(safeKey);
if (HasFailed(result)) {
continue;
}
this.set(key, result);
this.set(safeKey, result);
return result;
}
return finalFallback;
}
private transformKey(key: string): string {
return `${this.cacheVersion}-${key}`;
}
}

View file

@ -4,7 +4,7 @@ import { MatDialog } from '@angular/material/dialog';
import { Logger } from 'src/app/services/logger/logger.service';
import {
ConfirmDialogComponent,
ConfirmDialogData
ConfirmDialogData,
} from './confirm-dialog/confirm-dialog.component';
@Injectable({