Compare commits

...

21 commits

Author SHA1 Message Date
5387a91a2f
Merge pull request #6 from Redume/chore/migrating-to-hjson
Some checks failed
Create and publish a Docker image / detect what files changed (push) Has been cancelled
Create and publish a Docker image / build-and-push-server (push) Has been cancelled
Create and publish a Docker image / build-and-push-chart (push) Has been cancelled
Create and publish a Docker image / build-and-push-CR (push) Has been cancelled
Create and publish a Docker image / build-and-push-web (push) Has been cancelled
Chore/migrating to hjson
2025-03-05 19:28:54 +00:00
f79d2b166a docs: Rename the file, replace the examples with a new config 2025-03-05 22:27:36 +03:00
dd73f73869 Revert "chore(server): Changed the name of the keys. Made it easier to change the domain"
This reverts commit da7134f444.
2025-03-05 22:25:26 +03:00
8675fd918f chore: delete catch error 2025-03-05 21:48:45 +03:00
dd69715f8e fix(config): fix crontab 2025-03-05 21:48:27 +03:00
64185e7565 chore(chart): change to new key config 2025-03-03 10:52:28 +03:00
f307644a8c fix(chart): fixed an error with getting a config item 2025-03-03 10:47:56 +03:00
f4475ae466 deps(chart): Replaced pyyaml with hjson. 2025-03-03 10:40:06 +03:00
6cddaa53a8 chore(chart): rename config file, yaml to hjson 2025-03-03 10:39:43 +03:00
c00dea8a81 сhore(chart): Made parsing and converting hjson to json 2025-03-03 10:38:53 +03:00
1964fd333a chore(config): Adapted the code to output a new config. Added an error if there is no config file 2025-03-03 09:44:49 +03:00
dd24356e81 chore(web): Changed the keys of the new config. Improved code readability 2025-03-03 09:43:13 +03:00
58e6ccfda6 chore: change config name 2025-03-03 09:07:13 +03:00
e2c513fe81 fix(server): added the patch at the end, it was causing the analytics to malfunction 2025-03-03 01:29:38 +03:00
547b6c2754 chore(server): change key for config 2025-03-03 01:29:00 +03:00
8aa070d0d1 chore(server): If logging is set to “none”, it is disabled 2025-03-03 01:27:32 +03:00
da7134f444 chore(server): Changed the name of the keys. Made it easier to change the domain 2025-03-03 01:22:31 +03:00
7240fc4472 chore: eslint fix 2025-03-02 22:33:02 +03:00
3cbfe5dc70 fix: Fix line endings from CRLF to LF to comply with Prettier and ESLint rules 2025-03-02 22:01:54 +03:00
2a4c02f601 chore(config): Changed gitignore and split 2025-03-02 21:50:46 +03:00
f4838349d4 chore(config): replace example configs 2025-03-02 21:46:41 +03:00
18 changed files with 278 additions and 201 deletions

View file

@ -17,7 +17,8 @@ module.exports = {
'error', {
singleQuote: true,
parser: "flow",
tabWidth: 4
tabWidth: 4,
endOfLine: 'lf'
},
],
},

10
.gitignore vendored
View file

@ -1,12 +1,20 @@
# IDE & text redactors files
.idea
.vscode
# Project
CertSSL/
charts/
# Depends
node_modules/
# Docker compose
postgres-data/
config.yaml
# Config
config.hjson
.env
# Other
.DS_Store

View file

