- Enhanced deploy-backend.sh and update-backend.sh to create the adult verification directory under /opt/yourpart-data. - Updated permissions for the new directory to ensure proper access control. - Refactored file path handling in AdminService and SettingsService to utilize the new directory structure for adult verification files.
700 lines
26 KiB
JavaScript
700 lines
26 KiB
JavaScript
import BaseService from './BaseService.js';
|
|
import UserParamType from '../models/type/user_param.js';
|
|
import SettingsType from '../models/type/settings.js';
|
|
import UserParam from '../models/community/user_param.js';
|
|
import User from '../models/community/user.js';
|
|
import UserParamValue from '../models/type/user_param_value.js';
|
|
import Interest from '../models/type/interest.js';
|
|
import UserInterest from '../models/community/interest.js';
|
|
import InterestTranslation from '../models/type/interest_translation.js';
|
|
import { Op } from 'sequelize';
|
|
import UserParamVisibilityType from '../models/type/user_param_visibility.js';
|
|
import UserParamVisibility from '../models/community/user_param_visibility.js';
|
|
import { encrypt } from '../utils/encryption.js';
|
|
import { sequelize } from '../utils/sequelize.js';
|
|
import fsPromises from 'fs/promises';
|
|
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
import { getAdultVerificationBaseDir } from '../utils/storagePaths.js';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
/** Wie UserParam.value-Setter: bei Verschlüsselungsfehler leeren String speichern, nicht crashen. */
|
|
function encryptUserParamValue(plain) {
|
|
try {
|
|
return encrypt(plain);
|
|
} catch (error) {
|
|
console.error('Error encrypting user_param value:', error);
|
|
return '';
|
|
}
|
|
}
|
|
|
|
class SettingsService extends BaseService{
|
|
async ensureSpecialUserParamType(description) {
|
|
const specialTypes = {
|
|
adult_verification_status: { datatype: 'string', setting: 'account', orderId: 910, minAge: 18 },
|
|
adult_verification_request: { datatype: 'string', setting: 'account', orderId: 911, minAge: 18 },
|
|
adult_upload_blocked: { datatype: 'bool', setting: 'account', orderId: 912, minAge: 18 },
|
|
};
|
|
const definition = specialTypes[description];
|
|
if (!definition) {
|
|
return null;
|
|
}
|
|
|
|
const [settingsType] = await SettingsType.findOrCreate({
|
|
where: { name: definition.setting },
|
|
defaults: { name: definition.setting }
|
|
});
|
|
|
|
const [paramType] = await UserParamType.findOrCreate({
|
|
where: { description },
|
|
defaults: {
|
|
description,
|
|
datatype: definition.datatype,
|
|
settingsId: settingsType.id,
|
|
orderId: definition.orderId,
|
|
minAge: definition.minAge,
|
|
immutable: false
|
|
}
|
|
});
|
|
return paramType;
|
|
}
|
|
|
|
parseAdultVerificationRequest(value) {
|
|
if (!value) return null;
|
|
try {
|
|
return JSON.parse(value);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
normalizeAdultVerificationStatus(value) {
|
|
if (['pending', 'approved', 'rejected'].includes(value)) {
|
|
return value;
|
|
}
|
|
return 'none';
|
|
}
|
|
|
|
async getAdultAccessStateByUserId(userId) {
|
|
const userParams = await this.getUserParams(userId, ['birthdate', 'adult_verification_status', 'adult_verification_request']);
|
|
let birthdate = null;
|
|
let adultVerificationStatus = 'none';
|
|
let adultVerificationRequest = null;
|
|
for (const param of userParams) {
|
|
if (param.paramType.description === 'birthdate') {
|
|
birthdate = param.value;
|
|
}
|
|
if (param.paramType.description === 'adult_verification_status') {
|
|
adultVerificationStatus = this.normalizeAdultVerificationStatus(param.value);
|
|
}
|
|
if (param.paramType.description === 'adult_verification_request') {
|
|
adultVerificationRequest = this.parseAdultVerificationRequest(param.value);
|
|
}
|
|
}
|
|
const age = birthdate ? this.calculateAge(birthdate) : null;
|
|
const isAdult = age !== null && age >= 18;
|
|
return {
|
|
age,
|
|
isAdult,
|
|
adultVerificationStatus: isAdult ? adultVerificationStatus : 'none',
|
|
adultVerificationRequest: isAdult ? adultVerificationRequest : null,
|
|
adultAccessEnabled: isAdult && adultVerificationStatus === 'approved'
|
|
};
|
|
}
|
|
|
|
buildAdultVerificationFilePath(fileName) {
|
|
return path.join(getAdultVerificationBaseDir(), fileName);
|
|
}
|
|
|
|
async saveAdultVerificationDocument(file) {
|
|
const allowedMimeTypes = ['image/jpeg', 'image/png', 'image/webp', 'application/pdf'];
|
|
if (!file || !file.buffer) {
|
|
throw new Error('No verification document provided');
|
|
}
|
|
if (!allowedMimeTypes.includes(file.mimetype)) {
|
|
throw new Error('Unsupported verification document type');
|
|
}
|
|
|
|
const ext = path.extname(file.originalname || '').toLowerCase();
|
|
const safeExt = ext && ext.length <= 8 ? ext : (file.mimetype === 'application/pdf' ? '.pdf' : '.bin');
|
|
const fileName = `${uuidv4()}${safeExt}`;
|
|
const filePath = this.buildAdultVerificationFilePath(fileName);
|
|
await fsPromises.mkdir(path.dirname(filePath), { recursive: true });
|
|
await fsPromises.writeFile(filePath, file.buffer);
|
|
return { fileName, filePath };
|
|
}
|
|
|
|
async upsertUserParam(userId, description, value) {
|
|
let paramType = await UserParamType.findOne({ where: { description } });
|
|
if (!paramType) {
|
|
paramType = await this.ensureSpecialUserParamType(description);
|
|
}
|
|
if (!paramType) {
|
|
throw new Error(`Missing user param type: ${description}`);
|
|
}
|
|
const existingParam = await UserParam.findOne({
|
|
where: { userId, paramTypeId: paramType.id }
|
|
});
|
|
if (existingParam) {
|
|
await existingParam.update({ value });
|
|
return existingParam;
|
|
}
|
|
return UserParam.create({
|
|
userId,
|
|
paramTypeId: paramType.id,
|
|
value
|
|
});
|
|
}
|
|
|
|
async getUserParams(userId, paramDescriptions) {
|
|
return await UserParam.findAll({
|
|
where: { userId },
|
|
include: [
|
|
{
|
|
model: UserParamType,
|
|
as: 'paramType',
|
|
where: { description: { [Op.in]: paramDescriptions } },
|
|
order: [[ 'order_id', 'asc' ]]
|
|
}
|
|
]
|
|
});
|
|
}
|
|
|
|
async getFieldOptions(field) {
|
|
if (['singleselect', 'multiselect'].includes(field.datatype)) {
|
|
return await UserParamValue.findAll({
|
|
where: { userParamTypeId: field.id },
|
|
order: [[ 'order_id', 'asc' ]]
|
|
});
|
|
}
|
|
return [];
|
|
}
|
|
|
|
async filterSettings(hashedUserId, type) {
|
|
const user = await this.getUserByHashedId(hashedUserId);
|
|
const userParams = await this.getUserParams(user.id, ['birthdate', 'gender']);
|
|
let birthdate = null;
|
|
let gender = null;
|
|
for (const param of userParams) {
|
|
if (param.paramType.description === 'birthdate') {
|
|
birthdate = param.value;
|
|
}
|
|
if (param.paramType.description === 'gender') {
|
|
const genderResult = await UserParamValue.findOne({ where: { id: param.value } });
|
|
gender = genderResult ? genderResult.dataValues?.value : null;
|
|
}
|
|
}
|
|
const age = birthdate ? this.calculateAge(birthdate) : null;
|
|
const fields = await UserParamType.findAll({
|
|
include: [
|
|
{
|
|
model: SettingsType,
|
|
as: 'settings_type',
|
|
where: { name: type }
|
|
},
|
|
{
|
|
model: UserParam,
|
|
as: 'user_params',
|
|
required: false,
|
|
include: [
|
|
{
|
|
model: User,
|
|
as: 'user',
|
|
where: { id: user.id }
|
|
},
|
|
{
|
|
model: UserParamVisibility,
|
|
as: 'param_visibilities',
|
|
required: false,
|
|
include: [
|
|
{
|
|
model: UserParamVisibilityType,
|
|
as: 'visibility_type'
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
],
|
|
where: {
|
|
[Op.and]: [
|
|
{ minAge: { [Op.or]: [null, { [Op.lte]: age }] } },
|
|
{ gender: { [Op.or]: [null, gender] } }
|
|
]
|
|
}
|
|
});
|
|
return await Promise.all(fields.map(async (field) => {
|
|
const options = await this.getFieldOptions(field);
|
|
const visibilityData = field.user_params[0]?.param_visibilities?.[0];
|
|
const visibility = visibilityData
|
|
? { id: visibilityData.visibility_type?.id, description: visibilityData.visibility_type?.description }
|
|
: { id: null, description: 'Invisible' };
|
|
return {
|
|
id: field.id,
|
|
name: field.description,
|
|
minAge: field.minAge,
|
|
gender: field.gender,
|
|
datatype: field.datatype,
|
|
unit: field.unit,
|
|
immutable: field.immutable,
|
|
value: field.user_params.length > 0 ? field.user_params[0].value : null,
|
|
options: options.map(opt => ({ id: opt.id, value: opt.value })),
|
|
visibility
|
|
};
|
|
}));
|
|
}
|
|
|
|
async updateSetting(hashedUserId, settingId, value) {
|
|
try {
|
|
const user = await this.getUserByHashedId(hashedUserId);
|
|
const paramType = await UserParamType.findOne({ where: { id: settingId } });
|
|
if (!paramType) {
|
|
throw new Error('Parameter type not found');
|
|
}
|
|
|
|
// Prüfe ob das Feld unveränderlich ist
|
|
if (paramType.immutable) {
|
|
const userParam = await UserParam.findOne({
|
|
where: { userId: user.id, paramTypeId: settingId }
|
|
});
|
|
|
|
// Wenn bereits ein Wert existiert, ist das Feld unveränderlich
|
|
if (userParam && userParam.value) {
|
|
throw new Error('This field cannot be changed. Please contact support for modifications.');
|
|
}
|
|
}
|
|
|
|
const userParam = await UserParam.findOne({
|
|
where: { userId: user.id, paramTypeId: settingId }
|
|
});
|
|
if (userParam) {
|
|
console.log('update param with ', value)
|
|
if (typeof value === 'boolean') {
|
|
value = value ? 'true' : 'false';
|
|
}
|
|
await userParam.update({value: value});
|
|
} else {
|
|
await UserParam.create(
|
|
{
|
|
userId: user.id,
|
|
paramTypeId: settingId,
|
|
value: value
|
|
}
|
|
);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error updating setting:', hashedUserId, settingId, value, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async getTypeParamValueId(paramValue) {
|
|
const userParamValueObject = await UserParamValue.findOne({
|
|
where: { value: paramValue }
|
|
});
|
|
if (!userParamValueObject) {
|
|
throw new Error('Parameter value not found');
|
|
}
|
|
return userParamValueObject.id;
|
|
}
|
|
|
|
async getTypeParamValues(type) {
|
|
const userParamValues = await UserParamValue.findAll({
|
|
include: [
|
|
{
|
|
model: UserParamType,
|
|
as: 'user_param_value_type',
|
|
where: { description: type }
|
|
}
|
|
]
|
|
});
|
|
return userParamValues.map(type => ({ id: type.dataValues.id, name: type.dataValues.value }));
|
|
}
|
|
|
|
async getTypeParamValue(id) {
|
|
const userParamValueObject = await UserParamValue.findOne({
|
|
where: { id }
|
|
});
|
|
if (!userParamValueObject) {
|
|
throw new Error('Parameter value not found');
|
|
}
|
|
return userParamValueObject.value;
|
|
}
|
|
|
|
async addInterest(hashedUserId, name) {
|
|
try {
|
|
const user = await this.getUserByHashedId(hashedUserId);
|
|
|
|
const existingInterests = await Interest.findAll({ where: { name: name.toLowerCase() } });
|
|
if (existingInterests.length > 0) {
|
|
throw new Error('Interest already exists');
|
|
}
|
|
|
|
const userParam = await this.getUserParams(user.id, ['language']);
|
|
let language = 'en';
|
|
if (userParam.length > 0) {
|
|
const userParamValue = await UserParamValue.findOne({
|
|
where: { id: userParam[0].value }
|
|
});
|
|
language = userParamValue ? userParamValue.value : 'en';
|
|
}
|
|
|
|
const languageParam = await UserParamValue.findOne({ where: { value: language } });
|
|
const languageId = languageParam.id;
|
|
const interest = await Interest.create({ name: name.toLowerCase(), allowed: false, adultOnly: true });
|
|
await InterestTranslation.create({ interestsId: interest.id, language: languageId, translation: name });
|
|
return interest;
|
|
} catch (error) {
|
|
console.error('Error adding interest:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async addUserInterest(hashedUserId, interestId) {
|
|
try {
|
|
const user = await this.getUserByHashedId(hashedUserId);
|
|
|
|
const userParams = await this.getUserParams(user.id, ['birthdate']);
|
|
let birthdate = null;
|
|
for (const param of userParams) {
|
|
if (param.paramType.description === 'birthdate') {
|
|
birthdate = param.value;
|
|
}
|
|
}
|
|
|
|
const age = birthdate ? this.calculateAge(birthdate) : 0;
|
|
const interestsFilter = { id: interestId, allowed: true };
|
|
if (age < 18) {
|
|
interestsFilter[Op.or] = [
|
|
{ adultOnly: false },
|
|
{ adultOnly: { [Op.eq]: null } }
|
|
];
|
|
}
|
|
|
|
const existingInterests = await Interest.findAll({ where: interestsFilter });
|
|
if (existingInterests.length === 0) {
|
|
throw new Error('Interest not found');
|
|
};
|
|
|
|
const interest = await UserInterest.findAll({
|
|
where: { userId: user.id, userinterestId: interestId }
|
|
});
|
|
if (interest.length > 0) {
|
|
throw new Error('Interest already exists');
|
|
}
|
|
await UserInterest.create({ userId: user.id, userinterestId: interestId });
|
|
} catch (error) {
|
|
console.error('Error adding user interest:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async removeInterest(hashedUserId, interestId) {
|
|
try {
|
|
const user = await this.getUserByHashedId(hashedUserId);
|
|
const interests = await UserInterest.findAll({
|
|
where: { userId: user.id, userinterestId: interestId }
|
|
});
|
|
for (const interest of interests) {
|
|
await interest.destroy();
|
|
}
|
|
} catch (error) {
|
|
console.error('Error removing interest:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async getAccountSettings(hashedUserId) {
|
|
try {
|
|
const user = await this.getUserByHashedId(hashedUserId);
|
|
if (!user) {
|
|
throw new Error('User not found');
|
|
}
|
|
|
|
// Die Email wird automatisch durch den Getter entschlüsselt
|
|
// Falls die Entschlüsselung fehlschlägt, verwende null
|
|
let email = null;
|
|
try {
|
|
email = user.email; // Getter entschlüsselt automatisch
|
|
} catch (decryptError) {
|
|
console.warn('Email decryption failed, using null:', decryptError.message);
|
|
email = null;
|
|
}
|
|
|
|
const adultAccess = await this.getAdultAccessStateByUserId(user.id);
|
|
|
|
return {
|
|
username: user.username,
|
|
email: email,
|
|
showinsearch: user.searchable,
|
|
...adultAccess
|
|
};
|
|
} catch (error) {
|
|
console.error('Error getting account settings:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async setAccountSettings({ userId, settings }) {
|
|
try {
|
|
const user = await this.getUserByHashedId(userId);
|
|
if (!user) {
|
|
throw new Error('User not found');
|
|
}
|
|
|
|
const adultAccess = await this.getAdultAccessStateByUserId(user.id);
|
|
|
|
// Update username if provided
|
|
if (settings.username !== undefined) {
|
|
await user.update({ username: settings.username });
|
|
}
|
|
|
|
// Update email if provided
|
|
if (settings.email !== undefined) {
|
|
await user.update({ email: settings.email });
|
|
}
|
|
|
|
// Update searchable flag if provided
|
|
if (settings.showinsearch !== undefined) {
|
|
await user.update({ searchable: settings.showinsearch });
|
|
}
|
|
|
|
if (settings.requestAdultVerification) {
|
|
if (!adultAccess.isAdult) {
|
|
throw new Error('Adult verification can only be requested by adult users');
|
|
}
|
|
|
|
const normalizedValue = adultAccess.adultVerificationStatus === 'approved'
|
|
? 'approved'
|
|
: 'pending';
|
|
await this.upsertUserParam(user.id, 'adult_verification_status', normalizedValue);
|
|
}
|
|
|
|
// Update password if provided and not empty
|
|
if (settings.newpassword && settings.newpassword.trim() !== '') {
|
|
if (!settings.oldpassword || settings.oldpassword.trim() === '') {
|
|
throw new Error('Old password is required to change password');
|
|
}
|
|
|
|
// Verify old password
|
|
const bcrypt = await import('bcryptjs');
|
|
const match = await bcrypt.compare(settings.oldpassword, user.password);
|
|
if (!match) {
|
|
throw new Error('Old password is incorrect');
|
|
}
|
|
|
|
// Hash new password
|
|
const hashedPassword = await bcrypt.hash(settings.newpassword, 10);
|
|
await user.update({ password: hashedPassword });
|
|
}
|
|
|
|
return { success: true };
|
|
} catch (error) {
|
|
console.error('Error setting account settings:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async submitAdultVerificationRequest(hashedUserId, { note }, file) {
|
|
const user = await this.getUserByHashedId(hashedUserId);
|
|
if (!user) {
|
|
throw new Error('User not found');
|
|
}
|
|
const adultAccess = await this.getAdultAccessStateByUserId(user.id);
|
|
if (!adultAccess.isAdult) {
|
|
throw new Error('Adult verification can only be requested by adult users');
|
|
}
|
|
if (!file) {
|
|
throw new Error('No verification document provided');
|
|
}
|
|
|
|
const savedFile = await this.saveAdultVerificationDocument(file);
|
|
const requestPayload = {
|
|
fileName: savedFile.fileName,
|
|
storedFileName: savedFile.fileName,
|
|
filePath: savedFile.filePath,
|
|
originalName: file.originalname,
|
|
mimeType: file.mimetype,
|
|
note: note || '',
|
|
submittedAt: new Date().toISOString()
|
|
};
|
|
|
|
await this.upsertUserParam(user.id, 'adult_verification_request', JSON.stringify(requestPayload));
|
|
await this.upsertUserParam(user.id, 'adult_verification_status', adultAccess.adultVerificationStatus === 'approved' ? 'approved' : 'pending');
|
|
|
|
return requestPayload;
|
|
}
|
|
|
|
async getVisibilities() {
|
|
return UserParamVisibilityType.findAll();
|
|
}
|
|
|
|
async updateVisibility(hashedUserId, userParamTypeId, visibilityId) {
|
|
try {
|
|
const user = await this.getUserByHashedId(hashedUserId);
|
|
if (!user) {
|
|
throw new Error('User not found');
|
|
}
|
|
const userParam = await UserParam.findOne({
|
|
where: { paramTypeId: userParamTypeId, userId: user.id }
|
|
});
|
|
if (!userParam) {
|
|
console.error(`UserParam not found for settingId: ${userParamTypeId} and userId: ${user.id}`);
|
|
throw new Error('User parameter not found or does not belong to the user');
|
|
}
|
|
let userParamVisibility = await UserParamVisibility.findOne({
|
|
where: { param_id: userParam.id }
|
|
});
|
|
if (userParamVisibility) {
|
|
userParamVisibility.visibility = visibilityId;
|
|
await userParamVisibility.save();
|
|
} else {
|
|
await UserParamVisibility.create({
|
|
param_id: userParam.id,
|
|
visibility: visibilityId
|
|
});
|
|
}
|
|
console.log(`Visibility updated for settingId: ${userParamTypeId} with visibilityId: ${visibilityId}`);
|
|
} catch (error) {
|
|
console.error('Error updating visibility:', error.message);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* LLM-/Sprachassistent: Werte in community.user_param, Typen in type.user_param,
|
|
* Gruppe type.settings.name = languageAssistant. API-Key separat (llm_api_key), Metadaten als JSON in llm_settings.
|
|
* Kein Klartext-Key an den Client.
|
|
*/
|
|
async getLlmSettings(hashedUserId) {
|
|
const user = await this.getUserByHashedId(hashedUserId);
|
|
const settingsType = await UserParamType.findOne({ where: { description: 'llm_settings' } });
|
|
const apiKeyType = await UserParamType.findOne({ where: { description: 'llm_api_key' } });
|
|
if (!settingsType || !apiKeyType) {
|
|
return {
|
|
enabled: true,
|
|
baseUrl: '',
|
|
model: 'gpt-4o-mini',
|
|
hasKey: false,
|
|
keyLast4: null
|
|
};
|
|
}
|
|
|
|
const settingsRow = await UserParam.findOne({
|
|
where: { userId: user.id, paramTypeId: settingsType.id }
|
|
});
|
|
const keyRow = await UserParam.findOne({
|
|
where: { userId: user.id, paramTypeId: apiKeyType.id }
|
|
});
|
|
|
|
let parsed = {};
|
|
if (settingsRow?.value) {
|
|
try {
|
|
parsed = JSON.parse(settingsRow.value);
|
|
} catch {
|
|
parsed = {};
|
|
}
|
|
}
|
|
|
|
const hasStoredKey = Boolean(keyRow && keyRow.getDataValue('value') && String(keyRow.getDataValue('value')).trim());
|
|
const hasReadableKey = Boolean(keyRow && keyRow.value && String(keyRow.value).trim());
|
|
|
|
return {
|
|
enabled: parsed.enabled !== false,
|
|
baseUrl: parsed.baseUrl || '',
|
|
model: parsed.model || 'gpt-4o-mini',
|
|
hasKey: hasStoredKey,
|
|
keyLast4: parsed.keyLast4 || null,
|
|
keyStatus: hasStoredKey ? (hasReadableKey ? 'stored' : 'invalid') : 'missing'
|
|
};
|
|
}
|
|
|
|
async saveLlmSettings(hashedUserId, payload) {
|
|
const user = await this.getUserByHashedId(hashedUserId);
|
|
const settingsType = await UserParamType.findOne({ where: { description: 'llm_settings' } });
|
|
const apiKeyType = await UserParamType.findOne({ where: { description: 'llm_api_key' } });
|
|
if (!settingsType || !apiKeyType) {
|
|
throw new Error(
|
|
'LLM-Einstellungstypen fehlen (languageAssistant / llm_settings / llm_api_key). initializeSettings & initializeTypes ausführen.'
|
|
);
|
|
}
|
|
|
|
const settingsRow = await UserParam.findOne({
|
|
where: { userId: user.id, paramTypeId: settingsType.id }
|
|
});
|
|
let parsed = {};
|
|
if (settingsRow?.value) {
|
|
try {
|
|
parsed = JSON.parse(settingsRow.value);
|
|
} catch {
|
|
parsed = {};
|
|
}
|
|
}
|
|
|
|
const { apiKey, clearKey, baseUrl, model, enabled } = payload;
|
|
|
|
await sequelize.transaction(async (transaction) => {
|
|
if (clearKey) {
|
|
const keyRow = await UserParam.findOne({
|
|
where: { userId: user.id, paramTypeId: apiKeyType.id },
|
|
transaction
|
|
});
|
|
if (keyRow) {
|
|
await keyRow.destroy({ transaction });
|
|
}
|
|
delete parsed.keyLast4;
|
|
} else if (apiKey !== undefined && String(apiKey).trim() !== '') {
|
|
const plain = String(apiKey).trim();
|
|
parsed.keyLast4 = plain.length >= 4 ? plain.slice(-4) : plain;
|
|
const encKey = encryptUserParamValue(plain);
|
|
const [keyRow] = await UserParam.findOrCreate({
|
|
where: { userId: user.id, paramTypeId: apiKeyType.id },
|
|
defaults: {
|
|
userId: user.id,
|
|
paramTypeId: apiKeyType.id,
|
|
// Platzhalter: Setter verschlüsselt; wird sofort durch encKey überschrieben.
|
|
value: ' '
|
|
},
|
|
transaction
|
|
});
|
|
keyRow.setDataValue('value', encKey);
|
|
await keyRow.save({ fields: ['value'], transaction });
|
|
}
|
|
|
|
if (baseUrl !== undefined) {
|
|
parsed.baseUrl = String(baseUrl).trim();
|
|
}
|
|
if (model !== undefined) {
|
|
parsed.model = String(model).trim() || 'gpt-4o-mini';
|
|
}
|
|
if (enabled !== undefined) {
|
|
parsed.enabled = Boolean(enabled);
|
|
}
|
|
if (!parsed.model) {
|
|
parsed.model = 'gpt-4o-mini';
|
|
}
|
|
|
|
const jsonStr = JSON.stringify(parsed);
|
|
const encMeta = encryptUserParamValue(jsonStr);
|
|
const [metaRow] = await UserParam.findOrCreate({
|
|
where: { userId: user.id, paramTypeId: settingsType.id },
|
|
defaults: {
|
|
userId: user.id,
|
|
paramTypeId: settingsType.id,
|
|
value: ' '
|
|
},
|
|
transaction
|
|
});
|
|
metaRow.setDataValue('value', encMeta);
|
|
await metaRow.save({ fields: ['value'], transaction });
|
|
});
|
|
|
|
return { success: true };
|
|
}
|
|
}
|
|
|
|
export default new SettingsService();
|