diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..b8eed2e --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,81 @@ +name: check +permissions: + contents: read +# This configuration allows maintainers of this repo to create a branch and pull request based on +# the new branch. Restricting the push trigger to the main branch ensures that the PR only gets +# built once. +on: + push: + branches: [master] + pull_request: +# If new code is pushed to a PR branch, then cancel in progress workflows for that PR. Ensures that +# we don't waste CI time, and returns results quicker https://github.com/jonhoo/rust-ci-conf/pull/5 +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true +jobs: + fmt: + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, 'chore: release')" + name: stable / fmt + steps: + - uses: actions/checkout@v4 + - name: Install stable + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: cargo fmt --check + run: cargo fmt --check + clippy: + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, 'chore: release')" + name: ${{ matrix.toolchain }} / clippy + permissions: + contents: read + checks: write + strategy: + fail-fast: false + matrix: + # Get early warning of new lints which are regularly introduced in beta channels. + toolchain: [stable, beta] + steps: + - uses: actions/checkout@v4 + - name: Install ${{ matrix.toolchain }} + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.toolchain }} + components: clippy + - name: cargo clippy + uses: giraffate/clippy-action@v1 + with: + reporter: 'github-pr-check' + github_token: ${{ secrets.GITHUB_TOKEN }} + doc: + runs-on: ubuntu-24.04 + if: "!contains(github.event.head_commit.message, 'chore: release')" + name: nightly / doc + steps: + - run: sudo apt-get install -y libpango1.0-dev libgraphene-1.0-dev + - uses: actions/checkout@v4 + - name: Install nightly + uses: dtolnay/rust-toolchain@nightly + - name: cargo doc + run: cargo doc --no-deps --all-features + env: + RUSTDOCFLAGS: --cfg docsrs + msrv: + runs-on: ubuntu-24.04 + if: "!contains(github.event.head_commit.message, 'chore: release')" + strategy: + matrix: + msrv: ["1.74.0"] + name: ubuntu / ${{ matrix.msrv }} + steps: + - run: sudo apt-get install -y libpango1.0-dev libgraphene-1.0-dev + - uses: actions/checkout@v4 + - name: Install ${{ matrix.msrv }} + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.msrv }} + - name: cargo +${{ matrix.msrv }} check + run: cargo check diff --git a/.github/workflows/prepare_release.yml b/.github/workflows/prepare_release.yml new file mode 100644 index 0000000..3412323 --- /dev/null +++ b/.github/workflows/prepare_release.yml @@ -0,0 +1,26 @@ +name: prepare_release +permissions: + pull-requests: write + contents: write +on: + push: + branches: + - master + +jobs: + github: + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, 'chore: release')" + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: dtolnay/rust-toolchain@stable + + - uses: MarcoIeni/release-plz-action@v0.5 + with: + command: release-pr + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..f84c923 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,113 @@ +name: release +on: + push: + branches: + - master + +jobs: + github: + runs-on: ubuntu-24.04 + if: "contains(github.event.head_commit.message, 'chore: release')" + steps: + - run: sudo apt-get install -y libpango1.0-dev libgraphene-1.0-dev + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: dtolnay/rust-toolchain@stable + + - uses: MarcoIeni/release-plz-action@v0.5 + with: + command: release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + + linux: + name: linux / ${{ matrix.target }} + runs-on: ubuntu-latest + needs: github + strategy: + fail-fast: false + matrix: + target: + - aarch64-unknown-linux-gnu + - aarch64-unknown-linux-musl + - i686-unknown-linux-gnu + - x86_64-unknown-linux-gnu + - x86_64-unknown-linux-musl + steps: + - uses: actions/checkout@v4 + with: + fetch-tags: true + + - uses: actions-rs/cargo@v1 + with: + command: build + args: --release --target ${{ matrix.target }} + + - uses: actions/upload-artifact@v4 + with: + name: goldboot-${{ matrix.target }} + path: target/${{ matrix.target }}/release/turbine + + - name: Upload artifacts to release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + run: | + for tag in $(git tag --points-at HEAD); do + if cp target/${{ matrix.target }}/release/${tag%-*} ${tag%-*}_${{ matrix.target }}; then + gh release upload "${tag}" "${tag%-*}_${{ matrix.target }}" + fi + done + + docker: + runs-on: ubuntu-latest + needs: linux + steps: + - uses: actions/checkout@v4 + with: + fetch-tags: true + + - uses: docker/setup-qemu-action@v3 + + - uses: docker/setup-buildx-action@v3 + + - uses: actions/download-artifact@v4 + with: + name: turbine-x86_64-unknown-linux-musl + path: turbine/linux-amd64 + + - uses: actions/download-artifact@v4 + with: + name: turbine-aarch64-unknown-linux-musl + path: turbine/linux-arm64 + + - uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Find version tags + id: get_tags + run: | + for tag in $(git tag --points-at HEAD); do + echo "${tag%-*}=${tag##*-}" >>"$GITHUB_OUTPUT" + done + + - uses: docker/build-push-action@v5 + if: ${{ steps.get_tags.outputs.goldboot != '' }} + with: + context: . + platforms: linux/amd64,linux/arm64 #,linux/arm/v7 + push: true + tags: fossable/turbine:latest,fossable/turbine:${{ steps.get_tags.outputs.turbine }} + + - uses: peter-evans/dockerhub-description@v4 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + repository: fossable/turbine + diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..7745730 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,37 @@ +name: test +permissions: + contents: read +on: + push: + branches: [master] + pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true +jobs: + required: + runs-on: ubuntu-24.04 + if: "!contains(github.event.head_commit.message, 'chore: release')" + name: ubuntu / ${{ matrix.toolchain }} + strategy: + matrix: + # run on stable and beta to ensure that tests won't break on the next version of the rust + # toolchain + toolchain: [stable, beta] + steps: + - run: sudo apt-get install -y libpango1.0-dev libgraphene-1.0-dev + - uses: actions/checkout@v4 + - name: Install ${{ matrix.toolchain }} + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.toolchain }} + - name: cargo generate-lockfile + # enable this ci template to run regardless of whether the lockfile is checked in or not + if: hashFiles('Cargo.lock') == '' + run: cargo generate-lockfile + # https://twitter.com/jonhoo/status/1571290371124260865 + - name: cargo test --locked + run: cargo test --locked --all-features + # https://github.com/rust-lang/cargo/issues/6669 + - name: cargo test --doc + run: cargo test --locked --all-features --doc diff --git a/Cargo.lock b/Cargo.lock index 6b460e6..a276924 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,6 +28,18 @@ dependencies = [ "version_check", ] +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -37,6 +49,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + [[package]] name = "anstream" version = "0.6.14" @@ -92,6 +110,61 @@ version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +[[package]] +name = "askama" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28" +dependencies = [ + "askama_derive", + "askama_escape", + "humansize", + "num-traits", + "percent-encoding", +] + +[[package]] +name = "askama_axum" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a41603f7cdbf5ac4af60760f17253eb6adf6ec5b6f14a7ed830cf687d375f163" +dependencies = [ + "askama", + "axum-core", + "http 1.1.0", +] + +[[package]] +name = "askama_derive" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83" +dependencies = [ + "askama_parser", + "basic-toml", + "mime", + "mime_guess", + "proc-macro2", + "quote", + "serde", + "syn", +] + +[[package]] +name = "askama_escape" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" + +[[package]] +name = "askama_parser" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0" +dependencies = [ + "nom", +] + [[package]] name = "async-trait" version = "0.1.74" @@ -212,6 +285,15 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "basic-toml" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -251,6 +333,42 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +[[package]] +name = "cached" +version = "0.51.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd93a9f06ec296ca66b4c26fafa9ed63f32c473d7a708a5f28563ee64c948515" +dependencies = [ + "ahash 0.8.11", + "async-trait", + "cached_proc_macro", + "cached_proc_macro_types", + "futures", + "hashbrown 0.14.5", + "instant", + "once_cell", + "thiserror", + "tokio", +] + +[[package]] +name = "cached_proc_macro" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771aa57f3b17da6c8bcacb187bb9ec9bc81c8160e72342e67c329e0e1651a669" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cached_proc_macro_types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" + [[package]] name = "cc" version = "1.0.83" @@ -284,6 +402,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] @@ -298,6 +417,18 @@ dependencies = [ "strsim", ] +[[package]] +name = "clap_derive" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "clap_lex" version = "0.7.0" @@ -398,6 +529,41 @@ dependencies = [ "syn", ] +[[package]] +name = "darling" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "digest" version = "0.10.7" @@ -699,7 +865,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash", + "ahash 0.7.7", ] [[package]] @@ -707,6 +873,10 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash 0.8.11", + "allocator-api2", +] [[package]] name = "heck" @@ -714,6 +884,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.3" @@ -800,6 +976,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + [[package]] name = "hyper" version = "0.14.27" @@ -894,6 +1079,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.4.0" @@ -924,6 +1115,15 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + [[package]] name = "ipnet" version = "2.9.0" @@ -1012,6 +1212,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "libssh2-sys" version = "0.3.0" @@ -1103,6 +1309,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1732,7 +1948,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4a8caec23b7800fb97971a1c6ae365b6239aaeddfb934d6265f8505e795699d" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "syn", @@ -2198,7 +2414,10 @@ name = "turbine" version = "0.1.0" dependencies = [ "anyhow", + "askama", + "askama_axum", "axum", + "cached", "clap", "config", "git2", @@ -2223,6 +2442,15 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.13" @@ -2569,6 +2797,26 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "zerocopy" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zeroize" version = "1.6.0" diff --git a/Cargo.toml b/Cargo.toml index 1cc3ebc..71b1cbd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ axum = "0.7.5" config = "0.13.3" git2 = "0.18.1" anyhow = "1.0.86" -clap = { version = "4.5.4", features = ["string"] } +clap = { version = "4.5.4", features = ["string", "derive"] } monero = "0.21.0" monero-rpc = { version = "0.4.0", features = ["rpc_authentication"], optional = true } reqwest = { version = "0.12.4", features = ["json"] } @@ -16,6 +16,9 @@ serde = { version = "1.0.203", features = ["derive"] } tokio = { version = "1.37.0", features = ["full"] } tracing = "0.1.40" tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } +askama = { version = "0.12.1", features = ["with-axum"] } +askama_axum = "0.4.0" +cached = { version = "0.51.3", features = ["async"] } [features] -monero = ["dep:monero-rpc"] \ No newline at end of file +monero = ["dep:monero-rpc"] diff --git a/src/api/mod.rs b/src/api/mod.rs index e69de29..c1749e1 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -0,0 +1,25 @@ +use crate::cli::AppState; +use askama_axum::Template; +use axum::extract::State; +use cached::proc_macro::once; +use tracing::instrument; + +#[derive(Template, Debug)] +#[template(path = "index.html")] +pub struct IndexTemplate { + #[cfg(feature = "monero")] + monero_balance: String, + #[cfg(feature = "monero")] + monero_wallet_address: String, +} + +#[once(time = "60")] +#[instrument(ret)] +pub async fn index(State(state): State) -> IndexTemplate { + IndexTemplate { + #[cfg(feature = "monero")] + monero_balance: state.monero_balance(), + #[cfg(feature = "monero")] + monero_wallet_address: state.monero_wallet_address.clone(), + } +} diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..c73ddbd --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,47 @@ +use anyhow::Result; +use axum::{ + extract::FromRef, + routing::{get, post}, + Router, +}; +use clap::{Args, Parser}; +use std::{env, process::ExitCode, sync::Arc}; +use tokio::net::TcpListener; +use tokio::spawn; +use tracing::info; + +#[derive(clap::Subcommand, Debug, Clone)] +pub enum Commands { + Serve(ServeArgs), +} + +#[derive(Debug, Clone, Args)] +pub struct ServeArgs { + bind: Option, + // #[cfg(feature = "monero")] + // monero_wallet_url: String, +} + +#[derive(Clone, Debug)] +pub struct AppState { + #[cfg(feature = "monero")] + monero: crate::currency::monero::MoneroState, +} + +pub async fn serve(args: &ServeArgs) -> Result { + let state = AppState { + #[cfg(feature = "monero")] + monero: crate::currency::monero::MoneroState::new(&args).await?, + }; + + let app = Router::new().route("/", get(crate::api::index)); + + #[cfg(feature = "monero")] + let app = app.route("/xmr/provision", post(crate::currency::monero::provision)); + + info!("Starting listener"); + let listener = + TcpListener::bind(args.bind.as_ref().unwrap_or(&"0.0.0.0:3000".to_string())).await?; + axum::serve(listener, app.with_state(state)).await?; + Ok(ExitCode::SUCCESS) +} diff --git a/src/config.rs b/src/config.rs index c1bd3ab..6fb8d9b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,9 +1,8 @@ +use anyhow::Result; use serde::Deserialize; -use std::error::Error; #[derive(Debug, Default, Deserialize, PartialEq, Eq)] pub struct AppConfig { - /// HTTP URL of the repository we're managing repo_url: String, @@ -15,18 +14,23 @@ pub struct AppConfig { } impl AppConfig { - pub fn load() -> Result> { - config::Config::builder().add_source(config::Environment::with_prefix("TURBINE").try_parsing(true).separator("_")).build()?.try_deserialize()? + pub fn load() -> Result { + Ok(config::Config::builder() + .add_source( + config::Environment::with_prefix("TURBINE") + .try_parsing(true) + .separator("_"), + ) + .build()? + .try_deserialize()?) } } #[derive(Debug, Default, Deserialize, PartialEq, Eq)] -pub struct PayoutConfig { -} +pub struct PayoutConfig {} #[derive(Debug, Default, Deserialize, PartialEq, Eq)] -pub struct WalletConfig { -} +pub struct WalletConfig {} #[derive(Debug, Default, Deserialize, PartialEq, Eq)] pub struct GithubConfig { diff --git a/src/currency.rs b/src/currency/mod.rs similarity index 76% rename from src/currency.rs rename to src/currency/mod.rs index 0f7ce58..cddcba9 100644 --- a/src/currency.rs +++ b/src/currency/mod.rs @@ -1,6 +1,9 @@ +#[cfg(feature = "monero")] +pub mod monero; pub enum Address { BTC(String), + #[cfg(feature = "monero")] XMR(String), } @@ -9,6 +12,7 @@ impl Address { // TODO validate address match currency.to_lowercase().as_str() { "btc" => Some(Self::BTC(address.into())), + #[cfg(feature = "monero")] "xmr" => Some(Self::XMR(address.into())), _ => None, } diff --git a/src/currency/monero.rs b/src/currency/monero.rs new file mode 100644 index 0000000..c64bfb3 --- /dev/null +++ b/src/currency/monero.rs @@ -0,0 +1,49 @@ +use anyhow::Result; +use axum::{ + extract::{FromRef, State}, + http::StatusCode, + Json, +}; +use cached::proc_macro::once; +use monero_rpc::{RpcClientBuilder, WalletClient}; +use redb::TableDefinition; +use serde::{Deserialize, Serialize}; +use tracing::{debug, info, instrument}; + +use crate::{cli::serve::ServeArgs, AppState, CommandLine}; + +#[derive(Clone, Debug)] +pub struct MoneroState { + wallet: WalletClient, + wallet_address: String, +} + +impl MoneroState { + pub async fn new(args: &ServeArgs) -> anyhow::Result { + debug!("Connecting to wallet RPC"); + let wallet = RpcClientBuilder::new() + .rpc_authentication(monero_rpc::RpcAuthentication::Credentials { + username: "monero".to_string(), + password: "".to_string(), + }) + .build("http://127.0.0.1:1234")? + .wallet(); + + info!( + block_height = wallet.get_height().await?, + "Connected to wallet RPC" + ); + + let address = wallet.get_address(0, None).await?; + + Ok(Self { + wallet, + wallet_address: address.address.public_spend.to_string(), + }) + } + + pub async fn get_balance(&self) -> Result { + let balance = self.wallet.get_balance(0, None).await?; + Ok(balance.unlocked_balance.as_pico()) + } +} diff --git a/src/main.rs b/src/main.rs index 2de2513..1dcbe4c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,8 @@ use crate::cli::Commands; +use anyhow::Result; use axum::{response::Html, routing::get, Router}; use clap::Parser; +use std::process::ExitCode; mod api; mod cli; @@ -8,17 +10,6 @@ mod config; mod currency; mod repo; -// async fn main() { -// // build our application with a route -// let app = Router::new().route("/", get(todo!())); - -// // run it -// let listener = tokio::net::TcpListener::bind("127.0.0.1:3000") -// .await -// .unwrap(); -// axum::serve(listener, app).await.unwrap(); -// } - #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] struct CommandLine { @@ -27,18 +18,15 @@ struct CommandLine { } #[tokio::main] -async fn main() { - let command_line = CommandLine::parse(); - - // Configure logging +async fn main() -> Result { + let args = CommandLine::parse(); tracing_subscriber::fmt() .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) .init(); // Dispatch command - match &command_line.command { - Some(Commands::Provision {}) => todo!(), - Some(Commands::Deprovision {}) => todo!(), + match &args.command { + Some(Commands::Serve(args)) => crate::cli::serve(args).await, None => todo!(), } } diff --git a/src/repo.rs b/src/repo.rs index 4274203..bfaed98 100644 --- a/src/repo.rs +++ b/src/repo.rs @@ -1,8 +1,9 @@ -use git2::{Repository, Oid, Commit}; use crate::currency::Address; -use std::error::Error; +use git2::{Commit, Oid, Repository}; +use std::{error::Error, path::Path}; +use tracing::debug; -/// +/// pub struct TurbineRepo { /// Underlying git repository container: Repository, @@ -12,10 +13,12 @@ pub struct TurbineRepo { } impl TurbineRepo { - - pub fn new() -> Result> { + pub fn new

(path: P) -> Result> + where + P: AsRef, + { Ok(Self { - container: Repository::open(todo!())?, + container: Repository::open(path)?, last: None, }) } @@ -37,7 +40,7 @@ impl TurbineRepo { revwalk.next(); break; } - }, + } None => { // Ran out of commits before encountering "last" which // means history has been changed @@ -61,7 +64,7 @@ impl TurbineRepo { Ok(paid_commit) => { commits.push(paid_commit); } - Err(_) => {}, + Err(_) => {} } } None => { @@ -74,13 +77,11 @@ impl TurbineRepo { } pub struct PaidCommit { - address: Address, - commit: Box, + hash: String, } impl PaidCommit { - pub fn try_parse(commit: Commit) -> Result> { match commit.message() { Some(message) => { @@ -91,12 +92,13 @@ impl PaidCommit { Some(address) => { return Ok(Self { address, - commit: Box::new(commit), + // commit: Box::new(commit), + hash: "".into(), }); } None => (), } - }, + } None => (), } } diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..13ff03e --- /dev/null +++ b/templates/index.html @@ -0,0 +1,4 @@ + +a + + \ No newline at end of file