From cca1463a681cbcd5c0c29f37ead08004c55ab6d0 Mon Sep 17 00:00:00 2001 From: Alexander Roidl <34438048+alexanderroidl@users.noreply.github.com> Date: Wed, 23 Jul 2025 08:47:26 +0200 Subject: [PATCH] chore: run formatter (#145) --- index.js | 64 +-- lib/api/api.js | 2 +- lib/notification/notify.js | 2 +- lib/provider/immobilienDe.js | 2 +- lib/provider/kleinanzeigen.js | 68 +-- lib/provider/neubauKompass.js | 54 ++- lib/provider/wgGesucht.js | 56 +-- prod.js | 2 +- test/provider/immonet.test.js | 60 +-- test/provider/neubauKompass.test.js | 58 +-- test/utils/utils.test.js | 20 +- ui/src/App.jsx | 168 +++---- ui/src/Index.jsx | 2 +- ui/src/components/tracking/TrackingModal.jsx | 84 ++-- .../views/generalSettings/GeneralSettings.jsx | 448 +++++++++--------- ui/src/views/jobs/ProcessingTimes.jsx | 58 +-- .../NotificationAdapterMutator.jsx | 8 +- .../components/provider/ProviderMutator.jsx | 2 +- ui/src/views/login/Login.jsx | 173 +++---- 19 files changed, 666 insertions(+), 665 deletions(-) diff --git a/index.js b/index.js index 717675f..c08652f 100755 --- a/index.js +++ b/index.js @@ -1,14 +1,14 @@ import fs from 'fs'; -import {config} from './lib/utils.js'; +import { config } from './lib/utils.js'; import * as similarityCache from './lib/services/similarity-check/similarityCache.js'; import { setLastJobExecution } from './lib/services/storage/listingsStorage.js'; import * as jobStorage from './lib/services/storage/jobStorage.js'; import FredyRuntime from './lib/FredyRuntime.js'; import { duringWorkingHoursOrNotSet } from './lib/utils.js'; import './lib/api/api.js'; -import {track} from './lib/services/tracking/Tracker.js'; -import {handleDemoUser} from './lib/services/storage/userStorage.js'; -import {cleanupDemoAtMidnight} from './lib/services/demoCleanup.js'; +import { track } from './lib/services/tracking/Tracker.js'; +import { handleDemoUser } from './lib/services/storage/userStorage.js'; +import { cleanupDemoAtMidnight } from './lib/services/demoCleanup.js'; //if db folder does not exist, ensure to create it before loading anything else if (!fs.existsSync('./db')) { fs.mkdirSync('./db'); @@ -19,13 +19,13 @@ const provider = fs.readdirSync(path).filter((file) => file.endsWith('.js')); const INTERVAL = config.interval * 60 * 1000; /* eslint-disable no-console */ console.log(`Started Fredy successfully. Ui can be accessed via http://localhost:${config.port}`); -if(config.demoMode){ - console.info('Running in demo mode'); - cleanupDemoAtMidnight(); +if (config.demoMode) { + console.info('Running in demo mode'); + cleanupDemoAtMidnight(); } /* eslint-enable no-console */ const fetchedProvider = await Promise.all( - provider.filter((provider) => provider.endsWith('.js')).map(async (pro) => import(`${path}/${pro}`)) + provider.filter((provider) => provider.endsWith('.js')).map(async (pro) => import(`${path}/${pro}`)), ); handleDemoUser(); @@ -33,30 +33,30 @@ handleDemoUser(); setInterval( (function exec() { const isDuringWorkingHoursOrNotSet = duringWorkingHoursOrNotSet(config, Date.now()); - if(!config.demoMode) { - if (isDuringWorkingHoursOrNotSet) { - track(); - config.lastRun = Date.now(); - jobStorage - .getJobs() - .filter((job) => job.enabled) - .forEach((job) => { - job.provider - .filter((p) => fetchedProvider.find((fp) => fp.metaInformation.id === p.id) != null) - .forEach(async (prov) => { - const pro = fetchedProvider.find((fp) => fp.metaInformation.id === prov.id); - pro.init(prov, job.blacklist); - await new FredyRuntime(pro.config, job.notificationAdapter, prov.id, job.id, similarityCache).execute(); - setLastJobExecution(job.id); - }); - }); - } else { - /* eslint-disable no-console */ - console.debug('Working hours set. Skipping as outside of working hours.'); - /* eslint-enable no-console */ - } - } + if (!config.demoMode) { + if (isDuringWorkingHoursOrNotSet) { + track(); + config.lastRun = Date.now(); + jobStorage + .getJobs() + .filter((job) => job.enabled) + .forEach((job) => { + job.provider + .filter((p) => fetchedProvider.find((fp) => fp.metaInformation.id === p.id) != null) + .forEach(async (prov) => { + const pro = fetchedProvider.find((fp) => fp.metaInformation.id === prov.id); + pro.init(prov, job.blacklist); + await new FredyRuntime(pro.config, job.notificationAdapter, prov.id, job.id, similarityCache).execute(); + setLastJobExecution(job.id); + }); + }); + } else { + /* eslint-disable no-console */ + console.debug('Working hours set. Skipping as outside of working hours.'); + /* eslint-enable no-console */ + } + } return exec; })(), - INTERVAL + INTERVAL, ); diff --git a/lib/api/api.js b/lib/api/api.js index 7d44843..ca2cb8f 100644 --- a/lib/api/api.js +++ b/lib/api/api.js @@ -12,7 +12,7 @@ import restana from 'restana'; import files from 'serve-static'; import path from 'path'; import { getDirName } from '../utils.js'; -import {demoRouter} from './routes/demoRouter.js'; +import { demoRouter } from './routes/demoRouter.js'; const service = restana(); const staticService = files(path.join(getDirName(), '../ui/public')); const PORT = config.port || 9998; diff --git a/lib/notification/notify.js b/lib/notification/notify.js index 2f22a8b..5fcfec1 100755 --- a/lib/notification/notify.js +++ b/lib/notification/notify.js @@ -6,7 +6,7 @@ const adapter = await Promise.all( fs .readdirSync('./lib/notification/adapter') .filter((file) => file.endsWith('.js')) - .map(async (integPath) => await import(`${path}/${integPath}`)) + .map(async (integPath) => await import(`${path}/${integPath}`)), ); if (adapter.length === 0) { diff --git a/lib/provider/immobilienDe.js b/lib/provider/immobilienDe.js index 13e520f..3d3f5ab 100644 --- a/lib/provider/immobilienDe.js +++ b/lib/provider/immobilienDe.js @@ -1,4 +1,4 @@ -import utils, {buildHash} from '../utils.js'; +import utils, { buildHash } from '../utils.js'; let appliedBlackList = []; function shortenLink(link) { return link.substring(0, link.indexOf('?')); diff --git a/lib/provider/kleinanzeigen.js b/lib/provider/kleinanzeigen.js index 1f9bc9f..30e4c6d 100755 --- a/lib/provider/kleinanzeigen.js +++ b/lib/provider/kleinanzeigen.js @@ -1,50 +1,50 @@ -import utils, {buildHash} from '../utils.js'; +import utils, { buildHash } from '../utils.js'; let appliedBlackList = []; let appliedBlacklistedDistricts = []; function normalize(o) { - const size = o.size || '--- m²'; - const id = buildHash(o.id, o.price); - const link = `https://www.kleinanzeigen.de${o.link}`; - return Object.assign(o, {id, size, link}); + const size = o.size || '--- m²'; + const id = buildHash(o.id, o.price); + const link = `https://www.kleinanzeigen.de${o.link}`; + return Object.assign(o, { id, size, link }); } function applyBlacklist(o) { - const titleNotBlacklisted = !utils.isOneOf(o.title, appliedBlackList); - const descNotBlacklisted = !utils.isOneOf(o.description, appliedBlackList); - const isBlacklistedDistrict = - appliedBlacklistedDistricts.length === 0 ? false : utils.isOneOf(o.description, appliedBlacklistedDistricts); - return o.title != null && !isBlacklistedDistrict && titleNotBlacklisted && descNotBlacklisted; + const titleNotBlacklisted = !utils.isOneOf(o.title, appliedBlackList); + const descNotBlacklisted = !utils.isOneOf(o.description, appliedBlackList); + const isBlacklistedDistrict = + appliedBlacklistedDistricts.length === 0 ? false : utils.isOneOf(o.description, appliedBlacklistedDistricts); + return o.title != null && !isBlacklistedDistrict && titleNotBlacklisted && descNotBlacklisted; } const config = { - url: null, - crawlContainer: '#srchrslt-adtable .ad-listitem ', - //sort by date is standard oO - sortByDateParam: null, - waitForSelector: 'body', - crawlFields: { - id: '.aditem@data-adid | int', - price: '.aditem-main--middle--price-shipping--price | removeNewline | trim', - size: '.aditem-main .text-module-end | removeNewline | trim', - title: '.aditem-main .text-module-begin a | removeNewline | trim', - link: '.aditem-main .text-module-begin a@href | removeNewline | trim', - description: '.aditem-main .aditem-main--middle--description | removeNewline | trim', - address: '.aditem-main--top--left | trim | removeNewline', - }, - normalize: normalize, - filter: applyBlacklist, + url: null, + crawlContainer: '#srchrslt-adtable .ad-listitem ', + //sort by date is standard oO + sortByDateParam: null, + waitForSelector: 'body', + crawlFields: { + id: '.aditem@data-adid | int', + price: '.aditem-main--middle--price-shipping--price | removeNewline | trim', + size: '.aditem-main .text-module-end | removeNewline | trim', + title: '.aditem-main .text-module-begin a | removeNewline | trim', + link: '.aditem-main .text-module-begin a@href | removeNewline | trim', + description: '.aditem-main .aditem-main--middle--description | removeNewline | trim', + address: '.aditem-main--top--left | trim | removeNewline', + }, + normalize: normalize, + filter: applyBlacklist, }; export const metaInformation = { - name: 'Ebay Kleinanzeigen', - baseUrl: 'https://www.kleinanzeigen.de/', - id: 'kleinanzeigen', + name: 'Ebay Kleinanzeigen', + baseUrl: 'https://www.kleinanzeigen.de/', + id: 'kleinanzeigen', }; export const init = (sourceConfig, blacklist, blacklistedDistricts) => { - config.enabled = sourceConfig.enabled; - config.url = sourceConfig.url; - appliedBlacklistedDistricts = blacklistedDistricts || []; - appliedBlackList = blacklist || []; + config.enabled = sourceConfig.enabled; + config.url = sourceConfig.url; + appliedBlacklistedDistricts = blacklistedDistricts || []; + appliedBlackList = blacklist || []; }; -export {config}; +export { config }; diff --git a/lib/provider/neubauKompass.js b/lib/provider/neubauKompass.js index 41ba49f..7e5c066 100755 --- a/lib/provider/neubauKompass.js +++ b/lib/provider/neubauKompass.js @@ -1,44 +1,46 @@ -import utils, {buildHash} from '../utils.js'; +import utils, { buildHash } from '../utils.js'; let appliedBlackList = []; function nullOrEmpty(val) { - return val == null || val.length === 0; + return val == null || val.length === 0; } function normalize(o) { - const link = nullOrEmpty(o.link) ? 'NO LINK' : `https://www.neubaukompass.de${o.link.substring(o.link.indexOf('/neubau'))}`; - const id = buildHash(o.link, o.price); - return Object.assign(o, {id, link}); + const link = nullOrEmpty(o.link) + ? 'NO LINK' + : `https://www.neubaukompass.de${o.link.substring(o.link.indexOf('/neubau'))}`; + const id = buildHash(o.link, o.price); + return Object.assign(o, { id, link }); } function applyBlacklist(o) { - return !utils.isOneOf(o.title, appliedBlackList); + return !utils.isOneOf(o.title, appliedBlackList); } const config = { - url: null, - crawlContainer: '.col-12.mb-4', - sortByDateParam: 'Sortierung=Id&Richtung=DESC', - waitForSelector: '.nbk-section', - crawlFields: { - id: 'a@href', - title: 'a@title | removeNewline | trim', - link: 'a@href', - address: '.nbk-project-card__description | removeNewline | trim', - price: '.nbk-project-card__spec-item .nbk-project-card__spec-value | removeNewline | trim', - }, - normalize: normalize, - filter: applyBlacklist, + url: null, + crawlContainer: '.col-12.mb-4', + sortByDateParam: 'Sortierung=Id&Richtung=DESC', + waitForSelector: '.nbk-section', + crawlFields: { + id: 'a@href', + title: 'a@title | removeNewline | trim', + link: 'a@href', + address: '.nbk-project-card__description | removeNewline | trim', + price: '.nbk-project-card__spec-item .nbk-project-card__spec-value | removeNewline | trim', + }, + normalize: normalize, + filter: applyBlacklist, }; export const init = (sourceConfig, blacklist) => { - config.enabled = sourceConfig.enabled; - config.url = sourceConfig.url; - appliedBlackList = blacklist || []; + config.enabled = sourceConfig.enabled; + config.url = sourceConfig.url; + appliedBlackList = blacklist || []; }; export const metaInformation = { - name: 'Neubau Kompass', - baseUrl: 'https://www.neubaukompass.de/', - id: 'neubauKompass', + name: 'Neubau Kompass', + baseUrl: 'https://www.neubaukompass.de/', + id: 'neubauKompass', }; -export {config}; +export { config }; diff --git a/lib/provider/wgGesucht.js b/lib/provider/wgGesucht.js index 802ab9b..2392b00 100755 --- a/lib/provider/wgGesucht.js +++ b/lib/provider/wgGesucht.js @@ -1,43 +1,43 @@ -import utils, {buildHash} from '../utils.js'; +import utils, { buildHash } from '../utils.js'; let appliedBlackList = []; function normalize(o) { - const id = buildHash(o.id, o.price); - const link = `https://www.wg-gesucht.de${o.link}`; - return Object.assign(o, { id, link }); + const id = buildHash(o.id, o.price); + const link = `https://www.wg-gesucht.de${o.link}`; + return Object.assign(o, { id, link }); } function applyBlacklist(o) { - const titleNotBlacklisted = !utils.isOneOf(o.title, appliedBlackList); - const descNotBlacklisted = !utils.isOneOf(o.description, appliedBlackList); - return o.id != null && titleNotBlacklisted && descNotBlacklisted; + const titleNotBlacklisted = !utils.isOneOf(o.title, appliedBlackList); + const descNotBlacklisted = !utils.isOneOf(o.description, appliedBlackList); + return o.id != null && titleNotBlacklisted && descNotBlacklisted; } const config = { - url: null, - crawlContainer: '#main_column .wgg_card', - sortByDateParam: 'sort_column=0&sort_order=0', - waitForSelector: 'body', - crawlFields: { - id: '@data-id', - details: '.row .noprint .col-xs-11 |removeNewline |trim', - price: '.middle .col-xs-3 |removeNewline |trim', - size: '.middle .text-right |removeNewline |trim', - title: '.truncate_title a |removeNewline |trim', - link: '.truncate_title a@href', - }, - normalize: normalize, - filter: applyBlacklist, + url: null, + crawlContainer: '#main_column .wgg_card', + sortByDateParam: 'sort_column=0&sort_order=0', + waitForSelector: 'body', + crawlFields: { + id: '@data-id', + details: '.row .noprint .col-xs-11 |removeNewline |trim', + price: '.middle .col-xs-3 |removeNewline |trim', + size: '.middle .text-right |removeNewline |trim', + title: '.truncate_title a |removeNewline |trim', + link: '.truncate_title a@href', + }, + normalize: normalize, + filter: applyBlacklist, }; export const init = (sourceConfig, blacklist) => { - config.enabled = sourceConfig.enabled; - config.url = sourceConfig.url; - appliedBlackList = blacklist || []; + config.enabled = sourceConfig.enabled; + config.url = sourceConfig.url; + appliedBlackList = blacklist || []; }; export const metaInformation = { - name: 'Wg gesucht', - baseUrl: 'https://www.wg-gesucht.de/', - id: 'wgGesucht', + name: 'Wg gesucht', + baseUrl: 'https://www.wg-gesucht.de/', + id: 'wgGesucht', }; -export {config}; +export { config }; diff --git a/prod.js b/prod.js index b496da6..c9dd783 100644 --- a/prod.js +++ b/prod.js @@ -1,2 +1,2 @@ process.env.NODE_ENV = 'production'; -import('./index.js'); \ No newline at end of file +import('./index.js'); diff --git a/test/provider/immonet.test.js b/test/provider/immonet.test.js index 510135e..e21c8a7 100644 --- a/test/provider/immonet.test.js +++ b/test/provider/immonet.test.js @@ -1,38 +1,38 @@ import * as similarityCache from '../../lib/services/similarity-check/similarityCache.js'; -import {get} from '../mocks/mockNotification.js'; -import {mockFredy, providerConfig} from '../utils.js'; -import {expect} from 'chai'; +import { get } from '../mocks/mockNotification.js'; +import { mockFredy, providerConfig } from '../utils.js'; +import { expect } from 'chai'; import * as provider from '../../lib/provider/immonet.js'; describe('#immonet testsuite()', () => { - after(() => { - similarityCache.stopCacheCleanup(); - }); - provider.init(providerConfig.immonet, [], []); - it('should test immonet provider', async () => { - const Fredy = await mockFredy(); - return await new Promise((resolve) => { - const fredy = new Fredy(provider.config, null, provider.metaInformation.id, 'immonet', similarityCache); - fredy.execute().then((listing) => { - expect(listing).to.be.a('array'); - const notificationObj = get(); - expect(notificationObj).to.be.a('object'); - expect(notificationObj.serviceName).to.equal('immonet'); - notificationObj.payload.forEach((notify) => { - /** check the actual structure **/ - expect(notify.id).to.be.a('string'); - expect(notify.price).to.be.a('string'); - expect(notify.size).to.be.a('string'); - expect(notify.title).to.be.a('string'); - expect(notify.link).to.be.a('string'); - expect(notify.address).to.be.a('string'); + after(() => { + similarityCache.stopCacheCleanup(); + }); + provider.init(providerConfig.immonet, [], []); + it('should test immonet provider', async () => { + const Fredy = await mockFredy(); + return await new Promise((resolve) => { + const fredy = new Fredy(provider.config, null, provider.metaInformation.id, 'immonet', similarityCache); + fredy.execute().then((listing) => { + expect(listing).to.be.a('array'); + const notificationObj = get(); + expect(notificationObj).to.be.a('object'); + expect(notificationObj.serviceName).to.equal('immonet'); + notificationObj.payload.forEach((notify) => { + /** check the actual structure **/ + expect(notify.id).to.be.a('string'); + expect(notify.price).to.be.a('string'); + expect(notify.size).to.be.a('string'); + expect(notify.title).to.be.a('string'); + expect(notify.link).to.be.a('string'); + expect(notify.address).to.be.a('string'); - expect(notify.size).that.does.include('m²'); - expect(notify.title).to.be.not.empty; - expect(notify.address).to.be.not.empty; - }); - resolve(); - }); + expect(notify.size).that.does.include('m²'); + expect(notify.title).to.be.not.empty; + expect(notify.address).to.be.not.empty; }); + resolve(); + }); }); + }); }); diff --git a/test/provider/neubauKompass.test.js b/test/provider/neubauKompass.test.js index 09dce30..4682bd0 100644 --- a/test/provider/neubauKompass.test.js +++ b/test/provider/neubauKompass.test.js @@ -1,36 +1,36 @@ import * as similarityCache from '../../lib/services/similarity-check/similarityCache.js'; -import {get} from '../mocks/mockNotification.js'; -import {mockFredy, providerConfig} from '../utils.js'; -import {expect} from 'chai'; +import { get } from '../mocks/mockNotification.js'; +import { mockFredy, providerConfig } from '../utils.js'; +import { expect } from 'chai'; import * as provider from '../../lib/provider/neubauKompass.js'; describe('#neubauKompass testsuite()', () => { - after(() => { - similarityCache.stopCacheCleanup(); - }); - provider.init(providerConfig.neubauKompass, [], []); - it('should test neubauKompass provider', async () => { - const Fredy = await mockFredy(); - return await new Promise((resolve) => { - const fredy = new Fredy(provider.config, null, provider.metaInformation.id, 'neubauKompass', similarityCache); - fredy.execute().then((listing) => { - expect(listing).to.be.a('array'); - const notificationObj = get(); - expect(notificationObj.serviceName).to.equal('neubauKompass'); - notificationObj.payload.forEach((notify) => { - expect(notify).to.be.a('object'); - /** check the actual structure **/ - expect(notify.id).to.be.a('string'); - expect(notify.title).to.be.a('string'); - expect(notify.link).to.be.a('string'); - expect(notify.address).to.be.a('string'); - /** check the values if possible **/ - expect(notify.title).to.be.not.empty; - expect(notify.link).that.does.include('https://www.neubaukompass.de'); - expect(notify.address).to.be.not.empty; - }); - resolve(); - }); + after(() => { + similarityCache.stopCacheCleanup(); + }); + provider.init(providerConfig.neubauKompass, [], []); + it('should test neubauKompass provider', async () => { + const Fredy = await mockFredy(); + return await new Promise((resolve) => { + const fredy = new Fredy(provider.config, null, provider.metaInformation.id, 'neubauKompass', similarityCache); + fredy.execute().then((listing) => { + expect(listing).to.be.a('array'); + const notificationObj = get(); + expect(notificationObj.serviceName).to.equal('neubauKompass'); + notificationObj.payload.forEach((notify) => { + expect(notify).to.be.a('object'); + /** check the actual structure **/ + expect(notify.id).to.be.a('string'); + expect(notify.title).to.be.a('string'); + expect(notify.link).to.be.a('string'); + expect(notify.address).to.be.a('string'); + /** check the values if possible **/ + expect(notify.title).to.be.not.empty; + expect(notify.link).that.does.include('https://www.neubaukompass.de'); + expect(notify.address).to.be.not.empty; }); + resolve(); + }); }); + }); }); diff --git a/test/utils/utils.test.js b/test/utils/utils.test.js index 81afe51..151042b 100644 --- a/test/utils/utils.test.js +++ b/test/utils/utils.test.js @@ -1,16 +1,16 @@ import { expect } from 'chai'; -import {buildHash} from '../../lib/utils.js'; +import { buildHash } from '../../lib/utils.js'; describe('utilsCheck', () => { describe('#utilsCheck()', () => { - it('should be null when null input', () => { - expect(buildHash(null)).to.be.null; - }); - it('should be null when null empty', () => { - expect(buildHash('')).to.be.null; - }); - it('should return a value', () => { - expect(buildHash('bla', '', null)).to.be.a.string; - }); + it('should be null when null input', () => { + expect(buildHash(null)).to.be.null; + }); + it('should be null when null empty', () => { + expect(buildHash('')).to.be.null; + }); + it('should return a value', () => { + expect(buildHash('bla', '', null)).to.be.a.string; + }); }); }); diff --git a/ui/src/App.jsx b/ui/src/App.jsx index dadcdfa..86485ac 100644 --- a/ui/src/App.jsx +++ b/ui/src/App.jsx @@ -1,4 +1,4 @@ -import React, {useEffect} from 'react'; +import React, { useEffect } from 'react'; import InsufficientPermission from './components/permission/InsufficientPermission'; import PermissionAwareRoute from './components/permission/PermissionAwareRoute'; @@ -6,106 +6,108 @@ import GeneralSettings from './views/generalSettings/GeneralSettings'; import JobMutation from './views/jobs/mutation/JobMutation'; import UserMutator from './views/user/mutation/UserMutator'; import JobInsight from './views/jobs/insights/JobInsight.jsx'; -import {useDispatch, useSelector} from 'react-redux'; -import {Switch, Redirect} from 'react-router-dom'; +import { useDispatch, useSelector } from 'react-redux'; +import { Switch, Redirect } from 'react-router-dom'; import Logout from './components/logout/Logout'; import Logo from './components/logo/Logo'; import Menu from './components/menu/Menu'; import Login from './views/login/Login'; import Users from './views/user/Users'; import Jobs from './views/jobs/Jobs'; -import {Route} from 'react-router'; +import { Route } from 'react-router'; import './App.less'; import TrackingModal from './components/tracking/TrackingModal.jsx'; -import {Banner} from '@douyinfe/semi-ui'; +import { Banner } from '@douyinfe/semi-ui'; export default function FredyApp() { - const dispatch = useDispatch(); - const [loading, setLoading] = React.useState(true); - const currentUser = useSelector((state) => state.user.currentUser); - const settings = useSelector((state) => state.generalSettings.settings); + const dispatch = useDispatch(); + const [loading, setLoading] = React.useState(true); + const currentUser = useSelector((state) => state.user.currentUser); + const settings = useSelector((state) => state.generalSettings.settings); - useEffect(() => { - async function init() { - await dispatch.user.getCurrentUser(); - if (!needsLogin()) { - await dispatch.provider.getProvider(); - await dispatch.jobs.getJobs(); - await dispatch.jobs.getProcessingTimes(); - await dispatch.notificationAdapter.getAdapter(); - await dispatch.generalSettings.getGeneralSettings(); - } - setLoading(false); - } + useEffect(() => { + async function init() { + await dispatch.user.getCurrentUser(); + if (!needsLogin()) { + await dispatch.provider.getProvider(); + await dispatch.jobs.getJobs(); + await dispatch.jobs.getProcessingTimes(); + await dispatch.notificationAdapter.getAdapter(); + await dispatch.generalSettings.getGeneralSettings(); + } + setLoading(false); + } - init(); - }, [currentUser?.userId]); + init(); + }, [currentUser?.userId]); - const needsLogin = () => { - return currentUser == null || Object.keys(currentUser).length === 0; - }; + const needsLogin = () => { + return currentUser == null || Object.keys(currentUser).length === 0; + }; - const isAdmin = () => currentUser != null && currentUser.isAdmin; + const isAdmin = () => currentUser != null && currentUser.isAdmin; - const login = () => ( + const login = () => ( + + + + + ); + + return loading ? null : needsLogin() ? ( + login() + ) : ( +
+
+ + + + + {settings.demoMode && ( + <> + +
+ + )} + {settings.analyticsEnabled === null && !settings.demoMode && } - - + + + + + + } + currentUser={currentUser} + /> + } + currentUser={currentUser} + /> + } currentUser={currentUser} /> + } + currentUser={currentUser} + /> + + - ); - - return loading ? null : needsLogin() ? ( - login() - ) : ( -
-
- - - - - {settings.demoMode && ( - <> - -
- )} - {(settings.analyticsEnabled === null && !settings.demoMode) && } - - - - - - - } - currentUser={currentUser} - /> - } - currentUser={currentUser} - /> - } currentUser={currentUser}/> - } - currentUser={currentUser} - /> - - - -
-
- ); +
+
+ ); } FredyApp.displayName = 'FredyApp'; diff --git a/ui/src/Index.jsx b/ui/src/Index.jsx index c628120..d66dc47 100644 --- a/ui/src/Index.jsx +++ b/ui/src/Index.jsx @@ -23,5 +23,5 @@ root.render( - + , ); diff --git a/ui/src/components/tracking/TrackingModal.jsx b/ui/src/components/tracking/TrackingModal.jsx index 017838a..80383b5 100644 --- a/ui/src/components/tracking/TrackingModal.jsx +++ b/ui/src/components/tracking/TrackingModal.jsx @@ -1,52 +1,56 @@ import React from 'react'; -import {Modal} from '@douyinfe/semi-ui'; +import { Modal } from '@douyinfe/semi-ui'; import Logo from '../logo/Logo.jsx'; -import {xhrPost} from '../../services/xhr.js'; +import { xhrPost } from '../../services/xhr.js'; import './TrackingModal.less'; import inDevelopment from '../../services/developmentMode.js'; const saveResponse = async (analyticsEnabled) => { - await xhrPost('/api/admin/generalSettings', { - analyticsEnabled - }); + await xhrPost('/api/admin/generalSettings', { + analyticsEnabled, + }); }; export default function TrackingModal() { - if(inDevelopment()){ - return null; - } + if (inDevelopment()) { + return null; + } - return { - await saveResponse(true); - location.reload(); - }} - onCancel={async () => { - await saveResponse(false); - location.reload(); - }} - maskClosable={false} - closable={false} - okText="Yes! I want to help" - cancelText="No, thanks" + return ( + { + await saveResponse(true); + location.reload(); + }} + onCancel={async () => { + await saveResponse(false); + location.reload(); + }} + maskClosable={false} + closable={false} + okText="Yes! I want to help" + cancelText="No, thanks" > - -
-

