Some checks failed
lint / lint (push) Has been cancelled
test / unit (push) Has been cancelled
test / integration (push) Has been cancelled
lint / lint (pull_request) Failing after 5m8s
test / unit (pull_request) Successful in 16m18s
test / integration (pull_request) Failing after 16m59s
- Task 1: Fix feed response hydration by adding `to_thought_response` helper and updating feed handlers to return full `ThoughtResponse`. - Task 2: Wire follower/following REST routes for user feeds. - Task 3: Add user listing and count endpoints, including `GET /users` and `GET /users/count`. - Task 4: Implement popular tags feature with `GET /tags/popular`. - Task 5: Enhance configuration with HOST, CORS_ORIGINS, and optional rate limiting using tower-governor.
109 lines
3.1 KiB
Rust
109 lines
3.1 KiB
Rust
use async_trait::async_trait;
|
|
use domain::errors::DomainError;
|
|
use event_transport::{MessageSource, RawMessage, Transport};
|
|
use futures::stream::BoxStream;
|
|
|
|
// ── NatsTransport — raw NATS publish backend ────────────────────────────────
|
|
|
|
pub struct NatsTransport {
|
|
client: async_nats::Client,
|
|
}
|
|
|
|
impl NatsTransport {
|
|
pub fn new(client: async_nats::Client) -> Self {
|
|
Self { client }
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Transport for NatsTransport {
|
|
async fn publish_bytes(&self, subject: &str, bytes: &[u8]) -> Result<(), DomainError> {
|
|
self.client
|
|
.publish(subject.to_string(), bytes.to_vec().into())
|
|
.await
|
|
.map_err(|e| DomainError::Internal(e.to_string()))
|
|
}
|
|
}
|
|
|
|
// ── NatsMessageSource — raw NATS subscribe backend ──────────────────────────
|
|
|
|
pub struct NatsMessageSource {
|
|
client: async_nats::Client,
|
|
}
|
|
|
|
impl NatsMessageSource {
|
|
pub fn new(client: async_nats::Client) -> Self {
|
|
Self { client }
|
|
}
|
|
}
|
|
|
|
impl MessageSource for NatsMessageSource {
|
|
fn messages(&self) -> BoxStream<'_, Result<RawMessage, DomainError>> {
|
|
let client = self.client.clone();
|
|
Box::pin(async_stream::try_stream! {
|
|
let mut sub = client
|
|
.subscribe(">")
|
|
.await
|
|
.map_err(|e| DomainError::Internal(e.to_string()))?;
|
|
|
|
use futures::StreamExt;
|
|
while let Some(msg) = sub.next().await {
|
|
let subject = msg.subject.to_string();
|
|
let payload = msg.payload.to_vec();
|
|
// Basic NATS: at-most-once — ack/nack are no-ops.
|
|
yield RawMessage {
|
|
subject,
|
|
payload,
|
|
ack: Box::new(|| {}),
|
|
nack: Box::new(|| {}),
|
|
};
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use domain::{
|
|
events::DomainEvent,
|
|
value_objects::{LikeId, ThoughtId, UserId},
|
|
};
|
|
use event_payload::EventPayload;
|
|
|
|
#[test]
|
|
fn payload_from_domain_event_has_correct_subject() {
|
|
let event = DomainEvent::ThoughtCreated {
|
|
thought_id: ThoughtId::new(),
|
|
user_id: UserId::new(),
|
|
in_reply_to_id: None,
|
|
};
|
|
let payload = EventPayload::from(&event);
|
|
assert_eq!(payload.subject(), "thoughts.created");
|
|
}
|
|
|
|
#[test]
|
|
fn domain_event_roundtrip_via_payload() {
|
|
let uid = UserId::new();
|
|
let tid = ThoughtId::new();
|
|
let event = DomainEvent::LikeAdded {
|
|
like_id: LikeId::new(),
|
|
user_id: uid.clone(),
|
|
thought_id: tid.clone(),
|
|
};
|
|
let payload = EventPayload::from(&event);
|
|
let back = DomainEvent::try_from(payload).unwrap();
|
|
if let DomainEvent::LikeAdded {
|
|
user_id,
|
|
thought_id,
|
|
..
|
|
} = back
|
|
{
|
|
assert_eq!(user_id, uid);
|
|
assert_eq!(thought_id, tid);
|
|
} else {
|
|
panic!("wrong variant");
|
|
}
|
|
}
|
|
}
|