Skip to content

Commit 3c19893

Browse files
committed
feat: initial sqs audit logging
1 parent 14b7dab commit 3c19893

File tree

28 files changed

+5528
-105
lines changed

28 files changed

+5528
-105
lines changed

Cargo.lock

Lines changed: 4997 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
[workspace]
2+
members = ["common", "api", "consumer", "gateway"]
3+
resolver = "2"
4+
5+
[workspace.package]
6+
version = "2.0.0"
7+
edition = "2024"
8+
9+
[workspace.dependencies]
10+
lambda_http = { version = "0.17.0" }
11+
tokio = { version = "1", features = ["macros"] }
12+
tower-http = { version = "0.6", features = ["cors", "auth"] }
13+
axum = "0.8.4"
14+
http = "1.3.1"
15+
serde = { version = "1.0.223", features = ["derive"] }
16+
serde_json = "1.0.145"
17+
tower = "0.5.2"
18+
twilight = "0.16.0"
19+
twilight-http = "0.16.0"
20+
twilight-model = "0.16.0"
21+
twilight-gateway = "0.16.0"
22+
aws-sdk-sesv2 = "1.98.0"
23+
aws-sdk-scheduler = "1.84.0"
24+
aws-sdk-sqs = "1.92.0"
25+
aws-config = "1.8.6"
26+
reqwest = { version = "0.12.23", features = ["json", "native-tls-vendored"] }
27+
chrono = { version = "0.4.42", features = ["serde"] }
28+
ed25519-dalek = "2.2.0"
29+
once_cell = "1.21.3"
30+
hex = "0.4.3"
31+
http-body-util = "0.1.3"
32+
regex = "1.11.2"
33+
cached = { version = "0.56.0", features = ["async"] }
34+
hmac = "0.12.1"
35+
sha2 = "0.10.9"
36+
jwt = "0.16.0"
37+
sqlx = { version = "0.8", features = [ "runtime-tokio", "tls-rustls" , "postgres", "uuid", "bigdecimal"] }
38+
aws-sdk-dsql = "1.1"
39+
anyhow = { version = "1", features = ["backtrace"] }
40+
bigdecimal = "0.4" # Use the latest version
41+
tracing = "0.1.41"
42+
tracing-subscriber = "0.3.20"
43+
aws_lambda_events = "1.0.3"
44+
lambda_runtime = "1.0.2"
45+
uuid = "1.20.0"

api/Cargo.toml

Lines changed: 29 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,33 @@
11
[package]
22
name = "api"
3-
version = "2.0.1"
4-
edition = "2024"
3+
version.workspace = true
4+
edition.workspace = true
55

66
[dependencies]
7-
lambda_http = { version = "0.17.0" }
8-
tokio = { version = "1", features = ["macros"] }
9-
tower-http = { version = "0.6", features = ["cors", "auth"] }
10-
axum = "0.8.4"
11-
http = "1.3.1"
12-
serde = { version = "1.0.223", features = ["derive"] }
13-
serde_json = "1.0.145"
14-
tower = "0.5.2"
15-
twilight = "0.16.0"
16-
twilight-http = "0.16.0"
17-
twilight-model = "0.16.0"
18-
aws-sdk-sesv2 = "1.98.0"
19-
aws-sdk-scheduler = "1.84.0"
20-
aws-config = "1.8.6"
21-
reqwest = { version = "0.12.23", features = ["json", "native-tls-vendored"] }
22-
chrono = { version = "0.4.42", features = ["serde"] }
23-
ed25519-dalek = "2.2.0"
24-
once_cell = "1.21.3"
25-
hex = "0.4.3"
26-
http-body-util = "0.1.3"
27-
regex = "1.11.2"
28-
cached = { version = "0.56.0", features = ["async"] }
29-
hmac = "0.12.1"
30-
sha2 = "0.10.9"
31-
jwt = "0.16.0"
32-
sqlx = { version = "0.8", features = [ "runtime-tokio", "tls-rustls" , "postgres", "uuid", "bigdecimal"] }
33-
aws-sdk-dsql = "1.1"
34-
anyhow = { version = "1", features = ["backtrace"] }
35-
bigdecimal = "0.4" # Use the latest version
7+
lambda_http = { workspace = true }
8+
tokio = { workspace = true, features = ["macros"] }
9+
tower-http = { workspace = true, features = ["cors", "auth"] }
10+
axum = { workspace = true }
11+
http = { workspace = true }
12+
serde = { workspace = true, features = ["derive"] }
13+
serde_json = { workspace = true }
14+
twilight-http = { workspace = true }
15+
twilight-model = { workspace = true }
16+
aws-sdk-sesv2 = { workspace = true }
17+
aws-sdk-scheduler = { workspace = true }
18+
aws-config = { workspace = true }
19+
reqwest = { workspace = true, features = ["json", "native-tls-vendored"] }
20+
chrono = { workspace = true, features = ["serde"] }
21+
ed25519-dalek = { workspace = true }
22+
once_cell = { workspace = true }
23+
hex = { workspace = true }
24+
http-body-util = { workspace = true }
25+
regex = { workspace = true }
26+
cached = { workspace = true, features = ["async"] }
27+
hmac = { workspace = true }
28+
sha2 = { workspace = true }
29+
jwt = { workspace = true }
30+
sqlx = { workspace = true, features = [ "runtime-tokio", "tls-rustls" , "postgres", "uuid", "bigdecimal"] }
31+
bigdecimal = { workspace = true }
32+
common = { path = "../common", version = "2.0.0"}
33+
aws-sdk-sqs = { workspace = true }

