diff --git a/frontend/src/app/guards/permission.guard.ts b/frontend/src/app/guards/permission.guard.ts index a0d1b07..6b6af92 100644 --- a/frontend/src/app/guards/permission.guard.ts +++ b/frontend/src/app/guards/permission.guard.ts @@ -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) ); diff --git a/frontend/src/app/routes/settings/roles/settings-roles-edit/settings-roles-edit.component.ts b/frontend/src/app/routes/settings/roles/settings-roles-edit/settings-roles-edit.component.ts index b8a4c62..5682ed0 100644 --- a/frontend/src/app/routes/settings/roles/settings-roles-edit/settings-roles-edit.component.ts +++ b/frontend/src/app/routes/settings/roles/settings-roles-edit/settings-roles-edit.component.ts @@ -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() { diff --git a/frontend/src/app/routes/settings/roles/settings-roles.component.ts b/frontend/src/app/routes/settings/roles/settings-roles.component.ts index d83666c..751d910 100644 --- a/frontend/src/app/routes/settings/roles/settings-roles.component.ts +++ b/frontend/src/app/routes/settings/roles/settings-roles.component.ts @@ -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; } } diff --git a/frontend/src/app/routes/settings/syspref/settings-syspref.component.ts b/frontend/src/app/routes/settings/syspref/settings-syspref.component.ts index 4ac3e70..15439fc 100644 --- a/frontend/src/app/routes/settings/syspref/settings-syspref.component.ts +++ b/frontend/src/app/routes/settings/syspref/settings-syspref.component.ts @@ -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; + preferences: Observable; constructor(sysprefService: SysPrefService) { this.preferences = sysprefService.live; diff --git a/frontend/src/app/routes/settings/users/settings-users-edit/settings-users-edit.component.ts b/frontend/src/app/routes/settings/users/settings-users-edit/settings-users-edit.component.ts index 081f630..c577ac6 100644 --- a/frontend/src/app/routes/settings/users/settings-users-edit/settings-users-edit.component.ts +++ b/frontend/src/app/routes/settings/users/settings-users-edit/settings-users-edit.component.ts @@ -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; } diff --git a/frontend/src/app/routes/settings/users/settings-users.component.ts b/frontend/src/app/routes/settings/users/settings-users.component.ts index 41be00d..dbe73e7 100644 --- a/frontend/src/app/routes/settings/users/settings-users.component.ts +++ b/frontend/src/app/routes/settings/users/settings-users.component.ts @@ -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; } diff --git a/frontend/src/app/services/api/api.service.ts b/frontend/src/app/services/api/api.service.ts index d8490cc..4fa22ad 100644 --- a/frontend/src/app/services/api/api.service.ts +++ b/frontend/src/app/services/api/api.service.ts @@ -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', diff --git a/frontend/src/app/services/api/permission.service.ts b/frontend/src/app/services/api/permission.service.ts index 4c1be05..c2a4ec7 100644 --- a/frontend/src/app/services/api/permission.service.ts +++ b/frontend/src/app/services/api/permission.service.ts @@ -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(null); - // TODO: add full permission list as default public get live(): Observable { 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 { + public getLoadedSnapshot(): Promise { return new Promise((resolve) => { const filtered = this.permissionsSubject.pipe( filter((permissions) => permissions !== null), @@ -37,38 +35,42 @@ export class PermissionService { }); } - private permissionsSubject = new BehaviorSubject(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 { + private async updatePermissions(): AsyncFailable { const got = await this.api.get( UserMePermissionsResponse, '/api/user/me/permissions' ); if (HasFailed(got)) return got; - return got.permissions; - } - - public async fetchAllPermission(): AsyncFailable { - 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; } } diff --git a/frontend/src/app/services/api/roles.service.ts b/frontend/src/app/services/api/roles.service.ts index e0c67e3..21a09c8 100644 --- a/frontend/src/app/services/api/roles.service.ts +++ b/frontend/src/app/services/api/roles.service.ts @@ -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 { - 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 { - 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 { - 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 { - const cached = this.cacheService.get('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 { - const result = await this.getSpecialRoles(); - if (HasFailed(result)) { - return { - DefaultRoles: [], - ImmutableRoles: [], - SoulBoundRoles: [], - UndeletableRoles: [], - }; - } - - return result; - } } diff --git a/frontend/src/app/services/api/static-info.service.ts b/frontend/src/app/services/api/static-info.service.ts new file mode 100644 index 0000000..d7e5764 --- /dev/null +++ b/frontend/src/app/services/api/static-info.service.ts @@ -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 { + return this.cache.getFallback( + 'specialRoles', + { + DefaultRoles: [], + ImmutableRoles: [], + SoulBoundRoles: [], + UndeletableRoles: [], + }, + () => this.api.get(SpecialRolesResponse, '/api/roles/special') + ); + } + + public async getSpecialUsers(): Promise { + return this.cache.getFallback( + 'specialUsers', + { + ImmutableUsersList: [], + LockedLoginUsersList: [], + UndeletableUsersList: [], + }, + () => this.api.get(GetSpecialUsersResponse, '/api/user/special') + ); + } + + public async getAllPermissions(): Promise { + 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; + } + ); + } +} diff --git a/frontend/src/app/services/api/syspref.service.ts b/frontend/src/app/services/api/syspref.service.ts index 59c4be5..92a7bf3 100644 --- a/frontend/src/app/services/api/syspref.service.ts +++ b/frontend/src/app/services/api/syspref.service.ts @@ -22,18 +22,18 @@ import { PermissionService } from './permission.service'; export class SysprefService { private hasPermission = false; + private sysprefObservable = new BehaviorSubject( + [] + ); + public get snapshot() { return this.sysprefObservable.getValue(); } public get live() { - return this.sysprefObservable; + return this.sysprefObservable.asObservable(); } - private sysprefObservable = new BehaviorSubject( - [] - ); - 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) => { diff --git a/frontend/src/app/services/api/user.service.ts b/frontend/src/app/services/api/user.service.ts index 7a7bea5..24bfeac 100644 --- a/frontend/src/app/services/api/user.service.ts +++ b/frontend/src/app/services/api/user.service.ts @@ -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(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(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 { 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 { - 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 { 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 { const got = await this.api.get(UserMeResponse, '/api/user/me'); if (HasFailed(got)) return got; diff --git a/frontend/src/app/services/api/usermanage.service.ts b/frontend/src/app/services/api/usermanage.service.ts index 86dbd92..4d475ad 100644 --- a/frontend/src/app/services/api/usermanage.service.ts +++ b/frontend/src/app/services/api/usermanage.service.ts @@ -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 { 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 { - 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 { - 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 { - const cached = - this.cacheService.get('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 { - const result = await this.getSpecialUsers(); - if (HasFailed(result)) { - return { - ImmutableUsersList: [], - LockedLoginUsersList: [], - UndeletableUsersList: [], - }; - } - - return result; - } } diff --git a/frontend/src/app/services/sidebar-resolver/sidebar-resolver.service.ts b/frontend/src/app/services/sidebar-resolver/sidebar-resolver.service.ts index 7ba7202..50e5ee0 100644 --- a/frontend/src/app/services/sidebar-resolver/sidebar-resolver.service.ts +++ b/frontend/src/app/services/sidebar-resolver/sidebar-resolver.service.ts @@ -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', }) diff --git a/frontend/src/app/services/api/cache.service.ts b/frontend/src/app/services/storage/cache.service.ts similarity index 62% rename from frontend/src/app/services/api/cache.service.ts rename to frontend/src/app/services/storage/cache.service.ts index 64aed30..8a289a1 100644 --- a/frontend/src/app/services/api/cache.service.ts +++ b/frontend/src/app/services/storage/cache.service.ts @@ -1,4 +1,5 @@ import { Injectable } from '@angular/core'; +import { AsyncFailable, Failable, HasFailed } from 'picsur-shared/dist/types'; interface dataWrapper { data: T; @@ -21,6 +22,15 @@ export class CacheService { } } + public set(key: string, value: T): void { + const data: dataWrapper = { + data: value, + expires: Date.now() + this.cacheExpiresMS, + }; + + this.storage.setItem(key, JSON.stringify(data)); + } + public get(key: string): T | null { try { const data: dataWrapper = JSON.parse(this.storage.getItem(key) ?? ''); @@ -33,12 +43,26 @@ export class CacheService { } } - public set(key: string, value: T): void { - const data: dataWrapper = { - data: value, - expires: Date.now() + this.cacheExpiresMS, - }; + public async getFallback( + key: string, + finalFallback: T, + ...fallbacks: Array<(key: string) => AsyncFailable | Failable> + ): Promise { + const cached = this.get(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; } } diff --git a/frontend/src/app/services/api/key.service.ts b/frontend/src/app/services/storage/key.service.ts similarity index 100% rename from frontend/src/app/services/api/key.service.ts rename to frontend/src/app/services/storage/key.service.ts