dedupe
This commit is contained in:
parent
6913607eef
commit
0844071b7a
@ -116,7 +116,8 @@ const normalizeString = (value: string | null | undefined) => {
|
||||
|
||||
const normalizePrivateInput = (value: string | null | undefined) => {
|
||||
const trimmed = value?.trim()
|
||||
return trimmed ? trimmed : undefined
|
||||
if (trimmed === "") return null
|
||||
return trimmed || undefined
|
||||
}
|
||||
|
||||
const isSameStringList = (left: string[] | null | undefined, right: string[] | null | undefined) => {
|
||||
|
||||
@ -3,15 +3,14 @@ import { updateSettingsSchema } from '@shared/settings-schema.js';
|
||||
import * as settingsRepo from '@server/repositories/settings.js';
|
||||
import {
|
||||
applyEnvValue,
|
||||
getEnvSettingsData,
|
||||
normalizeEnvInput,
|
||||
} from '@server/services/envSettings.js';
|
||||
import {
|
||||
extractProjectsFromProfile,
|
||||
normalizeResumeProjectsSettings,
|
||||
resolveResumeProjectsSettings,
|
||||
} from '@server/services/resumeProjects.js';
|
||||
import { getProfile } from '@server/services/profile.js';
|
||||
import { getEffectiveSettings } from '@server/services/settings.js';
|
||||
|
||||
export const settingsRouter = Router();
|
||||
|
||||
@ -20,142 +19,8 @@ export const settingsRouter = Router();
|
||||
*/
|
||||
settingsRouter.get('/', async (_req: Request, res: Response) => {
|
||||
try {
|
||||
const overrideModel = await settingsRepo.getSetting('model');
|
||||
const defaultModel = process.env.MODEL || 'openai/gpt-4o-mini';
|
||||
const model = overrideModel || defaultModel;
|
||||
|
||||
// Specific AI models
|
||||
const overrideModelScorer = await settingsRepo.getSetting('modelScorer');
|
||||
const modelScorer = overrideModelScorer || model;
|
||||
|
||||
const overrideModelTailoring = await settingsRepo.getSetting('modelTailoring');
|
||||
const modelTailoring = overrideModelTailoring || model;
|
||||
|
||||
const overrideModelProjectSelection = await settingsRepo.getSetting('modelProjectSelection');
|
||||
const modelProjectSelection = overrideModelProjectSelection || model;
|
||||
|
||||
const overridePipelineWebhookUrl = await settingsRepo.getSetting('pipelineWebhookUrl');
|
||||
const defaultPipelineWebhookUrl = process.env.PIPELINE_WEBHOOK_URL || process.env.WEBHOOK_URL || '';
|
||||
const pipelineWebhookUrl = overridePipelineWebhookUrl || defaultPipelineWebhookUrl;
|
||||
|
||||
const overrideJobCompleteWebhookUrl = await settingsRepo.getSetting('jobCompleteWebhookUrl');
|
||||
const defaultJobCompleteWebhookUrl = process.env.JOB_COMPLETE_WEBHOOK_URL || '';
|
||||
const jobCompleteWebhookUrl = overrideJobCompleteWebhookUrl || defaultJobCompleteWebhookUrl;
|
||||
|
||||
const profile = await getProfile();
|
||||
const { catalog } = extractProjectsFromProfile(profile);
|
||||
const overrideResumeProjectsRaw = await settingsRepo.getSetting('resumeProjects');
|
||||
const resumeProjectsData = resolveResumeProjectsSettings({ catalog, overrideRaw: overrideResumeProjectsRaw });
|
||||
|
||||
const overrideUkvisajobsMaxJobsRaw = await settingsRepo.getSetting('ukvisajobsMaxJobs');
|
||||
const defaultUkvisajobsMaxJobs = 50;
|
||||
const overrideUkvisajobsMaxJobs = overrideUkvisajobsMaxJobsRaw ? parseInt(overrideUkvisajobsMaxJobsRaw, 10) : null;
|
||||
const ukvisajobsMaxJobs = overrideUkvisajobsMaxJobs ?? defaultUkvisajobsMaxJobs;
|
||||
|
||||
const overrideGradcrackerMaxJobsPerTermRaw = await settingsRepo.getSetting('gradcrackerMaxJobsPerTerm');
|
||||
const defaultGradcrackerMaxJobsPerTerm = 50;
|
||||
const overrideGradcrackerMaxJobsPerTerm = overrideGradcrackerMaxJobsPerTermRaw ? parseInt(overrideGradcrackerMaxJobsPerTermRaw, 10) : null;
|
||||
const gradcrackerMaxJobsPerTerm = overrideGradcrackerMaxJobsPerTerm ?? defaultGradcrackerMaxJobsPerTerm;
|
||||
|
||||
const overrideSearchTermsRaw = await settingsRepo.getSetting('searchTerms');
|
||||
const defaultSearchTermsEnv = process.env.JOBSPY_SEARCH_TERMS || 'web developer';
|
||||
const defaultSearchTerms = defaultSearchTermsEnv.split('|').map(s => s.trim()).filter(Boolean);
|
||||
const overrideSearchTerms = overrideSearchTermsRaw ? JSON.parse(overrideSearchTermsRaw) as string[] : null;
|
||||
const searchTerms = overrideSearchTerms ?? defaultSearchTerms;
|
||||
|
||||
// JobSpy settings (GET)
|
||||
const overrideJobspyLocation = await settingsRepo.getSetting('jobspyLocation');
|
||||
const defaultJobspyLocation = process.env.JOBSPY_LOCATION || 'UK';
|
||||
const jobspyLocation = overrideJobspyLocation || defaultJobspyLocation;
|
||||
|
||||
const overrideJobspyResultsWantedRaw = await settingsRepo.getSetting('jobspyResultsWanted');
|
||||
const defaultJobspyResultsWanted = parseInt(process.env.JOBSPY_RESULTS_WANTED || '200', 10);
|
||||
const overrideJobspyResultsWanted = overrideJobspyResultsWantedRaw ? parseInt(overrideJobspyResultsWantedRaw, 10) : null;
|
||||
const jobspyResultsWanted = overrideJobspyResultsWanted ?? defaultJobspyResultsWanted;
|
||||
|
||||
const overrideJobspyHoursOldRaw = await settingsRepo.getSetting('jobspyHoursOld');
|
||||
const defaultJobspyHoursOld = parseInt(process.env.JOBSPY_HOURS_OLD || '72', 10);
|
||||
const overrideJobspyHoursOld = overrideJobspyHoursOldRaw ? parseInt(overrideJobspyHoursOldRaw, 10) : null;
|
||||
const jobspyHoursOld = overrideJobspyHoursOld ?? defaultJobspyHoursOld;
|
||||
|
||||
const overrideJobspyCountryIndeed = await settingsRepo.getSetting('jobspyCountryIndeed');
|
||||
const defaultJobspyCountryIndeed = process.env.JOBSPY_COUNTRY_INDEED || 'UK';
|
||||
const jobspyCountryIndeed = overrideJobspyCountryIndeed || defaultJobspyCountryIndeed;
|
||||
|
||||
const overrideJobspySitesRaw = await settingsRepo.getSetting('jobspySites');
|
||||
const defaultJobspySites = (process.env.JOBSPY_SITES || 'indeed,linkedin').split(',').map(s => s.trim()).filter(Boolean);
|
||||
const overrideJobspySites = overrideJobspySitesRaw ? JSON.parse(overrideJobspySitesRaw) as string[] : null;
|
||||
const jobspySites = overrideJobspySites ?? defaultJobspySites;
|
||||
|
||||
const overrideJobspyLinkedinFetchDescriptionRaw = await settingsRepo.getSetting('jobspyLinkedinFetchDescription');
|
||||
const defaultJobspyLinkedinFetchDescription = (process.env.JOBSPY_LINKEDIN_FETCH_DESCRIPTION || '1') === '1';
|
||||
const overrideJobspyLinkedinFetchDescription = overrideJobspyLinkedinFetchDescriptionRaw
|
||||
? overrideJobspyLinkedinFetchDescriptionRaw === 'true' || overrideJobspyLinkedinFetchDescriptionRaw === '1'
|
||||
: null;
|
||||
const jobspyLinkedinFetchDescription = overrideJobspyLinkedinFetchDescription ?? defaultJobspyLinkedinFetchDescription;
|
||||
|
||||
// Show Sponsor Info setting (on by default)
|
||||
const overrideShowSponsorInfoRaw = await settingsRepo.getSetting('showSponsorInfo');
|
||||
const defaultShowSponsorInfo = true;
|
||||
const overrideShowSponsorInfo = overrideShowSponsorInfoRaw
|
||||
? overrideShowSponsorInfoRaw === 'true' || overrideShowSponsorInfoRaw === '1'
|
||||
: null;
|
||||
const showSponsorInfo = overrideShowSponsorInfo ?? defaultShowSponsorInfo;
|
||||
|
||||
const envSettings = await getEnvSettingsData();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
model,
|
||||
defaultModel,
|
||||
overrideModel,
|
||||
modelScorer,
|
||||
overrideModelScorer,
|
||||
modelTailoring,
|
||||
overrideModelTailoring,
|
||||
modelProjectSelection,
|
||||
overrideModelProjectSelection,
|
||||
pipelineWebhookUrl,
|
||||
defaultPipelineWebhookUrl,
|
||||
overridePipelineWebhookUrl,
|
||||
jobCompleteWebhookUrl,
|
||||
defaultJobCompleteWebhookUrl,
|
||||
overrideJobCompleteWebhookUrl,
|
||||
...resumeProjectsData,
|
||||
ukvisajobsMaxJobs,
|
||||
defaultUkvisajobsMaxJobs,
|
||||
overrideUkvisajobsMaxJobs,
|
||||
gradcrackerMaxJobsPerTerm,
|
||||
defaultGradcrackerMaxJobsPerTerm,
|
||||
overrideGradcrackerMaxJobsPerTerm,
|
||||
searchTerms,
|
||||
defaultSearchTerms,
|
||||
overrideSearchTerms,
|
||||
jobspyLocation,
|
||||
defaultJobspyLocation,
|
||||
overrideJobspyLocation,
|
||||
jobspyResultsWanted,
|
||||
defaultJobspyResultsWanted,
|
||||
overrideJobspyResultsWanted,
|
||||
jobspyHoursOld,
|
||||
defaultJobspyHoursOld,
|
||||
overrideJobspyHoursOld,
|
||||
jobspyCountryIndeed,
|
||||
defaultJobspyCountryIndeed,
|
||||
overrideJobspyCountryIndeed,
|
||||
jobspySites,
|
||||
defaultJobspySites,
|
||||
overrideJobspySites,
|
||||
jobspyLinkedinFetchDescription,
|
||||
defaultJobspyLinkedinFetchDescription,
|
||||
overrideJobspyLinkedinFetchDescription,
|
||||
showSponsorInfo,
|
||||
defaultShowSponsorInfo,
|
||||
overrideShowSponsorInfo,
|
||||
...envSettings,
|
||||
},
|
||||
});
|
||||
const data = await getEffectiveSettings();
|
||||
res.json({ success: true, data });
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error';
|
||||
res.status(500).json({ success: false, error: message });
|
||||
@ -312,146 +177,10 @@ settingsRouter.patch('/', async (req: Request, res: Response) => {
|
||||
applyEnvValue('WEBHOOK_SECRET', value);
|
||||
}
|
||||
|
||||
const overrideModel = await settingsRepo.getSetting('model');
|
||||
const defaultModel = process.env.MODEL || 'openai/gpt-4o-mini';
|
||||
const model = overrideModel || defaultModel;
|
||||
|
||||
const overrideModelScorer = await settingsRepo.getSetting('modelScorer');
|
||||
const modelScorer = overrideModelScorer || model;
|
||||
|
||||
const overrideModelTailoring = await settingsRepo.getSetting('modelTailoring');
|
||||
const modelTailoring = overrideModelTailoring || model;
|
||||
|
||||
const overrideModelProjectSelection = await settingsRepo.getSetting('modelProjectSelection');
|
||||
const modelProjectSelection = overrideModelProjectSelection || model;
|
||||
|
||||
const overridePipelineWebhookUrl = await settingsRepo.getSetting('pipelineWebhookUrl');
|
||||
const defaultPipelineWebhookUrl = process.env.PIPELINE_WEBHOOK_URL || process.env.WEBHOOK_URL || '';
|
||||
const pipelineWebhookUrl = overridePipelineWebhookUrl || defaultPipelineWebhookUrl;
|
||||
|
||||
const overrideJobCompleteWebhookUrl = await settingsRepo.getSetting('jobCompleteWebhookUrl');
|
||||
const defaultJobCompleteWebhookUrl = process.env.JOB_COMPLETE_WEBHOOK_URL || '';
|
||||
const jobCompleteWebhookUrl = overrideJobCompleteWebhookUrl || defaultJobCompleteWebhookUrl;
|
||||
|
||||
const profile = await getProfile();
|
||||
const { catalog } = extractProjectsFromProfile(profile);
|
||||
const overrideResumeProjectsRaw = await settingsRepo.getSetting('resumeProjects');
|
||||
const resumeProjectsData = resolveResumeProjectsSettings({ catalog, overrideRaw: overrideResumeProjectsRaw });
|
||||
|
||||
const overrideUkvisajobsMaxJobsRaw = await settingsRepo.getSetting('ukvisajobsMaxJobs');
|
||||
const defaultUkvisajobsMaxJobs = 50;
|
||||
const overrideUkvisajobsMaxJobs = overrideUkvisajobsMaxJobsRaw ? parseInt(overrideUkvisajobsMaxJobsRaw, 10) : null;
|
||||
const ukvisajobsMaxJobs = overrideUkvisajobsMaxJobs ?? defaultUkvisajobsMaxJobs;
|
||||
|
||||
const overrideGradcrackerMaxJobsPerTermRaw = await settingsRepo.getSetting('gradcrackerMaxJobsPerTerm');
|
||||
const defaultGradcrackerMaxJobsPerTerm = 50;
|
||||
const overrideGradcrackerMaxJobsPerTerm = overrideGradcrackerMaxJobsPerTermRaw ? parseInt(overrideGradcrackerMaxJobsPerTermRaw, 10) : null;
|
||||
const gradcrackerMaxJobsPerTerm = overrideGradcrackerMaxJobsPerTerm ?? defaultGradcrackerMaxJobsPerTerm;
|
||||
|
||||
// Search terms - stored as JSON array, default from env var (pipe-separated)
|
||||
const overrideSearchTermsRaw = await settingsRepo.getSetting('searchTerms');
|
||||
const defaultSearchTermsEnv = process.env.JOBSPY_SEARCH_TERMS || 'web developer';
|
||||
const defaultSearchTerms = defaultSearchTermsEnv.split('|').map(s => s.trim()).filter(Boolean);
|
||||
const overrideSearchTerms = overrideSearchTermsRaw ? JSON.parse(overrideSearchTermsRaw) as string[] : null;
|
||||
const searchTerms = overrideSearchTerms ?? defaultSearchTerms;
|
||||
|
||||
// JobSpy settings (re-fetch to update response)
|
||||
const overrideJobspyLocation = await settingsRepo.getSetting('jobspyLocation');
|
||||
const defaultJobspyLocation = process.env.JOBSPY_LOCATION || 'UK';
|
||||
const jobspyLocation = overrideJobspyLocation || defaultJobspyLocation;
|
||||
|
||||
const overrideJobspyResultsWantedRaw = await settingsRepo.getSetting('jobspyResultsWanted');
|
||||
const defaultJobspyResultsWanted = parseInt(process.env.JOBSPY_RESULTS_WANTED || '200', 10);
|
||||
const overrideJobspyResultsWanted = overrideJobspyResultsWantedRaw ? parseInt(overrideJobspyResultsWantedRaw, 10) : null;
|
||||
const jobspyResultsWanted = overrideJobspyResultsWanted ?? defaultJobspyResultsWanted;
|
||||
|
||||
const overrideJobspyHoursOldRaw = await settingsRepo.getSetting('jobspyHoursOld');
|
||||
const defaultJobspyHoursOld = parseInt(process.env.JOBSPY_HOURS_OLD || '72', 10);
|
||||
const overrideJobspyHoursOld = overrideJobspyHoursOldRaw ? parseInt(overrideJobspyHoursOldRaw, 10) : null;
|
||||
const jobspyHoursOld = overrideJobspyHoursOld ?? defaultJobspyHoursOld;
|
||||
|
||||
const overrideJobspyCountryIndeed = await settingsRepo.getSetting('jobspyCountryIndeed');
|
||||
const defaultJobspyCountryIndeed = process.env.JOBSPY_COUNTRY_INDEED || 'UK';
|
||||
const jobspyCountryIndeed = overrideJobspyCountryIndeed || defaultJobspyCountryIndeed;
|
||||
|
||||
const overrideJobspySitesRaw = await settingsRepo.getSetting('jobspySites');
|
||||
const defaultJobspySites = (process.env.JOBSPY_SITES || 'indeed,linkedin').split(',').map(s => s.trim()).filter(Boolean);
|
||||
const overrideJobspySites = overrideJobspySitesRaw ? JSON.parse(overrideJobspySitesRaw) as string[] : null;
|
||||
const jobspySites = overrideJobspySites ?? defaultJobspySites;
|
||||
|
||||
const overrideJobspyLinkedinFetchDescriptionRaw = await settingsRepo.getSetting('jobspyLinkedinFetchDescription');
|
||||
const defaultJobspyLinkedinFetchDescription = (process.env.JOBSPY_LINKEDIN_FETCH_DESCRIPTION || '1') === '1';
|
||||
const overrideJobspyLinkedinFetchDescription = overrideJobspyLinkedinFetchDescriptionRaw
|
||||
? overrideJobspyLinkedinFetchDescriptionRaw === 'true' || overrideJobspyLinkedinFetchDescriptionRaw === '1'
|
||||
: null;
|
||||
const jobspyLinkedinFetchDescription = overrideJobspyLinkedinFetchDescription ?? defaultJobspyLinkedinFetchDescription;
|
||||
|
||||
// Show Sponsor Info setting
|
||||
const overrideShowSponsorInfoRaw = await settingsRepo.getSetting('showSponsorInfo');
|
||||
const defaultShowSponsorInfo = true;
|
||||
const overrideShowSponsorInfo = overrideShowSponsorInfoRaw
|
||||
? overrideShowSponsorInfoRaw === 'true' || overrideShowSponsorInfoRaw === '1'
|
||||
: null;
|
||||
const showSponsorInfo = overrideShowSponsorInfo ?? defaultShowSponsorInfo;
|
||||
|
||||
const envSettings = await getEnvSettingsData();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
model,
|
||||
defaultModel,
|
||||
overrideModel,
|
||||
modelScorer,
|
||||
overrideModelScorer,
|
||||
modelTailoring,
|
||||
overrideModelTailoring,
|
||||
modelProjectSelection,
|
||||
overrideModelProjectSelection,
|
||||
pipelineWebhookUrl,
|
||||
defaultPipelineWebhookUrl,
|
||||
overridePipelineWebhookUrl,
|
||||
jobCompleteWebhookUrl,
|
||||
defaultJobCompleteWebhookUrl,
|
||||
overrideJobCompleteWebhookUrl,
|
||||
...resumeProjectsData,
|
||||
ukvisajobsMaxJobs,
|
||||
defaultUkvisajobsMaxJobs,
|
||||
overrideUkvisajobsMaxJobs,
|
||||
gradcrackerMaxJobsPerTerm,
|
||||
defaultGradcrackerMaxJobsPerTerm,
|
||||
overrideGradcrackerMaxJobsPerTerm,
|
||||
searchTerms,
|
||||
defaultSearchTerms,
|
||||
overrideSearchTerms,
|
||||
jobspyLocation,
|
||||
defaultJobspyLocation,
|
||||
overrideJobspyLocation,
|
||||
jobspyResultsWanted,
|
||||
defaultJobspyResultsWanted,
|
||||
overrideJobspyResultsWanted,
|
||||
jobspyHoursOld,
|
||||
defaultJobspyHoursOld,
|
||||
overrideJobspyHoursOld,
|
||||
jobspyCountryIndeed,
|
||||
defaultJobspyCountryIndeed,
|
||||
overrideJobspyCountryIndeed,
|
||||
jobspySites,
|
||||
defaultJobspySites,
|
||||
overrideJobspySites,
|
||||
jobspyLinkedinFetchDescription,
|
||||
defaultJobspyLinkedinFetchDescription,
|
||||
overrideJobspyLinkedinFetchDescription,
|
||||
showSponsorInfo,
|
||||
defaultShowSponsorInfo,
|
||||
overrideShowSponsorInfo,
|
||||
...envSettings,
|
||||
},
|
||||
});
|
||||
const data = await getEffectiveSettings();
|
||||
res.json({ success: true, data });
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error';
|
||||
// PATCH usually returns 500 for unknown, but let's stick to what was there (400?)
|
||||
// Wait, the file said 400? Let's verify line 608.
|
||||
res.status(400).json({ success: false, error: message });
|
||||
}
|
||||
});
|
||||
|
||||
@ -38,6 +38,14 @@ export async function getSetting(key: SettingKey): Promise<string | null> {
|
||||
return row?.value ?? null
|
||||
}
|
||||
|
||||
export async function getAllSettings(): Promise<Partial<Record<SettingKey, string>>> {
|
||||
const rows = await db.select().from(settings)
|
||||
return rows.reduce((acc, row) => {
|
||||
acc[row.key as SettingKey] = row.value
|
||||
return acc
|
||||
}, {} as Partial<Record<SettingKey, string>>)
|
||||
}
|
||||
|
||||
export async function setSetting(key: SettingKey, value: string | null): Promise<void> {
|
||||
const now = new Date().toISOString()
|
||||
|
||||
|
||||
@ -70,30 +70,37 @@ export async function applyStoredEnvOverrides(): Promise<void> {
|
||||
]);
|
||||
}
|
||||
|
||||
export async function getEnvSettingsData(): Promise<Record<string, string | boolean | number | null>> {
|
||||
export async function getEnvSettingsData(
|
||||
overrides?: Partial<Record<SettingKey, string>>
|
||||
): Promise<Record<string, string | boolean | number | null>> {
|
||||
const readableValues: Record<string, string | boolean | null> = {};
|
||||
const privateValues: Record<string, string | null> = {};
|
||||
|
||||
for (const { settingKey, envKey } of readableStringConfig) {
|
||||
const override = await settingsRepo.getSetting(settingKey);
|
||||
const override = overrides ? (overrides[settingKey] ?? null) : await settingsRepo.getSetting(settingKey);
|
||||
const rawValue = override ?? process.env[envKey];
|
||||
readableValues[settingKey] = normalizeEnvInput(rawValue);
|
||||
}
|
||||
|
||||
for (const { settingKey, envKey, defaultValue } of readableBooleanConfig) {
|
||||
const override = await settingsRepo.getSetting(settingKey);
|
||||
const override = overrides ? (overrides[settingKey] ?? null) : await settingsRepo.getSetting(settingKey);
|
||||
const rawValue = override ?? process.env[envKey];
|
||||
readableValues[settingKey] = parseEnvBoolean(rawValue, defaultValue);
|
||||
}
|
||||
|
||||
for (const { settingKey, envKey, hintKey } of privateStringConfig) {
|
||||
const override = await settingsRepo.getSetting(settingKey);
|
||||
const override = overrides ? (overrides[settingKey] ?? null) : await settingsRepo.getSetting(settingKey);
|
||||
const rawValue = override ?? process.env[envKey];
|
||||
privateValues[hintKey] = rawValue ? rawValue.slice(0, 4) : null;
|
||||
}
|
||||
|
||||
const basicAuthUser = (await settingsRepo.getSetting('basicAuthUser')) ?? process.env.BASIC_AUTH_USER;
|
||||
const basicAuthPassword = (await settingsRepo.getSetting('basicAuthPassword')) ?? process.env.BASIC_AUTH_PASSWORD;
|
||||
const basicAuthUser = overrides
|
||||
? (overrides['basicAuthUser'] ?? process.env.BASIC_AUTH_USER)
|
||||
: (await settingsRepo.getSetting('basicAuthUser')) ?? process.env.BASIC_AUTH_USER;
|
||||
|
||||
const basicAuthPassword = overrides
|
||||
? (overrides['basicAuthPassword'] ?? process.env.BASIC_AUTH_PASSWORD)
|
||||
: (await settingsRepo.getSetting('basicAuthPassword')) ?? process.env.BASIC_AUTH_PASSWORD;
|
||||
|
||||
return {
|
||||
...readableValues,
|
||||
|
||||
146
orchestrator/src/server/services/settings.ts
Normal file
146
orchestrator/src/server/services/settings.ts
Normal file
@ -0,0 +1,146 @@
|
||||
import { AppSettings } from '@shared/types.js';
|
||||
import * as settingsRepo from '@server/repositories/settings.js';
|
||||
import { getEnvSettingsData } from './envSettings.js';
|
||||
import { extractProjectsFromProfile, resolveResumeProjectsSettings } from './resumeProjects.js';
|
||||
import { getProfile } from './profile.js';
|
||||
|
||||
/**
|
||||
* Get the effective app settings, combining environment variables and database overrides.
|
||||
*/
|
||||
export async function getEffectiveSettings(): Promise<AppSettings> {
|
||||
// Parallelize slow operations
|
||||
const [overrides, profile] = await Promise.all([
|
||||
settingsRepo.getAllSettings(),
|
||||
getProfile(),
|
||||
]);
|
||||
|
||||
const envSettings = await getEnvSettingsData(overrides);
|
||||
|
||||
const defaultModel = process.env.MODEL || 'openai/gpt-4o-mini';
|
||||
const overrideModel = overrides.model ?? null;
|
||||
const model = overrideModel || defaultModel;
|
||||
|
||||
const overrideModelScorer = overrides.modelScorer ?? null;
|
||||
const modelScorer = overrideModelScorer || model;
|
||||
|
||||
const overrideModelTailoring = overrides.modelTailoring ?? null;
|
||||
const modelTailoring = overrideModelTailoring || model;
|
||||
|
||||
const overrideModelProjectSelection = overrides.modelProjectSelection ?? null;
|
||||
const modelProjectSelection = overrideModelProjectSelection || model;
|
||||
|
||||
const defaultPipelineWebhookUrl = process.env.PIPELINE_WEBHOOK_URL || process.env.WEBHOOK_URL || '';
|
||||
const overridePipelineWebhookUrl = overrides.pipelineWebhookUrl ?? null;
|
||||
const pipelineWebhookUrl = overridePipelineWebhookUrl || defaultPipelineWebhookUrl;
|
||||
|
||||
const defaultJobCompleteWebhookUrl = process.env.JOB_COMPLETE_WEBHOOK_URL || '';
|
||||
const overrideJobCompleteWebhookUrl = overrides.jobCompleteWebhookUrl ?? null;
|
||||
const jobCompleteWebhookUrl = overrideJobCompleteWebhookUrl || defaultJobCompleteWebhookUrl;
|
||||
|
||||
const { catalog } = extractProjectsFromProfile(profile);
|
||||
const overrideResumeProjectsRaw = overrides.resumeProjects ?? null;
|
||||
const resumeProjectsData = resolveResumeProjectsSettings({ catalog, overrideRaw: overrideResumeProjectsRaw });
|
||||
|
||||
const defaultUkvisajobsMaxJobs = 50;
|
||||
const overrideUkvisajobsMaxJobsRaw = overrides.ukvisajobsMaxJobs;
|
||||
const overrideUkvisajobsMaxJobs = overrideUkvisajobsMaxJobsRaw ? parseInt(overrideUkvisajobsMaxJobsRaw, 10) : null;
|
||||
const ukvisajobsMaxJobs = overrideUkvisajobsMaxJobs ?? defaultUkvisajobsMaxJobs;
|
||||
|
||||
const defaultGradcrackerMaxJobsPerTerm = 50;
|
||||
const overrideGradcrackerMaxJobsPerTermRaw = overrides.gradcrackerMaxJobsPerTerm;
|
||||
const overrideGradcrackerMaxJobsPerTerm = overrideGradcrackerMaxJobsPerTermRaw ? parseInt(overrideGradcrackerMaxJobsPerTermRaw, 10) : null;
|
||||
const gradcrackerMaxJobsPerTerm = overrideGradcrackerMaxJobsPerTerm ?? defaultGradcrackerMaxJobsPerTerm;
|
||||
|
||||
const defaultSearchTermsEnv = process.env.JOBSPY_SEARCH_TERMS || 'web developer';
|
||||
const defaultSearchTerms = defaultSearchTermsEnv.split('|').map(s => s.trim()).filter(Boolean);
|
||||
const overrideSearchTermsRaw = overrides.searchTerms;
|
||||
const overrideSearchTerms = overrideSearchTermsRaw ? JSON.parse(overrideSearchTermsRaw) as string[] : null;
|
||||
const searchTerms = overrideSearchTerms ?? defaultSearchTerms;
|
||||
|
||||
const defaultJobspyLocation = process.env.JOBSPY_LOCATION || 'UK';
|
||||
const overrideJobspyLocation = overrides.jobspyLocation ?? null;
|
||||
const jobspyLocation = overrideJobspyLocation || defaultJobspyLocation;
|
||||
|
||||
const defaultJobspyResultsWanted = parseInt(process.env.JOBSPY_RESULTS_WANTED || '200', 10);
|
||||
const overrideJobspyResultsWantedRaw = overrides.jobspyResultsWanted;
|
||||
const overrideJobspyResultsWanted = overrideJobspyResultsWantedRaw ? parseInt(overrideJobspyResultsWantedRaw, 10) : null;
|
||||
const jobspyResultsWanted = overrideJobspyResultsWanted ?? defaultJobspyResultsWanted;
|
||||
|
||||
const defaultJobspyHoursOld = parseInt(process.env.JOBSPY_HOURS_OLD || '72', 10);
|
||||
const overrideJobspyHoursOldRaw = overrides.jobspyHoursOld;
|
||||
const overrideJobspyHoursOld = overrideJobspyHoursOldRaw ? parseInt(overrideJobspyHoursOldRaw, 10) : null;
|
||||
const jobspyHoursOld = overrideJobspyHoursOld ?? defaultJobspyHoursOld;
|
||||
|
||||
const defaultJobspyCountryIndeed = process.env.JOBSPY_COUNTRY_INDEED || 'UK';
|
||||
const overrideJobspyCountryIndeed = overrides.jobspyCountryIndeed ?? null;
|
||||
const jobspyCountryIndeed = overrideJobspyCountryIndeed || defaultJobspyCountryIndeed;
|
||||
|
||||
const defaultJobspySites = (process.env.JOBSPY_SITES || 'indeed,linkedin').split(',').map(s => s.trim()).filter(Boolean);
|
||||
const overrideJobspySitesRaw = overrides.jobspySites;
|
||||
const overrideJobspySites = overrideJobspySitesRaw ? JSON.parse(overrideJobspySitesRaw) as string[] : null;
|
||||
const jobspySites = overrideJobspySites ?? defaultJobspySites;
|
||||
|
||||
const defaultJobspyLinkedinFetchDescription = (process.env.JOBSPY_LINKEDIN_FETCH_DESCRIPTION || '1') === '1';
|
||||
const overrideJobspyLinkedinFetchDescriptionRaw = overrides.jobspyLinkedinFetchDescription;
|
||||
const overrideJobspyLinkedinFetchDescription = overrideJobspyLinkedinFetchDescriptionRaw
|
||||
? overrideJobspyLinkedinFetchDescriptionRaw === 'true' || overrideJobspyLinkedinFetchDescriptionRaw === '1'
|
||||
: null;
|
||||
const jobspyLinkedinFetchDescription = overrideJobspyLinkedinFetchDescription ?? defaultJobspyLinkedinFetchDescription;
|
||||
|
||||
const defaultShowSponsorInfo = true;
|
||||
const overrideShowSponsorInfoRaw = overrides.showSponsorInfo;
|
||||
const overrideShowSponsorInfo = overrideShowSponsorInfoRaw
|
||||
? overrideShowSponsorInfoRaw === 'true' || overrideShowSponsorInfoRaw === '1'
|
||||
: null;
|
||||
const showSponsorInfo = overrideShowSponsorInfo ?? defaultShowSponsorInfo;
|
||||
|
||||
return {
|
||||
model,
|
||||
defaultModel,
|
||||
overrideModel,
|
||||
modelScorer,
|
||||
overrideModelScorer,
|
||||
modelTailoring,
|
||||
overrideModelTailoring,
|
||||
modelProjectSelection,
|
||||
overrideModelProjectSelection,
|
||||
pipelineWebhookUrl,
|
||||
defaultPipelineWebhookUrl,
|
||||
overridePipelineWebhookUrl,
|
||||
jobCompleteWebhookUrl,
|
||||
defaultJobCompleteWebhookUrl,
|
||||
overrideJobCompleteWebhookUrl,
|
||||
...resumeProjectsData,
|
||||
ukvisajobsMaxJobs,
|
||||
defaultUkvisajobsMaxJobs,
|
||||
overrideUkvisajobsMaxJobs,
|
||||
gradcrackerMaxJobsPerTerm,
|
||||
defaultGradcrackerMaxJobsPerTerm,
|
||||
overrideGradcrackerMaxJobsPerTerm,
|
||||
searchTerms,
|
||||
defaultSearchTerms,
|
||||
overrideSearchTerms,
|
||||
jobspyLocation,
|
||||
defaultJobspyLocation,
|
||||
overrideJobspyLocation,
|
||||
jobspyResultsWanted,
|
||||
defaultJobspyResultsWanted,
|
||||
overrideJobspyResultsWanted,
|
||||
jobspyHoursOld,
|
||||
defaultJobspyHoursOld,
|
||||
overrideJobspyHoursOld,
|
||||
jobspyCountryIndeed,
|
||||
defaultJobspyCountryIndeed,
|
||||
overrideJobspyCountryIndeed,
|
||||
jobspySites,
|
||||
defaultJobspySites,
|
||||
overrideJobspySites,
|
||||
jobspyLinkedinFetchDescription,
|
||||
defaultJobspyLinkedinFetchDescription,
|
||||
overrideJobspyLinkedinFetchDescription,
|
||||
showSponsorInfo,
|
||||
defaultShowSponsorInfo,
|
||||
overrideShowSponsorInfo,
|
||||
...envSettings,
|
||||
} as AppSettings;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user