From 92e57bb66eb28ce096c1f08fe9fcf69cf49965c7 Mon Sep 17 00:00:00 2001 From: max funk Date: Fri, 22 Mar 2024 21:04:10 -0700 Subject: [PATCH] add transactions-by-account service in rust --- Cargo.toml | 1 + docker/dev/transactions-by-account.Dockerfile | 40 +++++++-- project.yaml | 4 +- services/transactions-by-account/Cargo.toml | 18 ++++ services/transactions-by-account/makefile | 21 +++-- services/transactions-by-account/src/main.rs | 88 +++++++++++++++++++ 6 files changed, 154 insertions(+), 18 deletions(-) create mode 100644 services/transactions-by-account/Cargo.toml create mode 100644 services/transactions-by-account/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 60355208..66e247d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ members = [ "services/requests-by-account", "services/rule", "services/transaction-by-id", + "services/transactions-by-account", "tests", ] diff --git a/docker/dev/transactions-by-account.Dockerfile b/docker/dev/transactions-by-account.Dockerfile index cc51c267..8f55b8de 100644 --- a/docker/dev/transactions-by-account.Dockerfile +++ b/docker/dev/transactions-by-account.Dockerfile @@ -1,17 +1,39 @@ -FROM mxfactorial/go-base:v1 as builder +FROM rust:latest as builder -COPY . . +WORKDIR /app -WORKDIR /app/services/transactions-by-account +COPY . ./ -RUN go build -o transactions-by-account ./cmd +RUN rustup target add x86_64-unknown-linux-musl +RUN apt update && \ + apt install -y musl-tools perl make +RUN update-ca-certificates -FROM golang:alpine +ENV USER=transactions-by-account +ENV UID=10007 -WORKDIR /app +RUN adduser \ + --disabled-password \ + --gecos "" \ + --home "/nonexistent" \ + --shell "/sbin/nologin" \ + --no-create-home \ + --uid "${UID}" \ + "${USER}" + +RUN USER=root cargo build \ + --manifest-path=services/transactions-by-account/Cargo.toml \ + --target x86_64-unknown-linux-musl \ + --release + +FROM alpine + +COPY --from=builder /etc/passwd /etc/passwd +COPY --from=builder /etc/group /etc/group +COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/transactions-by-account /usr/local/bin -COPY --from=builder /app/services/transactions-by-account/transactions-by-account . +EXPOSE 10007 -EXPOSE 10008 +USER transactions-by-account:transactions-by-account -CMD ["/app/transactions-by-account"] \ No newline at end of file +CMD [ "/usr/local/bin/transactions-by-account" ] \ No newline at end of file diff --git a/project.yaml b/project.yaml index 88bdac50..e28d3412 100644 --- a/project.yaml +++ b/project.yaml @@ -514,13 +514,13 @@ services: - PGDATABASE - READINESS_CHECK_PATH transactions-by-account: - runtime: go1.x + runtime: rust1.x min_code_cov: null type: app local_dev: true params: [] deploy: true - build_src_path: cmd + build_src_path: null dependents: [] env_var: set: diff --git a/services/transactions-by-account/Cargo.toml b/services/transactions-by-account/Cargo.toml new file mode 100644 index 00000000..39282ac8 --- /dev/null +++ b/services/transactions-by-account/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "transactions-by-account" +version = "0.1.0" +edition = "2021" +rust-version.workspace = true + +[dependencies] +axum = "0.7.4" +tracing = "0.1.40" +tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } +tokio = { version = "1.35.1", features = ["macros", "rt-multi-thread"] } +shutdown = { path = "../../crates/shutdown" } +pg = { path = "../../crates/pg" } +service = { path = "../../crates/service" } +types = { path = "../../crates/types" } + +[target.x86_64-unknown-linux-musl.dependencies] +openssl = { version = "0.10", features = ["vendored"] } diff --git a/services/transactions-by-account/makefile b/services/transactions-by-account/makefile index 9dd710d5..f11ffee2 100644 --- a/services/transactions-by-account/makefile +++ b/services/transactions-by-account/makefile @@ -1,6 +1,6 @@ RELATIVE_PROJECT_ROOT_PATH=$(shell REL_PATH="."; while [ $$(ls "$$REL_PATH" | grep project.yaml | wc -l | xargs) -eq 0 ]; do REL_PATH="$$REL_PATH./.."; done; printf '%s' "$$REL_PATH") include $(RELATIVE_PROJECT_ROOT_PATH)/make/shared.mk -include $(RELATIVE_PROJECT_ROOT_PATH)/make/go.mk +include $(RELATIVE_PROJECT_ROOT_PATH)/make/rust.mk TRANSACTIONS_BY_ACCOUNT_PORT=$(shell yq '.services["$(APP_NAME)"].env_var.set.TRANSACTIONS_BY_ACCOUNT_PORT.default' $(PROJECT_CONF)) TRANSACTIONS_BY_ACCOUNT_URL=$(HOST):$(TRANSACTIONS_BY_ACCOUNT_PORT) @@ -11,14 +11,21 @@ TEST_EVENT='{"auth_account":"$(TEST_AUTH_ACCOUNT)","account_name":"$(TEST_ACCOUN TEST_SENDER_ACCOUNT=$(TEST_ACCOUNT) RETURN_RECORD_LIMIT=2 -run: - @$(DOCKER_ENV_VARS) \ - RETURN_RECORD_LIMIT=$(RETURN_RECORD_LIMIT) \ - TEST_EVENT=$(TEST_EVENT) \ - go run ./cmd/main.go +start: + @$(MAKE) get-secrets ENV=local + nohup cargo watch --env-file $(ENV_FILE) -w src -w $(RELATIVE_PROJECT_ROOT_PATH)/crates -x run >> $(NOHUP_LOG) & + +start-alone: + rm -f $(NOHUP_LOG) + $(MAKE) -C $(MIGRATIONS_DIR) run + $(MAKE) start + tail -F $(NOHUP_LOG) + +stop: + $(MAKE) -C $(RELATIVE_PROJECT_ROOT_PATH) stop invoke-local: - @curl -s -d $(TEST_EVENT) $(TRANSACTIONS_BY_ACCOUNT_URL) | yq -o=json + @curl -s -H 'Content-Type: application/json' -d $(TEST_EVENT) $(TRANSACTIONS_BY_ACCOUNT_URL) | yq -o=json demo: @printf "*** request to %s at %s\n" $(SUB_PATH) $(TRANSACTIONS_BY_ACCOUNT_URL) diff --git a/services/transactions-by-account/src/main.rs b/services/transactions-by-account/src/main.rs new file mode 100644 index 00000000..9e01c29b --- /dev/null +++ b/services/transactions-by-account/src/main.rs @@ -0,0 +1,88 @@ +use axum::{ + extract::{Json, State}, + http::StatusCode, + routing::{get, post}, + Router, +}; +use pg::postgres::{ConnectionPool, DB}; +use service::Service; +use shutdown::shutdown_signal; +use std::{env, net::ToSocketAddrs}; +use tokio::net::TcpListener; +use types::request_response::{IntraTransactions, QueryByAccount}; + +// used by lambda to test for service availability +const READINESS_CHECK_PATH: &str = "READINESS_CHECK_PATH"; + +async fn handle_event( + State(pool): State, + event: Json, +) -> Result, StatusCode> { + let client_request = event.0; + + let svc = Service::new(pool.get_conn().await); + + let account = client_request.account_name; + + let record_limit = env::var("RETURN_RECORD_LIMIT") + .unwrap_or_else(|_| panic!("RETURN_RECORD_LIMIT variable assignment")) + .parse::() + .unwrap(); + + let transactions = svc + .get_last_n_transactions(account.clone(), record_limit) + .await + .map_err(|e| { + tracing::error!("error: {}", e); + StatusCode::INTERNAL_SERVER_ERROR + })?; + + if transactions.is_empty() { + tracing::error!("transaction requests not found"); + } + + let response = IntraTransactions::new(account, transactions); + + Ok(axum::Json(response)) +} + +#[tokio::main] +async fn main() { + tracing_subscriber::fmt::init(); + + let readiness_check_path = env::var(READINESS_CHECK_PATH) + .unwrap_or_else(|_| panic!("{READINESS_CHECK_PATH} variable assignment")); + + let conn_uri = DB::create_conn_uri_from_env_vars(); + + let pool = DB::new_pool(&conn_uri).await; + + let app = Router::new() + .route("/", post(handle_event)) + .route( + readiness_check_path.as_str(), + get(|| async { StatusCode::OK }), + ) + .with_state(pool); + + let hostname_or_ip = env::var("HOSTNAME_OR_IP").unwrap_or("0.0.0.0".to_string()); + + let port = env::var("TRANSACTIONS_BY_ACCOUNT_PORT").unwrap_or("10006".to_string()); + + let serve_addr = format!("{hostname_or_ip}:{port}"); + + let mut addrs_iter = serve_addr.to_socket_addrs().unwrap_or( + format!("{hostname_or_ip}:{port}") + .to_socket_addrs() + .unwrap(), + ); + + let addr = addrs_iter.next().unwrap(); + + tracing::info!("listening on {}", addr); + + axum::serve(TcpListener::bind(addr).await.unwrap(), app) + .with_graceful_shutdown(shutdown_signal()) + .await + .unwrap(); +}