mirror of
https://github.com/Redume/Kekkai.git
synced 2025-04-01 21:07:35 +03:00
Compare commits
131 commits
Author | SHA1 | Date | |
---|---|---|---|
3fef9a219e | |||
8ce23cf311 | |||
8a0b75e9ea | |||
1045e497ed | |||
3a9043b244 | |||
6c07e348b3 | |||
57867e2d73 | |||
5387a91a2f | |||
f79d2b166a | |||
dd73f73869 | |||
8675fd918f | |||
dd69715f8e | |||
64185e7565 | |||
f307644a8c | |||
f4475ae466 | |||
6cddaa53a8 | |||
c00dea8a81 | |||
1964fd333a | |||
dd24356e81 | |||
58e6ccfda6 | |||
e2c513fe81 | |||
547b6c2754 | |||
8aa070d0d1 | |||
da7134f444 | |||
241494ec2d | |||
ed7e54988e | |||
7240fc4472 | |||
3cbfe5dc70 | |||
2a4c02f601 | |||
f4838349d4 | |||
6e7dbe4c14 | |||
fc97f39011 | |||
b1c234cb5e | |||
520df54650 | |||
ae9402e0da | |||
84e5d389a0 | |||
5d36dfc6ee | |||
d479c8494c | |||
d45bde7624 | |||
d8396d2331 | |||
9724fac958 | |||
c057ca3c34 | |||
8e1e9fe3c7 | |||
102790613f | |||
5a1043e7e6 | |||
7de6cf13d3 | |||
6269512daa | |||
ac52635834 | |||
9d6b54b0e4 | |||
9c9d0352c6 | |||
fe8959577c | |||
a707bb2da7 | |||
b3aab3e7aa | |||
c6f89c0ed0 | |||
6e8921d3fe | |||
f2b85ae0b0 | |||
91bacafe80 | |||
00cfc7f611 | |||
179a9207df | |||
07439f7d71 | |||
c812ea5025 | |||
8e1b6e6883 | |||
6ff4881777 | |||
02ef286326 | |||
2c4a15a3fd | |||
3daaedaa2d | |||
77aaddb119 | |||
e1fdda4b54 | |||
ef2c126d68 | |||
ab97ee1e62 | |||
5407830125 | |||
2c28db0eb6 | |||
7fab1b45fe | |||
bb4d8bbde8 | |||
ef68511286 | |||
125b00b4bb | |||
632f2f34b6 | |||
6bae4dabfc | |||
907b614f25 | |||
f543d2336f | |||
424ecff857 | |||
c944e552ec | |||
87d19540f8 | |||
b6490ca65a | |||
9bc6c0a203 | |||
fff7cebdb7 | |||
3720c3d66f | |||
20773385f4 | |||
1a3c751abc | |||
c26cedf5ed | |||
266a2ae389 | |||
d3f9652d8a | |||
bc1e05f575 | |||
0e8575a04f | |||
04a2d18eb3 | |||
eae175efa9 | |||
40331c909c | |||
f1f26889ea | |||
82ad228c1a | |||
e2cb35a880 | |||
2c5a80263b | |||
23fc87b524 | |||
41a800785f | |||
100afe78ea | |||
aef3eeb793 | |||
8b03043293 | |||
4c857c435f | |||
d935fad5c9 | |||
d427f21b02 | |||
5bff5e4641 | |||
c80af2db24 | |||
4345a2d856 | |||
2b891ef592 | |||
71759fb98f | |||
b80015a6c3 | |||
c2720114c4 | |||
6ff258799d | |||
e51aa5e00c | |||
a7ad1e77d3 | |||
872c2836bc | |||
9db37e8300 | |||
f8899ab607 | |||
1c6a735e0b | |||
ed8cf91558 | |||
c5d3d9fe9b | |||
a3b94f18ef | |||
ded9abc816 | |||
85acbbb16c | |||
110effaeb2 | |||
9750900d3e | |||
a34bcd0a12 |
86 changed files with 10341 additions and 1249 deletions
|
@ -1,2 +1,3 @@
|
|||
node_modules/
|
||||
docs/
|
||||
docs/
|
||||
.gitignore
|
|
@ -17,7 +17,8 @@ module.exports = {
|
|||
'error', {
|
||||
singleQuote: true,
|
||||
parser: "flow",
|
||||
tabWidth: 4
|
||||
tabWidth: 4,
|
||||
endOfLine: 'lf'
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
150
.github/workflows/docker.yml
vendored
150
.github/workflows/docker.yml
vendored
|
@ -1,46 +1,66 @@
|
|||
name: Create and publish a Docker image
|
||||
name: Create and publish Docker images
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
IMAGE_PREFIX: ghcr.io/${{ github.repository_owner }}/kekkai
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
name: Detect changed files
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: read
|
||||
outputs:
|
||||
server: ${{ steps.filter.outputs.server }}
|
||||
chart: ${{ steps.filter.outputs.chart }}
|
||||
CR: ${{ steps.filter.outputs.CR }}
|
||||
web: ${{ steps.filter.outputs.web }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
server:
|
||||
- 'server/**'
|
||||
chart:
|
||||
- 'chart/**'
|
||||
CR:
|
||||
- 'collect-currency/**'
|
||||
web:
|
||||
- 'web/**'
|
||||
|
||||
build-and-push-server:
|
||||
needs: changes
|
||||
if: ${{ needs.changes.outputs.server == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
- id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
images: ${{ env.IMAGE_PREFIX }}-server
|
||||
tags: |
|
||||
type=raw,value=latest
|
||||
type=ref,event=branch
|
||||
type=sha
|
||||
- uses: docker/build-push-action@v5
|
||||
with:
|
||||
file: Dockerfile-server
|
||||
context: .
|
||||
|
@ -50,33 +70,30 @@ jobs:
|
|||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
build-and-push-chart:
|
||||
needs: changes
|
||||
if: ${{ needs.changes.outputs.chart == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
- id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
images: ${{ env.IMAGE_PREFIX }}-chart
|
||||
tags: |
|
||||
type=raw,value=latest
|
||||
type=ref,event=branch
|
||||
type=sha
|
||||
- uses: docker/build-push-action@v5
|
||||
with:
|
||||
file: ./chart/Dockerfile
|
||||
context: ./chart
|
||||
|
@ -85,39 +102,68 @@ jobs:
|
|||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
|
||||
build-and-push-CR:
|
||||
needs: changes
|
||||
if: ${{ needs.changes.outputs.CR == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
- id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
images: ${{ env.IMAGE_PREFIX }}-collect-currency
|
||||
tags: |
|
||||
type=raw,value=latest
|
||||
type=ref,event=branch
|
||||
type=sha
|
||||
- uses: docker/build-push-action@v5
|
||||
with:
|
||||
file: Dockerfile-collect-currency
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
build-and-push-web:
|
||||
needs: changes
|
||||
if: ${{ needs.changes.outputs.web == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.IMAGE_PREFIX }}-web
|
||||
tags: |
|
||||
type=raw,value=latest
|
||||
type=ref,event=branch
|
||||
type=sha
|
||||
- uses: docker/build-push-action@v5
|
||||
with:
|
||||
file: Dockerfile-web
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
|
29
.github/workflows/documentation.yml
vendored
29
.github/workflows/documentation.yml
vendored
|
@ -1,29 +0,0 @@
|
|||
name: Deploy docs
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
permissions:
|
||||
contents: write
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Configure Git Credentials
|
||||
run: |
|
||||
git config user.name github-actions[bot]
|
||||
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.x
|
||||
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
key: mkdocs-material-${{ env.cache_id }}
|
||||
path: .cache
|
||||
restore-keys: |
|
||||
mkdocs-material-
|
||||
- run: pip install mkdocs-material
|
||||
- run: mkdocs gh-deploy --force
|
12
.gitignore
vendored
12
.gitignore
vendored
|
@ -1,10 +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
|
|
@ -1,35 +1,35 @@
|
|||
FROM node:20 AS shared-config
|
||||
# Устанавливаем зависимости shared/config
|
||||
FROM node:20-alpine AS shared-config
|
||||
# Install shared/config dependencies
|
||||
WORKDIR /
|
||||
COPY ./shared/config/package*.json .
|
||||
RUN npm install
|
||||
|
||||
FROM node:20 AS shared-database
|
||||
# Устанавливаем зависимости shared/database
|
||||
FROM node:20-alpine AS shared-database
|
||||
# Install shared/database dependencies
|
||||
WORKDIR /
|
||||
COPY ./shared/database/package*.json .
|
||||
RUN npm install
|
||||
|
||||
FROM node:20 AS shared-logger
|
||||
# Устанавливаем зависимости shared/logger
|
||||
FROM node:20-alpine AS shared-logger
|
||||
# Install the shared/logger dependencies
|
||||
WORKDIR /
|
||||
COPY ./shared/logger/package*.json .
|
||||
RUN npm install
|
||||
|
||||
FROM node:20 AS collect-currency
|
||||
FROM node:20-alpine AS collect-currency
|
||||
|
||||
WORKDIR /
|
||||
|
||||
# Устанавливаем зависимости server
|
||||
# Install server dependencies
|
||||
COPY ./collect-currency/package*.json ./
|
||||
RUN npm install
|
||||
|
||||
# Копируем зависимости shared
|
||||
# Copying shared dependencies
|
||||
COPY --from=shared-config /node_modules /node_modules
|
||||
COPY --from=shared-database /node_modules /node_modules
|
||||
COPY --from=shared-logger /node_modules /node_modules
|
||||
|
||||
# Копируем все остальные файлы
|
||||
# Copy all the other files
|
||||
COPY ./collect-currency/ ./
|
||||
COPY ./shared/ ./shared/
|
||||
|
||||
|
|
|
@ -1,35 +1,35 @@
|
|||
FROM node:20 AS shared-config
|
||||
# Устанавливаем зависимости shared/config
|
||||
FROM node:20-alpine AS shared-config
|
||||
# Install shared/config dependencies
|
||||
WORKDIR /
|
||||
COPY ./shared/config/package*.json .
|
||||
RUN npm install
|
||||
|
||||
FROM node:20 AS shared-database
|
||||
# Устанавливаем зависимости shared/database
|
||||
FROM node:20-alpine AS shared-database
|
||||
# Install shared/database dependencies
|
||||
WORKDIR /
|
||||
COPY ./shared/database/package*.json .
|
||||
RUN npm install
|
||||
|
||||
FROM node:20 AS shared-logger
|
||||
# Устанавливаем зависимости shared/logger
|
||||
FROM node:20-alpine AS shared-logger
|
||||
# Install the shared/logger dependencies
|
||||
WORKDIR /
|
||||
COPY ./shared/logger/package*.json .
|
||||
RUN npm install
|
||||
|
||||
FROM node:20 AS server
|
||||
FROM node:20-alpine AS server
|
||||
|
||||
WORKDIR /
|
||||
|
||||
# Устанавливаем зависимости server
|
||||
# Install server dependencies
|
||||
COPY ./server/package*.json ./
|
||||
RUN npm install
|
||||
|
||||
# Копируем зависимости shared
|
||||
# Copying shared dependencies
|
||||
COPY --from=shared-config /node_modules /node_modules
|
||||
COPY --from=shared-database /node_modules /node_modules
|
||||
COPY --from=shared-logger /node_modules /node_modules
|
||||
|
||||
# Копируем все остальные файлы
|
||||
# Copy all the other files
|
||||
COPY ./server/ ./
|
||||
COPY ./shared/ ./shared/
|
||||
|
||||
|
|
31
Dockerfile-web
Normal file
31
Dockerfile-web
Normal file
|
@ -0,0 +1,31 @@
|
|||
FROM node:20-alpine AS shared-config
|
||||
# Install shared/config dependencies
|
||||
WORKDIR /
|
||||
COPY ./shared/config/package*.json .
|
||||
RUN npm install
|
||||
|
||||
FROM node:20-alpine AS shared-logger
|
||||
# Install the shared/logger dependencies
|
||||
WORKDIR /
|
||||
COPY ./shared/logger/package*.json .
|
||||
RUN npm install
|
||||
|
||||
FROM node:20-alpine AS web
|
||||
|
||||
WORKDIR /
|
||||
|
||||
# Install web dependencies
|
||||
COPY ./web/package*.json ./
|
||||
RUN npm install
|
||||
|
||||
# Copying shared dependencies, without database
|
||||
COPY --from=shared-config /node_modules /node_modules
|
||||
COPY --from=shared-logger /node_modules /node_modules
|
||||
|
||||
# Copy all the other files
|
||||
COPY ./web/ ./
|
||||
COPY ./shared/ ./shared/
|
||||
|
||||
EXPOSE 3050
|
||||
|
||||
CMD ["node", "main.js"]
|
17
README.md
17
README.md
|
@ -10,13 +10,10 @@
|
|||
<img alt="GPLv3 license" src="https://img.shields.io/github/license/redume/kekkai?color=blue">
|
||||
</a>
|
||||
<a href="https://github.com/redume/kekkai/releases/latest">
|
||||
<img alt="Latest release" src="https://img.shields.io/github/v/release/redume/kakkai?display_name=release">
|
||||
<img alt="Latest release" src="https://img.shields.io/github/v/release/redume/kekkai?display_name=release">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
> [!IMPORTANT]
|
||||
> After Nov. 1, the coinAPI service removed the plan with free 100 requests. At the moment, cryptocurrency collection is limited (free of charge). Some cryptocurrencies are possible to get through DuckDuckGO, but the list of available currencies is unknown. More details can be found in the [discussion](https://github.com/Redume/Kekkai/discussions/1)
|
||||
|
||||
> [!NOTE]
|
||||
> The first free Open-Source Tool for Saving Historical Currency data
|
||||
|
||||
|
@ -46,14 +43,8 @@
|
|||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
```
|
||||
|
||||
### Third-party library license
|
||||
## Third-Party Libraries and Licenses
|
||||
|
||||
- [eslint/eslint](https://github.com/eslint/eslint) — [MIT](https://github.com/eslint/eslint/blob/main/LICENSE)
|
||||
- [prettier/prettier](https://github.com/prettier/prettier) — [MIT](https://github.com/prettier/prettier/blob/main/LICENSE)
|
||||
- [pinojs/pino](https://github.com/pinojs/pino) - [MIT](https://github.com/pinojs/pino/blob/main/LICENSE)
|
||||
- [pinojs/pino-prettier](https://github.com/pinojs/pino-pretty) - [MIT](https://github.com/pinojs/pino-pretty/blob/master/LICENSE)
|
||||
- [brianc/pg](https://github.com/brianc/node-postgres) - [MIT](https://github.com/brianc/node-postgres/blob/master/LICENSE)
|
||||
- [eemeli/pg](https://github.com/eemeli/yaml) - [ISC](https://github.com/eemeli/yaml/blob/main/LICENSE)
|
||||
- [fastify/fastify](https://github.com/fastify/fastify) — [MIT](https://github.com/fastify/fastify/blob/main/LICENSE)
|
||||
- [axios/axios](https://github.com/axios/axios) - [MIT](https://github.com/axios/axios/blob/v1.x/LICENSE)
|
||||
- [GuillaumeRochat/cron-validator](https://github.com/GuillaumeRochat/cron-validator) - [MIT](https://github.com/GuillaumeRochat/cron-validator/blob/master/LICENSE)
|
||||
- [node-schedule/node-schedule](https://github.com/node-schedule/node-schedule) - [MIT](https://github.com/node-schedule/node-schedule/blob/master/LICENSE)
|
||||
- [withastro/starlight](https://github.com/withastro/starlight) - [MIT](https://github.com/withastro/starlight/blob/main/LICENSE)
|
BIN
assets/logo.png
Normal file
BIN
assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
|
@ -1,2 +0,0 @@
|
|||
mypy.ini
|
||||
pylintrc
|
1
chart/.gitignore
vendored
Normal file
1
chart/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
__pycache__
|
|
@ -1,12 +1,10 @@
|
|||
FROM python:3.12
|
||||
FROM python:3.13
|
||||
|
||||
WORKDIR /
|
||||
|
||||
COPY ./requirements.txt ./
|
||||
COPY . .
|
||||
|
||||
RUN pip3 install --no-cache-dir --upgrade -r ./requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 3030
|
||||
CMD ["python", "main.py"]
|
14
chart/README.md
Normal file
14
chart/README.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
This microservice generates graphs of currency exchange rates using Python and the `matplotlib` library.
|
||||
It retrieves historical exchange rate data from an internal database and visualizes the data in the form of line charts
|
||||
|
||||
|
||||
## Third-Party Libraries and Licenses
|
||||
|
||||
- [pylint-dev/pylint](https://github.com/pylint-dev/pylint) — [GPLv2 or later](https://github.com/pylint-dev/pylint/blob/main/LICENSE)
|
||||
- [python/mypy](https://github.com/python/mypy/) — [MIT](https://github.com/python/mypy/blob/master/LICENSE)
|
||||
- [encode/starlette](https://github.com/encode/starlette) — [BSD-3-Clause](https://github.com/encode/starlette/blob/master/LICENSE.md)
|
||||
- [yaml/pyyaml](https://github.com/yaml/pyyaml) — [MIT](https://github.com/yaml/pyyaml/blob/main/LICENSE)
|
||||
- [encode/uvicorn](https://github.com/encode/uvicorn) — [BSD-3-Clause](https://github.com/encode/uvicorn/blob/master/LICENSE.md)
|
||||
- [fastapi/fastapi](https://github.com/fastapi/fastapi) — [MIT](https://github.com/fastapi/fastapi/blob/master/LICENSE)
|
||||
- [selwin/python-user-agents](https://github.com/selwin/python-user-agents) — [MIT](https://github.com/selwin/python-user-agents/blob/master/LICENSE.txt)
|
||||
- [MagicStack/asyncpg](https://github.com/MagicStack/asyncpg)—- [Apache-2.0](https://github.com/MagicStack/asyncpg/blob/master/LICENSE)
|
35
chart/database/server.py
Normal file
35
chart/database/server.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
"""
|
||||
This module initializes the database connection pool using asyncpg.
|
||||
|
||||
It reads database configuration from a YAML file and creates a connection pool
|
||||
that can be used throughout the application for database operations.
|
||||
"""
|
||||
|
||||
import asyncpg
|
||||
|
||||
from utils.load_config import load_config
|
||||
|
||||
config = load_config('config.hjson')
|
||||
|
||||
async def create_pool() -> asyncpg.pool.Pool:
|
||||
"""
|
||||
Creates and returns a connection pool for the PostgreSQL database.
|
||||
|
||||
The function uses configuration settings loaded from a YAML file to
|
||||
establish the connection pool. The pool allows multiple database
|
||||
connections to be reused efficiently across the application.
|
||||
|
||||
Returns:
|
||||
asyncpg.pool.Pool: The connection pool object.
|
||||
"""
|
||||
pool = await asyncpg.create_pool(
|
||||
user=config['database']['user'],
|
||||
password=config['database']['password'],
|
||||
database=config['database']['name'],
|
||||
host=config['database']['host'],
|
||||
port=config['database']['port'],
|
||||
min_size=5,
|
||||
max_size=20
|
||||
)
|
||||
|
||||
return pool
|
117
chart/function/create_chart.py
Normal file
117
chart/function/create_chart.py
Normal file
|
@ -0,0 +1,117 @@
|
|||
"""
|
||||
This module provides functionality to generate currency rate charts
|
||||
based on historical data retrieved from the database.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from matplotlib import pyplot as plt
|
||||
from scipy.interpolate import make_interp_spline
|
||||
import numpy as np
|
||||
|
||||
from function.gen_unique_name import generate_unique_name
|
||||
from database.server import create_pool
|
||||
|
||||
async def create_chart(
|
||||
from_currency: str,
|
||||
conv_currency: str,
|
||||
start_date: str,
|
||||
end_date: str
|
||||
) -> (str, None):
|
||||
"""
|
||||
Generates a line chart of currency rates for a given date range.
|
||||
|
||||
The chart shows the exchange rate trend between `from_currency` and
|
||||
`conv_currency` within the specified `start_date` and `end_date` range.
|
||||
The generated chart is saved as a PNG file, and the function returns the
|
||||
file name. If data is invalid or insufficient, the function returns `None`.
|
||||
|
||||
Args:
|
||||
from_currency (str): The base currency (e.g., "USD").
|
||||
conv_currency (str): The target currency (e.g., "EUR").
|
||||
start_date (str): The start date in the format 'YYYY-MM-DD'.
|
||||
end_date (str): The end date in the format 'YYYY-MM-DD'.
|
||||
|
||||
Returns:
|
||||
str | None: The name of the saved chart file, or `None` if the operation fails.
|
||||
"""
|
||||
pool = await create_pool()
|
||||
|
||||
if not validate_date(start_date) or not validate_date(end_date):
|
||||
return None
|
||||
|
||||
start_date_obj = datetime.strptime(start_date, '%Y-%m-%d').date()
|
||||
end_date_obj = datetime.strptime(end_date, '%Y-%m-%d').date()
|
||||
|
||||
async with pool.acquire() as conn:
|
||||
data = await conn.fetch(
|
||||
'SELECT date, rate FROM currency '
|
||||
'WHERE (date BETWEEN $1 AND $2) ' +
|
||||
'AND from_currency = $3 AND conv_currency = $4 ORDER BY date',
|
||||
start_date_obj,
|
||||
end_date_obj,
|
||||
from_currency.upper(),
|
||||
conv_currency.upper()
|
||||
)
|
||||
|
||||
if not data or len(data) <= 1:
|
||||
return None
|
||||
|
||||
date, rate = [], []
|
||||
|
||||
for row in data:
|
||||
date.append(row[0])
|
||||
rate.append(row[1])
|
||||
|
||||
spline = make_interp_spline(range(len(date)), rate, k=2)
|
||||
x = np.arange(len(date))
|
||||
|
||||
newx_2 = np.linspace(0, len(date) - 1, 200)
|
||||
newy_2 = spline(newx_2)
|
||||
fig, ax = plt.subplots(figsize=(15, 6))
|
||||
|
||||
for label in (ax.get_xticklabels() + ax.get_yticklabels()):
|
||||
label.set_fontsize(10)
|
||||
|
||||
ax.set_xticks(np.linspace(0, len(date) - 1, 10))
|
||||
ax.set_xticklabels(
|
||||
[
|
||||
date[int(i)].strftime('%Y-%m-%d')
|
||||
for i in np.linspace(0, len(date) - 1, 10).astype(int)
|
||||
]
|
||||
)
|
||||
|
||||
name = await generate_unique_name(
|
||||
f'{from_currency.upper()}_{conv_currency.upper()}',
|
||||
datetime.now()
|
||||
)
|
||||
|
||||
|
||||
if rate[0] < rate[-1]:
|
||||
plt.plot(newx_2, newy_2, color='green')
|
||||
elif rate[0] > rate[-1]:
|
||||
plt.plot(newx_2, newy_2, color='red')
|
||||
else:
|
||||
plt.plot(newx_2, newy_2, color='grey')
|
||||
|
||||
plt.savefig(f'../charts/{name}.png')
|
||||
fig.clear()
|
||||
|
||||
return name
|
||||
|
||||
|
||||
def validate_date(date_str: str) -> bool:
|
||||
"""
|
||||
Validates whether the provided string is a valid date in the format 'YYYY-MM-DD'.
|
||||
|
||||
Args:
|
||||
date_str (str): The date string to validate.
|
||||
|
||||
Returns:
|
||||
bool: `True` if the string is a valid date, `False` otherwise.
|
||||
"""
|
||||
try:
|
||||
datetime.strptime(date_str, '%Y-%m-%d')
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
25
chart/function/gen_unique_name.py
Normal file
25
chart/function/gen_unique_name.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
"""
|
||||
This module provides a function to generate a unique name for src files.
|
||||
"""
|
||||
import datetime
|
||||
import random
|
||||
import string
|
||||
|
||||
|
||||
async def generate_unique_name(currency_pair: str, date: datetime) -> str:
|
||||
"""
|
||||
Generates a unique name for a chart file based on the currency pair,
|
||||
current date, and a random suffix.
|
||||
|
||||
Args:
|
||||
currency_pair (str): A string representing the currency pair (e.g., "USD_EUR").
|
||||
date (datetime.datetime): The current datetime object.
|
||||
|
||||
Returns:
|
||||
str: A unique name in the format "CURRENCYPAIR_YYYYMMDD_RANDOMSUFFIX".
|
||||
"""
|
||||
date_str = date.strftime("%Y%m%d")
|
||||
random_suffix = ''.join(random.choices(string.ascii_uppercase + string.digits, k=6))
|
||||
unique_name = f"{currency_pair}_{date_str}_{random_suffix}"
|
||||
|
||||
return unique_name
|
205
chart/main.py
205
chart/main.py
|
@ -1,34 +1,22 @@
|
|||
"""
|
||||
This is the main application file for the chart service using FastAPI.
|
||||
|
||||
The application serves static files, provides endpoints for generating charts,
|
||||
and integrates with Plausible Analytics for tracking usage.
|
||||
"""
|
||||
|
||||
import os
|
||||
import psycopg2
|
||||
|
||||
import uvicorn
|
||||
|
||||
import yaml
|
||||
import matplotlib.pyplot as plt
|
||||
import datetime
|
||||
|
||||
import dateutil.relativedelta
|
||||
import random
|
||||
import string
|
||||
|
||||
from fastapi import FastAPI, Response, status, Request
|
||||
from psycopg2.extras import DictCursor
|
||||
|
||||
from fastapi import FastAPI
|
||||
from starlette.staticfiles import StaticFiles
|
||||
from middleware.plausible_analytics import PlausibleAnalytics
|
||||
|
||||
from middleware.plausible_analytics import PlausibleAnalytics
|
||||
from routes import get_chart, get_chart_period
|
||||
from utils.load_config import load_config
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
config = yaml.safe_load(open('../config.yaml'))
|
||||
|
||||
con = psycopg2.connect(host=config['database']['host'],
|
||||
user=config['database']['user'],
|
||||
password=config['database']['password'],
|
||||
database=config['database']['name'],
|
||||
port=config['database']['port'])
|
||||
|
||||
cur = con.cursor(cursor_factory=DictCursor)
|
||||
con.autocommit = True
|
||||
config = load_config('config.hjson')
|
||||
|
||||
if not os.path.exists('../charts'):
|
||||
os.mkdir('../charts')
|
||||
|
@ -36,158 +24,21 @@ if not os.path.exists('../charts'):
|
|||
app.mount('/static/charts', StaticFiles(directory='../charts/'))
|
||||
app.middleware('http')(PlausibleAnalytics())
|
||||
|
||||
@app.get("/api/getChart/", status_code=status.HTTP_201_CREATED)
|
||||
async def get_chart(
|
||||
response: Response,
|
||||
request: Request,
|
||||
from_currency: str = None,
|
||||
conv_currency: str = None,
|
||||
start_date: str = None,
|
||||
end_date: str = None,
|
||||
):
|
||||
|
||||
if not from_currency or not conv_currency:
|
||||
response.status_code = status.HTTP_400_BAD_REQUEST
|
||||
return {
|
||||
'status': status.HTTP_400_BAD_REQUEST,
|
||||
'message': 'The from_currency and conv_currency fields are required.',
|
||||
}
|
||||
elif not start_date and not end_date:
|
||||
response.status_code = status.HTTP_400_BAD_REQUEST
|
||||
return {
|
||||
'status': status.HTTP_400_BAD_REQUEST,
|
||||
'message': 'The start_date and end_date fields are required.',
|
||||
}
|
||||
|
||||
|
||||
chart = await create_chart(from_currency, conv_currency, start_date, end_date)
|
||||
|
||||
if not chart:
|
||||
response.status_code = status.HTTP_404_NOT_FOUND
|
||||
return {'message': 'No data found.', 'status_code': status.HTTP_404_NOT_FOUND}
|
||||
|
||||
host = request.headers.get("host")
|
||||
url_schema = request.url.scheme
|
||||
|
||||
return {
|
||||
'status': status.HTTP_400_BAD_REQUEST,
|
||||
'message': f'{url_schema}://{host}/static/charts/{chart}.png',
|
||||
}
|
||||
|
||||
|
||||
@app.get("/api/getChart/{period}", status_code=status.HTTP_201_CREATED)
|
||||
async def get_chart_period(
|
||||
response: Response,
|
||||
request: Request,
|
||||
from_currency: str = None,
|
||||
conv_currency: str = None,
|
||||
period: str = None,
|
||||
):
|
||||
|
||||
if not from_currency or not conv_currency:
|
||||
response.status_code = status.HTTP_400_BAD_REQUEST
|
||||
return {
|
||||
'status': status.HTTP_400_BAD_REQUEST,
|
||||
'message': 'The from_currency and conv_currency fields are required.',
|
||||
}
|
||||
|
||||
if period not in ['week', 'month', 'quarter', 'year']:
|
||||
response.status_code = status.HTTP_400_BAD_REQUEST
|
||||
return {'message': 'Invalid period.', 'status_code': status.HTTP_400_BAD_REQUEST}
|
||||
|
||||
days, month, years = 0, 0, 0
|
||||
|
||||
if period == 'week':
|
||||
days = -7
|
||||
elif period == 'month':
|
||||
month = -1
|
||||
elif period == 'quarter':
|
||||
month = -3
|
||||
elif period == 'year':
|
||||
years = -1
|
||||
|
||||
end_date = datetime.datetime.now()
|
||||
start_date = end_date + dateutil.relativedelta.relativedelta(months=month, days=days, years=years)
|
||||
|
||||
chart = await create_chart(from_currency,
|
||||
conv_currency,
|
||||
start_date.strftime('%Y-%m-%d'),
|
||||
end_date.strftime('%Y-%m-%d')
|
||||
)
|
||||
|
||||
if not chart:
|
||||
response.status_code = status.HTTP_404_NOT_FOUND
|
||||
return {'message': 'No data found.', 'status_code': status.HTTP_404_NOT_FOUND}
|
||||
|
||||
host = request.headers.get("host")
|
||||
url_schema = request.url.scheme
|
||||
|
||||
return {
|
||||
'status': status.HTTP_201_CREATED,
|
||||
'message': f'{url_schema}://{host}/static/charts/{chart}.png',
|
||||
}
|
||||
|
||||
|
||||
async def create_chart(from_currency: str, conv_currency: str, start_date: str, end_date: str) -> (str, None):
|
||||
cur.execute('SELECT date, rate FROM currency WHERE (date BETWEEN %s AND %s) '
|
||||
'AND from_currency = %s AND conv_currency = %s ORDER BY date',
|
||||
[
|
||||
start_date, end_date,
|
||||
from_currency.upper(), conv_currency.upper()
|
||||
])
|
||||
|
||||
con.commit()
|
||||
data = cur.fetchall()
|
||||
|
||||
if not data or len(data) <= 1:
|
||||
return None
|
||||
|
||||
date, rate = [], []
|
||||
|
||||
for i in range(len(data)):
|
||||
date.append(str(data[i][0]))
|
||||
rate.append(data[i][1])
|
||||
|
||||
if rate[0] < rate[-1]:
|
||||
plt.plot(date, rate, color='green', marker='o')
|
||||
elif rate[0] > rate[-1]:
|
||||
plt.plot(date, rate, color='red', marker='o')
|
||||
else:
|
||||
plt.plot(date, rate, color='grey')
|
||||
|
||||
plt.xlabel('Date')
|
||||
plt.ylabel('Rate')
|
||||
|
||||
fig = plt.gcf()
|
||||
fig.set_size_inches(18.5, 9.5)
|
||||
|
||||
name = await generate_unique_name(
|
||||
f'{from_currency.upper()}_{conv_currency.upper()}',
|
||||
datetime.datetime.now()
|
||||
)
|
||||
|
||||
fig.savefig(f'../charts/{name}.png')
|
||||
fig.clear()
|
||||
|
||||
return name
|
||||
|
||||
|
||||
async def generate_unique_name(currency_pair: str, date: datetime) -> str:
|
||||
date_str = date.strftime("%Y%m%d")
|
||||
random_suffix = ''.join(random.choices(string.ascii_uppercase + string.digits, k=6))
|
||||
unique_name = f"{currency_pair}_{date_str}_{random_suffix}"
|
||||
|
||||
return unique_name
|
||||
app.include_router(get_chart.router)
|
||||
app.include_router(get_chart_period.router)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
uvicorn.run(app,
|
||||
host=config['server']['host'],
|
||||
port=3030,
|
||||
ssl_keyfile=config['server']['ssl']['private_key']
|
||||
if config['server']['ssl']['work']
|
||||
else None,
|
||||
ssl_certfile=config['server']['ssl']['cert']
|
||||
if config['server']['ssl']['work']
|
||||
else None
|
||||
)
|
||||
uvicorn.run(
|
||||
app,
|
||||
host=config['server']['host'],
|
||||
port=3030,
|
||||
ssl_keyfile=
|
||||
config['server']['ssl']['private_key']
|
||||
if config['server']['ssl']['enabled']
|
||||
else None,
|
||||
ssl_certfile=
|
||||
config['server']['ssl']['cert']
|
||||
if config['server']['ssl']['enabled']
|
||||
else None
|
||||
)
|
||||
|
|
|
@ -1,16 +1,29 @@
|
|||
import httpx
|
||||
import yaml
|
||||
|
||||
from user_agents import parse as ua_parse
|
||||
"""
|
||||
This module provides middleware for integrating Plausible Analytics
|
||||
into a FastAPI application.
|
||||
"""
|
||||
from http import HTTPStatus
|
||||
|
||||
config = yaml.safe_load(open('../config.yaml'))
|
||||
import httpx
|
||||
from user_agents import parse as ua_parse
|
||||
|
||||
from utils.load_config import load_config
|
||||
|
||||
config = load_config('config.hjson')
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class PlausibleAnalytics:
|
||||
"""
|
||||
Middleware for sending events to Plausible Analytics.
|
||||
|
||||
This middleware intercepts each incoming request, collects metadata such as
|
||||
user-agent, request path, and response status, and sends it as an event to
|
||||
Plausible Analytics.
|
||||
"""
|
||||
async def __call__(self, request, call_next):
|
||||
response = await call_next(request)
|
||||
|
||||
if HTTPStatus(response.status_code).is_client_error:
|
||||
if HTTPStatus(response.status_code).is_client_error or not config['analytics']['enabled']:
|
||||
return response
|
||||
|
||||
user_agent = request.headers.get('user-agent', 'unknown')
|
||||
|
@ -23,8 +36,10 @@ class PlausibleAnalytics:
|
|||
"props": {
|
||||
"method": request.method,
|
||||
"statusCode": response.status_code,
|
||||
"browser": f"{user_agent_parsed.browser.family} {user_agent_parsed.browser.version_string}",
|
||||
"os": f"{user_agent_parsed.os.family} {user_agent_parsed.os.version_string}",
|
||||
"browser": f"{user_agent_parsed.browser.family} "
|
||||
f"{user_agent_parsed.browser.version_string}",
|
||||
"os": f"{user_agent_parsed.os.family} "
|
||||
f"{user_agent_parsed.os.version_string}",
|
||||
"source": request.headers.get('referer', 'direct'),
|
||||
},
|
||||
}
|
||||
|
@ -40,7 +55,12 @@ class PlausibleAnalytics:
|
|||
"User-Agent": request.headers.get('user-agent', 'unknown'),
|
||||
},
|
||||
)
|
||||
except httpx.RequestError as e:
|
||||
print(f"Request error while sending event to Plausible: {e}")
|
||||
except httpx.HTTPStatusError as e:
|
||||
print(f"HTTP status error while sending event to Plausible: {e}")
|
||||
# pylint: disable=broad-exception-caught
|
||||
except Exception as e:
|
||||
print(f"Error sending event to Plausible: {e}")
|
||||
print(f"Unexpected error sending event to Plausible: {e}")
|
||||
|
||||
return response
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
matplotlib~=3.9.1
|
||||
PyYAML~=6.0.1
|
||||
hjson~=3.1.0
|
||||
uvicorn~=0.29.0
|
||||
fastapi[standard]~=0.115.2
|
||||
psycopg2~=2.9.9
|
||||
starlette~=0.40.0
|
||||
user_agents==2.2.0
|
||||
user_agents==2.2.0
|
||||
asyncpg~=0.30.0
|
||||
scipy~=1.15.2
|
||||
numpy~=2.2.0
|
54
chart/routes/get_chart.py
Normal file
54
chart/routes/get_chart.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
"""
|
||||
This module contains the route for retrieving a chart based on a given currency pair and date range.
|
||||
It defines the `/api/getChart/` endpoint that processes requests for generating charts.
|
||||
"""
|
||||
from fastapi import APIRouter, status, Request, Response
|
||||
|
||||
from function.create_chart import create_chart
|
||||
from .get_chart_period import prepare_chart_response
|
||||
|
||||
# pylint: disable=duplicate-code
|
||||
router = APIRouter()
|
||||
|
||||
# pylint: disable=too-many-arguments, too-many-positional-arguments
|
||||
@router.get("/api/getChart/", status_code=status.HTTP_201_CREATED)
|
||||
async def get_chart(
|
||||
response: Response,
|
||||
request: Request,
|
||||
from_currency: str = None,
|
||||
conv_currency: str = None,
|
||||
start_date: str = None,
|
||||
end_date: str = None,
|
||||
):
|
||||
"""
|
||||
Fetches a chart for a given currency pair and date range.
|
||||
|
||||
:param response: The response object used for returning the HTTP response.
|
||||
:param request: The request object containing details about the incoming request.
|
||||
:param from_currency: The base currency for conversion.
|
||||
:param conv_currency: The target currency for conversion.
|
||||
:param start_date: The start date for the chart data.
|
||||
:param end_date: The end date for the chart data.
|
||||
:return: A chart or an error message if the request is invalid.
|
||||
"""
|
||||
if not from_currency or not conv_currency:
|
||||
response.status_code = status.HTTP_400_BAD_REQUEST
|
||||
return {
|
||||
'status': status.HTTP_400_BAD_REQUEST,
|
||||
'message': 'The from_currency and conv_currency fields are required.',
|
||||
}
|
||||
|
||||
if not start_date or not end_date:
|
||||
response.status_code = status.HTTP_400_BAD_REQUEST
|
||||
return {
|
||||
'status': status.HTTP_400_BAD_REQUEST,
|
||||
'message': 'The start_date and end_date fields are required.',
|
||||
}
|
||||
|
||||
chart = await create_chart(
|
||||
from_currency,
|
||||
conv_currency,
|
||||
start_date,
|
||||
end_date
|
||||
)
|
||||
return await prepare_chart_response(response, request, chart)
|
101
chart/routes/get_chart_period.py
Normal file
101
chart/routes/get_chart_period.py
Normal file
|
@ -0,0 +1,101 @@
|
|||
"""
|
||||
This module defines the route for fetching a chart for a specific currency pair and period.
|
||||
|
||||
It includes the endpoint `/api/getChart/{period}` which allows users to request a chart for
|
||||
a given currency pair (from_currency and conv_currency)
|
||||
over a specified time period (week, month, quarter, or year).
|
||||
"""
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from fastapi import APIRouter, status, Request, Response
|
||||
|
||||
from function.create_chart import create_chart
|
||||
|
||||
# pylint: disable=duplicate-code
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/api/getChart/{period}", status_code=status.HTTP_201_CREATED)
|
||||
async def get_chart_period(
|
||||
response: Response,
|
||||
request: Request,
|
||||
from_currency: str = None,
|
||||
conv_currency: str = None,
|
||||
period: str = None,
|
||||
):
|
||||
"""
|
||||
Fetches a chart for a given currency pair and a specific period.
|
||||
|
||||
The period can be one of the following: 'week', 'month', 'quarter', 'year'.
|
||||
Based on the selected period, it calculates the start date and retrieves the chart data.
|
||||
|
||||
:param response: The response object used to set status and message.
|
||||
:param request: The request object used to retrieve details of the incoming request.
|
||||
:param from_currency: The base currency in the pair (e.g., 'USD').
|
||||
:param conv_currency: The target currency in the pair (e.g., 'EUR').
|
||||
:param period: The time period for which the chart is requested
|
||||
(e.g., 'week', 'month', 'quarter', 'year').
|
||||
:return: A response containing the chart URL or an error message if parameters are invalid.
|
||||
"""
|
||||
if not from_currency or not conv_currency:
|
||||
response.status_code = status.HTTP_400_BAD_REQUEST
|
||||
return {
|
||||
'status': status.HTTP_400_BAD_REQUEST,
|
||||
'message': 'The from_currency and conv_currency fields are required.',
|
||||
}
|
||||
|
||||
if period not in ['week', 'month', 'quarter', 'year']:
|
||||
response.status_code = status.HTTP_400_BAD_REQUEST
|
||||
return {'message': 'Invalid period.', 'status_code': status.HTTP_400_BAD_REQUEST}
|
||||
|
||||
days, month, years = 0, 0, 0
|
||||
|
||||
if period == 'week':
|
||||
days = -7
|
||||
elif period == 'month':
|
||||
month = -1
|
||||
elif period == 'quarter':
|
||||
month = -3
|
||||
elif period == 'year':
|
||||
years = -1
|
||||
|
||||
end_date = datetime.now()
|
||||
start_date = end_date + relativedelta(months=month, days=days, years=years)
|
||||
|
||||
chart = await create_chart(
|
||||
from_currency,
|
||||
conv_currency,
|
||||
start_date.strftime('%Y-%m-%d'),
|
||||
end_date.strftime('%Y-%m-%d')
|
||||
)
|
||||
|
||||
return await prepare_chart_response(response, request, chart)
|
||||
|
||||
|
||||
async def prepare_chart_response(
|
||||
response: Response,
|
||||
request: Request,
|
||||
chart_name: str
|
||||
):
|
||||
"""
|
||||
Prepares the response to return the URL of the generated chart.
|
||||
|
||||
If the chart data is not found, it returns a 404 error with an appropriate message.
|
||||
Otherwise, it returns a URL to access the chart image.
|
||||
|
||||
:param response: The response object used to set status and message.
|
||||
:param request: The request object used to retrieve details of the incoming request.
|
||||
:param chart_name: The name of the generated chart (used to build the URL).
|
||||
:return: A dictionary with the chart URL or an error message if no chart is found.
|
||||
"""
|
||||
if not chart_name:
|
||||
response.status_code = status.HTTP_404_NOT_FOUND
|
||||
return {'message': 'No data found.', 'status_code': status.HTTP_404_NOT_FOUND}
|
||||
|
||||
host = request.headers.get("host")
|
||||
url_schema = request.url.scheme
|
||||
|
||||
return {
|
||||
'status': status.HTTP_201_CREATED,
|
||||
'message': f'{url_schema}://{host}/static/charts/{chart_name}.png',
|
||||
}
|
20
chart/utils/load_config.py
Normal file
20
chart/utils/load_config.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
"""
|
||||
Parsing and converting HJSON config to JSON
|
||||
"""
|
||||
import hjson
|
||||
import json
|
||||
|
||||
def load_config(file_path: str) -> dict:
|
||||
"""
|
||||
Load an HJSON file, convert it to a JSON string with indentation,
|
||||
and return it.
|
||||
|
||||
params: file_path (str): The path to the HJSON file.
|
||||
|
||||
returns str: The JSON string formatted with indentation.
|
||||
"""
|
||||
with open(file_path, 'r', encoding='utf-8') as file:
|
||||
hjson_data = hjson.load(file)
|
||||
return json.loads(
|
||||
json.dumps(hjson_data, indent=4)
|
||||
)
|
7
collect-currency/README.md
Normal file
7
collect-currency/README.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
This microservice collects currency exchange rate data for selected currencies from open sources and stores it in a database.
|
||||
|
||||
## Third-Party Libraries and Licenses
|
||||
|
||||
- [axios/axios](https://github.com/axios/axios) — [MIT](https://github.com/axios/axios/blob/v1.x/LICENSE)
|
||||
- [GuillaumeRochat/cron-validator](https://github.com/GuillaumeRochat/cron-validator) - [MIT](https://github.com/GuillaumeRochat/cron-validator/blob/master/LICENSE)
|
||||
- [node-schedule/node-schedule](https://github.com/node-schedule/node-schedule) - [MIT](https://github.com/node-schedule/node-schedule/blob/master/LICENSE)
|
|
@ -3,6 +3,7 @@ const config = require('../shared/config/src/main.js')();
|
|||
const cron = require('cron-validator');
|
||||
|
||||
const save_fiat = require('./save_fiat');
|
||||
const save_crypto = require('./save_crypto');
|
||||
const logger = require('../shared/logger/src/main.js');
|
||||
|
||||
async function validateSchedule(schedule) {
|
||||
|
@ -12,14 +13,15 @@ async function validateSchedule(schedule) {
|
|||
}
|
||||
|
||||
async function initialize() {
|
||||
await require('../shared/database/src/create_table')();
|
||||
//await require('../shared/database/src/create_table')();
|
||||
}
|
||||
|
||||
async function runTasks() {
|
||||
await Promise.all([save_fiat()]);
|
||||
await Promise.all([save_fiat(), save_crypto()]);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log(config['currency']['collecting']['schedule'])
|
||||
await initialize();
|
||||
await validateSchedule(config['currency']['collecting']['schedule']);
|
||||
|
||||
|
@ -31,8 +33,6 @@ async function main() {
|
|||
);
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
logger.error('Error in main execution:', err);
|
||||
});
|
||||
main();
|
||||
|
||||
module.exports = { main };
|
||||
|
|
47
collect-currency/save_crypto.js
Normal file
47
collect-currency/save_crypto.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
const pool = require('../shared/database/src/postgresql.js');
|
||||
const axios = require('axios');
|
||||
const config = require('../shared/config/src/main.js')();
|
||||
const logger = require('../shared/logger/src/main.js');
|
||||
|
||||
async function save_crypto() {
|
||||
if (!config['currency']['collecting']['crypto']) return;
|
||||
|
||||
config['currency']['crypto'].forEach((value) =>
|
||||
config['currency']['crypto'].forEach(async (pair) => {
|
||||
if (value === pair) return;
|
||||
|
||||
await axios.get('https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest', {
|
||||
timeout: 3000,
|
||||
params: {
|
||||
'symbol': value,
|
||||
'convert': pair,
|
||||
},
|
||||
headers: {
|
||||
'X-CMC_PRO_API_KEY': config['currency']['collecting']['crypto_apikey'],
|
||||
}
|
||||
})
|
||||
.then(async (res) => {
|
||||
const data = res['data']['data'][value]['quote'][pair];
|
||||
|
||||
logger.debug(JSON.stringify(data, null, 4));
|
||||
|
||||
const point = data['price'].toString().indexOf('.') + 4;
|
||||
|
||||
await pool.query(
|
||||
`INSERT INTO currency (from_currency, conv_currency, rate, date) VALUES ($1, $2, $3, $4)`,
|
||||
[
|
||||
value,
|
||||
pair,
|
||||
data['price'].toString().slice(0, point),
|
||||
new Date(data['last_updated']).toISOString().substring(0, 10),
|
||||
],
|
||||
);
|
||||
})
|
||||
.catch((err) => {
|
||||
logger.error(err);
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = save_crypto;
|
|
@ -31,17 +31,6 @@ async function save_fiat() {
|
|||
const point =
|
||||
data['to'][0]['mid'].toString().indexOf('.') + 4;
|
||||
|
||||
const db = await pool.query(
|
||||
'SELECT * FROM currency WHERE ' +
|
||||
'from_currency = $1 AND conv_currency = $2 AND date = $3',
|
||||
[
|
||||
value,
|
||||
pair,
|
||||
new Date(data['timestamp']).toISOString().substring(0, 10),
|
||||
],
|
||||
);
|
||||
|
||||
if (db['rows'][0]) return;
|
||||
await pool.query(
|
||||
`INSERT INTO currency (from_currency, conv_currency, rate, date) VALUES ($1, $2, $3, $4)`,
|
||||
[
|
||||
|
|
57
config.example.hjson
Normal file
57
config.example.hjson
Normal 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
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,36 +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]
|
||||
schedule: '30 8 * * *' # Currency collection schedule in crontab format [String]
|
||||
fiat: # List of fiat currency to save the exchange rate [Array]
|
||||
- USD
|
||||
- RUB
|
||||
- EUR
|
||||
- UAH
|
||||
- TRY
|
||||
- KZT
|
|
@ -1,16 +1,19 @@
|
|||
services:
|
||||
nginx:
|
||||
image: nginx:latest
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- '80:80'
|
||||
- '443:443'
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf
|
||||
- ./nginx/:/etc/nginx/
|
||||
- ./CertSSL:/etc/nginx/ssl
|
||||
- ./robots.txt:/etc/nginx/robots.txt
|
||||
- ./assets/logo.png:/etc/nginx/assets/logo.png
|
||||
- ./docs/dist:/etc/nginx/dist
|
||||
depends_on:
|
||||
- server
|
||||
- chart
|
||||
- web
|
||||
- docs
|
||||
|
||||
server:
|
||||
build:
|
||||
|
@ -21,7 +24,7 @@ services:
|
|||
- '3000:3000'
|
||||
volumes:
|
||||
- './CertSSL:/CertSSL'
|
||||
- './config.yaml:/config.yaml'
|
||||
- './config.hjson:/config.hjson'
|
||||
depends_on:
|
||||
- postgres
|
||||
|
||||
|
@ -33,18 +36,34 @@ services:
|
|||
- '3030:3030'
|
||||
volumes:
|
||||
- './CertSSL:/CertSSL'
|
||||
- './config.yaml:/config.yaml'
|
||||
- './config.hjson:/config.hjson'
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
|
||||
web:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: Dockerfile-web
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- '3050:3050'
|
||||
volumes:
|
||||
- './CertSSL:/CertSSL'
|
||||
- './config.hjson:/config.hjson'
|
||||
|
||||
docs:
|
||||
build:
|
||||
context: ./docs
|
||||
dockerfile: Dockerfile
|
||||
|
||||
collect-currency:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: Dockerfile-collect-currency
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- './config.yaml:/config.yaml'
|
||||
- './config.hjson:/config.hjson'
|
||||
depends_on:
|
||||
- postgres
|
||||
|
||||
|
|
21
docs/.gitignore
vendored
Normal file
21
docs/.gitignore
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
# build output
|
||||
dist/
|
||||
# generated types
|
||||
.astro/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
|
||||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
9
docs/Dockerfile
Normal file
9
docs/Dockerfile
Normal file
|
@ -0,0 +1,9 @@
|
|||
FROM node:20-alpine
|
||||
|
||||
WORKDIR /
|
||||
|
||||
COPY ./package*.json .
|
||||
|
||||
RUN npm install
|
||||
|
||||
CMD ["npm", "run", "build"]
|
66
docs/astro.config.mjs
Normal file
66
docs/astro.config.mjs
Normal file
|
@ -0,0 +1,66 @@
|
|||
// @ts-check
|
||||
import { defineConfig } from 'astro/config';
|
||||
import starlight from '@astrojs/starlight';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
integrations: [
|
||||
starlight({
|
||||
title: 'Kekkai',
|
||||
social: { github: 'https://github.com/redume/kekkai' },
|
||||
editLink: { baseUrl: 'https://github.com/redume/kekkai/edit/main/docs/' },
|
||||
sidebar: [
|
||||
{
|
||||
label: 'Getting started',
|
||||
items: [
|
||||
{
|
||||
label: 'Docker',
|
||||
slug: 'docs/getting-started/docker',
|
||||
badge: 'recommended',
|
||||
},
|
||||
{
|
||||
label: 'Contributing', slug: 'docs/getting-started/contributing'
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Endpoints',
|
||||
items: [
|
||||
{
|
||||
label: 'Endpoints list', slug: 'docs/endpoints/endpoints-list'
|
||||
},
|
||||
{
|
||||
label: 'Get currency rate - /api/getRate',
|
||||
slug: 'docs/endpoints/getrate'
|
||||
},
|
||||
{
|
||||
label: 'Create charts - /api/getChart',
|
||||
slug: 'docs/endpoints/create-chart'
|
||||
},
|
||||
{
|
||||
label: 'Get metadata - /api/metadata',
|
||||
slug: 'docs/endpoints/metadata'
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Config',
|
||||
items: [
|
||||
{
|
||||
label: 'Configure .env',
|
||||
slug: 'docs/config/config-env'
|
||||
},
|
||||
{
|
||||
label: 'Configure config.hjson',
|
||||
slug: 'docs/config/config-hjson'
|
||||
},
|
||||
{
|
||||
label: 'Configure Nginx',
|
||||
slug: 'docs/config/config-nginx'
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
|
@ -1,27 +0,0 @@
|
|||
Kekkai is used by `Nginx` to redirect to the correct microservice.
|
||||
|
||||
## Change domain
|
||||
|
||||
Change localhost to your `domain` or `ipv4` based on your needs. If you need to use Kekkai locally, you don't need to change anything,
|
||||
|
||||
??? note
|
||||
```
|
||||
...
|
||||
server_name localhost; # Your domain
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
## Change name for SSL
|
||||
This is where the name of the SSL files changes. This needs to be edited if you have a different one
|
||||
|
||||
Change `privkey.pem` and `fullchain.pem` to the names of the files you have.
|
||||
|
||||
??? note
|
||||
```
|
||||
...
|
||||
ssl_certificate /etc/nginx/ssl/fullchain.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
|
||||
...
|
||||
```
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
Kekkai can be configured using the `.env` file in the working directory. `.env.example`.
|
||||
|
||||
`.env` config is used to configure PosgreSQL running in Docker Compose.
|
||||
|
||||
!!! info
|
||||
If you are not using Docker Compose, you do not need to edit this config
|
||||
|
||||
??? note "Example file `.env.example`"
|
||||
```
|
||||
# Connection secret for postgres. You should change it to a random password
|
||||
# Please use only the characters `A-Za-z0-9`, without special characters or spaces
|
||||
|
||||
POSTGRES_PASSWORD=my_password
|
||||
|
||||
# If you do not know what you are doing, then you should not edit the values below
|
||||
###################################################################################
|
||||
POSTGRES_DB=kekkai
|
||||
DB_HOST=postgres
|
||||
POSTGRES_USER=postgres
|
||||
```
|
||||
|
||||
This config only edits the password for PosgreSQL.
|
||||
|
||||
Please use only the characters `A-Za-z0-9`, without special characters or spaces
|
|
@ -1,117 +0,0 @@
|
|||
Kekkai can be configured using the `config.yaml` file in the working directory. `config.example.yaml`.
|
||||
|
||||
??? "Example file `config.example.yaml`"
|
||||
```
|
||||
# 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: true
|
||||
currency:
|
||||
chart:
|
||||
save: false
|
||||
collecting:
|
||||
fiat: true
|
||||
schedule: '30 8 * * *'
|
||||
fiat:
|
||||
- USD
|
||||
- RUB
|
||||
- EUR
|
||||
- UAH
|
||||
- TRY
|
||||
- KZT
|
||||
```
|
||||
|
||||
## Database
|
||||
Kekkai is used as a `PostgreSQL` database.
|
||||
|
||||
!!! info
|
||||
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`.
|
||||
|
||||
What should it look like:
|
||||
```yaml
|
||||
database:
|
||||
...
|
||||
host: 'postgres'
|
||||
...
|
||||
...
|
||||
```
|
||||
|
||||
## Server
|
||||
!!! info
|
||||
If you installed Kekkai via Docker Compose, then changing `server.host`, `server.ssl` is not recommended.
|
||||
|
||||
### SSL
|
||||
Create a folder CertSSL to store your certificates
|
||||
```shell
|
||||
mkdir CertSSL
|
||||
```
|
||||
|
||||
Copy your certificates to the CertSSL folder.
|
||||
|
||||
It is recommended to rename the certificates to `privkey.pem` and `fullchain.pem`. If this is not possible, you need to change the SSL name in `nginx.conf` (if using Docker Compose)
|
||||
|
||||
## Analytics
|
||||
Kekkai uses [`Plausbile`](https://plausible.io/) as an analyst. Minimal data is transferred for anilithics. Such as: browser, OS, status code, url, where the user came from. Most of the data is built on User Agent. It is possible to disable analytics in Kekkai.
|
||||
|
||||
|
||||
??? note
|
||||
```yaml
|
||||
...
|
||||
analytics:
|
||||
plausible_api: 'https://plausible.io/api/event/'
|
||||
plausible_domain: 'PLAUSIBLE_DOMAIN'
|
||||
plausible_token: 'PLAUSIBLE_TOKEN'
|
||||
work: true
|
||||
...
|
||||
```
|
||||
|
||||
- `plausible_api`: This is where the Plausible instance is specified. The official instance is specified by default.
|
||||
- `plausible_domain`: Kekkai Instance Domain. It should be added to Plausible first, and then to the config. 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.
|
||||
|
||||
## Currency
|
||||
`DuckDuckGo` (fiat currency collection) is used to collect currency rates.
|
||||
|
||||
??? note
|
||||
```yaml
|
||||
...
|
||||
currency:
|
||||
chart:
|
||||
save: false
|
||||
collecting:
|
||||
fiat: true
|
||||
schedule: '30 8 * * *'
|
||||
fiat:
|
||||
- USD
|
||||
- RUB
|
||||
- EUR
|
||||
- UAH
|
||||
- TRY
|
||||
- KZT
|
||||
```
|
||||
|
||||
- `currency.chart.save`: Enable or disable saving graphs.
|
||||
- `currency.collecting`: Enable or disable cryptocurrency or fiat currency exchange rate collection.
|
||||
- `currency.schedule`: Currency collection interval (Configurable via cron. It is recommended to use [crontab.guru](https://crontab.guru), not supported in `Non-standard format`, like `@daily`).
|
||||
- `currency.fiat`: A list of fiat currencies that will be saved to the database.
|
|
@ -1,130 +0,0 @@
|
|||
Creating a currency rate chart.
|
||||
|
||||
## Creating a graph for a certain period
|
||||
|
||||
### Request
|
||||
=== "Shell"
|
||||
=== "Curl"
|
||||
```bash
|
||||
curl --request GET \
|
||||
--url https://kekkai-api.redume.su/api/getChart/week?from_currency=RUB&conv_currency=USD
|
||||
```
|
||||
=== "Python"
|
||||
=== "Request"
|
||||
```python
|
||||
import requests
|
||||
|
||||
res = requests.get('https://kekkai-api.redume.su/api/getChart/week', {
|
||||
'from_currency': 'USD',
|
||||
'conv_currency': 'RUB',
|
||||
}, timeout=3)
|
||||
|
||||
print(res.json())
|
||||
```
|
||||
|
||||
=== "Node.JS"
|
||||
=== "Axios"
|
||||
```js
|
||||
const axios = require('axios');
|
||||
|
||||
axios.get('https://kekkai-api.redume.su/api/getChart/week', {
|
||||
timeout: 3000,
|
||||
'from_currency': 'USD',
|
||||
'conv_currency': 'RUB',
|
||||
})
|
||||
.then((res) => {
|
||||
console.log(res['data']);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
```
|
||||
|
||||
### Query params
|
||||
| Parameter | Description |
|
||||
|---------------|-------------------------------------------------------------------------|
|
||||
| `from_currency` | `ISO 4217` code of the currency from which the conversion takes place |
|
||||
| `conv_currency` | `ISO 4217` code of the currency to which the conversion is performed |
|
||||
|
||||
### URL params
|
||||
| Parameter | Description |
|
||||
|---------------|-------------------------------------------------------------------------|
|
||||
| `period` | Available parameters: `week`, `month`, `quarter`, `year` |
|
||||
|
||||
### Response
|
||||
!!! info "Output"
|
||||
```json
|
||||
{
|
||||
"status": 201,
|
||||
"message": "http://kekkai-api.redume.su/static/charts/RUB_USD_20241108_DQVDN7.png"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Creating a schedule for specific days
|
||||
|
||||
### Request
|
||||
=== "Shell"
|
||||
=== "Curl"
|
||||
```bash
|
||||
curl --request GET \
|
||||
--url https://kekkai-api.redume.su/api/getChart/?from_currency=RUB&conv_currency=USD&start_date=2024-10-31&end_date=2024-11-08
|
||||
```
|
||||
=== "Python"
|
||||
=== "Request"
|
||||
```python
|
||||
import requests
|
||||
|
||||
res = requests.get('https://kekkai-api.redume.su/api/getChart/', {
|
||||
'from_currency': 'USD',
|
||||
'conv_currency': 'RUB',
|
||||
'start_date': '2024-10-31',
|
||||
'end_date': '2024-11-08'
|
||||
}, timeout=3)
|
||||
|
||||
print(res.json())
|
||||
```
|
||||
|
||||
=== "Node.JS"
|
||||
=== "Axios"
|
||||
```js
|
||||
const axios = require('axios');
|
||||
|
||||
axios.get('https://kekkai-api.redume.su/api/getChart/', {
|
||||
timeout: 3000,
|
||||
'from_currency': 'USD',
|
||||
'conv_currency': 'RUB',
|
||||
'start_date': '2024-10-31',
|
||||
'end_date': '2024-11-08'
|
||||
})
|
||||
.then((res) => {
|
||||
console.log(res['data']);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
```
|
||||
|
||||
### Query params
|
||||
| Parameter | Description |
|
||||
|---------------|-------------------------------------------------------------------------|
|
||||
| `from_currency` | `ISO 4217` code of the currency from which the conversion takes place |
|
||||
| `conv_currency` | `ISO 4217` code of the currency to which the conversion is performed |
|
||||
| `start_date` | Start date of the period in the format `YYYYY-DD-MM` |
|
||||
| `end_date` | Period end date in the format `YYYYY-DD-MM` |
|
||||
|
||||
### Response
|
||||
!!! info "Output"
|
||||
```json
|
||||
{
|
||||
"status": 400,
|
||||
"message": "http://kekkai-api.redume.su/static/charts/RUB_USD_20241108_1T2RI3.png"
|
||||
}
|
||||
```
|
||||
|
||||
## What the name of the chart file consists of
|
||||
Example: ``.../RUB_USD_20241108_DQVDN7.png``
|
||||
|
||||
- `RUB_USD` - Name of currencies.
|
||||
- `20241108` - Schedule request date in `YYYMMDD` format.
|
||||
- `DQVDN7` - Random file character identifier.
|
|
@ -1,159 +0,0 @@
|
|||
Currencies are identified by standard three-letter `ISO 4217` currency codes.
|
||||
|
||||
## Getting the currency rate for a certain day.
|
||||
|
||||
### Request
|
||||
=== "Shell"
|
||||
=== "Curl"
|
||||
```bash
|
||||
curl --request GET \
|
||||
--url https://kekkai-api.redume.su/api/getRate/?from_currency=RUB&conv_currency=USD&date=2024-10-16
|
||||
```
|
||||
|
||||
=== "Python"
|
||||
=== "Requests"
|
||||
```py
|
||||
import requests
|
||||
|
||||
res = requests.get('https://kekkai-api.redume.su/api/getRate/', {
|
||||
'from_currency': 'RUB',
|
||||
'conv_currency': 'USD',
|
||||
'date': '2024-10-16',
|
||||
}, timeout=3)
|
||||
|
||||
print(res.json())
|
||||
```
|
||||
|
||||
|
||||
=== "Node.JS"
|
||||
=== "Axios"
|
||||
```js
|
||||
const axios = require('axios');
|
||||
|
||||
axios.get('https://kekkai-api.redume.su/api/getRate/', {
|
||||
timeout: 3000,
|
||||
'from_currency': 'RUB',
|
||||
'conv_currency': 'USD',
|
||||
'date': '2024-10-16',
|
||||
}
|
||||
)
|
||||
.then((res) => {
|
||||
console.log(JSON.stringify(res.json()));
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
```
|
||||
|
||||
### Query Parameters
|
||||
| Parameter | Description |
|
||||
|---------------|-------------------------------------------------------------------------|
|
||||
| `from_currency` | `ISO 4217` code of the currency from which the conversion takes place |
|
||||
| `conv_currency` | `ISO 4217` code of the currency to which the conversion is performed |
|
||||
| `date` | Currency rate date in the format `YYYYY-DD-MM` |
|
||||
|
||||
### Response
|
||||
!!! info "Output"
|
||||
```json
|
||||
[
|
||||
{
|
||||
"from_currency": "RUB",
|
||||
"conv_currency": "USD",
|
||||
"rate": 0.01,
|
||||
"date": "2024-10-17T00:00:00.000Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Get currency exchange rate for a certain period
|
||||
Getting the list of the array with currency rate for a certain period of time.
|
||||
|
||||
### Request
|
||||
=== "Shell"
|
||||
=== "Curl"
|
||||
```bash
|
||||
curl --request GET \
|
||||
--url https://kekkai-api.redume.su/api/getRate/?from_currency=RUB&conv_currency=USD&start_date=2024-10-16&end_date=2024-10-20
|
||||
```
|
||||
|
||||
=== "Python"
|
||||
=== "Requests"
|
||||
```py
|
||||
import requests
|
||||
|
||||
res = requests.get('https://kekkai-api.redume.su/api/getRate/', {
|
||||
'from_currency': 'RUB',
|
||||
'conv_currency': 'USD',
|
||||
'start_date': '2024-10-16',
|
||||
'end_date': '2024-10-20',
|
||||
}, timeout=3)
|
||||
|
||||
print(res.json())
|
||||
```
|
||||
|
||||
|
||||
=== "Node.JS"
|
||||
=== "Axios"
|
||||
```js
|
||||
const axios = require('axios');
|
||||
|
||||
axios.get('https://kekkai-api.redume.su/api/getRate/', {
|
||||
timeout: 3000,
|
||||
'from_currency': 'RUB',
|
||||
'conv_currency': 'USD',
|
||||
'start_date': '2024-10-16',
|
||||
'end_date': '2024-10-20',
|
||||
}
|
||||
)
|
||||
.then((res) => {
|
||||
console.log(res['data']);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
```
|
||||
|
||||
### Query params
|
||||
| Parameter | Description |
|
||||
|---------------|-------------------------------------------------------------------------|
|
||||
| `from_currency` | `ISO 4217` code of the currency from which the conversion takes place |
|
||||
| `conv_currency` | `ISO 4217` code of the currency to which the conversion is performed |
|
||||
| `start_date` | Start date of the period in the format `YYYYY-DD-MM` |
|
||||
| `end_date` | Period end date in the format `YYYYY-DD-MM` |
|
||||
|
||||
### Response
|
||||
!!! info "Output"
|
||||
```json
|
||||
[
|
||||
{
|
||||
"from_currency": "RUB",
|
||||
"conv_currency": "USD",
|
||||
"rate": 0.01,
|
||||
"date": "2024-10-17T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"from_currency": "RUB",
|
||||
"conv_currency": "USD",
|
||||
"rate": 0.01,
|
||||
"date": "2024-10-18T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"from_currency": "RUB",
|
||||
"conv_currency": "USD",
|
||||
"rate": 0.01,
|
||||
"date": "2024-10-19T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"from_currency": "RUB",
|
||||
"conv_currency": "USD",
|
||||
"rate": 0.01,
|
||||
"date": "2024-10-20T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"from_currency": "RUB",
|
||||
"conv_currency": "USD",
|
||||
"rate": 0.01,
|
||||
"date": "2024-10-21T00:00:00.000Z"
|
||||
}
|
||||
]
|
||||
```
|
|
@ -1,16 +0,0 @@
|
|||
## API Base URL
|
||||
All requests to our API should be directed to the URL below:
|
||||
|
||||
```
|
||||
https://kekkai-api.redume.su/api/
|
||||
```
|
||||
|
||||
|
||||
## API Endpoints
|
||||
Kekkai has 3 API endpoints: `getRate`, `getChart` and `configurations`. Below you will find a list of parameters that each endpoint requires and a description of what the API does.
|
||||
|
||||
| Service | API Endpoint | Description |
|
||||
|--------------|--------------------------------------------------------|--------------------------------------------------------------------|
|
||||
| Get Rate | `https://kekkai-api.redume.su/api/getRate/` | Get currency exchange rate for a specific day or period |
|
||||
| Create Chart | `https://kekkai-api.redume.su/api/getChart` | Creating a chart with exchange rate |
|
||||
| Get Config | `https://kekkai-api.redume.su/api/configurations/json` | Get instance config file |
|
|
@ -1,76 +0,0 @@
|
|||
Docker Compose is the recommended method to run Kekkai in production. Below are the steps to deploy Kekkai with Docker Compose.
|
||||
|
||||
Kekkai requires Docker Compose version 2.x.
|
||||
|
||||
### Steps 1 - Preparing files
|
||||
```shell
|
||||
git clone https://github.com/Redume/Kekkai.git
|
||||
```
|
||||
|
||||
```shell
|
||||
cd Kekkai
|
||||
```
|
||||
|
||||
|
||||
### Steps 2 - Change config files
|
||||
??? note "Nginx Configuration"
|
||||
In `nginx.conf`, you need to specify your domain or ipv4 address
|
||||
```
|
||||
...
|
||||
listen 443 ssl;
|
||||
server_name localhost; # Your domain
|
||||
...
|
||||
```
|
||||
|
||||
To set up SSL
|
||||
```bash
|
||||
mkdir CertSLL
|
||||
```
|
||||
|
||||
After that, copy the SSL certificates to the `CertSSL` folder, if the names are different,
|
||||
then change either the name of the certificates or in `nginx.conf`
|
||||
|
||||
```
|
||||
ssl_certificate /etc/nginx/ssl/fullchain.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
|
||||
```
|
||||
|
||||
??? note "The main config is `config.sample.yaml` for Kekkai"
|
||||
```yaml
|
||||
database:
|
||||
user: 'DATABASE_USERNAME'
|
||||
password: 'DATABASE_PASSWORD'
|
||||
host: 'DATABASE_HOST'
|
||||
name: 'DATABASE_NAME'
|
||||
port: 5432
|
||||
...
|
||||
```
|
||||
|
||||
Fill in the data in the `database` item, as well as in the `.env` config
|
||||
|
||||
|
||||
??? note "`.env.sample` config for PostgreSQL"
|
||||
```.env
|
||||
# Connection secret for postgres. You should change it to a random password
|
||||
# Please use only the characters `A-Za-z0-9`, without special characters or spaces
|
||||
|
||||
POSTGRES_PASSWORD=my_password
|
||||
|
||||
# If you do not know what you are doing, then you should not edit the values below
|
||||
###################################################################################
|
||||
POSTGRES_DB=kekkai
|
||||
DB_HOST=postgres
|
||||
POSTGRES_USER=postgres
|
||||
```
|
||||
|
||||
- Populate custom database information if necessary.
|
||||
- Consider changing `DB_PASSWORD` to a custom value. Postgres is not publically exposed, so this password is only used for - local authentication. To avoid issues with Docker parsing this value, it is best to use only the characters `A-Za-z0-9`.
|
||||
|
||||
!!! note
|
||||
After editing, rename the config files by removing `.sample` in the name
|
||||
|
||||
|
||||
### Steps 3 - Start the containers
|
||||
```shell title='Start the containers using docker compose command'
|
||||
docker compose -f "docker-compose.yaml" up -d --build
|
||||
```
|
|
@ -1,72 +0,0 @@
|
|||
For full use, you need to install `Node.JS v20`, `PostgreSQL v15`, `Python v13.3`, `Nginx`
|
||||
|
||||
### Steps 1 - Preparing files
|
||||
|
||||
```shell
|
||||
git clone https://github.com/Redume/Kekkai.git
|
||||
```
|
||||
|
||||
```shell
|
||||
cd Kekkai
|
||||
```
|
||||
|
||||
### Steps 2 - Change config files
|
||||
|
||||
??? note "Nginx Configuration"
|
||||
In `nginx.conf`, you need to specify your domain or ipv4 address
|
||||
```nginx
|
||||
...
|
||||
listen 443 ssl;
|
||||
server_name localhost; # Your domain
|
||||
...
|
||||
```
|
||||
|
||||
??? note "The main config is `config.sample.yaml` for Kekkai"
|
||||
```yaml
|
||||
database:
|
||||
user: 'DATABASE_USERNAME'
|
||||
password: 'DATABASE_PASSWORD'
|
||||
host: 'DATABASE_HOST'
|
||||
name: 'DATABASE_NAME'
|
||||
port: 5432
|
||||
...
|
||||
```
|
||||
|
||||
Fill in the data in the `database` item, as well as in the `.env` config
|
||||
|
||||
### Steps 3 - Install libs
|
||||
|
||||
Install library. In `/shared/logger`, `/shared/config`, `/shared/database`, `/collect-currency`,` /server`,
|
||||
the required node.JS libraries In each of the directories, you need to write this command
|
||||
|
||||
```shell
|
||||
npm install
|
||||
```
|
||||
|
||||
and install python libs
|
||||
```shell
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
Start the nginx service
|
||||
```shell
|
||||
sudo systemctl start nginx.service
|
||||
```
|
||||
|
||||
### Steps 4 - Launch Services
|
||||
|
||||
Launch each of the services
|
||||
There are all three services `MainService`, `Collect-currency`, `Chart`
|
||||
|
||||
- `MainService` is an API for getting the exchange rate
|
||||
```shell
|
||||
cd server & node .
|
||||
```
|
||||
- `Collect-Currency` is a service for collecting and save the rate in a database
|
||||
```shell
|
||||
cd collect-currency/src/ && node .
|
||||
```
|
||||
- `ChartService` is a service for creating currency rate charts
|
||||
```shell
|
||||
python3 main.py
|
||||
```
|
6933
docs/package-lock.json
generated
Normal file
6933
docs/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
17
docs/package.json
Normal file
17
docs/package.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "docs",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"start": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/starlight": "^0.32.1",
|
||||
"astro": "^5.1.5",
|
||||
"sharp": "^0.32.5"
|
||||
}
|
||||
}
|
36
docs/public/favicon.svg
Normal file
36
docs/public/favicon.svg
Normal file
|
@ -0,0 +1,36 @@
|
|||
<svg width="157" height="122" viewBox="0 0 157 122" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_d_179_28)">
|
||||
<path d="M54.8999 97V7.39999H79.7319V43.752L110.58 7.39999H140.02L106.1 45.928L141.172 97H111.348L88.8199 63.72L79.7319 73.832V97H54.8999Z" fill="#297BCD"/>
|
||||
</g>
|
||||
<g opacity="0.7" filter="url(#filter1_dd_179_28)">
|
||||
<path d="M38.76 109.905V97.1055C25.6187 95.7401 14.0987 91.5161 4.2 84.4334L15.72 67.4094C23.912 73.1268 31.848 76.6254 39.528 77.9054V62.5454C28.776 60.0708 20.8827 56.7428 15.848 52.5614C10.8987 48.3801 8.424 42.4068 8.424 34.6414C8.424 26.9614 11.112 20.7321 16.488 15.9534C21.864 11.0894 29.3733 8.31611 39.016 7.63345V0.337448L52.328 0.337448V8.01745C62.1413 9.12678 71.144 12.4548 79.336 18.0014L68.84 35.1534C63.8053 31.5694 58.088 29.0095 51.688 27.4734V42.3214C62.2693 44.7961 69.992 48.1668 74.856 52.4334C79.8053 56.7001 82.28 62.7161 82.28 70.4814C82.28 78.1614 79.5493 84.3481 74.088 89.0414C68.712 93.7348 61.3733 96.5081 52.072 97.3615V109.905H38.76ZM51.56 78.6734C57.2773 78.0761 60.136 75.8148 60.136 71.8894C60.136 70.0974 59.496 68.6894 58.216 67.6654C57.0213 66.5561 54.8027 65.5321 51.56 64.5934V78.6734ZM39.656 40.1454V26.3214C33.8533 26.8334 30.952 29.0521 30.952 32.9774C30.952 34.6841 31.5493 36.0921 32.744 37.2014C34.024 38.2254 36.328 39.2068 39.656 40.1454Z" fill="#297BCD"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_179_28" x="54.8999" y="7.40002" width="101.372" height="101.7" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="10" dy="7"/>
|
||||
<feGaussianBlur stdDeviation="2.55"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_179_28"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_179_28" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter1_dd_179_28" x="0.199951" y="0.337402" width="97.1801" height="121.668" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="10" dy="7"/>
|
||||
<feGaussianBlur stdDeviation="2.55"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_179_28"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="4"/>
|
||||
<feGaussianBlur stdDeviation="2"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
|
||||
<feBlend mode="normal" in2="effect1_dropShadow_179_28" result="effect2_dropShadow_179_28"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_179_28" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 3.1 KiB |
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"yaml.schemas": {
|
||||
"https://squidfunk.github.io/mkdocs-material/schema.json": "mkdocs.yml"
|
||||
},
|
||||
"yaml.customTags": [
|
||||
"!ENV scalar",
|
||||
"!ENV sequence",
|
||||
"!relative scalar",
|
||||
"tag:yaml.org,2002:python/name:material.extensions.emoji.to_svg",
|
||||
"tag:yaml.org,2002:python/name:material.extensions.emoji.twemoji",
|
||||
"tag:yaml.org,2002:python/name:pymdownx.superfences.fence_code_format",
|
||||
"tag:yaml.org,2002:python/object/apply:pymdownx.slugs.slugify mapping"
|
||||
]
|
||||
}
|
7
docs/src/content.config.ts
Normal file
7
docs/src/content.config.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { defineCollection } from 'astro:content';
|
||||
import { docsLoader } from '@astrojs/starlight/loaders';
|
||||
import { docsSchema } from '@astrojs/starlight/schema';
|
||||
|
||||
export const collections = {
|
||||
docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
|
||||
};
|
31
docs/src/content/docs/docs/config/config-env.mdx
Normal file
31
docs/src/content/docs/docs/config/config-env.mdx
Normal file
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
title: Configure .env
|
||||
---
|
||||
|
||||
import { Aside, Code } from '@astrojs/starlight/components';
|
||||
|
||||
Kekkai can be configured using the `.env` file in the working directory. `.env.example`.
|
||||
|
||||
`.env` config is used to configure PosgreSQL running in Docker Compose.
|
||||
|
||||
<Aside>
|
||||
If you are not using Docker Compose, you do not need to edit this config
|
||||
</Aside>
|
||||
|
||||
<Code code=
|
||||
'
|
||||
# Connection secret for postgres. You should change it to a random password
|
||||
# Please use only the characters `A-Za-z0-9`, without special characters or spaces
|
||||
|
||||
POSTGRES_PASSWORD=my_password
|
||||
|
||||
# If you do not know what you are doing, then you should not edit the values below
|
||||
###################################################################################
|
||||
POSTGRES_DB=kekkai
|
||||
DB_HOST=postgres
|
||||
POSTGRES_USER=postgres
|
||||
' title='.env.example'/>
|
||||
|
||||
This config only edits the password for PosgreSQL.
|
||||
|
||||
Please use only the characters `A-Za-z0-9`, without special characters or spaces
|
182
docs/src/content/docs/docs/config/config-hjson.mdx
Normal file
182
docs/src/content/docs/docs/config/config-hjson.mdx
Normal file
|
@ -0,0 +1,182 @@
|
|||
---
|
||||
title: Configure config.hjson
|
||||
---
|
||||
|
||||
import { Aside } from '@astrojs/starlight/components';
|
||||
|
||||
Kekkai can be configured using the `config.hjson` file in the working directory.
|
||||
`config.example.hjson`.
|
||||
|
||||
```hjson
|
||||
# For more information, see the documentation
|
||||
# https://kekkai-docs.redume.su/
|
||||
|
||||
{
|
||||
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
|
||||
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`,
|
||||
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:
|
||||
```hjson
|
||||
database:
|
||||
{
|
||||
...
|
||||
password: PASSWORD_FROM_ENV
|
||||
host: postgres
|
||||
...
|
||||
}
|
||||
...
|
||||
```
|
||||
</Aside>
|
||||
|
||||
## Server
|
||||
<Aside>
|
||||
If you installed Kekkai via Docker Compose, then changing `server.host`, `
|
||||
server.ssl` is not recommended.
|
||||
</Aside>
|
||||
|
||||
### SSL
|
||||
Create a folder CertSSL to store your certificates
|
||||
```shell
|
||||
mkdir CertSSL
|
||||
```
|
||||
|
||||
Copy your certificates to the CertSSL folder.
|
||||
|
||||
It is recommended to rename the certificates to `privkey.pem` and `fullchain.pem`.
|
||||
If this is not possible,
|
||||
you need to change the SSL name in `nginx.conf` (if using Docker Compose)
|
||||
|
||||
## Analytics
|
||||
Kekkai uses [`Plausbile`](https://plausible.io/) as an analyst.
|
||||
Minimal data is transferred for anilithics.
|
||||
Such as: browser, OS, status code, url, where the user came from.
|
||||
Most of the data is built on User Agent.
|
||||
It is possible to disable analytics in Kekkai.
|
||||
|
||||
```yaml
|
||||
...
|
||||
analytics:
|
||||
plausible_api: 'https://plausible.io/api/event/'
|
||||
plausible_domain: 'PLAUSIBLE_DOMAIN'
|
||||
plausible_token: 'PLAUSIBLE_TOKEN'
|
||||
enabled: true
|
||||
...
|
||||
```
|
||||
|
||||
- `plausible_api`: This is where the Plausible instance is specified.
|
||||
The official instance is specified by default.
|
||||
- `plausible_domain`: Kekkai Instance Domain.
|
||||
It should be added to Plausible first, and then to the config.
|
||||
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).
|
||||
- `enabled`: Enable or disable analytics.
|
||||
|
||||
## Currency
|
||||
`DuckDuckGo` (fiat currency collection) and `CoinMarketCap` (cryptocurrency collection)
|
||||
are used to collect currency rates.
|
||||
|
||||
|
||||
```yaml
|
||||
...
|
||||
currency:
|
||||
{
|
||||
collecting:
|
||||
{
|
||||
fiat: true
|
||||
crypto: false
|
||||
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.
|
||||
- `currency.collecting`: Enable or disable cryptocurrency
|
||||
or fiat currency exchange rate collection.
|
||||
- `currency.schedule`: Currency collection interval (Configurable via cron.
|
||||
It is recommended to use [crontab.guru](https://crontab.guru),
|
||||
not supported in `Non-standard format`, like `@daily`).
|
||||
- `crypto.crypto_apiKey`: API-key from CoinMarketCap service
|
||||
- `currency.fiat`: A list of fiat currencies that will be saved to the database.
|
||||
- `currency.crypto`: A list of crypto currencies that will be saved to the database.
|
32
docs/src/content/docs/docs/config/config-nginx.mdx
Normal file
32
docs/src/content/docs/docs/config/config-nginx.mdx
Normal file
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
title: Nginx configuration
|
||||
---
|
||||
|
||||
import { Code } from '@astrojs/starlight/components';
|
||||
|
||||
Kekkai is used by `Nginx` to redirect to the correct microservice.
|
||||
|
||||
## Change domain
|
||||
|
||||
Change localhost to your `domain` or `ipv4` based on your needs.
|
||||
If you need to use Kekkai locally, you don't need to change anything,
|
||||
|
||||
<Code code='
|
||||
...
|
||||
server_name localhost; # Your domain
|
||||
...
|
||||
'title='nginx.conf'/>
|
||||
|
||||
|
||||
## Change name for SSL
|
||||
This is where the name of the SSL files changes. This needs to be edited if you have a different one
|
||||
|
||||
Change `privkey.pem` and `fullchain.pem` to the names of the files you have.
|
||||
|
||||
<Code code=
|
||||
'
|
||||
...
|
||||
ssl_certificate /etc/nginx/ssl/fullchain.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
|
||||
...
|
||||
' title='nginx.conf'/>
|
172
docs/src/content/docs/docs/endpoints/create-chart.mdx
Normal file
172
docs/src/content/docs/docs/endpoints/create-chart.mdx
Normal file
|
@ -0,0 +1,172 @@
|
|||
---
|
||||
title: Create Charts - /api/getChart
|
||||
---
|
||||
|
||||
Creating a currency rate chart.
|
||||
|
||||
import { Tabs, TabItem, Aside } from '@astrojs/starlight/components';
|
||||
|
||||
|
||||
## Creating a graph for a certain period
|
||||
### Request
|
||||
|
||||
<Tabs>
|
||||
<TabItem label='Shell'>
|
||||
<Tabs>
|
||||
<TabItem label='curl'>
|
||||
```shell
|
||||
curl --request GET --url https://kekkai-api.redume.su/api/getChart/week?from_currency=RUB&conv_currency=USD
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
</TabItem>
|
||||
<TabItem label='Python'>
|
||||
<Tabs>
|
||||
<TabItem label='requests'>
|
||||
```python
|
||||
import requests
|
||||
|
||||
res = requests.get('https://kekkai-api.redume.su/api/getChart/week', {
|
||||
'from_currency': 'USD',
|
||||
'conv_currency': 'RUB',
|
||||
}, timeout=3)
|
||||
|
||||
print(res.json())
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
</TabItem>
|
||||
<TabItem label='Node.JS'>
|
||||
<Tabs>
|
||||
<TabItem label='axios'>
|
||||
```javascript
|
||||
const axios = require('axios');
|
||||
|
||||
axios.get('https://kekkai-api.redume.su/api/getChart/week', {
|
||||
timeout: 3000,
|
||||
'from_currency': 'USD',
|
||||
'conv_currency': 'RUB',
|
||||
})
|
||||
.then((res) => {
|
||||
console.log(res['data']);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
|
||||
|
||||
### Query params
|
||||
| Parameter | Description |
|
||||
|-------------------|-------------------------------------------------------------------------|
|
||||
| `from_currency`* | `ISO 4217` code of the currency from which the conversion takes place |
|
||||
| `conv_currency`* | `ISO 4217` code of the currency to which the conversion is performed |
|
||||
|
||||
### URL params
|
||||
| Parameter | Description |
|
||||
|---------------|-------------------------------------------------------------------------|
|
||||
| `period` | Available parameters: `week`, `month`, `quarter`, `year` |
|
||||
|
||||
`*` - Required arguments
|
||||
|
||||
### Response
|
||||
|
||||
<Aside title='Output'>
|
||||
```json
|
||||
{
|
||||
"status": 201,
|
||||
"message": "http://kekkai-api.redume.su/static/charts/RUB_USD_20241108_DQVDN7.png"
|
||||
}
|
||||
```
|
||||
</Aside>
|
||||
|
||||
## Creating a schedule for specific days
|
||||
### Request
|
||||
|
||||
<Tabs>
|
||||
<TabItem label='Shell'>
|
||||
<Tabs>
|
||||
<TabItem label='curl'>
|
||||
```shell
|
||||
curl --request GET --url https://kekkai-api.redume.su/api/getChart/?from_currency=RUB&conv_currency=USD&start_date=2024-10-31&end_date=2024-11-08
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
</TabItem>
|
||||
<TabItem label='Python'>
|
||||
<Tabs>
|
||||
<TabItem label='requests'>
|
||||
```python
|
||||
import requests
|
||||
|
||||
res = requests.get('https://kekkai-api.redume.su/api/getChart/', {
|
||||
'from_currency': 'USD',
|
||||
'conv_currency': 'RUB',
|
||||
'start_date': '2024-10-31',
|
||||
'end_date': '2024-11-08'
|
||||
}, timeout=3)
|
||||
|
||||
print(res.json())
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
</TabItem>
|
||||
<TabItem label='Node.JS'>
|
||||
<Tabs>
|
||||
<TabItem label='axios'>
|
||||
```javascript
|
||||
const axios = require('axios');
|
||||
|
||||
axios.get('https://kekkai-api.redume.su/api/getChart/', {
|
||||
timeout: 3000,
|
||||
'from_currency': 'USD',
|
||||
'conv_currency': 'RUB',
|
||||
'start_date': '2024-10-31',
|
||||
'end_date': '2024-11-08'
|
||||
})
|
||||
.then((res) => {
|
||||
console.log(res['data']);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Query params
|
||||
| Parameter | Description |
|
||||
|------------------|------------------------------------------------------------------------|
|
||||
| `from_currency`* | `ISO 4217` code of the currency from which the conversion takes place |
|
||||
| `conv_currency`* | `ISO 4217` code of the currency to which the conversion is performed |
|
||||
| `start_date`* | Start date of the period in the format `YYYYY-DD-MM` |
|
||||
| `end_date`* | Period end date in the format `YYYYY-DD-MM` |
|
||||
|
||||
`*` - Required arguments
|
||||
|
||||
### Response
|
||||
|
||||
<Aside title='Output'>
|
||||
```json
|
||||
{
|
||||
"status": 201,
|
||||
"message": "http://kekkai-api.redume.su/static/charts/RUB_USD_20250226_RX02RN.png"
|
||||
}
|
||||
```
|
||||
</Aside>
|
||||
|
||||
## What the name of the chart file consists of
|
||||
Example: ``.../RUB_USD_20241108_DQVDN7.png``
|
||||
|
||||
- `RUB_USD` - Name of currencies.
|
||||
- `20241108` - Schedule request date in `YYYMMDD` format.
|
||||
- `DQVDN7` - Random file character identifier.
|
||||
|
||||
All charts are in the charts folder, which is in the root directory (`/chart`)
|
21
docs/src/content/docs/docs/endpoints/endpoints-list.mdx
Normal file
21
docs/src/content/docs/docs/endpoints/endpoints-list.mdx
Normal file
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
title: Endpoints list
|
||||
---
|
||||
|
||||
## API Base URL
|
||||
All requests to our API should be directed to the URL below:
|
||||
|
||||
```
|
||||
https://kekkai.redume.su/api/
|
||||
```
|
||||
|
||||
|
||||
## API Endpoints
|
||||
Kekkai has 3 API endpoints: `getRate`, `getChart` and `metadata`.
|
||||
Below you will find a list of parameters that each endpoint requires and a description of what the API does.
|
||||
|
||||
| Service | API Endpoint | Description |
|
||||
|--------------|----------------------------------------------|---------------------------------------------------------------------------------------------|
|
||||
| Get Rate | `https://kekkai-api.redume.su/api/getRate/` | Get currency exchange rate for a specific day or period |
|
||||
| Create Chart | `https://kekkai-api.redume.su/api/getChart/` | Creating a chart with exchange rate |
|
||||
| Metadata | `https://kekkai-api.redume.su/api/metadata/` | Shows the last and first dates of currency rate collection, as well as available currencies |
|
199
docs/src/content/docs/docs/endpoints/getrate.mdx
Normal file
199
docs/src/content/docs/docs/endpoints/getrate.mdx
Normal file
|
@ -0,0 +1,199 @@
|
|||
---
|
||||
title: Get currency rate - /api/getRate
|
||||
---
|
||||
|
||||
Currencies are identified by standard three-letter `ISO 4217` codes.
|
||||
|
||||
import { Tabs, TabItem, Aside } from '@astrojs/starlight/components';
|
||||
|
||||
|
||||
## Getting the currency rate for a certain day.
|
||||
### Request
|
||||
|
||||
<Tabs>
|
||||
<TabItem label='Shell'>
|
||||
<Tabs>
|
||||
<TabItem label='curl'>
|
||||
```shell
|
||||
curl --request GET --url https://kekkai-api.redume.su/api/getRate/?from_currency=RUB&conv_currency=USD&date=2024-10-16
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
</TabItem>
|
||||
<TabItem label='Python'>
|
||||
<Tabs>
|
||||
<TabItem label='requests'>
|
||||
```python
|
||||
import requests
|
||||
|
||||
res = requests.get('https://kekkai-api.redume.su/api/getRate/', {
|
||||
'from_currency': 'RUB',
|
||||
'conv_currency': 'USD',
|
||||
'date': '2024-10-16',
|
||||
}, timeout=3)
|
||||
|
||||
print(res.json())
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
</TabItem>
|
||||
<TabItem label='Node.JS'>
|
||||
<Tabs>
|
||||
<TabItem label='axios'>
|
||||
```javascript
|
||||
const axios = require('axios');
|
||||
|
||||
axios.get('https://kekkai-api.redume.su/api/getRate/', {
|
||||
timeout: 3000,
|
||||
'from_currency': 'RUB',
|
||||
'conv_currency': 'USD',
|
||||
'date': '2024-10-16',
|
||||
}
|
||||
)
|
||||
.then((res) => {
|
||||
console.log(JSON.stringify(res.json()));
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Query Parameters
|
||||
| Parameter | Description |
|
||||
|-------------------|------------------------------------------------------------------------|
|
||||
| `from_currency`* | `ISO 4217` code of the currency from which the conversion takes place |
|
||||
| `conv_currency`* | `ISO 4217` code of the currency to which the conversion is performed |
|
||||
| `date`* | Currency rate date in the format `YYYYY-DD-MM` |
|
||||
| `conv_amount` | Multiplier for number conversion (Optional) |
|
||||
|
||||
`*` - Required arguments
|
||||
|
||||
|
||||
|
||||
|
||||
### Response
|
||||
|
||||
<Aside title='Output'>
|
||||
```json
|
||||
[
|
||||
{
|
||||
"from_currency": "RUB",
|
||||
"conv_currency": "USD",
|
||||
"rate": 0.01,
|
||||
"date": "2024-10-17T00:00:00.000Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
</Aside>
|
||||
|
||||
|
||||
## Get currency exchange rate for a certain period
|
||||
Getting the list of the array with currency rate for a certain period of time.
|
||||
|
||||
|
||||
### Request
|
||||
<Tabs>
|
||||
<TabItem label='Shell'>
|
||||
<Tabs>
|
||||
<TabItem label='curl'>
|
||||
```shell
|
||||
curl --request GET --url https://kekkai-api.redume.su/api/getRate/?from_currency=RUB&conv_currency=USD&start_date=2024-10-16&end_date=2024-10-20
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
</TabItem>
|
||||
<TabItem label='Python'>
|
||||
<Tabs>
|
||||
<TabItem label='requests'>
|
||||
```python
|
||||
import requests
|
||||
|
||||
res = requests.get('https://kekkai-api.redume.su/api/getRate/', {
|
||||
'from_currency': 'RUB',
|
||||
'conv_currency': 'USD',
|
||||
'start_date': '2024-10-16',
|
||||
'end_date': '2024-10-20',
|
||||
}, timeout=3)
|
||||
|
||||
print(res.json())
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
</TabItem>
|
||||
<TabItem label='Node.JS'>
|
||||
<Tabs>
|
||||
<TabItem label='axios'>
|
||||
```javascript
|
||||
const axios = require('axios');
|
||||
|
||||
axios.get('https://kekkai-api.redume.su/api/getRate/', {
|
||||
timeout: 3000,
|
||||
'from_currency': 'RUB',
|
||||
'conv_currency': 'USD',
|
||||
'start_date': '2024-10-16',
|
||||
'end_date': '2024-10-20',
|
||||
}
|
||||
)
|
||||
.then((res) => {
|
||||
console.log(JSON.stringify(res.json()));
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Query parameters
|
||||
| Parameter | Description |
|
||||
|------------------|-------------------------------------------------------------------------|
|
||||
| `from_currency`* | `ISO 4217` code of the currency from which the conversion takes place |
|
||||
| `conv_currency`* | `ISO 4217` code of the currency to which the conversion is performed |
|
||||
| `start_date`* | Start date of the period in the format `YYYYY-DD-MM` |
|
||||
| `end_date`* | Period end date in the format `YYYYY-DD-MM` |
|
||||
|
||||
`*` - Required arguments
|
||||
|
||||
### Response
|
||||
<Aside title='Output'>
|
||||
```json
|
||||
[
|
||||
{
|
||||
"from_currency": "RUB",
|
||||
"conv_currency": "USD",
|
||||
"rate": 0.01,
|
||||
"date": "2024-10-17T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"from_currency": "RUB",
|
||||
"conv_currency": "USD",
|
||||
"rate": 0.01,
|
||||
"date": "2024-10-18T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"from_currency": "RUB",
|
||||
"conv_currency": "USD",
|
||||
"rate": 0.01,
|
||||
"date": "2024-10-19T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"from_currency": "RUB",
|
||||
"conv_currency": "USD",
|
||||
"rate": 0.01,
|
||||
"date": "2024-10-20T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"from_currency": "RUB",
|
||||
"conv_currency": "USD",
|
||||
"rate": 0.01,
|
||||
"date": "2024-10-21T00:00:00.000Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
</Aside>
|
79
docs/src/content/docs/docs/endpoints/metadata.mdx
Normal file
79
docs/src/content/docs/docs/endpoints/metadata.mdx
Normal file
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
title: Get metadata - /api/metadata
|
||||
---
|
||||
|
||||
Currencies are identified by standard three-letter `ISO 4217` currency codes.
|
||||
|
||||
import { Tabs, TabItem, Aside } from '@astrojs/starlight/components';
|
||||
|
||||
## Get data on available dates and currencies.
|
||||
### Request
|
||||
|
||||
<Tabs>
|
||||
<TabItem label='Shell'>
|
||||
<Tabs>
|
||||
<TabItem label='curl'>
|
||||
```shell
|
||||
curl --request GET --url https://kekkai-api.redume.su/api/metadata/
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
</TabItem>
|
||||
<TabItem label='Python'>
|
||||
<Tabs>
|
||||
<TabItem label='requests'>
|
||||
```python
|
||||
import requests
|
||||
|
||||
res = requests.get('https://kekkai-api.redume.su/api/metadata/', timeout=3)
|
||||
|
||||
print(res.json())
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
</TabItem>
|
||||
<TabItem label='Node.JS'>
|
||||
<Tabs>
|
||||
<TabItem label='axios'>
|
||||
```javascript
|
||||
const axios = require('axios');
|
||||
|
||||
axios.get('https://kekkai-api.redume.su/api/metadata/')
|
||||
.then((res) => {
|
||||
console.log(JSON.stringify(res.json()));
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Response
|
||||
|
||||
<Aside title='Output'>
|
||||
```json
|
||||
{
|
||||
"first_date": "2024-11-26T21:00:00.000Z",
|
||||
"last_date": "2025-01-01T21:00:00.000Z",
|
||||
"currencies": {
|
||||
"crypto": [
|
||||
"USDT",
|
||||
"TON",
|
||||
"BTC",
|
||||
"ETH"
|
||||
],
|
||||
"fiat": [
|
||||
"USD",
|
||||
"RUB",
|
||||
"EUR",
|
||||
"UAH",
|
||||
"TRY",
|
||||
"KZT"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
</Aside>
|
|
@ -1,7 +1,14 @@
|
|||
If you have any questions, you can write to the [mail](mailto:redddume@gmail.com) or [Telegram](https://t.me/Redddume)
|
||||
---
|
||||
title: Contributing
|
||||
---
|
||||
|
||||
If you have any questions, you can write to the
|
||||
[mail](mailto:redddume@gmail.com)
|
||||
or [Telegram](https://t.me/Redddume)
|
||||
|
||||
### Fork and clone your repository
|
||||
1. Fork the repository ([click here to fork now](https://github.com/Redume/Kekkai/fork))
|
||||
1. Fork the repository
|
||||
([click here to fork now](https://github.com/Redume/Kekkai/fork))
|
||||
2. Clone your forked code
|
||||
```bash
|
||||
git clone https://github.com/<nickname>/Kekkai.git
|
||||
|
@ -18,16 +25,20 @@ git checkout <name_new_branch>
|
|||
6. Submit a new Pull Request
|
||||
|
||||
### Testing
|
||||
Before sending a Pull Request, test the functionality. Everything should work both in Docker Compose and without it.
|
||||
Before sending a Pull Request, test the functionality.
|
||||
Everything should work both in Docker Compose and without it.
|
||||
|
||||
It is recommended to use Debugger and Debug log for testing. The logging level is changed in `config.yaml`
|
||||
It is recommended to use Debugger and Debug log for testing.
|
||||
The logging level is changed in `config.yaml`
|
||||
|
||||
### Code Style
|
||||
[`Pylint`][pylint], [`mypy`][mypy], [`eslint`][eslint] and [`prettier`][prettier] are used as code syntax checks
|
||||
[`Pylint`][pylint], [`mypy`][mypy],
|
||||
[`eslint`][eslint] and [`prettier`][prettier] are used as code syntax checks
|
||||
|
||||
#### Checking the Node.JS code
|
||||
|
||||
To check the code, you must first download the necessary libraries, which are located at the root of the project
|
||||
To check the code, you must first download the necessary libraries,
|
||||
which are located at the root of the project
|
||||
```bash
|
||||
npm install
|
||||
```
|
84
docs/src/content/docs/docs/getting-started/docker.mdx
Normal file
84
docs/src/content/docs/docs/getting-started/docker.mdx
Normal file
|
@ -0,0 +1,84 @@
|
|||
---
|
||||
title: Docker
|
||||
---
|
||||
|
||||
Docker Compose is the recommended method to run Kekkai in production.
|
||||
Below are the steps to deploy Kekkai with Docker Compose.
|
||||
|
||||
Kekkai requires Docker Compose version 2.x.
|
||||
|
||||
import { Steps, Code } from '@astrojs/starlight/components';
|
||||
|
||||
<Steps>
|
||||
1. Preparing files
|
||||
```
|
||||
git clone https://github.com/redume/Kekkai
|
||||
```
|
||||
```
|
||||
cd Kekkai
|
||||
```
|
||||
|
||||
2. Change config files
|
||||
|
||||
In nginx.conf, you need to specify your domain or ipv4 address
|
||||
<Code code='
|
||||
...
|
||||
listen 443 ssl;
|
||||
server_name localhost; # Your domain
|
||||
...
|
||||
' lang='txt' title='nginx.conf' />
|
||||
|
||||
|
||||
To set up SSL. Create folder `CertSSL`
|
||||
```
|
||||
mkdir CertSLL
|
||||
```
|
||||
|
||||
After that, copy the SSL certificates to the CertSSL folder,
|
||||
if the names are different,
|
||||
then change either the name of the certificates or in `nginx.conf`
|
||||
|
||||
<Code code='
|
||||
ssl_certificate /etc/nginx/ssl/fullchain.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
|
||||
' lang='txt' title='nginx.conf' />
|
||||
|
||||
<Code code=
|
||||
"
|
||||
database:
|
||||
user: 'DATABASE_USERNAME'
|
||||
password: 'DATABASE_PASSWORD'
|
||||
host: 'DATABASE_HOST'
|
||||
name: 'DATABASE_NAME'
|
||||
port: 5432
|
||||
...
|
||||
" lang='yaml' title='config.sample.yaml' />
|
||||
Fill in the data in the database item, as well as in the .env config
|
||||
|
||||
|
||||
<Code code='
|
||||
# Connection secret for postgres. You should change it to a random password
|
||||
# Please use only the characters `A-Za-z0-9`, without special characters or spaces
|
||||
|
||||
POSTGRES_PASSWORD=my_password
|
||||
|
||||
# If you do not know what you are doing, then you should not edit the values below
|
||||
###################################################################################
|
||||
POSTGRES_DB=kekkai
|
||||
DB_HOST=postgres
|
||||
POSTGRES_USER=postgres
|
||||
' lang='txt' title='.env.sample' />
|
||||
|
||||
- Populate custom database information if necessary.
|
||||
- Consider changing `DB_PASSWORD` to a custom value.
|
||||
Postgres is not publically exposed,
|
||||
so this password is only used for - local authentication.
|
||||
To avoid issues with Docker parsing this value,
|
||||
it is best to use only the characters `A-Za-z0-9`.
|
||||
|
||||
3. Start the containers
|
||||
```shell
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
</Steps>
|
|
@ -1,11 +1,17 @@
|
|||
---
|
||||
title: Home
|
||||
description: The first free Open-Source Tool for Saving Historical Currency data
|
||||
---
|
||||
|
||||
## What is Kekkai?
|
||||
Kekkai — The first free Open-Source Tool for Saving Historical Currency data
|
||||
|
||||
It is a simple tool for collecting historical currency data from open sources, with the ability to create currency exchange rate charts. Cryptocurrencies and fiat currency are supported
|
||||
It is a simple tool for collecting historical currency data from open sources,
|
||||
with the ability to create currency exchange rate charts.
|
||||
|
||||
## Why Kekkai?
|
||||
|
||||
- Free & Open-Source
|
||||
- The ability to create graphs
|
||||
- Decentralized data collection and use. You are not dependent on any particular server
|
||||
- Plausible support. Anonymous data collection with the ability to disable it. Available only to server owners
|
||||
- Plausible support. Anonymous data collection with the ability to disable it.
|
||||
Available only to server owners
|
5
docs/tsconfig.json
Normal file
5
docs/tsconfig.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"include": [".astro/types.d.ts", "**/*"],
|
||||
"exclude": ["dist"]
|
||||
}
|
61
mkdocs.yml
61
mkdocs.yml
|
@ -1,61 +0,0 @@
|
|||
site_name: Kekkai
|
||||
site_url: https://kekkai-docs.redume.su
|
||||
site_author: Redume
|
||||
site_description: The first free Open-Source Tool for Saving Historical Currency data
|
||||
|
||||
repo_name: Redume/Kekkai
|
||||
repo_url: https://github.com/Redume/Kekkai
|
||||
edit_uri: ''
|
||||
|
||||
copyright: Copyright © 2024 Redume
|
||||
|
||||
nav:
|
||||
- Getting started:
|
||||
- Docker [Recommended]: getting-started/docker.md
|
||||
- Manual: getting-started/manual.md
|
||||
- Contributing: getting-started/contributing.md
|
||||
- Endpoints:
|
||||
- Endpoints list: endpoints/list-endpoints.md
|
||||
- Get currency rate - /api/getRate: endpoints/get-rate.md
|
||||
- Create Charts - /api/getChart: endpoints/create-chart.md
|
||||
- Config:
|
||||
- Configure config.yaml: config/config-yaml.md
|
||||
- Configure .env: config/config-env.md
|
||||
- Configure nginx.conf: config/conf-nginx.md
|
||||
|
||||
theme:
|
||||
language: en
|
||||
name: material
|
||||
palette:
|
||||
# Palette toggle for light mode
|
||||
- media: "(prefers-color-scheme: light)"
|
||||
scheme: default
|
||||
toggle:
|
||||
icon: material/brightness-7
|
||||
name: Switch to dark mode
|
||||
|
||||
# Palette toggle for dark mode
|
||||
- media: "(prefers-color-scheme: dark)"
|
||||
scheme: slate
|
||||
toggle:
|
||||
icon: material/brightness-4
|
||||
name: Switch to system preference
|
||||
|
||||
features:
|
||||
- content.code.copy
|
||||
- content.tabs.link
|
||||
|
||||
markdown_extensions:
|
||||
- pymdownx.tabbed:
|
||||
alternate_style: true
|
||||
- pymdownx.highlight:
|
||||
anchor_linenums: true
|
||||
line_spans: __span
|
||||
pygments_lang_class: true
|
||||
- pymdownx.inlinehilite
|
||||
- pymdownx.snippets
|
||||
- pymdownx.superfences
|
||||
- admonition
|
||||
- pymdownx.details
|
||||
- pymdownx.superfences
|
||||
- tables
|
119
nginx.conf
119
nginx.conf
|
@ -1,119 +0,0 @@
|
|||
events { }
|
||||
|
||||
http {
|
||||
limit_req_zone $binary_remote_addr zone=kekkai:10m rate=10r/s;
|
||||
# Change the number '10' to your own, to change the threshold number for rate limit
|
||||
|
||||
upstream server_backend {
|
||||
server server:3000;
|
||||
}
|
||||
|
||||
upstream chart_backend {
|
||||
server chart:3030;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost; # Your domain
|
||||
|
||||
limit_req zone=kekkai;
|
||||
|
||||
location / {
|
||||
limit_req zone=kekkai burst=4;
|
||||
|
||||
proxy_pass http://server_backend;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /api/configuration {
|
||||
limit_req zone=kekkai burst=4;
|
||||
|
||||
proxy_pass http://server_backend;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /api/getChart {
|
||||
limit_req zone=kekkai burst=4;
|
||||
|
||||
proxy_pass http://chart_backend;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /static/chart {
|
||||
limit_req zone=kekkai burst=4;
|
||||
|
||||
proxy_pass http://chart_backend;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /robots.txt {
|
||||
alias /etc/nginx/robots.txt;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name localhost; # Your domain
|
||||
|
||||
ssl_certificate /etc/nginx/ssl/fullchain.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
|
||||
|
||||
limit_req zone=kekkai;
|
||||
|
||||
location / {
|
||||
limit_req zone=kekkai burst=4;
|
||||
|
||||
proxy_pass http://server_backend;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /api/configuration {
|
||||
limit_req zone=kekkai burst=4;
|
||||
|
||||
proxy_pass http://server_backend;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /api/getChart {
|
||||
limit_req zone=kekkai burst=4;
|
||||
|
||||
proxy_pass http://chart_backend;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /static/chart {
|
||||
limit_req zone=kekkai burst=4;
|
||||
|
||||
proxy_pass http://chart_backend;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /robots.txt {
|
||||
alias /etc/nginx/robots.txt;
|
||||
}
|
||||
}
|
||||
}
|
2
nginx/.gitignore
vendored
Normal file
2
nginx/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
ssl
|
||||
assets
|
17
nginx/chart/nginx.conf
Normal file
17
nginx/chart/nginx.conf
Normal file
|
@ -0,0 +1,17 @@
|
|||
location /api/getChart {
|
||||
limit_req zone=kekkai burst=4;
|
||||
|
||||
proxy_pass http://chart:3030;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /static/chart {
|
||||
proxy_pass http://chart:3030;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
4
nginx/docs/nginx.conf
Normal file
4
nginx/docs/nginx.conf
Normal file
|
@ -0,0 +1,4 @@
|
|||
location ~ ^/(_astro|assets|docs|pagefind)/ {
|
||||
root /etc/nginx/dist;
|
||||
index index.html;
|
||||
}
|
99
nginx/mime.types
Normal file
99
nginx/mime.types
Normal file
|
@ -0,0 +1,99 @@
|
|||
|
||||
types {
|
||||
text/html html htm shtml;
|
||||
text/css css;
|
||||
text/xml xml;
|
||||
image/gif gif;
|
||||
image/jpeg jpeg jpg;
|
||||
application/javascript js;
|
||||
application/atom+xml atom;
|
||||
application/rss+xml rss;
|
||||
|
||||
text/mathml mml;
|
||||
text/plain txt;
|
||||
text/vnd.sun.j2me.app-descriptor jad;
|
||||
text/vnd.wap.wml wml;
|
||||
text/x-component htc;
|
||||
|
||||
image/avif avif;
|
||||
image/png png;
|
||||
image/svg+xml svg svgz;
|
||||
image/tiff tif tiff;
|
||||
image/vnd.wap.wbmp wbmp;
|
||||
image/webp webp;
|
||||
image/x-icon ico;
|
||||
image/x-jng jng;
|
||||
image/x-ms-bmp bmp;
|
||||
|
||||
font/woff woff;
|
||||
font/woff2 woff2;
|
||||
|
||||
application/java-archive jar war ear;
|
||||
application/json json;
|
||||
application/mac-binhex40 hqx;
|
||||
application/msword doc;
|
||||
application/pdf pdf;
|
||||
application/postscript ps eps ai;
|
||||
application/rtf rtf;
|
||||
application/vnd.apple.mpegurl m3u8;
|
||||
application/vnd.google-earth.kml+xml kml;
|
||||
application/vnd.google-earth.kmz kmz;
|
||||
application/vnd.ms-excel xls;
|
||||
application/vnd.ms-fontobject eot;
|
||||
application/vnd.ms-powerpoint ppt;
|
||||
application/vnd.oasis.opendocument.graphics odg;
|
||||
application/vnd.oasis.opendocument.presentation odp;
|
||||
application/vnd.oasis.opendocument.spreadsheet ods;
|
||||
application/vnd.oasis.opendocument.text odt;
|
||||
application/vnd.openxmlformats-officedocument.presentationml.presentation
|
||||
pptx;
|
||||
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||
xlsx;
|
||||
application/vnd.openxmlformats-officedocument.wordprocessingml.document
|
||||
docx;
|
||||
application/vnd.wap.wmlc wmlc;
|
||||
application/wasm wasm;
|
||||
application/x-7z-compressed 7z;
|
||||
application/x-cocoa cco;
|
||||
application/x-java-archive-diff jardiff;
|
||||
application/x-java-jnlp-file jnlp;
|
||||
application/x-makeself run;
|
||||
application/x-perl pl pm;
|
||||
application/x-pilot prc pdb;
|
||||
application/x-rar-compressed rar;
|
||||
application/x-redhat-package-manager rpm;
|
||||
application/x-sea sea;
|
||||
application/x-shockwave-flash swf;
|
||||
application/x-stuffit sit;
|
||||
application/x-tcl tcl tk;
|
||||
application/x-x509-ca-cert der pem crt;
|
||||
application/x-xpinstall xpi;
|
||||
application/xhtml+xml xhtml;
|
||||
application/xspf+xml xspf;
|
||||
application/zip zip;
|
||||
|
||||
application/octet-stream bin exe dll;
|
||||
application/octet-stream deb;
|
||||
application/octet-stream dmg;
|
||||
application/octet-stream iso img;
|
||||
application/octet-stream msi msp msm;
|
||||
|
||||
audio/midi mid midi kar;
|
||||
audio/mpeg mp3;
|
||||
audio/ogg ogg;
|
||||
audio/x-m4a m4a;
|
||||
audio/x-realaudio ra;
|
||||
|
||||
video/3gpp 3gpp 3gp;
|
||||
video/mp2t ts;
|
||||
video/mp4 mp4;
|
||||
video/mpeg mpeg mpg;
|
||||
video/quicktime mov;
|
||||
video/webm webm;
|
||||
video/x-flv flv;
|
||||
video/x-m4v m4v;
|
||||
video/x-mng mng;
|
||||
video/x-ms-asf asx asf;
|
||||
video/x-ms-wmv wmv;
|
||||
video/x-msvideo avi;
|
||||
}
|
28
nginx/nginx.conf
Normal file
28
nginx/nginx.conf
Normal file
|
@ -0,0 +1,28 @@
|
|||
events { }
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
|
||||
limit_req_zone $binary_remote_addr zone=kekkai:10m rate=10r/s;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost; # Your domain
|
||||
|
||||
return 301 https://$host$request_uri$is_args$args;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
|
||||
server_name localhost; # Your domain
|
||||
|
||||
ssl_certificate /etc/nginx/ssl/fullchain.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
|
||||
|
||||
include chart/nginx.conf;
|
||||
include server/nginx.conf;
|
||||
include web/nginx.conf;
|
||||
include docs/nginx.conf;
|
||||
}
|
||||
}
|
19
nginx/server/nginx.conf
Normal file
19
nginx/server/nginx.conf
Normal file
|
@ -0,0 +1,19 @@
|
|||
location /api/getRate/ {
|
||||
limit_req zone=kekkai burst=4;
|
||||
|
||||
proxy_pass http://server:3000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /api/metadata/ {
|
||||
limit_req zone=kekkai burst=4;
|
||||
|
||||
proxy_pass http://server:3000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
15
nginx/web/nginx.conf
Normal file
15
nginx/web/nginx.conf
Normal file
|
@ -0,0 +1,15 @@
|
|||
location / {
|
||||
proxy_pass http://web:3050;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /robots.txt {
|
||||
alias robots.txt;
|
||||
}
|
||||
|
||||
location /favicon.ico {
|
||||
alias assets/logo.png;
|
||||
}
|
|
@ -2,10 +2,6 @@
|
|||
"name": "kekkai",
|
||||
"version": "1.0.0",
|
||||
"description": "Historical data on the rate of fiat and crypto currencies",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Redume/Kekkai.git"
|
||||
|
@ -15,7 +11,7 @@
|
|||
"bugs": {
|
||||
"url": "https://github.com/Redume/Kekkai/issues"
|
||||
},
|
||||
"homepage": "https://github.com/Redume/Kekkai#readme",
|
||||
"homepage": "https://kekkai.redume.su/",
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^8.57.0",
|
||||
"eslint": "^8.57.0",
|
||||
|
|
7
server/README.md
Normal file
7
server/README.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
## Third-Party Libraries and Licenses
|
||||
|
||||
- [brianc/pg](https://github.com/brianc/node-postgres) — [MIT](https://github.com/brianc/node-postgres/blob/master/LICENSE)
|
||||
- [eemeli/yaml](https://github.com/eemeli/yaml) — [ISC](https://github.com/eemeli/yaml/blob/main/LICENSE)
|
||||
- [fastify/fastify](https://github.com/fastify/fastify) — [MIT](https://github.com/fastify/fastify/blob/main/LICENSE)
|
||||
- [axios/axios](https://github.com/axios/axios) — [MIT](https://github.com/axios/axios/blob/v1.x/LICENSE)
|
||||
- [faisalman/ua-parser-js](https://github.com/faisalman/ua-parser-js) — [AGPL-v3](https://github.com/faisalman/ua-parser-js/blob/master/LICENSE.md)
|
|
@ -8,8 +8,8 @@ const UAParser = require('ua-parser-js');
|
|||
require('../shared/database/src/create_table.js')();
|
||||
|
||||
const fastify = require('fastify')({
|
||||
logger: config['server']['log']['print'] ? logger : false,
|
||||
...(config['server']['ssl']['work']
|
||||
logger: config['server']['log']['level'] !== 'none' ? logger : false,
|
||||
...(config['server']['ssl']['enabled']
|
||||
? {
|
||||
https: {
|
||||
key: fs.readFileSync(
|
||||
|
@ -26,12 +26,10 @@ const fastify = require('fastify')({
|
|||
});
|
||||
|
||||
const getRateRoute = require('./routes/getRate.js');
|
||||
const configurationRoutes = require('./routes/configuration.js');
|
||||
const HomeRoute = require('./routes/home.js');
|
||||
const getMetadata = require('./routes/metadata.js');
|
||||
|
||||
fastify.register(getRateRoute);
|
||||
fastify.register(configurationRoutes);
|
||||
fastify.register(HomeRoute);
|
||||
fastify.register(getMetadata);
|
||||
|
||||
fastify.setNotFoundHandler(function (res, reply) {
|
||||
return reply.status(404).send({
|
||||
|
@ -83,13 +81,17 @@ fastify.addHook('onResponse', async (request, reply) => {
|
|||
};
|
||||
|
||||
try {
|
||||
await axios.post(config['analytics']['plausible_api'], event, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${config['analytics']['plausible_token']}`,
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': userAgent,
|
||||
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,
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
} catch (error) {
|
||||
fastify.log.error('Error sending event to Plausible:', error.message);
|
||||
}
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
const config = require('../../shared/config/src/main.js')();
|
||||
|
||||
module.exports = async function configurationRoutes(fastify) {
|
||||
fastify.get('/api/configurations/json', async function (req, res) {
|
||||
delete config['database'];
|
||||
delete config['currency']['coinapiKeys'];
|
||||
delete config['server']['ssl']['private_key'];
|
||||
delete config['server']['ssl']['cert'];
|
||||
delete config['analytics']['plausible_token'];
|
||||
|
||||
return res.status(200).send({
|
||||
config,
|
||||
});
|
||||
});
|
||||
};
|
|
@ -18,6 +18,7 @@ module.exports = async function getRateRoute(fastify) {
|
|||
query['from_currency'],
|
||||
query['conv_currency'],
|
||||
query['date'],
|
||||
query['conv_amount'] ? query['conv_amount'] : 0
|
||||
);
|
||||
else if (query['start_date'] && query['end_date'])
|
||||
rate_res = await rate.getPeriod(
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
module.exports = async function getRateRoute(fastify) {
|
||||
fastify.get('/', async function (req, res) {
|
||||
return res.status(200).send({
|
||||
message: 'Hello World!',
|
||||
documentation: 'https://kekkai-docs.redume.su/',
|
||||
});
|
||||
});
|
||||
};
|
26
server/routes/metadata.js
Normal file
26
server/routes/metadata.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
const pool = require('../../shared/database/src/postgresql.js');
|
||||
const config = require('../../shared/config/src/main.js')();
|
||||
|
||||
module.exports = async function metadata(fastify) {
|
||||
fastify.get('/api/metadata/', async function (req, res) {
|
||||
const first_date = await pool.query(
|
||||
'SELECT * FROM currency ORDER BY date LIMIT 1',
|
||||
);
|
||||
const last_date = await pool.query(
|
||||
'SELECT * FROM currency ORDER BY date DESC LIMIT 1',
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
first_date: first_date.rows[0]?.date
|
||||
? first_date.rows[0]?.date
|
||||
: 'None',
|
||||
last_date: last_date.rows[0]?.date
|
||||
? last_date.rows[0]?.date
|
||||
: 'None',
|
||||
currencies: {
|
||||
crypto: config['currency']['crypto'],
|
||||
fiat: config['currency']['fiat'],
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
8
shared/README.md
Normal file
8
shared/README.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
This repository contains shared libraries used across the project. These libraries provide common functionality, utilities, and components that are used by different microservices within the project to ensure consistency and code reuse.
|
||||
|
||||
## Third-Party Libraries and Licenses
|
||||
|
||||
- [pinojs/pino](https://github.com/pinojs/pino) - [MIT](https://github.com/pinojs/pino/blob/main/LICENSE)
|
||||
- [pinojs/pino-prettier](https://github.com/pinojs/pino-pretty) - [MIT](https://github.com/pinojs/pino-pretty/blob/master/LICENSE)
|
||||
- [brianc/pg](https://github.com/brianc/node-postgres) - [MIT](https://github.com/brianc/node-postgres/blob/master/LICENSE)
|
||||
- [eemeli/yaml](https://github.com/eemeli/yaml) - [ISC](https://github.com/eemeli/yaml/blob/main/LICENSE)
|
17
shared/config/package-lock.json
generated
17
shared/config/package-lock.json
generated
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,6 @@
|
|||
"homepage": "https://github.com/Redume/Kekkai#readme",
|
||||
"description": "Config management service",
|
||||
"dependencies": {
|
||||
"yaml": "^2.5.0"
|
||||
"hjson": "^3.2.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const pool = require('./postgresql.js');
|
||||
const logger = require('../../logger/src/main.js');
|
||||
|
||||
async function getDay(from_currency, conv_currency, date) {
|
||||
async function getDay(from_currency, conv_currency, date, conv_amount) {
|
||||
if (!from_currency || !conv_currency)
|
||||
return new Error('fromCurrency and convCurrency are required');
|
||||
else if (!date) return new Error('date is required');
|
||||
|
@ -12,8 +12,14 @@ async function getDay(from_currency, conv_currency, date) {
|
|||
[from_currency.toUpperCase(), conv_currency.toUpperCase(), date],
|
||||
);
|
||||
|
||||
|
||||
if (data?.['rows'].length <= 0) return 'Missing data';
|
||||
|
||||
if (conv_amount) {
|
||||
let conv_rate = data?.['rows'][0]['rate'] * conv_amount;
|
||||
data['rows'][0]['conv_amount'] = Number(conv_rate.toFixed(2));
|
||||
}
|
||||
|
||||
logger.debug(data['rows'][0]);
|
||||
|
||||
return data['rows'][0];
|
||||
|
|
43
web/main.js
Normal file
43
web/main.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
const logger = require('../shared/logger');
|
||||
const config = require('../shared/config/src/main.js')();
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('node:path');
|
||||
|
||||
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',
|
||||
),
|
||||
},
|
||||
}
|
||||
: false),
|
||||
});
|
||||
|
||||
fastify.register(require('@fastify/static'), {
|
||||
root: path.join(__dirname, 'src/static'),
|
||||
prefix: '/static/',
|
||||
});
|
||||
|
||||
fastify.register(require('./routes/home.js'));
|
||||
|
||||
fastify.listen(
|
||||
{
|
||||
port: 3050,
|
||||
host: config['server']['host'] ? config['server']['host'] : 'localhost',
|
||||
},
|
||||
(err) => {
|
||||
if (err) {
|
||||
fastify.log.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
},
|
||||
);
|
1146
web/package-lock.json
generated
Normal file
1146
web/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
19
web/package.json
Normal file
19
web/package.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "web",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Redume/Kekkai/issues"
|
||||
},
|
||||
"license": "GPL-3.0-or-later",
|
||||
"author": "Redume",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fastify/static": "^7.0.4",
|
||||
"fastify": "^4.28.1"
|
||||
}
|
||||
}
|
7
web/routes/home.js
Normal file
7
web/routes/home.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
const fs = require("node:fs");
|
||||
|
||||
module.exports = async function homeRoute(fastify) {
|
||||
fastify.get('/', (req, res) => {
|
||||
res.send(fs.createReadStream('./src/html/index.html'));
|
||||
});
|
||||
}
|
58
web/src/html/index.html
Normal file
58
web/src/html/index.html
Normal file
|
@ -0,0 +1,58 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Kekkai</title>
|
||||
|
||||
<meta name="author" content="Redume">
|
||||
<meta name="description" content="API providing historical currency data">
|
||||
<meta name="keywords" content="kekkai, currency, exchange rate, api exchange rate, cryptocurrency api rate, fiat api rate">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link type="text/css" rel="stylesheet" href="/static/styles/index.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="banner">
|
||||
<div class="banner-img">
|
||||
<img src="/static/assets/kekkai.svg" alt="Kekkai title">
|
||||
</div>
|
||||
<p class="desc">The first <a style="color: #69C9FF">free Open-Source</a> Tool for Saving Historical Currency data</p>
|
||||
</div>
|
||||
<p class="desc-xl">It is a simple tool for collecting historical currency data from open sources, with the ability to create currency exchange rate charts. Cryptocurrencies and fiat currency are supported</p>
|
||||
<div class="features-container">
|
||||
<div class="features">
|
||||
<svg width="64px" height="64px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="#ffffff">
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
|
||||
<g id="SVGRepo_iconCarrier"> <g> <path fill="none" d="M0 0H24V24H0z"></path>
|
||||
<path d="M12 2c5.523 0 10 4.477 10 10 0 4.4-2.841 8.136-6.789 9.473l-.226.074-2.904-7.55C13.15 13.95 14 13.054 14 12c0-1.105-.895-2-2-2s-2 .895-2 2c0 1.077.851 1.955 1.917 1.998l-2.903 7.549-.225-.074C4.84 20.136 2 16.4 2 12 2 6.477 6.477 2 12 2zm0 2c-4.418 0-8 3.582-8 8 0 2.92 1.564 5.475 3.901 6.872l1.48-3.849C8.534 14.29 8 13.207 8 12c0-2.21 1.79-4 4-4s4 1.79 4 4c0 1.207-.535 2.29-1.38 3.023.565 1.474 1.059 2.757 1.479 3.85C18.435 17.475 20 14.92 20 12c0-4.418-3.582-8-8-8z"></path> </g> </g>
|
||||
</svg>
|
||||
<p class="text">Free & Open-Source</p>
|
||||
</div>
|
||||
<div class="features">
|
||||
<svg width="64px" height="64px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="#ffffff">
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<path d="M12 18V12M12 12L16 7M12 12L8 7M16 12H8M15.5 15H8.5M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<p class="text">Support for fiat currency and cryptocurrencies</p>
|
||||
</div>
|
||||
<div class="features">
|
||||
<svg width="64px" height="64px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
|
||||
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<g id="SVGRepo_iconCarrier"> <path d="M16 11V16M8 11V16M12 8V16M7 21H17C19.2091 21 21 19.2091 21 17V7C21 4.79086 19.2091 3 17 3H7C4.79086 3 3 4.79086 3 7V17C3 19.2091 4.79086 21 7 21Z" stroke="#ffffff" stroke-width="2" stroke-linecap="round"/> </g>
|
||||
</svg>
|
||||
<p class="text">Creating graphs</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="buttons-container">
|
||||
<div class="pr-button" onclick="window.open('https://github.com/Redume/Kekkai')">
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M4.0744 2.9938C4.13263 1.96371 4.37869 1.51577 5.08432 1.15606C5.84357 0.768899 7.04106 0.949072 8.45014 1.66261C9.05706 1.97009 9.11886 1.97635 10.1825 1.83998C11.5963 1.65865 13.4164 1.65929 14.7213 1.84164C15.7081 1.97954 15.7729 1.97265 16.3813 1.66453C18.3814 0.651679 19.9605 0.71795 20.5323 1.8387C20.8177 2.39812 20.8707 3.84971 20.6494 5.04695C20.5267 5.71069 20.5397 5.79356 20.8353 6.22912C22.915 9.29385 21.4165 14.2616 17.8528 16.1155C17.5801 16.2574 17.3503 16.3452 17.163 16.4167C16.5879 16.6363 16.4133 16.703 16.6247 17.7138C16.7265 18.2 16.8491 19.4088 16.8973 20.4002C16.9844 22.1922 16.9831 22.2047 16.6688 22.5703C16.241 23.0676 15.6244 23.076 15.2066 22.5902C14.9341 22.2734 14.9075 22.1238 14.9075 20.9015C14.9075 19.0952 14.7095 17.8946 14.2417 16.8658C13.6854 15.6415 14.0978 15.185 15.37 14.9114C17.1383 14.531 18.5194 13.4397 19.2892 11.8146C20.0211 10.2698 20.1314 8.13501 18.8082 6.83668C18.4319 6.3895 18.4057 5.98446 18.6744 4.76309C18.7748 4.3066 18.859 3.71768 18.8615 3.45425C18.8653 3.03823 18.8274 2.97541 18.5719 2.97541C18.4102 2.97541 17.7924 3.21062 17.1992 3.49805L16.2524 3.95695C16.1663 3.99866 16.07 4.0147 15.975 4.0038C13.5675 3.72746 11.2799 3.72319 8.86062 4.00488C8.76526 4.01598 8.66853 3.99994 8.58215 3.95802L7.63585 3.49882C7.04259 3.21087 6.42482 2.97541 6.26317 2.97541C5.88941 2.97541 5.88379 3.25135 6.22447 4.89078C6.43258 5.89203 6.57262 6.11513 5.97101 6.91572C5.06925 8.11576 4.844 9.60592 5.32757 11.1716C5.93704 13.1446 7.4295 14.4775 9.52773 14.9222C10.7926 15.1903 11.1232 15.5401 10.6402 16.9905C10.26 18.1319 10.0196 18.4261 9.46707 18.4261C8.72365 18.4261 8.25796 17.7821 8.51424 17.1082C8.62712 16.8112 8.59354 16.7795 7.89711 16.5255C5.77117 15.7504 4.14514 14.0131 3.40172 11.7223C2.82711 9.95184 3.07994 7.64739 4.00175 6.25453C4.31561 5.78028 4.32047 5.74006 4.174 4.83217C4.09113 4.31822 4.04631 3.49103 4.0744 2.9938Z" fill="#ffffff"></path> <path d="M3.33203 15.9454C3.02568 15.4859 2.40481 15.3617 1.94528 15.6681C1.48576 15.9744 1.36158 16.5953 1.66793 17.0548C1.8941 17.3941 2.16467 17.6728 2.39444 17.9025C2.4368 17.9449 2.47796 17.9858 2.51815 18.0257C2.71062 18.2169 2.88056 18.3857 3.05124 18.5861C3.42875 19.0292 3.80536 19.626 4.0194 20.6962C4.11474 21.1729 4.45739 21.4297 4.64725 21.5419C4.85315 21.6635 5.07812 21.7352 5.26325 21.7819C5.64196 21.8774 6.10169 21.927 6.53799 21.9559C7.01695 21.9877 7.53592 21.998 7.99999 22.0008C8.00033 22.5527 8.44791 23.0001 8.99998 23.0001C9.55227 23.0001 9.99998 22.5524 9.99998 22.0001V21.0001C9.99998 20.4478 9.55227 20.0001 8.99998 20.0001C8.90571 20.0001 8.80372 20.0004 8.69569 20.0008C8.10883 20.0026 7.34388 20.0049 6.67018 19.9603C6.34531 19.9388 6.07825 19.9083 5.88241 19.871C5.58083 18.6871 5.09362 17.8994 4.57373 17.2891C4.34391 17.0194 4.10593 16.7834 3.91236 16.5914C3.87612 16.5555 3.84144 16.5211 3.80865 16.4883C3.5853 16.265 3.4392 16.1062 3.33203 15.9454Z" fill="#ffffff"></path> </g></svg>
|
||||
<a>Source Code</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
4
web/src/static/assets/kekkai.svg
Normal file
4
web/src/static/assets/kekkai.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 7.6 KiB |
145
web/src/static/styles/index.css
Normal file
145
web/src/static/styles/index.css
Normal file
|
@ -0,0 +1,145 @@
|
|||
body {
|
||||
background: #4F5574;
|
||||
color: white;
|
||||
font: medium "Gotham Pro", sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.banner {
|
||||
background: rgba(0, 0, 0, 0.39);
|
||||
border-radius: 3vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2%;
|
||||
max-width: 95vw;
|
||||
margin: .5% auto;
|
||||
}
|
||||
|
||||
.banner-img {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.banner-img img {
|
||||
width: 100%;
|
||||
max-width: 450px;
|
||||
height: auto;
|
||||
margin: 1.5%;
|
||||
}
|
||||
|
||||
.desc {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
text-align: center;
|
||||
margin: 1% auto;
|
||||
word-wrap: break-word;
|
||||
padding: 0 2%;
|
||||
box-sizing: border-box;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.desc-xl {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
margin: 1% auto;
|
||||
word-wrap: break-word;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
font-weight: bold;
|
||||
padding: 10px 2%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.features-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 40px;
|
||||
padding: 80px 20px;
|
||||
max-width: 1200px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.features {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.features svg {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.buttons-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.pr-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
background-color: #297BCD;
|
||||
border-radius: 12px;
|
||||
padding: 15px 25px;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
transition: background-color 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.pr-button svg {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.pr-button a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.pr-button:hover {
|
||||
background-color: #2b6ea3;
|
||||
}
|
||||
|
||||
.pr-button {
|
||||
margin-bottom: 130px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.desc {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.footer-wrapper {
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.desc {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.features-container {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 200px;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue