@@ -6,11 +6,30 @@ use axum::{
66 Form ,
77} ;
88use rusqlite:: params;
9- use serde:: Deserialize ;
9+ use serde:: { Deserialize , Serialize } ;
1010use tera:: Context ;
1111
1212use super :: server:: AppState ;
1313
14+ #[ derive( Debug , Serialize ) ]
15+ struct UserExport {
16+ id : String ,
17+ name : String ,
18+ level : i32 ,
19+ xp : i32 ,
20+ social_credit : i64 ,
21+ relationship : String ,
22+ example_input : String ,
23+ example_output : String ,
24+ memories : Vec < MemoryExport > ,
25+ }
26+
27+ #[ derive( Debug , Serialize ) ]
28+ struct MemoryExport {
29+ key : String ,
30+ content : String ,
31+ }
32+
1433#[ derive( Debug , Deserialize ) ]
1534pub struct UserUpdateForm {
1635 pub name : String ,
@@ -25,19 +44,6 @@ pub struct MemoryForm {
2544 pub content : String ,
2645}
2746
28- pub async fn index ( State ( state) : State < AppState > ) -> Response {
29- let mut context = Context :: new ( ) ;
30- context. insert ( "title" , "Memory Manager" ) ;
31-
32- match state. templates . render ( "index.html" , & context) {
33- Ok ( html) => Html ( html) . into_response ( ) ,
34- Err ( e) => {
35- tracing:: error!( "Template error: {:?}" , e) ;
36- ( StatusCode :: INTERNAL_SERVER_ERROR , format ! ( "Template error: {}" , e) ) . into_response ( )
37- }
38- }
39- }
40-
4147pub async fn list_users ( State ( state) : State < AppState > ) -> Response {
4248 let conn = match state. db . get ( ) {
4349 Ok ( conn) => conn,
@@ -489,3 +495,144 @@ pub async fn serve_css() -> Response {
489495 )
490496 . into_response ( )
491497}
498+
499+ // Helper function to get all user exports with memories
500+ fn get_all_user_exports ( conn : & rusqlite:: Connection ) -> Result < Vec < UserExport > , String > {
501+ // Get all users with their data
502+ let mut stmt = conn. prepare (
503+ "SELECT id, level, xp, social_credit, name, relationship, example_input, example_output FROM user ORDER BY id"
504+ ) . map_err ( |e| format ! ( "Database error: {}" , e) ) ?;
505+
506+ let users_result: Result < Vec < ( String , i32 , i32 , i64 , String , String , String , String ) > , _ > = stmt
507+ . query_map ( [ ] , |row| {
508+ Ok ( (
509+ row. get ( 0 ) ?,
510+ row. get ( 1 ) ?,
511+ row. get ( 2 ) ?,
512+ row. get ( 3 ) ?,
513+ row. get ( 4 ) ?,
514+ row. get ( 5 ) ?,
515+ row. get ( 6 ) ?,
516+ row. get ( 7 ) ?,
517+ ) )
518+ } )
519+ . and_then ( |mapped| mapped. collect ( ) ) ;
520+
521+ let users = users_result. map_err ( |e| format ! ( "Database error: {}" , e) ) ?;
522+
523+ // Create user exports without memories
524+ let exports: Vec < UserExport > = users
525+ . into_iter ( )
526+ . map ( |( id, level, xp, social_credit, name, relationship, example_input, example_output) | {
527+ UserExport {
528+ id,
529+ name,
530+ level,
531+ xp,
532+ social_credit,
533+ relationship,
534+ example_input,
535+ example_output,
536+ memories : Vec :: new ( ) ,
537+ }
538+ } )
539+ . collect ( ) ;
540+
541+ Ok ( exports)
542+ }
543+
544+ pub async fn export_prompts_json ( State ( state) : State < AppState > ) -> Response {
545+ let conn = match state. db . get ( ) {
546+ Ok ( conn) => conn,
547+ Err ( e) => {
548+ return ( StatusCode :: INTERNAL_SERVER_ERROR , format ! ( "Database error: {}" , e) ) . into_response ( )
549+ }
550+ } ;
551+
552+ let exports = match get_all_user_exports ( & conn) {
553+ Ok ( exports) => exports,
554+ Err ( e) => {
555+ return ( StatusCode :: INTERNAL_SERVER_ERROR , e) . into_response ( )
556+ }
557+ } ;
558+
559+ match serde_json:: to_string_pretty ( & exports) {
560+ Ok ( json) => (
561+ [
562+ ( axum:: http:: header:: CONTENT_TYPE , "application/json" ) ,
563+ (
564+ axum:: http:: header:: CONTENT_DISPOSITION ,
565+ "attachment; filename=\" prompts_export.json\" " ,
566+ ) ,
567+ ] ,
568+ json,
569+ )
570+ . into_response ( ) ,
571+ Err ( e) => {
572+ ( StatusCode :: INTERNAL_SERVER_ERROR , format ! ( "JSON error: {}" , e) ) . into_response ( )
573+ }
574+ }
575+ }
576+
577+ pub async fn export_users_csv ( State ( state) : State < AppState > ) -> Response {
578+ let conn = match state. db . get ( ) {
579+ Ok ( conn) => conn,
580+ Err ( e) => {
581+ return ( StatusCode :: INTERNAL_SERVER_ERROR , format ! ( "Database error: {}" , e) ) . into_response ( )
582+ }
583+ } ;
584+
585+ let exports = match get_all_user_exports ( & conn) {
586+ Ok ( exports) => exports,
587+ Err ( e) => {
588+ return ( StatusCode :: INTERNAL_SERVER_ERROR , e) . into_response ( )
589+ }
590+ } ;
591+
592+ // Helper to escape CSV fields
593+ let escape_csv = |s : & str | -> String {
594+ if s. contains ( ',' ) || s. contains ( '"' ) || s. contains ( '\n' ) {
595+ format ! ( "\" {}\" " , s. replace( '"' , "\" \" " ) )
596+ } else {
597+ s. to_string ( )
598+ }
599+ } ;
600+
601+ // Build CSV with memories
602+ let mut csv = String :: from ( "user_id,name,level,xp,social_credit,relationship,example_input,example_output,memories\n " ) ;
603+
604+ for user in exports {
605+ // Serialize memories as JSON for the CSV field
606+ let memories_json = match serde_json:: to_string ( & user. memories ) {
607+ Ok ( json) => json,
608+ Err ( e) => {
609+ return ( StatusCode :: INTERNAL_SERVER_ERROR , format ! ( "JSON error: {}" , e) ) . into_response ( )
610+ }
611+ } ;
612+
613+ csv. push_str ( & format ! (
614+ "{},{},{},{},{},{},{},{},{}\n " ,
615+ escape_csv( & user. id) ,
616+ escape_csv( & user. name) ,
617+ user. level,
618+ user. xp,
619+ user. social_credit,
620+ escape_csv( & user. relationship) ,
621+ escape_csv( & user. example_input) ,
622+ escape_csv( & user. example_output) ,
623+ escape_csv( & memories_json) ,
624+ ) ) ;
625+ }
626+
627+ (
628+ [
629+ ( axum:: http:: header:: CONTENT_TYPE , "text/csv" ) ,
630+ (
631+ axum:: http:: header:: CONTENT_DISPOSITION ,
632+ "attachment; filename=\" users_export.csv\" " ,
633+ ) ,
634+ ] ,
635+ csv,
636+ )
637+ . into_response ( )
638+ }
0 commit comments