44use axum:: {
55 extract:: { Query , State } ,
66 http:: StatusCode ,
7- routing:: get,
7+ routing:: { delete , get} ,
88 Json , Router ,
99} ;
1010use serde:: Deserialize ;
11+ use std:: collections:: HashSet ;
1112
1213use crate :: auth:: AuthUser ;
1314use crate :: models:: {
@@ -17,6 +18,7 @@ use crate::models::{
1718 UpdateTranscriptionPreferencesRequest , UpdateUserProfileRequest , UserLanguage , UserProfile ,
1819 UserSettingsStatusResponse , AssistantSettingsData ,
1920} ;
21+ use crate :: services:: firestore:: { LLM_USAGE_SUBCOLLECTION , SCREEN_ACTIVITY_SUBCOLLECTION } ;
2022use crate :: AppState ;
2123
2224// ============================================================================
@@ -553,6 +555,302 @@ async fn update_assistant_settings(
553555 }
554556}
555557
558+ // ============================================================================
559+ // Account Deletion
560+ // ============================================================================
561+
562+ /// DELETE /v1/users/delete-account
563+ /// Deletes all server-side data for the authenticated user and then deletes Firebase Auth account.
564+ async fn delete_account (
565+ State ( state) : State < AppState > ,
566+ user : AuthUser ,
567+ ) -> Result < Json < UserSettingsStatusResponse > , StatusCode > {
568+ tracing:: info!( "Deleting account and all data for user {}" , user. uid) ;
569+
570+ // Conversations
571+ let conversations = state
572+ . firestore
573+ . get_conversations ( & user. uid , 5000 , 0 , true , & [ ] , None , None , None , None )
574+ . await
575+ . map_err ( |e| {
576+ tracing:: error!( "Failed to list conversations during delete-account: {}" , e) ;
577+ StatusCode :: INTERNAL_SERVER_ERROR
578+ } ) ?;
579+ for conversation in conversations {
580+ state
581+ . firestore
582+ . delete_conversation ( & user. uid , & conversation. id )
583+ . await
584+ . map_err ( |e| {
585+ tracing:: error!( "Failed to delete conversation {}: {}" , conversation. id, e) ;
586+ StatusCode :: INTERNAL_SERVER_ERROR
587+ } ) ?;
588+ }
589+
590+ // Action items
591+ let action_items = state
592+ . firestore
593+ . get_action_items (
594+ & user. uid , 5000 , 0 , None , None , None , None , None , None , None , Some ( true ) ,
595+ )
596+ . await
597+ . map_err ( |e| {
598+ tracing:: error!( "Failed to list action items during delete-account: {}" , e) ;
599+ StatusCode :: INTERNAL_SERVER_ERROR
600+ } ) ?;
601+ for item in action_items {
602+ state
603+ . firestore
604+ . delete_action_item ( & user. uid , & item. id )
605+ . await
606+ . map_err ( |e| {
607+ tracing:: error!( "Failed to delete action item {}: {}" , item. id, e) ;
608+ StatusCode :: INTERNAL_SERVER_ERROR
609+ } ) ?;
610+ }
611+
612+ // Staged tasks
613+ let staged_tasks = state
614+ . firestore
615+ . get_staged_tasks ( & user. uid , 5000 , 0 )
616+ . await
617+ . map_err ( |e| {
618+ tracing:: error!( "Failed to list staged tasks during delete-account: {}" , e) ;
619+ StatusCode :: INTERNAL_SERVER_ERROR
620+ } ) ?;
621+ for task in staged_tasks {
622+ state
623+ . firestore
624+ . delete_staged_task ( & user. uid , & task. id )
625+ . await
626+ . map_err ( |e| {
627+ tracing:: error!( "Failed to delete staged task {}: {}" , task. id, e) ;
628+ StatusCode :: INTERNAL_SERVER_ERROR
629+ } ) ?;
630+ }
631+
632+ // Memories
633+ state
634+ . firestore
635+ . delete_all_memories ( & user. uid )
636+ . await
637+ . map_err ( |e| {
638+ tracing:: error!( "Failed to delete all memories during delete-account: {}" , e) ;
639+ StatusCode :: INTERNAL_SERVER_ERROR
640+ } ) ?;
641+
642+ // Focus sessions
643+ let focus_sessions = state
644+ . firestore
645+ . get_focus_sessions ( & user. uid , 5000 , 0 , None )
646+ . await
647+ . map_err ( |e| {
648+ tracing:: error!( "Failed to list focus sessions during delete-account: {}" , e) ;
649+ StatusCode :: INTERNAL_SERVER_ERROR
650+ } ) ?;
651+ for session in focus_sessions {
652+ state
653+ . firestore
654+ . delete_focus_session ( & user. uid , & session. id )
655+ . await
656+ . map_err ( |e| {
657+ tracing:: error!( "Failed to delete focus session {}: {}" , session. id, e) ;
658+ StatusCode :: INTERNAL_SERVER_ERROR
659+ } ) ?;
660+ }
661+
662+ // Advice
663+ let advice_items = state
664+ . firestore
665+ . get_advice ( & user. uid , 5000 , 0 , None , true )
666+ . await
667+ . map_err ( |e| {
668+ tracing:: error!( "Failed to list advice during delete-account: {}" , e) ;
669+ StatusCode :: INTERNAL_SERVER_ERROR
670+ } ) ?;
671+ for advice in advice_items {
672+ state
673+ . firestore
674+ . delete_advice ( & user. uid , & advice. id )
675+ . await
676+ . map_err ( |e| {
677+ tracing:: error!( "Failed to delete advice {}: {}" , advice. id, e) ;
678+ StatusCode :: INTERNAL_SERVER_ERROR
679+ } ) ?;
680+ }
681+
682+ // Messages
683+ state
684+ . firestore
685+ . delete_messages ( & user. uid , None )
686+ . await
687+ . map_err ( |e| {
688+ tracing:: error!( "Failed to delete messages during delete-account: {}" , e) ;
689+ StatusCode :: INTERNAL_SERVER_ERROR
690+ } ) ?;
691+
692+ // Chat sessions
693+ let chat_sessions = state
694+ . firestore
695+ . get_chat_sessions ( & user. uid , None , 5000 , 0 , None )
696+ . await
697+ . map_err ( |e| {
698+ tracing:: error!( "Failed to list chat sessions during delete-account: {}" , e) ;
699+ StatusCode :: INTERNAL_SERVER_ERROR
700+ } ) ?;
701+ for session in chat_sessions {
702+ state
703+ . firestore
704+ . delete_chat_session ( & user. uid , & session. id )
705+ . await
706+ . map_err ( |e| {
707+ tracing:: error!( "Failed to delete chat session {}: {}" , session. id, e) ;
708+ StatusCode :: INTERNAL_SERVER_ERROR
709+ } ) ?;
710+ }
711+
712+ // Folders
713+ let folders = state. firestore . get_folders ( & user. uid ) . await . map_err ( |e| {
714+ tracing:: error!( "Failed to list folders during delete-account: {}" , e) ;
715+ StatusCode :: INTERNAL_SERVER_ERROR
716+ } ) ?;
717+ for folder in folders {
718+ state
719+ . firestore
720+ . delete_folder ( & user. uid , & folder. id , None )
721+ . await
722+ . map_err ( |e| {
723+ tracing:: error!( "Failed to delete folder {}: {}" , folder. id, e) ;
724+ StatusCode :: INTERNAL_SERVER_ERROR
725+ } ) ?;
726+ }
727+
728+ // Goals (active + completed)
729+ let mut goal_ids = HashSet :: new ( ) ;
730+ let active_goals = state. firestore . get_user_goals ( & user. uid , 5000 ) . await . map_err ( |e| {
731+ tracing:: error!( "Failed to list active goals during delete-account: {}" , e) ;
732+ StatusCode :: INTERNAL_SERVER_ERROR
733+ } ) ?;
734+ let completed_goals = state
735+ . firestore
736+ . get_completed_goals ( & user. uid , 5000 )
737+ . await
738+ . map_err ( |e| {
739+ tracing:: error!( "Failed to list completed goals during delete-account: {}" , e) ;
740+ StatusCode :: INTERNAL_SERVER_ERROR
741+ } ) ?;
742+ for goal in active_goals {
743+ goal_ids. insert ( goal. id ) ;
744+ }
745+ for goal in completed_goals {
746+ goal_ids. insert ( goal. id ) ;
747+ }
748+ for goal_id in goal_ids {
749+ state
750+ . firestore
751+ . delete_goal ( & user. uid , & goal_id)
752+ . await
753+ . map_err ( |e| {
754+ tracing:: error!( "Failed to delete goal {}: {}" , goal_id, e) ;
755+ StatusCode :: INTERNAL_SERVER_ERROR
756+ } ) ?;
757+ }
758+
759+ // People
760+ let people = state. firestore . get_people ( & user. uid ) . await . map_err ( |e| {
761+ tracing:: error!( "Failed to list people during delete-account: {}" , e) ;
762+ StatusCode :: INTERNAL_SERVER_ERROR
763+ } ) ?;
764+ for person in people {
765+ state
766+ . firestore
767+ . delete_person ( & user. uid , & person. id )
768+ . await
769+ . map_err ( |e| {
770+ tracing:: error!( "Failed to delete person {}: {}" , person. id, e) ;
771+ StatusCode :: INTERNAL_SERVER_ERROR
772+ } ) ?;
773+ }
774+
775+ // Persona
776+ if let Some ( persona) = state
777+ . firestore
778+ . get_user_persona ( & user. uid )
779+ . await
780+ . map_err ( |e| {
781+ tracing:: error!( "Failed to get user persona during delete-account: {}" , e) ;
782+ StatusCode :: INTERNAL_SERVER_ERROR
783+ } ) ?
784+ {
785+ state
786+ . firestore
787+ . delete_persona ( & persona. id )
788+ . await
789+ . map_err ( |e| {
790+ tracing:: error!( "Failed to delete persona {}: {}" , persona. id, e) ;
791+ StatusCode :: INTERNAL_SERVER_ERROR
792+ } ) ?;
793+ }
794+
795+ // Knowledge graph
796+ state
797+ . firestore
798+ . delete_kg_data ( & user. uid )
799+ . await
800+ . map_err ( |e| {
801+ tracing:: error!( "Failed to delete knowledge graph during delete-account: {}" , e) ;
802+ StatusCode :: INTERNAL_SERVER_ERROR
803+ } ) ?;
804+
805+ // Subcollections not otherwise covered
806+ state
807+ . firestore
808+ . delete_all_documents_in_subcollection ( & user. uid , SCREEN_ACTIVITY_SUBCOLLECTION )
809+ . await
810+ . map_err ( |e| {
811+ tracing:: error!( "Failed to delete screen activity during delete-account: {}" , e) ;
812+ StatusCode :: INTERNAL_SERVER_ERROR
813+ } ) ?;
814+ state
815+ . firestore
816+ . delete_all_documents_in_subcollection ( & user. uid , LLM_USAGE_SUBCOLLECTION )
817+ . await
818+ . map_err ( |e| {
819+ tracing:: error!( "Failed to delete llm usage during delete-account: {}" , e) ;
820+ StatusCode :: INTERNAL_SERVER_ERROR
821+ } ) ?;
822+
823+ // Delete root users/{uid} document after subcollections are purged
824+ state
825+ . firestore
826+ . delete_user_root_document ( & user. uid )
827+ . await
828+ . map_err ( |e| {
829+ tracing:: error!( "Failed to delete root user document during delete-account: {}" , e) ;
830+ StatusCode :: INTERNAL_SERVER_ERROR
831+ } ) ?;
832+
833+ // Delete Firebase Auth account via admin API (service account OAuth).
834+ let project_id = state
835+ . config
836+ . firebase_project_id
837+ . clone ( )
838+ . unwrap_or_else ( || "based-hardware" . to_string ( ) ) ;
839+ state
840+ . firestore
841+ . delete_firebase_auth_user ( & project_id, & user. uid )
842+ . await
843+ . map_err ( |e| {
844+ tracing:: error!( "Failed to delete Firebase Auth account for {}: {}" , user. uid, e) ;
845+ StatusCode :: INTERNAL_SERVER_ERROR
846+ } ) ?;
847+
848+ tracing:: info!( "Account deletion completed for user {}" , user. uid) ;
849+ Ok ( Json ( UserSettingsStatusResponse {
850+ status : "ok" . to_string ( ) ,
851+ } ) )
852+ }
853+
556854// ============================================================================
557855// Router
558856// ============================================================================
@@ -601,4 +899,6 @@ pub fn users_routes() -> Router<AppState> {
601899 "/v1/users/assistant-settings" ,
602900 get ( get_assistant_settings) . patch ( update_assistant_settings) ,
603901 )
902+ // Account deletion
903+ . route ( "/v1/users/delete-account" , delete ( delete_account) )
604904}
0 commit comments