back to ids

i still think uuids are a better idea, but sea orm has some kinks to work out
This commit is contained in:
Bryan Stitt 2022-08-06 00:07:12 +00:00
parent 20384e7f2f
commit b90f80f46b
14 changed files with 217 additions and 167 deletions

@ -2,15 +2,14 @@
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
use sea_orm::prelude::Uuid;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "block_list")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub uuid: Uuid,
#[sea_orm(primary_key)]
pub id: i64,
#[sea_orm(unique)]
pub address: String,
pub address: Vec<u8>,
pub description: Option<String>,
}

@ -2,16 +2,15 @@
use super::sea_orm_active_enums::Role;
use sea_orm::entity::prelude::*;
use sea_orm::prelude::Uuid;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "secondary_user")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub uuid: Uuid,
pub user_id: Uuid,
pub address: String,
#[sea_orm(primary_key)]
pub id: i64,
pub user_id: i64,
pub address: Vec<u8>,
pub description: Option<String>,
pub email: Option<String>,
pub role: Role,
@ -22,7 +21,7 @@ pub enum Relation {
#[sea_orm(
belongs_to = "super::user::Entity",
from = "Column::UserId",
to = "super::user::Column::Uuid",
to = "super::user::Column::Id",
on_update = "NoAction",
on_delete = "NoAction"
)]

@ -2,15 +2,14 @@
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
use sea_orm::prelude::Uuid;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "user")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub uuid: Uuid,
#[sea_orm(primary_key)]
pub id: i64,
#[sea_orm(unique)]
pub address: String,
pub address: Vec<u8>,
pub description: Option<String>,
pub email: Option<String>,
}

@ -7,9 +7,9 @@ use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "user_keys")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub uuid: Uuid,
pub user_uuid: Uuid,
#[sea_orm(primary_key)]
pub id: i64,
pub user_id: i64,
#[sea_orm(unique)]
pub api_key: Uuid,
pub description: Option<String>,
@ -21,8 +21,8 @@ pub struct Model {
pub enum Relation {
#[sea_orm(
belongs_to = "super::user::Entity",
from = "Column::UserUuid",
to = "super::user::Column::Uuid",
from = "Column::UserId",
to = "super::user::Column::Id",
on_update = "NoAction",
on_delete = "NoAction"
)]

