diff --git a/config_sample.yaml b/config_sample.yaml index e312d08..198b002 100644 --- a/config_sample.yaml +++ b/config_sample.yaml @@ -16,6 +16,11 @@ server: log: print: true # Enable or disable logging [Boolean] level: 'info' # Log level (Fatal/Error/Warn/Log/Debug) [String] +analytics: + plausible_api: 'https://plausible.io/api/event' + plausible_domain: 'PLAUSIBLE_DOMAIN' + plausible_token: 'PLAUSIBLE_TOKEN' + work: true currency: chart: save: false # Enable or disable saving graphs to an image (Boolean) diff --git a/server/main.js b/server/main.js index 29aeb0c..5cd666f 100644 --- a/server/main.js +++ b/server/main.js @@ -1,6 +1,9 @@ const logger = require('../shared/logger/src/main.js'); const config = require('../shared/config/src/main.js')(); + const fs = require('fs'); +const axios= require("axios"); +const UAParser = require('ua-parser-js'); require('../shared/database/src/create_table.js')(); @@ -30,6 +33,39 @@ fastify.register(getRateRoute); fastify.register(configurationRoutes); fastify.register(HomeRoute); +fastify.addHook('onResponse', async (request, reply) => { + if (!config['analytics']['work']) return; + + const userAgent = request.headers['user-agent']; + const parser = new UAParser(userAgent); + const browser = parser.getBrowser(); + const os = parser.getOS(); + + const event = { + domain: config['analytics']['plausible_domain'], + name: request.routerPath || '404 - Not Found', + url: request.raw.url, + props: { + method: request.method, + statusCode: reply.statusCode, + browser: `${browser.name} ${browser.version}`, + os: `${os.name} ${os.version}`, + source: request.headers['referer'] || 'direct', + }, + }; + + try { + await axios.post(config['analytics']['plausible_api'], event, { + headers: { + Authorization: `Bearer ${config['analytics']['plausible_token']}`, + 'Content-Type': 'application/json', + }, + }); + } catch (error) { + fastify.log.error('Error sending event to Plausible:', error.message); + } +}); + fastify.listen( { port: 3000, diff --git a/server/package-lock.json b/server/package-lock.json index a72c1f2..81f29d8 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -9,8 +9,10 @@ "version": "1.0.0", "license": "GPL-3.0-or-later", "dependencies": { + "axios": "^1.7.7", "fastify": "^4.28.1", - "pino": "^9.3.2" + "pino": "^9.3.2", + "ua-parser-js": "^1.0.39" } }, "node_modules/@fastify/ajv-compiler": { @@ -105,6 +107,12 @@ "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", "license": "MIT" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/atomic-sleep": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", @@ -124,6 +132,17 @@ "fastq": "^1.17.1" } }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -168,6 +187,18 @@ "ieee754": "^1.2.1" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", @@ -177,6 +208,15 @@ "node": ">= 0.6" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -326,6 +366,40 @@ "node": ">=14" } }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -390,6 +464,27 @@ "set-cookie-parser": "^2.4.1" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/on-exit-leak-free": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", @@ -471,6 +566,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/quick-format-unescaped": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", @@ -642,6 +743,32 @@ "engines": { "node": ">=12" } + }, + "node_modules/ua-parser-js": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.39.tgz", + "integrity": "sha512-k24RCVWlEcjkdOxYmVJgeD/0a1TiSpqLg+ZalVGV9lsnr4yqu0w7tX/x2xX6G4zpkgQnRf89lxuZ1wsbjXM8lw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "license": "MIT", + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } } } } diff --git a/server/package.json b/server/package.json index 1f047d6..ed55f16 100644 --- a/server/package.json +++ b/server/package.json @@ -17,7 +17,9 @@ "homepage": "https://github.com/Redume/Kekkai#readme", "description": "The main service. Web server", "dependencies": { + "axios": "^1.7.7", "fastify": "^4.28.1", - "pino": "^9.3.2" + "pino": "^9.3.2", + "ua-parser-js": "^1.0.39" } }