Merge pull request #2 from lucas-labs/refactor/convert-to-lib

docs: 📝 change readme logo
This commit is contained in:
Lucas Colombo 2024-04-23 09:41:15 -03:00 committed by GitHub
commit 3dd71cd9f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
41 changed files with 279 additions and 942 deletions

13
.github/img/rustler-core-logo.svg vendored Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

1
.github/img/todo.svg vendored Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 24"><path d="M44.65674,21.2998a3.0394,3.0394,0,0,1-2.4126-.875,3.41057,3.41057,0,0,1-.76269-2.32519,6.92661,6.92661,0,0,1,.03759-.71191q.03737-.36327.13769-.9375l.92481-5.5H39.28173l.47461-2.8501h2.0752A1.92861,1.92861,0,0,0,43.044,7.7749a1.76782,1.76782,0,0,0,.53759-1.1748l.5-3.0503H47.4815l-.75,4.5503h5.05029l-.4751,2.8501H46.25635l-1.19971,7.19921.5.17579,3.8999-3.0752,1.79981,2.0498-1.5,1.29981q-.79981.67529-1.44971,1.1875a10.42993,10.42993,0,0,1-1.23779.85059,5.22593,5.22593,0,0,1-1.16211.5A4.62212,4.62212,0,0,1,44.65674,21.2998Zm14.6748,0a5.46483,5.46483,0,0,1-4.10009-1.4873A5.56943,5.56943,0,0,1,53.78174,15.75a9.66527,9.66527,0,0,1,.53711-3.3252,7.20855,7.20855,0,0,1,1.5-2.5,6.50936,6.50936,0,0,1,2.30029-1.5747,7.71558,7.71558,0,0,1,2.9624-.5503,5.464,5.464,0,0,1,4.10009,1.4878,5.57305,5.57305,0,0,1,1.44971,4.0625,9.672,9.672,0,0,1-.53711,3.3247,7.21883,7.21883,0,0,1-1.5,2.5A6.50666,6.50666,0,0,1,62.294,20.75,7.72834,7.72834,0,0,1,59.33154,21.2998Zm.27491-2.7246a2.73119,2.73119,0,0,0,1.9624-.72559,3.57849,3.57849,0,0,0,.98779-2.0752l.32471-1.94921c.0332-.1504.0625-.32081.08789-.5127a4.01545,4.01545,0,0,0,.03711-.5127,2.26958,2.26958,0,0,0-.5874-1.7124,2.235,2.235,0,0,0-1.61231-.5625,2.7301,2.7301,0,0,0-1.9624.7251,3.57547,3.57547,0,0,0-.98779,2.0752l-.32471,1.94921c-.03369.15039-.0625.32129-.08789.5127a4.02821,4.02821,0,0,0-.03711.51269,2.2681,2.2681,0,0,0,.5874,1.7129A2.23342,2.23342,0,0,0,59.60645,18.5752Zm17.27441-.67579h-.125a12.32427,12.32427,0,0,1-.78711,1.373,5.54578,5.54578,0,0,1-.92529,1.07031,3.86152,3.86152,0,0,1-1.14991.70508,4.43681,4.43681,0,0,1-3.20019-.09765,2.96636,2.96636,0,0,1-1.16211-1.0127,4.63007,4.63007,0,0,1-.6626-1.58789,9.13873,9.13873,0,0,1-.2124-2.04981,13.27046,13.27046,0,0,1,.4248-3.4873,8.56935,8.56935,0,0,1,1.2002-2.6748,5.6303,5.6303,0,0,1,1.8623-1.7251,4.882,4.882,0,0,1,2.4375-.6128,3.32428,3.32428,0,0,1,2.3125.71875,3.66607,3.66607,0,0,1,1.0625,2.00538h.17481L79.45605,2.5H83.106L80.03125,21H76.38086Zm-2.82471.55079a2.2377,2.2377,0,0,0,1.19971-.32325,3.739,3.739,0,0,0,.9751-.89453,6.92872,6.92872,0,0,0,.67529-.98144,4.00289,4.00289,0,0,0,.5-1.42774l.34961-2.062a1.79174,1.79174,0,0,0-.3125-1.56494,2.18107,2.18107,0,0,0-1.6875-.54639,2.6884,2.6884,0,0,0-2.07471.78272,4.29282,4.29282,0,0,0-.9751,2.248l-.25,1.49024a3.83444,3.83444,0,0,0-.07518.50976q-.0249.28566-.02491.60743a3.19053,3.19053,0,0,0,.38769,1.541A1.38859,1.38859,0,0,0,74.05615,18.4502Zm15.2749,2.8496A5.46485,5.46485,0,0,1,85.231,19.8125,5.56945,5.56945,0,0,1,83.78125,15.75a9.66527,9.66527,0,0,1,.53711-3.3252,7.20855,7.20855,0,0,1,1.5-2.5,6.50945,6.50945,0,0,1,2.30029-1.5747,7.71565,7.71565,0,0,1,2.9624-.5503,5.464,5.464,0,0,1,4.1001,1.4878,5.57305,5.57305,0,0,1,1.44971,4.0625,9.672,9.672,0,0,1-.53711,3.3247,7.21883,7.21883,0,0,1-1.5,2.5A6.50662,6.50662,0,0,1,92.29346,20.75,7.72832,7.72832,0,0,1,89.33105,21.2998Zm.27491-2.7246a2.7312,2.7312,0,0,0,1.96241-.72559,3.57843,3.57843,0,0,0,.98779-2.0752l.32471-1.94921c.0332-.1504.0625-.32081.08789-.5127a4.01545,4.01545,0,0,0,.03711-.5127,2.26953,2.26953,0,0,0-.58741-1.7124,2.235,2.235,0,0,0-1.61231-.5625,2.73005,2.73005,0,0,0-1.9624.7251,3.57541,3.57541,0,0,0-.98779,2.0752l-.32471,1.94921c-.03369.15039-.0625.32129-.08789.5127a4.02638,4.02638,0,0,0-.03711.51269,2.26806,2.26806,0,0,0,.5874,1.7129,2.23345,2.23345,0,0,0,1.61235.5625Zm14.49952,2.7246a2.48881,2.48881,0,0,1-1.6875-.46289,1.44412,1.44412,0,0,1-.51221-1.1123,3.7521,3.7521,0,0,1,.0249-.3877q.02416-.2373.125-.8125a2.1494,2.1494,0,0,1,.75-1.33691,2.81154,2.81154,0,0,1,1.875-.53809,2.49057,2.49057,0,0,1,1.6875.46289,1.4487,1.4487,0,0,1,.51221,1.11231,3.86127,3.86127,0,0,1-.0249.38769c-.01661.15821-.0586.42969-.125.8125a2.15625,2.15625,0,0,1-.75,1.3379A2.81688,2.81688,0,0,1,104.10548,21.2998Zm.32518-6.3496-.30029-6.314.8252-5.08643h4.02491l-.8501,5.08643-2.3999,6.314Zm15.521-1.5312c.00553-4.31647,2.8785-8.54763,7.07519-10.919h3.5747c-3.306,1.97586-6.39479,4.74307-7.10013,8.17388l-.39987,1.9911c-.62683,3.139,1.23593,6.42127,3.7749,8.63482h-3.2749A10.3606,10.3606,0,0,1,119.95167,13.419ZM142,10.3804c-.0043,4.31742-2.878,8.54776-7.07373,10.9194h-3.57518c3.30582-1.97581,6.39557-4.74308,7.10062-8.17388l.40036-1.99109C139.47743,7.99569,137.615,4.714,135.07666,2.5h3.27544A10.36251,10.36251,0,0,1,142,10.3804ZM16,0,0,24H32Zm2,22H14V18h4Zm-4-6V8h4v8Z" style="fill:#ffb74d"/></svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

