Picsur/backend/src/collections/preference-db/sys-preference-db.service.ts

167 lines
5.2 KiB
TypeScript

import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import {
DecodedSysPref,
PrefValueType,
PrefValueTypeStrings
} from 'picsur-shared/dist/dto/preferences.dto';
import { SysPreference } from 'picsur-shared/dist/dto/sys-preferences.enum';
import { AsyncFailable, Fail, FT, HasFailed } from 'picsur-shared/dist/types';
import { Repository } from 'typeorm';
import {
ESysPreferenceBackend,
ESysPreferenceSchema
} from '../../database/entities/sys-preference.entity';
import {
SysPreferenceList,
SysPreferenceValueTypes
} from '../../models/constants/syspreferences.const';
import { MutexFallBack } from '../../models/util/mutex-fallback';
import { PreferenceCommonService } from './preference-common.service';
import { PreferenceDefaultsService } from './preference-defaults.service';
@Injectable()
export class SysPreferenceService {
private readonly logger = new Logger('SysPreferenceService');
constructor(
@InjectRepository(ESysPreferenceBackend)
private readonly sysPreferenceRepository: Repository<ESysPreferenceBackend>,
private readonly defaultsService: PreferenceDefaultsService,
private readonly prefCommon: PreferenceCommonService,
) {}
public async setPreference(
key: string,
value: PrefValueType,
): AsyncFailable<DecodedSysPref> {
// Validate
let sysPreference = await this.encodeSysPref(key, value);
if (HasFailed(sysPreference)) return sysPreference;
// Set
try {
// Upsert here, because we want to create a new record if it does not exist
await this.sysPreferenceRepository.upsert(sysPreference, {
conflictPaths: ['key'],
});
} catch (e) {
return Fail(FT.Database, e);
}
return {
key: sysPreference.key,
value,
// key has to be valid here, we validated it
type: SysPreferenceValueTypes[key as SysPreference],
};
}
public async getPreference(key: string): AsyncFailable<DecodedSysPref> {
// Validate
let validatedKey = this.prefCommon.validatePrefKey(key, SysPreference);
if (HasFailed(validatedKey)) return validatedKey;
// See the comment in 'mutex-fallback.ts' for why we are using a mutex here
return MutexFallBack(
`fetchSysPrefrence-${key}`,
async () => {
let existing: ESysPreferenceBackend | null;
try {
existing = await this.sysPreferenceRepository.findOne({
where: { key: validatedKey as SysPreference },
cache: 60000,
});
if (!existing) return null;
} catch (e) {
return Fail(FT.Database, e);
}
// Validate
const result = ESysPreferenceSchema.safeParse(existing);
if (!result.success) {
return Fail(FT.SysValidation, result.error);
}
// Return
return this.prefCommon.DecodePref(
result.data,
SysPreference,
SysPreferenceValueTypes,
);
},
() => this.saveDefault(validatedKey as SysPreference),
);
}
public async getStringPreference(key: string): AsyncFailable<string> {
return this.getPreferencePinned(key, 'string') as AsyncFailable<string>;
}
public async getNumberPreference(key: string): AsyncFailable<number> {
return this.getPreferencePinned(key, 'number') as AsyncFailable<number>;
}
public async getBooleanPreference(key: string): AsyncFailable<boolean> {
return this.getPreferencePinned(key, 'boolean') as AsyncFailable<boolean>;
}
// Get a preference that will be pinned to a specified type
private async getPreferencePinned(
key: string,
type: PrefValueTypeStrings,
): AsyncFailable<PrefValueType> {
let pref = await this.getPreference(key);
if (HasFailed(pref)) return pref;
if (pref.type !== type)
return Fail(FT.UsrValidation, 'Invalid preference type');
return pref.value;
}
public async getAllPreferences(): AsyncFailable<DecodedSysPref[]> {
// TODO: We are fetching each value invidually, we should fetch all at once
let internalSysPrefs = await Promise.all(
SysPreferenceList.map((key) => this.getPreference(key)),
);
if (internalSysPrefs.some((pref) => HasFailed(pref))) {
return Fail(FT.Internal, 'Could not get all preferences');
}
return internalSysPrefs as DecodedSysPref[];
}
// Private
private async saveDefault(
key: SysPreference, // Force enum here because we dont validate
): AsyncFailable<DecodedSysPref> {
return this.setPreference(key, this.defaultsService.sysDefaults[key]());
}
private async encodeSysPref(
key: string,
value: PrefValueType,
): AsyncFailable<ESysPreferenceBackend> {
const validated = await this.prefCommon.EncodePref(
key,
value,
SysPreference,
SysPreferenceValueTypes,
);
if (HasFailed(validated)) return validated;
let verifySysPreference = new ESysPreferenceBackend();
verifySysPreference.key = validated.key;
verifySysPreference.value = validated.value;
// It should already be valid, but these two validators might go out of sync
const result = ESysPreferenceSchema.safeParse(verifySysPreference);
if (!result.success) {
return Fail(FT.UsrValidation, result.error);
}
return result.data;
}
}