Hey 👋

-

Fed up with popups? Yeah, me too. But this one’s important, and I promise it will only appear once ;)

-

Fredy is completely free (and will always remain free). If you’d like, you can support me by donating - through my GitHub, but there’s absolutely no obligation to do so.

-

However, it would be a huge - help if you’d allow me to collect some analytical data. Wait, before you click "no", let me explain. If - you - agree, Fredy will send a ping to my Mixpanel project each time it runs.

-

The data includes: names of - active adapters/providers, OS, architecture, Node version, and language. The information is entirely - anonymous and helps me understand which adapters/providers are most frequently used.

-

Thanks🤘

-
-
; - -} \ No newline at end of file + +
+

Hey 👋

+

Fed up with popups? Yeah, me too. But this one’s important, and I promise it will only appear once ;)

+

+ Fredy is completely free (and will always remain free). If you’d like, you can support me by donating through + my GitHub, but there’s absolutely no obligation to do so. +

+

+ However, it would be a huge help if you’d allow me to collect some analytical data. Wait, before you click + "no", let me explain. If you agree, Fredy will send a ping to my Mixpanel project each time it runs. +

+

+ The data includes: names of active adapters/providers, OS, architecture, Node version, and language. The + information is entirely anonymous and helps me understand which adapters/providers are most frequently used. +