661
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,48 +1,51 @@
[package]
name = "rustler"
name = "rustler-core"
version = "0.0.0-alpha.0"
edition = "2021"
description = "🤠 » single-action data extractor"
description = "🤠 » rustler-core market data extractor core functionality"
authors = ["Lucas Colombo <lucas@lucode.ar>"]
publish = false
build = "lib/build.rs"
[profile.release]
strip = true
lto = true
codegen-units = 16
opt-level = 'z'
panic = "abort"
rpath = false
overflow-checks = false
debug = 0
debug-assertions = false
[[bin]]
name = "rustler"
path = "app/main.rs"
[workspace]
members = [".", "migration", "entities", "grpc", "rustlers"]
[workspace.dependencies]
lool = { version = "0.2.0", registry = "lugit" } # crates: disable-check
eyre = { version = "0.6.12", default-features = false }
tokio-tungstenite = { version = "0.21.0" }
tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread"] }
[lib]
path = "lib/lib.rs"
[dependencies]
# internal
entities = { path = "entities" }
grpc = { path = "grpc" }
rustlers = { path = "rustlers" }
# workspace
eyre = { workspace = true, default-features = false, features = [
"auto-install",
] }
lool = { workspace = true }
tokio = { workspace = true }
# external
# utils
eyre = { version = "0.6.12", default-features = false }
dotenvy = "0.15.7"
chrono = "0.4.37"
getset = "0.1.2"
# async
tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread"] }
async-trait = "0.1.79"
# grpc & websocket
tokio-tungstenite = { version = "0.21.0" }
tonic = "0.11.0"
prost = "0.12.3" # protocol buffers
# database
sea-orm = { version = "0.12.15", features = [
"runtime-tokio-native-tls",
"sqlx-sqlite",
"macros",
] }
sea-orm-migration = { version = "0.12.15", features = [
"runtime-tokio-native-tls",
"sqlx-sqlite",
] }
[dependencies.lool]
version = "0.2.0" # crates: disable-check
registry = "lugit"
features = [
"cli.stylize",
"logger",
"sched.tokio",
"sched.rule-recurrence",
"macros",
]
[build-dependencies]
tonic-build = "0.11.0"

