Skip to content

Commit b8e36c3

Browse files
committed
Add person DO test and build step
1 parent dc29ab7 commit b8e36c3

10 files changed

Lines changed: 339 additions & 15 deletions

File tree

.github/workflows/ci.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ jobs:
2323
- name: Cache cargo artifacts
2424
uses: Swatinem/rust-cache@v2
2525

26+
- name: Install wasm target
27+
run: rustup target add wasm32-unknown-unknown
28+
29+
- name: Install worker-build
30+
run: cargo install worker-build@^0.7
31+
2632
- name: Set up Bun
2733
uses: oven-sh/setup-bun@v2
2834

@@ -35,5 +41,8 @@ jobs:
3541
- name: Pre-build fake pipeline image
3642
run: docker compose build fake-pipeline
3743

44+
- name: Build worker bundle
45+
run: worker-build --release
46+
3847
- name: Run tests
3948
run: cargo test --all --locked

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,4 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
3838
[dev-dependencies]
3939
serde_json = "1.0"
4040
http-body-util = "0.1"
41+
tempfile = "3.10"

src/config.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub struct Config {
1515
pub posthog_project_api_key: Option<String>,
1616
pub session_recording_endpoint: Option<String>,
1717
pub posthog_signing_secret: Option<String>,
18+
pub person_debug_token: Option<String>,
1819
}
1920

