use cratefield_core::{Database, DbError, Row, Statement};
use sea_query::{Alias, Expr, Query};
const NONCES: &str = "wallet_nonces";
const LINKS: &str = "wallet_links";
fn iden(name: &str) -> Alias {
Alias::new(name)
}
pub(crate) struct NonceRow {
pub(crate) account_id: String,
pub(crate) chain: String,
pub(crate) chain_id: String,
}
fn nonce_from(row: &Row) -> NonceRow {
NonceRow {
account_id: row.get("account_id").unwrap_or_default(),
chain: row.get("chain").unwrap_or_default(),
chain_id: row.get("chain_id").unwrap_or_default(),
}
}
pub(crate) async fn insert_nonce(
db: &dyn Database,
nonce: &str,
account_id: &str,
chain: &str,
chain_id: &str,
now: &str,
expires_at: &str,
) -> Result<(), DbError> {
let mut insert = Query::insert();
insert
.into_table(iden(NONCES))
.columns([
"nonce",
"account_id",
"chain",
"chain_id",
"created_at",
"expires_at",
])
.values_panic([
nonce.into(),
account_id.into(),
chain.into(),
chain_id.into(),
now.into(),
expires_at.into(),
]);
db.execute(&Statement::render(&insert)).await.map(|_| ())
}
pub(crate) async fn find_nonce(
db: &dyn Database,
nonce: &str,
) -> Result<Option<NonceRow>, DbError> {
let mut select = Query::select();
select
.columns(["account_id", "chain", "chain_id"])
.from(iden(NONCES))
.and_where(Expr::col(iden("nonce")).eq(nonce));
Ok(db
.query(&Statement::render(&select))
.await?
.first()
.map(nonce_from))
}
pub(crate) async fn consume_nonce(
db: &dyn Database,
nonce: &str,
now: &str,
) -> Result<u64, DbError> {
let mut update = Query::update();
update
.table(iden(NONCES))
.value(iden("consumed_at"), now)
.and_where(Expr::col(iden("nonce")).eq(nonce))
.and_where(Expr::col(iden("consumed_at")).is_null())
.and_where(Expr::col(iden("expires_at")).gt(now));
db.execute(&Statement::render(&update)).await
}
pub(crate) async fn purge_expired_nonces(db: &dyn Database, now: &str) -> Result<u64, DbError> {
let mut delete = Query::delete();
delete
.from_table(iden(NONCES))
.and_where(Expr::col(iden("expires_at")).lte(now));
db.execute(&Statement::render(&delete)).await
}
pub(crate) struct LinkRow {
pub(crate) id: String,
pub(crate) account_id: String,
pub(crate) chain: String,
pub(crate) address: String,
pub(crate) linked_at: String,
}
fn link_from(row: &Row) -> LinkRow {
LinkRow {
id: row.get("id").unwrap_or_default(),
account_id: row.get("account_id").unwrap_or_default(),
chain: row.get("chain").unwrap_or_default(),
address: row.get("address").unwrap_or_default(),
linked_at: row.get("linked_at").unwrap_or_default(),
}
}
pub(crate) enum InsertOutcome {
Created(LinkRow),
SameAccount(LinkRow),
DifferentAccount,
}
pub(crate) async fn insert_link(
db: &dyn Database,
id: &str,
account_id: &str,
chain: &str,
address: &str,
now: &str,
) -> Result<InsertOutcome, DbError> {
let mut insert = Query::insert();
insert
.into_table(iden(LINKS))
.columns(["id", "account_id", "chain", "address", "linked_at"])
.values_panic([
id.into(),
account_id.into(),
chain.into(),
address.into(),
now.into(),
])
.on_conflict(
sea_query::OnConflict::columns([iden("chain"), iden("address")])
.do_nothing()
.to_owned(),
);
let inserted = db.execute(&Statement::render(&insert)).await?;
if inserted == 1 {
return Ok(InsertOutcome::Created(LinkRow {
id: id.to_owned(),
account_id: account_id.to_owned(),
chain: chain.to_owned(),
address: address.to_owned(),
linked_at: now.to_owned(),
}));
}
let existing = find_link_by_chain_address(db, chain, address).await?;
match existing {
Some(row) if row.account_id == account_id => Ok(InsertOutcome::SameAccount(row)),
Some(_) => Ok(InsertOutcome::DifferentAccount),
None => {
Ok(InsertOutcome::DifferentAccount)
}
}
}
pub(crate) async fn find_link_by_chain_address(
db: &dyn Database,
chain: &str,
address: &str,
) -> Result<Option<LinkRow>, DbError> {
let mut select = Query::select();
select
.columns(["id", "account_id", "chain", "address", "linked_at"])
.from(iden(LINKS))
.and_where(Expr::col(iden("chain")).eq(chain))
.and_where(Expr::col(iden("address")).eq(address));
Ok(db
.query(&Statement::render(&select))
.await?
.first()
.map(link_from))
}
pub(crate) async fn links_for_account(
db: &dyn Database,
account_id: &str,
) -> Result<Vec<LinkRow>, DbError> {
let mut select = Query::select();
select
.columns(["id", "account_id", "chain", "address", "linked_at"])
.from(iden(LINKS))
.and_where(Expr::col(iden("account_id")).eq(account_id));
Ok(db
.query(&Statement::render(&select))
.await?
.rows
.iter()
.map(link_from)
.collect())
}
pub(crate) async fn delete_link(
db: &dyn Database,
id: &str,
account_id: &str,
) -> Result<u64, DbError> {
let mut delete = Query::delete();
delete
.from_table(iden(LINKS))
.and_where(Expr::col(iden("id")).eq(id))
.and_where(Expr::col(iden("account_id")).eq(account_id));
db.execute(&Statement::render(&delete)).await
}