feat: search reindex, worker improvements, person IDs, user display names
- add admin POST /api/v1/admin/reindex-search endpoint + event-driven handler - backfill persons from movie_cast/movie_crew into persons table - paginate person list_page/backfill_from_credits_batch to cap memory - concurrent worker event dispatch with semaphore (max 8) - graceful worker shutdown (drain in-flight tasks on SIGINT) - always ack events, log handler errors as warnings (no infinite retry) - NATS ack_wait 600s, AtomicBool guard against concurrent reindex - add username/display_name to UserSummaryDto and users list - add person_id to CastMemberDto/CrewMemberDto via get_movie_profile use case - add movie_id to wrapup MovieRef, person_id to wrapup PersonStat - thread tmdb_person_id through wrapup cast pipeline - add is_federated to FeedEntryDto - cap orphaned persons query with LIMIT 500 - add SPA link to classic site footer
This commit is contained in:
@@ -61,6 +61,7 @@ impl EventHandler for RecordingHandler {
|
||||
DomainEvent::WatchEventIngested { .. } => "watch_event_ingested",
|
||||
DomainEvent::WrapUpRequested { .. } => "wrapup_requested",
|
||||
DomainEvent::WrapUpCompleted { .. } => "wrapup_completed",
|
||||
DomainEvent::SearchReindexRequested => "search_reindex",
|
||||
};
|
||||
self.calls.lock().unwrap().push(label);
|
||||
Ok(())
|
||||
@@ -85,34 +86,34 @@ async fn dispatches_to_all_handlers() {
|
||||
};
|
||||
|
||||
WorkerService::new(Arc::new(consumer), vec![Arc::new(handler)])
|
||||
.run()
|
||||
.run(tokio::sync::watch::channel(false).1)
|
||||
.await;
|
||||
|
||||
assert_eq!(*calls.lock().unwrap(), vec!["movie_discovered"]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn nacks_when_handler_fails() {
|
||||
let nack_called = Arc::new(Mutex::new(false));
|
||||
async fn acks_even_when_handler_fails() {
|
||||
let ack_called = Arc::new(Mutex::new(false));
|
||||
|
||||
struct TrackingAck {
|
||||
nack_called: Arc<Mutex<bool>>,
|
||||
ack_called: Arc<Mutex<bool>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl AckHandle for TrackingAck {
|
||||
async fn ack(&self) -> Result<(), DomainError> {
|
||||
*self.ack_called.lock().unwrap() = true;
|
||||
Ok(())
|
||||
}
|
||||
async fn nack(&self) -> Result<(), DomainError> {
|
||||
*self.nack_called.lock().unwrap() = true;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct TrackingConsumer {
|
||||
event: DomainEvent,
|
||||
nack_called: Arc<Mutex<bool>>,
|
||||
ack_called: Arc<Mutex<bool>>,
|
||||
}
|
||||
|
||||
impl EventConsumer for TrackingConsumer {
|
||||
@@ -120,7 +121,7 @@ async fn nacks_when_handler_fails() {
|
||||
let envelope = EventEnvelope::new(
|
||||
self.event.clone(),
|
||||
Box::new(TrackingAck {
|
||||
nack_called: Arc::clone(&self.nack_called),
|
||||
ack_called: Arc::clone(&self.ack_called),
|
||||
}),
|
||||
);
|
||||
Box::pin(stream::iter(vec![Ok(envelope)]))
|
||||
@@ -138,14 +139,14 @@ async fn nacks_when_handler_fails() {
|
||||
|
||||
let consumer = TrackingConsumer {
|
||||
event: movie_discovered(),
|
||||
nack_called: Arc::clone(&nack_called),
|
||||
ack_called: Arc::clone(&ack_called),
|
||||
};
|
||||
|
||||
WorkerService::new(Arc::new(consumer), vec![Arc::new(FailingHandler)])
|
||||
.run()
|
||||
.run(tokio::sync::watch::channel(false).1)
|
||||
.await;
|
||||
|
||||
assert!(*nack_called.lock().unwrap());
|
||||
assert!(*ack_called.lock().unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -189,7 +190,9 @@ async fn acks_when_all_handlers_succeed() {
|
||||
ack_called: Arc::clone(&ack_called),
|
||||
};
|
||||
|
||||
WorkerService::new(Arc::new(consumer), vec![]).run().await;
|
||||
WorkerService::new(Arc::new(consumer), vec![])
|
||||
.run(tokio::sync::watch::channel(false).1)
|
||||
.await;
|
||||
|
||||
assert!(*ack_called.lock().unwrap());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user