@ -12,15 +12,15 @@ impl MigrationTrait for Migration {
Table::create()
.table(User::Table)
.col(
ColumnDef::new(User::Uuid)
.uuid()
ColumnDef::new(User::Id)
.big_integer()
.not_null()
.extra("DEFAULT (UUID_TO_BIN(UUID()))".to_string())
.primary_key(),
.primary_key()
.auto_increment(),
)
.col(
ColumnDef::new(User::Address)
.string_len(42)
.binary_len(20)
.not_null()
.unique_key(),
)
@ -36,16 +36,20 @@ impl MigrationTrait for Migration {
Table::create()
.table(SecondaryUser::Table)
.col(
ColumnDef::new(SecondaryUser::Uuid)
.uuid()
ColumnDef::new(SecondaryUser::Id)
.big_integer()
.not_null()
.extra("DEFAULT (UUID_TO_BIN(UUID()))".to_string())
.primary_key(),
.primary_key()
.auto_increment(),
)
.col(
ColumnDef::new(SecondaryUser::UserId)
.big_integer()
.not_null(),
)
.col(ColumnDef::new(SecondaryUser::UserId).uuid().not_null())
.col(
ColumnDef::new(SecondaryUser::Address)
.string_len(42)
.binary_len(20)
.not_null(),
)
.col(ColumnDef::new(SecondaryUser::Description).string())
@ -59,7 +63,7 @@ impl MigrationTrait for Migration {
.foreign_key(
sea_query::ForeignKey::create()
.from(SecondaryUser::Table, SecondaryUser::UserId)
.to(User::Table, User::Uuid),
.to(User::Table, User::Id),
)
.to_owned(),
)
@ -71,15 +75,15 @@ impl MigrationTrait for Migration {
Table::create()
.table(BlockList::Table)
.col(
ColumnDef::new(BlockList::Uuid)
.uuid()
ColumnDef::new(BlockList::Id)
.big_integer()
.not_null()
.extra("DEFAULT (UUID_TO_BIN(UUID()))".to_string())
.primary_key(),
.primary_key()
.auto_increment(),
)
.col(
ColumnDef::new(BlockList::Address)
.string()
.binary_len(20)
.not_null()
.unique_key(),
)
@ -94,13 +98,13 @@ impl MigrationTrait for Migration {
Table::create()
.table(UserKeys::Table)
.col(
ColumnDef::new(UserKeys::Uuid)
.uuid()
ColumnDef::new(UserKeys::Id)
.big_integer()
.not_null()
.extra("DEFAULT (UUID_TO_BIN(UUID()))".to_string())
.primary_key(),
.primary_key()
.auto_increment(),
)
.col(ColumnDef::new(UserKeys::UserUuid).uuid().not_null())
.col(ColumnDef::new(UserKeys::UserId).big_integer().not_null())
.col(
ColumnDef::new(UserKeys::ApiKey)
.uuid()
@ -123,8 +127,8 @@ impl MigrationTrait for Migration {
.index(sea_query::Index::create().col(UserKeys::Active))
.foreign_key(
sea_query::ForeignKey::create()
.from(UserKeys::Table, UserKeys::UserUuid)
.to(User::Table, User::Uuid),
.from(UserKeys::Table, UserKeys::UserId)
.to(User::Table, User::Id),
)
.to_owned(),
)
@ -156,7 +160,7 @@ impl MigrationTrait for Migration {
#[derive(Iden)]
enum User {
Table,
Uuid,
Id,
Address,
Description,
Email,
@ -172,7 +176,7 @@ enum User {
#[derive(Iden)]
enum SecondaryUser {
Table,
Uuid,
Id,
UserId,
Address,
Description,
@ -184,7 +188,7 @@ enum SecondaryUser {
#[derive(Iden)]
enum BlockList {
Table,
Uuid,
Id,
Address,
Description,
}
@ -202,8 +206,8 @@ enum BlockList {
#[derive(Iden)]
enum UserKeys {
Table,
Uuid,
UserUuid,
Id,
UserId,
ApiKey,
Description,
PrivateTxs,

@ -48,6 +48,7 @@ sea-orm = { version = "0.9.1", features = ["macros"] }
serde = { version = "1.0.142", features = [] }
serde_json = { version = "1.0.83", default-features = false, features = ["alloc", "raw_value"] }
tokio = { version = "1.20.1", features = ["full", "tracing"] }
# TODO: make sure this uuid version matches what is in sea orm. PR on sea orm to put builder into prelude
uuid = "1.1.2"
toml = "0.5.9"
tracing = "0.1.36"

@ -14,6 +14,7 @@ use migration::{Migrator, MigratorTrait};
use parking_lot::RwLock;
use redis_cell_client::bb8::ErrorSink;
use redis_cell_client::{bb8, RedisCellClient, RedisConnectionManager};
use sea_orm::DatabaseConnection;
use serde_json::json;
use std::fmt;
use std::mem::size_of_val;
@ -236,6 +237,31 @@ fn block_needed(
}
}
pub async fn get_migrated_db(
db_url: String,
min_connections: u32,
) -> anyhow::Result<DatabaseConnection> {
let mut db_opt = sea_orm::ConnectOptions::new(db_url);
// TODO: load all these options from the config file
// TODO: sqlx logging only in debug. way too verbose for production
db_opt
.max_connections(100)
.min_connections(min_connections)
.connect_timeout(Duration::from_secs(8))
.idle_timeout(Duration::from_secs(8))
.max_lifetime(Duration::from_secs(60))
.sqlx_logging(false);
// .sqlx_logging_level(log::LevelFilter::Info);
let db = sea_orm::Database::connect(db_opt).await?;
// TODO: if error, roll back?
Migrator::up(&db, None).await?;
Ok(db)
}
// TODO: think more about TxState. d
#[derive(Clone)]
pub enum TxState {
@ -296,24 +322,11 @@ impl Web3ProxyApp {
)> {
// first, we connect to mysql and make sure the latest migrations have run
let db_conn = if let Some(db_url) = app_config.shared.db_url {
let mut db_opt = sea_orm::ConnectOptions::new(db_url);
let min_connections = num_workers.try_into()?;
// TODO: load all these options from the config file
db_opt
.max_connections(100)
.min_connections(num_workers.try_into()?)
.connect_timeout(Duration::from_secs(8))
.idle_timeout(Duration::from_secs(8))
.max_lifetime(Duration::from_secs(60))
.sqlx_logging(true);
// .sqlx_logging_level(log::LevelFilter::Info);
let db = get_migrated_db(db_url, min_connections).await?;
let db_conn = sea_orm::Database::connect(db_opt).await?;
// TODO: if error, roll back
Migrator::up(&db_conn, None).await?;
Some(db_conn)
Some(db)
} else {
info!("no database");
None

@ -1,100 +0,0 @@
use argh::FromArgs;
use entities::{user, user_keys};
use fstrings::{format_args_f, println_f};
use sea_orm::ActiveModelTrait;
use web3_proxy::users::new_api_key;
#[derive(Debug, FromArgs)]
/// Command line interface for admins to interact with web3-proxy
pub struct TopConfig {
/// what host the client should connect to
#[argh(
option,
default = "\"mysql://root:dev_web3_proxy@127.0.0.1:3306/dev_web3_proxy\".to_string()"
)]
pub db_url: String,
/// this one cli can do multiple things
#[argh(subcommand)]
sub_command: SubCommand,
}
#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand)]
enum SubCommand {
CreateUser(CreateUserSubCommand),
Two(SubCommandTwo),
// TODO: sub command to downgrade migrations?
// TODO: sub command to add new api keys to an existing user?
}
#[derive(FromArgs, PartialEq, Debug)]
/// First subcommand.
#[argh(subcommand, name = "create_user")]
struct CreateUserSubCommand {
#[argh(option)]
/// the user's ethereum address
address: String,
#[argh(option)]
/// the user's optional email
email: Option<String>,
}
impl CreateUserSubCommand {
async fn main(self, db: &sea_orm::DatabaseConnection) -> anyhow::Result<()> {
let u = user::ActiveModel {
address: sea_orm::Set(self.address),
email: sea_orm::Set(self.email),
..Default::default()
};
// TODO: proper error message
let u = u.insert(db).await?;
println_f!("user: {u:?}");
// create a key for the new user
let uk = user_keys::ActiveModel {
user_uuid: sea_orm::Set(u.uuid),
api_key: sea_orm::Set(new_api_key()),
..Default::default()
};
println_f!("user key: {uk:?}");
Ok(())
}
}
#[derive(FromArgs, PartialEq, Debug)]
/// Second subcommand.
#[argh(subcommand, name = "two")]
struct SubCommandTwo {
#[argh(switch)]
/// whether to fooey
fooey: bool,
}
impl SubCommandTwo {
async fn main(self) -> anyhow::Result<()> {
todo!()
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let cli_config: TopConfig = argh::from_env();
println!("hello, {}", cli_config.db_url);
match cli_config.sub_command {
SubCommand::CreateUser(x) => {
// TODO: more advanced settings
let db_conn = sea_orm::Database::connect(cli_config.db_url).await?;
x.main(&db_conn).await
}
SubCommand::Two(x) => x.main().await,
}
}

@ -0,0 +1,54 @@
use anyhow::Context;
use argh::FromArgs;
use entities::{user, user_keys};
use ethers::types::Address;
use fstrings::{format_args_f, println_f};
use sea_orm::ActiveModelTrait;
use web3_proxy::users::new_api_key;
#[derive(FromArgs, PartialEq, Debug)]
/// First subcommand.
#[argh(subcommand, name = "create_user")]
pub struct CreateUserSubCommand {
#[argh(option)]
/// the user's ethereum address
address: String,
#[argh(option)]
/// the user's optional email
email: Option<String>,
}
impl CreateUserSubCommand {
pub async fn main(self, db: &sea_orm::DatabaseConnection) -> anyhow::Result<()> {
let address = self
.address
.parse::<Address>()
.context("Failed parsing new user address")?
.to_fixed_bytes()
.into();
let u = user::ActiveModel {
address: sea_orm::Set(address),
email: sea_orm::Set(self.email),
..Default::default()
};
let u = u.insert(db).await.context("Failed saving new user")?;
println_f!("user: {u:?}");
// create a key for the new user
let uk = user_keys::ActiveModel {
user_id: sea_orm::Set(u.id),
api_key: sea_orm::Set(new_api_key()),
..Default::default()
};
let uk = uk.insert(db).await.context("Failed saving new user key")?;
println_f!("user key: {uk:?}");
Ok(())
}
}

@ -0,0 +1,62 @@
mod create_user;
mod two;
use argh::FromArgs;
use tracing::info;
use web3_proxy::app::get_migrated_db;
#[derive(Debug, FromArgs)]
/// Command line interface for admins to interact with web3-proxy
pub struct TopConfig {
/// what host the client should connect to
#[argh(
option,
default = "\"mysql://root:dev_web3_proxy@127.0.0.1:3306/dev_web3_proxy\".to_string()"
)]
pub db_url: String,
/// this one cli can do multiple things
#[argh(subcommand)]
sub_command: SubCommand,
}
#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand)]
enum SubCommand {
CreateUser(create_user::CreateUserSubCommand),
Two(two::SubCommandTwo),
// TODO: sub command to downgrade migrations?
// TODO: sub command to add new api keys to an existing user?
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// if RUST_LOG isn't set, configure a default
// TODO: is there a better way to do this?
if std::env::var("RUST_LOG").is_err() {
// std::env::set_var("RUST_LOG", "info,web3_proxy=debug,web3_proxy_cli=debug");
std::env::set_var("RUST_LOG", "info,web3_proxy=debug,web3_proxy_cli=debug");
}
// install global collector configured based on RUST_LOG env var.
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.compact()
.init();
// this probably won't matter for us in docker, but better safe than sorry
fdlimit::raise_fd_limit();
let cli_config: TopConfig = argh::from_env();
info!("hello, {}", cli_config.db_url);
match cli_config.sub_command {
SubCommand::CreateUser(x) => {
let db = get_migrated_db(cli_config.db_url, 1).await?;
x.main(&db).await
}
SubCommand::Two(x) => x.main().await,
}
}

@ -0,0 +1,16 @@
use argh::FromArgs;
#[derive(FromArgs, PartialEq, Debug)]
/// Second subcommand.
#[argh(subcommand, name = "two")]
pub struct SubCommandTwo {
#[argh(switch)]
/// whether to fooey
fooey: bool,
}
impl SubCommandTwo {
pub async fn main(self) -> anyhow::Result<()> {
todo!()
}
}

@ -40,7 +40,7 @@ pub async fn rate_limit_by_key(
// TODO: probably want a cache on this
match user_keys::Entity::find()
.select_only()
.column(user_keys::Column::UserUuid)
.column(user_keys::Column::UserId)
.filter(user_keys::Column::ApiKey.eq(user_key))
.filter(user_keys::Column::Active.eq(true))
.one(db)

@ -44,7 +44,7 @@ pub async fn create_user(
}
let user = user::ActiveModel {
address: sea_orm::Set(payload.address.to_string()),
address: sea_orm::Set(payload.address.to_fixed_bytes().into()),
email: sea_orm::Set(payload.email),
..Default::default()
};

@ -4,6 +4,7 @@ use serde::de::{self, Deserialize, Deserializer, MapAccess, SeqAccess, Visitor};
use serde::Serialize;
use serde_json::value::RawValue;
use std::fmt;
use tracing::warn;
#[derive(Clone, serde::Deserialize)]
pub struct JsonRpcRequest {
@ -166,6 +167,8 @@ impl JsonRpcForwardedResponse {
pub fn from_anyhow_error(err: anyhow::Error, id: Box<RawValue>) -> Self {
let err = format!("{:?}", err);
warn!("forwarding error. {:?}", err);
JsonRpcForwardedResponse {
jsonrpc: "2.0".to_string(),
id,
@ -173,7 +176,7 @@ impl JsonRpcForwardedResponse {
error: Some(JsonRpcErrorData {
// TODO: set this jsonrpc error code to match the http status code
code: -32099,
message: err,
message: "internal server error".to_string(),
data: None,
}),
}