@@ -8,7 +8,7 @@ use std::sync::Arc;
88
99use 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::{
3535use serde:: Deserialize ;
3636use serde_json:: { json, Value } ;
3737use 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" ) ) ]
4143use 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
205210pub 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
209221pub 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) ]
321342fn 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 ) ]
679732pub enum RunError {
680733 #[ error( transparent) ]
0 commit comments