api/migrations/20260119010332_init.up.sql

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,7 @@ CREATE TABLE IF NOT EXISTS users(
1111

1212
-- Future Use Case
1313
--
14-
-- CREATE TABLE users(
15-
-- id NUMERIC(20, 0) NOT NULL PRIMARY KEY, -- u64
16-
-- created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
17-
-- updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
18-
-- );
19-
--
20-
-- INSERT INTO users(id) VALUES (228541431483072513); -- Insert JayDwee
2114
--
22-
-- CREATE TABLE guilds(
23-
-- id NUMERIC(20, 0) NOT NULL PRIMARY KEY, -- u64
24-
-- created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
25-
-- updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
26-
-- );
27-
--
28-
-- CREATE TABLE audit(
29-
-- id UUID NOT NULL PRIMARY KEY DEFAULT gen_random_uuid(),
30-
-- user_id NUMERIC(20, 0) NOT NULL, -- u64
31-
-- guild_id NUMERIC(20, 0), -- u64
32-
-- action VARCHAR,
33-
-- data VARCHAR,
34-
-- created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
35-
-- updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
36-
-- );
3715
--
3816
--
3917
-- CREATE TABLE feature_flags(
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DROP TABLE audit;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CREATE TABLE audit(
2+
id UUID NOT NULL PRIMARY KEY DEFAULT gen_random_uuid(),
3+
action VARCHAR,
4+
user_id NUMERIC(20, 0) NOT NULL, -- u64
5+
guild_id NUMERIC(20, 0), -- u64
6+
old_data VARCHAR,
7+
new_data VARCHAR,
8+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
9+
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
10+
);

api/src/audit.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use aws_sdk_sqs::types::MessageAttributeValue;
2+
use lambda_http::tracing::error;
3+
use serde::{Deserialize, Serialize};
4+
pub use common::audit::AuditMessage;
5+
6+
pub async fn audit<T>(audit: &AuditMessage<T>, sqs: &aws_sdk_sqs::Client)
7+
where
8+
T: Serialize + for<'de> Deserialize<'de>,
9+
{
10+
let queue_url = std::env::var("SQS_URL").expect("SQS_URL must be set");
11+
12+
let mut attempts = 0;
13+
14+
loop {
15+
attempts += 1;
16+
17+
match sqs
18+
.send_message()
19+
.queue_url(&queue_url)
20+
.message_attributes("kind", MessageAttributeValue::builder().string_value("audit").build().unwrap())
21+
.message_body(serde_json::to_string(audit).unwrap())
22+
.send()
23+
.await {
24+
Ok(_) => break,
25+
Err(e) => {
26+
if attempts >= 3 {
27+
error!("Failed to send audit to SQS after 3 attempts {}", audit);
28+
break;
29+
} else {
30+
error!("Failed to send audit to SQS, retrying... {}", e);
31+
}
32+
}
33+
}
34+
}
35+
}

api/src/guilds/models.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use bigdecimal::{BigDecimal, ToPrimitive};
77
use sqlx::{Pool, Postgres, Row};
88
use twilight_model::id::Id;
99
use twilight_model::id::marker::GuildMarker;
10-
use crate::dsql::{bind_in_params, in_params};
10+
use common::dsql::{bind_in_params, in_params};
1111

1212
#[derive(Clone, Serialize, Deserialize, PartialEq)]
1313
pub struct Guild {

api/src/main.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,28 @@
1-
use crate::dsql::establish_connection;
1+
use common::dsql::establish_connection;
22
use aws_config::BehaviorVersion;
33
use axum::body::Body;
4-
use axum::{Json, Router, http::StatusCode, routing::get};
4+
use axum::{http::StatusCode, routing::get, Json, Router};
55
use http::Response;
6-
use lambda_http::{Error, run, tracing};
7-
use serde_json::{Value, json};
6+
use lambda_http::{run, tracing, Error};
7+
use serde_json::{json, Value};
88
use sqlx::{Pool, Postgres};
99
use std::sync::Arc;
1010
use tower_http::cors::CorsLayer;
1111

1212
mod discord;
13-
mod dsql;
1413
mod guilds;
1514
mod interactions;
1615
mod meta;
1716
mod middleware;
1817
mod users;
1918
mod utils;
19+
mod audit;
2020

2121
#[derive(Clone)]
2222
pub struct AppState {
2323
scheduler: aws_sdk_scheduler::Client,
2424
ses: aws_sdk_sesv2::Client,
25+
sqs: aws_sdk_sqs::Client,
2526
pg_pool: Pool<Postgres>,
2627
discord_bot: Arc<twilight_http::Client>,
2728
reqwest: Arc<reqwest::Client>,
@@ -83,6 +84,7 @@ async fn main() -> Result<(), Error> {
8384
let app_state = AppState {
8485
scheduler: aws_sdk_scheduler::Client::new(&config),
8586
ses: aws_sdk_sesv2::Client::new(&config),
87+
sqs: aws_sdk_sqs::Client::new(&config),
8688
pg_pool: pool,
8789
discord_bot,
8890
reqwest: Arc::new(reqwest::Client::new()),

api/src/users/link_guilds.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ use tower_http::cors::CorsLayer;
1313
use twilight_model::id::Id;
1414
use twilight_model::id::marker::{GuildMarker, UserMarker};
1515
use twilight_model::user::CurrentUser;
16+
use common::audit::AuditMessage;
17+
use crate::audit::audit;
1618

1719
pub fn router() -> axum::Router<AppState> {
1820
axum::Router::new()
@@ -41,6 +43,7 @@ async fn put_link_guilds_id(
4143
let mut user = User::from_db(user_id, &app_state.pg_pool)
4244
.await
4345
.unwrap();
46+
let audit_old_data = user.link_guilds.clone();
4447
if user
4548
.link_guilds
4649
.iter()
@@ -51,6 +54,7 @@ async fn put_link_guilds_id(
5154
}
5255
user.link_guilds.retain(|g| g.guild_id != guild_id);
5356
user.link_guilds.push(new_link_guild.clone());
57+
let audit_new_data = user.link_guilds.clone();
5458
user.save(&app_state.pg_pool).await;
5559

5660
let mut guild = Guild::from_db(guild_id, &app_state.pg_pool).await.unwrap();
@@ -76,6 +80,11 @@ async fn put_link_guilds_id(
7680
}
7781

7882
guild.save(&app_state.pg_pool).await;
83+
84+
// write audit
85+
audit(&AuditMessage::new("update_link_guilds".to_string(), user_id, Some(guild_id),
86+
Some(audit_old_data), Some(audit_new_data)), &app_state.sqs).await;
87+
7988
Ok(Json(json!(new_link_guild)))
8089
}
8190

0 commit comments

Comments
 (0)