refactor services

This commit is contained in:
rubikscraft
2022-03-30 11:40:50 +02:00
parent 6f5f68ab0e
commit 9cf3e71efc
16 changed files with 203 additions and 240 deletions

View File

@@ -6,10 +6,10 @@ import {
Router,
RouterStateSnapshot
} from '@angular/router';
import { HasFailed } from 'picsur-shared/dist/types';
import { isPermissionsArray } from 'picsur-shared/dist/validators/permissions.validator';
import { PRouteData } from '../models/dto/picsur-routes.dto';
import { PermissionService } from '../services/api/permission.service';
import { StaticInfoService } from '../services/api/static-info.service';
import { Logger } from '../services/logger/logger.service';
@Injectable({
@@ -21,18 +21,14 @@ export class PermissionGuard implements CanActivate, CanActivateChild {
constructor(
private permissionService: PermissionService,
private staticInfo: StaticInfoService,
private router: Router
) {
this.setupAllPermissions().catch(this.logger.error);
}
private async setupAllPermissions() {
const permissions = await this.permissionService.fetchAllPermission();
if (HasFailed(permissions)) {
return this.logger.error(`Could not fetch all permissions`);
}
this.allPermissionsArray = permissions;
this.allPermissionsArray = await this.staticInfo.getAllPermissions();
}
async canActivateChild(
@@ -61,7 +57,7 @@ export class PermissionGuard implements CanActivate, CanActivateChild {
return false;
}
const ourPermissions = await this.permissionService.loadedSnapshot();
const ourPermissions = await this.permissionService.getLoadedSnapshot();
const weHavePermission = requiredPermissions.every((permission) =>
ourPermissions.includes(permission)
);

View File

@@ -3,8 +3,8 @@ import { ActivatedRoute, Router } from '@angular/router';
import { HasFailed } from 'picsur-shared/dist/types';
import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto';
import { UpdateRoleControl } from 'src/app/models/forms/updaterole.control';
import { PermissionService } from 'src/app/services/api/permission.service';
import { RolesService } from 'src/app/services/api/roles.service';
import { StaticInfoService } from 'src/app/services/api/static-info.service';
import { UtilService } from 'src/app/util/util.service';
enum EditMode {
@@ -34,7 +34,7 @@ export class SettingsRolesEditComponent implements OnInit {
private router: Router,
private utilService: UtilService,
private rolesService: RolesService,
private permissionsService: PermissionService
private staticInfo: StaticInfoService
) {}
ngOnInit() {
@@ -65,16 +65,7 @@ export class SettingsRolesEditComponent implements OnInit {
private async initPermissions() {
// Get a list of all permissions so that we can select them
const allPermissions = await this.permissionsService.fetchAllPermission();
if (HasFailed(allPermissions)) {
this.utilService.showSnackBar(
'Failed to fetch permissions',
SnackBarType.Error
);
return;
}
this.allPermissions = allPermissions;
this.allPermissions = await this.staticInfo.getAllPermissions();
}
async updateRole() {

View File

@@ -8,6 +8,7 @@ import { HasFailed } from 'picsur-shared/dist/types';
import { UIFriendlyPermissions } from 'src/app/i18n/permissions.i18n';
import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto';
import { RolesService } from 'src/app/services/api/roles.service';
import { StaticInfoService } from 'src/app/services/api/static-info.service';
import { UtilService } from 'src/app/util/util.service';
@Component({
@@ -34,6 +35,7 @@ export class SettingsRolesComponent implements OnInit, AfterViewInit {
constructor(
private rolesService: RolesService,
private utilService: UtilService,
private staticInfo: StaticInfoService,
private router: Router
) {}
@@ -101,23 +103,15 @@ export class SettingsRolesComponent implements OnInit, AfterViewInit {
private async loadRoles() {
const [roles, specialRoles] = await Promise.all([
this.rolesService.getRoles(),
this.rolesService.getSpecialRoles(),
this.staticInfo.getSpecialRoles(),
]);
this.UndeletableRolesList = specialRoles.UndeletableRoles;
this.ImmutableRolesList = specialRoles.ImmutableRoles;
if (HasFailed(roles)) {
this.utilService.showSnackBar('Failed to load roles', SnackBarType.Error);
return;
}
this.dataSource.data = roles;
if (HasFailed(specialRoles)) {
this.utilService.showSnackBar(
'Failed to load special roles',
SnackBarType.Error
);
return;
}
this.UndeletableRolesList = specialRoles.UndeletableRoles;
this.ImmutableRolesList = specialRoles.ImmutableRoles;
}
}

View File

@@ -1,13 +1,13 @@
import { Component } from '@angular/core';
import { SysPreferenceBaseResponse } from 'picsur-shared/dist/dto/api/pref.dto';
import { Subject } from 'rxjs';
import { Observable } from 'rxjs';
import { SysprefService as SysPrefService } from 'src/app/services/api/syspref.service';
@Component({
templateUrl: './settings-syspref.component.html',
})
export class SettingsSysprefComponent {
preferences: Subject<SysPreferenceBaseResponse[]>;
preferences: Observable<SysPreferenceBaseResponse[]>;
constructor(sysprefService: SysPrefService) {
this.preferences = sysprefService.live;

View File

@@ -7,6 +7,7 @@ import { UIFriendlyPermissions } from 'src/app/i18n/permissions.i18n';
import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto';
import { UpdateUserControl } from 'src/app/models/forms/updateuser.control';
import { RolesService } from 'src/app/services/api/roles.service';
import { StaticInfoService } from 'src/app/services/api/static-info.service';
import { UserManageService } from 'src/app/services/api/usermanage.service';
import { UtilService } from 'src/app/util/util.service';
@@ -43,7 +44,8 @@ export class SettingsUsersEditComponent implements OnInit {
private router: Router,
private userManageService: UserManageService,
private utilService: UtilService,
private rolesService: RolesService
private rolesService: RolesService,
private staticInfo: StaticInfoService
) {}
ngOnInit() {
@@ -58,14 +60,7 @@ export class SettingsUsersEditComponent implements OnInit {
const username = this.route.snapshot.paramMap.get('username');
// Get special roles
const SpecialRoles = await this.rolesService.getSpecialRoles();
if (HasFailed(SpecialRoles)) {
this.utilService.showSnackBar(
'Failed to get special roles',
SnackBarType.Error
);
return;
}
const SpecialRoles = await this.staticInfo.getSpecialRoles();
this.soulBoundRoles = SpecialRoles.SoulBoundRoles;
// Check if edit or add
@@ -93,15 +88,7 @@ export class SettingsUsersEditComponent implements OnInit {
}
private async initImmutableUsersList() {
const SpecialUsers = await this.userManageService.getSpecialUsers();
if (HasFailed(SpecialUsers)) {
this.utilService.showSnackBar(
'Failed to get special users',
SnackBarType.Error
);
return;
}
const SpecialUsers = await this.staticInfo.getSpecialUsers();
this.ImmutableUsersList = SpecialUsers.ImmutableUsersList;
}

View File

@@ -5,7 +5,8 @@ import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
import { EUser } from 'picsur-shared/dist/entities/user.entity';
import { HasFailed } from 'picsur-shared/dist/types';
import { BehaviorSubject, Subject, throttleTime } from 'rxjs';
import { SnackBarType } from "src/app/models/dto/snack-bar-type.dto";
import { SnackBarType } from 'src/app/models/dto/snack-bar-type.dto';
import { StaticInfoService } from 'src/app/services/api/static-info.service';
import { UserManageService } from 'src/app/services/api/usermanage.service';
import { UtilService } from 'src/app/util/util.service';
@@ -29,6 +30,7 @@ export class SettingsUsersComponent implements OnInit {
constructor(
private userManageService: UserManageService,
private utilService: UtilService,
private staticInfo: StaticInfoService,
private router: Router
) {}
@@ -129,15 +131,7 @@ export class SettingsUsersComponent implements OnInit {
}
private async initSpecialUsers() {
const specialUsers = await this.userManageService.getSpecialUsers();
if (HasFailed(specialUsers)) {
this.utilService.showSnackBar(
'Failed to fetch special users',
SnackBarType.Error
);
return;
}
const specialUsers = await this.staticInfo.getSpecialUsers();
this.UndeletableUsersList = specialUsers.UndeletableUsersList;
}

View File

@@ -1,12 +1,19 @@
import { Injectable } from '@angular/core';
import { ClassConstructor, plainToClass } from 'class-transformer';
import { ApiResponse, ApiSuccessResponse } from 'picsur-shared/dist/dto/api/api.dto';
import {
ApiResponse,
ApiSuccessResponse
} from 'picsur-shared/dist/dto/api/api.dto';
import { AsyncFailable, Fail, HasFailed } from 'picsur-shared/dist/types';
import { strictValidate } from 'picsur-shared/dist/util/validate';
import { Subject } from 'rxjs';
import { ApiError } from 'src/app/models/dto/api-error.dto';
import { MultiPartRequest } from '../../models/dto/multi-part-request.dto';
import { KeyService } from './key.service';
import { KeyService } from '../storage/key.service';
/*
Proud of this, it works so smoooth
*/
@Injectable({
providedIn: 'root',

View File

@@ -1,33 +1,31 @@
import { Injectable } from '@angular/core';
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
import { AllPermissionsResponse } from 'picsur-shared/dist/dto/api/info.dto';
import { UserMePermissionsResponse } from 'picsur-shared/dist/dto/api/user.dto';
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
import { BehaviorSubject, filter, map, Observable, take } from 'rxjs';
import { ApiService } from './api.service';
import { StaticInfoService } from './static-info.service';
import { UserService } from './user.service';
@Injectable({ providedIn: 'root' })
export class PermissionService {
private readonly logger = console;
constructor(private userService: UserService, private api: ApiService) {
this.onUser();
}
private allPermissions: string[] = [];
private permissionsSubject = new BehaviorSubject<string[] | null>(null);
// TODO: add full permission list as default
public get live(): Observable<string[]> {
return this.permissionsSubject.pipe(
map((permissions) => permissions ?? [])
map((permissions) => permissions ?? this.allPermissions)
);
}
public get snapshot(): string[] {
return this.permissionsSubject.getValue() ?? [];
return this.permissionsSubject.getValue() ?? this.allPermissions;
}
// This will not be optimistic, it will instead wait for correct data
public loadedSnapshot(): Promise<string[]> {
public getLoadedSnapshot(): Promise<string[]> {
return new Promise((resolve) => {
const filtered = this.permissionsSubject.pipe(
filter((permissions) => permissions !== null),
@@ -37,38 +35,42 @@ export class PermissionService {
});
}
private permissionsSubject = new BehaviorSubject<string[] | null>(null);
constructor(
private userService: UserService,
private api: ApiService,
private staticInfo: StaticInfoService
) {
this.subscribeUser();
this.loadAllPermissions().catch(this.logger.error);
}
private async loadAllPermissions() {
this.allPermissions = await this.staticInfo.getAllPermissions();
if (this.snapshot === null) {
this.permissionsSubject.next(null);
}
}
@AutoUnsubscribe()
private onUser() {
private subscribeUser() {
return this.userService.live.subscribe(async (user) => {
const permissions = await this.fetchPermissions();
const permissions = await this.updatePermissions();
if (HasFailed(permissions)) {
this.logger.warn(permissions.getReason());
return;
}
this.permissionsSubject.next(permissions);
});
}
private async fetchPermissions(): AsyncFailable<string[]> {
private async updatePermissions(): AsyncFailable<true> {
const got = await this.api.get(
UserMePermissionsResponse,
'/api/user/me/permissions'
);
if (HasFailed(got)) return got;
return got.permissions;
}
public async fetchAllPermission(): AsyncFailable<string[]> {
const result = await this.api.get(
AllPermissionsResponse,
'/api/info/permissions'
);
if (HasFailed(result)) return result;
return result.Permissions;
this.permissionsSubject.next(got.permissions);
return true;
}
}

View File

@@ -8,26 +8,21 @@ import {
RoleInfoResponse,
RoleListResponse,
RoleUpdateRequest,
RoleUpdateResponse,
SpecialRolesResponse
RoleUpdateResponse
} from 'picsur-shared/dist/dto/api/roles.dto';
import { ERole } from 'picsur-shared/dist/entities/role.entity';
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
import { RoleModel } from 'src/app/models/forms-dto/role.dto';
import { ApiService } from './api.service';
import { CacheService } from './cache.service';
@Injectable({
providedIn: 'root',
})
export class RolesService {
constructor(
private apiService: ApiService,
private cacheService: CacheService
) {}
constructor(private api: ApiService) {}
public async getRoles(): AsyncFailable<ERole[]> {
const result = await this.apiService.get(
const result = await this.api.get(
RoleListResponse,
'/api/roles/list'
);
@@ -44,7 +39,7 @@ export class RolesService {
name,
};
const result = await this.apiService.post(
const result = await this.api.post(
RoleInfoRequest,
RoleInfoResponse,
'/api/roles/info',
@@ -55,7 +50,7 @@ export class RolesService {
}
public async createRole(role: RoleModel): AsyncFailable<ERole> {
const result = await this.apiService.post(
const result = await this.api.post(
RoleCreateRequest,
RoleCreateResponse,
'/api/roles/create',
@@ -66,7 +61,7 @@ export class RolesService {
}
public async updateRole(role: RoleModel): AsyncFailable<ERole> {
const result = await this.apiService.post(
const result = await this.api.post(
RoleUpdateRequest,
RoleUpdateResponse,
'/api/roles/update',
@@ -81,7 +76,7 @@ export class RolesService {
name,
};
const result = await this.apiService.post(
const result = await this.api.post(
RoleDeleteRequest,
RoleDeleteResponse,
'/api/roles/delete',
@@ -90,38 +85,4 @@ export class RolesService {
return result;
}
public async getSpecialRoles(): AsyncFailable<SpecialRolesResponse> {
const cached = this.cacheService.get<SpecialRolesResponse>('specialRoles');
if (cached !== null) {
return cached;
}
const result = await this.apiService.get(
SpecialRolesResponse,
'/api/roles/special'
);
if (HasFailed(result)) {
return result;
}
this.cacheService.set('specialRoles', result);
return result;
}
public async getSpecialRolesOptimistic(): Promise<SpecialRolesResponse> {
const result = await this.getSpecialRoles();
if (HasFailed(result)) {
return {
DefaultRoles: [],
ImmutableRoles: [],
SoulBoundRoles: [],
UndeletableRoles: [],
};
}
return result;
}
}

View File

@@ -0,0 +1,55 @@
import { Injectable } from '@angular/core';
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/usermanage.dto';
import { HasFailed } from 'picsur-shared/dist/types';
import { CacheService } from '../storage/cache.service';
import { ApiService } from './api.service';
@Injectable({
providedIn: 'root',
})
export class StaticInfoService {
constructor(private api: ApiService, private cache: CacheService) {}
public async getSpecialRoles(): Promise<SpecialRolesResponse> {
return this.cache.getFallback(
'specialRoles',
{
DefaultRoles: [],
ImmutableRoles: [],
SoulBoundRoles: [],
UndeletableRoles: [],
},
() => this.api.get(SpecialRolesResponse, '/api/roles/special')
);
}
public async getSpecialUsers(): Promise<GetSpecialUsersResponse> {
return this.cache.getFallback(
'specialUsers',
{
ImmutableUsersList: [],
LockedLoginUsersList: [],
UndeletableUsersList: [],
},
() => this.api.get(GetSpecialUsersResponse, '/api/user/special')
);
}
public async getAllPermissions(): Promise<string[]> {
return this.cache.getFallback(
'allPermissions',
// "error" works fine, it is not a valid permission, but will display where necessary and will otherwise be ignored
['error'] as string[],
async () => {
const res = await this.api.get(
AllPermissionsResponse,
'/api/info/permissions'
);
if (HasFailed(res)) return res;
return res.Permissions;
}
);
}
}

View File

@@ -22,18 +22,18 @@ import { PermissionService } from './permission.service';
export class SysprefService {
private hasPermission = false;
private sysprefObservable = new BehaviorSubject<SysPreferenceBaseResponse[]>(
[]
);
public get snapshot() {
return this.sysprefObservable.getValue();
}
public get live() {
return this.sysprefObservable;
return this.sysprefObservable.asObservable();
}
private sysprefObservable = new BehaviorSubject<SysPreferenceBaseResponse[]>(
[]
);
constructor(
private api: ApiService,
private permissionsService: PermissionService,
@@ -62,10 +62,7 @@ export class SysprefService {
MultipleSysPreferencesResponse,
'/api/pref/sys'
);
if (HasFailed(response)) {
this.sync();
return response;
}
if (HasFailed(response)) return response;
this.sysprefObservable.next(response.preferences);
return response.preferences;
@@ -81,12 +78,8 @@ export class SysprefService {
GetSyspreferenceResponse,
`/api/pref/sys/${key}`
);
if (HasFailed(response)) {
this.sync();
return response;
}
this.updatePrefArray(response);
if (!HasFailed(response)) this.updatePrefArray(response);
return response;
}
@@ -103,12 +96,8 @@ export class SysprefService {
`/api/pref/sys/${key}`,
{ value }
);
if (HasFailed(response)) {
this.sync();
return response;
}
this.updatePrefArray(response);
if (!HasFailed(response)) this.updatePrefArray(response);
return response;
}
@@ -126,16 +115,11 @@ export class SysprefService {
}
}
private sync() {
this.sysprefObservable.next(
([] as SysPreferenceBaseResponse[]).concat(this.snapshot)
);
}
private flush() {
this.sysprefObservable.next([]);
}
// We want to flush on logout, because the syspreferences can contain sensitive information
@AutoUnsubscribe()
private subscribePermissions() {
return this.permissionsService.live.subscribe((permissions) => {

View File

@@ -13,17 +13,18 @@ import { EUser } from 'picsur-shared/dist/entities/user.entity';
import { AsyncFailable, Fail, HasFailed } from 'picsur-shared/dist/types';
import { strictValidate } from 'picsur-shared/dist/util/validate';
import { BehaviorSubject } from 'rxjs';
import { KeyService } from '../storage/key.service';
import { ApiService } from './api.service';
import { KeyService } from './key.service';
@Injectable({
providedIn: 'root',
})
export class UserService {
private readonly logger = console;
private userSubject = new BehaviorSubject<EUser | null>(null);
public get live() {
return this.userSubject;
return this.userSubject.asObservable();
}
public get snapshot() {
@@ -34,12 +35,33 @@ export class UserService {
return this.userSubject.getValue() !== null;
}
private userSubject = new BehaviorSubject<EUser | null>(null);
constructor(private api: ApiService, private key: KeyService) {
this.init().catch(this.logger.error);
}
private async init() {
const apikey = await this.key.get();
if (!apikey) return;
const user = await this.extractUser(apikey);
if (HasFailed(user)) {
this.logger.warn(user.getReason());
await this.logout();
return;
}
this.userSubject.next(user);
const fetchedUser = await this.fetchUser();
if (HasFailed(fetchedUser)) {
this.logger.warn(fetchedUser.getReason());
await this.logout();
return;
}
this.userSubject.next(fetchedUser);
}
public async login(username: string, password: string): AsyncFailable<EUser> {
const request: UserLoginRequest = {
username,
@@ -52,8 +74,9 @@ export class UserService {
'/api/user/login',
request
);
if (HasFailed(response)) return response;
// Set the key so the apiservice can use it
this.key.set(response.jwt_token);
const user = await this.fetchUser();
@@ -83,10 +106,11 @@ export class UserService {
}
public async logout(): AsyncFailable<EUser> {
console.log('logging out');
const value = this.userSubject.getValue();
const value = this.snapshot;
this.key.clear();
this.userSubject.next(null);
if (value === null) {
return Fail('Not logged in');
} else {
@@ -94,29 +118,7 @@ export class UserService {
}
}
private async init() {
const apikey = await this.key.get();
if (!apikey) return;
const user = await this.extractUser(apikey);
if (HasFailed(user)) {
this.logger.warn(user.getReason());
await this.logout();
return;
}
this.userSubject.next(user);
const fetchedUser = await this.fetchUser();
if (HasFailed(fetchedUser)) {
this.logger.warn(fetchedUser.getReason());
await this.logout();
return;
}
this.userSubject.next(fetchedUser);
}
// This extracts the available userdata from the jwt token
private async extractUser(token: string): AsyncFailable<EUser> {
let decoded: any;
try {
@@ -135,6 +137,7 @@ export class UserService {
return jwtData.user;
}
// 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');
if (HasFailed(got)) return got;

View File

@@ -1,6 +1,5 @@
import { Injectable } from '@angular/core';
import {
GetSpecialUsersResponse,
UserCreateRequest,
UserCreateResponse,
UserDeleteRequest,
@@ -16,23 +15,19 @@ import { EUser } from 'picsur-shared/dist/entities/user.entity';
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
import { FullUserModel } from 'src/app/models/forms-dto/fulluser.dto';
import { ApiService } from './api.service';
import { CacheService } from './cache.service';
@Injectable({
providedIn: 'root',
})
export class UserManageService {
constructor(
private apiService: ApiService,
private cacheService: CacheService
) {}
constructor(private api: ApiService) {}
public async getUser(username: string): AsyncFailable<EUser> {
const body = {
username,
};
const result = await this.apiService.post(
const result = await this.api.post(
UserInfoRequest,
UserInfoResponse,
'api/user/info',
@@ -48,7 +43,7 @@ export class UserManageService {
page,
};
const result = await this.apiService.post(
const result = await this.api.post(
UserListRequest,
UserListResponse,
'/api/user/list',
@@ -63,7 +58,7 @@ export class UserManageService {
}
public async createUser(user: FullUserModel): AsyncFailable<EUser> {
const result = await this.apiService.post(
const result = await this.api.post(
UserCreateRequest,
UserCreateResponse,
'/api/user/create',
@@ -74,7 +69,7 @@ export class UserManageService {
}
public async updateUser(user: FullUserModel): AsyncFailable<EUser> {
const result = await this.apiService.post(
const result = await this.api.post(
UserUpdateRequest,
UserUpdateResponse,
'/api/user/update',
@@ -89,7 +84,7 @@ export class UserManageService {
username,
};
const result = await this.apiService.post(
const result = await this.api.post(
UserDeleteRequest,
UserDeleteResponse,
'/api/user/delete',
@@ -98,38 +93,4 @@ export class UserManageService {
return result;
}
public async getSpecialUsers(): AsyncFailable<GetSpecialUsersResponse> {
const cached =
this.cacheService.get<GetSpecialUsersResponse>('specialUsers');
if (cached !== null) {
return cached;
}
const result = await this.apiService.get(
GetSpecialUsersResponse,
'/api/user/special'
);
if (HasFailed(result)) {
return result;
}
this.cacheService.set('specialUsers', result);
return result;
}
public async getSpecialUsersOptimistic(): Promise<GetSpecialUsersResponse> {
const result = await this.getSpecialUsers();
if (HasFailed(result)) {
return {
ImmutableUsersList: [],
LockedLoginUsersList: [],
UndeletableUsersList: [],
};
}
return result;
}
}

View File

@@ -3,6 +3,10 @@ import { Injectable, Injector } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve } from '@angular/router';
import { PRouteData } from 'src/app/models/dto/picsur-routes.dto';
// This service makes sure that any sidebar components are getting dependency injection
// from their correct module. Instead of getting it from the module where it is being
// sent via the portal.
@Injectable({
providedIn: 'any',
})

View File

@@ -1,4 +1,5 @@
import { Injectable } from '@angular/core';
import { AsyncFailable, Failable, HasFailed } from 'picsur-shared/dist/types';
interface dataWrapper<T> {
data: T;
@@ -21,6 +22,15 @@ export class CacheService {
}
}
public set<T>(key: string, value: T): void {
const data: dataWrapper<T> = {
data: value,
expires: Date.now() + this.cacheExpiresMS,
};
this.storage.setItem(key, JSON.stringify(data));
}
public get<T>(key: string): T | null {
try {
const data: dataWrapper<T> = JSON.parse(this.storage.getItem(key) ?? '');
@@ -33,12 +43,26 @@ export class CacheService {
}
}
public set<T>(key: string, value: T): void {
const data: dataWrapper<T> = {
data: value,
expires: Date.now() + this.cacheExpiresMS,
};
public async getFallback<T>(
key: string,
finalFallback: T,
...fallbacks: Array<(key: string) => AsyncFailable<T> | Failable<T>>
): Promise<T> {
const cached = this.get<T>(key);
if (cached !== null) {
return cached;
}
this.storage.setItem(key, JSON.stringify(data));
for (const fallback of fallbacks) {
const result = await fallback(key);
if (HasFailed(result)) {
continue;
}
this.set(key, result);
return result;
}
return finalFallback;
}
}