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

221 lines
6 KiB
TypeScript
Raw Normal View History

2022-03-04 11:24:49 +00:00
import { Injectable, Logger } from '@nestjs/common';
2022-03-03 23:01:34 +00:00
import { InjectRepository } from '@nestjs/typeorm';
2022-03-19 14:42:25 +00:00
import {
2022-09-02 15:18:22 +00:00
DecodedUsrPref,
PrefValueType,
2022-12-27 15:05:53 +00:00
PrefValueTypeStrings,
2022-04-02 21:25:49 +00:00
} from 'picsur-shared/dist/dto/preferences.dto';
2022-12-25 22:11:13 +00:00
import {
UsrPreference,
UsrPreferenceList,
UsrPreferenceValidators,
2022-12-27 15:05:53 +00:00
UsrPreferenceValueTypes,
2022-12-25 22:11:13 +00:00
} from 'picsur-shared/dist/dto/usr-preferences.enum';
import {
AsyncFailable,
Fail,
FT,
HasFailed,
} from 'picsur-shared/dist/types/failable';
2022-03-03 23:01:34 +00:00
import { Repository } from 'typeorm';
import {
2022-09-02 15:18:22 +00:00
EUsrPreferenceBackend,
2022-12-27 15:05:53 +00:00
EUsrPreferenceSchema,
2022-12-26 11:49:42 +00:00
} from '../../database/entities/system/usr-preference.entity';
2022-09-02 14:48:07 +00:00
import { MutexFallBack } from '../../util/mutex-fallback';
2022-04-18 12:34:53 +00:00
import { PreferenceCommonService } from './preference-common.service';
import { PreferenceDefaultsService } from './preference-defaults.service';
2022-03-03 23:01:34 +00:00
@Injectable()
2022-09-02 15:18:22 +00:00
export class UsrPreferenceDbService {
private readonly logger = new Logger(UsrPreferenceDbService.name);
2022-03-04 11:24:49 +00:00
2022-03-03 23:01:34 +00:00
constructor(
2022-04-13 12:33:18 +00:00
@InjectRepository(EUsrPreferenceBackend)
2022-06-27 15:37:37 +00:00
private readonly usrPreferenceRepository: Repository<EUsrPreferenceBackend>,
private readonly defaultsService: PreferenceDefaultsService,
private readonly prefCommon: PreferenceCommonService,
2022-03-03 23:01:34 +00:00
) {}
2022-03-04 11:24:49 +00:00
public async setPreference(
2022-04-13 14:33:07 +00:00
userid: string,
2022-03-27 20:33:13 +00:00
key: string,
value: PrefValueType,
2022-04-13 12:33:18 +00:00
): AsyncFailable<DecodedUsrPref> {
2022-03-19 14:42:25 +00:00
// Validate
const usrPreference = await this.encodeUsrPref(userid, key, value);
2022-04-13 14:33:07 +00:00
if (HasFailed(usrPreference)) return usrPreference;
2022-03-04 11:24:49 +00:00
2022-03-19 14:42:25 +00:00
// Set
2022-03-04 11:24:49 +00:00
try {
2022-03-27 21:56:25 +00:00
// Upsert here, because we want to create a new record if it does not exist
2022-04-13 14:33:07 +00:00
await this.usrPreferenceRepository.upsert(usrPreference, {
2022-04-25 13:44:30 +00:00
conflictPaths: ['key', 'user_id'],
2022-03-04 11:24:49 +00:00
});
2022-04-18 14:43:27 +00:00
} catch (e) {
2022-07-04 15:11:42 +00:00
return Fail(FT.Database, e);
2022-03-04 11:24:49 +00:00
}
2022-03-19 14:42:25 +00:00
// Return
return {
2022-04-13 14:33:07 +00:00
key: usrPreference.key,
2022-03-19 14:42:25 +00:00
value,
2022-03-27 20:33:13 +00:00
// key has to be valid here, we validated it
2022-04-13 12:33:18 +00:00
type: UsrPreferenceValueTypes[key as UsrPreference],
2022-04-13 14:33:07 +00:00
user: userid,
2022-03-19 14:42:25 +00:00
};
2022-03-04 11:24:49 +00:00
}
2022-04-13 14:33:07 +00:00
public async getPreference(
userid: string,
key: string,
): AsyncFailable<DecodedUsrPref> {
2022-03-19 14:42:25 +00:00
// Validate
const validatedKey = this.prefCommon.validatePrefKey(key, UsrPreference);
2022-03-19 14:42:25 +00:00
if (HasFailed(validatedKey)) return validatedKey;
2022-03-04 14:28:56 +00:00
2022-06-27 15:16:42 +00:00
// See the comment in 'mutex-fallback.ts' for why we are using a mutex here
2022-04-25 13:31:25 +00:00
return MutexFallBack(
'fetchUsrPrefrence',
async () => {
let existing: EUsrPreferenceBackend | null;
try {
existing = await this.usrPreferenceRepository.findOne({
2022-04-25 13:44:30 +00:00
where: { key: validatedKey as UsrPreference, user_id: userid },
2022-04-14 09:25:39 +00:00
cache: 60000,
2022-04-25 13:31:25 +00:00
});
if (!existing) return null;
} catch (e) {
2022-07-04 15:11:42 +00:00
return Fail(FT.Database, e);
2022-04-25 13:31:25 +00:00
}
2022-03-04 11:24:49 +00:00
2022-04-25 13:31:25 +00:00
// Validate
const result = EUsrPreferenceSchema.safeParse(existing);
if (!result.success) {
2022-07-04 15:11:42 +00:00
return Fail(FT.SysValidation, result.error);
2022-04-25 13:31:25 +00:00
}
2022-03-04 11:24:49 +00:00
2022-04-25 13:31:25 +00:00
// Return
2022-06-27 15:16:42 +00:00
const unpacked = this.prefCommon.DecodePref(
2022-12-25 22:11:13 +00:00
result.data as any,
2022-04-25 13:31:25 +00:00
UsrPreference,
UsrPreferenceValueTypes,
);
if (HasFailed(unpacked)) return unpacked;
return {
...unpacked,
2022-04-25 13:44:30 +00:00
user: result.data.user_id,
2022-04-25 13:31:25 +00:00
};
},
() => this.saveDefault(userid, validatedKey as UsrPreference),
2022-04-13 14:33:07 +00:00
);
2022-03-19 14:42:25 +00:00
}
2022-04-13 14:33:07 +00:00
public async getStringPreference(
userid: string,
key: string,
): AsyncFailable<string> {
return this.getPreferencePinned(
userid,
key,
'string',
) as AsyncFailable<string>;
2022-03-19 14:42:25 +00:00
}
2022-04-13 14:33:07 +00:00
public async getNumberPreference(
userid: string,
key: string,
): AsyncFailable<number> {
return this.getPreferencePinned(
userid,
key,
'number',
) as AsyncFailable<number>;
2022-03-04 11:24:49 +00:00
}
2022-04-13 14:33:07 +00:00
public async getBooleanPreference(
userid: string,
key: string,
): AsyncFailable<boolean> {
return this.getPreferencePinned(
userid,
key,
'boolean',
) as AsyncFailable<boolean>;
2022-03-27 21:56:25 +00:00
}
2022-06-27 15:16:42 +00:00
// Get a preference that will be pinned to a specified type
2022-03-27 21:56:25 +00:00
private async getPreferencePinned(
2022-04-13 14:33:07 +00:00
userid: string,
2022-03-27 21:56:25 +00:00
key: string,
type: PrefValueTypeStrings,
): AsyncFailable<PrefValueType> {
const pref = await this.getPreference(userid, key);
2022-03-19 14:42:25 +00:00
if (HasFailed(pref)) return pref;
2022-07-04 15:11:42 +00:00
if (pref.type !== type)
return Fail(FT.UsrValidation, 'Invalid preference type');
2022-03-19 14:42:25 +00:00
2022-03-27 21:56:25 +00:00
return pref.value;
2022-03-19 14:42:25 +00:00
}
2022-04-13 14:33:07 +00:00
public async getAllPreferences(
userid: string,
): AsyncFailable<DecodedUsrPref[]> {
2022-03-27 21:56:25 +00:00
// TODO: We are fetching each value invidually, we should fetch all at once
const internalSysPrefs = await Promise.all(
2022-04-13 14:33:07 +00:00
UsrPreferenceList.map((key) => this.getPreference(userid, key)),
2022-03-19 14:56:29 +00:00
);
if (internalSysPrefs.some((pref) => HasFailed(pref))) {
2022-07-04 15:11:42 +00:00
return Fail(FT.Internal, 'Could not get all preferences');
2022-03-19 14:56:29 +00:00
}
2022-04-13 12:33:18 +00:00
return internalSysPrefs as DecodedUsrPref[];
2022-03-19 14:56:29 +00:00
}
2022-03-27 21:56:25 +00:00
2022-03-19 14:42:25 +00:00
// Private
2022-03-04 11:24:49 +00:00
private async saveDefault(
2022-04-13 14:33:07 +00:00
userid: string,
2022-04-13 12:33:18 +00:00
key: UsrPreference, // Force enum here because we dont validate
): AsyncFailable<DecodedUsrPref> {
2022-04-13 14:33:07 +00:00
return this.setPreference(
userid,
key,
2022-12-25 22:11:13 +00:00
this.defaultsService.getUsrDefault(key),
2022-04-13 14:33:07 +00:00
);
2022-03-19 14:42:25 +00:00
}
2022-06-27 15:16:42 +00:00
private async encodeUsrPref(
2022-04-13 14:33:07 +00:00
userid: string,
2022-03-04 14:28:56 +00:00
key: string,
value: PrefValueType,
2022-04-13 12:33:18 +00:00
): AsyncFailable<EUsrPreferenceBackend> {
2022-06-27 15:16:42 +00:00
const validated = await this.prefCommon.EncodePref(
2022-04-13 14:33:07 +00:00
key,
value,
UsrPreference,
UsrPreferenceValueTypes,
);
if (HasFailed(validated)) return validated;
2022-03-19 14:42:25 +00:00
const valueValidated =
UsrPreferenceValidators[key as UsrPreference].safeParse(value);
if (!valueValidated.success) {
return Fail(FT.UsrValidation, undefined, valueValidated.error);
}
2022-12-25 22:11:13 +00:00
const verifySysPreference = new EUsrPreferenceBackend();
2022-04-13 14:33:07 +00:00
verifySysPreference.key = validated.key;
verifySysPreference.value = validated.value;
2022-04-25 13:44:30 +00:00
verifySysPreference.user_id = userid;
2022-03-04 14:28:56 +00:00
2022-03-27 21:56:25 +00:00
// It should already be valid, but these two validators might go out of sync
2022-04-13 12:33:18 +00:00
const result = EUsrPreferenceSchema.safeParse(verifySysPreference);
2022-04-04 08:36:59 +00:00
if (!result.success) {
2022-07-04 15:11:42 +00:00
return Fail(FT.UsrValidation, result.error);
2022-03-04 14:28:56 +00:00
}
2022-04-04 08:36:59 +00:00
return result.data;
2022-03-04 14:28:56 +00:00
}
2022-03-03 23:01:34 +00:00
}