Merge pull request #6 from Redume/chore/migrating-to-hjson
Some checks failed
Create and publish a Docker image / build-and-push-chart (push) Has been cancelled
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-CR (push) Has been cancelled
Create and publish a Docker image / build-and-push-web (push) Has been cancelled

Chore/migrating to hjson
This commit is contained in:
Данил 2025-03-05 19:28:54 +00:00 committed by GitHub
commit 5387a91a2f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
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) {