Files
movies-diary/crates/adapters/nats/src/subject.rs
Gabriel Kaszewski 5da979649b feat: image storage generalization, user profile, and federation polish
- Replace PosterStorage with generic ImageStorage port (IMAGE_STORAGE_BACKEND/PATH env vars)
- Rename poster-storage crate to image-storage; serve at /images/{*key}
- Add bio and avatar_path to User model (migration 0009_user_profile)
- update_profile use case with avatar upload, mime validation, old avatar cleanup
- GET/PUT /api/v1/profile and GET/POST /settings/profile HTML page
- Enrich AP Person actor with summary, icon, url, discoverable fields
- Store remote actor avatar_url (migration 0010_ap_remote_actor_avatar)
- Shared inbox delivery via collect_inboxes deduplication
- Broadcast Update(Person) to followers on UserUpdated event
- Paginated outbox: OrderedCollection + OrderedCollectionPage with cursor
- Announce/boost tracking in ap_announces table (migration 0011_ap_announces)
2026-05-11 22:59:52 +02:00

79 lines
2.4 KiB
Rust

use domain::events::DomainEvent;
pub fn event_to_subject(prefix: &str, event: &DomainEvent) -> String {
let suffix = match event {
DomainEvent::ReviewLogged { .. } => "review.logged",
DomainEvent::ReviewUpdated { .. } => "review.updated",
DomainEvent::MovieDiscovered { .. } => "movie.discovered",
DomainEvent::MovieDeleted { .. } => "movie.deleted",
DomainEvent::UserUpdated { .. } => "user.updated",
};
format!("{prefix}.{suffix}")
}
pub fn consumer_subject_filter(prefix: &str) -> String {
format!("{prefix}.>")
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::NaiveDateTime;
use domain::value_objects::{ExternalMetadataId, MovieId, Rating, ReviewId, UserId};
use uuid::Uuid;
fn dt() -> NaiveDateTime {
chrono::DateTime::from_timestamp(1_700_000_000, 0).unwrap().naive_utc()
}
#[test]
fn review_logged_subject() {
let event = DomainEvent::ReviewLogged {
review_id: ReviewId::from_uuid(Uuid::new_v4()),
movie_id: MovieId::from_uuid(Uuid::new_v4()),
user_id: UserId::from_uuid(Uuid::new_v4()),
rating: Rating::new(3).unwrap(),
watched_at: dt(),
};
assert_eq!(
event_to_subject("movies-diary.events", &event),
"movies-diary.events.review.logged"
);
}
#[test]
fn review_updated_subject() {
let event = DomainEvent::ReviewUpdated {
review_id: ReviewId::from_uuid(Uuid::new_v4()),
movie_id: MovieId::from_uuid(Uuid::new_v4()),
user_id: UserId::from_uuid(Uuid::new_v4()),
rating: Rating::new(5).unwrap(),
watched_at: dt(),
};
assert_eq!(
event_to_subject("movies-diary.events", &event),
"movies-diary.events.review.updated"
);
}
#[test]
fn movie_discovered_subject() {
let event = DomainEvent::MovieDiscovered {
movie_id: MovieId::from_uuid(Uuid::new_v4()),
external_metadata_id: ExternalMetadataId::new("tt0000001".into()).unwrap(),
};
assert_eq!(
event_to_subject("movies-diary.events", &event),
"movies-diary.events.movie.discovered"
);
}
#[test]
fn consumer_subject_filter_appends_wildcard() {
assert_eq!(
consumer_subject_filter("movies-diary.events"),
"movies-diary.events.>"
);
}
}