Skip to content

Replace runtime imports with static imports #148

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,22 @@ 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 providers from './lib/provider/index.js';

//if db folder does not exist, ensure to create it before loading anything else
if (!fs.existsSync('./db')) {
fs.mkdirSync('./db');
}
const path = './lib/provider';
const provider = fs.readdirSync(path).filter((file) => file.endsWith('.js'));

//assuming interval is always in minutes
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();
}
/* eslint-enable no-console */
const fetchedProvider = await Promise.all(
provider.filter((provider) => provider.endsWith('.js')).map(async (pro) => import(`${path}/${pro}`)),
);

handleDemoUser();

Expand All @@ -42,9 +40,9 @@ setInterval(
.filter((job) => job.enabled)
.forEach((job) => {
job.provider
.filter((p) => fetchedProvider.find((fp) => fp.metaInformation.id === p.id) != null)
.filter((p) => providers.find((fp) => fp.metaInformation.id === p.id) != null)
.forEach(async (prov) => {
const pro = fetchedProvider.find((fp) => fp.metaInformation.id === prov.id);
const pro = providers.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);
Expand Down
14 changes: 5 additions & 9 deletions lib/api/routes/notificationAdapterRouter.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import fs from 'fs';
import restana from 'restana';
import notificationAdapters from '../../notification/adapter/index.js';

const service = restana();
const notificationAdapterRouter = service.newRouter();
const notificationAdapterList = fs.readdirSync('./lib//notification/adapter').filter((file) => file.endsWith('.js'));
const notificationAdapter = await Promise.all(
notificationAdapterList.map(async (pro) => {
return await import(`../../notification/adapter/${pro}`);
})
);

notificationAdapterRouter.post('/try', async (req, res) => {
const { id, fields } = req.body;
const adapter = notificationAdapter.find((adapter) => adapter.config.id === id);
const adapter = notificationAdapters.find((adapter) => adapter.config.id === id);
if (adapter == null) {
res.send(404);
}
Expand Down Expand Up @@ -45,7 +41,7 @@ notificationAdapterRouter.post('/try', async (req, res) => {
}
});
notificationAdapterRouter.get('/', async (req, res) => {
res.body = notificationAdapter.map((adapter) => adapter.config);
res.body = notificationAdapters.map((adapter) => adapter.config);
res.send();
});
export { notificationAdapterRouter };
12 changes: 4 additions & 8 deletions lib/api/routes/providerRouter.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import fs from 'fs';
import restana from 'restana';
import providers from '../../provider/index.js';

const service = restana();
const providerRouter = service.newRouter();
const providerList = fs.readdirSync('./lib/provider').filter((file) => file.endsWith('.js'));
const provider = await Promise.all(
providerList.map(async (pro) => {
return await import(`../../provider/${pro}`);
})
);

providerRouter.get('/', async (req, res) => {
res.body = provider.map((p) => p.metaInformation);
res.body = providers.map((p) => p.metaInformation);
res.send();
});
export { providerRouter };
12 changes: 12 additions & 0 deletions lib/notification/adapter/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as apprise from './apprise.js';
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know I know, this makes things more performant.. BUT, that comes with a price. The reason I did it the way it is today is because ease of use. You can simply add a new file, throw it into the folder and you can be sure it will be read and it is available. You do not need to register it somewhere. This ease of use of course comes with the price of a little bit of performance, but since Fredy is not rly a high performant app, I'm willing to pay the price.

What's your take on this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I understand how there is convenience to importing the files like so I would avoid it here because it requires us implementing loading logic ourselves to a certain degree while all functionality we need is already available natively.

I would avoid implementing anything around runtime-loading application parts, as it's not necessary for the project, we have a set number of providers and adapters. They don't change during runtime, plus there is an existing system for indexing them in directories.

What we have here is a cool idea but highly uncommon and desirable to avoid IMO

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I totally get your point and usually would agree though I still think for this very case, I don't see a real reason of changing this to a way that makes it more complicated for those trying to implement provider. Again I fully see your point and in an app that is resource-critical, I would never do it this way, but for Fredy, I still think this is better.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regardless of performance I don't think our case justifies a custom loading implementation. I would expect anybody implementing a provider capable of adding to the index too and it not being too much of an extra effort

But other than that, I would like for providers and notif. adapters to use their own directory and multiple files. That would mean we would have to further complicate our logic to support this :/ What do you think?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the benefit of having their own folder? Mostly structurally 'cleanup' (less cluttered)?

import * as console from './console.js';
import * as mailJet from './mailJet.js';
import * as mattermost from './mattermost.js';
import * as ntfy from './ntfy.js';
import * as pushover from './pushover.js';
import * as sendGrid from './sendGrid.js';
import * as slack from './slack.js';
import * as sqlite from './sqlite.js';
import * as telegram from './telegram.js';

export default [apprise, console, mailJet, mattermost, ntfy, pushover, sendGrid, slack, sqlite, telegram];
16 changes: 2 additions & 14 deletions lib/notification/notify.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,7 @@
import fs from 'fs';
const path = './adapter';
import notificationAdapters from './adapter/index.js';

/** Read every integration existing in ./adapter **/
const adapter = await Promise.all(
fs
.readdirSync('./lib/notification/adapter')
.filter((file) => file.endsWith('.js'))
.map(async (integPath) => await import(`${path}/${integPath}`)),
);

if (adapter.length === 0) {
throw new Error('Please specify at least one notification provider');
}
const findAdapter = (notificationAdapter) => {
return adapter.find((a) => a.config.id === notificationAdapter.id);
return notificationAdapters.find((a) => a.config.id === notificationAdapter.id);
};
export const send = (serviceName, newListings, notificationConfig, jobKey) => {
//this is not being used in tests, therefore adapter are always set
Expand Down
21 changes: 21 additions & 0 deletions lib/provider/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as einsAImmobilien from './einsAImmobilien.js';
import * as immobilienDe from './immobilienDe.js';
import * as immonet from './immonet.js';
import * as immoscout from './immoscout.js';
import * as immoswp from './immoswp.js';
import * as immowelt from './immowelt.js';
import * as kleinanzeigen from './kleinanzeigen.js';
import * as neubauKompass from './neubauKompass.js';
import * as wgGesucht from './wgGesucht.js';

export default [
einsAImmobilien,
immobilienDe,
immonet,
immoscout,
immoswp,
immowelt,
kleinanzeigen,
neubauKompass,
wgGesucht,
];
10 changes: 3 additions & 7 deletions test/queryStringMutator/queryStringMutator.test.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
import fs from 'fs';
import { expect } from 'chai';
import { readFile } from 'fs/promises';
import mutator from '../../lib/services/queryStringMutator.js';
import queryString from 'query-string';
import providers from '../../lib/provider/index.js';

const data = await readFile(new URL('./testData.json', import.meta.url));

const testData = JSON.parse(data);

let _provider = await Promise.all(
fs.readdirSync('./lib/provider/').map(async (integPath) => await import(`../../lib/provider/${integPath}`)),
);

/**
* Test test might look a bit weird at first, but listen stranger...
* The test might look a bit weird at first, but listen stranger...
* It's not wise to compare 2 urls, as this means all url params must be in the expected order. This is however not
* guaranteed, as params (and their order) are totally variable.
*/
describe('queryStringMutator', () => {
it('should fix all urls', () => {
for (let test of testData) {
const provider = _provider.find((p) => p.metaInformation.id === test.id);
const provider = providers.find((p) => p.metaInformation.id === test.id);
if (provider == null) {
throw new Error(`Cannot find provider for given id: ${test.id}`);
}
Expand Down
Loading