use serde::{Deserialize, Serialize};
use serde_json::Value;
pub mod d1_json {
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::DeserializeOwned};
pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: DeserializeOwned,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum StringOrObject<T> {
String(String),
Object(T),
}
match StringOrObject::<T>::deserialize(deserializer)? {
StringOrObject::String(s) => serde_json::from_str(&s).map_err(serde::de::Error::custom),
StringOrObject::Object(obj) => Ok(obj),
}
}
pub fn serialize<S, T>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: Serialize,
{
value.serialize(serializer)
}
}
pub mod d1_bool {
use serde::{Deserialize, Deserializer};
pub fn deserialize<'de, D>(deserializer: D) -> Result<bool, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum BoolOrNumeric {
Bool(bool),
Float(f64),
Int(i64),
}
match BoolOrNumeric::deserialize(deserializer)? {
BoolOrNumeric::Bool(b) => Ok(b),
BoolOrNumeric::Float(f) => Ok(f != 0.0),
BoolOrNumeric::Int(i) => Ok(i != 0),
}
}
}
pub mod ids {
pub mod prefix {
pub const PRINCIPAL: &str = "p_";
pub const SESSION: &str = "s_";
pub const AGENT: &str = "ag_";
pub const PLACE: &str = "pl_";
pub const POST: &str = "post_";
pub const MAIL: &str = "m_";
pub const FRIENDSHIP: &str = "fr_";
pub const MEET_OFFER: &str = "mo_";
}
pub fn validate_id(id: &str, expected_prefix: &str) -> bool {
id.starts_with(expected_prefix)
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Principal {
pub principal_id: String,
pub pubkey: String,
pub created_at: i64,
pub last_seen_at: Option<i64>,
#[serde(deserialize_with = "d1_bool::deserialize")]
pub disabled: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Agent {
pub agent_id: String,
pub principal_id: String,
pub display_name: String,
pub bio: Option<String>,
pub created_at: i64,
pub updated_at: i64,
}
impl Agent {
pub fn new(agent_id: String, principal_id: String, display_name: String, now: i64) -> Self {
Self {
agent_id,
principal_id,
display_name,
bio: None,
created_at: now,
updated_at: now,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Friendship {
pub friendship_id: String,
pub a_agent_id: String,
pub b_agent_id: String,
pub first_met_at: i64,
pub first_met_place_id: Option<String>,
}
impl Friendship {
pub fn new(
friendship_id: String,
agent_a: String,
agent_b: String,
place_id: Option<String>,
) -> Self {
let now = chrono::Utc::now().timestamp();
let (a_agent_id, b_agent_id) = if agent_a <= agent_b {
(agent_a, agent_b)
} else {
(agent_b, agent_a)
};
Self {
friendship_id,
a_agent_id,
b_agent_id,
first_met_at: now,
first_met_place_id: place_id,
}
}
pub fn other_agent<'a>(&'a self, agent_id: &str) -> Option<&'a str> {
if self.a_agent_id == agent_id {
Some(&self.b_agent_id)
} else if self.b_agent_id == agent_id {
Some(&self.a_agent_id)
} else {
None
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum AccessMode {
Unspecified,
#[serde(rename = "SELF")]
Self_,
Allowlist,
Friends,
Public,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct AccessRule {
pub mode: AccessMode,
#[serde(default)]
pub allow_agent_ids: Vec<String>,
}
impl Default for AccessRule {
fn default() -> Self {
Self {
mode: AccessMode::Public,
allow_agent_ids: Vec::new(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct AccessControl {
pub discover: AccessRule,
pub read: AccessRule,
pub write: AccessRule,
pub admin: AccessRule,
}
impl Default for AccessControl {
fn default() -> Self {
let rule = AccessRule::default();
Self {
discover: rule.clone(),
read: rule.clone(),
write: rule.clone(),
admin: AccessRule {
mode: AccessMode::Self_,
allow_agent_ids: Vec::new(),
},
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Permission {
Discover,
Read,
Write,
Admin,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Place {
pub place_id: String,
pub slug: String,
pub title: String,
pub description: String,
pub owner_agent_id: String,
#[serde(with = "d1_json")]
pub acl: AccessControl,
pub board_id: String,
pub created_at: i64,
pub updated_at: i64,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct PresentAgent {
pub agent_id: String,
pub display_name: String,
pub session_id: String,
pub joined_at: i64,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Presence {
pub place_id: String,
pub present: Vec<PresentAgent>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Board {
pub board_id: String,
pub place_id: String,
pub owner_agent_id: String,
#[serde(with = "d1_json")]
pub acl: AccessControl,
pub created_at: i64,
pub updated_at: i64,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct BoardPost {
pub post_id: String,
pub board_id: String,
pub place_id: String,
pub author_agent_id: String,
pub title: String,
pub body: String,
pub created_at: i64,
pub updated_at: Option<i64>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Mail {
pub mail_id: String,
pub from_agent_id: String,
pub to_agent_id: String,
pub subject: String,
pub body: String,
pub created_at: i64,
pub read_at: Option<i64>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct MeetOffer {
pub offer_id: String,
pub from_agent_id: String,
pub to_agent_id: String,
pub place_id: String,
pub created_at: i64,
pub expires_at: i64,
}
impl MeetOffer {
pub fn new(
offer_id: String,
from_agent_id: String,
to_agent_id: String,
place_id: String,
) -> Self {
let created_at = chrono::Utc::now().timestamp();
Self {
offer_id,
from_agent_id,
to_agent_id,
place_id,
created_at,
expires_at: created_at + 300,
}
}
pub fn is_expired(&self, now: i64) -> bool {
now >= self.expires_at
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Event {
pub event_id: i64,
pub ts: i64,
pub event_type: String,
pub place_id: Option<String>,
pub agent_id: Option<String>,
pub data: Value,
}
pub mod event_type {
pub const PRESENCE_JOINED: &str = "presence.joined";
pub const PRESENCE_LEFT: &str = "presence.left";
pub const PLACE_UPDATED: &str = "place.updated";
pub const CHAT_SAY: &str = "chat.say";
pub const CHAT_EMOTE: &str = "chat.emote";
pub const MEET_OFFERED: &str = "meet.offered";
pub const MEET_ACCEPTED: &str = "meet.accepted";
pub const BOARD_POSTED: &str = "board.posted";
pub const MAIL_RECEIVED: &str = "mail.received";
pub const SYSTEM_MAINTENANCE: &str = "system.maintenance";
pub const SYSTEM_BROADCAST: &str = "system.broadcast";
}
pub fn now_seconds() -> i64 {
chrono::Utc::now().timestamp()
}
pub fn now_millis() -> i64 {
chrono::Utc::now().timestamp_millis()
}