Organize websocket codebase
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
@@ -3,7 +3,7 @@ RUN apk add --no-cache pkgconf musl-dev openssl-dev
|
|||||||
|
|
||||||
WORKDIR /usr/src/qrust
|
WORKDIR /usr/src/qrust
|
||||||
|
|
||||||
ENV SQLX_OFFLINE true
|
ENV SQLX_OFFLINE=true
|
||||||
|
|
||||||
RUN mkdir src && echo "fn main() {}" > src/main.rs
|
RUN mkdir src && echo "fn main() {}" > src/main.rs
|
||||||
COPY Cargo.toml .sqlx ./
|
COPY Cargo.toml .sqlx ./
|
||||||
|
@@ -3,6 +3,7 @@ use crate::{
|
|||||||
Config, ALPACA_CRYPTO_DATA_URL, ALPACA_CRYPTO_WEBSOCKET_URL, ALPACA_STOCK_DATA_URL,
|
Config, ALPACA_CRYPTO_DATA_URL, ALPACA_CRYPTO_WEBSOCKET_URL, ALPACA_STOCK_DATA_URL,
|
||||||
ALPACA_STOCK_WEBSOCKET_URL, ALPACA_TIMESTAMP_FORMAT,
|
ALPACA_STOCK_WEBSOCKET_URL, ALPACA_TIMESTAMP_FORMAT,
|
||||||
},
|
},
|
||||||
|
data::authenticate_websocket,
|
||||||
database,
|
database,
|
||||||
time::{duration_until, last_minute, next_30s, next_minute, ONE_MINUTE, THIRTY_SECONDS},
|
time::{duration_until, last_minute, next_30s, next_minute, ONE_MINUTE, THIRTY_SECONDS},
|
||||||
types::{
|
types::{
|
||||||
@@ -19,7 +20,7 @@ use futures_util::{
|
|||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use serde_json::{from_str, to_string};
|
use serde_json::from_str;
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
@@ -83,50 +84,6 @@ pub async fn run(
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn authenticate_websocket(
|
|
||||||
app_config: &Arc<Config>,
|
|
||||||
stream: &mut SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>,
|
|
||||||
sink: &mut SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>,
|
|
||||||
) {
|
|
||||||
match stream.next().await {
|
|
||||||
Some(Ok(Message::Text(data)))
|
|
||||||
if from_str::<Vec<websocket::incoming::Message>>(&data)
|
|
||||||
.unwrap()
|
|
||||||
.get(0)
|
|
||||||
== Some(&websocket::incoming::Message::Success(
|
|
||||||
websocket::incoming::success::Message {
|
|
||||||
msg: websocket::incoming::success::MessageType::Connected,
|
|
||||||
},
|
|
||||||
)) => {}
|
|
||||||
_ => panic!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
sink.send(Message::Text(
|
|
||||||
to_string(&websocket::outgoing::Message::Auth(
|
|
||||||
websocket::outgoing::auth::Message::new(
|
|
||||||
app_config.alpaca_api_key.clone(),
|
|
||||||
app_config.alpaca_api_secret.clone(),
|
|
||||||
),
|
|
||||||
))
|
|
||||||
.unwrap(),
|
|
||||||
))
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
match stream.next().await {
|
|
||||||
Some(Ok(Message::Text(data)))
|
|
||||||
if from_str::<Vec<websocket::incoming::Message>>(&data)
|
|
||||||
.unwrap()
|
|
||||||
.get(0)
|
|
||||||
== Some(&websocket::incoming::Message::Success(
|
|
||||||
websocket::incoming::success::Message {
|
|
||||||
msg: websocket::incoming::success::MessageType::Authenticated,
|
|
||||||
},
|
|
||||||
)) => {}
|
|
||||||
_ => panic!(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn websocket_broadcast_handler(
|
async fn websocket_broadcast_handler(
|
||||||
class: Class,
|
class: Class,
|
||||||
sink: Arc<RwLock<SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>>>,
|
sink: Arc<RwLock<SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>>>,
|
||||||
@@ -140,8 +97,10 @@ async fn websocket_broadcast_handler(
|
|||||||
sink.write()
|
sink.write()
|
||||||
.await
|
.await
|
||||||
.send(Message::Text(
|
.send(Message::Text(
|
||||||
serde_json::to_string(&websocket::outgoing::Message::Subscribe(
|
serde_json::to_string(&websocket::data::outgoing::Message::Subscribe(
|
||||||
websocket::outgoing::subscribe::Message::new(asset.clone().symbol),
|
websocket::data::outgoing::subscribe::Message::new(
|
||||||
|
asset.clone().symbol,
|
||||||
|
),
|
||||||
))
|
))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
))
|
))
|
||||||
@@ -154,8 +113,10 @@ async fn websocket_broadcast_handler(
|
|||||||
sink.write()
|
sink.write()
|
||||||
.await
|
.await
|
||||||
.send(Message::Text(
|
.send(Message::Text(
|
||||||
serde_json::to_string(&websocket::outgoing::Message::Unsubscribe(
|
serde_json::to_string(&websocket::data::outgoing::Message::Unsubscribe(
|
||||||
websocket::outgoing::subscribe::Message::new(asset.clone().symbol),
|
websocket::data::outgoing::subscribe::Message::new(
|
||||||
|
asset.clone().symbol,
|
||||||
|
),
|
||||||
))
|
))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
))
|
))
|
||||||
@@ -177,9 +138,12 @@ async fn websocket_message_handler(
|
|||||||
loop {
|
loop {
|
||||||
match stream.next().await {
|
match stream.next().await {
|
||||||
Some(Ok(Message::Text(data))) => {
|
Some(Ok(Message::Text(data))) => {
|
||||||
let parsed_data = from_str::<Vec<websocket::incoming::Message>>(&data);
|
let parsed_data = from_str::<Vec<websocket::data::incoming::Message>>(&data);
|
||||||
if let Err(e) = &parsed_data {
|
if let Err(e) = &parsed_data {
|
||||||
warn!("Unparsed websocket::incoming message: {:?}: {}", data, e);
|
warn!(
|
||||||
|
"Unparsed websocket::data::incoming message: {:?}: {}",
|
||||||
|
data, e
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for message in parsed_data.unwrap_or_default() {
|
for message in parsed_data.unwrap_or_default() {
|
||||||
@@ -192,7 +156,7 @@ async fn websocket_message_handler(
|
|||||||
.send(Message::Pong(vec![]))
|
.send(Message::Pong(vec![]))
|
||||||
.await
|
.await
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
Some(unknown) => error!("Unknown websocket::incoming message: {:?}", unknown),
|
Some(unknown) => error!("Unknown websocket::data::incoming message: {:?}", unknown),
|
||||||
None => panic!(),
|
None => panic!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,11 +165,11 @@ async fn websocket_message_handler(
|
|||||||
async fn websocket_handle_text_message(
|
async fn websocket_handle_text_message(
|
||||||
app_config: &Arc<Config>,
|
app_config: &Arc<Config>,
|
||||||
class: Class,
|
class: Class,
|
||||||
message: websocket::incoming::Message,
|
message: websocket::data::incoming::Message,
|
||||||
backfilled: &Arc<RwLock<HashMap<String, bool>>>,
|
backfilled: &Arc<RwLock<HashMap<String, bool>>>,
|
||||||
) {
|
) {
|
||||||
match message {
|
match message {
|
||||||
websocket::incoming::Message::Subscription(subscription_message) => {
|
websocket::data::incoming::Message::Subscription(subscription_message) => {
|
||||||
let old_assets = backfilled
|
let old_assets = backfilled
|
||||||
.read()
|
.read()
|
||||||
.await
|
.await
|
||||||
@@ -243,7 +207,7 @@ async fn websocket_handle_text_message(
|
|||||||
class, added_assets, deleted_assets
|
class, added_assets, deleted_assets
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
websocket::incoming::Message::Bars(bar_message) => {
|
websocket::data::incoming::Message::Bars(bar_message) => {
|
||||||
let bar = Bar::from(bar_message);
|
let bar = Bar::from(bar_message);
|
||||||
info!(
|
info!(
|
||||||
"websocket::Incoming bar for {}: {}",
|
"websocket::Incoming bar for {}: {}",
|
||||||
@@ -256,7 +220,7 @@ async fn websocket_handle_text_message(
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
websocket::incoming::Message::UpdatedBars(bar_message) => {
|
websocket::data::incoming::Message::UpdatedBars(bar_message) => {
|
||||||
let bar = Bar::from(bar_message);
|
let bar = Bar::from(bar_message);
|
||||||
info!(
|
info!(
|
||||||
"websocket::Incoming bar for {}: {}",
|
"websocket::Incoming bar for {}: {}",
|
||||||
@@ -276,7 +240,7 @@ async fn websocket_handle_text_message(
|
|||||||
}
|
}
|
||||||
transaction.commit().await.unwrap();
|
transaction.commit().await.unwrap();
|
||||||
}
|
}
|
||||||
websocket::incoming::Message::Success(_) => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1 +1,56 @@
|
|||||||
pub mod market;
|
pub mod market;
|
||||||
|
|
||||||
|
use crate::{config::Config, types::websocket};
|
||||||
|
use core::panic;
|
||||||
|
use futures_util::{
|
||||||
|
stream::{SplitSink, SplitStream},
|
||||||
|
SinkExt, StreamExt,
|
||||||
|
};
|
||||||
|
use serde_json::{from_str, to_string};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::net::TcpStream;
|
||||||
|
use tokio_tungstenite::{tungstenite::Message, MaybeTlsStream, WebSocketStream};
|
||||||
|
|
||||||
|
async fn authenticate_websocket(
|
||||||
|
app_config: &Arc<Config>,
|
||||||
|
stream: &mut SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>,
|
||||||
|
sink: &mut SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>,
|
||||||
|
) {
|
||||||
|
match stream.next().await {
|
||||||
|
Some(Ok(Message::Text(data)))
|
||||||
|
if from_str::<Vec<websocket::data::incoming::Message>>(&data)
|
||||||
|
.unwrap()
|
||||||
|
.get(0)
|
||||||
|
== Some(&websocket::data::incoming::Message::Success(
|
||||||
|
websocket::data::incoming::success::Message {
|
||||||
|
msg: websocket::data::incoming::success::MessageType::Connected,
|
||||||
|
},
|
||||||
|
)) => {}
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
sink.send(Message::Text(
|
||||||
|
to_string(&websocket::data::outgoing::Message::Auth(
|
||||||
|
websocket::data::outgoing::auth::Message::new(
|
||||||
|
app_config.alpaca_api_key.clone(),
|
||||||
|
app_config.alpaca_api_secret.clone(),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.unwrap(),
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match stream.next().await {
|
||||||
|
Some(Ok(Message::Text(data)))
|
||||||
|
if from_str::<Vec<websocket::data::incoming::Message>>(&data)
|
||||||
|
.unwrap()
|
||||||
|
.get(0)
|
||||||
|
== Some(&websocket::data::incoming::Message::Success(
|
||||||
|
websocket::data::incoming::success::Message {
|
||||||
|
msg: websocket::data::incoming::success::MessageType::Authenticated,
|
||||||
|
},
|
||||||
|
)) => {}
|
||||||
|
_ => panic!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@@ -1,8 +1,56 @@
|
|||||||
use super::{api::incoming, class::Class, exchange::Exchange};
|
use super::api;
|
||||||
use serde::Serialize;
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::FromRow;
|
use sqlx::{FromRow, Type};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Type)]
|
||||||
|
pub enum Class {
|
||||||
|
#[sqlx(rename = "us_equity")]
|
||||||
|
#[serde(rename = "us_equity")]
|
||||||
|
UsEquity,
|
||||||
|
#[sqlx(rename = "crypto")]
|
||||||
|
#[serde(rename = "crypto")]
|
||||||
|
Crypto,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Type)]
|
||||||
|
pub enum Exchange {
|
||||||
|
#[sqlx(rename = "AMEX")]
|
||||||
|
#[serde(rename = "AMEX")]
|
||||||
|
Amex,
|
||||||
|
#[sqlx(rename = "ARCA")]
|
||||||
|
#[serde(rename = "ARCA")]
|
||||||
|
Arca,
|
||||||
|
#[sqlx(rename = "BATS")]
|
||||||
|
#[serde(rename = "BATS")]
|
||||||
|
Bats,
|
||||||
|
#[sqlx(rename = "NYSE")]
|
||||||
|
#[serde(rename = "NYSE")]
|
||||||
|
Nyse,
|
||||||
|
#[sqlx(rename = "NASDAQ")]
|
||||||
|
#[serde(rename = "NASDAQ")]
|
||||||
|
Nasdaq,
|
||||||
|
#[sqlx(rename = "NYSEARCA")]
|
||||||
|
#[serde(rename = "NYSEARCA")]
|
||||||
|
Nysearca,
|
||||||
|
#[sqlx(rename = "OTC")]
|
||||||
|
#[serde(rename = "OTC")]
|
||||||
|
Otc,
|
||||||
|
#[sqlx(rename = "CRYPTO")]
|
||||||
|
#[serde(rename = "CRYPTO")]
|
||||||
|
Crypto,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Deserialize, Type)]
|
||||||
|
pub enum Status {
|
||||||
|
#[sqlx(rename = "active")]
|
||||||
|
#[serde(rename = "active")]
|
||||||
|
Active,
|
||||||
|
#[sqlx(rename = "inactive")]
|
||||||
|
#[serde(rename = "inactive")]
|
||||||
|
Inactive,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, FromRow, Serialize)]
|
#[derive(Clone, Debug, FromRow, Serialize)]
|
||||||
pub struct Asset {
|
pub struct Asset {
|
||||||
pub symbol: String,
|
pub symbol: String,
|
||||||
@@ -14,8 +62,10 @@ pub struct Asset {
|
|||||||
pub timestamp_last: OffsetDateTime,
|
pub timestamp_last: OffsetDateTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<(incoming::Asset, bool, OffsetDateTime)> for Asset {
|
impl From<(api::incoming::Asset, bool, OffsetDateTime)> for Asset {
|
||||||
fn from((asset, trading, timestamp_first): (incoming::Asset, bool, OffsetDateTime)) -> Self {
|
fn from(
|
||||||
|
(asset, trading, timestamp_first): (api::incoming::Asset, bool, OffsetDateTime),
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
symbol: asset.symbol,
|
symbol: asset.symbol,
|
||||||
class: asset.class,
|
class: asset.class,
|
||||||
|
@@ -39,8 +39,8 @@ impl Bar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<websocket::incoming::bar::Message> for Bar {
|
impl From<websocket::data::incoming::bar::Message> for Bar {
|
||||||
fn from(bar_message: websocket::incoming::bar::Message) -> Self {
|
fn from(bar_message: websocket::data::incoming::bar::Message) -> Self {
|
||||||
Self {
|
Self {
|
||||||
timestamp: bar_message.timestamp,
|
timestamp: bar_message.timestamp,
|
||||||
asset_symbol: bar_message.symbol,
|
asset_symbol: bar_message.symbol,
|
||||||
|
@@ -1,12 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use sqlx::Type;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Type)]
|
|
||||||
pub enum Class {
|
|
||||||
#[sqlx(rename = "us_equity")]
|
|
||||||
#[serde(rename = "us_equity")]
|
|
||||||
UsEquity,
|
|
||||||
#[sqlx(rename = "crypto")]
|
|
||||||
#[serde(rename = "crypto")]
|
|
||||||
Crypto,
|
|
||||||
}
|
|
@@ -1,30 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use sqlx::Type;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Type)]
|
|
||||||
pub enum Exchange {
|
|
||||||
#[sqlx(rename = "AMEX")]
|
|
||||||
#[serde(rename = "AMEX")]
|
|
||||||
Amex,
|
|
||||||
#[sqlx(rename = "ARCA")]
|
|
||||||
#[serde(rename = "ARCA")]
|
|
||||||
Arca,
|
|
||||||
#[sqlx(rename = "BATS")]
|
|
||||||
#[serde(rename = "BATS")]
|
|
||||||
Bats,
|
|
||||||
#[sqlx(rename = "NYSE")]
|
|
||||||
#[serde(rename = "NYSE")]
|
|
||||||
Nyse,
|
|
||||||
#[sqlx(rename = "NASDAQ")]
|
|
||||||
#[serde(rename = "NASDAQ")]
|
|
||||||
Nasdaq,
|
|
||||||
#[sqlx(rename = "NYSEARCA")]
|
|
||||||
#[serde(rename = "NYSEARCA")]
|
|
||||||
Nysearca,
|
|
||||||
#[sqlx(rename = "OTC")]
|
|
||||||
#[serde(rename = "OTC")]
|
|
||||||
Otc,
|
|
||||||
#[sqlx(rename = "CRYPTO")]
|
|
||||||
#[serde(rename = "CRYPTO")]
|
|
||||||
Crypto,
|
|
||||||
}
|
|
@@ -1,18 +1,12 @@
|
|||||||
pub mod api;
|
pub mod api;
|
||||||
pub mod asset;
|
pub mod asset;
|
||||||
pub mod bar;
|
pub mod bar;
|
||||||
pub mod class;
|
|
||||||
pub mod exchange;
|
|
||||||
pub mod source;
|
pub mod source;
|
||||||
pub mod status;
|
|
||||||
pub mod websocket;
|
pub mod websocket;
|
||||||
|
|
||||||
pub use asset::Asset;
|
pub use asset::{Asset, Class, Exchange, Status};
|
||||||
pub use bar::Bar;
|
pub use bar::Bar;
|
||||||
pub use class::Class;
|
|
||||||
pub use exchange::Exchange;
|
|
||||||
pub use source::Source;
|
pub use source::Source;
|
||||||
pub use status::Status;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum BroadcastMessage {
|
pub enum BroadcastMessage {
|
||||||
|
@@ -1,12 +0,0 @@
|
|||||||
use serde::Deserialize;
|
|
||||||
use sqlx::Type;
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Deserialize, Type)]
|
|
||||||
pub enum Status {
|
|
||||||
#[sqlx(rename = "active")]
|
|
||||||
#[serde(rename = "active")]
|
|
||||||
Active,
|
|
||||||
#[sqlx(rename = "inactive")]
|
|
||||||
#[serde(rename = "inactive")]
|
|
||||||
Inactive,
|
|
||||||
}
|
|
2
src/types/websocket/data/mod.rs
Normal file
2
src/types/websocket/data/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod incoming;
|
||||||
|
pub mod outgoing;
|
@@ -1,2 +1 @@
|
|||||||
pub mod incoming;
|
pub mod data;
|
||||||
pub mod outgoing;
|
|
||||||
|
Reference in New Issue
Block a user