+

Thanks🤘

+
+
+ ); +} diff --git a/ui/src/views/generalSettings/GeneralSettings.jsx b/ui/src/views/generalSettings/GeneralSettings.jsx index 29c8fbb..6cb2655 100644 --- a/ui/src/views/generalSettings/GeneralSettings.jsx +++ b/ui/src/views/generalSettings/GeneralSettings.jsx @@ -1,261 +1,251 @@ import React from 'react'; -import {useDispatch, useSelector} from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; -import {Divider, TimePicker, Button, Checkbox} from '@douyinfe/semi-ui'; -import {InputNumber} from '@douyinfe/semi-ui'; +import { Divider, TimePicker, Button, Checkbox } from '@douyinfe/semi-ui'; +import { InputNumber } from '@douyinfe/semi-ui'; import Headline from '../../components/headline/Headline'; -import {xhrPost} from '../../services/xhr'; -import {SegmentPart} from '../../components/segment/SegmentPart'; -import {Banner, Toast} from '@douyinfe/semi-ui'; -import {IconSave, IconCalendar, IconRefresh, IconSignal, IconLineChartStroked, IconSearch} from '@douyinfe/semi-icons'; +import { xhrPost } from '../../services/xhr'; +import { SegmentPart } from '../../components/segment/SegmentPart'; +import { Banner, Toast } from '@douyinfe/semi-ui'; +import { + IconSave, + IconCalendar, + IconRefresh, + IconSignal, + IconLineChartStroked, + IconSearch, +} from '@douyinfe/semi-icons'; import './GeneralSettings.less'; function formatFromTimestamp(ts) { - const date = new Date(ts); - return `${date.getHours()}:${date.getMinutes() > 9 ? date.getMinutes() : '0' + date.getMinutes()}`; + const date = new Date(ts); + return `${date.getHours()}:${date.getMinutes() > 9 ? date.getMinutes() : '0' + date.getMinutes()}`; } function formatFromTBackend(time) { - if (time == null || time.length === 0) { - return null; - } - const date = new Date(); - const split = time.split(':'); - date.setHours(split[0]); - date.setMinutes(split[1]); - return date.getTime(); + if (time == null || time.length === 0) { + return null; + } + const date = new Date(); + const split = time.split(':'); + date.setHours(split[0]); + date.setMinutes(split[1]); + return date.getTime(); } const GeneralSettings = function GeneralSettings() { - const dispatch = useDispatch(); - const [loading, setLoading] = React.useState(true); + const dispatch = useDispatch(); + const [loading, setLoading] = React.useState(true); - const settings = useSelector((state) => state.generalSettings.settings); + const settings = useSelector((state) => state.generalSettings.settings); - const [interval, setInterval] = React.useState(''); - const [port, setPort] = React.useState(''); - const [workingHourFrom, setWorkingHourFrom] = React.useState(null); - const [workingHourTo, setWorkingHourTo] = React.useState(null); - const [demoMode, setDemoMode] = React.useState(null); - const [analyticsEnabled, setAnalyticsEnabled] = React.useState(null); + const [interval, setInterval] = React.useState(''); + const [port, setPort] = React.useState(''); + const [workingHourFrom, setWorkingHourFrom] = React.useState(null); + const [workingHourTo, setWorkingHourTo] = React.useState(null); + const [demoMode, setDemoMode] = React.useState(null); + const [analyticsEnabled, setAnalyticsEnabled] = React.useState(null); - React.useEffect(() => { - async function init() { - await dispatch.generalSettings.getGeneralSettings(); - setLoading(false); - } + React.useEffect(() => { + async function init() { + await dispatch.generalSettings.getGeneralSettings(); + setLoading(false); + } - init(); - }, []); + init(); + }, []); - React.useEffect(() => { - async function init() { - setInterval(settings?.interval); - setPort(settings?.port); - setWorkingHourFrom(settings?.workingHours?.from); - setWorkingHourTo(settings?.workingHours?.to); - setAnalyticsEnabled(settings?.analyticsEnabled || false); - setDemoMode(settings?.demoMode || false); - } + React.useEffect(() => { + async function init() { + setInterval(settings?.interval); + setPort(settings?.port); + setWorkingHourFrom(settings?.workingHours?.from); + setWorkingHourTo(settings?.workingHours?.to); + setAnalyticsEnabled(settings?.analyticsEnabled || false); + setDemoMode(settings?.demoMode || false); + } - init(); - }, [settings]); + init(); + }, [settings]); - const nullOrEmpty = (val) => val == null || val.length === 0; + const nullOrEmpty = (val) => val == null || val.length === 0; - const throwMessage = (message, type) => { - if (type === 'error') { - Toast.error(message); - } else { - Toast.success(message); - } - }; + const throwMessage = (message, type) => { + if (type === 'error') { + Toast.error(message); + } else { + Toast.success(message); + } + }; - const onStore = async () => { - if (nullOrEmpty(interval)) { - throwMessage('Interval may not be empty.', 'error'); - return; - } - if (nullOrEmpty(port)) { - throwMessage('Port may not be empty.', 'error'); - return; - } - if ( - (!nullOrEmpty(workingHourFrom) && nullOrEmpty(workingHourTo)) || - (nullOrEmpty(workingHourFrom) && !nullOrEmpty(workingHourTo)) - ) { - throwMessage('Working hours to and from must be set if either to or from has been set before.', 'error'); - return; - } - try { - await xhrPost('/api/admin/generalSettings', { - interval, - port, - workingHours: { - from: workingHourFrom, - to: workingHourTo, - }, - demoMode, - analyticsEnabled - }); - } catch (exception) { - console.error(exception); - if(exception?.json?.message != null){ - throwMessage(exception.json.message, 'error'); - }else { - throwMessage('Error while trying to store settings.', 'error'); - } - return; - } - throwMessage('Settings stored successfully. We will reload your browser in 3 seconds.', 'success'); - setTimeout(()=>{ - location.reload(); - }, 3000); - }; + const onStore = async () => { + if (nullOrEmpty(interval)) { + throwMessage('Interval may not be empty.', 'error'); + return; + } + if (nullOrEmpty(port)) { + throwMessage('Port may not be empty.', 'error'); + return; + } + if ( + (!nullOrEmpty(workingHourFrom) && nullOrEmpty(workingHourTo)) || + (nullOrEmpty(workingHourFrom) && !nullOrEmpty(workingHourTo)) + ) { + throwMessage('Working hours to and from must be set if either to or from has been set before.', 'error'); + return; + } + try { + await xhrPost('/api/admin/generalSettings', { + interval, + port, + workingHours: { + from: workingHourFrom, + to: workingHourTo, + }, + demoMode, + analyticsEnabled, + }); + } catch (exception) { + console.error(exception); + if (exception?.json?.message != null) { + throwMessage(exception.json.message, 'error'); + } else { + throwMessage('Error while trying to store settings.', 'error'); + } + return; + } + throwMessage('Settings stored successfully. We will reload your browser in 3 seconds.', 'success'); + setTimeout(() => { + location.reload(); + }, 3000); + }; - return ( -
- {!loading && ( - - -
- - `${value}`.replace(/\D/g, '')} - onChange={(value) => setInterval(value)} - suffix={'minutes'} - /> - - - - `${value}`.replace(/\D/g, '')} - onChange={(value) => setPort(value)} - /> - - - -
- { - setWorkingHourFrom(val == null ? null : formatFromTimestamp(val)); - }} - /> - { - setWorkingHourTo(val == null ? null : formatFromTimestamp(val)); - }} - /> -
-
- + return ( +
+ {!loading && ( + + +
+ + `${value}`.replace(/\D/g, '')} + onChange={(value) => setInterval(value)} + suffix={'minutes'} + /> + + + + `${value}`.replace(/\D/g, '')} + onChange={(value) => setPort(value)} + /> + + + +
+ { + setWorkingHourFrom(val == null ? null : formatFromTimestamp(val)); + }} + /> + { + setWorkingHourTo(val == null ? null : formatFromTimestamp(val)); + }} + /> +
+
+ - - - Explanation -
- } - style={{marginBottom: '1rem'}} - description={ -
- Analytics are disabled by default. If you choose to enable them, we will begin tracking the following:
-
    -
  • Name of active provider (e.g. Immoscout)
  • -
  • Name of active adapter (e.g. Console)
  • -
  • language
  • -
  • os
  • -
  • node version
  • -
  • arch
  • -
- The data is sent anonymously and helps me understand which providers or adapters are being used the most. In the end it helps me to improve fredy. -
- } - /> + + Explanation
} + style={{ marginBottom: '1rem' }} + description={ +
+ Analytics are disabled by default. If you choose to enable them, we will begin tracking the + following: +
+
    +
  • Name of active provider (e.g. Immoscout)
  • +
  • Name of active adapter (e.g. Console)
  • +
  • language
  • +
  • os
  • +
  • node version
  • +
  • arch
  • +
+ The data is sent anonymously and helps me understand which providers or adapters are being used the + most. In the end it helps me to improve fredy. +
+ } + /> - setAnalyticsEnabled(e.target.checked)} - > Enabled - + setAnalyticsEnabled(e.target.checked)}> + {' '} + Enabled + + - + - + + Explanation
} + style={{ marginBottom: '1rem' }} + description={ +
+ In demo mode, Fredy will not (really) search for any real estates. Fredy is in a lockdown mode. Also + all database files will be set back to the default values at midnight. +
+ } + /> - - - Explanation -
- } - style={{marginBottom: '1rem'}} - description={ -
- In demo mode, Fredy will not (really) search for any real estates. Fredy is in a lockdown mode. Also - all database files will be set back to the default values at midnight. -
- } - /> + setDemoMode(e.target.checked)}> + {' '} + Enabled + + - setDemoMode(e.target.checked)} - > Enabled - - - - - - - - - )} - - ); + + + + + )} + + ); }; export default GeneralSettings; diff --git a/ui/src/views/jobs/ProcessingTimes.jsx b/ui/src/views/jobs/ProcessingTimes.jsx index f67ffa8..a02acc5 100644 --- a/ui/src/views/jobs/ProcessingTimes.jsx +++ b/ui/src/views/jobs/ProcessingTimes.jsx @@ -1,32 +1,32 @@ import React from 'react'; -import {format} from '../../services/time/timeService'; -import {Descriptions} from '@douyinfe/semi-ui'; +import { format } from '../../services/time/timeService'; +import { Descriptions } from '@douyinfe/semi-ui'; -export default function ProcessingTimes({processingTimes = {}}) { - if (Object.keys(processingTimes).length === 0) { - return null; - } - return ( - <> - - {processingTimes.interval} min - {processingTimes.lastRun && ( - <> - {format(processingTimes.lastRun)} - - {format(processingTimes.lastRun + processingTimes.interval * 60000)} - - - )} - - - ); +export default function ProcessingTimes({ processingTimes = {} }) { + if (Object.keys(processingTimes).length === 0) { + return null; + } + return ( + <> + + {processingTimes.interval} min + {processingTimes.lastRun && ( + <> + {format(processingTimes.lastRun)} + + {format(processingTimes.lastRun + processingTimes.interval * 60000)} + + + )} + + + ); } diff --git a/ui/src/views/jobs/mutation/components/notificationAdapter/NotificationAdapterMutator.jsx b/ui/src/views/jobs/mutation/components/notificationAdapter/NotificationAdapterMutator.jsx index 70db99b..ae43d71 100644 --- a/ui/src/views/jobs/mutation/components/notificationAdapter/NotificationAdapterMutator.jsx +++ b/ui/src/views/jobs/mutation/components/notificationAdapter/NotificationAdapterMutator.jsx @@ -27,7 +27,7 @@ const validate = (selectedAdapter) => { } if (uiElement.type === 'number') { const numberValue = parseFloat(uiElement.value); - if(isNaN(numberValue) || numberValue < 0) { + if (isNaN(numberValue) || numberValue < 0) { results.push('A number field cannot contain anything else and must be > 0.'); continue; } @@ -83,7 +83,7 @@ export default function NotificationAdapterMutator({ id: selectedAdapter.id, name: selectedAdapter.name, fields: selectedAdapter.fields || {}, - }) + }), ); setSelectedAdapter(null); @@ -114,7 +114,7 @@ export default function NotificationAdapterMutator({ setSuccessMessage('It seems like it worked! Please check your service.'); }) .catch((error) => - setValidationMessage(`This did not work :-( I've received the following error: ${error.json.message}`) + setValidationMessage(`This did not work :-( I've received the following error: ${error.json.message}`), ); }; @@ -229,7 +229,7 @@ export default function NotificationAdapterMutator({ .filter((option) => editNotificationAdapter != null ? true - : selected.find((selectedOption) => selectedOption.id === option.key) == null + : selected.find((selectedOption) => selectedOption.id === option.key) == null, ) .sort(sortAdapter)} onChange={(value) => { diff --git a/ui/src/views/jobs/mutation/components/provider/ProviderMutator.jsx b/ui/src/views/jobs/mutation/components/provider/ProviderMutator.jsx index 3e78c50..e7990f3 100644 --- a/ui/src/views/jobs/mutation/components/provider/ProviderMutator.jsx +++ b/ui/src/views/jobs/mutation/components/provider/ProviderMutator.jsx @@ -45,7 +45,7 @@ export default function ProviderMutator({ onVisibilityChanged, visible = false, url: providerUrl, id: selectedProvider.id, name: selectedProvider.name, - }) + }), ); setProviderUrl(null); setSelectedProvider(null); diff --git a/ui/src/views/login/Login.jsx b/ui/src/views/login/Login.jsx index 4c49c53..94984f9 100644 --- a/ui/src/views/login/Login.jsx +++ b/ui/src/views/login/Login.jsx @@ -1,102 +1,105 @@ -import React, {useEffect} from 'react'; +import React, { useEffect } from 'react'; import cityBackground from '../../assets/city_background.jpg'; import Logo from '../../components/logo/Logo'; -import {xhrPost} from '../../services/xhr'; -import {useHistory} from 'react-router'; -import {useDispatch, useSelector} from 'react-redux'; -import {Input, Button, Banner} from '@douyinfe/semi-ui'; +import { xhrPost } from '../../services/xhr'; +import { useHistory } from 'react-router'; +import { useDispatch, useSelector } from 'react-redux'; +import { Input, Button, Banner } from '@douyinfe/semi-ui'; import './login.less'; -import {IconUser, IconLock} from '@douyinfe/semi-icons'; +import { IconUser, IconLock } from '@douyinfe/semi-icons'; export default function Login() { - const dispatch = useDispatch(); - const [username, setUserName] = React.useState(''); - const [password, setPassword] = React.useState(''); - const [error, setError] = React.useState(null); - const demoMode = useSelector((state) => state.demoMode.demoMode || false); - const history = useHistory(); + const dispatch = useDispatch(); + const [username, setUserName] = React.useState(''); + const [password, setPassword] = React.useState(''); + const [error, setError] = React.useState(null); + const demoMode = useSelector((state) => state.demoMode.demoMode || false); + const history = useHistory(); - useEffect(() => { - async function init() { - await dispatch.demoMode.getDemoMode(); - } + useEffect(() => { + async function init() { + await dispatch.demoMode.getDemoMode(); + } - init(); - }, []); + init(); + }, []); - const tryLogin = async () => { - if (username.length === 0 || password.length === 0) { - setError('Username and password are mandatory.'); - return; - } - try { - await xhrPost('/api/login', { - username, - password, - }); - setError(null); - } catch (Exception) { - setError('Login not successful...'); - return; - } - await dispatch.user.getCurrentUser(); - history.push('/jobs'); - }; + const tryLogin = async () => { + if (username.length === 0 || password.length === 0) { + setError('Username and password are mandatory.'); + return; + } + try { + await xhrPost('/api/login', { + username, + password, + }); + setError(null); + } catch (Exception) { + setError('Login not successful...'); + return; + } + await dispatch.user.getCurrentUser(); + history.push('/jobs'); + }; - return ( -
-
- -
-
- {error && } - } - placeholder="Username" - value={username} - showClear - style={{marginTop: error ? '1rem' : '4rem'}} - autoFocus - onChange={(value) => setUserName(value)} - onKeyPress={async (e) => { - if (e.key === 'Enter') { - await tryLogin(); - } - }} - /> + return ( +
+
+ + +
+ {error && } + } + placeholder="Username" + value={username} + showClear + style={{ marginTop: error ? '1rem' : '4rem' }} + autoFocus + onChange={(value) => setUserName(value)} + onKeyPress={async (e) => { + if (e.key === 'Enter') { + await tryLogin(); + } + }} + /> - } - value={password} - placeholder="Password" - style={{marginTop: '2rem'}} - onChange={(value) => setPassword(value)} - onKeyPress={async (e) => { - if (e.key === 'Enter') { - await tryLogin(); - } - }} - /> + } + value={password} + placeholder="Password" + style={{ marginTop: '2rem' }} + onChange={(value) => setPassword(value)} + onKeyPress={async (e) => { + if (e.key === 'Enter') { + await tryLogin(); + } + }} + /> - -
- {demoMode && } -
- + +
+ {demoMode && ( + + )}
- ); + +
+ ); } Login.displayName = 'Login';