@ -9,7 +9,7 @@ import asyncpg
from utils.load_config import load_config
config = load_config('config.yaml')
config = load_config('config.hjson')
async def create_pool() -> asyncpg.pool.Pool:
"""

View file

@ -16,7 +16,7 @@ from routes import get_chart, get_chart_period
from utils.load_config import load_config
app = FastAPI()
config = load_config('config.yaml')
config = load_config('config.hjson')
if not os.path.exists('../charts'):
os.mkdir('../charts')
@ -35,10 +35,10 @@ if __name__ == '__main__':
port=3030,
ssl_keyfile=
config['server']['ssl']['private_key']
if config['server']['ssl']['work']
if config['server']['ssl']['enabled']
else None,
ssl_certfile=
config['server']['ssl']['cert']
if config['server']['ssl']['work']
if config['server']['ssl']['enabled']
else None
)

View file

@ -9,7 +9,7 @@ from user_agents import parse as ua_parse
from utils.load_config import load_config
config = load_config('config.yaml')
config = load_config('config.hjson')
# pylint: disable=too-few-public-methods
class PlausibleAnalytics:
@ -23,7 +23,7 @@ class PlausibleAnalytics:
async def __call__(self, request, call_next):
response = await call_next(request)
if HTTPStatus(response.status_code).is_client_error or not config['analytics']['work']:
if HTTPStatus(response.status_code).is_client_error or not config['analytics']['enabled']:
return response
user_agent = request.headers.get('user-agent', 'unknown')

View file

@ -1,5 +1,5 @@
matplotlib~=3.9.1
PyYAML~=6.0.1
hjson~=3.1.0
uvicorn~=0.29.0
fastapi[standard]~=0.115.2
starlette~=0.40.0

View file

@ -1,20 +1,20 @@
# pylint: disable=R0801
"""
This module provides a function for loading a YAML configuration file.
The function reads the file content and returns it as a Python dictionary.
Parsing and converting HJSON config to JSON
"""
import yaml
import hjson
import json
def load_config(file_path: str) -> dict:
"""
Loads a YAML configuration file and returns its contents as a dictionary.
Load an HJSON file, convert it to a JSON string with indentation,
and return it.
This function opens the specified YAML file, parses its content, and
returns it in dictionary format, making it accessible for use in
the application.
params: file_path (str): The path to the HJSON file.
:param file_path: The path to the YAML configuration file to be loaded.
:return: A dictionary containing the parsed content of the YAML file.
returns str: The JSON string formatted with indentation.
"""
with open(file_path, 'r', encoding='utf-8') as file:
return yaml.safe_load(file)
hjson_data = hjson.load(file)
return json.loads(
json.dumps(hjson_data, indent=4)
)

View file

@ -13,7 +13,7 @@ async function validateSchedule(schedule) {
}
async function initialize() {
await require('../shared/database/src/create_table')();
//await require('../shared/database/src/create_table')();
}
async function runTasks() {
@ -21,6 +21,7 @@ async function runTasks() {
}
async function main() {
console.log(config['currency']['collecting']['schedule'])
await initialize();
await validateSchedule(config['currency']['collecting']['schedule']);
@ -32,8 +33,6 @@ async function main() {
);
}
main().catch((err) => {
logger.error('Error in main execution:', err);
});
main();
module.exports = { main };

57
config.example.hjson Normal file
View file

@ -0,0 +1,57 @@
{
database:
{
user: DATABASE_USERNAME
password: DATABASE_PASSWORD
host: localhost
name: kekkai
port: 5432
}
server:
{
host: 0.0.0.0
ssl:
{
private_key: /CertSSL/privkey.pem
cert: /CertSSL/fullchain.pem
enabled: false
}
log:
{
level: info
}
}
analytics:
{
plausible_domain: plausible.io
plausible_token: TOKEN
plausiblee_api: https://plausible.io//api/event/
enabled: false
}
currency:
{
collecting:
{
fiat: true
crypto: false
schedule: 30 8 * * *
crypto_apikey: TOKEN
}
fiat:
[
USD
RUB
EUR
UAH
TRY
KZT
]
crypto:
[
ETH
TON
USDT
BTC
]
}
}

View file

@ -1,42 +0,0 @@
# For more information, see the documentation
# https://kekkai-docs.redume.su/
database: # Postgresql database data, for connection
user: 'DATABASE_USERNAME'
password: 'DATABASE_PASSWORD'
host: 'DATABASE_HOST'
name: 'DATABASE_NAME'
port: 5432
server:
host: '0.0.0.0'
ssl:
private_key: '/CertSSL/privkey.pem' # The path to the private SSL key file (String)
cert: '/CertSSL/fullchain.pem' # The path to the SSL certificate (String)
work: false # Enable or disable SSL support [Boolean]
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: false
currency:
chart:
save: false # Enable or disable saving graphs to an image (Boolean)
collecting:
fiat: true # Turn off or turn on the collection of the fiat currency rate [Boolean]
crypto: false
schedule: '30 8 * * *' # Currency collection schedule in crontab format [String]
crypto_apikey: 'APIKEY'
fiat: # List of fiat currency to save the exchange rate [Array]
- USD
- RUB
- EUR
- UAH
- TRY
- KZT
crypto:
- ETH
- TON
- USDT

View file

@ -24,7 +24,7 @@ services:
- '3000:3000'
volumes:
- './CertSSL:/CertSSL'
- './config.yaml:/config.yaml'
- './config.hjson:/config.hjson'
depends_on:
- postgres
@ -36,7 +36,7 @@ services:
- '3030:3030'
volumes:
- './CertSSL:/CertSSL'
- './config.yaml:/config.yaml'
- './config.hjson:/config.hjson'
depends_on:
postgres:
condition: service_healthy
@ -50,7 +50,7 @@ services:
- '3050:3050'
volumes:
- './CertSSL:/CertSSL'
- './config.yaml:/config.yaml'
- './config.hjson:/config.hjson'
docs:
build:
@ -63,7 +63,7 @@ services:
dockerfile: Dockerfile-collect-currency
restart: unless-stopped
volumes:
- './config.yaml:/config.yaml'
- './config.hjson:/config.hjson'
depends_on:
- postgres

View file

@ -51,8 +51,8 @@ export default defineConfig({
slug: 'docs/config/config-env'
},
{
label: 'Configure config.yaml',
slug: 'docs/config/config-yaml'
label: 'Configure config.hjson',
slug: 'docs/config/config-hjson'
},
{
label: 'Configure Nginx',

View file

@ -1,66 +1,93 @@
---
title: Configure config.yaml
title: Configure config.hjson
---
import { Aside } from '@astrojs/starlight/components';
Kekkai can be configured using the `config.yaml` file in the working directory.
`config.example.yaml`.
Kekkai can be configured using the `config.hjson` file in the working directory.
`config.example.hjson`.
```yaml
```hjson
# For more information, see the documentation
# https://kekkai-docs.redume.su/
database:
user: 'DATABASE_USERNAME'
password: 'DATABASE_PASSWORD'
host: 'DATABASE_HOST'
name: 'DATABASE_NAME'
port: 5432
server:
host: '0.0.0.0'
ssl:
private_key: '/CertSSL/privkey.pem'
cert: '/CertSSL/fullchain.pem'
work: true
log:
print: true
level: 'info'
analytics:
plausible_api: 'https://plausible.io/api/event/'
plausible_domain: 'PLAUSIBLE_DOMAIN'
plausible_token: 'PLAUSIBLE_TOKEN'
work: false
currency:
chart:
save: false
collecting:
{
database:
{
user: DATABASE_USERNAME
password: DATABASE_PASSWORD
host: localhost
name: kekkai
port: 5432
}
server:
{
host: 0.0.0.0
ssl:
{
private_key: /CertSSL/privkey.pem
cert: /CertSSL/fullchain.pem
enabled: false
}
log:
{
level: info
}
}
analytics:
{
plausible_domain: plausible.io
plausible_token: TOKEN
enabled: false
}
currency:
{
collecting:
{
fiat: true
schedule: '30 8 * * *'
fiat:
- USD
- RUB
- EUR
- UAH
- TRY
- KZT
crypto: false
schedule: 30 8 * * *
crypto_apikey: TOKEN
}
fiat:
[
USD
RUB
EUR
UAH
TRY
KZT
]
crypto:
[
ETH
TON
USDT
BTC
]
}
}
```
## Database
Kekkai is used as a `PostgreSQL` database.
<Aside>
If you installed Kekkai via Docker Compose,
then install it in the `database.host` value of `postgres`.
The rest of the data does not have to be filled in.
They need to be filled in `.env`.
If you installed Kekkai via `Docker Compose`,
set it to `database.host` for postgres.
The password (database.password) should be the same as in `.env`,
the rest of the data doesn't need to be filled in,
it should be in `.env`
What should it look like:
```yaml
```hjson
database:
...
host: 'postgres'
...
{
...
password: PASSWORD_FROM_ENV
host: postgres
...
}
...
```
</Aside>
@ -96,7 +123,7 @@ analytics:
plausible_api: 'https://plausible.io/api/event/'
plausible_domain: 'PLAUSIBLE_DOMAIN'
plausible_token: 'PLAUSIBLE_TOKEN'
work: true
enabled: true
...
```
@ -107,7 +134,7 @@ analytics:
You can add the domain [here](https://plausible.io/sites/new?flow=provisioning).
- `plausible_token`: Api token for authorization and sending requests.
You can create it [here](https://plausible.io/settings/api-keys).
- `work`: Enable or disable analytics.
- `enabled`: Enable or disable analytics.
## Currency
`DuckDuckGo` (fiat currency collection) and `CoinMarketCap` (cryptocurrency collection)
@ -116,25 +143,32 @@ are used to collect currency rates.
```yaml
...
currency:
chart:
save: false # Enable or disable saving graphs to an image (Boolean)
collecting:
fiat: true # Turn off or turn on the collection of the fiat currency rate [Boolean]
currency:
{
collecting:
{
fiat: true
crypto: false
schedule: '30 8 * * *' # Currency collection schedule in crontab format [String]
crypto_apikey: 'APIKEY'
fiat: # List of fiat currency to save the exchange rate [Array]
- USD
- RUB
- EUR
- UAH
- TRY
- KZT
crypto:
- ETH
- TON
- USDT
schedule: 30 8 * * *
crypto_apikey: TOKEN
}
fiat:
[
USD
RUB
EUR
UAH
TRY
KZT
]
crypto:
[
ETH
TON
USDT
BTC
]
}
```
- `currency.chart.save`: Enable or disable saving graphs.

View file

@ -1,26 +1,32 @@
const logger = require("../shared/logger/src/main.js");
const config = require("../shared/config/src/main.js")();
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");
const fs = require('fs');
const axios = require('axios');
const UAParser = require('ua-parser-js');
require("../shared/database/src/create_table.js")();
require('../shared/database/src/create_table.js')();
const fastify = require("fastify")({
logger: config["server"]["log"]["print"] ? logger : false,
...(config["server"]["ssl"]["work"]
const fastify = require('fastify')({
logger: config['server']['log']['level'] !== 'none' ? logger : false,
...(config['server']['ssl']['enabled']
? {
https: {
key: fs.readFileSync(config["server"]["ssl"]["private_key"], "utf8"),
cert: fs.readFileSync(config["server"]["ssl"]["cert"], "utf8"),
},
}
https: {
key: fs.readFileSync(
config['server']['ssl']['private_key'],
'utf8',
),
cert: fs.readFileSync(
config['server']['ssl']['cert'],
'utf8',
),
},
}
: false),
});
const getRateRoute = require("./routes/getRate.js");
const getMetadata = require("./routes/metadata.js");
const getRateRoute = require('./routes/getRate.js');
const getMetadata = require('./routes/metadata.js');
fastify.register(getRateRoute);
fastify.register(getMetadata);
@ -28,69 +34,81 @@ fastify.register(getMetadata);
fastify.setNotFoundHandler(function (res, reply) {
return reply.status(404).send({
status: 404,
message: "Page not found!",
documentation: "https://kekkai-docs.redume.su/",
message: 'Page not found!',
documentation: 'https://kekkai-docs.redume.su/',
});
});
fastify.addHook("onResponse", async (request, reply) => {
const routePart = request.raw.url.split("/");
fastify.addHook('onResponse', async (request, reply) => {
const routePart = request.raw.url.split('/');
const routePartFiltered = routePart
.filter((part) => part !== "")
.filter((part) => part !== '')
.map((part) => `${part}/`);
routePartFiltered.unshift("/");
routePartFiltered.unshift('/');
if (!config?.["analytics"]["work"] ? config?.["analytics"]["work"] : false)
if (!config?.['analytics']['work'] ? config?.['analytics']['work'] : false)
return;
else if (!fastify.printRoutes().includes(routePartFiltered.at(-1))) return;
const userAgent = request.headers["user-agent"];
const userAgent = request.headers['user-agent'];
const parser = new UAParser(userAgent);
const browser = parser.getBrowser();
const os = parser.getOS();
const formattedOS =
os.name && os.version ? `${os.name} ${os.version}` : "N/A";
os.name && os.version ? `${os.name} ${os.version}` : 'N/A';
const formattedBrowser =
browser.name && browser.version
? `${browser.name} ${browser.version}`
: "N/A";
: 'N/A';
const event = {
domain: config["analytics"]["plausible_domain"],
domain: config['analytics']['plausible_domain'],
name: request.routeOptions.url
? request.routeOptions.url
: "404 - Not Found",
: '404 - Not Found',
url: request.raw.url,
props: {
method: request.method,
statusCode: reply.statusCode,
browser: formattedBrowser,
os: formattedOS,
source: request.headers["referer"]
? request.headers["referer"]
: "direct",
source: request.headers['referer']
? request.headers['referer']
: 'direct',
},
};
try {
await axios.post(config["analytics"]["plausible_api"], event, {
<<<<<<< HEAD
await axios.post(
`https://${config['analytics']['plausible_domain']}/api/event/`,
event,
{
headers: {
Authorization: `Bearer ${config['analytics']['plausible_token']}`,
'Content-Type': 'application/json',
'User-Agent': userAgent,
},
=======
await axios.post(config['analytics']['plausible_api'], event, {
headers: {
Authorization: `Bearer ${config["analytics"]["plausible_token"]}`,
"Content-Type": "application/json",
"User-Agent": userAgent,
Authorization: `Bearer ${config['analytics']['plausible_token']}`,
'Content-Type': 'application/json',
'User-Agent': userAgent,
>>>>>>> parent of da7134f (chore(server): Changed the name of the keys. Made it easier to change the domain)
},
});
} catch (error) {
fastify.log.error("Error sending event to Plausible:", error.message);
fastify.log.error('Error sending event to Plausible:', error.message);
}
});
fastify.listen(
{
port: 3000,
host: config["server"]["host"] ? config["server"]["host"] : "localhost",
host: config['server']['host'] ? config['server']['host'] : 'localhost',
},
(err) => {
if (err) {

View file

@ -9,19 +9,16 @@
"version": "1.0.0",
"license": "GPL-3.0-or-later",
"dependencies": {
"yaml": "^2.5.0"
"hjson": "^3.2.2"
}
},
"node_modules/yaml": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz",
"integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==",
"license": "ISC",
"node_modules/hjson": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/hjson/-/hjson-3.2.2.tgz",
"integrity": "sha512-MkUeB0cTIlppeSsndgESkfFD21T2nXPRaBStLtf3cAYA2bVEFdXlodZB0TukwZiobPD1Ksax5DK4RTZeaXCI3Q==",
"license": "MIT",
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14"
"hjson": "bin/hjson"
}
}
}

View file

@ -17,6 +17,6 @@
"homepage": "https://github.com/Redume/Kekkai#readme",
"description": "Config management service",
"dependencies": {
"yaml": "^2.5.0"
"hjson": "^3.2.2"
}
}

View file

@ -1,10 +1,10 @@
const fs = require('fs');
const yaml = require('yaml');
const hjson = require('hjson');
const config = () => {
if (!fs.existsSync('../config.yaml')) return;
if (!fs.existsSync('../config.hjson')) throw new Error('Config not found');
return yaml.parse(fs.readFileSync('../config.yaml', 'utf-8'));
return hjson.parse(fs.readFileSync('../config.hjson', 'utf-8'));
};
module.exports = config;

View file

@ -1,18 +1,24 @@
const logger = require("../shared/logger");
const config = require("../shared/config/src/main.js")();
const logger = require('../shared/logger');
const config = require('../shared/config/src/main.js')();
const fs = require("fs");
const path = require("node:path");
const fs = require('fs');
const path = require('node:path');
const fastify = require("fastify")({
logger: config["server"]["log"]["print"] ? logger : false,
...(config["server"]["ssl"]["work"]
const fastify = require('fastify')({
logger: config['server']['log']['level'] !== 'none' ? logger : false,
...(config['server']['ssl']['enabled']
? {
https: {
key: fs.readFileSync(config["server"]["ssl"]["private_key"], "utf8"),
cert: fs.readFileSync(config["server"]["ssl"]["cert"], "utf8"),
},
}
https: {
key: fs.readFileSync(
config['server']['ssl']['private_key'],
'utf8',
),
cert: fs.readFileSync(
config['server']['ssl']['cert'],
'utf8',
),
},
}
: false),
});
@ -23,11 +29,10 @@ fastify.register(require('@fastify/static'), {
fastify.register(require('./routes/home.js'));
fastify.listen(
{
port: 3050,
host: config["server"]["host"] ? config["server"]["host"] : "localhost",
host: config['server']['host'] ? config['server']['host'] : 'localhost',
},
(err) => {
if (err) {