chore: run formatter (#145)

This commit is contained in:
Alexander Roidl
2025-07-23 08:47:26 +02:00
committed by GitHub
parent 314b1818d7
commit cca1463a68
19 changed files with 666 additions and 665 deletions

View File

@@ -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){
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,7 +33,7 @@ handleDemoUser();
setInterval(
(function exec() {
const isDuringWorkingHoursOrNotSet = duringWorkingHoursOrNotSet(config, Date.now());
if(!config.demoMode) {
if (!config.demoMode) {
if (isDuringWorkingHoursOrNotSet) {
track();
config.lastRun = Date.now();
@@ -58,5 +58,5 @@ setInterval(
}
return exec;
})(),
INTERVAL
INTERVAL,
);

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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('?'));

View File

@@ -1,4 +1,4 @@
import utils, {buildHash} from '../utils.js';
import utils, { buildHash } from '../utils.js';
let appliedBlackList = [];
let appliedBlacklistedDistricts = [];
@@ -7,7 +7,7 @@ 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});
return Object.assign(o, { id, size, link });
}
function applyBlacklist(o) {
@@ -47,4 +47,4 @@ export const init = (sourceConfig, blacklist, blacklistedDistricts) => {
appliedBlacklistedDistricts = blacklistedDistricts || [];
appliedBlackList = blacklist || [];
};
export {config};
export { config };

View File

@@ -1,4 +1,4 @@
import utils, {buildHash} from '../utils.js';
import utils, { buildHash } from '../utils.js';
let appliedBlackList = [];
@@ -7,9 +7,11 @@ function nullOrEmpty(val) {
}
function normalize(o) {
const link = nullOrEmpty(o.link) ? 'NO LINK' : `https://www.neubaukompass.de${o.link.substring(o.link.indexOf('/neubau'))}`;
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});
return Object.assign(o, { id, link });
}
function applyBlacklist(o) {
@@ -41,4 +43,4 @@ export const metaInformation = {
baseUrl: 'https://www.neubaukompass.de/',
id: 'neubauKompass',
};
export {config};
export { config };

View File

@@ -1,4 +1,4 @@
import utils, {buildHash} from '../utils.js';
import utils, { buildHash } from '../utils.js';
let appliedBlackList = [];
@@ -40,4 +40,4 @@ export const metaInformation = {
baseUrl: 'https://www.wg-gesucht.de/',
id: 'wgGesucht',
};
export {config};
export { config };

View File

@@ -1,7 +1,7 @@
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()', () => {

View File

@@ -1,7 +1,7 @@
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()', () => {

View File

@@ -1,5 +1,5 @@
import { expect } from 'chai';
import {buildHash} from '../../lib/utils.js';
import { buildHash } from '../../lib/utils.js';
describe('utilsCheck', () => {
describe('#utilsCheck()', () => {

View File

@@ -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,19 +6,19 @@ 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();
@@ -50,8 +50,8 @@ export default function FredyApp() {
const login = () => (
<Switch>
<Route name="Login" path={'/login'} component={Login}/>
<Redirect from="*" to={'/login'}/>
<Route name="Login" path={'/login'} component={Login} />
<Redirect from="*" to={'/login'} />
</Switch>
);
@@ -60,48 +60,50 @@ export default function FredyApp() {
) : (
<div className="app">
<div className="app__container">
<Logout/>
<Logo width={190} white/>
<Menu isAdmin={isAdmin()}/>
<Logout />
<Logo width={190} white />
<Menu isAdmin={isAdmin()} />
{settings.demoMode && (
<>
<Banner fullMode={true}
<Banner
fullMode={true}
type="info"
bordered
closeIcon={null}
description="You're currently viewing the demo version of Fredy. Jobs won't scrape websites, and any changes you make will be reverted at midnight."
/>
<br/>
</>)}
{(settings.analyticsEnabled === null && !settings.demoMode) && <TrackingModal/>}
<br />
</>
)}
{settings.analyticsEnabled === null && !settings.demoMode && <TrackingModal />}
<Switch>
<Route name="Insufficient Permission" path={'/403'} component={InsufficientPermission}/>
<Route name="Create new Job" path={'/jobs/new'} component={JobMutation}/>
<Route name="Edit a Job" path={'/jobs/edit/:jobId'} component={JobMutation}/>
<Route name="Insights into a Job" path={'/jobs/insights/:jobId'} component={JobInsight}/>
<Route name="Job overview" path={'/jobs'} component={Jobs}/>
<Route name="Insufficient Permission" path={'/403'} component={InsufficientPermission} />
<Route name="Create new Job" path={'/jobs/new'} component={JobMutation} />
<Route name="Edit a Job" path={'/jobs/edit/:jobId'} component={JobMutation} />
<Route name="Insights into a Job" path={'/jobs/insights/:jobId'} component={JobInsight} />
<Route name="Job overview" path={'/jobs'} component={Jobs} />
<PermissionAwareRoute
name="Create new User"
path="/users/new"
component={<UserMutator/>}
component={<UserMutator />}
currentUser={currentUser}
/>
<PermissionAwareRoute
name="Edit a user"
path="/users/edit/:userId"
component={<UserMutator/>}
component={<UserMutator />}
currentUser={currentUser}
/>
<PermissionAwareRoute name="Users" path="/users" component={<Users/>} currentUser={currentUser}/>
<PermissionAwareRoute name="Users" path="/users" component={<Users />} currentUser={currentUser} />
<PermissionAwareRoute
name="General Settings"
path="/generalSettings"
component={<GeneralSettings/>}
component={<GeneralSettings />}
currentUser={currentUser}
/>
<Redirect from="/" to={'/jobs'}/>
<Redirect from="/" to={'/jobs'} />
</Switch>
</div>
</div>

View File

@@ -23,5 +23,5 @@ root.render(
<App />
</LocaleProvider>
</HashRouter>
</Provider>
</Provider>,
);

View File

@@ -1,23 +1,24 @@
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
analyticsEnabled,
});
};
export default function TrackingModal() {
if(inDevelopment()){
if (inDevelopment()) {
return null;
}
return <Modal
return (
<Modal
visible={true}
onOk={async () => {
await saveResponse(true);
@@ -32,21 +33,24 @@ export default function TrackingModal() {
okText="Yes! I want to help"
cancelText="No, thanks"
>
<Logo white/>
<Logo white />
<div className="trackingModal__description">
<p>Hey 👋</p>
<p>Fed up with popups? Yeah, me too. But this ones important, and I promise it will only appear once ;)</p>
<p>Fredy is completely free (and will always remain free). If youd like, you can support me by donating
through my GitHub, but theres absolutely no obligation to do so.</p>
<p>However, it would be a huge
help if youd 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.</p>
<p>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.</p>
<p>
Fredy is completely free (and will always remain free). If youd like, you can support me by donating through
my GitHub, but theres absolutely no obligation to do so.
</p>
<p>
However, it would be a huge help if youd 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.
</p>
<p>
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.
</p>
<p>Thanks🤘</p>
</div>
</Modal>;
</Modal>
);
}

View File

@@ -1,14 +1,21 @@
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) {
@@ -97,19 +104,19 @@ const GeneralSettings = function GeneralSettings() {
to: workingHourTo,
},
demoMode,
analyticsEnabled
analyticsEnabled,
});
} catch (exception) {
console.error(exception);
if(exception?.json?.message != null){
if (exception?.json?.message != null) {
throwMessage(exception.json.message, 'error');
}else {
} else {
throwMessage('Error while trying to store settings.', 'error');
}
return;
}
throwMessage('Settings stored successfully. We will reload your browser in 3 seconds.', 'success');
setTimeout(()=>{
setTimeout(() => {
location.reload();
}, 3000);
};
@@ -118,7 +125,7 @@ const GeneralSettings = function GeneralSettings() {
<div>
{!loading && (
<React.Fragment>
<Headline text="General Settings"/>
<Headline text="General Settings" />
<div>
<SegmentPart
name="Interval"
@@ -135,7 +142,7 @@ const GeneralSettings = function GeneralSettings() {
suffix={'minutes'}
/>
</SegmentPart>
<Divider margin="1rem"/>
<Divider margin="1rem" />
<SegmentPart name="Port" helpText="Port on which Fredy is running." Icon={IconSignal}>
<InputNumber
min={0}
@@ -146,7 +153,7 @@ const GeneralSettings = function GeneralSettings() {
onChange={(value) => setPort(value)}
/>
</SegmentPart>
<Divider margin="1rem"/>
<Divider margin="1rem" />
<SegmentPart
name="Working hours"
helpText="During this hours, Fredy will search for new apartments. If nothing is configured, Fredy will search around the clock."
@@ -173,26 +180,20 @@ const GeneralSettings = function GeneralSettings() {
/>
</div>
</SegmentPart>
<Divider margin="1rem"/>
<Divider margin="1rem" />
<SegmentPart
name="Analytics"
helpText="Insights into the usage of Fredy."
Icon={IconLineChartStroked}
>
<SegmentPart name="Analytics" helpText="Insights into the usage of Fredy." Icon={IconLineChartStroked}>
<Banner
fullMode={false}
type="info"
closeIcon={null}
title={
<div style={{fontWeight: 600, fontSize: '14px', lineHeight: '20px'}}>
Explanation
</div>
}
style={{marginBottom: '1rem'}}
title={<div style={{ fontWeight: 600, fontSize: '14px', lineHeight: '20px' }}>Explanation</div>}
style={{ marginBottom: '1rem' }}
description={
<div>
Analytics are disabled by default. If you choose to enable them, we will begin tracking the following:<br/>
Analytics are disabled by default. If you choose to enable them, we will begin tracking the
following:
<br />
<ul>
<li>Name of active provider (e.g. Immoscout)</li>
<li>Name of active adapter (e.g. Console)</li>
@@ -201,36 +202,27 @@ const GeneralSettings = function GeneralSettings() {
<li>node version</li>
<li>arch</li>
</ul>
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.
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.
</div>
}
/>
<Checkbox
checked={analyticsEnabled}
onChange={(e) => setAnalyticsEnabled(e.target.checked)}
> Enabled
<Checkbox checked={analyticsEnabled} onChange={(e) => setAnalyticsEnabled(e.target.checked)}>
{' '}
Enabled
</Checkbox>
</SegmentPart>
<Divider margin="1rem"/>
<Divider margin="1rem" />
<SegmentPart
name="Demo Mode"
helpText="If enabled, Fredy runs in demo mode."
Icon={IconSearch}
>
<SegmentPart name="Demo Mode" helpText="If enabled, Fredy runs in demo mode." Icon={IconSearch}>
<Banner
fullMode={false}
type="info"
closeIcon={null}
title={
<div style={{fontWeight: 600, fontSize: '14px', lineHeight: '20px'}}>
Explanation
</div>
}
style={{marginBottom: '1rem'}}
title={<div style={{ fontWeight: 600, fontSize: '14px', lineHeight: '20px' }}>Explanation</div>}
style={{ marginBottom: '1rem' }}
description={
<div>
In demo mode, Fredy will not (really) search for any real estates. Fredy is in a lockdown mode. Also
@@ -239,16 +231,14 @@ const GeneralSettings = function GeneralSettings() {
}
/>
<Checkbox
checked={demoMode}
onChange={(e) => setDemoMode(e.target.checked)}
> Enabled
<Checkbox checked={demoMode} onChange={(e) => setDemoMode(e.target.checked)}>
{' '}
Enabled
</Checkbox>
</SegmentPart>
<Divider margin="1rem"/>
<Button type="primary" theme="solid" onClick={onStore} icon={<IconSave/>}>
<Divider margin="1rem" />
<Button type="primary" theme="solid" onClick={onStore} icon={<IconSave />}>
Save
</Button>
</div>

View File

@@ -1,8 +1,8 @@
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 = {}}) {
export default function ProcessingTimes({ processingTimes = {} }) {
if (Object.keys(processingTimes).length === 0) {
return null;
}

View File

@@ -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) => {

View File

@@ -45,7 +45,7 @@ export default function ProviderMutator({ onVisibilityChanged, visible = false,
url: providerUrl,
id: selectedProvider.id,
name: selectedProvider.name,
})
}),
);
setProviderUrl(null);
setSelectedProvider(null);

View File

@@ -1,14 +1,14 @@
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();
@@ -47,18 +47,18 @@ export default function Login() {
return (
<div className="login">
<div className="login__bgImage" style={{background: `url("${cityBackground}")`}}/>
<Logo/>
<div className="login__bgImage" style={{ background: `url("${cityBackground}")` }} />
<Logo />
<form>
<div className="login__loginWrapper">
{error && <Banner type="danger" closeIcon={null} description={error}/>}
{error && <Banner type="danger" closeIcon={null} description={error} />}
<Input
size="large"
prefix={<IconUser/>}
prefix={<IconUser />}
placeholder="Username"
value={username}
showClear
style={{marginTop: error ? '1rem' : '4rem'}}
style={{ marginTop: error ? '1rem' : '4rem' }}
autoFocus
onChange={(value) => setUserName(value)}
onKeyPress={async (e) => {
@@ -71,10 +71,10 @@ export default function Login() {
<Input
size="large"
mode="password"
prefix={<IconLock/>}
prefix={<IconLock />}
value={password}
placeholder="Password"
style={{marginTop: '2rem'}}
style={{ marginTop: '2rem' }}
onChange={(value) => setPassword(value)}
onKeyPress={async (e) => {
if (e.key === 'Enter') {
@@ -83,16 +83,19 @@ export default function Login() {
}}
/>
<Button type="primary" onClick={tryLogin} theme="solid" style={{marginTop: '3rem'}}>
<Button type="primary" onClick={tryLogin} theme="solid" style={{ marginTop: '3rem' }}>
Login
</Button>
<br/>
{demoMode && <Banner fullMode={true}
<br />
{demoMode && (
<Banner
fullMode={true}
type="info"
bordered
closeIcon={null}
description="This is the demo version of Fredy. Use 'demo' as both the username and password to log in."
/>}
/>
)}
</div>
</form>
</div>