use crate::access_control::RevokedMapping;
use crate::db::Pool;
pub const EVENTS_CHANNEL: &str = "skilj_events";
pub const REVOCATIONS_CHANNEL: &str = "skilj_revocations";
pub const REGISTRATION_CHANGED_CHANNEL: &str = "skilj_registration_changed";
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum Message {
EventAppended {
bounded_context: String,
sequence: i64,
origin_instance_id: String,
},
Revoked {
revoked: RevokedMapping,
origin_instance_id: String,
},
RegistrationChanged,
Resync,
}
#[derive(serde::Deserialize)]
struct EventAppendedPayload {
bounded_context: String,
sequence: i64,
#[serde(default)]
origin_instance_id: String,
}
#[derive(serde::Deserialize)]
struct RevokedPayload {
role_id: String,
bounded_context: String,
#[serde(default)]
origin_instance_id: String,
}
pub struct Listener {
pool: Pool,
inner: Option<sqlx::postgres::PgListener>,
}
impl Listener {
pub async fn connect(pool: &Pool) -> crate::error::Result<Self> {
Ok(Self {
pool: pool.clone(),
inner: Some(Self::listen(pool).await?),
})
}
async fn listen(pool: &Pool) -> crate::error::Result<sqlx::postgres::PgListener> {
let mut listener = sqlx::postgres::PgListener::connect_with(pool).await?;
listener
.listen_all([
EVENTS_CHANNEL,
REVOCATIONS_CHANNEL,
REGISTRATION_CHANGED_CHANNEL,
])
.await?;
Ok(listener)
}
pub async fn recv(&mut self) -> crate::error::Result<Message> {
loop {
let listener = match &mut self.inner {
Some(listener) => listener,
None => {
self.inner = Some(Self::listen(&self.pool).await?);
return Ok(Message::Resync);
}
};
let received = match listener.try_recv().await {
Ok(received) => received,
Err(err) => {
self.inner = None;
return Err(err.into());
}
};
let Some(notification) = received else {
tracing::warn!("cross-instance listener connection lost, reconnecting");
self.inner = None;
continue;
};
match notification.channel() {
EVENTS_CHANNEL => {
match serde_json::from_str::<EventAppendedPayload>(notification.payload()) {
Ok(p) => {
return Ok(Message::EventAppended {
bounded_context: p.bounded_context,
sequence: p.sequence,
origin_instance_id: p.origin_instance_id,
})
}
Err(err) => tracing::warn!(
error = %err,
"malformed skilj_events NOTIFY payload, skipping"
),
}
}
REVOCATIONS_CHANNEL => {
match serde_json::from_str::<RevokedPayload>(notification.payload()) {
Ok(p) => {
return Ok(Message::Revoked {
revoked: RevokedMapping {
role_id: p.role_id,
bounded_context: p.bounded_context,
},
origin_instance_id: p.origin_instance_id,
})
}
Err(err) => tracing::warn!(
error = %err,
"malformed skilj_revocations NOTIFY payload, skipping"
),
}
}
REGISTRATION_CHANGED_CHANNEL => return Ok(Message::RegistrationChanged),
other => {
tracing::warn!(channel = other, "unexpected NOTIFY channel, skipping")
}
}
}
}
}