diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ad8767b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "vsicons.presets.angular": true +} \ No newline at end of file diff --git a/backend/src/collections/syspreferencesdb/syspreferencedb.service.ts b/backend/src/collections/syspreferencesdb/syspreferencedb.service.ts index a1662d2..4922084 100644 --- a/backend/src/collections/syspreferencesdb/syspreferencedb.service.ts +++ b/backend/src/collections/syspreferencesdb/syspreferencedb.service.ts @@ -1,14 +1,70 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { validate } from 'class-validator'; +import { SysPreferences } from 'picsur-shared/dist/dto/syspreferences.dto'; +import { AsyncFailable, Fail } from 'picsur-shared/dist/types'; import { Repository } from 'typeorm'; +import { SysPreferenceDefaults } from '../../models/dto/syspreference.dto'; import { ESysPreferenceBackend } from '../../models/entities/syspreference.entity'; @Injectable() export class SysPreferenceService { + private readonly logger = new Logger('SysPreferenceService'); + constructor( @InjectRepository(ESysPreferenceBackend) - private usersRepository: Repository, + private sysPreferenceRepository: Repository, ) {} - + public async setPreference( + key: SysPreferences, + value: string, + ): AsyncFailable { + let sysPreference = new ESysPreferenceBackend(); + sysPreference.key = key; + sysPreference.value = value; + + const errors = await validate(sysPreference); + if (errors.length > 0) { + this.logger.warn(errors); + return Fail('Invalid preference'); + } + + try { + sysPreference = await this.sysPreferenceRepository.save(sysPreference, { + reload: true, + }); + } catch (e: any) { + this.logger.warn(e); + return Fail('Could not save preference'); + } + + return sysPreference; + } + + public async getPreference( + key: SysPreferences, + ): AsyncFailable { + let sysPreference: ESysPreferenceBackend | undefined; + try { + sysPreference = await this.sysPreferenceRepository.findOne({ + key, + }); + } catch (e: any) { + this.logger.warn(e); + return Fail('Could not get preference'); + } + + if (!sysPreference) { + return this.saveDefault(key); + } + + return sysPreference; + } + + private async saveDefault( + key: SysPreferences, + ): AsyncFailable { + return this.setPreference(key, SysPreferenceDefaults[key]()); + } } diff --git a/backend/src/models/dto/syspreference.dto.ts b/backend/src/models/dto/syspreference.dto.ts new file mode 100644 index 0000000..794f474 --- /dev/null +++ b/backend/src/models/dto/syspreference.dto.ts @@ -0,0 +1,12 @@ +import { SysPreferences } from 'picsur-shared/dist/dto/syspreferences.dto'; +import { generateRandomString } from 'picsur-shared/dist/util/random'; +import Config from '../../env'; + +export const SysPreferenceDefaults: { + [key in SysPreferences]: () => string; +} = { + jwt_secret: () => { + if (Config.jwt.secret !== 'CHANGE_ME') return Config.jwt.secret; + return generateRandomString(32); + }, +}; diff --git a/backend/src/models/entities/syspreference.entity.ts b/backend/src/models/entities/syspreference.entity.ts index 8f84ed2..53bbee9 100644 --- a/backend/src/models/entities/syspreference.entity.ts +++ b/backend/src/models/entities/syspreference.entity.ts @@ -1,3 +1,4 @@ +import { SysPreferences } from 'picsur-shared/dist/dto/syspreferences.dto'; import { ESysPreference } from 'picsur-shared/dist/entities/syspreference.entity'; import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; @@ -7,7 +8,7 @@ export class ESysPreferenceBackend extends ESysPreference { override id?: number; @Column() - override name: string; + override key: SysPreferences; @Column() override value: string; diff --git a/shared/src/dto/mimes.dto.ts b/shared/src/dto/mimes.dto.ts index 7d6e05e..f2c4a40 100644 --- a/shared/src/dto/mimes.dto.ts +++ b/shared/src/dto/mimes.dto.ts @@ -1,4 +1,4 @@ -const tuple = (...args: T): T => args; +import tuple from '../types/tuple'; // Config diff --git a/shared/src/dto/syspreferences.dto.ts b/shared/src/dto/syspreferences.dto.ts new file mode 100644 index 0000000..9db9d5b --- /dev/null +++ b/shared/src/dto/syspreferences.dto.ts @@ -0,0 +1,8 @@ +import { generateRandomString } from '../util/random'; +import tuple from '../types/tuple'; +import { randomBytes } from 'crypto'; + +const SysPreferencesTuple = tuple('jwt_secret'); + +export const SysPreferences: string[] = SysPreferencesTuple; +export type SysPreferences = typeof SysPreferencesTuple[number]; diff --git a/shared/src/entities/syspreference.entity.ts b/shared/src/entities/syspreference.entity.ts index 42ce1ca..a638d34 100644 --- a/shared/src/entities/syspreference.entity.ts +++ b/shared/src/entities/syspreference.entity.ts @@ -1,11 +1,13 @@ -import { IsNotEmpty, IsOptional } from 'class-validator'; +import { IsEnum, IsNotEmpty, IsOptional } from 'class-validator'; +import { SysPreferences } from '../dto/syspreferences.dto'; export class ESysPreference { @IsOptional() id?: number; @IsNotEmpty() - name: string; + @IsEnum(SysPreferences) + key: SysPreferences; @IsNotEmpty() value: string; diff --git a/shared/src/types/tuple.ts b/shared/src/types/tuple.ts new file mode 100644 index 0000000..0432787 --- /dev/null +++ b/shared/src/types/tuple.ts @@ -0,0 +1,3 @@ +const tuple = (...args: T): T => args; + +export default tuple; diff --git a/shared/src/util/random.ts b/shared/src/util/random.ts new file mode 100644 index 0000000..8d39e7d --- /dev/null +++ b/shared/src/util/random.ts @@ -0,0 +1,12 @@ +import crypto from 'crypto'; + +const randomCharacters = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + +export function generateRandomString(length: number): string { + let out = ''; + for (let i = 0; i < length; i++) { + out += randomCharacters[crypto.randomInt(0, randomCharacters.length - 1)]; + } + return out; +}