pub mod store;
use localtime::LocalTime;
use serde::Serialize;
use sqlite as sql;
use thiserror::Error;
use crate::cob;
use crate::cob::TypedId;
use crate::git::{BranchName, fmt::Qualified};
use crate::prelude::RepoId;
use crate::storage::{RefUpdate, RemoteId};
pub use store::{Error, Store};
/// Read and write to the store.
pub type StoreWriter = Store<store::Write>;
/// Write to the store.
pub type StoreReader = Store<store::Read>;
/// Unique identifier for a notification.
pub type NotificationId = u32;
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
pub enum NotificationStatus {
ReadAt(LocalTime),
Unread,
}
impl NotificationStatus {
pub fn is_read(&self) -> bool {
matches!(self, Self::ReadAt(_))
}
}
/// A notification for an updated ref.
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
pub struct Notification {
/// Unique notification ID.
pub id: NotificationId,
/// Source repository for this notification.
pub repo: RepoId,
/// Remote, if any.
pub remote: Option<RemoteId>,
/// Qualified ref name that was updated.
pub qualified: Qualified<'static>,
/// The underlying ref update.
pub update: RefUpdate,
/// Notification kind.
pub kind: NotificationKind,
/// Read status.
pub status: NotificationStatus,
/// Timestamp of the update.
pub timestamp: LocalTime,
}
/// Type of notification.
#[derive(Debug, PartialEq, Eq, Clone, Serialize)]
pub enum NotificationKind {
/// A COB changed.
Cob {
#[serde(flatten)]
typed_id: TypedId,
},
/// A source branch changed.
Branch { name: BranchName },
/// Unknown reference.
Unknown { refname: Qualified<'static> },
}
#[derive(Error, Debug)]
pub enum NotificationKindError {
#[error("invalid cob identifier: {0}")]
TypedId(#[from] cob::ParseIdentifierError),
/// Invalid Git ref format.
#[error("invalid ref format: {0}")]
RefFormat(#[from] crate::git::fmt::Error),
}
impl TryFrom<Qualified<'_>> for NotificationKind {
type Error = NotificationKindError;
fn try_from(value: Qualified) -> Result<Self, Self::Error> {
let kind = match TypedId::from_qualified(&value)? {
Some(typed_id) => Self::Cob { typed_id },
None => match value.non_empty_iter() {
("refs", "heads", head, rest) => Self::Branch {
name: [head]
.into_iter()
.chain(rest)
.collect::<Vec<_>>()
.join("/")
.try_into()?,
},
_ => Self::Unknown {
refname: value.to_owned(),
},
},
};
Ok(kind)
}
}
impl TryFrom<&sql::Value> for NotificationStatus {
type Error = sql::Error;
fn try_from(value: &sql::Value) -> Result<Self, Self::Error> {
match value {
sql::Value::Null => Ok(NotificationStatus::Unread),
sql::Value::Integer(i) => Ok(NotificationStatus::ReadAt(LocalTime::from_millis(
*i as u128,
))),
_ => Err(sql::Error {
code: None,
message: Some("sql: invalid type for notification status".to_owned()),
}),
}
}
}
impl sql::BindableWithIndex for &NotificationStatus {
fn bind<I: sql::ParameterIndex>(self, stmt: &mut sql::Statement<'_>, i: I) -> sql::Result<()> {
match self {
NotificationStatus::Unread => sql::Value::Null.bind(stmt, i),
NotificationStatus::ReadAt(t) => {
sql::Value::Integer(t.as_millis() as i64).bind(stmt, i)
}
}
}
}