View File

@ -1,35 +1,46 @@
<p align="center"><img src=".github/img/rustler.svg" height="256"></p>
<p align="center"><img src=".github/img/rustler-core-logo.svg" height="264"></p>
<br>
<br>
<br>
<p align="center">
𝐫𝐮𝐬𝐭𝐥𝐞𝐫 is a web scraping service that scrapes several stock market providers for stock pricing data. It is built using the <code>Rust</code> programming language.
𝐫𝐮𝐬𝐭𝐥𝐞𝐫 𝐜𝐨𝐫𝐞 is a library that contains the core functionality for `rustler`, a web scraping service that scrapes several stock market providers for stock pricing data. It is built using the <code>Rust</code> programming language.
</p>
<br>
<br>
<br>
## Framweork
## Why "rustler"
- [Tonic (grpc)](https://docs.rs/tonic/latest/tonic/index.html)
- websockets:
- [tokio-tungstenite](https://docs.rs/tokio-tungstenite/latest/tokio_tungstenite/)
- [fastwebsockets](https://crates.io/crates/fastwebsockets)
- [embedded-websocket](https://crates.io/crates/embedded-websocket) - bajo nivel - small
- [web-socket](https://crates.io/crates/web-socket) - supuestamente el mas rapido
- Rx Rust:
- [rxrust](https://crates.io/crates/rxrust)
- [another-rxrust](https://crates.io/crates/another-rxrust) parece un poco mejor
A `rustler` is a person who steals live***stock***. Well, this library is a service that collects _stock_ market data from the internet. So, it's a "_rustler_" for stock market data.
## Commands
Also, this library is built using the `Rust` programming language... so, ***rust***ler 😊
```bash
# add a dependency to a project
cargo add {dependency} -p {project}
## What this library includes
# example
cargo add tokio-tungstenite -p gateway
```
This library defines the core functionality for a `rustler`. It includes the following:
- A [`Rustler`](./lib/rustlers/rustlers.rs) trait that defines the core functionality for a `rustler`.
- A [`RustlersSvc`](./lib/rustlers/svc.rs) which orchestrates the `rustlers` at runtime, scheduling them to scrape stock pricing data between market hours.
Apart from the above, this library also defines:
- a [database schema](./lib/entities/orm/) for storing market hours, which is used by the `RustlersSvc` to schedule the `rustlers`.
- initial [database migrations](./lib/entities/migration) to create the schema.
- a [grpc server](./lib/grpc/) to interact with the rustlers database.
- <img alt="unimplemented" src="./.github/img/todo.svg" height="12"> a [websocket gateway server](./lib/socket/) to stream stock pricing data to subscribed clients
> [!NOTE]
>
> This library defines a _rustler_ as a service that scrapes stock pricing data for a
> particular market.
>
> Although this library contains the core and abstract functionality for the rustlers, it doesn't include any concrete implementation for them.
>
> Actual concrete implementations for each market cannot be published for many reasons.
## Example
Check the [examples](./examples) directory for an example of how to use this library.

View File

@ -16,7 +16,7 @@ tasks:
run:watch:
desc: 🚀 watch rustler
cmds:
- cargo watch -c -x "run"
- cargo watch -c -x "run --example=rustler"
build:watch:
desc: 🚀 watch rustler «build»

View File

@ -1,60 +0,0 @@
"""
small script to run after build, to check if there was a significant
change on executable size, compared to the previous build.
this aims to detect unwanted big differences before it's too late
"""
import os
import pathlib
curr_dir = pathlib.Path(os.getcwd())
sizefile_path = pathlib.Path(curr_dir.joinpath('.task'))
def bad(txt):
return '\033[91m' + txt + '\033[0m'
def good(txt):
return '\033[92m' + txt + '\033[0m'
def head(txt):
return '\033[94m' + txt + '\033[0m'
files = {
'release': curr_dir.joinpath('target/release/rustler.exe'),
'debug': curr_dir.joinpath('target/debug/rustler.exe'),
}
print("\n🧉 » exe file sizes change\n")
for key, exe in files.items():
if exe.is_file():
sizefile = sizefile_path.joinpath(key)
new_size: float = os.stat(exe).st_size / 1024
old_size: float
try:
with open(sizefile, 'r') as f:
old_size = float(f.read())
except FileNotFoundError:
old_size = 0
# diff: str = f'{old_size:.0f}kb'
diff = new_size - old_size
diff_str = f"{'+' if diff > 0 else '=' if diff == 0 else ''}{diff:.0f}kb"
fmt = bad if diff > 10 else good
sizefile.parent.mkdir(parents=True, exist_ok=True)
print(
f'{head(key)}: {fmt(diff_str)} (prev: {old_size}kb, now: {fmt(f"{str(new_size)}kb")})'
)
with open(sizefile, 'w') as f:
f.write(f'{new_size}')

View File

@ -1,21 +0,0 @@
[package]
name = "entities"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
name = "entities"
path = "src/lib.rs"
[dependencies]
# common
eyre = { workspace = true, default-features = false }
lool = { workspace = true, features = [ "cli", "cli.stylize", "macros", "logger" ] }
# external
sea-orm = { version = "0.12.15", features = [
"runtime-tokio-native-tls",
"sqlx-sqlite",
"macros",
] }

View File

@ -1,33 +1,27 @@
use {
crate::{
async_trait::async_trait,
eyre::Result,
lool::logger::info,
rustler_core::{
rustler,
rustlers::{Rustler, RustlerAccessor, RustlerStatus, Ticker},
},
async_trait::async_trait,
eyre::Result,
lool::{cli::stylize::Stylize, logger::info},
std::collections::HashMap,
};
const BINANCE_WSS_URL: &str = "wss://stream.binance.com:9443/stream";
// const MANUAL_CLOSE_CODE: u16 = 4663;
// const MANUAL_CLOSE_REASON: &str = "Manually Disconnected";
rustler!(
/// 🤠 » **binance rustler**
///
/// A rustler that steals quotes from Binance
pub struct BinanceRustler {}
/// A fake rustler that does nothing but changing between different statuses.
pub struct FooRustler {}
);
impl BinanceRustler {
impl FooRustler {
pub fn create() -> impl Rustler {
Self::default()
}
}
#[async_trait]
impl Rustler for BinanceRustler {
impl Rustler for FooRustler {
async fn connect(&mut self) -> Result<()> {
if self.status == RustlerStatus::Connected || self.status == RustlerStatus::Connecting {
return Ok(());
@ -35,10 +29,7 @@ impl Rustler for BinanceRustler {
self.set_status(RustlerStatus::Connecting)?;
info!(
"Connecting to Binance WSS: {}",
BINANCE_WSS_URL.bright_green()
);
info!("Connecting to data source");
self.set_status(RustlerStatus::Connected)?;
@ -53,7 +44,7 @@ impl Rustler for BinanceRustler {
self.set_status(RustlerStatus::Disconnecting)?;
info!("Disconnecting from Binance WSS");
info!("Disconnecting from data source");
self.set_status(RustlerStatus::Disconnected)?;

View File

@ -1,24 +1,25 @@
mod binance;
use {
binance::FooRustler,
dotenvy::dotenv,
eyre::Result,
eyre::{set_hook, DefaultHandler, Result},
lool::logger::{info, ConsoleLogger, Level},
rustlers::{rustlerjar, rustlers::binance::BinanceRustler, svc::RustlersSvc},
rustler_core::{entities::db::get_connection, grpc, rustlerjar, rustlers::svc::RustlersSvc},
tokio::join,
};
// TODO: here we will trigger the start of both the grpc server and the websocket gateway
// look at: https://github.com/hyperium/tonic/discussions/740
#[tokio::main]
async fn main() -> Result<()> {
set_hook(Box::new(DefaultHandler::default_with))?;
ConsoleLogger::default_setup(Level::Trace, "rustler")?;
dotenv()?;
let conn = entities::db::get_connection().await?;
let conn = get_connection().await?;
let mut rustler = RustlersSvc::new(
conn.clone(),
rustlerjar! {
"BINANCE" => BinanceRustler
"BINANCE" => FooRustler
},
)
.await;

View File

@ -1,14 +0,0 @@
[package]
name = "gateway"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
name = "gateway"
path = "src/lib.rs"
[dependencies]
entities = { path = "../entities" }
eyre = { workspace = true, default-features = false }

View File

View File

@ -1,25 +0,0 @@
[package]
name = "grpc"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
name = "grpc"
path = "src/lib.rs"
[dependencies]
# common
lool = { workspace = true, features = ["logger"]}
eyre = { workspace = true, default-features = false }
# internal
entities = { path = "../entities" }
# external
prost = "0.12.3"
tonic = "0.11.0"
[build-dependencies]
tonic-build = "0.11.0"

View File

@ -1,5 +1,9 @@
fn main() {
let proto_files = vec!["./proto/rustler.proto", "./proto/market.proto", "./proto/ticker.proto"];
let proto_files = vec![
"./lib/grpc/proto/rustler.proto",
"./lib/grpc/proto/market.proto",
"./lib/grpc/proto/ticker.proto",
];
for proto_file in proto_files {
compile_proto(proto_file);

View File

@ -1,6 +1,7 @@
use sea_orm_migration::{async_trait::async_trait, prelude::*};
#[derive(DeriveMigrationName)]
/// 🤠 » create table `market`
pub struct Migration;
#[async_trait]

View File

@ -1,6 +1,7 @@
use sea_orm_migration::{async_trait::async_trait, prelude::*};
#[derive(DeriveMigrationName)]
/// 🤠 » create table `ticker`
pub struct Migration;
#[async_trait]

View File

@ -0,0 +1,2 @@
mod m20220101_000001_create_table_market;
mod m20240325_200049_create_table_ticker;

View File

@ -1,3 +1,4 @@
pub mod migration;
pub use sea_orm;
mod orm {

View File

@ -4,6 +4,7 @@ use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "market")]
/// 🤠 » market entity model
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: String,

View File

@ -4,6 +4,7 @@ use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "ticker")]
/// 🤠 » ticker entity model
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: String,

View File

@ -1,5 +1,5 @@
use {
crate::{
crate::entities::{
market::{Entity as Market, Model as MarketModel},
ticker::{Entity as Ticker, Model as TickerModel},
},
@ -7,6 +7,7 @@ use {
sea_orm::{DatabaseConnection, DbErr, EntityTrait, IntoActiveModel},
};
/// 🤠 » service for the `Market` entity
pub struct Service {
conn: DatabaseConnection,
}
@ -16,16 +17,19 @@ impl Service {
Self { conn }
}
/// 🤠 » gets all markets from the database
pub async fn get_all(&self) -> Result<Vec<MarketModel>, DbErr> {
let markets = Market::find().all(&self.conn).await?;
Ok(markets)
}
/// 🤠 » gets a market by its id
pub async fn create(&self, market: MarketModel) -> Result<MarketModel, DbErr> {
Market::insert(market.clone().into_active_model()).exec(&self.conn).await?;
Ok(market)
}
/// 🤠 » gets all markets with their tickers
pub async fn get_all_with_tickers(
&self,
) -> Result<Vec<(MarketModel, Vec<TickerModel>)>, DbErr> {

View File

@ -1,5 +1,5 @@
use {
crate::{
crate::entities::{
orm::ticker,
ticker::{Entity as Ticker, Model as TickerModel},
},
@ -7,6 +7,7 @@ use {
sea_orm::{ColumnTrait, DatabaseConnection, DbErr, EntityTrait, IntoActiveModel, QueryFilter},
};
/// 🤠 » service for the `Ticker` entity
pub struct Service {
conn: DatabaseConnection,
}

View File

@ -1,12 +1,15 @@
mod services {
use {
entities::sea_orm::{DbErr, SqlErr},
lool::s,
sea_orm::{DbErr, SqlErr},
};
/// market grpc services
pub mod market;
/// ticker grpc services
pub mod ticker;
/// general error handling for sql errors in grpc services
pub(crate) fn handle_sql_err(err: DbErr, action: &str, entity_name: &str) -> tonic::Status {
let sqlerr = err.sql_err();

View File

@ -1,15 +1,18 @@
use {
crate::services,
entities::{market, sea_orm::DatabaseConnection, ticker},
crate::{
entities::{market, ticker},
grpc::services,
},
eyre::Result,
lool::{cli::stylize::Stylize, logger::info},
sea_orm::DatabaseConnection,
std::net::SocketAddr,
tonic::transport::Server,
};
const RUSTLER_GRPC_API_ADDR: &str = "RUSTLER_GRPC_API_ADDR";
/// Starts the gRPC server
/// 🤠 » starts the rustler gRPC server
pub async fn start(conn: DatabaseConnection) -> Result<()> {
fn get_default_addr() -> String {
let addr = "0.0.0.0:50051";
@ -36,8 +39,8 @@ pub async fn start(conn: DatabaseConnection) -> Result<()> {
);
Server::builder()
.add_service(market_grpc.svc())
.add_service(ticker_grpc.svc())
.add_service(market_grpc.svc()) // add the market api
.add_service(ticker_grpc.svc()) // add the ticker api
.serve(addr)
.await?;

View File

@ -1,7 +1,6 @@
use {
self::market_mod::Empty,
crate::services::handle_sql_err,
entities::market,
crate::{entities::market, grpc::services::handle_sql_err},
eyre::Result,
lool::logger::{error, info},
market_mod::{
@ -48,6 +47,7 @@ impl Market {
}
}
/// 🤠 » grpc Server to manage market entities
pub struct GrpcServer {
pub(crate) svc: market::Service,
}
@ -59,6 +59,7 @@ impl GrpcServer {
}
}
/// 🤠 » creates the market api server
pub fn svc(self) -> MarketApiServer<GrpcServer> {
MarketApiServer::new(self)
}

View File

@ -1,6 +1,5 @@
use {
crate::services::handle_sql_err,
entities::ticker,
crate::{entities::ticker, grpc::services::handle_sql_err},
eyre::Result,
lool::logger::{error, info},
std::{any::Any, fmt::Debug, time::Instant},
@ -33,6 +32,7 @@ impl Ticker {
}
}
/// 🤠 » grpc Server to manage ticker entities
pub struct GrpcServer {
pub(crate) svc: ticker::Service,
}
@ -44,6 +44,7 @@ impl GrpcServer {
}
}
/// 🤠 » creates the ticker api server
pub fn svc(self) -> TickerApiServer<GrpcServer> {
TickerApiServer::new(self)
}

4
lib/lib.rs Normal file
View File

@ -0,0 +1,4 @@
pub mod entities;
pub mod grpc;
pub mod rustlers;
pub mod socket;

6
lib/rustlers/mod.rs Normal file
View File

@ -0,0 +1,6 @@
mod rustler;
pub mod rustlerjar;
pub mod svc;
pub use rustler::*;

View File

@ -1,11 +1,10 @@
pub mod binance;
pub extern crate chrono;
pub extern crate eyre;
use {
crate::entities::{market, ticker},
async_trait::async_trait,
chrono::{DateTime, Local},
entities::{market, ticker},
eyre::Result,
std::collections::HashMap,
};
@ -59,6 +58,13 @@ pub struct ScrapperCallbacks {
pub on_message: Option<fn(message: Quote) -> Result<()>>,
}
/// 🤠 » a scruct representing a ticker
///
/// in `rustler` a ticker is the union between a symbol (stock identifier) and its market
///
/// they `key` of a ticker is the concatenation of the market and the symbol separated by a colon
///
/// e.g. `AAPL` in the `NASDAQ` market would have the key `NASDAQ:AAPL`
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Ticker {
pub symbol: String,
@ -77,6 +83,7 @@ impl Ticker {
tickers.iter().map(|t| Self::from(t, market)).collect()
}
/// 🤠 » returns the key of the ticker
pub fn key(&self) -> String {
format!("{}:{}", self.market, self.symbol)
}
@ -123,17 +130,17 @@ pub trait RustlerAccessor {
#[async_trait]
pub trait Rustler: RustlerAccessor + Send + Sync {
// #region Unimplemented trait functions
/// fn called after tickers are added to the rustler
/// 🤠 » fn called after tickers are added to the rustler
fn on_add(&mut self, tickers: &[Ticker]) -> Result<()>;
/// fn called after tickers are deleted from the rustler
/// 🤠 » fn called after tickers are deleted from the rustler
fn on_delete(&mut self, tickers: &[Ticker]) -> Result<()>;
/// connects the rustler to the data source
/// 🤠 » connects the rustler to the data source
async fn connect(&mut self) -> Result<()>;
/// disconnects the rustler from the data source
/// 🤠 » disconnects the rustler from the data source
async fn disconnect(&mut self) -> Result<()>;
// #endregion
/// should be called at construction time
/// 🤠 » starts the rustler
async fn start(&mut self) -> Result<()> {
let opts = self.opts();
if opts.connect_on_start {
@ -142,7 +149,7 @@ pub trait Rustler: RustlerAccessor + Send + Sync {
Ok(())
}
/// updates last stop and last run times and calls the appropriate callback
/// 🤠 » updates last stop and last run times and calls the appropriate callback
///
/// should be called after the status of the rustler changes
fn handle_status_change(&mut self) -> Result<()> {
@ -333,7 +340,11 @@ macro_rules! rustler_accessors {
};
}
// Define the macro
/// **🤠 » rustler builder macro**
///
/// The `rustler!` macro is used to define a new `Rustler` struct, expanding the struct definition
/// with the required fields and derives, and implementing the `RustlerAccessor` trait for the
/// struct.
#[macro_export]
macro_rules! rustler {
// Entry point for the macro, takes the struct definition

View File

@ -1,6 +1,6 @@
use {
crate::rustlers::Rustler,
entities::market,
super::rustler::Rustler,
crate::entities::market,
std::{collections::HashMap, sync::Arc},
tokio::sync::Mutex,
};
@ -34,7 +34,7 @@ macro_rules! rustlerjar {
instances.push(instance);
)*
$crate::rustlerjar::RustlerJar::new(instances, mappings)
$crate::rustlers::rustlerjar::RustlerJar::new(instances, mappings)
}};
}

View File

@ -1,5 +1,9 @@
use {
entities::{market, sea_orm::DatabaseConnection, ticker},
super::{
rustler::{Rustler, Ticker},
rustlerjar::RustlerJar,
},
crate::entities::{market, sea_orm::DatabaseConnection, ticker},
eyre::Result,
lool::{
logger::{info, warn},
@ -12,11 +16,6 @@ use {
tokio::sync::Mutex,
};
use crate::{
rustlerjar::RustlerJar,
rustlers::{Rustler, Ticker},
};
// interface MarketExecData {
// entity: MarketModel;
// startJob?: Job;
@ -34,6 +33,9 @@ use crate::{
// stopJob?: Job,
// }
/// **🤠 » Rustlers Service**
///
/// The `RustlersSvc` is a service that manages the rustlers and orchestrates their executions.
pub struct RustlersSvc {
market_svc: market::Service,
sched: Scheduler,
@ -41,7 +43,16 @@ pub struct RustlersSvc {
}
impl RustlersSvc {
/// **🤠 » create service**
///
/// creates a new instance of the `RustlersSvc`
///
/// **Arguments**
/// - `conn` - the database connection that will be used to get market and tickers data
/// - `rustlers` - the rustlers to be used by the service
///
/// **Returns**
/// the created `RustlersSvc` instance
pub async fn new(conn: DatabaseConnection, rustlers: RustlerJar) -> Self {
let market_svc = market::Service::new(conn).await;
let sched = Scheduler::new();
@ -53,6 +64,8 @@ impl RustlersSvc {
}
}
/// **🤠 » start rustlers**
///
/// gets market data from the the database and starts
/// the corresponding rustler for each market
pub async fn start(&mut self) -> Result<()> {
@ -66,6 +79,8 @@ impl RustlersSvc {
Ok(())
}
/// **🤠 » restart rustlers**
///
/// stops all rustlers and then starts them again
pub async fn restart(&self) -> Result<()> {
// TODO: restart all rustlers; this method should clear everything we set up about the
@ -222,6 +237,9 @@ impl RustlersSvc {
}
}
/// creates a rule for the given time and offset
///
/// TODO: handle timezones
fn make_rule(
base: &RecurrenceRuleSet,
time: (u32, u32, u32),
@ -239,6 +257,7 @@ fn make_rule(
rule
}
/// the operation to be performed on the time (addition or subtraction)
enum Op {
Add,
Sub,

1
lib/socket/mod.rs Normal file
View File

@ -0,0 +1 @@
// TODO: websocket gateway

View File

@ -1,20 +0,0 @@
[package]
name = "migration"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
name = "migration"
path = "src/lib.rs"
[dependencies]
async-std = { version = "1", features = ["attributes", "tokio1"] }
entities = { path = "../entities" }
[dependencies.sea-orm-migration]
version = "0.12.15"
features = [
"runtime-tokio-native-tls",
"sqlx-sqlite",
]

View File

@ -1,41 +0,0 @@
# Running Migrator CLI
- Generate a new migration file
```sh
cargo run -- generate MIGRATION_NAME
```
- Apply all pending migrations
```sh
cargo run
```
```sh
cargo run -- up
```
- Apply first 10 pending migrations
```sh
cargo run -- up -n 10
```
- Rollback last applied migrations
```sh
cargo run -- down
```
- Rollback last 10 applied migrations
```sh
cargo run -- down -n 10
```
- Drop all tables from the database, then reapply all migrations
```sh
cargo run -- fresh
```
- Rollback all applied migrations, then reapply all migrations
```sh
cargo run -- refresh
```
- Rollback all applied migrations
```sh
cargo run -- reset
```
- Check the status of all migrations
```sh
cargo run -- status
```

View File

@ -1,21 +0,0 @@
use sea_orm_migration::async_trait::async_trait;
pub use sea_orm_migration::prelude::*;
mod m20220101_000001_create_table_market;
mod m20240325_200049_create_table_ticker;
pub struct Migrator;
#[async_trait]
impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![
Box::new(m20220101_000001_create_table_market::Migration),
Box::new(m20240325_200049_create_table_ticker::Migration),
]
}
fn migration_table_name() -> sea_orm::DynIden {
Alias::new("migrations").into_iden()
}
}

View File

@ -1,6 +0,0 @@
use sea_orm_migration::prelude::*;
#[async_std::main]
async fn main() {
cli::run_cli(migration::Migrator).await;
}

View File

@ -1,32 +0,0 @@
[package]
name = "rustlers"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
name = "rustlers"
path = "src/lib.rs"
[dependencies]
# workspace
eyre = { workspace = true, default-features = false }
tokio-tungstenite = { workspace = true }
tokio = { workspace = true }
lool = { workspace = true, features = [
"logger",
"sched.tokio",
"sched.rule-recurrence",
"macros",
] }
# internal
entities = { path = "../entities" }
# external
chrono = "0.4.37"
async-trait = "0.1.79"
getset = "0.1.2"
[build-dependencies]
tonic-build = "0.11.0"

View File

@ -1,3 +0,0 @@
pub mod rustlerjar;
pub mod rustlers;
pub mod svc;