11use  crate :: authentication:: UserId ; 
2- use  crate :: domain:: SubscriberEmail ; 
3- use  crate :: email_client:: EmailClient ; 
4- use  crate :: idempotency:: { get_saved_response,  save_response} ; 
52use  crate :: idempotency:: IdempotencyKey ; 
3+ use  crate :: idempotency:: { get_saved_response,  save_response,  try_processing,  NextAction } ; 
64use  crate :: utils:: { e400,  e500,  see_other} ; 
75use  actix_web:: web:: ReqData ; 
86use  actix_web:: { web,  HttpResponse } ; 
97use  actix_web_flash_messages:: FlashMessage ; 
108use  anyhow:: Context ; 
11- use  sqlx:: PgPool ; 
9+ use  sqlx:: { PgPool ,  Postgres ,  Transaction } ; 
10+ use  uuid:: Uuid ; 
1211
1312#[ derive( serde:: Deserialize ) ]  
1413pub  struct  FormData  { 
@@ -20,13 +19,12 @@ pub struct FormData {
2019
2120#[ tracing:: instrument(  
2221name = "Publish a newsletter issue" ,   
23-     skip ( form ,  pool ,  email_client ,  user_id ) ,  
22+     skip_all ,  
2423    fields( user_id = %* user_id)  
2524) ] 
2625pub  async  fn  publish_newsletter ( 
2726    form :  web:: Form < FormData > , 
2827    pool :  web:: Data < PgPool > , 
29-     email_client :  web:: Data < EmailClient > , 
3028    user_id :  ReqData < UserId > , 
3129)  -> Result < HttpResponse ,  actix_web:: Error >  { 
3230    let  user_id = user_id. into_inner ( ) ; 
@@ -37,65 +35,92 @@ pub async fn publish_newsletter(
3735        idempotency_key, 
3836    }  = form. 0 ; 
3937    let  idempotency_key:  IdempotencyKey  = idempotency_key. try_into ( ) . map_err ( e400) ?; 
38+     let  mut  transaction = match  try_processing ( & pool,  & idempotency_key,  * user_id) 
39+         . await 
40+         . map_err ( e500) ?
41+     { 
42+         NextAction :: StartProcessing ( t)  => t, 
43+         NextAction :: ReturnSavedResponse ( saved_response)  => { 
44+             success_message ( ) . send ( ) ; 
45+             return  Ok ( saved_response) ; 
46+         } 
47+     } ; 
48+ 
4049    if  let  Some ( save_response)  = get_saved_response ( & pool,  & idempotency_key,  * user_id) 
4150        . await 
4251        . map_err ( e500) ?
4352    { 
4453        return  Ok ( save_response) ; 
4554    } 
46-     let  subscribers = get_confirmed_subscribers ( & pool) . await . map_err ( e500) ?; 
47-     for  subscriber in  subscribers { 
48-         match  subscriber { 
49-             Ok ( subscriber)  => { 
50-                 email_client
51-                     . send_email ( & subscriber. email ,  & title,  & html_content,  & text_content) 
52-                     . await 
53-                     . with_context ( || { 
54-                         format ! ( "Failed to send newsletter issue to {}" ,  subscriber. email) 
55-                     } ) 
56-                     . map_err ( e500) ?; 
57-             } 
55+     let  issue_id = insert_newsletter_issue ( & mut  transaction,  & title,  & text_content,  & html_content) 
56+         . await 
57+         . context ( "Failed to store newsletter issue details" ) 
58+         . map_err ( e500) ?; 
59+     enqueue_delivery_tasks ( & mut  transaction,  issue_id) 
60+         . await 
61+         . context ( "Failed to enqueue delivery task" ) 
62+         . map_err ( e500) ?; 
5863
59-             Err ( error)  => { 
60-                 tracing:: warn!( 
61-                     error. cause_chain = ?error, 
62-                     error. message = %error, 
63-                     "Skipping a confirmed subscriber. \  
64-                      Their stored contact details are invalid", 
65-                 ) ; 
66-             } 
67-         } 
68-     } 
69-     FlashMessage :: info ( "The newsletter issue has been published!" ) . send ( ) ; 
7064    let  response = see_other ( "/admin/newsletters" ) ; 
71-     let  response = save_response ( & pool ,  & idempotency_key,  * user_id,  response) 
65+     let  response = save_response ( transaction ,  & idempotency_key,  * user_id,  response) 
7266        . await 
7367        . map_err ( e500) ?; 
68+     success_message ( ) . send ( ) ; 
7469    Ok ( response) 
7570} 
7671
77- struct  ConfirmedSubscriber  { 
78-     email :  SubscriberEmail , 
72+ fn  success_message ( )  -> FlashMessage  { 
73+     FlashMessage :: info ( "The newsletter issue has been accepted - \  
74+      emails will go out shortly.", ) 
75+ } 
76+ 
77+ #[ tracing:: instrument( skip_all) ]  
78+ async  fn  insert_newsletter_issue ( 
79+     transaction :  & mut  Transaction < ' _ ,  Postgres > , 
80+     title :  & str , 
81+     text_content :  & str , 
82+     html_content :  & str , 
83+ )  -> Result < Uuid ,  sqlx:: Error >  { 
84+     let  newsletter_issue_id = Uuid :: new_v4 ( ) ; 
85+     sqlx:: query!( 
86+         r#" 
87+             INSERT INTO newsletter_issues ( 
88+             newsletter_issue_id, 
89+             title, 
90+             text_content, 
91+             html_content, 
92+             published_at 
93+             ) 
94+             VALUES ($1, $2, $3, $4, now()) 
95+         "# , 
96+         newsletter_issue_id, 
97+         title, 
98+         text_content, 
99+         html_content
100+     ) 
101+     . execute ( transaction) 
102+     . await ?; 
103+     Ok ( newsletter_issue_id) 
79104} 
80105
81- #[ tracing:: instrument( name = "Get confirmed subscribers" ,  skip( pool) ) ]  
82- async  fn  get_confirmed_subscribers ( 
83-     pool :  & PgPool , 
84- )  -> Result < Vec < Result < ConfirmedSubscriber ,  anyhow:: Error > > ,  anyhow:: Error >  { 
85-     let  confirmed_subscribers = sqlx:: query!( 
106+ #[ tracing:: instrument( skip_all) ]  
107+ async  fn  enqueue_delivery_tasks ( 
108+     transaction :  & mut  Transaction < ' _ ,  Postgres > , 
109+     newsletter_issue_id :  Uuid , 
110+ )  -> Result < ( ) ,  sqlx:: Error >  { 
111+     sqlx:: query!( 
86112        r#" 
87-         SELECT email 
88-         FROM subscriptions 
89-         WHERE status = 'confirmed' 
113+             INSERT INTO issue_delivery_queue ( 
114+                 newsletter_issue_id, 
115+                 subscriber_email 
116+             ) 
117+             SELECT $1, email 
118+             FROM subscriptions 
119+             WHERE status = 'confirmed' 
90120        "# , 
121+         newsletter_issue_id, 
91122    ) 
92-     . fetch_all ( pool) 
93-     . await ?
94-     . into_iter ( ) 
95-     . map ( |r| match  SubscriberEmail :: parse ( r. email )  { 
96-         Ok ( email)  => Ok ( ConfirmedSubscriber  {  email } ) , 
97-         Err ( error)  => Err ( anyhow:: anyhow!( error) ) , 
98-     } ) 
99-     . collect ( ) ; 
100-     Ok ( confirmed_subscribers) 
123+     . execute ( transaction) 
124+     . await ?; 
125+     Ok ( ( ) ) 
101126} 
0 commit comments