mirror of
https://github.com/orangecoding/fredy.git
synced 2026-06-16 12:31:07 +00:00
storing settings in db
This commit is contained in:
@@ -1 +1 @@
|
||||
{"interval":"60","port":9998,"workingHours":{"from":"","to":""},"demoMode":false,"analyticsEnabled":true,"sqlitepath":"/db"}
|
||||
{"sqlitepath":"/db"}
|
||||
32
index.js
32
index.js
@@ -1,6 +1,6 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { checkIfConfigIsAccessible, config, getProviders, refreshConfig } from './lib/utils.js';
|
||||
import { checkIfConfigIsAccessible, getProviders, refreshConfig } from './lib/utils.js';
|
||||
import * as similarityCache from './lib/services/similarity-check/similarityCache.js';
|
||||
import * as jobStorage from './lib/services/storage/jobStorage.js';
|
||||
import FredyPipeline from './lib/FredyPipeline.js';
|
||||
@@ -12,28 +12,34 @@ import { initTrackerCron } from './lib/services/crons/tracker-cron.js';
|
||||
import logger from './lib/services/logger.js';
|
||||
import { bus } from './lib/services/events/event-bus.js';
|
||||
import { initActiveCheckerCron } from './lib/services/crons/listing-alive-cron.js';
|
||||
import { getSettings } from './lib/services/storage/settingsStorage.js';
|
||||
import SqliteConnection from './lib/services/storage/SqliteConnection.js';
|
||||
|
||||
//in the config, we store the path of the sqlite file, thus we must check if it is available
|
||||
const isConfigAccessible = await checkIfConfigIsAccessible();
|
||||
await SqliteConnection.init();
|
||||
|
||||
// Load configuration before any other startup steps
|
||||
await refreshConfig();
|
||||
|
||||
const isConfigAccessible = await checkIfConfigIsAccessible();
|
||||
|
||||
if (!isConfigAccessible) {
|
||||
logger.error('Configuration exists, but is not accessible. Please check the file permission');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Run DB migrations once at startup and block until finished
|
||||
await runMigrations();
|
||||
|
||||
const settings = await getSettings();
|
||||
|
||||
// Ensure sqlite directory exists before loading anything else (based on config.sqlitepath)
|
||||
const rawDir = config.sqlitepath || '/db';
|
||||
const rawDir = settings.sqlitepath || '/db';
|
||||
const relDir = rawDir.startsWith('/') ? rawDir.slice(1) : rawDir;
|
||||
const absDir = path.isAbsolute(relDir) ? relDir : path.join(process.cwd(), relDir);
|
||||
if (!fs.existsSync(absDir)) {
|
||||
fs.mkdirSync(absDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Run DB migrations once at startup and block until finished
|
||||
await runMigrations();
|
||||
|
||||
// Load provider modules once at startup
|
||||
const providers = await getProviders();
|
||||
|
||||
@@ -41,17 +47,17 @@ similarityCache.initSimilarityCache();
|
||||
similarityCache.startSimilarityCacheReloader();
|
||||
|
||||
//assuming interval is always in minutes
|
||||
const INTERVAL = config.interval * 60 * 1000;
|
||||
const INTERVAL = settings.interval * 60 * 1000;
|
||||
|
||||
// Initialize API only after migrations completed
|
||||
await import('./lib/api/api.js');
|
||||
|
||||
if (config.demoMode) {
|
||||
if (settings.demoMode) {
|
||||
logger.info('Running in demo mode');
|
||||
cleanupDemoAtMidnight();
|
||||
}
|
||||
|
||||
logger.info(`Started Fredy successfully. Ui can be accessed via http://localhost:${config.port}`);
|
||||
logger.info(`Started Fredy successfully. Ui can be accessed via http://localhost:${settings.port}`);
|
||||
|
||||
ensureAdminUserExists();
|
||||
ensureDemoUserExists();
|
||||
@@ -65,10 +71,10 @@ bus.on('jobs:runAll', () => {
|
||||
});
|
||||
|
||||
const execute = () => {
|
||||
const isDuringWorkingHoursOrNotSet = duringWorkingHoursOrNotSet(config, Date.now());
|
||||
if (!config.demoMode) {
|
||||
const isDuringWorkingHoursOrNotSet = duringWorkingHoursOrNotSet(settings, Date.now());
|
||||
if (!settings.demoMode) {
|
||||
if (isDuringWorkingHoursOrNotSet) {
|
||||
config.lastRun = Date.now();
|
||||
settings.lastRun = Date.now();
|
||||
jobStorage
|
||||
.getJobs()
|
||||
.filter((job) => job.enabled)
|
||||
|
||||
@@ -7,7 +7,6 @@ import { versionRouter } from './routes/versionRouter.js';
|
||||
import { loginRouter } from './routes/loginRoute.js';
|
||||
import { userRouter } from './routes/userRoute.js';
|
||||
import { jobRouter } from './routes/jobRouter.js';
|
||||
import { config } from '../utils.js';
|
||||
import bodyParser from 'body-parser';
|
||||
import restana from 'restana';
|
||||
import files from 'serve-static';
|
||||
@@ -16,9 +15,10 @@ import { getDirName } from '../utils.js';
|
||||
import { demoRouter } from './routes/demoRouter.js';
|
||||
import logger from '../services/logger.js';
|
||||
import { listingsRouter } from './routes/listingsRouter.js';
|
||||
import { getSettings } from '../services/storage/settingsStorage.js';
|
||||
const service = restana();
|
||||
const staticService = files(path.join(getDirName(), '../ui/public'));
|
||||
const PORT = config.port || 9998;
|
||||
const PORT = (await getSettings()).port || 9998;
|
||||
|
||||
service.use(bodyParser.json());
|
||||
service.use(cookieSession());
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import restana from 'restana';
|
||||
import { config } from '../../utils.js';
|
||||
import { getSettings } from '../../services/storage/settingsStorage.js';
|
||||
const service = restana();
|
||||
const demoRouter = service.newRouter();
|
||||
|
||||
demoRouter.get('/', async (req, res) => {
|
||||
res.body = Object.assign({}, { demoMode: config.demoMode });
|
||||
const settings = await getSettings();
|
||||
res.body = Object.assign({}, { demoMode: settings.demoMode });
|
||||
res.send();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
import restana from 'restana';
|
||||
import { config, getDirName, readConfigFromStorage, refreshConfig } from '../../utils.js';
|
||||
import { getDirName } from '../../utils.js';
|
||||
import fs from 'fs';
|
||||
import { ensureDemoUserExists } from '../../services/storage/userStorage.js';
|
||||
import logger from '../../services/logger.js';
|
||||
import { getSettings, upsertSettings } from '../../services/storage/settingsStorage.js';
|
||||
const service = restana();
|
||||
const generalSettingsRouter = service.newRouter();
|
||||
|
||||
generalSettingsRouter.get('/', async (req, res) => {
|
||||
res.body = Object.assign({}, config);
|
||||
res.body = Object.assign({}, await getSettings());
|
||||
res.send();
|
||||
});
|
||||
generalSettingsRouter.post('/', async (req, res) => {
|
||||
const settings = req.body;
|
||||
const { sqlitepath, ...appSettings } = req.body || {};
|
||||
const localSettings = await getSettings();
|
||||
|
||||
if (localSettings.demoMode) {
|
||||
res.send(new Error('In demo mode, it is not allowed to change these settings.'));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (config.demoMode) {
|
||||
res.send(new Error('In demo mode, it is not allowed to change these settings.'));
|
||||
return;
|
||||
if (typeof sqlitepath !== 'undefined') {
|
||||
fs.writeFileSync(`${getDirName()}/../conf/config.json`, JSON.stringify({ sqlitepath }));
|
||||
}
|
||||
const currentConfig = await readConfigFromStorage();
|
||||
fs.writeFileSync(`${getDirName()}/../conf/config.json`, JSON.stringify({ ...currentConfig, ...settings }));
|
||||
await refreshConfig();
|
||||
upsertSettings(appSettings);
|
||||
ensureDemoUserExists();
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import restana from 'restana';
|
||||
import * as jobStorage from '../../services/storage/jobStorage.js';
|
||||
import * as userStorage from '../../services/storage/userStorage.js';
|
||||
import { config } from '../../utils.js';
|
||||
import { isAdmin } from '../security.js';
|
||||
import logger from '../../services/logger.js';
|
||||
import { bus } from '../../services/events/event-bus.js';
|
||||
import { getSettings } from '../../services/storage/settingsStorage.js';
|
||||
|
||||
const service = restana();
|
||||
const jobRouter = service.newRouter();
|
||||
@@ -44,9 +44,10 @@ jobRouter.get('/', async (req, res) => {
|
||||
});
|
||||
|
||||
jobRouter.get('/processingTimes', async (req, res) => {
|
||||
const settings = await getSettings();
|
||||
res.body = {
|
||||
interval: config.interval,
|
||||
lastRun: config.lastRun || null,
|
||||
interval: settings.interval,
|
||||
lastRun: settings.lastRun || null,
|
||||
};
|
||||
res.send();
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import restana from 'restana';
|
||||
import * as userStorage from '../../services/storage/userStorage.js';
|
||||
import * as hasher from '../../services/security/hash.js';
|
||||
import { config } from '../../utils.js';
|
||||
import { trackDemoAccessed } from '../../services/tracking/Tracker.js';
|
||||
import logger from '../../services/logger.js';
|
||||
import { getSettings } from '../../services/storage/settingsStorage.js';
|
||||
const service = restana();
|
||||
const loginRouter = service.newRouter();
|
||||
loginRouter.get('/user', async (req, res) => {
|
||||
@@ -20,6 +20,7 @@ loginRouter.get('/user', async (req, res) => {
|
||||
res.send();
|
||||
});
|
||||
loginRouter.post('/', async (req, res) => {
|
||||
const settings = await getSettings();
|
||||
const { username, password } = req.body;
|
||||
const user = userStorage.getUsers(true).find((user) => user.username === username);
|
||||
if (user == null) {
|
||||
@@ -27,7 +28,7 @@ loginRouter.post('/', async (req, res) => {
|
||||
return;
|
||||
}
|
||||
if (user.password === hasher.hash(password)) {
|
||||
if (config.demoMode) {
|
||||
if (settings.demoMode) {
|
||||
await trackDemoAccessed();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import restana from 'restana';
|
||||
import * as userStorage from '../../services/storage/userStorage.js';
|
||||
import * as jobStorage from '../../services/storage/jobStorage.js';
|
||||
import { config } from '../../utils.js';
|
||||
import { getSettings } from '../../services/storage/settingsStorage.js';
|
||||
const service = restana();
|
||||
const userRouter = service.newRouter();
|
||||
function checkIfAnyAdminAfterRemovingUser(userIdToBeRemoved, allUser) {
|
||||
@@ -23,7 +23,8 @@ userRouter.get('/:userId', async (req, res) => {
|
||||
res.send();
|
||||
});
|
||||
userRouter.delete('/', async (req, res) => {
|
||||
if (config.demoMode) {
|
||||
const settings = await getSettings();
|
||||
if (settings.demoMode) {
|
||||
res.send(new Error('In demo mode, it is not allowed to remove user.'));
|
||||
return;
|
||||
}
|
||||
@@ -44,7 +45,8 @@ userRouter.delete('/', async (req, res) => {
|
||||
res.send();
|
||||
});
|
||||
userRouter.post('/', async (req, res) => {
|
||||
if (config.demoMode) {
|
||||
const settings = await getSettings();
|
||||
if (settings.demoMode) {
|
||||
res.send(new Error('In demo mode, it is not allowed to change or add user.'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { removeJobsByUserId } from '../storage/jobStorage.js';
|
||||
import { config } from '../../utils.js';
|
||||
import { getUsers } from '../storage/userStorage.js';
|
||||
import logger from '../logger.js';
|
||||
import cron from 'node-cron';
|
||||
import { getSettings } from '../storage/settingsStorage.js';
|
||||
|
||||
/**
|
||||
* if we are running in demo environment, we have to cleanup the db files (specifically the jobs table)
|
||||
@@ -11,12 +11,13 @@ export function cleanupDemoAtMidnight() {
|
||||
cron.schedule('0 0 * * *', cleanup);
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
if (config.demoMode) {
|
||||
async function cleanup() {
|
||||
const settings = await getSettings();
|
||||
if (settings.demoMode) {
|
||||
const demoUser = getUsers(false).find((user) => user.username === 'demo');
|
||||
if (demoUser == null) {
|
||||
logger.error('Demo user not found, cannot remove Jobs');
|
||||
return;
|
||||
return Promise.resolve();
|
||||
}
|
||||
removeJobsByUserId(demoUser.id);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import cron from 'node-cron';
|
||||
import { config, inDevMode } from '../../utils.js';
|
||||
import { inDevMode } from '../../utils.js';
|
||||
import { trackMainEvent } from '../tracking/Tracker.js';
|
||||
import { getSettings } from '../storage/settingsStorage.js';
|
||||
|
||||
async function runTask() {
|
||||
const settings = await getSettings();
|
||||
//make sure to only send tracking events if the user gave us the green light and we are not in dev mode
|
||||
if (config.analyticsEnabled && !inDevMode()) {
|
||||
if (settings.analyticsEnabled && !inDevMode()) {
|
||||
await trackMainEvent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
import Database from 'better-sqlite3';
|
||||
import logger from '../../services/logger.js';
|
||||
import { config } from '../../utils.js';
|
||||
import { readConfigFromStorage } from '../../utils.js';
|
||||
|
||||
/**
|
||||
* SqliteConnection
|
||||
@@ -25,6 +25,15 @@ import { config } from '../../utils.js';
|
||||
class SqliteConnection {
|
||||
static #db = null;
|
||||
|
||||
static #sqlLiteCfg = null;
|
||||
|
||||
static async init() {
|
||||
if (this.#sqlLiteCfg == null) {
|
||||
readConfigFromStorage().then((c) => {
|
||||
this.#sqlLiteCfg = c.sqlitepath;
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns a singleton instance of better-sqlite3 Database.
|
||||
* Respects env var SQLITE_DB_PATH and defaults to db/listings.db.
|
||||
@@ -32,9 +41,12 @@ class SqliteConnection {
|
||||
static getConnection() {
|
||||
if (this.#db) return this.#db;
|
||||
|
||||
if (this.#sqlLiteCfg == null) {
|
||||
logger.warn('No sqlitepath configured. Using default db/listings.db');
|
||||
}
|
||||
|
||||
// Interpret config.sqlitepath as a directory relative to project root when it starts with '/'
|
||||
const cfg = typeof config === 'object' && config ? config.sqlitepath : undefined;
|
||||
const rawDir = cfg && cfg.length > 0 ? cfg : '/db';
|
||||
const rawDir = this.#sqlLiteCfg && this.#sqlLiteCfg.length > 0 ? this.#sqlLiteCfg : '/db';
|
||||
const relDir = rawDir.startsWith('/') ? rawDir.slice(1) : rawDir;
|
||||
const absDir = path.isAbsolute(relDir) ? relDir : path.join(process.cwd(), relDir);
|
||||
const dbPath = path.join(absDir, 'listings.db');
|
||||
|
||||
73
lib/services/storage/migrations/sql/6.settings.js
Normal file
73
lib/services/storage/migrations/sql/6.settings.js
Normal file
@@ -0,0 +1,73 @@
|
||||
// Migration: Adding a settings table to store important (config) settings instead of using config file
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { nanoid } from 'nanoid';
|
||||
import logger from '../../../logger.js';
|
||||
|
||||
export function up(db) {
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS settings
|
||||
(
|
||||
id TEXT PRIMARY KEY,
|
||||
create_date INTEGER NOT NULL,
|
||||
user_id TEXT,
|
||||
name TEXT NOT NULL,
|
||||
value jsonb NOT NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_settings_name ON settings (name);
|
||||
`);
|
||||
|
||||
// Helper to insert one setting row
|
||||
const insertSetting = (name, rawValue) => {
|
||||
try {
|
||||
const id = nanoid();
|
||||
const createDate = Date.now();
|
||||
const value = JSON.stringify(rawValue);
|
||||
db.prepare(
|
||||
`INSERT INTO settings (id, create_date, name, value)
|
||||
VALUES (@id, @create_date, @name, @value)`,
|
||||
).run({ id, create_date: createDate, name, value });
|
||||
} catch {
|
||||
// Ignore duplicate inserts if any (unique by name)
|
||||
}
|
||||
};
|
||||
|
||||
// Migrate currently existing config.json into settings
|
||||
try {
|
||||
const configPath = path.resolve(process.cwd(), 'conf', 'config.json');
|
||||
|
||||
// Defaults
|
||||
const defaults = {
|
||||
interval: '60',
|
||||
port: 9998,
|
||||
workingHours: { from: '', to: '' },
|
||||
demoMode: false,
|
||||
analyticsEnabled: true,
|
||||
};
|
||||
|
||||
let config = {};
|
||||
if (fs.existsSync(configPath)) {
|
||||
const file = fs.readFileSync(configPath, 'utf8');
|
||||
try {
|
||||
config = JSON.parse(file) || {};
|
||||
} catch (parseErr) {
|
||||
// If parsing fails, still proceed with defaults
|
||||
logger.error(parseErr);
|
||||
config = {};
|
||||
}
|
||||
}
|
||||
|
||||
// Insert each known setting, using the value from config when present, otherwise default
|
||||
insertSetting('interval', config.interval != null ? config.interval : defaults.interval);
|
||||
insertSetting('port', config.port != null ? config.port : defaults.port);
|
||||
insertSetting('workingHours', config.workingHours != null ? config.workingHours : defaults.workingHours);
|
||||
insertSetting('demoMode', config.demoMode != null ? config.demoMode : defaults.demoMode);
|
||||
insertSetting(
|
||||
'analyticsEnabled',
|
||||
config.analyticsEnabled != null ? config.analyticsEnabled : defaults.analyticsEnabled,
|
||||
);
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
}
|
||||
}
|
||||
87
lib/services/storage/settingsStorage.js
Normal file
87
lib/services/storage/settingsStorage.js
Normal file
@@ -0,0 +1,87 @@
|
||||
import { nanoid } from 'nanoid';
|
||||
import SqliteConnection from './SqliteConnection.js';
|
||||
import { fromJson, readConfigFromStorage, toJson } from '../../utils.js';
|
||||
|
||||
// In-memory cache for compiled settings config
|
||||
/** @type {Record<string, any>|null} */
|
||||
let cachedSettingsConfig = null;
|
||||
|
||||
/**
|
||||
* Build a config object from DB rows of settings.
|
||||
* - Unwraps stored shape { value: any } into raw values.
|
||||
* - Add additional config values from file config. E.g. sqlite part cannot be stored in db for obvious reasons ;)
|
||||
* @param {{name:string, value:string|null}[]} rows
|
||||
* @param {{name:value}} configValues
|
||||
* @returns {Record<string, any>}
|
||||
*/
|
||||
function compileSettings(rows, configValues) {
|
||||
const config = {};
|
||||
for (const r of rows) {
|
||||
const parsed = fromJson(r.value, null);
|
||||
// unwrap { value: any } if present
|
||||
config[r.name] = parsed && typeof parsed === 'object' && 'value' in parsed ? parsed.value : parsed;
|
||||
}
|
||||
return {
|
||||
...config,
|
||||
...configValues,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Force reload the settings config cache from DB and return it.
|
||||
* @returns {Record<string, any>}
|
||||
*/
|
||||
export async function refreshSettingsCache() {
|
||||
const rows = SqliteConnection.query(`SELECT name, value FROM settings`);
|
||||
const configValues = await readConfigFromStorage();
|
||||
cachedSettingsConfig = compileSettings(rows, configValues);
|
||||
return cachedSettingsConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the compiled settings config. Loads it once and caches the result.
|
||||
* @returns {Record<string, any>}
|
||||
*/
|
||||
export async function getSettings() {
|
||||
if (cachedSettingsConfig == null) {
|
||||
return refreshSettingsCache();
|
||||
}
|
||||
return cachedSettingsConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upsert settings rows.
|
||||
* - Accepts an object map of name -> value, or an entry {name, value}.
|
||||
* - id: random string (nanoid) when inserting
|
||||
* - create_date: epoch ms when inserting
|
||||
* - name: unique key
|
||||
* - value: JSON string of the raw value (no wrapper)
|
||||
* @param {Record<string, any>|{name:string, value:any}|[string, any][]} settingsMapOrEntry
|
||||
* @returns {void}
|
||||
*/
|
||||
// Upsert one or more settings by name. Accepts either a single pair or an object map.
|
||||
// Preferred usage: upsertSettings({ settingName: any, another: any })
|
||||
export function upsertSettings(settingsMapOrEntry, userId = null) {
|
||||
const entries = Array.isArray(settingsMapOrEntry)
|
||||
? settingsMapOrEntry
|
||||
: typeof settingsMapOrEntry === 'object' &&
|
||||
settingsMapOrEntry != null &&
|
||||
'name' in settingsMapOrEntry &&
|
||||
'value' in settingsMapOrEntry
|
||||
? [[settingsMapOrEntry.name, settingsMapOrEntry.value]]
|
||||
: Object.entries(settingsMapOrEntry || {});
|
||||
|
||||
for (const [name, rawValue] of entries) {
|
||||
const id = nanoid();
|
||||
const create_date = Date.now();
|
||||
const json = toJson(rawValue);
|
||||
SqliteConnection.execute(
|
||||
`INSERT INTO settings (id, create_date, name, value, user_id)
|
||||
VALUES (@id, @create_date, @name, @value, @userId)
|
||||
ON CONFLICT(name) DO UPDATE SET value = excluded.value`,
|
||||
{ id, create_date, name, value: json, userId },
|
||||
);
|
||||
}
|
||||
// keep cache in sync
|
||||
refreshSettingsCache();
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { config } from '../../utils.js';
|
||||
import * as hasher from '../security/hash.js';
|
||||
import { nanoid } from 'nanoid';
|
||||
import SqliteConnection from './SqliteConnection.js';
|
||||
import { getSettings } from './settingsStorage.js';
|
||||
|
||||
/**
|
||||
* Get all users.
|
||||
@@ -129,8 +129,9 @@ export const removeUser = (userId) => {
|
||||
* Security: The demo user's password is set to a known value ('demo') and should only be enabled in demoMode.
|
||||
* @returns {void}
|
||||
*/
|
||||
export const ensureDemoUserExists = () => {
|
||||
if (!config.demoMode) {
|
||||
export const ensureDemoUserExists = async () => {
|
||||
const settings = await getSettings();
|
||||
if (!settings.demoMode) {
|
||||
// Remove demo user (and cascade delete their jobs/listings)
|
||||
SqliteConnection.execute(`DELETE FROM users WHERE username = 'demo'`);
|
||||
return;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { getJobs } from '../storage/jobStorage.js';
|
||||
import { getUniqueId } from './uniqueId.js';
|
||||
import { config, getPackageVersion, inDevMode } from '../../utils.js';
|
||||
import { getPackageVersion, inDevMode } from '../../utils.js';
|
||||
import os from 'os';
|
||||
import fetch from 'node-fetch';
|
||||
import logger from '../logger.js';
|
||||
import { getSettings } from '../storage/settingsStorage.js';
|
||||
|
||||
const deviceId = getUniqueId() || 'N/A';
|
||||
const version = await getPackageVersion();
|
||||
@@ -11,7 +12,8 @@ const FREDY_TRACKING_URL = 'https://fredy.orange-coding.net/tracking';
|
||||
|
||||
export const trackMainEvent = async () => {
|
||||
try {
|
||||
if (config.analyticsEnabled && !inDevMode()) {
|
||||
const settings = await getSettings();
|
||||
if (settings.analyticsEnabled && !inDevMode()) {
|
||||
const activeProvider = new Set();
|
||||
const activeAdapter = new Set();
|
||||
|
||||
@@ -44,7 +46,8 @@ export const trackMainEvent = async () => {
|
||||
* Note, this will only be used when Fredy runs in demo mode
|
||||
*/
|
||||
export async function trackDemoAccessed() {
|
||||
if (config.analyticsEnabled && !inDevMode() && config.demoMode) {
|
||||
const settings = await getSettings();
|
||||
if (settings.analyticsEnabled && !inDevMode() && settings.demoMode) {
|
||||
try {
|
||||
await fetch(`${FREDY_TRACKING_URL}/demo/accessed`, {
|
||||
method: 'POST',
|
||||
@@ -56,7 +59,8 @@ export async function trackDemoAccessed() {
|
||||
}
|
||||
}
|
||||
|
||||
function enrichTrackingObject(trackingObject) {
|
||||
async function enrichTrackingObject(trackingObject) {
|
||||
const settings = await getSettings();
|
||||
const operatingSystem = os.platform();
|
||||
const osVersion = os.release();
|
||||
const arch = process.arch;
|
||||
@@ -65,7 +69,7 @@ function enrichTrackingObject(trackingObject) {
|
||||
|
||||
return {
|
||||
...trackingObject,
|
||||
isDemo: config.demoMode,
|
||||
isDemo: settings.demoMode,
|
||||
operatingSystem,
|
||||
osVersion,
|
||||
arch,
|
||||
|
||||
@@ -215,10 +215,6 @@ export async function refreshConfig() {
|
||||
|
||||
try {
|
||||
config = await readConfigFromStorage();
|
||||
//backwards compatibility...
|
||||
config.analyticsEnabled ??= null;
|
||||
config.demoMode ??= false;
|
||||
// default sqlitepath when missing in older configs
|
||||
config.sqlitepath ??= '/db';
|
||||
} catch (error) {
|
||||
config = { ...DEFAULT_CONFIG };
|
||||
@@ -306,7 +302,6 @@ export {
|
||||
getDirName,
|
||||
sleep,
|
||||
randomBetween,
|
||||
config,
|
||||
buildHash,
|
||||
getPackageVersion,
|
||||
toJson,
|
||||
|
||||
@@ -56,8 +56,8 @@
|
||||
"Firefox ESR"
|
||||
],
|
||||
"dependencies": {
|
||||
"@douyinfe/semi-icons": "^2.88.0",
|
||||
"@douyinfe/semi-ui": "2.88.0",
|
||||
"@douyinfe/semi-icons": "^2.88.1",
|
||||
"@douyinfe/semi-ui": "2.88.1",
|
||||
"@sendgrid/mail": "8.1.6",
|
||||
"@visactor/react-vchart": "^2.0.8",
|
||||
"@visactor/vchart": "^2.0.8",
|
||||
|
||||
92
yarn.lock
92
yarn.lock
@@ -997,34 +997,34 @@
|
||||
dependencies:
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@douyinfe/semi-animation-react@2.88.0":
|
||||
version "2.88.0"
|
||||
resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation-react/-/semi-animation-react-2.88.0.tgz#34d951e46a263b14db563b4044b3144f787e44e5"
|
||||
integrity sha512-K6WzTDnLn75I+XOB/9C/hA2Mwjqd+TQpYiEjxSC+l3Ep6MiLS/5VbkGOSt4jiRJJQs584xfw59ReUJ5LGuPQLQ==
|
||||
"@douyinfe/semi-animation-react@2.88.1":
|
||||
version "2.88.1"
|
||||
resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation-react/-/semi-animation-react-2.88.1.tgz#edf263f35bd77bdd48ef158ad0c2d31456e277d2"
|
||||
integrity sha512-vQeJEXd0hWwYafovYz7mcC/HOuUnt1QnCE/+KZx0gsuQ9CuBGUkCMuMDtHkmJtj4S8tQM440CTD7dh3ZC7yyUw==
|
||||
dependencies:
|
||||
"@douyinfe/semi-animation" "2.88.0"
|
||||
"@douyinfe/semi-animation-styled" "2.88.0"
|
||||
"@douyinfe/semi-animation" "2.88.1"
|
||||
"@douyinfe/semi-animation-styled" "2.88.1"
|
||||
classnames "^2.2.6"
|
||||
|
||||
"@douyinfe/semi-animation-styled@2.88.0":
|
||||
version "2.88.0"
|
||||
resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation-styled/-/semi-animation-styled-2.88.0.tgz#abc29d577fc910ee3707af0f581548608c388d27"
|
||||
integrity sha512-iHqrD2HoWL9Vd40DAsSjZHONHU91ayelMlziFoBjvvmaiuvcQms2ead7hLFkDtvkDswT0Mfd8BqkVDJSxTwxnw==
|
||||
"@douyinfe/semi-animation-styled@2.88.1":
|
||||
version "2.88.1"
|
||||
resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation-styled/-/semi-animation-styled-2.88.1.tgz#8a8fbb5613becdea26bec307a0b71d3bf1083f31"
|
||||
integrity sha512-97qugh5GQWDHtDJbSLez7EYuC0oXgkhIMzRivBoaJ1i7jrDLVt+7Cua/CXiRnSoYi32c0ySQuns5M90/1gQD4g==
|
||||
|
||||
"@douyinfe/semi-animation@2.88.0":
|
||||
version "2.88.0"
|
||||
resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation/-/semi-animation-2.88.0.tgz#2c069476b24a55041837e976b0d045c2c0da0049"
|
||||
integrity sha512-J7fjwnVJEYvS2ZbKvWTjRRXTWQPlmYwkeXasICom+KFuE2vrkCzeqTXXIJ25MuaWlM/OWBPqrkAZBIfmNNQXWg==
|
||||
"@douyinfe/semi-animation@2.88.1":
|
||||
version "2.88.1"
|
||||
resolved "https://registry.yarnpkg.com/@douyinfe/semi-animation/-/semi-animation-2.88.1.tgz#6224da91742040de43da98ef7f546888ff54a8be"
|
||||
integrity sha512-xi1NE+L26sf8722O+4FUA1ycw8+qAsqHj4FofAFQoUzj5k4nwZi9KEhdEfcXfiF4ML6Kx+4LsA6J9N2pajpdWw==
|
||||
dependencies:
|
||||
bezier-easing "^2.1.0"
|
||||
|
||||
"@douyinfe/semi-foundation@2.88.0":
|
||||
version "2.88.0"
|
||||
resolved "https://registry.yarnpkg.com/@douyinfe/semi-foundation/-/semi-foundation-2.88.0.tgz#8fa4d5373acb5bb9f1e9fe1ca97c553c0ae76bfc"
|
||||
integrity sha512-WYT1blbg2873xAU9iCasMRnTUsE/9WP/9gE1Zd87vsnZYWwl3WP9imH0iSqeSXkFdJllNo/KBImBY7clOoVIYA==
|
||||
"@douyinfe/semi-foundation@2.88.1":
|
||||
version "2.88.1"
|
||||
resolved "https://registry.yarnpkg.com/@douyinfe/semi-foundation/-/semi-foundation-2.88.1.tgz#2eff45a3b61027a74f65460f00af4d9105ad42c1"
|
||||
integrity sha512-GHQOiwTvlep77QF6Kw18UIeqIjaEDqJdraqTzS4J3ePO/KK9FsnzhyN5ggfhhNoAXXx+NHNFaTDKXxOPQXqCVA==
|
||||
dependencies:
|
||||
"@douyinfe/semi-animation" "2.88.0"
|
||||
"@douyinfe/semi-json-viewer-core" "2.88.0"
|
||||
"@douyinfe/semi-animation" "2.88.1"
|
||||
"@douyinfe/semi-json-viewer-core" "2.88.1"
|
||||
"@mdx-js/mdx" "^3.0.1"
|
||||
async-validator "^3.5.0"
|
||||
classnames "^2.2.6"
|
||||
@@ -1038,44 +1038,44 @@
|
||||
remark-gfm "^4.0.0"
|
||||
scroll-into-view-if-needed "^2.2.24"
|
||||
|
||||
"@douyinfe/semi-icons@2.88.0", "@douyinfe/semi-icons@^2.88.0":
|
||||
version "2.88.0"
|
||||
resolved "https://registry.yarnpkg.com/@douyinfe/semi-icons/-/semi-icons-2.88.0.tgz#8bc28881aba3fa5a190599e1ddf4c6fb1840dbaa"
|
||||
integrity sha512-kZSni5KZFL6fxs+c2nF4e3biPNcnAxV9U27577kOlaqP7l2FqP9U+d4x2YQisgsoT+Z3brqfWEayastQk5fzig==
|
||||
"@douyinfe/semi-icons@2.88.1", "@douyinfe/semi-icons@^2.88.1":
|
||||
version "2.88.1"
|
||||
resolved "https://registry.yarnpkg.com/@douyinfe/semi-icons/-/semi-icons-2.88.1.tgz#e169d1c17571a3eadf84ad014294af42895f2380"
|
||||
integrity sha512-ictYoa+9/9I/A+ioIoubIOY6vY5j285Nj8fJNo39LPr6OEH/Y80yL3aeaQOoi9vTHLx/iV8yp5fgk5NUoOZYeg==
|
||||
dependencies:
|
||||
classnames "^2.2.6"
|
||||
|
||||
"@douyinfe/semi-illustrations@2.88.0":
|
||||
version "2.88.0"
|
||||
resolved "https://registry.yarnpkg.com/@douyinfe/semi-illustrations/-/semi-illustrations-2.88.0.tgz#7ba4dad1fe98c813386c3baf7fd9720974cab1b3"
|
||||
integrity sha512-fQ+Q9g9KjE9a2nH59uNHEzUdSt40GDloPCB4n7J3Q9EUeOiWpOsXbC/3NCDZc2ElZVryMChT3g6vjvIzHAl9Hw==
|
||||
"@douyinfe/semi-illustrations@2.88.1":
|
||||
version "2.88.1"
|
||||
resolved "https://registry.yarnpkg.com/@douyinfe/semi-illustrations/-/semi-illustrations-2.88.1.tgz#5e0545536bf3d16b851c7c37f8c4bde771407248"
|
||||
integrity sha512-ynnTxM4oTOi0byp08J5V/JRnTVdx8ACexwroYUDtiqNb7esNf1fcDPfkKPzuj3g7gwVJUnw4bpjquQk79yNOsw==
|
||||
|
||||
"@douyinfe/semi-json-viewer-core@2.88.0":
|
||||
version "2.88.0"
|
||||
resolved "https://registry.yarnpkg.com/@douyinfe/semi-json-viewer-core/-/semi-json-viewer-core-2.88.0.tgz#53cd6e6aa2a7f4b517c4cd532b08e65af4d60da7"
|
||||
integrity sha512-LLdLZ477eJBQKlCPIqPhpIcXL1GOy9mvjpwryqiAj/h6BXmwcvp1zJwJQP9Rq9inePawdYMSZozaB2X1FPjKOg==
|
||||
"@douyinfe/semi-json-viewer-core@2.88.1":
|
||||
version "2.88.1"
|
||||
resolved "https://registry.yarnpkg.com/@douyinfe/semi-json-viewer-core/-/semi-json-viewer-core-2.88.1.tgz#9cd502530658ca1e7d2d8d4b64aac84fbc91286c"
|
||||
integrity sha512-kG7vEd9qPvQ2q3vOe8wT5VBiIATZ+4WPUUpexeg/otkF4Da0II5f2AO4CfGbPB9wFzIgDttRrLHX6QcRBmF4mQ==
|
||||
dependencies:
|
||||
jsonc-parser "^3.3.1"
|
||||
|
||||
"@douyinfe/semi-theme-default@2.88.0":
|
||||
version "2.88.0"
|
||||
resolved "https://registry.yarnpkg.com/@douyinfe/semi-theme-default/-/semi-theme-default-2.88.0.tgz#caa8c24c3afd3c24689a74efacdd6e11199cc22c"
|
||||
integrity sha512-Cykl39Tkw9cJYTBpDToyj0uyXBGS15QDZGR2zCskdG52+eaCyZAoCds4W3HOxlToUmuw0JgVES5VSalIy3M07A==
|
||||
"@douyinfe/semi-theme-default@2.88.1":
|
||||
version "2.88.1"
|
||||
resolved "https://registry.yarnpkg.com/@douyinfe/semi-theme-default/-/semi-theme-default-2.88.1.tgz#9b72a4f2a77a58c84a3c14e465ca4008da43b0e0"
|
||||
integrity sha512-JKpC23F0ZCHlyazB4J3+vx43/+++odrgzZIGKHprXBTjbvqagu5wFe8mpeaY9mD8Nrd3ZeQJ3wApgwHuQR1fwA==
|
||||
|
||||
"@douyinfe/semi-ui@2.88.0":
|
||||
version "2.88.0"
|
||||
resolved "https://registry.yarnpkg.com/@douyinfe/semi-ui/-/semi-ui-2.88.0.tgz#a220fcfcad593f9669acb44b74c3c1e10efcb262"
|
||||
integrity sha512-MlfLjUpTqnfk3Sg6pQOA2JETvZaWFEQwLvEcbfwA5LijX/hu7hG1Zhj1AVnpXTXrOUiU+ENTOiLu4GggoW2EaA==
|
||||
"@douyinfe/semi-ui@2.88.1":
|
||||
version "2.88.1"
|
||||
resolved "https://registry.yarnpkg.com/@douyinfe/semi-ui/-/semi-ui-2.88.1.tgz#68dd9b2d7421c0741fe0599932b15aa51a314b38"
|
||||
integrity sha512-x7HsvBn8AVbpLQcNk6C4vhAORmSBGN9kzbljSGWzFUx2xqrUz10bu39eBrKLMVuR6+GX3Gw22edPfx62QAaYow==
|
||||
dependencies:
|
||||
"@dnd-kit/core" "^6.0.8"
|
||||
"@dnd-kit/sortable" "^7.0.2"
|
||||
"@dnd-kit/utilities" "^3.2.1"
|
||||
"@douyinfe/semi-animation" "2.88.0"
|
||||
"@douyinfe/semi-animation-react" "2.88.0"
|
||||
"@douyinfe/semi-foundation" "2.88.0"
|
||||
"@douyinfe/semi-icons" "2.88.0"
|
||||
"@douyinfe/semi-illustrations" "2.88.0"
|
||||
"@douyinfe/semi-theme-default" "2.88.0"
|
||||
"@douyinfe/semi-animation" "2.88.1"
|
||||
"@douyinfe/semi-animation-react" "2.88.1"
|
||||
"@douyinfe/semi-foundation" "2.88.1"
|
||||
"@douyinfe/semi-icons" "2.88.1"
|
||||
"@douyinfe/semi-illustrations" "2.88.1"
|
||||
"@douyinfe/semi-theme-default" "2.88.1"
|
||||
"@tiptap/core" "^3.1.0"
|
||||
"@tiptap/extension-document" "^3.3.0"
|
||||
"@tiptap/extension-hard-break" "^3.3.0"
|
||||
|
||||
Reference in New Issue
Block a user