@ -108,6 +108,15 @@ pub async fn bulk_import(
return Err ( AppError ::Validation ( "No sources provided" . into ( ) ) ) ;
}
// Verify theme ownership if theme_id is provided
let theme_id = body . theme_id ;
if let Some ( tid ) = theme_id {
let theme = db ::themes ::get_by_id ( & state . pool , auth_user . id , tid ) . await ? ;
if theme . is_none ( ) {
return Err ( AppError ::NotFound ( "Theme not found" . into ( ) ) ) ;
}
}
let mut valid_sources : Vec < ( String , String ) > = Vec ::new ( ) ;
let mut errors : Vec < String > = Vec ::new ( ) ;
@ -124,6 +133,7 @@ pub async fn bulk_import(
auth_user . id ,
& mut valid_sources ,
& mut errors ,
theme_id ,
"Bulk import" ,
)
. await ? ;
@ -138,6 +148,7 @@ async fn do_bulk_import(
user_id : uuid ::Uuid ,
valid_sources : & mut Vec < ( String , String ) > ,
errors : & mut Vec < String > ,
theme_id : Option < Uuid > ,
log_label : & str ,
) -> Result < BulkImportResponse , AppError > {
let current_count = db ::sources ::count_for_user ( pool , user_id ) . await ? ;
@ -151,7 +162,7 @@ async fn do_bulk_import(
) ) ;
}
let created = db ::sources ::bulk_create ( pool , user_id , valid_sources , None ) . await ? ;
let created = db ::sources ::bulk_create ( pool , user_id , valid_sources , theme_id ) . await ? ;
let imported = created . len ( ) ;
let skipped = valid_sources . len ( ) - imported ;
@ -170,6 +181,12 @@ async fn do_bulk_import(
} )
}
/// Query parameters for `POST /api/v1/sources/import-csv`.
#[ derive(Debug, Deserialize) ]
pub struct CsvImportQuery {
pub theme_id : Option < Uuid > ,
}
/// `POST /api/v1/sources/import-csv`
///
/// Imports sources from a CSV file uploaded via multipart form data.
@ -178,8 +195,17 @@ async fn do_bulk_import(
pub async fn import_csv (
auth_user : AuthUser ,
State ( state ) : State < AppState > ,
Query ( params ) : Query < CsvImportQuery > ,
mut multipart : Multipart ,
) -> Result < impl IntoResponse , AppError > {
// Verify theme ownership if theme_id is provided
let theme_id = params . theme_id ;
if let Some ( tid ) = theme_id {
let theme = db ::themes ::get_by_id ( & state . pool , auth_user . id , tid ) . await ? ;
if theme . is_none ( ) {
return Err ( AppError ::NotFound ( "Theme not found" . into ( ) ) ) ;
}
}
// Extract the first file field from the multipart upload
let field = multipart
. next_field ( )
@ -232,7 +258,7 @@ pub async fn import_csv(
}
let response =
do_bulk_import ( & state . pool , auth_user . id , & mut valid_sources , & mut errors , "CSV import" )
do_bulk_import ( & state . pool , auth_user . id , & mut valid_sources , & mut errors , theme_id , "CSV import" )
. await ? ;
Ok ( Json ( response ) )
@ -268,12 +294,19 @@ pub async fn export_csv(
/// `PUT /api/v1/sources/preferred`
///
/// Bulk-update which sources are marked as preferred.
/// Accepts a list of source IDs; all other sources are set to non-preferred.
/// Accepts a list of source IDs and a theme_id; all other sources
/// in that theme are set to non-preferred.
pub async fn update_preferred (
auth_user : AuthUser ,
State ( state ) : State < AppState > ,
Json ( body ) : Json < UpdatePreferredRequest > ,
) -> Result < impl IntoResponse , AppError > {
db ::sources ::update_preferred ( & state . pool , auth_user . id , & body . source_ids ) . await ? ;
// Verify theme ownership
let theme = db ::themes ::get_by_id ( & state . pool , auth_user . id , body . theme_id ) . await ? ;
if theme . is_none ( ) {
return Err ( AppError ::NotFound ( "Theme not found" . into ( ) ) ) ;
}
db ::sources ::update_preferred ( & state . pool , auth_user . id , & body . source_ids , body . theme_id ) . await ? ;
Ok ( StatusCode ::OK )
}