fix bug in frontend caching
This commit is contained in:
parent
c866522888
commit
ef255d6e66
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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) {}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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({
|
||||
|
|
Loading…
Reference in a new issue