Skip to content

Commit

Permalink
impl: initial badge generator
Browse files Browse the repository at this point in the history
  • Loading branch information
cilki committed Jun 15, 2024
1 parent 859ee51 commit 7186ace
Show file tree
Hide file tree
Showing 8 changed files with 606 additions and 334 deletions.
803 changes: 509 additions & 294 deletions Cargo.lock

Large diffs are not rendered by default.

12 changes: 7 additions & 5 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ use axum::{
};
use axum_macros::debug_handler;
use cached::proc_macro::once;
use chrono::{DateTime, Utc};
use float_pretty_print::PrettyPrintFloat;
use rust_embed::Embed;
use tracing::{debug, instrument};
use tracing::debug;

#[derive(Template, Debug, Clone, Default)]
#[template(path = "index.html")]
Expand All @@ -23,13 +22,14 @@ pub struct IndexTemplate {
monero_wallet_address: String,
repository_url: String,
monero_transactions: Vec<Transaction>,
usd_balance: String,
monero_balance_usd: String,
}

#[once(time = "60")]
#[debug_handler]
pub async fn index(State(state): State<AppState>) -> IndexTemplate {
let monero_balance = state.monero.get_balance().await.unwrap() as f64 / f64::powf(10.0, 12.0);
#[cfg(feature = "monero")]
let monero_balance = state.monero.get_balance().await.unwrap().as_xmr();
let repo = state.repo.lock().await;

IndexTemplate {
Expand All @@ -48,6 +48,7 @@ pub async fn index(State(state): State<AppState>) -> IndexTemplate {
#[cfg(feature = "monero")]
monero_wallet_address: state.monero.wallet_address.to_string(),
repository_url: repo.remote.clone(),
#[cfg(feature = "monero")]
monero_transactions: state
.monero
.get_transfers()
Expand All @@ -56,7 +57,8 @@ pub async fn index(State(state): State<AppState>) -> IndexTemplate {
.iter()
.filter_map(|transfer| repo.monero_transfer(transfer).ok())
.collect(),
usd_balance: format!(
#[cfg(feature = "monero")]
monero_balance_usd: format!(
"{}",
PrettyPrintFloat(crate::currency::lookup("XMR").await.unwrap_or(0.0) * monero_balance)
),
Expand Down
31 changes: 31 additions & 0 deletions src/badge.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/// Generate a simple SVG badge with the given attributes.
pub fn generate(title: &str, value: &str) -> String {
format!(
r###"
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" width="116" height="20" role="img" aria-label="balance: 20.0 XMR">
<title>balance: 20.0 XMR</title>
<linearGradient id="s" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<clipPath id="r">
<rect width="116" height="20" rx="3" fill="#fff"/>
</clipPath>
<g clip-path="url(#r)">
<rect width="53" height="20" fill="#555"/>
<rect x="53" width="63" height="20" fill="#97ca00"/>
<rect width="116" height="20" fill="url(#s)"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110">
<text aria-hidden="true" x="275" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">balance</text>
<text x="275" y="140" transform="scale(.1)" fill="#fff" textLength="430">{}</text>
<text aria-hidden="true" x="835" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="530">20.0 XMR</text>
<text x="835" y="140" transform="scale(.1)" fill="#fff" textLength="530">{}</text>
</g>
</svg>
"###,
title, value,
)
}
17 changes: 10 additions & 7 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
use crate::repo::TurbineRepo;
use anyhow::Result;
use axum::{
extract::FromRef,
routing::{get, post},
Router,
};
use chrono::Utc;
use clap::{Args, Parser};
use std::{env, process::ExitCode, sync::Arc};
use tokio::spawn;
use clap::Args;
use std::{process::ExitCode, sync::Arc};

use tokio::{net::TcpListener, sync::Mutex};
use tokio_schedule::{every, Job};
use tracing::info;
Expand Down Expand Up @@ -90,6 +89,9 @@ pub async fn serve(args: &ServeArgs) -> Result<ExitCode> {
.route("/refresh", post(crate::api::refresh))
.route("/assets/*file", get(crate::api::assets));

#[cfg(feature = "monero")]
let app = app.route("/xmr/balance", get(crate::currency::monero::balance));

// Refresh every hour
let every_hour = every(1)
.hour()
Expand All @@ -104,9 +106,10 @@ pub async fn serve(args: &ServeArgs) -> Result<ExitCode> {
});
tokio::spawn(every_hour);

info!("Starting listener");
let listener =
TcpListener::bind(args.bind.as_ref().unwrap_or(&"0.0.0.0:3000".to_string())).await?;
let address = args.bind.clone().unwrap_or("0.0.0.0:3000".to_string());

info!(address =?address,"Starting API");
let listener = TcpListener::bind(address).await?;
axum::serve(listener, app.with_state(state)).await?;
Ok(ExitCode::SUCCESS)
}
63 changes: 41 additions & 22 deletions src/currency/monero.rs
Original file line number Diff line number Diff line change
@@ -1,43 +1,47 @@
use crate::{
cli::ServeArgs,
repo::{Transaction, TurbineRepo},
CommandLine,
};
use crate::cli::{AppState, ServeArgs};
use anyhow::Result;
use axum::extract::State;
use axum::response::IntoResponse;

use float_pretty_print::PrettyPrintFloat;
use git2::Oid;
use monero::util::address::PaymentId;
use monero_rpc::{
monero::{Address, Amount},
AddressData, BlockHeightFilter, GetTransfersCategory, GetTransfersSelector, GotTransfer,
BlockHeightFilter, GetTransfersCategory, GetTransfersSelector, GotTransfer,
RestoreDeterministicWalletArgs, RpcClientBuilder, TransferOptions, TransferPriority,
WalletClient,
};
use std::str::FromStr;
use reqwest::header;
use std::{
collections::HashMap,
process::{Child, Command},
sync::Arc,
time::Duration,
};
use tracing::{debug, info, instrument};
use std::{str::FromStr, sync::Mutex};
use tracing::{debug, info};

#[derive(Clone, Debug)]
pub struct MoneroState {
pub wallet: WalletClient,
wallet_process: Option<Arc<Child>>,
wallet_process: Option<Arc<Mutex<Child>>>,
pub wallet_address: Address,
account_index: u32,
minimum_block_height: u64,
}

// impl Drop for MoneroState {
// fn drop(&mut self) {
// if let Some(process) = self.wallet_process.as_mut() {
// debug!("Stopping RPC wallet daemon");
// process.kill().unwrap_or_default();
// }
// }
// }
impl Drop for MoneroState {
fn drop(&mut self) {
if let Some(process) = self.wallet_process.as_mut() {
if let Some(process) = Arc::get_mut(process) {
let mut process = process.lock().unwrap();

debug!("Stopping RPC wallet daemon");
process.kill().unwrap_or_default();
}
}
}
}

impl MoneroState {
pub async fn new(args: &ServeArgs) -> anyhow::Result<Self> {
Expand Down Expand Up @@ -102,18 +106,20 @@ impl MoneroState {
);

Ok(Self {
wallet_process: Some(Arc::new(wallet_process)),
wallet_process: Some(Arc::new(Mutex::new(wallet_process))),
wallet_address: wallet.get_address(0, None).await?.address,
wallet,
account_index: 0,
minimum_block_height: args.monero_block_height,
})
}

pub async fn get_balance(&self) -> Result<u64> {
/// Query the current wallet balance.
// #[once(time = "60")]
pub async fn get_balance(&self) -> Result<Amount> {
let balance = self.wallet.get_balance(self.account_index, None).await?;
debug!(balance = ?balance, "Current Monero wallet balance");
Ok(balance.unlocked_balance.as_pico())
Ok(balance.unlocked_balance)
}

/// Count outbound transfers to the given address.
Expand Down Expand Up @@ -161,7 +167,7 @@ impl MoneroState {
}

/// Transfer the given amount of Monero.
pub async fn transfer(&self, address: &str, amount: Amount, commit_id: &Oid) -> Result<()> {
pub async fn transfer(&self, address: &str, amount: Amount, _commit_id: &Oid) -> Result<()> {
info!(amount = ?amount, dest = ?address, "Transferring Monero");
self.wallet
.transfer(
Expand All @@ -181,3 +187,16 @@ impl MoneroState {
Ok(())
}
}

/// Return an SVG badge with the current monero balance.
pub async fn balance(State(state): State<AppState>) -> impl IntoResponse {
let monero_balance = state.monero.get_balance().await.unwrap().as_xmr();

(
[(header::CONTENT_TYPE, "image/svg+xml")],
crate::badge::generate(
"balance",
&format!("{} XMR", PrettyPrintFloat(monero_balance)),
),
)
}
3 changes: 2 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use crate::cli::Commands;
use anyhow::Result;
use axum::{response::Html, routing::get, Router};

use clap::Parser;
use std::process::ExitCode;

mod api;
mod badge;
mod cli;
mod config;
mod currency;
Expand Down
9 changes: 5 additions & 4 deletions src/repo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ use crate::currency::Address;
use anyhow::{bail, Result};
use base64::prelude::*;
use chrono::{DateTime, Utc};
use git2::{Commit, Oid, Repository, Revwalk, Sort};
use std::process::{Command, Stdio};
use git2::{Commit, Oid, Repository, Sort};
use std::process::{Command};
use tempfile::TempDir;
use tracing::{debug, info, instrument, trace};
use tracing::{debug, info, instrument};

#[derive(Debug)]
pub struct Contributor {
Expand Down Expand Up @@ -120,8 +120,9 @@ impl TurbineRepo {
.unwrap())
}

#[cfg(feature = "monero")]
pub fn monero_transfer(&self, transfer: &monero_rpc::GotTransfer) -> Result<Transaction> {
if let Ok(contributor) = self.find_contributor(transfer.payment_id.to_string()) {
if let Ok(_contributor) = self.find_contributor(transfer.payment_id.to_string()) {
Ok(Transaction {
amount: format!("{}", transfer.amount.as_xmr()),
timestamp: transfer.timestamp.timestamp() as u64,
Expand Down
2 changes: 1 addition & 1 deletion templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@
<div class="summary-block">
<img class="svg-monero" src="/assets/monero.svg">
{% if monero_balance != "0.0" %}
<p class="balance-positive">{{ monero_balance }} XMR ~ (${{ usd_balance }})</p>
<p class="balance-positive">{{ monero_balance }} XMR ~ (${{ monero_balance_usd }})</p>
{% else %}
<p class="balance-zero">{{ monero_balance }} XMR</p>
{% endif %}
Expand Down

0 comments on commit 7186ace

Please sign in to comment.