diff --git a/backend/src/collections/syspreferencesdb/syspreferencedb.service.ts b/backend/src/collections/syspreferencesdb/syspreferencedb.service.ts index 9a1d0ec..d68b9ad 100644 --- a/backend/src/collections/syspreferencesdb/syspreferencedb.service.ts +++ b/backend/src/collections/syspreferencesdb/syspreferencedb.service.ts @@ -3,8 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import { plainToClass } from 'class-transformer'; import { InternalSysprefRepresentation, - SysPreferences, - SysPreferenceValueTypes, + SysPreference, SysPrefValueType } from 'picsur-shared/dist/dto/syspreferences.dto'; import { @@ -15,6 +14,10 @@ import { } from 'picsur-shared/dist/types'; import { strictValidate } from 'picsur-shared/dist/util/validate'; import { Repository } from 'typeorm'; +import { + SysPreferenceList, + SysPreferenceValueTypes +} from '../../models/dto/syspreferences.dto'; import { ESysPreferenceBackend } from '../../models/entities/syspreference.entity'; import { SysPreferenceDefaultsService } from './syspreferencedefaults.service'; @@ -29,7 +32,7 @@ export class SysPreferenceService { ) {} public async setPreference( - key: SysPreferences, + key: string, value: SysPrefValueType, ): AsyncFailable { // Validate @@ -50,12 +53,13 @@ export class SysPreferenceService { return { key: sysPreference.key, value, - type: SysPreferenceValueTypes[key], + // key has to be valid here, we validated it + type: SysPreferenceValueTypes[key as SysPreference], }; } public async getPreference( - key: SysPreferences, + key: string, ): AsyncFailable { // Validate let validatedKey = this.validatePrefKey(key); @@ -92,7 +96,7 @@ export class SysPreferenceService { return this.retrieveConvertedValue(foundSysPreference); } - public async getStringPreference(key: SysPreferences): AsyncFailable { + public async getStringPreference(key: string): AsyncFailable { const pref = await this.getPreference(key); if (HasFailed(pref)) return pref; if (pref.type !== 'string') return Fail('Invalid preference type'); @@ -100,7 +104,7 @@ export class SysPreferenceService { return pref.value as string; } - public async getNumberPreference(key: SysPreferences): AsyncFailable { + public async getNumberPreference(key: string): AsyncFailable { const pref = await this.getPreference(key); if (HasFailed(pref)) return pref; if (pref.type !== 'number') return Fail('Invalid preference type'); @@ -108,9 +112,7 @@ export class SysPreferenceService { return pref.value as number; } - public async getBooleanPreference( - key: SysPreferences, - ): AsyncFailable { + public async getBooleanPreference(key: string): AsyncFailable { const pref = await this.getPreference(key); if (HasFailed(pref)) return pref; if (pref.type !== 'boolean') return Fail('Invalid preference type'); @@ -122,7 +124,7 @@ export class SysPreferenceService { InternalSysprefRepresentation[] > { let internalSysPrefs = await Promise.all( - SysPreferences.map((key) => this.getPreference(key as SysPreferences)), + SysPreferenceList.map((key) => this.getPreference(key)), ); if (internalSysPrefs.some((pref) => HasFailed(pref))) { return Fail('Could not get all preferences'); @@ -133,7 +135,7 @@ export class SysPreferenceService { // Private private async saveDefault( - key: SysPreferences, + key: SysPreference, // Force enum here because we dont validate ): AsyncFailable { return this.setPreference(key, this.defaultsService.defaults[key]()); } @@ -141,7 +143,10 @@ export class SysPreferenceService { private retrieveConvertedValue( preference: ESysPreferenceBackend, ): Failable { - const type = SysPreferenceValueTypes[preference.key]; + const key = this.validatePrefKey(preference.key); + if (HasFailed(key)) return key; + + const type = SysPreferenceValueTypes[key]; switch (type) { case 'string': return { @@ -190,16 +195,16 @@ export class SysPreferenceService { return verifySysPreference; } - private validatePrefKey(key: string): Failable { - if (!SysPreferences.includes(key)) { + private validatePrefKey(key: string): Failable { + if (!SysPreferenceList.includes(key)) { return Fail('Invalid preference key'); } - return key as SysPreferences; + return key as SysPreference; } private validatePrefValue( - key: SysPreferences, + key: SysPreference, value: SysPrefValueType, ): Failable { const expectedType = SysPreferenceValueTypes[key]; diff --git a/backend/src/collections/syspreferencesdb/syspreferencedefaults.service.ts b/backend/src/collections/syspreferencesdb/syspreferencedefaults.service.ts index 6811c12..44d8f76 100644 --- a/backend/src/collections/syspreferencesdb/syspreferencedefaults.service.ts +++ b/backend/src/collections/syspreferencesdb/syspreferencedefaults.service.ts @@ -1,6 +1,6 @@ import { Injectable, Logger } from '@nestjs/common'; import { - SysPreferences, + SysPreference, SysPrefValueType } from 'picsur-shared/dist/dto/syspreferences.dto'; import { generateRandomString } from 'picsur-shared/dist/util/random'; @@ -13,9 +13,9 @@ export class SysPreferenceDefaultsService { constructor(private jwtConfigService: EnvJwtConfigService) {} public readonly defaults: { - [key in SysPreferences]: () => SysPrefValueType; + [key in SysPreference]: () => SysPrefValueType; } = { - jwt_secret: () => { + [SysPreference.JwtSecret]: () => { const envSecret = this.jwtConfigService.getJwtSecret(); if (envSecret) { return envSecret; @@ -26,10 +26,10 @@ export class SysPreferenceDefaultsService { return generateRandomString(64); } }, - jwt_expires_in: () => this.jwtConfigService.getJwtExpiresIn() ?? '7d', + [SysPreference.JwtExpiresIn]: () => this.jwtConfigService.getJwtExpiresIn() ?? '7d', - test_string: () => 'test_string', - test_number: () => 123, - test_boolean: () => true, + [SysPreference.TestString]: () => 'test_string', + [SysPreference.TestNumber]: () => 123, + [SysPreference.TestBoolean]: () => true, }; } diff --git a/backend/src/models/dto/permissions.dto.ts b/backend/src/models/dto/permissions.dto.ts index d23c114..e8413a8 100644 --- a/backend/src/models/dto/permissions.dto.ts +++ b/backend/src/models/dto/permissions.dto.ts @@ -6,5 +6,4 @@ export { Permission } from 'picsur-shared/dist/dto/permissions.dto'; // Derivatives export const PermissionsList: Permission[] = Object.values(Permission); - export type Permissions = Permission[]; diff --git a/backend/src/models/dto/syspreferences.dto.ts b/backend/src/models/dto/syspreferences.dto.ts new file mode 100644 index 0000000..c2792e1 --- /dev/null +++ b/backend/src/models/dto/syspreferences.dto.ts @@ -0,0 +1,19 @@ +import { + SysPreference, + SysPrefValueTypeStrings +} from 'picsur-shared/dist/dto/syspreferences.dto'; + +export type SysPreferences = SysPreference[]; +export const SysPreferenceList: string[] = Object.values(SysPreference); + +// Syspref Values + +export const SysPreferenceValueTypes: { + [key in SysPreference]: SysPrefValueTypeStrings; +} = { + [SysPreference.JwtSecret]: 'string', + [SysPreference.JwtExpiresIn]: 'string', + [SysPreference.TestString]: 'string', + [SysPreference.TestNumber]: 'number', + [SysPreference.TestBoolean]: 'boolean', +}; diff --git a/backend/src/models/entities/syspreference.entity.ts b/backend/src/models/entities/syspreference.entity.ts index 3b6d227..286185d 100644 --- a/backend/src/models/entities/syspreference.entity.ts +++ b/backend/src/models/entities/syspreference.entity.ts @@ -1,4 +1,3 @@ -import { SysPreferences } from 'picsur-shared/dist/dto/syspreferences.dto'; import { ESysPreference } from 'picsur-shared/dist/entities/syspreference.entity'; import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; @@ -8,7 +7,7 @@ export class ESysPreferenceBackend extends ESysPreference { override id?: number; @Column({ nullable: false, unique: true }) - override key: SysPreferences; + override key: string; @Column({ nullable: false }) override value: string; diff --git a/backend/src/routes/api/info/info.controller.ts b/backend/src/routes/api/info/info.controller.ts index 0927beb..82ebd1a 100644 --- a/backend/src/routes/api/info/info.controller.ts +++ b/backend/src/routes/api/info/info.controller.ts @@ -1,7 +1,6 @@ import { Controller, Get } from '@nestjs/common'; import { plainToClass } from 'class-transformer'; -import { InfoResponse } from 'picsur-shared/dist/dto/api/info.dto'; -import { AllPermissionsResponse } from 'picsur-shared/dist/dto/api/roles.dto'; +import { AllPermissionsResponse, InfoResponse } from 'picsur-shared/dist/dto/api/info.dto'; import { HostConfigService } from '../../../config/host.config.service'; import { NoPermissions } from '../../../decorators/permissions.decorator'; import { PermissionsList } from '../../../models/dto/permissions.dto'; diff --git a/backend/src/routes/api/pref/pref.controller.ts b/backend/src/routes/api/pref/pref.controller.ts index 78c486d..10fd8d7 100644 --- a/backend/src/routes/api/pref/pref.controller.ts +++ b/backend/src/routes/api/pref/pref.controller.ts @@ -15,7 +15,6 @@ import { UpdateSysPreferenceRequest, UpdateSysPreferenceResponse } from 'picsur-shared/dist/dto/api/pref.dto'; -import { SysPreferences } from 'picsur-shared/dist/dto/syspreferences.dto'; import { HasFailed } from 'picsur-shared/dist/types'; import { SysPreferenceService } from '../../../collections/syspreferencesdb/syspreferencedb.service'; import { RequiredPermissions } from '../../../decorators/permissions.decorator'; @@ -50,7 +49,7 @@ export class PrefController { async getSysPref( @Param('key') key: string, ): Promise { - const pref = await this.prefService.getPreference(key as SysPreferences); + const pref = await this.prefService.getPreference(key); if (HasFailed(pref)) { this.logger.warn(pref.getReason()); throw new InternalServerErrorException('Could not get preference'); @@ -66,20 +65,18 @@ export class PrefController { ): Promise { const value = body.value; - const pref = await this.prefService.setPreference( - key as SysPreferences, - value, - ); + const pref = await this.prefService.setPreference(key, value); if (HasFailed(pref)) { this.logger.warn(pref.getReason()); throw new InternalServerErrorException('Could not set preference'); } - const returned = new UpdateSysPreferenceResponse(); - returned.key = key as SysPreferences; - returned.value = pref.value; - returned.type = pref.type; + const returned = { + key, + value: pref.value, + type: pref.type, + }; - return returned; + return plainToClass(UpdateSysPreferenceResponse, returned); } } diff --git a/frontend/src/app/i18n/syspref.i18n.ts b/frontend/src/app/i18n/syspref.i18n.ts new file mode 100644 index 0000000..6ef9ab1 --- /dev/null +++ b/frontend/src/app/i18n/syspref.i18n.ts @@ -0,0 +1,11 @@ +import { SysPreference } from 'picsur-shared/dist/dto/syspreferences.dto'; + +export const SysPreferenceFriendlyNames: { + [key in SysPreference]: string; +} = { + [SysPreference.JwtSecret]: 'JWT Secret', + [SysPreference.JwtExpiresIn]: 'JWT Expiry Time', + [SysPreference.TestString]: 'Test String', + [SysPreference.TestNumber]: 'Test Number', + [SysPreference.TestBoolean]: 'Test Boolean', +}; diff --git a/frontend/src/app/routes/settings/syspref/syspref-option/settings-syspref-option.component.ts b/frontend/src/app/routes/settings/syspref/syspref-option/settings-syspref-option.component.ts index f97869a..09a9c05 100644 --- a/frontend/src/app/routes/settings/syspref/syspref-option/settings-syspref-option.component.ts +++ b/frontend/src/app/routes/settings/syspref/syspref-option/settings-syspref-option.component.ts @@ -1,12 +1,10 @@ import { Component, Input, OnInit } from '@angular/core'; import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator'; import { SysPreferenceBaseResponse } from 'picsur-shared/dist/dto/api/pref.dto'; -import { - SysPreferenceFriendlyNames, - SysPrefValueType -} from 'picsur-shared/dist/dto/syspreferences.dto'; +import { SysPreference, SysPrefValueType } from 'picsur-shared/dist/dto/syspreferences.dto'; import { HasFailed } from 'picsur-shared/dist/types'; import { Subject, throttleTime } from 'rxjs'; +import { SysPreferenceFriendlyNames } from 'src/app/i18n/syspref.i18n'; import { SnackBarType } from 'src/app/models/snack-bar-type'; import { SysprefService } from 'src/app/services/api/syspref.service'; import { UtilService } from 'src/app/util/util.service'; @@ -31,7 +29,7 @@ export class SettingsSysprefOptionComponent implements OnInit { } get name(): string { - return SysPreferenceFriendlyNames[this.pref.key]; + return SysPreferenceFriendlyNames[this.pref.key as SysPreference] ?? this.pref.key; } get valString(): string { diff --git a/frontend/src/app/services/api/permission.service.ts b/frontend/src/app/services/api/permission.service.ts index f083830..4c1be05 100644 --- a/frontend/src/app/services/api/permission.service.ts +++ b/frontend/src/app/services/api/permission.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator'; -import { AllPermissionsResponse } from 'picsur-shared/dist/dto/api/roles.dto'; +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'; diff --git a/frontend/src/app/services/api/syspref.service.ts b/frontend/src/app/services/api/syspref.service.ts index 564e5c8..cf890b0 100644 --- a/frontend/src/app/services/api/syspref.service.ts +++ b/frontend/src/app/services/api/syspref.service.ts @@ -8,15 +8,13 @@ import { UpdateSysPreferenceResponse } from 'picsur-shared/dist/dto/api/pref.dto'; import { Permission } from 'picsur-shared/dist/dto/permissions.dto'; -import { - SysPreferences, - SysPrefValueType -} from 'picsur-shared/dist/dto/syspreferences.dto'; +import { SysPrefValueType } from 'picsur-shared/dist/dto/syspreferences.dto'; import { AsyncFailable, Fail, HasFailed } from 'picsur-shared/dist/types'; import { BehaviorSubject } from 'rxjs'; import { ApiService } from './api.service'; import { PermissionService } from './permission.service'; + @Injectable({ providedIn: 'root', }) @@ -60,7 +58,7 @@ export class SysprefService { } public async getPreference( - key: SysPreferences + key: string, ): AsyncFailable { if (!this.hasPermission) return Fail('You do not have permission to edit system preferences'); @@ -79,7 +77,7 @@ export class SysprefService { } public async setPreference( - key: SysPreferences, + key: string, value: SysPrefValueType ): AsyncFailable { if (!this.hasPermission) diff --git a/shared/src/dto/api/info.dto.ts b/shared/src/dto/api/info.dto.ts index 8c153f8..147b57b 100644 --- a/shared/src/dto/api/info.dto.ts +++ b/shared/src/dto/api/info.dto.ts @@ -1,4 +1,5 @@ import { IsBoolean, IsDefined, IsSemVer, IsString } from 'class-validator'; +import { IsStringList } from '../../validators/string-list.validator'; export class InfoResponse { @IsBoolean() @@ -14,3 +15,10 @@ export class InfoResponse { @IsSemVer() version: string; } + +// AllPermissions +export class AllPermissionsResponse { + @IsDefined() + @IsStringList() + Permissions: string[]; +} diff --git a/shared/src/dto/api/pref.dto.ts b/shared/src/dto/api/pref.dto.ts index c8e70be..d6c0b6f 100644 --- a/shared/src/dto/api/pref.dto.ts +++ b/shared/src/dto/api/pref.dto.ts @@ -1,20 +1,15 @@ import { Type } from 'class-transformer'; import { - IsArray, IsEnum, IsNotEmpty, ValidateNested + IsArray, IsEnum, IsNotEmpty, IsString, ValidateNested } from 'class-validator'; import { IsPosInt } from '../../validators/positive-int.validator'; import { IsSysPrefValue } from '../../validators/syspref.validator'; -import { - SysPreferences, - SysPrefValueType, - SysPrefValueTypes, - SysPrefValueTypeStrings -} from '../syspreferences.dto'; +import { SysPrefValueType, SysPrefValueTypes, SysPrefValueTypeStrings } from '../syspreferences.dto'; export class SysPreferenceBaseResponse { @IsNotEmpty() - @IsEnum(SysPreferences) - key: SysPreferences; + @IsString() + key: string; @IsNotEmpty() @IsSysPrefValue() diff --git a/shared/src/dto/api/roles.dto.ts b/shared/src/dto/api/roles.dto.ts index 934260d..97ed9bd 100644 --- a/shared/src/dto/api/roles.dto.ts +++ b/shared/src/dto/api/roles.dto.ts @@ -54,10 +54,3 @@ export class SpecialRolesResponse { @IsStringList() DefaultRoles: string[]; } - -// AllPermissions -export class AllPermissionsResponse { - @IsDefined() - @IsStringList() - Permissions: string[]; -} diff --git a/shared/src/dto/permissions.dto.ts b/shared/src/dto/permissions.dto.ts index b4b7b8f..e008239 100644 --- a/shared/src/dto/permissions.dto.ts +++ b/shared/src/dto/permissions.dto.ts @@ -1,4 +1,7 @@ // Only add no rename +// This enum only makes permissions easier to program, +// This does not have to be a complete list of all permissions +// -> the frontend and backend can be somewhat out of sync export enum Permission { ImageView = 'image-view', ImageUpload = 'image-upload', diff --git a/shared/src/dto/syspreferences.dto.ts b/shared/src/dto/syspreferences.dto.ts index ed54226..52811c3 100644 --- a/shared/src/dto/syspreferences.dto.ts +++ b/shared/src/dto/syspreferences.dto.ts @@ -1,51 +1,19 @@ -import tuple from '../types/tuple'; - -// Syspref keys - -const SysPreferencesTuple = tuple( - 'jwt_secret', - 'jwt_expires_in', - 'test_string', - 'test_number', - 'test_boolean', -); - -export const SysPreferences: string[] = SysPreferencesTuple; -export type SysPreferences = typeof SysPreferencesTuple[number]; - -export const SysPreferenceFriendlyNames: { - [key in SysPreferences]: string; -} = { - jwt_secret: 'JWT Secret', - jwt_expires_in: 'JWT Expiry Time', - test_string: 'Test String', - test_number: 'Test Number', - test_boolean: 'Test Boolean', -}; - -// Syspref Values +export enum SysPreference { + JwtSecret = 'jwt_secret', + JwtExpiresIn = 'jwt_expires_in', + TestString = 'test_string', + TestNumber = 'test_number', + TestBoolean = 'test_boolean', +} +// Variable value type export type SysPrefValueType = string | number | boolean; export type SysPrefValueTypeStrings = 'string' | 'number' | 'boolean'; export const SysPrefValueTypes = ['string', 'number', 'boolean']; -export const SysPreferenceValueTypes: { - [key in SysPreferences]: SysPrefValueTypeStrings; -} = { - jwt_secret: 'string', - jwt_expires_in: 'string', - test_string: 'string', - test_number: 'number', - test_boolean: 'boolean', -}; - -// Validators - - -// interfaces - +// Interfaces export interface InternalSysprefRepresentation { - key: SysPreferences; + key: string; value: SysPrefValueType; type: SysPrefValueTypeStrings; } diff --git a/shared/src/entities/syspreference.entity.ts b/shared/src/entities/syspreference.entity.ts index 33de49d..62c75fe 100644 --- a/shared/src/entities/syspreference.entity.ts +++ b/shared/src/entities/syspreference.entity.ts @@ -1,5 +1,4 @@ -import { IsEnum, IsNotEmpty, IsString } from 'class-validator'; -import { SysPreferences } from '../dto/syspreferences.dto'; +import { IsNotEmpty, IsString } from 'class-validator'; import { EntityID } from '../validators/entity-id.validator'; export class ESysPreference { @@ -7,8 +6,8 @@ export class ESysPreference { id?: number; @IsNotEmpty() - @IsEnum(SysPreferences) - key: SysPreferences; + @IsString() + key: string; @IsNotEmpty() @IsString()