2021
#[derive(Debug, Error)]
@@ -84,6 +85,7 @@ impl Config {
8485
.ok()
8586
.map(|secret| secret.to_string())
8687
.or_else(|| env.var("POSTHOG_SIGNING_SECRET").ok().map(|v| v.to_string()));
88+
let person_debug_token = env.var("PERSON_DEBUG_TOKEN").ok().map(|v| v.to_string());
8789

8890
Ok(Self {
8991
address,
@@ -93,6 +95,7 @@ impl Config {
9395
posthog_project_api_key,
9496
session_recording_endpoint,
9597
posthog_signing_secret,
98+
person_debug_token,
9699
})
97100
}
98101

@@ -140,6 +143,7 @@ impl Config {
140143
let posthog_project_api_key = env::var("POSTHOG_API_KEY").ok();
141144
let session_recording_endpoint = env::var("POSTHOG_SESSION_RECORDING_ENDPOINT").ok();
142145
let posthog_signing_secret = env::var("POSTHOG_SIGNING_SECRET").ok();
146+
let person_debug_token = env::var("PERSON_DEBUG_TOKEN").ok();
143147

144148
Ok(Self {
145149
address,
@@ -149,6 +153,7 @@ impl Config {
149153
posthog_project_api_key,
150154
session_recording_endpoint,
151155
posthog_signing_secret,
156+
person_debug_token,
152157
})
153158
}
154159
}

src/extractors.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -772,7 +772,12 @@ mod tests {
772772
use serde_json::{json, Value};
773773
use std::{io::Write, sync::Arc, time::Duration};
774774

775-
use crate::{models::CaptureRequest, pipeline::PipelineClient, persons::NoopPersonStore, AppState};
775+
use crate::{
776+
models::CaptureRequest,
777+
pipeline::PipelineClient,
778+
persons::NoopPersonStore,
779+
AppState,
780+
};
776781
use reqwest::Url;
777782

778783
fn test_state() -> AppState {
@@ -789,6 +794,7 @@ mod tests {
789794
session_recording_endpoint: None,
790795
signing_secret: None,
791796
person_store: Arc::new(NoopPersonStore),
797+
person_debug_token: None,
792798
}
793799
}
794800

src/lib.rs

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::sync::Arc;
88

99
use axum::{
1010
body::Bytes,
11-
extract::State,
11+
extract::{Path, State},
1212
http::{HeaderMap, StatusCode},
1313
response::IntoResponse,
1414
routing::{get, post},
@@ -35,7 +35,9 @@ use persons::{
3535
use serde::Deserialize;
3636
use serde_json::{json, Value};
3737
use thiserror::Error;
38-
use tracing::{error, info, warn};
38+
use tracing::{error, warn};
39+
#[cfg(not(target_arch = "wasm32"))]
40+
use tracing::info;
3941

4042
#[cfg(not(target_arch = "wasm32"))]
4143
use tokio::net::TcpListener;
@@ -53,6 +55,7 @@ pub(crate) struct AppState {
5355
pub(crate) session_recording_endpoint: Option<String>,
5456
pub(crate) signing_secret: Option<String>,
5557
pub(crate) person_store: Arc<dyn PersonStore>,
58+
pub(crate) person_debug_token: Option<String>,
5659
}
5760

5861
#[derive(Debug, Error)]
@@ -151,6 +154,7 @@ pub async fn run_with_config(config: Config) -> Result<(), RunError> {
151154
config.posthog_project_api_key.clone(),
152155
config.session_recording_endpoint.clone(),
153156
config.posthog_signing_secret.clone(),
157+
config.person_debug_token.clone(),
154158
)
155159
.await
156160
}
@@ -196,28 +200,38 @@ pub async fn fetch(
196200
config.posthog_project_api_key.clone(),
197201
config.session_recording_endpoint.clone(),
198202
config.posthog_signing_secret.clone(),
203+
config.person_debug_token.clone(),
199204
person_store,
200205
);
201206

202207
Ok(router.call(req).await?)
203208
}
204209

205210
pub fn build_router(pipeline: Arc<PipelineClient>) -> Router {
206-
build_router_with_options(pipeline, None, None, None, Arc::new(NoopPersonStore))
211+
build_router_with_options(
212+
pipeline,
213+
None,
214+
None,
215+
None,
216+
None,
217+
Arc::new(NoopPersonStore),
218+
)
207219
}
208220

209221
pub fn build_router_with_options(
210222
pipeline: Arc<PipelineClient>,
211223
decide_api_token: Option<String>,
212224
session_recording_endpoint: Option<String>,
213225
signing_secret: Option<String>,
226+
person_debug_token: Option<String>,
214227
person_store: Arc<dyn PersonStore>,
215228
) -> Router {
216229
router(build_state(
217230
pipeline,
218231
decide_api_token,
219232
session_recording_endpoint,
220233
signing_secret,
234+
person_debug_token,
221235
person_store,
222236
))
223237
}
@@ -231,6 +245,7 @@ pub async fn serve(listener: TcpListener, pipeline: Arc<PipelineClient>) -> Resu
231245
None,
232246
None,
233247
None,
248+
None,
234249
Arc::new(NoopPersonStore),
235250
),
236251
)
@@ -244,12 +259,14 @@ pub async fn serve_with_options(
244259
decide_api_token: Option<String>,
245260
session_recording_endpoint: Option<String>,
246261
signing_secret: Option<String>,
262+
person_debug_token: Option<String>,
247263
) -> Result<(), RunError> {
248264
let state = build_state(
249265
pipeline,
250266
decide_api_token,
251267
session_recording_endpoint,
252268
signing_secret,
269+
person_debug_token,
253270
Arc::new(NoopPersonStore),
254271
);
255272
serve_with_state(listener, state).await
@@ -271,6 +288,7 @@ fn router(state: AppState) -> Router {
271288
.route("/flags/", post(decide))
272289
.route("/s", post(session_recording))
273290
.route("/s/", post(session_recording))
291+
.route("/__debug/person/:id", get(debug_person))
274292
.route("/healthz", get(health))
275293
.with_state(state);
276294

@@ -289,6 +307,7 @@ fn build_state(
289307
decide_api_token: Option<String>,
290308
session_recording_endpoint: Option<String>,
291309
signing_secret: Option<String>,
310+
person_debug_token: Option<String>,
292311
person_store: Arc<dyn PersonStore>,
293312
) -> AppState {
294313
AppState {
@@ -297,6 +316,7 @@ fn build_state(
297316
session_recording_endpoint,
298317
signing_secret,
299318
person_store,
319+
person_debug_token,
300320
}
301321
}
302322

@@ -318,6 +338,7 @@ fn init_tracing() {
318338
}
319339

320340
#[cfg(target_arch = "wasm32")]
341+
#[allow(dead_code)]
321342
fn init_tracing() {}
322343

323344
#[cfg_attr(target_arch = "wasm32", worker::send)]
@@ -675,6 +696,38 @@ async fn health() -> impl IntoResponse {
675696
Json(json!({ "status": "ok" }))
676697
}
677698

699+
#[cfg_attr(target_arch = "wasm32", worker::send)]
700+
async fn debug_person(
701+
State(state): State<AppState>,
702+
headers: HeaderMap,
703+
Path(distinct_id): Path<String>,
704+
) -> impl IntoResponse {
705+
let Some(expected) = state.person_debug_token.as_deref() else {
706+
return StatusCode::NOT_FOUND.into_response();
707+
};
708+
709+
let provided = headers
710+
.get("x-hogflare-debug-token")
711+
.and_then(|value| value.to_str().ok())
712+
.map(str::trim);
713+
714+
if provided != Some(expected) {
715+
return StatusCode::UNAUTHORIZED.into_response();
716+
}
717+
718+
match state.person_store.get_snapshot(&distinct_id).await {
719+
Ok(snapshot) => (StatusCode::OK, Json(snapshot)).into_response(),
720+
Err(err) => {
721+
error!(error = %err, "failed to load person record");
722+
let body = Json(ErrorResponse {
723+
status: 0,
724+
error: "failed to load person record".to_string(),
725+
});
726+
(StatusCode::INTERNAL_SERVER_ERROR, body).into_response()
727+
}
728+
}
729+
}
730+
678731
#[derive(Debug, Error)]
679732
pub enum RunError {
680733
#[error(transparent)]

0 commit comments

Comments
 (0)