From 947e895de65c1988f4f3d6d28fef7edb6a592692 Mon Sep 17 00:00:00 2001 From: orangecoding Date: Fri, 5 Sep 2025 12:07:08 +0200 Subject: [PATCH 01/46] upgrading puppeteer / updating config --- conf/config.json | 2 +- package.json | 3 ++- yarn.lock | 32 ++++++++++++++++---------------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/conf/config.json b/conf/config.json index 3b48654..e8c9521 100755 --- a/conf/config.json +++ b/conf/config.json @@ -1 +1 @@ -{"interval":"60","port":9998,"workingHours":{"from":"","to":""},"demoMode":false,"analyticsEnabled":false} \ No newline at end of file +{"interval":"60","port":9998,"workingHours":{"from":"","to":""},"demoMode":false,"analyticsEnabled":null} \ No newline at end of file diff --git a/package.json b/package.json index b675841..34e0028 100755 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "@rematch/loading": "2.1.2", "@sendgrid/mail": "8.1.5", "@visactor/react-vchart": "^2.0.4", + "@visactor/vchart": "^2.0.4", "@visactor/vchart-semi-theme": "^1.12.2", "@vitejs/plugin-react": "5.0.2", "better-sqlite3": "^12.2.0", @@ -75,7 +76,7 @@ "node-mailjet": "6.0.9", "p-throttle": "^8.0.0", "package-up": "^5.0.0", - "puppeteer": "^24.18.0", + "puppeteer": "^24.19.0", "puppeteer-extra": "^3.3.6", "puppeteer-extra-plugin-stealth": "^2.11.2", "query-string": "9.2.2", diff --git a/yarn.lock b/yarn.lock index 8cadfc4..d11fc3a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1754,7 +1754,7 @@ resolved "https://registry.yarnpkg.com/@visactor/vchart-theme-utils/-/vchart-theme-utils-1.12.2.tgz#bad0035e79dabbe80890bbd6196668551a12c874" integrity sha512-PkgSAivtUZukCWVUGCXxKcbTzI/oMj1Ky22VYcVs/KM4VFmmCywU2xjBBe1du0LUey6CAKB7bMlj5bL2jctG0A== -"@visactor/vchart@2.0.4": +"@visactor/vchart@2.0.4", "@visactor/vchart@^2.0.4": version "2.0.4" resolved "https://registry.yarnpkg.com/@visactor/vchart/-/vchart-2.0.4.tgz#36770240ae6ffd84fa285b7610192f2e06a56299" integrity sha512-/NWBQFYd5A52I8Bkp+iod2LAhBo4cQcxt+xazrmJ/5L17Gk/LdUqCRpnF5dk3XncHb4ls+SRNGkH4kf0rNH2Mg== @@ -2860,10 +2860,10 @@ devlop@^1.0.0, devlop@^1.1.0: dependencies: dequal "^2.0.0" -devtools-protocol@0.0.1475386: - version "0.0.1475386" - resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1475386.tgz#5378401a2c5698ab68c3482c9b7816ff62ec652b" - integrity sha512-RQ809ykTfJ+dgj9bftdeL2vRVxASAuGU+I9LEx9Ij5TXU5HrgAQVmzi72VA+mkzscE12uzlRv5/tWWv9R9J1SA== +devtools-protocol@0.0.1495869: + version "0.0.1495869" + resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1495869.tgz#f68daef77a48d5dcbcdd55dbfa3265a51989c91b" + integrity sha512-i+bkd9UYFis40RcnkW7XrOprCujXRAHg62IVh/Ah3G8MmNXpCGt1m0dTFhSdx/AVs8XEMbdOGRwdkR1Bcta8AA== diff@^7.0.0: version "7.0.0" @@ -5984,15 +5984,15 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== -puppeteer-core@24.18.0: - version "24.18.0" - resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-24.18.0.tgz#444388d619de422f1d325b313942f46fa2925d96" - integrity sha512-As0BvfXxek2MbV0m7iqBmQKFnfSrzSvTM4zGipjd4cL+9f2Ccgut6RvHlc8+qBieKHqCMFy9BSI4QyveoYXTug== +puppeteer-core@24.19.0: + version "24.19.0" + resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-24.19.0.tgz#038f5229b9910f5daf717d5aaff3b63228afbf6c" + integrity sha512-qsEys4OIb2VGC2tNWKAs4U0mnjkIAxueMOOzk2nEFM9g4Y8QuvYkEMtmwsEdvzNGsUFd7DprOQfABmlN7WBOlg== dependencies: "@puppeteer/browsers" "2.10.8" chromium-bidi "8.0.0" debug "^4.4.1" - devtools-protocol "0.0.1475386" + devtools-protocol "0.0.1495869" typed-query-selector "^2.12.0" ws "^8.18.3" @@ -6043,16 +6043,16 @@ puppeteer-extra@^3.3.6: debug "^4.1.1" deepmerge "^4.2.2" -puppeteer@^24.18.0: - version "24.18.0" - resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-24.18.0.tgz#7bb2668b219fed4f23aa98ac1650766f4306b743" - integrity sha512-Ke8oL/87GhzKIM2Ag6Yj49t5xbGc4rspGIuSuFLFCQBtYzWqCSanvqoCu08WkI78rbqcwnHjxiTH6oDlYFrjrw== +puppeteer@^24.19.0: + version "24.19.0" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-24.19.0.tgz#86cef2d1cc45066c9f5ed9edabf93b2d3b206eb3" + integrity sha512-gUWgHX36m9K6yUbvNBEA7CXElIL92yXMoAVFrO8OpZkItqrruLVqYA8ikmfgwcw/cNfYgkt0n2+yP9jd9RSETA== dependencies: "@puppeteer/browsers" "2.10.8" chromium-bidi "8.0.0" cosmiconfig "^9.0.0" - devtools-protocol "0.0.1475386" - puppeteer-core "24.18.0" + devtools-protocol "0.0.1495869" + puppeteer-core "24.19.0" typed-query-selector "^2.12.0" qs@^6.14.0: From bed0843f30b5a484da1abd149a914e832fabfcb8 Mon Sep 17 00:00:00 2001 From: orangecoding Date: Fri, 5 Sep 2025 12:07:35 +0200 Subject: [PATCH 02/46] next release version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 34e0028..83fadf1 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fredy", - "version": "11.5.0", + "version": "11.5.1", "description": "[F]ind [R]eal [E]states [d]amn eas[y].", "scripts": { "prepare": "husky", From 9f8d189f47e80fb388fcb3e868201c5f470f79be Mon Sep 17 00:00:00 2001 From: Christian Kellner Date: Fri, 5 Sep 2025 12:24:16 +0200 Subject: [PATCH 03/46] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 17a37da..ab42c07 100755 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ same listing twice. ![Tests](https://github.com/orangecoding/fredy/actions/workflows/test.yml/badge.svg) [![Docker](https://github.com/orangecoding/fredy/actions/workflows/docker.yml/badge.svg)](https://github.com/orangecoding/fredy/actions/workflows/docker.yml) ![Source](https://github.com/orangecoding/fredy/actions/workflows/check_source.yml/badge.svg) +![Docker Pulls](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fghcr-badge.elias.eu.org%2Fapi%2Forangecoding%2Ffredy%2Ffredy&query=%24.downloadCount&label=Docker%20Pulls) ------------------------------------------------------------------------ From c264e11c26edc47b6785a58619d3dade6e98acbd Mon Sep 17 00:00:00 2001 From: Christian Kellner Date: Fri, 5 Sep 2025 12:32:50 +0200 Subject: [PATCH 04/46] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index ab42c07..12510f8 100755 --- a/README.md +++ b/README.md @@ -42,8 +42,7 @@ If you find it useful, consider supporting the project 💙 [JetBrains](https://jb.gg/OpenSourceSupport) -Fredy is proudly supported by the **JetBrains Open Source Support -Program**. +Fredy is proudly backed by the JetBrains **JetBrains Open Source Support Program**. ------------------------------------------------------------------------ From 79a2d967e89ab44bda70faccf85b1792cc7aad31 Mon Sep 17 00:00:00 2001 From: Christian Kellner Date: Fri, 5 Sep 2025 12:33:12 +0200 Subject: [PATCH 05/46] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 12510f8..a65b705 100755 --- a/README.md +++ b/README.md @@ -40,9 +40,8 @@ same listing twice. I maintain Fredy and other open-source projects in my free time.\ If you find it useful, consider supporting the project 💙 -[JetBrains](https://jb.gg/OpenSourceSupport) - Fredy is proudly backed by the JetBrains **JetBrains Open Source Support Program**. +[JetBrains](https://jb.gg/OpenSourceSupport) ------------------------------------------------------------------------ From a93c7ffee564665f67ce480f96495b02baf5935f Mon Sep 17 00:00:00 2001 From: Christian Kellner Date: Fri, 5 Sep 2025 12:33:28 +0200 Subject: [PATCH 06/46] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a65b705..c114ea1 100755 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ same listing twice. I maintain Fredy and other open-source projects in my free time.\ If you find it useful, consider supporting the project 💙 -Fredy is proudly backed by the JetBrains **JetBrains Open Source Support Program**. +Fredy is proudly backed by the JetBrains **JetBrains Open Source Support Program**. [JetBrains](https://jb.gg/OpenSourceSupport) ------------------------------------------------------------------------ From dda5b5fbcb0ff868fad40c46375c50c2dc26aa61 Mon Sep 17 00:00:00 2001 From: Christian Kellner Date: Fri, 5 Sep 2025 12:34:03 +0200 Subject: [PATCH 07/46] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c114ea1..a4ce1de 100755 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ same listing twice. I maintain Fredy and other open-source projects in my free time.\ If you find it useful, consider supporting the project 💙 -Fredy is proudly backed by the JetBrains **JetBrains Open Source Support Program**. +Fredy is proudly backed by the **JetBrains Open Source Support Program**. [JetBrains](https://jb.gg/OpenSourceSupport) ------------------------------------------------------------------------ From f201090b56e73e3ad93ece7f67e15968d5dccfb0 Mon Sep 17 00:00:00 2001 From: Christian Kellner Date: Fri, 5 Sep 2025 12:35:20 +0200 Subject: [PATCH 08/46] Update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index a4ce1de..d041f0e 100755 --- a/README.md +++ b/README.md @@ -187,9 +187,7 @@ flowchart TD Thanks to everyone who has contributed! -``{=html} -``{=html} -``{=html} + See the [Contributing Guide](https://github.com/orangecoding/fredy/blob/master/CONTRIBUTING.md). From 7fa9a265efc9008052946f6907f77073fa50b76c Mon Sep 17 00:00:00 2001 From: Christian Kellner Date: Sun, 7 Sep 2025 16:46:43 +0200 Subject: [PATCH 09/46] Fixing docker command --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d041f0e..01ff3a7 100755 --- a/README.md +++ b/README.md @@ -49,10 +49,11 @@ Fredy is proudly backed by the **JetBrains Open Source Support Program**. ### With Docker +> [!NOTE] +> In order to start Fredy, you must provide a config.json. As a start, use the one in this repo: https://github.com/orangecoding/fredy/blob/master/conf/config.json + ``` bash -docker pull ghcr.io/orangecoding/fredy:master -docker create --name fredy -v /path/to/your/conf/:/conf -p 9998:9998 fredy/fredy -docker start fredy +docker run -d --name fredy -v fredy_conf:/conf -p 9998:9998 ghcr.io/orangecoding/fredy:master ``` Logs: From 09c6ce1d0b9ad1c6d0b6e360e411eff4fdbd9bee Mon Sep 17 00:00:00 2001 From: orangecoding Date: Sun, 7 Sep 2025 22:15:14 +0200 Subject: [PATCH 10/46] improve similarity cache. It now checks for similarities independend from jobs --- lib/FredyRuntime.js | 6 +- .../similarity-check/SimilarityCacheEntry.js | 26 ---- .../similarity-check/similarityCache.js | 144 +++++++++++++----- package.json | 1 - test/similarity/similarity.test.js | 60 +++----- yarn.lock | 5 - 6 files changed, 138 insertions(+), 104 deletions(-) delete mode 100644 lib/services/similarity-check/SimilarityCacheEntry.js diff --git a/lib/FredyRuntime.js b/lib/FredyRuntime.js index 8037f89..d73494c 100755 --- a/lib/FredyRuntime.js +++ b/lib/FredyRuntime.js @@ -102,15 +102,15 @@ class FredyRuntime { _filterBySimilarListings(listings) { const filteredList = listings.filter((listing) => { - const similar = this._similarityCache.hasSimilarEntries(this._jobKey, listing.title); + const similar = this._similarityCache.hasSimilarEntries(listing.title, listing.address); if (similar) { /* eslint-disable no-console */ - console.debug(`Filtering similar entry for job with id ${this._jobKey} with title: `, listing.title); + console.debug(`Filtering similar entry for title: ${listing.title} and address ${listing.address}`); /* eslint-enable no-console */ } return !similar; }); - filteredList.forEach((filter) => this._similarityCache.addCacheEntry(this._jobKey, filter.title)); + filteredList.forEach((filter) => this._similarityCache.addCacheEntry(filter.title, listings.address)); return filteredList; } diff --git a/lib/services/similarity-check/SimilarityCacheEntry.js b/lib/services/similarity-check/SimilarityCacheEntry.js deleted file mode 100644 index 65b32c7..0000000 --- a/lib/services/similarity-check/SimilarityCacheEntry.js +++ /dev/null @@ -1,26 +0,0 @@ -import stringSimilarity from 'string-similarity'; -//if the score is higher than this, it will be considered a match -const MAX_DICE_INDEX = 0.7; -export default (class SimilarityCacheEntry { - constructor(time) { - this.time = time; - this.values = []; - } - setCacheEntry = (entry) => { - this.values.push(entry); - }; - getTime = () => { - return this.time; - }; - hasSimilarEntries = (value) => { - if (this.values.length > 0) { - for (let i = 0; i < this.values.length; i++) { - const index = stringSimilarity.compareTwoStrings(value, this.values[i]); - if (index >= MAX_DICE_INDEX) { - return true; - } - } - } - return false; - }; -}); diff --git a/lib/services/similarity-check/similarityCache.js b/lib/services/similarity-check/similarityCache.js index 0e94819..f89f8f3 100644 --- a/lib/services/similarity-check/similarityCache.js +++ b/lib/services/similarity-check/similarityCache.js @@ -1,40 +1,116 @@ -import SimilarityCacheEntry from './SimilarityCacheEntry.js'; -import { config } from '../../utils.js'; -//5 minutes -let retention = 5 * 60 * 1000; -const intervalInMs = config.interval * 60 * 1000; -//an interval below 5 mins sounds crazy, but there are ppl out there doing crazy shit. -if (intervalInMs <= retention) { - retention = Math.floor(intervalInMs / 2); -} -//jobid -> SimilarityCacheEntry -const cache = {}; -let intervalId; +import crypto from 'crypto'; + +const retention = 60 * 60 * 1000; /** - * cleanup + * Internal cache storage. + * Maps a SHA-256 hash (string) to its expiry timestamp (number in ms). + * @type {Map} */ -intervalId = setInterval(() => { - const keysToBeRemoved = []; +const entries = new Map(); + +/** + * Reference to the currently scheduled cleanup timer. + * @type {NodeJS.Timeout | null} + */ +let timer = null; + +/** + * Generate a SHA-256 hash from a list of input strings. + * Null or undefined values are ignored. + * + * @param {...(string|null|undefined)} strings - Input values to hash + * @returns {string} Hexadecimal hash + */ +function toHash(...strings) { + return crypto.createHash('sha256').update(strings.filter(Boolean).join('|')).digest('hex'); +} + +/** + * Cleanup expired cache entries and schedule the next cleanup run. + * This function is invoked automatically by scheduled timers. + * + * @private + */ +function runCleanup() { const now = Date.now(); - Object.keys(cache).forEach((key) => { - if (cache[key].getTime() + retention < now) { - keysToBeRemoved.push(key); - } - }); - if (keysToBeRemoved.length > 0) { - keysToBeRemoved.forEach((key) => delete cache[key]); + for (const [hash, expiry] of entries) { + if (expiry <= now) entries.delete(hash); } -}, 10000); -export const addCacheEntry = (jobId, value) => { - cache[jobId] = cache[jobId] || new SimilarityCacheEntry(Date.now()); - cache[jobId].setCacheEntry(value); -}; -export const hasSimilarEntries = (jobId, value) => { - if (cache[jobId] == null) { + scheduleNext(); +} + +/** + * Find the soonest expiry timestamp among all cache entries + * and schedule a one-shot timer that will trigger at that time. + * Cancels any existing timer before scheduling a new one. + * + * @private + */ +function scheduleNext() { + if (timer) { + clearTimeout(timer); + timer = null; + } + let next = Infinity; + const now = Date.now(); + for (const expiry of entries.values()) { + if (expiry > now && expiry < next) next = expiry; + } + if (next !== Infinity) { + timer = setTimeout(runCleanup, Math.max(0, next - now)); + } +} + +/** + * Add or refresh a cache entry for the given title and address. + * The entry will automatically expire after the configured retention window. + * + * @param {string} title - The title used to build the cache key + * @param {string} address - The address used to build the cache key + */ +export function addCacheEntry(title, address) { + const hash = toHash(title, address); + const expiry = Date.now() + retention; + entries.set(hash, expiry); + scheduleNext(); +} + +/** + * Check if a cache entry with the same title and address exists + * and is still valid (not expired). + * + * @param {string} title - The title used to build the cache key + * @param {string} address - The address used to build the cache key + * @returns {boolean} True if a valid cache entry exists, false otherwise + */ +export function hasSimilarEntries(title, address) { + const hash = toHash(title, address); + const expiry = entries.get(hash); + if (expiry == null) return false; + if (expiry <= Date.now()) { + entries.delete(hash); + scheduleNext(); return false; } - return cache[jobId].hasSimilarEntries(value); -}; -export const stopCacheCleanup = () => { - clearInterval(intervalId); -}; + return true; +} + +/** + * Stop any scheduled cleanup timers and prevent further automatic cleanup. + * Entries that are already in the cache will remain until removed manually + * or until cleanup is started again by adding new entries. + */ +export function stopCacheCleanup() { + if (timer) clearTimeout(timer); + timer = null; +} + +/** + * this is only for test purposes + */ +export function invalidateAllForTest() { + for (const key of entries.keys()) { + entries.set(key, 0); + } + runCleanup(); +} diff --git a/package.json b/package.json index 83fadf1..a80389d 100755 --- a/package.json +++ b/package.json @@ -90,7 +90,6 @@ "restana": "5.1.0", "serve-static": "2.2.0", "slack": "11.0.2", - "string-similarity": "^4.0.4", "vite": "7.1.4", "x-var": "^2.1.0" }, diff --git a/test/similarity/similarity.test.js b/test/similarity/similarity.test.js index a345a60..418e43e 100644 --- a/test/similarity/similarity.test.js +++ b/test/similarity/similarity.test.js @@ -1,40 +1,30 @@ -import SimilarityCacheEntry from '../../lib/services/similarity-check/SimilarityCacheEntry.js'; import { expect } from 'chai'; +import * as similarityCache from '../../lib/services/similarity-check/similarityCache.js'; describe('similarityCheck', () => { - describe('#similarityCheck()', () => { - it('should be false', () => { - const check = new SimilarityCacheEntry(0); - check.setCacheEntry('Hallo'); - expect(check.hasSimilarEntries('Welt')).to.be.false; - }); - it('should be true', () => { - const check = new SimilarityCacheEntry(0); - check.setCacheEntry('Hallo'); - expect(check.hasSimilarEntries('hallo')).to.be.true; - }); - it('should be true', () => { - const check = new SimilarityCacheEntry(0); - check.setCacheEntry('Selling an incredible house in san francisco'); - expect(check.hasSimilarEntries('incredible house in san francisco for sale')).to.be.true; - }); - it('should be true', () => { - const check = new SimilarityCacheEntry(0); - check.setCacheEntry('a'); - check.setCacheEntry('b'); - check.setCacheEntry('c'); - check.setCacheEntry('d'); - expect(check.hasSimilarEntries('b')).to.be.true; - }); - it('should be false', () => { - const check = new SimilarityCacheEntry(0); - check.setCacheEntry( - 'The index is known by several other names, especially Sørensen–Dice index,[3] Sørensen index and Dice\'s coefficient. Other variations include the "similarity coefficient" or "index", such as Dice similarity coefficient (DSC). Common alternate spellings for Sørensen are Sorenson, Soerenson and Sörenson, and all three can also be seen with the –sen ending.', - ); - check.setCacheEntry( - 'where |X| and |Y| are the cardinalities of the two sets (i.e. the number of elements in each set). The Sørensen index equals twice the number of elements common to both sets divided by the sum of the number of elements in each set.', - ); - expect(check.hasSimilarEntries('unrelated text')).to.be.false; - }); + it('should return true on duplicate', () => { + similarityCache.addCacheEntry('Hello World', 'Test'); + expect(similarityCache.hasSimilarEntries('Hello World', 'Test')).to.be.true; + }); + + it('should return true even if one value is null', () => { + similarityCache.addCacheEntry('Hello World', null); + expect(similarityCache.hasSimilarEntries('Hello World', null)).to.be.true; + }); + + it('should return true even if one value is an obj', () => { + similarityCache.addCacheEntry('Hello World', [{ TR: 'OLOLO' }]); + expect(similarityCache.hasSimilarEntries('Hello World', [{ TR: 'OLOLO' }])).to.be.true; + }); + + it('should return false when no duplicate', () => { + similarityCache.addCacheEntry('Hello World__', 'Test'); + expect(similarityCache.hasSimilarEntries('Hello World___', 'Test')).to.be.false; + }); + + it('should return false when no duplicate', () => { + expect(similarityCache.hasSimilarEntries('Hello World', 'Test')).to.be.true; + similarityCache.invalidateAllForTest(); + expect(similarityCache.hasSimilarEntries('Hello World', 'Test')).to.be.false; }); }); diff --git a/yarn.lock b/yarn.lock index d11fc3a..cf13a98 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6899,11 +6899,6 @@ string-argv@^0.3.2: resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6" integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== -string-similarity@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-4.0.4.tgz#42d01ab0b34660ea8a018da8f56a3309bb8b2a5b" - integrity sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ== - "string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" From fa76821f7d8906bb0b5a00272c0d552e2f1333c6 Mon Sep 17 00:00:00 2001 From: orangecoding Date: Sun, 7 Sep 2025 22:15:45 +0200 Subject: [PATCH 11/46] next release version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a80389d..d803eb8 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fredy", - "version": "11.5.1", + "version": "11.6.0", "description": "[F]ind [R]eal [E]states [d]amn eas[y].", "scripts": { "prepare": "husky", From 04265eaec79bbc26a702fa50ec071f95e2c84776 Mon Sep 17 00:00:00 2001 From: orangecoding Date: Mon, 8 Sep 2025 08:30:45 +0200 Subject: [PATCH 12/46] making sure scan interval does not go under 5 --- ui/src/views/generalSettings/GeneralSettings.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/views/generalSettings/GeneralSettings.jsx b/ui/src/views/generalSettings/GeneralSettings.jsx index 512d153..f78af7c 100644 --- a/ui/src/views/generalSettings/GeneralSettings.jsx +++ b/ui/src/views/generalSettings/GeneralSettings.jsx @@ -121,11 +121,11 @@ const GeneralSettings = function GeneralSettings() {
Date: Tue, 9 Sep 2025 15:17:36 +0200 Subject: [PATCH 13/46] upgrading dependencies --- package.json | 10 ++-- yarn.lock | 137 +++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 106 insertions(+), 41 deletions(-) diff --git a/package.json b/package.json index d803eb8..a711d70 100755 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "puppeteer": "^24.19.0", "puppeteer-extra": "^3.3.6", "puppeteer-extra-plugin-stealth": "^2.11.2", - "query-string": "9.2.2", + "query-string": "9.3.0", "react": "18.3.1", "react-dom": "18.3.1", "react-redux": "9.2.0", @@ -90,16 +90,16 @@ "restana": "5.1.0", "serve-static": "2.2.0", "slack": "11.0.2", - "vite": "7.1.4", + "vite": "7.1.5", "x-var": "^2.1.0" }, "devDependencies": { - "@babel/core": "7.28.3", - "@babel/eslint-parser": "7.28.0", + "@babel/core": "7.28.4", + "@babel/eslint-parser": "7.28.4", "@babel/preset-env": "7.28.3", "@babel/preset-react": "7.27.1", "chai": "6.0.1", - "eslint": "9.34.0", + "eslint": "9.35.0", "eslint-config-prettier": "10.1.8", "eslint-plugin-react": "7.37.5", "esmock": "2.7.2", diff --git a/yarn.lock b/yarn.lock index cf13a98..33a8f1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -33,7 +33,28 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.0.tgz#9fc6fd58c2a6a15243cd13983224968392070790" integrity sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw== -"@babel/core@7.28.3", "@babel/core@^7.28.3": +"@babel/core@7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.4.tgz#12a550b8794452df4c8b084f95003bce1742d496" + integrity sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.3" + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-module-transforms" "^7.28.3" + "@babel/helpers" "^7.28.4" + "@babel/parser" "^7.28.4" + "@babel/template" "^7.27.2" + "@babel/traverse" "^7.28.4" + "@babel/types" "^7.28.4" + "@jridgewell/remapping" "^2.3.5" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/core@^7.28.3": version "7.28.3" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.3.tgz#aceddde69c5d1def69b839d09efa3e3ff59c97cb" integrity sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ== @@ -54,10 +75,10 @@ json5 "^2.2.3" semver "^6.3.1" -"@babel/eslint-parser@7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.28.0.tgz#c1b3fbba070f5bac32e3d02f244201add4afdd6e" - integrity sha512-N4ntErOlKvcbTt01rr5wj3y55xnIdx1ymrfIr8C2WnM1Y9glFgWaGDEULJIazOX3XM9NRzhfJ6zZnQ1sBNWU+w== +"@babel/eslint-parser@7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.28.4.tgz#80dd86e0aeaae9704411a044db60e1ae6477d93f" + integrity sha512-Aa+yDiH87980jR6zvRfFuCR1+dLb00vBydhTL+zI992Rz/wQhSvuxjmOOuJOgO3XmakO6RykRGD2S1mq1AtgHA== dependencies: "@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1" eslint-visitor-keys "^2.1.0" @@ -225,6 +246,14 @@ "@babel/template" "^7.27.2" "@babel/types" "^7.28.2" +"@babel/helpers@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.4.tgz#fe07274742e95bdf7cf1443593eeb8926ab63827" + integrity sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w== + dependencies: + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.4" + "@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.27.2", "@babel/parser@^7.28.3": version "7.28.3" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.3.tgz#d2d25b814621bca5fe9d172bc93792547e7a2a71" @@ -232,6 +261,13 @@ dependencies: "@babel/types" "^7.28.2" +"@babel/parser@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.4.tgz#da25d4643532890932cc03f7705fe19637e03fa8" + integrity sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg== + dependencies: + "@babel/types" "^7.28.4" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz#61dd8a8e61f7eb568268d1b5f129da3eee364bf9" @@ -873,6 +909,19 @@ "@babel/types" "^7.28.2" debug "^4.3.1" +"@babel/traverse@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.4.tgz#8d456101b96ab175d487249f60680221692b958b" + integrity sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.3" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.4" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.4" + debug "^4.3.1" + "@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.4.4": version "7.28.2" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.2.tgz#da9db0856a9a88e0a13b019881d7513588cf712b" @@ -881,6 +930,14 @@ "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.27.1" +"@babel/types@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.4.tgz#0a4e618f4c60a7cd6c11cb2d48060e4dbe38ac3a" + integrity sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@dnd-kit/accessibility@^3.1.1": version "3.1.1" resolved "https://registry.yarnpkg.com/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz#3b4202bd6bb370a0730f6734867785919beac6af" @@ -1135,10 +1192,10 @@ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz#585624dc829cfb6e7c0aa6c3ca7d7e6daa87e34f" integrity sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ== -"@eslint-community/eslint-utils@^4.2.0": - version "4.7.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz#607084630c6c033992a082de6e6fbc1a8b52175a" - integrity sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw== +"@eslint-community/eslint-utils@^4.8.0": + version "4.9.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz#7308df158e064f0dd8b8fdb58aa14fa2a7f913b3" + integrity sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g== dependencies: eslint-visitor-keys "^3.4.3" @@ -1183,10 +1240,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@9.34.0": - version "9.34.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.34.0.tgz#fc423168b9d10e08dea9088d083788ec6442996b" - integrity sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw== +"@eslint/js@9.35.0": + version "9.35.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.35.0.tgz#ffbc7e13cf1204db18552e9cd9d4a8e17c692d07" + integrity sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw== "@eslint/object-schema@^2.1.6": version "2.1.6" @@ -1249,6 +1306,14 @@ "@jridgewell/sourcemap-codec" "^1.5.0" "@jridgewell/trace-mapping" "^0.3.24" +"@jridgewell/remapping@^2.3.5": + version "2.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" + integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + "@jridgewell/resolve-uri@^3.1.0": version "3.1.2" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" @@ -3273,18 +3338,18 @@ eslint-visitor-keys@^4.2.1: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== -eslint@9.34.0: - version "9.34.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.34.0.tgz#0ea1f2c1b5d1671db8f01aa6b8ce722302016f7b" - integrity sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg== +eslint@9.35.0: + version "9.35.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.35.0.tgz#7a89054b7b9ee1dfd1b62035d8ce75547773f47e" + integrity sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg== dependencies: - "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/eslint-utils" "^4.8.0" "@eslint-community/regexpp" "^4.12.1" "@eslint/config-array" "^0.21.0" "@eslint/config-helpers" "^0.3.1" "@eslint/core" "^0.15.2" "@eslint/eslintrc" "^3.3.1" - "@eslint/js" "9.34.0" + "@eslint/js" "9.35.0" "@eslint/plugin-kit" "^0.3.5" "@humanfs/node" "^0.16.6" "@humanwhocodes/module-importer" "^1.0.1" @@ -3484,7 +3549,7 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" -fdir@^6.4.4, fdir@^6.5.0: +fdir@^6.5.0: version "6.5.0" resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== @@ -5834,7 +5899,7 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -picomatch@^4.0.2, picomatch@^4.0.3: +picomatch@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== @@ -6062,10 +6127,10 @@ qs@^6.14.0: dependencies: side-channel "^1.1.0" -query-string@9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-9.2.2.tgz#a0104824edfdd2c1db2f18af71cef7abf6a3b20f" - integrity sha512-pDSIZJ9sFuOp6VnD+5IkakSVf+rICAuuU88Hcsr6AKL0QtxSIfVuKiVP2oahFI7tk3CRSexwV+Ya6MOoTxzg9g== +query-string@9.3.0: + version "9.3.0" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-9.3.0.tgz#f2d60d6b4442cb445f374b5ff749b937b2cccd03" + integrity sha512-IQHOQ9aauHAApwAaUYifpEyLHv6fpVGVkMOnwPzcDScLjbLj8tLsILn6unSW79NafOw1llh8oK7Gd0VwmXBFmA== dependencies: decode-uri-component "^0.4.1" filter-obj "^5.1.0" @@ -7143,13 +7208,13 @@ tiny-json-http@^7.0.2: resolved "https://registry.yarnpkg.com/tiny-json-http/-/tiny-json-http-7.5.1.tgz#82efaa190c3edf6f5f2d906a9e88f792d38f8532" integrity sha512-lB7qkBGpL3HR/8gidBu3MMfgfnDj2mlvK/eYXgSbO06gKphemLKGp/TgRTy/BKVD7nCbgIeCm41lMNayXO1f2w== -tinyglobby@^0.2.14: - version "0.2.14" - resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.14.tgz#5280b0cf3f972b050e74ae88406c0a6a58f4079d" - integrity sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ== +tinyglobby@^0.2.15: + version "0.2.15" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" + integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== dependencies: - fdir "^6.4.4" - picomatch "^4.0.2" + fdir "^6.5.0" + picomatch "^4.0.3" to-regex-range@^5.0.1: version "5.0.1" @@ -7464,17 +7529,17 @@ vfile@^6.0.0: "@types/unist" "^3.0.0" vfile-message "^4.0.0" -vite@7.1.4: - version "7.1.4" - resolved "https://registry.yarnpkg.com/vite/-/vite-7.1.4.tgz#354944affb55e1aff0157406b74e0d0a3232df9a" - integrity sha512-X5QFK4SGynAeeIt+A7ZWnApdUyHYm+pzv/8/A57LqSGcI88U6R6ipOs3uCesdc6yl7nl+zNO0t8LmqAdXcQihw== +vite@7.1.5: + version "7.1.5" + resolved "https://registry.yarnpkg.com/vite/-/vite-7.1.5.tgz#4dbcb48c6313116689be540466fc80faa377be38" + integrity sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ== dependencies: esbuild "^0.25.0" fdir "^6.5.0" picomatch "^4.0.3" postcss "^8.5.6" rollup "^4.43.0" - tinyglobby "^0.2.14" + tinyglobby "^0.2.15" optionalDependencies: fsevents "~2.3.3" From 7372e5313fb66a08b8ac7a067813c40379fb40e3 Mon Sep 17 00:00:00 2001 From: orangecoding Date: Tue, 9 Sep 2025 18:41:14 +0200 Subject: [PATCH 14/46] creating config automagically if missing --- conf/config.json | 0 lib/utils.js | 25 ++++++++++++++++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) mode change 100755 => 100644 conf/config.json diff --git a/conf/config.json b/conf/config.json old mode 100755 new mode 100644 diff --git a/lib/utils.js b/lib/utils.js index 54d7715..3f180c7 100755 --- a/lib/utils.js +++ b/lib/utils.js @@ -3,6 +3,12 @@ import { fileURLToPath } from 'node:url'; import { readFile } from 'fs/promises'; import { createHash } from 'crypto'; import { DEFAULT_CONFIG } from './defaultConfig.js'; +import fs from 'fs'; + +const RE_GT = />/g; +const RE_WEBP = /\/format\/webp/gi; +const RE_EXT = /\.(jpe?g|png|gif)(\?.*)?$/i; +const HTTPS_PREFIX = 'https://'; function inDevMode() { return process.env.NODE_ENV == null || process.env.NODE_ENV !== 'production'; @@ -53,11 +59,14 @@ function buildHash(...inputs) { } let config = {}; + export async function readConfigFromStorage() { return JSON.parse(await readFile(new URL('../conf/config.json', import.meta.url))); } export async function refreshConfig() { + checkIfConfigExistsAndWriteIfNot(); + try { config = await readConfigFromStorage(); //backwards compatability... @@ -65,14 +74,20 @@ export async function refreshConfig() { config.demoMode ??= false; } catch (error) { config = { ...DEFAULT_CONFIG }; - console.error('Error reading config file', error); + /* eslint-disable no-console */ + console.info('Error reading config file.', error); } } -const RE_GT = />/g; -const RE_WEBP = /\/format\/webp/gi; -const RE_EXT = /\.(jpe?g|png|gif)(\?.*)?$/i; -const HTTPS_PREFIX = 'https://'; +/** + * If the config file does not exist, we will create it. + */ +const checkIfConfigExistsAndWriteIfNot = () => { + if (!fs.existsSync(`${getDirName()}/../conf/config.json`)) { + console.info('Could not find config file. Will create one with default values now'); + fs.writeFileSync(`${getDirName()}/../conf/config.json`, JSON.stringify({ ...DEFAULT_CONFIG })); + } +}; const normalizeImageUrl = (url) => { if (typeof url !== 'string' || url.length === 0) return null; From 4ad2895eecb827e6fdd91e7efaba2997c5d85200 Mon Sep 17 00:00:00 2001 From: Christian Kellner Date: Wed, 10 Sep 2025 11:31:49 +0200 Subject: [PATCH 15/46] Update docker command --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 01ff3a7..93578e5 100755 --- a/README.md +++ b/README.md @@ -53,7 +53,11 @@ Fredy is proudly backed by the **JetBrains Open Source Support Program**. > In order to start Fredy, you must provide a config.json. As a start, use the one in this repo: https://github.com/orangecoding/fredy/blob/master/conf/config.json ``` bash -docker run -d --name fredy -v fredy_conf:/conf -p 9998:9998 ghcr.io/orangecoding/fredy:master +docker run -d --name fredy \ + -v fredy_conf:/conf \ + -v fredy_db:/db \ + -p 9998:9998 \ + ghcr.io/orangecoding/fredy:master ``` Logs: From 05c2df917cc82f206e2b9c7d8b3eb2aee585a964 Mon Sep 17 00:00:00 2001 From: Christian Kellner Date: Fri, 12 Sep 2025 13:00:43 +0200 Subject: [PATCH 16/46] Adding link to fredy demo --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 93578e5..63f9ac3 100755 --- a/README.md +++ b/README.md @@ -45,6 +45,11 @@ Fredy is proudly backed by the **JetBrains Open Source Support Program**. ------------------------------------------------------------------------ +## 👨‍🏫 Demo +You can try out Fredy here: [Fredy Demo](https://fredy-demo.orange-coding.net/) + +------------------------------------------------------------------------ + ## 🚀 Quick Start ### With Docker From 85cea66051a7e53425b29f94f43fd6737a50d488 Mon Sep 17 00:00:00 2001 From: orangecoding Date: Fri, 12 Sep 2025 13:38:53 +0200 Subject: [PATCH 17/46] improving tracking. now using internal tracking --- README.md | 2 +- lib/api/routes/jobRouter.js | 6 -- lib/api/routes/loginRoute.js | 2 +- lib/services/tracking/Tracker.js | 90 +++++++++++--------- package.json | 3 +- ui/src/components/tracking/TrackingModal.jsx | 3 +- yarn.lock | 22 ----- 7 files changed, 56 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index 01ff3a7..b43ecf8 100755 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ Immoscout has implemented advanced bot detection. In order to work around this, Fredy is completely free (and will always remain free). However, it would be a huge help if you’d allow me to collect some analytical data. Before you freak out, let me explain... -If you agree, Fredy will send a ping to my Mixpanel project each time it runs. +If you agree, Fredy will send a ping once every 6 hours to my internal tracking project (Will be open sourced soon). 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/lib/api/routes/jobRouter.js b/lib/api/routes/jobRouter.js index 077aa48..f906444 100644 --- a/lib/api/routes/jobRouter.js +++ b/lib/api/routes/jobRouter.js @@ -3,7 +3,6 @@ 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 { trackDemoJobCreated } from '../../services/tracking/Tracker.js'; const service = restana(); const jobRouter = service.newRouter(); function doesJobBelongsToUser(job, req) { @@ -46,11 +45,6 @@ jobRouter.post('/', async (req, res) => { res.send(new Error(error)); console.error(error); } - trackDemoJobCreated({ - name, - provider, - adapter: notificationAdapter, - }); res.send(); }); jobRouter.delete('', async (req, res) => { diff --git a/lib/api/routes/loginRoute.js b/lib/api/routes/loginRoute.js index 0a2626d..061b274 100644 --- a/lib/api/routes/loginRoute.js +++ b/lib/api/routes/loginRoute.js @@ -27,7 +27,7 @@ loginRouter.post('/', async (req, res) => { } if (user.password === hasher.hash(password)) { if (config.demoMode) { - trackDemoAccessed(); + await trackDemoAccessed(); } req.session.currentUser = user.id; diff --git a/lib/services/tracking/Tracker.js b/lib/services/tracking/Tracker.js index 04e6b60..e99ad65 100644 --- a/lib/services/tracking/Tracker.js +++ b/lib/services/tracking/Tracker.js @@ -1,65 +1,77 @@ -import Mixpanel from 'mixpanel'; import { getJobs } from '../storage/jobStorage.js'; import { getUniqueId } from './uniqueId.js'; import { config, inDevMode } from '../../utils.js'; import os from 'os'; import { readFileSync } from 'fs'; import { packageUp } from 'package-up'; +import fetch from 'node-fetch'; -const mixpanelTracker = Mixpanel.init('718670ef1c58c0208256c1e408a3d75e'); -const distinct_id = getUniqueId() || 'N/A'; +const deviceId = getUniqueId() || 'N/A'; const version = await getPackageVersion(); +const FREDY_TRACKING_URL = 'https://fredy.orange-coding.net/tracking'; -export const track = function () { - //only send tracking information if the user allowed to do so. - if (config.analyticsEnabled && !inDevMode()) { - const activeProvider = new Set(); - const activeAdapter = new Set(); +let cached = null; +let lastSent = 0; +const SIX_HOURS = 6 * 3_600_000; - const jobs = getJobs(); +export const track = async () => { + try { + if (config.analyticsEnabled && !inDevMode()) { + const activeProvider = new Set(); + const activeAdapter = new Set(); - if (jobs != null && jobs.length > 0) { - jobs.forEach((job) => { - job.provider.forEach((provider) => { - activeProvider.add(provider.id); + const jobs = getJobs(); + + if (jobs != null && jobs.length > 0) { + jobs.forEach((job) => { + job.provider.forEach((provider) => activeProvider.add(provider.id)); + job.notificationAdapter.forEach((adapter) => activeAdapter.add(adapter.id)); }); - job.notificationAdapter.forEach((adapter) => { - activeAdapter.add(adapter.id); - }); - }); - mixpanelTracker.track( - 'fredy_tracking', - enrichTrackingObject({ + const trackingObj = enrichTrackingObject({ adapter: Array.from(activeAdapter), provider: Array.from(activeProvider), - }), - ); + }); + + const stringify = JSON.stringify(trackingObj); + const now = Date.now(); + + // send if changed OR six hours passed since last send + if (stringify !== cached || now - lastSent >= SIX_HOURS) { + await fetch(`${FREDY_TRACKING_URL}/main`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: stringify, + }); + cached = stringify; + lastSent = now; + } + } } + } catch (error) { + console.warn('Error sending tracking data', error); } }; /** * Note, this will only be used when Fredy runs in demo mode */ -export function trackDemoJobCreated(jobData) { +export async function trackDemoAccessed() { if (config.analyticsEnabled && !inDevMode() && config.demoMode) { - mixpanelTracker.track('demoJobCreated', enrichTrackingObject(jobData)); - } -} - -/** - * Note, this will only be used when Fredy runs in demo mode - */ -export function trackDemoAccessed() { - if (config.analyticsEnabled && !inDevMode() && config.demoMode) { - mixpanelTracker.track('demoAccessed', enrichTrackingObject({})); + try { + await fetch(`${FREDY_TRACKING_URL}/demo/accessed`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + }); + } catch (error) { + console.warn('Error sending tracking data', error); + } } } function enrichTrackingObject(trackingObject) { - const operating_system = os.platform(); - const os_version = os.release(); + const operatingSystem = os.platform(); + const osVersion = os.release(); const arch = process.arch; const language = process.env.LANG || 'en'; const nodeVersion = process.version || 'N/A'; @@ -67,13 +79,13 @@ function enrichTrackingObject(trackingObject) { return { ...trackingObject, isDemo: config.demoMode, - operating_system, - os_version, + operatingSystem, + osVersion, arch, nodeVersion, language, - distinct_id, - fredy_version: version, + deviceId, + version, }; } diff --git a/package.json b/package.json index a711d70..ff92ef8 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fredy", - "version": "11.6.0", + "version": "11.6.1", "description": "[F]ind [R]eal [E]states [d]amn eas[y].", "scripts": { "prepare": "husky", @@ -70,7 +70,6 @@ "lodash": "4.17.21", "lowdb": "7.0.1", "markdown": "^0.5.0", - "mixpanel": "^0.18.1", "nanoid": "5.1.5", "node-fetch": "3.3.2", "node-mailjet": "6.0.9", diff --git a/ui/src/components/tracking/TrackingModal.jsx b/ui/src/components/tracking/TrackingModal.jsx index 80383b5..7ec8460 100644 --- a/ui/src/components/tracking/TrackingModal.jsx +++ b/ui/src/components/tracking/TrackingModal.jsx @@ -43,7 +43,8 @@ export default function TrackingModal() {

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. + "no", let me explain. If you agree, Fredy will send a ping once every 6 hours to my internal tracking project. + (Will be open-sourced soon)

The data includes: names of active adapters/providers, OS, architecture, Node version, and language. The diff --git a/yarn.lock b/yarn.lock index 33a8f1c..58da8d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1979,13 +1979,6 @@ acorn@^8.0.0, acorn@^8.15.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== -agent-base@6: - version "6.0.2" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" - integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== - dependencies: - debug "4" - agent-base@^7.1.0, agent-base@^7.1.2: version "7.1.4" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.4.tgz#e3cd76d4c548ee895d3c3fd8dc1f6c5b9032e7a8" @@ -4056,14 +4049,6 @@ http-proxy-agent@^7.0.0, http-proxy-agent@^7.0.1: agent-base "^7.1.0" debug "^4.3.4" -https-proxy-agent@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" - integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== - dependencies: - agent-base "6" - debug "4" - https-proxy-agent@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9" @@ -5450,13 +5435,6 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" -mixpanel@^0.18.1: - version "0.18.1" - resolved "https://registry.yarnpkg.com/mixpanel/-/mixpanel-0.18.1.tgz#beefdce6c260165f4e2059c8cdd34c5c557162f7" - integrity sha512-YD1xfn6WP6ZLQ6Pmgh0KgdXhueJEsrodThMTsHzHMH0VbWa9ck8s+ynDtM83OSgt+yQ61W/SQNrH8Y4wIwocGg== - dependencies: - https-proxy-agent "5.0.0" - mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" From ac0ea64c07809fd58f904dd1ac271c3c87cfdfb4 Mon Sep 17 00:00:00 2001 From: orangecoding Date: Fri, 12 Sep 2025 13:41:08 +0200 Subject: [PATCH 18/46] remove unnecessary logging --- lib/services/extractor/parser/parser.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/services/extractor/parser/parser.js b/lib/services/extractor/parser/parser.js index be22838..3507250 100644 --- a/lib/services/extractor/parser/parser.js +++ b/lib/services/extractor/parser/parser.js @@ -66,7 +66,9 @@ export function parse(crawlContainer, crawlFields, text, url) { if (parsedObject.id != null) { result.push(parsedObject); } else { - console.warn('ID not found. Not relaying object.'); + /* eslint-disable no-console */ + console.debug('ID not found. Not relaying object.'); + /* eslint-enable no-console */ } }); From edc91291b6af54c69b8e20f84e42684d11584e10 Mon Sep 17 00:00:00 2001 From: orangecoding Date: Fri, 12 Sep 2025 13:45:54 +0200 Subject: [PATCH 19/46] fixing telegram --- lib/notification/adapter/telegram.js | 30 ++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/notification/adapter/telegram.js b/lib/notification/adapter/telegram.js index 15b5289..a33b1ce 100644 --- a/lib/notification/adapter/telegram.js +++ b/lib/notification/adapter/telegram.js @@ -63,31 +63,41 @@ export const send = ({ serviceName, newListings, notificationConfig, jobKey }) = const jobName = job == null ? jobKey : job.name; const throttledCall = getThrottled(chatId, async function (endpoint, body) { - await fetch(`https://api.telegram.org/bot${token}/${endpoint}`, { + const res = await fetch(`https://api.telegram.org/bot${token}/${endpoint}`, { method: 'post', body: JSON.stringify(body), headers: { 'Content-Type': 'application/json' }, }); + return res; }); const promises = newListings.map(async (o) => { const img = normalizeImageUrl(o.image); + const textPayload = { + chat_id: chatId, + text: buildText(jobName, serviceName, o), + parse_mode: 'HTML', + disable_web_page_preview: true, + }; - if (img) { - return throttledCall('sendPhoto', { + if (!img) { + return throttledCall('sendMessage', textPayload); + } + + try { + return await throttledCall('sendPhoto', { chat_id: chatId, photo: img, caption: buildCaption(jobName, serviceName, o), parse_mode: 'HTML', }); + } catch (e) { + // If we see a timeout due to sending an image, try sending it without + if (e && (e.code === 'ETIMEDOUT' || e.errno === 'ETIMEDOUT')) { + return throttledCall('sendMessage', textPayload); + } + throw e; } - - return throttledCall('sendMessage', { - chat_id: chatId, - text: buildText(jobName, serviceName, o), - parse_mode: 'HTML', - disable_web_page_preview: true, - }); }); return Promise.all(promises); From 251de1e42d243275b815f2c0db7842c2743984db Mon Sep 17 00:00:00 2001 From: orangecoding Date: Fri, 12 Sep 2025 13:48:05 +0200 Subject: [PATCH 20/46] next release version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ff92ef8..fb63dde 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fredy", - "version": "11.6.1", + "version": "11.6.2", "description": "[F]ind [R]eal [E]states [d]amn eas[y].", "scripts": { "prepare": "husky", From 5e0405f1ecc25598c53a7422cdf5553454e9e305 Mon Sep 17 00:00:00 2001 From: Christian Kellner Date: Fri, 12 Sep 2025 18:47:10 +0200 Subject: [PATCH 21/46] Update README.md --- README.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2acaf3b..e596330 100755 --- a/README.md +++ b/README.md @@ -1,3 +1,16 @@ +

+ + Expo logo + +

+ + + ![Tests](https://github.com/orangecoding/fredy/actions/workflows/test.yml/badge.svg) +[![Docker](https://github.com/orangecoding/fredy/actions/workflows/docker.yml/badge.svg)](https://github.com/orangecoding/fredy/actions/workflows/docker.yml) +![Source](https://github.com/orangecoding/fredy/actions/workflows/check_source.yml/badge.svg) +![Docker Pulls](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fghcr-badge.elias.eu.org%2Fapi%2Forangecoding%2Ffredy%2Ffredy&query=%24.downloadCount&label=Docker%20Pulls) + + # Fredy 🏡 – Your Self-Hosted Real Estate Finder for Germany Finding an apartment or house in Germany can be stressful and @@ -11,12 +24,7 @@ With a modern architecture, Fredy provides a **clean Web UI**, removes duplicates across platforms, and stores results so you never see the same listing twice. - -![Tests](https://github.com/orangecoding/fredy/actions/workflows/test.yml/badge.svg) -[![Docker](https://github.com/orangecoding/fredy/actions/workflows/docker.yml/badge.svg)](https://github.com/orangecoding/fredy/actions/workflows/docker.yml) -![Source](https://github.com/orangecoding/fredy/actions/workflows/check_source.yml/badge.svg) -![Docker Pulls](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fghcr-badge.elias.eu.org%2Fapi%2Forangecoding%2Ffredy%2Ffredy&query=%24.downloadCount&label=Docker%20Pulls) ------------------------------------------------------------------------ From d66dc2cd93a51535d6760f7207487f32c4138b55 Mon Sep 17 00:00:00 2001 From: orangecoding Date: Sat, 13 Sep 2025 17:06:18 +0200 Subject: [PATCH 22/46] improve tracking --- index.js | 4 ++-- lib/services/tracking/Tracker-Cron.js | 17 +++++++++++++++++ lib/services/tracking/Tracker.js | 24 ++++++------------------ package.json | 3 ++- yarn.lock | 5 +++++ 5 files changed, 32 insertions(+), 21 deletions(-) create mode 100644 lib/services/tracking/Tracker-Cron.js diff --git a/index.js b/index.js index c08652f..6d833b4 100755 --- a/index.js +++ b/index.js @@ -6,9 +6,9 @@ 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 { initTrackerCron } from './lib/services/tracking/Tracker-Cron.js'; //if db folder does not exist, ensure to create it before loading anything else if (!fs.existsSync('./db')) { fs.mkdirSync('./db'); @@ -29,13 +29,13 @@ const fetchedProvider = await Promise.all( ); handleDemoUser(); +await initTrackerCron(); setInterval( (function exec() { const isDuringWorkingHoursOrNotSet = duringWorkingHoursOrNotSet(config, Date.now()); if (!config.demoMode) { if (isDuringWorkingHoursOrNotSet) { - track(); config.lastRun = Date.now(); jobStorage .getJobs() diff --git a/lib/services/tracking/Tracker-Cron.js b/lib/services/tracking/Tracker-Cron.js new file mode 100644 index 0000000..9b89438 --- /dev/null +++ b/lib/services/tracking/Tracker-Cron.js @@ -0,0 +1,17 @@ +import cron from 'node-cron'; +import { config, inDevMode } from '../../utils.js'; +import { trackMainEvent } from './Tracker.js'; + +async function runTask() { + //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()) { + await trackMainEvent(); + } +} + +export async function initTrackerCron() { + //run directly on start + await runTask(); + // then every 6 hours + cron.schedule('0 */6 * * *', runTask); +} diff --git a/lib/services/tracking/Tracker.js b/lib/services/tracking/Tracker.js index e99ad65..0e76b18 100644 --- a/lib/services/tracking/Tracker.js +++ b/lib/services/tracking/Tracker.js @@ -10,11 +10,7 @@ const deviceId = getUniqueId() || 'N/A'; const version = await getPackageVersion(); const FREDY_TRACKING_URL = 'https://fredy.orange-coding.net/tracking'; -let cached = null; -let lastSent = 0; -const SIX_HOURS = 6 * 3_600_000; - -export const track = async () => { +export const trackMainEvent = async () => { try { if (config.analyticsEnabled && !inDevMode()) { const activeProvider = new Set(); @@ -33,19 +29,11 @@ export const track = async () => { provider: Array.from(activeProvider), }); - const stringify = JSON.stringify(trackingObj); - const now = Date.now(); - - // send if changed OR six hours passed since last send - if (stringify !== cached || now - lastSent >= SIX_HOURS) { - await fetch(`${FREDY_TRACKING_URL}/main`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: stringify, - }); - cached = stringify; - lastSent = now; - } + await fetch(`${FREDY_TRACKING_URL}/main`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(trackingObj), + }); } } } catch (error) { diff --git a/package.json b/package.json index fb63dde..84916cc 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fredy", - "version": "11.6.2", + "version": "11.6.3", "description": "[F]ind [R]eal [E]states [d]amn eas[y].", "scripts": { "prepare": "husky", @@ -71,6 +71,7 @@ "lowdb": "7.0.1", "markdown": "^0.5.0", "nanoid": "5.1.5", + "node-cron": "^4.2.1", "node-fetch": "3.3.2", "node-mailjet": "6.0.9", "p-throttle": "^8.0.0", diff --git a/yarn.lock b/yarn.lock index 58da8d9..d948e4e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5521,6 +5521,11 @@ node-abi@^3.3.0: dependencies: semver "^7.3.5" +node-cron@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/node-cron/-/node-cron-4.2.1.tgz#6979be4aee4702f06322d21220df8de252c8e265" + integrity sha512-lgimEHPE/QDgFlywTd8yTR61ptugX3Qer29efeyWw2rv259HtGBNn1vZVmp8lB9uo9wC0t/AT4iGqXxia+CJFg== + node-domexception@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" From 21415dcff368e9827b6ebed20569a3aa5ae3b4a7 Mon Sep 17 00:00:00 2001 From: orangecoding Date: Sat, 13 Sep 2025 18:57:56 +0200 Subject: [PATCH 23/46] using winston logger --- index.js | 11 +- lib/FredyRuntime.js | 9 +- lib/api/api.js | 4 +- lib/api/routes/generalSettingsRoute.js | 3 +- lib/api/routes/jobRouter.js | 7 +- lib/api/routes/loginRoute.js | 3 +- lib/notification/adapter/mailJet.js | 5 +- lib/provider/immoscout.js | 3 +- lib/services/demoCleanup.js | 3 +- lib/services/extractor/extractor.js | 3 +- lib/services/extractor/parser/parser.js | 15 +- lib/services/extractor/puppeteerExtractor.js | 5 +- lib/services/extractor/utils.js | 6 +- lib/services/logger.js | 15 ++ lib/services/storage/jobStorage.js | 5 +- lib/services/tracking/Tracker.js | 7 +- lib/utils.js | 6 +- package.json | 3 +- yarn.lock | 166 ++++++++++++++++++- 19 files changed, 230 insertions(+), 49 deletions(-) create mode 100644 lib/services/logger.js diff --git a/index.js b/index.js index 6d833b4..446cd18 100755 --- a/index.js +++ b/index.js @@ -9,6 +9,7 @@ import './lib/api/api.js'; import { handleDemoUser } from './lib/services/storage/userStorage.js'; import { cleanupDemoAtMidnight } from './lib/services/demoCleanup.js'; import { initTrackerCron } from './lib/services/tracking/Tracker-Cron.js'; +import logger from './lib/services/logger.js'; //if db folder does not exist, ensure to create it before loading anything else if (!fs.existsSync('./db')) { fs.mkdirSync('./db'); @@ -17,13 +18,11 @@ 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}`); +logger.info(`Started Fredy successfully. Ui can be accessed via http://localhost:${config.port}`); if (config.demoMode) { - console.info('Running in demo mode'); + logger.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}`)), ); @@ -51,9 +50,7 @@ setInterval( }); }); } else { - /* eslint-disable no-console */ - console.debug('Working hours set. Skipping as outside of working hours.'); - /* eslint-enable no-console */ + logger.debug('Working hours set. Skipping as outside of working hours.'); } } return exec; diff --git a/lib/FredyRuntime.js b/lib/FredyRuntime.js index d73494c..5097199 100755 --- a/lib/FredyRuntime.js +++ b/lib/FredyRuntime.js @@ -3,6 +3,7 @@ import { setKnownListings, getKnownListings } from './services/storage/listingsS import * as notify from './notification/notify.js'; import Extractor from './services/extractor/extractor.js'; import urlModifier from './services/queryStringMutator.js'; +import logger from './services/logger.js'; class FredyRuntime { /** @@ -59,7 +60,7 @@ class FredyRuntime { }) .catch((err) => { reject(err); - console.error(err); + logger.error(err); }); }); } @@ -104,9 +105,7 @@ class FredyRuntime { const filteredList = listings.filter((listing) => { const similar = this._similarityCache.hasSimilarEntries(listing.title, listing.address); if (similar) { - /* eslint-disable no-console */ - console.debug(`Filtering similar entry for title: ${listing.title} and address ${listing.address}`); - /* eslint-enable no-console */ + logger.debug(`Filtering similar entry for title: ${listing.title} and address ${listing.address}`); } return !similar; }); @@ -115,7 +114,7 @@ class FredyRuntime { } _handleError(err) { - if (err.name !== 'NoNewListingsWarning') console.error(err); + if (err.name !== 'NoNewListingsWarning') logger.error(err); } } diff --git a/lib/api/api.js b/lib/api/api.js index ca2cb8f..194855c 100644 --- a/lib/api/api.js +++ b/lib/api/api.js @@ -13,6 +13,7 @@ import files from 'serve-static'; import path from 'path'; import { getDirName } from '../utils.js'; import { demoRouter } from './routes/demoRouter.js'; +import logger from '../services/logger.js'; const service = restana(); const staticService = files(path.join(getDirName(), '../ui/public')); const PORT = config.port || 9998; @@ -34,7 +35,6 @@ service.use('/api/login', loginRouter); //this route is unsecured intentionally as it is being queried from the login page service.use('/api/demo', demoRouter); -/* eslint-disable no-console */ service.start(PORT).then(() => { - console.info(`Started API service on port ${PORT}`); + logger.debug(`Started API service on port ${PORT}`); }); diff --git a/lib/api/routes/generalSettingsRoute.js b/lib/api/routes/generalSettingsRoute.js index f6acbde..bff4eac 100644 --- a/lib/api/routes/generalSettingsRoute.js +++ b/lib/api/routes/generalSettingsRoute.js @@ -2,6 +2,7 @@ import restana from 'restana'; import { config, getDirName, readConfigFromStorage, refreshConfig } from '../../utils.js'; import fs from 'fs'; import { handleDemoUser } from '../../services/storage/userStorage.js'; +import logger from '../../services/logger.js'; const service = restana(); const generalSettingsRouter = service.newRouter(); generalSettingsRouter.get('/', async (req, res) => { @@ -20,7 +21,7 @@ generalSettingsRouter.post('/', async (req, res) => { await refreshConfig(); handleDemoUser(); } catch (err) { - console.error(err); + logger.error(err); res.send(new Error('Error while trying to write settings.')); return; } diff --git a/lib/api/routes/jobRouter.js b/lib/api/routes/jobRouter.js index f906444..5d47334 100644 --- a/lib/api/routes/jobRouter.js +++ b/lib/api/routes/jobRouter.js @@ -3,6 +3,7 @@ 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'; const service = restana(); const jobRouter = service.newRouter(); function doesJobBelongsToUser(job, req) { @@ -43,7 +44,7 @@ jobRouter.post('/', async (req, res) => { }); } catch (error) { res.send(new Error(error)); - console.error(error); + logger.error(error); } res.send(); }); @@ -58,7 +59,7 @@ jobRouter.delete('', async (req, res) => { } } catch (error) { res.send(new Error(error)); - console.error(error); + logger.error(error); } res.send(); }); @@ -77,7 +78,7 @@ jobRouter.put('/:jobId/status', async (req, res) => { } } catch (error) { res.send(new Error(error)); - console.error(error); + logger.error(error); } res.send(); }); diff --git a/lib/api/routes/loginRoute.js b/lib/api/routes/loginRoute.js index 061b274..1eb28cc 100644 --- a/lib/api/routes/loginRoute.js +++ b/lib/api/routes/loginRoute.js @@ -3,6 +3,7 @@ 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'; const service = restana(); const loginRouter = service.newRouter(); loginRouter.get('/user', async (req, res) => { @@ -35,7 +36,7 @@ loginRouter.post('/', async (req, res) => { res.send(200); return; } else { - console.error(`User ${username} tried to login, but password was wrong.`); + logger.error(`User ${username} tried to login, but password was wrong.`); } res.send(401); }); diff --git a/lib/notification/adapter/mailJet.js b/lib/notification/adapter/mailJet.js index 826e913..7c57845 100755 --- a/lib/notification/adapter/mailJet.js +++ b/lib/notification/adapter/mailJet.js @@ -5,6 +5,7 @@ import Handlebars from 'handlebars'; import fetch from 'node-fetch'; import { markdown2Html } from '../../services/markdown.js'; import { getDirName, normalizeImageUrl } from '../../utils.js'; +import logger from '../../services/logger.js'; const __dirname = getDirName(); const template = fs.readFileSync(path.resolve(__dirname + '/notification/emailTemplate/template.hbs'), 'utf8'); @@ -24,7 +25,7 @@ const toBase64 = async (url) => { const ab = await res.arrayBuffer(); return Buffer.from(ab).toString('base64'); } catch (error) { - console.error(`Error fetching image from ${url}:`, error.message); + logger.error(`Error fetching image from ${url}:`, error.message); throw error; } }; @@ -62,7 +63,7 @@ const mapListingsWithCid = async (serviceName, jobKey, listings) => { item.hasImage = true; item.imageCid = cid; } catch (error) { - console.warn(`Skipping image for listing ${i} due to error: ${error.message}`); + logger.warn(`Skipping image for listing ${i} due to error: ${error.message}`); } } diff --git a/lib/provider/immoscout.js b/lib/provider/immoscout.js index 06a4b39..9dac88b 100644 --- a/lib/provider/immoscout.js +++ b/lib/provider/immoscout.js @@ -37,6 +37,7 @@ import utils, { buildHash } from '../utils.js'; import { convertWebToMobile } from '../services/immoscout/immoscout-web-translator.js'; +import logger from '../services/logger.js'; let appliedBlackList = []; async function getListings(url) { @@ -52,7 +53,7 @@ async function getListings(url) { }), }); if (!response.ok) { - console.error('Error fetching data from ImmoScout Mobile API:', response.statusText); + logger.error('Error fetching data from ImmoScout Mobile API:', response.statusText); return []; } diff --git a/lib/services/demoCleanup.js b/lib/services/demoCleanup.js index 4d995a4..de1899f 100644 --- a/lib/services/demoCleanup.js +++ b/lib/services/demoCleanup.js @@ -2,6 +2,7 @@ import { setInterval } from 'node:timers'; import { removeJobsByUserName } from './storage/jobStorage.js'; import { config } from '../utils.js'; import { getUsers } from './storage/userStorage.js'; +import logger from './logger.js'; /** * if we are running in demo environment, we have to cleanup the db files (specifically the jobs table) @@ -29,7 +30,7 @@ function cleanup() { if (config.demoMode) { const demoUser = getUsers(false).find((user) => user.username === 'demo'); if (demoUser == null) { - console.error('Demo user not found, cannot remove Jobs'); + logger.error('Demo user not found, cannot remove Jobs'); return; } removeJobsByUserName(demoUser.id); diff --git a/lib/services/extractor/extractor.js b/lib/services/extractor/extractor.js index 8e5cbaf..b881141 100644 --- a/lib/services/extractor/extractor.js +++ b/lib/services/extractor/extractor.js @@ -1,6 +1,7 @@ import { setDebug } from './utils.js'; import puppeteerExtractor from './puppeteerExtractor.js'; import { loadParser, parse } from './parser/parser.js'; +import logger from '../logger.js'; const DEFAULT_OPTIONS = { debug: false, @@ -32,7 +33,7 @@ export default class Extractor { loadParser(this.responseText); } } catch (error) { - console.error('Error trying to load page.', error); + logger.error('Error trying to load page.', error); } return this; }; diff --git a/lib/services/extractor/parser/parser.js b/lib/services/extractor/parser/parser.js index 3507250..84b0e69 100644 --- a/lib/services/extractor/parser/parser.js +++ b/lib/services/extractor/parser/parser.js @@ -1,4 +1,5 @@ import * as cheerio from 'cheerio'; +import logger from '../../logger.js'; let $ = null; @@ -8,19 +9,19 @@ export function loadParser(text) { export function parse(crawlContainer, crawlFields, text, url) { if (!text) { - console.warn('No content found for ', url); + logger.warn('No content found for ', url); return null; } if (!crawlContainer || !crawlFields) { - console.warn('Cannot parse, selector was empty for url ', url); + logger.warn('Cannot parse, selector was empty for url ', url); return null; } const result = []; if ($(crawlContainer).length === 0) { - console.warn('No elements in crawl container found for url ', url); + logger.warn('No elements in crawl container found for url ', url); return null; } @@ -58,7 +59,7 @@ export function parse(crawlContainer, crawlFields, text, url) { parsedObject[key] = value || null; } catch (error) { - console.error(`Error parsing field '${key}' with selector '${fieldSelector}':`, error); + logger.error(`Error parsing field '${key}' with selector '${fieldSelector}':`, error); parsedObject[key] = null; } } @@ -66,9 +67,7 @@ export function parse(crawlContainer, crawlFields, text, url) { if (parsedObject.id != null) { result.push(parsedObject); } else { - /* eslint-disable no-console */ - console.debug('ID not found. Not relaying object.'); - /* eslint-enable no-console */ + logger.debug('ID not found. Not relaying object.'); } }); @@ -91,7 +90,7 @@ function applyModifiers(value, modifiers) { value = value.replace(/\n/g, ' '); break; default: - console.warn(`Unknown modifier: ${modifier}`); + logger.warn(`Unknown modifier: ${modifier}`); } }); diff --git a/lib/services/extractor/puppeteerExtractor.js b/lib/services/extractor/puppeteerExtractor.js index 7d858de..b8f7259 100644 --- a/lib/services/extractor/puppeteerExtractor.js +++ b/lib/services/extractor/puppeteerExtractor.js @@ -1,6 +1,7 @@ import puppeteer from 'puppeteer-extra'; import StealthPlugin from 'puppeteer-extra-plugin-stealth'; import { debug, DEFAULT_HEADER, botDetected } from './utils.js'; +import logger from '../logger.js'; puppeteer.use(StealthPlugin()); @@ -33,13 +34,13 @@ export default async function execute(url, waitForSelector, options) { const statusCode = response.status(); if (botDetected(pageSource, statusCode)) { - console.warn('We have been detected as a bot :-/ Tried url: => ', url); + logger.warn('We have been detected as a bot :-/ Tried url: => ', url); return null; } return await page.content(); } catch (error) { - console.error('Error executing with puppeteer executor', error); + logger.error('Error executing with puppeteer executor', error); return null; } finally { if (browser != null) { diff --git a/lib/services/extractor/utils.js b/lib/services/extractor/utils.js index a356ece..af42f8d 100644 --- a/lib/services/extractor/utils.js +++ b/lib/services/extractor/utils.js @@ -1,3 +1,5 @@ +import logger from '../logger.js'; + let debuggingOn = false; export const DEFAULT_HEADER = { @@ -15,9 +17,7 @@ export const setDebug = (options) => { export const debug = (message) => { if (debuggingOn) { - /* eslint-disable no-console */ - console.debug(message); - /* eslint-enable no-console */ + logger.debug(message); } }; diff --git a/lib/services/logger.js b/lib/services/logger.js new file mode 100644 index 0000000..484bc8e --- /dev/null +++ b/lib/services/logger.js @@ -0,0 +1,15 @@ +import winston from 'winston'; + +const env = process.env.NODE_ENV || 'development'; + +const logger = winston.createLogger({ + level: env !== 'development' ? 'info' : 'debug', + format: winston.format.combine( + winston.format.colorize(), + winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + winston.format.printf(({ timestamp, level, message }) => `[${timestamp}] ${level}: ${message}`), + ), + transports: [new winston.transports.Console()], +}); + +export default logger; diff --git a/lib/services/storage/jobStorage.js b/lib/services/storage/jobStorage.js index 6697b5c..eecb597 100644 --- a/lib/services/storage/jobStorage.js +++ b/lib/services/storage/jobStorage.js @@ -4,6 +4,7 @@ import * as listingStorage from './listingsStorage.js'; import { getDirName } from '../../utils.js'; import path from 'path'; import LowdashAdapter from './LowDashAdapter.js'; +import logger from '../logger.js'; const file = path.join(getDirName(), '../', 'db/jobs.json'); const adapter = new JSONFileSync(file); @@ -91,9 +92,7 @@ export const removeJobsByUserName = (userId) => { .value(); db.write(); if (removedDemoJobs > 0) { - /* eslint-disable no-console */ - console.log(`Removed ${removedDemoJobs} demo jobs`); - /* eslint-enable no-console */ + logger.info(`Removed ${removedDemoJobs} demo jobs`); } }; export const getJobs = () => { diff --git a/lib/services/tracking/Tracker.js b/lib/services/tracking/Tracker.js index 0e76b18..154f0d5 100644 --- a/lib/services/tracking/Tracker.js +++ b/lib/services/tracking/Tracker.js @@ -5,6 +5,7 @@ import os from 'os'; import { readFileSync } from 'fs'; import { packageUp } from 'package-up'; import fetch from 'node-fetch'; +import logger from '../logger.js'; const deviceId = getUniqueId() || 'N/A'; const version = await getPackageVersion(); @@ -37,7 +38,7 @@ export const trackMainEvent = async () => { } } } catch (error) { - console.warn('Error sending tracking data', error); + logger.warn('Error sending tracking data', error); } }; @@ -52,7 +53,7 @@ export async function trackDemoAccessed() { headers: { 'Content-Type': 'application/json' }, }); } catch (error) { - console.warn('Error sending tracking data', error); + logger.warn('Error sending tracking data', error); } } } @@ -84,7 +85,7 @@ async function getPackageVersion() { const json = JSON.parse(packageJson); return json.version; } catch (error) { - console.error('Error reading version from package.json', error); + logger.error('Error reading version from package.json', error); } return 'N/A'; } diff --git a/lib/utils.js b/lib/utils.js index 3f180c7..8c0ecd8 100755 --- a/lib/utils.js +++ b/lib/utils.js @@ -4,6 +4,7 @@ import { readFile } from 'fs/promises'; import { createHash } from 'crypto'; import { DEFAULT_CONFIG } from './defaultConfig.js'; import fs from 'fs'; +import logger from './services/logger.js'; const RE_GT = />/g; const RE_WEBP = /\/format\/webp/gi; @@ -74,8 +75,7 @@ export async function refreshConfig() { config.demoMode ??= false; } catch (error) { config = { ...DEFAULT_CONFIG }; - /* eslint-disable no-console */ - console.info('Error reading config file.', error); + logger.info('Error reading config file.', error); } } @@ -84,7 +84,7 @@ export async function refreshConfig() { */ const checkIfConfigExistsAndWriteIfNot = () => { if (!fs.existsSync(`${getDirName()}/../conf/config.json`)) { - console.info('Could not find config file. Will create one with default values now'); + logger.info('Could not find config file. Will create one with default values now'); fs.writeFileSync(`${getDirName()}/../conf/config.json`, JSON.stringify({ ...DEFAULT_CONFIG })); } }; diff --git a/package.json b/package.json index 84916cc..902abd2 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fredy", - "version": "11.6.3", + "version": "11.6.4", "description": "[F]ind [R]eal [E]states [d]amn eas[y].", "scripts": { "prepare": "husky", @@ -91,6 +91,7 @@ "serve-static": "2.2.0", "slack": "11.0.2", "vite": "7.1.5", + "winston": "^3.17.0", "x-var": "^2.1.0" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index d948e4e..b039bca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -938,6 +938,20 @@ "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.27.1" +"@colors/colors@1.6.0", "@colors/colors@^1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.6.0.tgz#ec6cd237440700bc23ca23087f513c75508958b0" + integrity sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA== + +"@dabh/diagnostics@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.3.tgz#7f7e97ee9a725dffc7808d93668cc984e1dc477a" + integrity sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA== + dependencies: + colorspace "1.1.x" + enabled "2.0.x" + kuler "^2.0.0" + "@dnd-kit/accessibility@^3.1.1": version "3.1.1" resolved "https://registry.yarnpkg.com/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz#3b4202bd6bb370a0730f6734867785919beac6af" @@ -1754,6 +1768,11 @@ dependencies: undici-types "~7.10.0" +"@types/triple-beam@^1.3.2": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/triple-beam/-/triple-beam-1.3.5.tgz#74fef9ffbaa198eb8b588be029f38b00299caa2c" + integrity sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw== + "@types/unist@*", "@types/unist@^3.0.0": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.3.tgz#acaab0f919ce69cce629c2d4ed2eb4adc1b6c20c" @@ -2146,6 +2165,11 @@ async-validator@^3.5.0: resolved "https://registry.yarnpkg.com/async-validator/-/async-validator-3.5.2.tgz#68e866a96824e8b2694ff7a831c1a25c44d5e500" integrity sha512-8eLCg00W9pIRZSB781UUX/H6Oskmm8xloZfr09lz5bikRpBVDlJ3hRVuxxP1SxcwsEYfJ4IU8Q19Y8/893r3rQ== +async@^3.2.3: + version "3.2.6" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" + integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -2573,16 +2597,52 @@ color-convert@2.0.1, color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@~1.1.4: +color-convert@^1.9.3: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@^1.0.0, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +color-string@^1.6.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^3.1.3: + version "3.2.1" + resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164" + integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== + dependencies: + color-convert "^1.9.3" + color-string "^1.6.0" + colorette@^2.0.20: version "2.0.20" resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== +colorspace@1.1.x: + version "1.1.4" + resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.4.tgz#8d442d1186152f60453bf8070cd66eb364e59243" + integrity sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w== + dependencies: + color "^3.1.3" + text-hex "1.0.x" + combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -3009,6 +3069,11 @@ emoji-regex@^9.2.2: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== +enabled@2.0.x: + version "2.0.0" + resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" + integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== + encodeurl@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" @@ -3547,6 +3612,11 @@ fdir@^6.5.0: resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== +fecha@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd" + integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw== + fetch-blob@^3.1.2, fetch-blob@^3.1.4: version "3.2.0" resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" @@ -3617,6 +3687,11 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== +fn.name@1.x.x: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" + integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== + follow-redirects@^1.15.6: version "1.15.11" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" @@ -4178,6 +4253,11 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== +is-arrayish@^0.3.1: + version "0.3.4" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.4.tgz#1ee5553818511915685d33bb13d31bf854e5059d" + integrity sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA== + is-async-function@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.1.1.tgz#3e69018c8e04e73b738793d020bfe884b9fd3523" @@ -4368,6 +4448,11 @@ is-shared-array-buffer@^1.0.4: dependencies: call-bound "^1.0.3" +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + is-string@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.1.1.tgz#92ea3f3d5c5b6e039ca8677e5ac8d07ea773cbb9" @@ -4574,6 +4659,11 @@ kind-of@^3.0.2: dependencies: is-buffer "^1.1.5" +kuler@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" + integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== + lazy-cache@^0.2.3: version "0.2.7" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-0.2.7.tgz#7feddf2dcb6edb77d11ef1d117ab5ffdf0ab1b65" @@ -4688,6 +4778,18 @@ log-update@^6.1.0: strip-ansi "^7.1.0" wrap-ansi "^9.0.0" +logform@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/logform/-/logform-2.7.0.tgz#cfca97528ef290f2e125a08396805002b2d060d1" + integrity sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ== + dependencies: + "@colors/colors" "1.6.0" + "@types/triple-beam" "^1.3.2" + fecha "^4.2.0" + ms "^2.1.1" + safe-stable-stringify "^2.3.1" + triple-beam "^1.3.0" + longest-streak@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4" @@ -5665,6 +5767,13 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" +one-time@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45" + integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g== + dependencies: + fn.name "1.x.x" + onetime@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/onetime/-/onetime-7.0.0.tgz#9f16c92d8c9ef5120e3acd9dd9957cceecc1ab60" @@ -6228,7 +6337,7 @@ react@18.3.1: dependencies: loose-envify "^1.1.0" -readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0: +readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.2: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -6587,6 +6696,11 @@ safe-regex-test@^1.1.0: es-errors "^1.3.0" is-regex "^1.2.1" +safe-stable-stringify@^2.3.1: + version "2.5.0" + resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz#4ca2f8e385f2831c432a719b108a3bf7af42a1dd" + integrity sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA== + "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -6808,6 +6922,13 @@ simple-statistics@^7.7.3: resolved "https://registry.yarnpkg.com/simple-statistics/-/simple-statistics-7.8.8.tgz#8acf4f78ba5a111e7c8424ffda2ac32fe353198c" integrity sha512-CUtP0+uZbcbsFpqEyvNDYjJCl+612fNgjT8GaVuvMG7tBuJg8gXGpsP5M7X658zy0IcepWOZ6nPBu1Qb9ezA1w== +simple-swizzle@^0.2.2: + version "0.2.4" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.4.tgz#a8d11a45a11600d6a1ecdff6363329e3648c3667" + integrity sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw== + dependencies: + is-arrayish "^0.3.1" + simple-update-notifier@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb" @@ -6904,6 +7025,11 @@ split-on-first@^3.0.0: resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-3.0.0.tgz#f04959c9ea8101b9b0bbf35a61b9ebea784a23e7" integrity sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA== +stack-trace@0.0.x: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" + integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== + statuses@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" @@ -7186,6 +7312,11 @@ text-encoding@^0.6.4: resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19" integrity sha512-hJnc6Qg3dWoOMkqP53F0dzRIgtmsAge09kxUIqGrEUS4qr5rWLckGYaQAVr+opBrIMRErGgy6f5aPnyPpyGRfg== +text-hex@1.0.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" + integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== + tiny-json-http@^7.0.2: version "7.5.1" resolved "https://registry.yarnpkg.com/tiny-json-http/-/tiny-json-http-7.5.1.tgz#82efaa190c3edf6f5f2d906a9e88f792d38f8532" @@ -7235,6 +7366,11 @@ trim-lines@^3.0.0: resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338" integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg== +triple-beam@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.4.1.tgz#6fde70271dc6e5d73ca0c3b24e2d92afb7441984" + integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg== + trough@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/trough/-/trough-2.2.0.tgz#94a60bd6bd375c152c1df911a4b11d5b0256f50f" @@ -7603,6 +7739,32 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +winston-transport@^4.9.0: + version "4.9.0" + resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.9.0.tgz#3bba345de10297654ea6f33519424560003b3bf9" + integrity sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A== + dependencies: + logform "^2.7.0" + readable-stream "^3.6.2" + triple-beam "^1.3.0" + +winston@^3.17.0: + version "3.17.0" + resolved "https://registry.yarnpkg.com/winston/-/winston-3.17.0.tgz#74b8665ce9b4ea7b29d0922cfccf852a08a11423" + integrity sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw== + dependencies: + "@colors/colors" "^1.6.0" + "@dabh/diagnostics" "^2.0.2" + async "^3.2.3" + is-stream "^2.0.0" + logform "^2.7.0" + one-time "^1.0.0" + readable-stream "^3.4.0" + safe-stable-stringify "^2.3.1" + stack-trace "0.0.x" + triple-beam "^1.3.0" + winston-transport "^4.9.0" + word-wrap@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" From 3cd1893b5169d7b120c9dffad31ad725b8ac86e0 Mon Sep 17 00:00:00 2001 From: Christian Kellner Date: Sat, 13 Sep 2025 22:16:16 +0200 Subject: [PATCH 24/46] Update Jetbrains logo to use the correct one on dark background --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e596330..915478e 100755 --- a/README.md +++ b/README.md @@ -49,7 +49,12 @@ I maintain Fredy and other open-source projects in my free time.\ If you find it useful, consider supporting the project 💙 Fredy is proudly backed by the **JetBrains Open Source Support Program**. -[JetBrains](https://jb.gg/OpenSourceSupport) + + + + + Jetbrains Open Source + ------------------------------------------------------------------------ From 694809fedf343b3e1dabc0dfc9b356e7779e7776 Mon Sep 17 00:00:00 2001 From: Christian Kellner Date: Sat, 13 Sep 2025 22:20:50 +0200 Subject: [PATCH 25/46] Using white fredy logo on dark background --- README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 915478e..0f3aac6 100755 --- a/README.md +++ b/README.md @@ -1,11 +1,15 @@

- - Expo logo - + + + + + + Jetbrains Open Source + +

- - ![Tests](https://github.com/orangecoding/fredy/actions/workflows/test.yml/badge.svg) +![Tests](https://github.com/orangecoding/fredy/actions/workflows/test.yml/badge.svg) [![Docker](https://github.com/orangecoding/fredy/actions/workflows/docker.yml/badge.svg)](https://github.com/orangecoding/fredy/actions/workflows/docker.yml) ![Source](https://github.com/orangecoding/fredy/actions/workflows/check_source.yml/badge.svg) ![Docker Pulls](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fghcr-badge.elias.eu.org%2Fapi%2Forangecoding%2Ffredy%2Ffredy&query=%24.downloadCount&label=Docker%20Pulls) From bda42122494816eb48cbeae331c14cb307cc7edb Mon Sep 17 00:00:00 2001 From: orangecoding Date: Sun, 14 Sep 2025 10:32:39 +0200 Subject: [PATCH 26/46] improve logging --- lib/services/logger.js | 70 ++++++++++++++--- package.json | 1 - yarn.lock | 166 +---------------------------------------- 3 files changed, 60 insertions(+), 177 deletions(-) diff --git a/lib/services/logger.js b/lib/services/logger.js index 484bc8e..ab40f37 100644 --- a/lib/services/logger.js +++ b/lib/services/logger.js @@ -1,15 +1,61 @@ -import winston from 'winston'; +const COLORS = { + debug: '\x1b[36m', + info: '\x1b[32m', + warn: '\x1b[33m', + error: '\x1b[31m', + reset: '\x1b[0m', +}; -const env = process.env.NODE_ENV || 'development'; +const useColor = process.stdout.isTTY || process.stderr.isTTY; -const logger = winston.createLogger({ - level: env !== 'development' ? 'info' : 'debug', - format: winston.format.combine( - winston.format.colorize(), - winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), - winston.format.printf(({ timestamp, level, message }) => `[${timestamp}] ${level}: ${message}`), - ), - transports: [new winston.transports.Console()], -}); +function ts() { + const d = new Date(); + const yyyy = d.getFullYear(); + const mm = String(d.getMonth() + 1).padStart(2, '0'); + const dd = String(d.getDate()).padStart(2, '0'); + const hh = String(d.getHours()).padStart(2, '0'); + const mi = String(d.getMinutes()).padStart(2, '0'); + const ss = String(d.getSeconds()).padStart(2, '0'); + return `${yyyy}-${mm}-${dd} ${hh}:${mi}:${ss}`; +} -export default logger; +function lvl(level) { + const upper = level.toUpperCase(); + if (!useColor) return upper; + return `${COLORS[level] || ''}${upper}${COLORS.reset}`; +} + +/* eslint-disable no-console */ +function log(level, ...args) { + const prefix = `[${ts()}] ${lvl(level)}:`; + switch (level) { + case 'debug': + console.debug(prefix, ...args); + break; + case 'info': + console.info(prefix, ...args); + break; + case 'warn': + console.warn(prefix, ...args); + break; + case 'error': + console.error(prefix, ...args); + break; + default: + console.log(prefix, ...args); + } +} + +export default { + debug: (...a) => log('debug', ...a), + info: (...a) => log('info', ...a), + warn: (...a) => log('warn', ...a), + error: (...a) => log('error', ...a), +}; + +// Beispiel: +// import logger from './logger.js'; +// const a = 'fick'; +// const b = { tr: 'lolo' }; +// logger.info('hallo', a, b); +// -> In IntelliJ siehst du das Objekt wie bei console.info, plus Prefix diff --git a/package.json b/package.json index 902abd2..84b15d4 100755 --- a/package.json +++ b/package.json @@ -91,7 +91,6 @@ "serve-static": "2.2.0", "slack": "11.0.2", "vite": "7.1.5", - "winston": "^3.17.0", "x-var": "^2.1.0" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index b039bca..d948e4e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -938,20 +938,6 @@ "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.27.1" -"@colors/colors@1.6.0", "@colors/colors@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.6.0.tgz#ec6cd237440700bc23ca23087f513c75508958b0" - integrity sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA== - -"@dabh/diagnostics@^2.0.2": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.3.tgz#7f7e97ee9a725dffc7808d93668cc984e1dc477a" - integrity sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA== - dependencies: - colorspace "1.1.x" - enabled "2.0.x" - kuler "^2.0.0" - "@dnd-kit/accessibility@^3.1.1": version "3.1.1" resolved "https://registry.yarnpkg.com/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz#3b4202bd6bb370a0730f6734867785919beac6af" @@ -1768,11 +1754,6 @@ dependencies: undici-types "~7.10.0" -"@types/triple-beam@^1.3.2": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@types/triple-beam/-/triple-beam-1.3.5.tgz#74fef9ffbaa198eb8b588be029f38b00299caa2c" - integrity sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw== - "@types/unist@*", "@types/unist@^3.0.0": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.3.tgz#acaab0f919ce69cce629c2d4ed2eb4adc1b6c20c" @@ -2165,11 +2146,6 @@ async-validator@^3.5.0: resolved "https://registry.yarnpkg.com/async-validator/-/async-validator-3.5.2.tgz#68e866a96824e8b2694ff7a831c1a25c44d5e500" integrity sha512-8eLCg00W9pIRZSB781UUX/H6Oskmm8xloZfr09lz5bikRpBVDlJ3hRVuxxP1SxcwsEYfJ4IU8Q19Y8/893r3rQ== -async@^3.2.3: - version "3.2.6" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" - integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -2597,52 +2573,16 @@ color-convert@2.0.1, color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-convert@^1.9.3: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - -color-name@^1.0.0, color-name@~1.1.4: +color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@^1.6.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" - integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - -color@^3.1.3: - version "3.2.1" - resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164" - integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== - dependencies: - color-convert "^1.9.3" - color-string "^1.6.0" - colorette@^2.0.20: version "2.0.20" resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== -colorspace@1.1.x: - version "1.1.4" - resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.4.tgz#8d442d1186152f60453bf8070cd66eb364e59243" - integrity sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w== - dependencies: - color "^3.1.3" - text-hex "1.0.x" - combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -3069,11 +3009,6 @@ emoji-regex@^9.2.2: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== -enabled@2.0.x: - version "2.0.0" - resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" - integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== - encodeurl@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" @@ -3612,11 +3547,6 @@ fdir@^6.5.0: resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== -fecha@^4.2.0: - version "4.2.3" - resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd" - integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw== - fetch-blob@^3.1.2, fetch-blob@^3.1.4: version "3.2.0" resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" @@ -3687,11 +3617,6 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== -fn.name@1.x.x: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" - integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== - follow-redirects@^1.15.6: version "1.15.11" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" @@ -4253,11 +4178,6 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== -is-arrayish@^0.3.1: - version "0.3.4" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.4.tgz#1ee5553818511915685d33bb13d31bf854e5059d" - integrity sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA== - is-async-function@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.1.1.tgz#3e69018c8e04e73b738793d020bfe884b9fd3523" @@ -4448,11 +4368,6 @@ is-shared-array-buffer@^1.0.4: dependencies: call-bound "^1.0.3" -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - is-string@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.1.1.tgz#92ea3f3d5c5b6e039ca8677e5ac8d07ea773cbb9" @@ -4659,11 +4574,6 @@ kind-of@^3.0.2: dependencies: is-buffer "^1.1.5" -kuler@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" - integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== - lazy-cache@^0.2.3: version "0.2.7" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-0.2.7.tgz#7feddf2dcb6edb77d11ef1d117ab5ffdf0ab1b65" @@ -4778,18 +4688,6 @@ log-update@^6.1.0: strip-ansi "^7.1.0" wrap-ansi "^9.0.0" -logform@^2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/logform/-/logform-2.7.0.tgz#cfca97528ef290f2e125a08396805002b2d060d1" - integrity sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ== - dependencies: - "@colors/colors" "1.6.0" - "@types/triple-beam" "^1.3.2" - fecha "^4.2.0" - ms "^2.1.1" - safe-stable-stringify "^2.3.1" - triple-beam "^1.3.0" - longest-streak@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4" @@ -5767,13 +5665,6 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -one-time@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45" - integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g== - dependencies: - fn.name "1.x.x" - onetime@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/onetime/-/onetime-7.0.0.tgz#9f16c92d8c9ef5120e3acd9dd9957cceecc1ab60" @@ -6337,7 +6228,7 @@ react@18.3.1: dependencies: loose-envify "^1.1.0" -readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.2: +readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -6696,11 +6587,6 @@ safe-regex-test@^1.1.0: es-errors "^1.3.0" is-regex "^1.2.1" -safe-stable-stringify@^2.3.1: - version "2.5.0" - resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz#4ca2f8e385f2831c432a719b108a3bf7af42a1dd" - integrity sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA== - "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -6922,13 +6808,6 @@ simple-statistics@^7.7.3: resolved "https://registry.yarnpkg.com/simple-statistics/-/simple-statistics-7.8.8.tgz#8acf4f78ba5a111e7c8424ffda2ac32fe353198c" integrity sha512-CUtP0+uZbcbsFpqEyvNDYjJCl+612fNgjT8GaVuvMG7tBuJg8gXGpsP5M7X658zy0IcepWOZ6nPBu1Qb9ezA1w== -simple-swizzle@^0.2.2: - version "0.2.4" - resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.4.tgz#a8d11a45a11600d6a1ecdff6363329e3648c3667" - integrity sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw== - dependencies: - is-arrayish "^0.3.1" - simple-update-notifier@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb" @@ -7025,11 +6904,6 @@ split-on-first@^3.0.0: resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-3.0.0.tgz#f04959c9ea8101b9b0bbf35a61b9ebea784a23e7" integrity sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA== -stack-trace@0.0.x: - version "0.0.10" - resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" - integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== - statuses@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" @@ -7312,11 +7186,6 @@ text-encoding@^0.6.4: resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19" integrity sha512-hJnc6Qg3dWoOMkqP53F0dzRIgtmsAge09kxUIqGrEUS4qr5rWLckGYaQAVr+opBrIMRErGgy6f5aPnyPpyGRfg== -text-hex@1.0.x: - version "1.0.0" - resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" - integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== - tiny-json-http@^7.0.2: version "7.5.1" resolved "https://registry.yarnpkg.com/tiny-json-http/-/tiny-json-http-7.5.1.tgz#82efaa190c3edf6f5f2d906a9e88f792d38f8532" @@ -7366,11 +7235,6 @@ trim-lines@^3.0.0: resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338" integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg== -triple-beam@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.4.1.tgz#6fde70271dc6e5d73ca0c3b24e2d92afb7441984" - integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg== - trough@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/trough/-/trough-2.2.0.tgz#94a60bd6bd375c152c1df911a4b11d5b0256f50f" @@ -7739,32 +7603,6 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -winston-transport@^4.9.0: - version "4.9.0" - resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.9.0.tgz#3bba345de10297654ea6f33519424560003b3bf9" - integrity sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A== - dependencies: - logform "^2.7.0" - readable-stream "^3.6.2" - triple-beam "^1.3.0" - -winston@^3.17.0: - version "3.17.0" - resolved "https://registry.yarnpkg.com/winston/-/winston-3.17.0.tgz#74b8665ce9b4ea7b29d0922cfccf852a08a11423" - integrity sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw== - dependencies: - "@colors/colors" "^1.6.0" - "@dabh/diagnostics" "^2.0.2" - async "^3.2.3" - is-stream "^2.0.0" - logform "^2.7.0" - one-time "^1.0.0" - readable-stream "^3.4.0" - safe-stable-stringify "^2.3.1" - stack-trace "0.0.x" - triple-beam "^1.3.0" - winston-transport "^4.9.0" - word-wrap@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" From ae1c4d936be1396ceb4811145577cd003d855224 Mon Sep 17 00:00:00 2001 From: orangecoding Date: Sun, 14 Sep 2025 10:40:18 +0200 Subject: [PATCH 27/46] do not log debug on production --- lib/services/logger.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/services/logger.js b/lib/services/logger.js index ab40f37..0ac13a2 100644 --- a/lib/services/logger.js +++ b/lib/services/logger.js @@ -6,6 +6,7 @@ const COLORS = { reset: '\x1b[0m', }; +const env = process.env.NODE_ENV || 'development'; const useColor = process.stdout.isTTY || process.stderr.isTTY; function ts() { @@ -27,6 +28,10 @@ function lvl(level) { /* eslint-disable no-console */ function log(level, ...args) { + if (level === 'debug' && env !== 'development') { + return; // Skip debug logs in non-development environments + } + const prefix = `[${ts()}] ${lvl(level)}:`; switch (level) { case 'debug': From ce7f0bca9f7f470132ab7aea08eb1744928bbb46 Mon Sep 17 00:00:00 2001 From: orangecoding Date: Sun, 14 Sep 2025 10:40:41 +0200 Subject: [PATCH 28/46] next release version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 84b15d4..3bf82eb 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fredy", - "version": "11.6.4", + "version": "11.6.5", "description": "[F]ind [R]eal [E]states [d]amn eas[y].", "scripts": { "prepare": "husky", From de119c91995ff5eec7251e55ddef05decab62e5e Mon Sep 17 00:00:00 2001 From: Christian Kellner Date: Sun, 14 Sep 2025 15:46:31 +0200 Subject: [PATCH 29/46] Update logger.js --- lib/services/logger.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/services/logger.js b/lib/services/logger.js index 0ac13a2..b60aafe 100644 --- a/lib/services/logger.js +++ b/lib/services/logger.js @@ -57,10 +57,3 @@ export default { warn: (...a) => log('warn', ...a), error: (...a) => log('error', ...a), }; - -// Beispiel: -// import logger from './logger.js'; -// const a = 'fick'; -// const b = { tr: 'lolo' }; -// logger.info('hallo', a, b); -// -> In IntelliJ siehst du das Objekt wie bei console.info, plus Prefix From 027e7d70edfbae9bb97d82fe6b86f5115ef35a51 Mon Sep 17 00:00:00 2001 From: Iaroslav Postovalov <38042667+CommanderTvis@users.noreply.github.com> Date: Wed, 17 Sep 2025 09:12:04 +0200 Subject: [PATCH 30/46] Update SQLite adapter: configurable database path (#169) --- lib/notification/adapter/sqlite.js | 29 ++++++++++++++++++++++++----- lib/notification/adapter/sqlite.md | 8 ++++---- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/lib/notification/adapter/sqlite.js b/lib/notification/adapter/sqlite.js index 4291643..12c40a2 100644 --- a/lib/notification/adapter/sqlite.js +++ b/lib/notification/adapter/sqlite.js @@ -1,7 +1,18 @@ import { markdown2Html } from '../../services/markdown.js'; import Database from 'better-sqlite3'; -export const send = ({ serviceName, newListings, jobKey }) => { - const db = new Database('db/listings.db'); +import path from 'path'; +import fs from 'fs'; + +export const send = ({ serviceName, newListings, jobKey, notificationConfig }) => { + const sqliteConfig = notificationConfig.find((adapter) => adapter.id === config.id); + const dbPath = sqliteConfig?.fields?.dbPath || 'db/listings.db'; + + const dbDir = path.dirname(dbPath); + if (!fs.existsSync(dbDir)) { + fs.mkdirSync(dbDir, { recursive: true }); + } + + const db = new Database(dbPath); const fields = [ 'serviceName', 'jobKey', @@ -30,8 +41,16 @@ export const send = ({ serviceName, newListings, jobKey }) => { }; export const config = { id: 'sqlite', - name: 'Sqlite', - description: 'This adapter stores listings in a local sqlite3 database.', - config: {}, + name: 'SQLite', + description: 'This adapter stores listings in a local SQLite 3 database.', + fields: { + dbPath: { + type: 'text', + label: 'Database Path', + description: + 'Path to the SQLite database file (e.g., db/listings.db). If not specified, defaults to db/listings.db', + placeholder: 'db/listings.db', + }, + }, readme: markdown2Html('lib/notification/adapter/sqlite.md'), }; diff --git a/lib/notification/adapter/sqlite.md b/lib/notification/adapter/sqlite.md index 6b455b5..bc9592f 100644 --- a/lib/notification/adapter/sqlite.md +++ b/lib/notification/adapter/sqlite.md @@ -1,9 +1,9 @@ -### Sqlite Adapter +### SQLite Adapter -This adapter stores search results in a sqlite database located in db/listings.db. This file can be used for further analysis later on. +This adapter stores search results in an SQLite database. By default, the database is located at `db/listings.db`, but you can configure a custom location. This file can be used for further analysis later. -Fields are: +The database table contains the following columns (all stored as `TEXT` type): ``` -['serviceName', 'jobKey', 'id', 'size', 'rooms', 'price', 'address', 'title', 'link', 'description'] +['serviceName', 'jobKey', 'id', 'size', 'rooms', 'price', 'address', 'title', 'link', 'description', 'image'] ``` From 18fdbd761a4e01ffcb479d873b163d9c4d2386ca Mon Sep 17 00:00:00 2001 From: orangecoding Date: Wed, 17 Sep 2025 09:12:45 +0200 Subject: [PATCH 31/46] next release version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3bf82eb..2c0be02 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fredy", - "version": "11.6.5", + "version": "11.6.6", "description": "[F]ind [R]eal [E]states [d]amn eas[y].", "scripts": { "prepare": "husky", From 8d95f052c6b78d4b09bebd76295ac1b359b8c4b0 Mon Sep 17 00:00:00 2001 From: Christian Kellner Date: Thu, 18 Sep 2025 15:38:23 +0200 Subject: [PATCH 32/46] Migrate to SQLite (#174) * Migrating Fredy from LowDb to SqLite :tada: * adding new sql migration system for future sql migrations * adding setting to change sqlite path for db files * create migration plan for graceful migration lowdb -> sqlite * Improving Documentation * adding test for sqliteconnection * upgrading dependencies * making nodejs 22 as min version * improve scraper * adding overwrite ability for db migra --- .github/workflows/check_source.yml | 2 +- .github/workflows/test.yml | 2 +- .gitignore | 3 +- README.md | 2 +- conf/config.json | 2 +- db/.gitkeep | 0 db/migrations/migrate.js | 199 +++++++++++ db/migrations/sql/0.init.js | 16 + .../sql/1.create-fredy-base-structure.js | 117 +++++++ index.js | 38 +- lib/FredyRuntime.js | 12 +- lib/api/routes/generalSettingsRoute.js | 4 +- lib/defaultConfig.js | 2 + lib/provider/einsAImmobilien.js | 4 +- lib/provider/immobilienDe.js | 6 +- lib/provider/immonet.js | 12 +- lib/provider/immoscout.js | 1 + lib/services/demoCleanup.js | 4 +- lib/services/storage/LowDashAdapter.js | 8 - lib/services/storage/SqliteConnection.js | 140 ++++++++ lib/services/storage/jobStorage.js | 216 +++++++----- lib/services/storage/listingsStorage.js | 180 +++++++--- lib/services/storage/userStorage.js | 279 +++++++++------ lib/utils.js | 100 +++++- package.json | 23 +- test/db/migrations/migrate.test.js | 329 ++++++++++++++++++ test/mocks/mockStore.js | 4 +- test/provider/einsAImmobilien.test.js | 1 + test/storage/SqliteConnection.test.js | 142 ++++++++ .../views/generalSettings/GeneralSettings.jsx | 40 ++- yarn.lock | 160 ++++----- 31 files changed, 1636 insertions(+), 412 deletions(-) create mode 100644 db/.gitkeep create mode 100644 db/migrations/migrate.js create mode 100644 db/migrations/sql/0.init.js create mode 100644 db/migrations/sql/1.create-fredy-base-structure.js delete mode 100644 lib/services/storage/LowDashAdapter.js create mode 100644 lib/services/storage/SqliteConnection.js create mode 100644 test/db/migrations/migrate.test.js create mode 100644 test/storage/SqliteConnection.test.js diff --git a/.github/workflows/check_source.yml b/.github/workflows/check_source.yml index a3d1b92..f0c34fa 100644 --- a/.github/workflows/check_source.yml +++ b/.github/workflows/check_source.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 cache: 'yarn' - name: Install dependencies diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d5ce6ee..236ddf3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 cache: 'yarn' - run: yarn install diff --git a/.gitignore b/.gitignore index 45b5ac8..f59f123 100755 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ node_modules/ ui/public/ -db/ +db/*.json +db/*.db* npm-debug.log .DS_Store .idea diff --git a/README.md b/README.md index 0f3aac6..9e47046 100755 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ docker logs fredy -f ### Manual (Node.js) -- Requirement: **Node.js 20 or higher** +- Requirement: **Node.js 22 or higher** - Install dependencies and start: ``` bash diff --git a/conf/config.json b/conf/config.json index e8c9521..eb00028 100644 --- a/conf/config.json +++ b/conf/config.json @@ -1 +1 @@ -{"interval":"60","port":9998,"workingHours":{"from":"","to":""},"demoMode":false,"analyticsEnabled":null} \ No newline at end of file +{"interval":"60","port":9998,"workingHours":{"from":"","to":""},"demoMode":false,"analyticsEnabled":null,"sqlitepath":"/db"} \ No newline at end of file diff --git a/db/.gitkeep b/db/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/db/migrations/migrate.js b/db/migrations/migrate.js new file mode 100644 index 0000000..6fa2029 --- /dev/null +++ b/db/migrations/migrate.js @@ -0,0 +1,199 @@ +/** + * Migration Runner for better-sqlite3 + * I know there are external libs out there, but + * a) most of them are pretty bloated + * b) I wanted to have something that fit's this limited use-case + * c) I was searching for justifications anyway to build a migration system on my own. Don't judge me ;) + * + * Executes all migration files in db/migrations/sql in natural order. + * Each migration runs in its own transaction. If a migration fails, only that + * migration is rolled back and the process stops with a non-zero exit code. + * Already applied migrations are skipped using the schema_migrations table. + * + * Usage: + * CLI: yarn run migratedb + * Programmatic: + * import { runMigrations } from './db/migrations/migrate.js'; + * await runMigrations(); + * + * Migration file format (example: db/migrations/sql/1.add-users.js): + * export function up(db) { + * db.exec("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)"); + * } + * + */ +import fs from 'fs'; +import path from 'path'; +import {pathToFileURL} from 'url'; +import crypto from 'crypto'; +import SqliteConnection from '../../lib/services/storage/SqliteConnection.js'; +import logger from '../../lib/services/logger.js'; + +const ROOT = path.resolve('.'); +const MIGRATIONS_DIR = path.join(ROOT, 'db', 'migrations', 'sql'); + +/** + * Ensures that the given directory exists, creating it recursively if needed. + * @param {string} p - Path to the directory. + */ +function ensureDir(p) { + if (!fs.existsSync(p)) fs.mkdirSync(p, {recursive: true}); +} + +/** + * Lists all migration files in the migrations directory. + * Migration files must follow the format: .