feat(chaper-9): Add newsletter publication endpoint#19
Conversation
This commit introduces a new endpoint `/newsletter` that allows for publishing newsletters. It includes the necessary handlers and data structures for handling newsletter publication.
WalkthroughRemoves axum macros feature; adjusts error conversions for surrealdb::Error; simplifies health_check to always return 200; adds newsletter publishing handler and routes; exposes handlers::newsletter; introduces ConfirmedSubscriber type and get_confirmed_subscribers; refactors error propagation in model; adds newsletter integration tests and test module wiring. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant C as Client
participant R as Axum Router
participant H as publish_newsletter
participant M as ModelManager
participant E as EmailClient
C->>R: POST /newsletter {title, content{html,text}}
R->>H: Route to handler (State<Arc<...>>, Json)
rect rgba(200,220,255,0.25)
note right of H: Retrieve recipients
H->>M: get_confirmed_subscribers()
M-->>H: Vec<ConfirmedSubscriber>
end
loop For each confirmed subscriber
H->>E: send_email(to, subject, html, text)
E-->>H: Result()
end
H-->>R: StatusCode::OK
R-->>C: 200 OK
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
✨ Finishing Touches
🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 12
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
src/handlers/subscription.rs (2)
11-15: Harden deserialization: reject unknown fields.
Prevents silent acceptance of misspelled JSON/form keys.Apply:
-#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] pub struct FormData { @@ -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] pub struct Params {Also applies to: 35-38
50-56: Fix random token generation to use thread_rng() with rand 0.9.2
Cargo.toml pinsrand = "0.9.2", sorand::rng()is unavailable. Update imports and invocation as follows:--- a/src/handlers/subscription.rs +++ b/src/handlers/subscription.rs @@ - use rand::Rng; - use rand::distr::Alphanumeric; + use rand::{distributions::Alphanumeric, Rng, thread_rng}; @@ fn get_confirmation_token() -> String { - rand::rng() + thread_rng() .sample_iter(&Alphanumeric) .map(char::from) .take(25) .collect() }src/model.rs (2)
60-77: Consistently check query responses for errors.
create_subscriber uses .check()?; confirm_subscriber should do the same to surface statement errors consistently.pub async fn confirm_subscriber(&self, token: String) -> Result<()> { self.db() .await? .query( r#" UPDATE subscriptions SET status = 'CONFIRMED' @@ ) .bind(("token_val", token)) - .await?; + .await? + .check()?; Ok(()) }
95-121: Connection flow looks solid. Minor readiness optimization.
Happy path is fine. If this backs a /health readiness check, consider a cheap ping instead of re-running health() frequently; OnceCell ensures single connect anyway.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (10)
Cargo.toml(1 hunks)src/errors.rs(2 hunks)src/handlers/health_check.rs(1 hunks)src/handlers/mod.rs(1 hunks)src/handlers/newsletter.rs(1 hunks)src/handlers/subscription.rs(1 hunks)src/model.rs(6 hunks)src/startup.rs(2 hunks)tests/api/main.rs(1 hunks)tests/api/newsletter.rs(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-28T13:50:40.460Z
Learnt from: TN19N
PR: TN19N/subscriptions#9
File: src/domain/subscriber/email.rs:18-19
Timestamp: 2025-08-28T13:50:40.460Z
Learning: For the SubscriberEmail validation in src/domain/subscriber/email.rs, the user TN19N prefers to include the actual email value in ValidationError messages for debugging purposes, accepting the PII exposure trade-off in favor of detailed error information returned with 400 HTTP status.
Applied to files:
src/model.rs
🧬 Code graph analysis (7)
src/handlers/subscription.rs (2)
src/state.rs (4)
AppState(10-14)AppState(16-24)FromRef(32-36)from_ref(33-35)tests/api/helpers.rs (2)
TestApp(12-16)TestApp(23-72)
tests/api/newsletter.rs (1)
tests/api/helpers.rs (1)
TestApp(23-72)
src/handlers/health_check.rs (1)
src/state.rs (1)
AppState(10-14)
src/startup.rs (1)
src/handlers/newsletter.rs (1)
publish_newsletter(21-40)
src/handlers/mod.rs (1)
src/lib.rs (1)
handlers(5-5)
src/handlers/newsletter.rs (3)
src/email_client.rs (2)
email_client(207-215)EmailClient(25-62)src/domain/subscriber/mod.rs (1)
Subscriber(11-14)src/domain/subscriber/email.rs (1)
SubscriberEmail(6-6)
src/model.rs (2)
tests/api/subscriptions_confirm.rs (2)
QueryResult(52-56)confirmation_works(24-75)src/domain/subscriber/mod.rs (1)
Subscriber(11-14)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: check
🔇 Additional comments (5)
Cargo.toml (1)
17-17: No remaining axum macros detectedtests/api/main.rs (1)
3-3: LGTM: newsletter test module wired in.src/handlers/subscription.rs (1)
1-1: Axum state import cleanup looks good.
No issues with removing AppState/debug macro usage here. Handler signatures already extract Arc directly.src/model.rs (2)
32-56: Good: transactional insert and response checking.
Using a transaction plus .check()? is the right call to catch multi-statement failures.
32-56: Ensure subscriptions.status defaults to 'PENDING'.create_subscriberomitsstatus, whileconfirm_subscriberfilters bystatus = 'PENDING'. Please verify that your migrations definestatuswith a default of'PENDING'.
/newsletterthat allows for publishing newsletters.Summary by CodeRabbit
New Features
Refactor
Tests
Chores