Skip to main content

coven_database/
members.rs

1//! Members, devices and circles as the committed prefix has them (E9, E12).
2//! Each call and its live subscription run the same query.
3
4use crate::{sqlite::DatabaseConnection, store_state::DeviceState, DbError};
5use coven_format::admin::{MemberPublicKeys, MemberRole};
6use coven_foundation::id_source::{CircleId, DeviceId, MemberId};
7
8/// A live member and every device they registered (E9).
9#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct MemberInfo {
11    /// The stable member id, independent of its keys (§10).
12    pub id: MemberId,
13    /// The current public signing and sealing pair.
14    pub keys: MemberPublicKeys,
15    /// Current store role.
16    pub role: MemberRole,
17    /// How the member reaches storage: their provider account, or the id of
18    /// the S3 key they use, never its secret (§14.3).
19    pub access: coven_format::MemberAccess,
20    /// Every registered device, including removed and replaced ids.
21    pub devices: Vec<DeviceInfo>,
22    /// Whether this is the local member.
23    pub is_self: bool,
24}
25
26/// One of a member's device ids in the committed prefix (E9).
27#[derive(Clone, Debug, PartialEq, Eq)]
28pub struct DeviceInfo {
29    /// The device id.
30    pub id: DeviceId,
31    /// Its name from its registration.
32    pub name: String,
33    /// Active, removed, or replaced by a kept registration (§14.1).
34    pub state: DeviceState,
35    /// Whether this is the local installation, regardless of registration state.
36    pub is_self: bool,
37}
38
39/// A circle the local member belongs to (E12).
40#[derive(Clone, Debug, PartialEq, Eq)]
41pub struct Circle {
42    /// Circle identity.
43    pub id: CircleId,
44    /// Current name.
45    pub name: String,
46}
47
48/// An active store member in a circle (E12).
49#[derive(Clone, Debug, PartialEq, Eq)]
50pub struct CircleMemberInfo {
51    /// The stable member id, independent of its keys (§10).
52    pub member: MemberId,
53    /// Whether this is the local member.
54    pub is_self: bool,
55}
56
57use crate::store_state_tables::{circle, device_member, member};
58
59pub(crate) fn members(
60    db: &DatabaseConnection,
61    device: DeviceId,
62) -> Result<Vec<MemberInfo>, DbError> {
63    let me = device_member(db, device)?;
64    let members = db.query(
65        "SELECT member,signing,sealing,role,removed,access FROM _coven_members WHERE removed=0
66         ORDER BY member",
67        [],
68        crate::store_state_tables::stored_member,
69    )?;
70    members
71        .into_iter()
72        .map(|(id, crate::StoreMember { keys, access, role, .. })| {
73            let devices = db.query(
74                "SELECT device,name,state FROM _coven_devices WHERE member=?1 ORDER BY device",
75                [id],
76                |r| {
77                    let id = r.get::<_, DeviceId>(0)?;
78                    Ok(DeviceInfo {
79                        id,
80                        is_self: id == device,
81                        name: r.get(1)?,
82                        state: crate::store_state_tables::device_state(r, 2)?,
83                    })
84                },
85            )?;
86            Ok(MemberInfo { is_self: me.as_ref() == Some(&id), keys, id, role, access, devices })
87        })
88        .collect()
89}
90
91pub(crate) fn circles(db: &DatabaseConnection, device: DeviceId) -> Result<Vec<Circle>, DbError> {
92    let Some(me) = device_member(db, device)? else {
93        return Ok(Vec::new());
94    };
95    // Ordered by the membership's circle, which the member's index holds in
96    // order; `c.circle` is the same value but would be sorted after the join.
97    let circles = db.query(
98        "SELECT c.circle,c.name FROM _coven_circle_members m JOIN _coven_circles c ON c.circle=m.circle
99         WHERE m.member=?1 AND NOT EXISTS(SELECT 1 FROM _coven_circle_deletions d WHERE d.circle=c.circle)
100         ORDER BY m.circle",
101        [me],
102        |r| {
103            Ok(Circle {
104                id: circle(r, 0)?,
105                name: r.get(1)?,
106            })
107        },
108    )?;
109    Ok(circles)
110}
111
112/// A circle's members who remain in the store; none for an absent or deleted circle.
113pub(crate) fn circle_members(
114    db: &DatabaseConnection,
115    device: DeviceId,
116    circle: CircleId,
117) -> Result<Vec<CircleMemberInfo>, DbError> {
118    let me = device_member(db, device)?;
119    let members = db.query(
120        "SELECT m.member FROM _coven_circle_members m JOIN _coven_members s ON s.member=m.member
121         WHERE m.circle=?1 AND s.removed=0
122           AND NOT EXISTS(SELECT 1 FROM _coven_circle_deletions d WHERE d.circle=m.circle)
123         ORDER BY m.member",
124        [circle],
125        |r| member(r, 0),
126    )?;
127    Ok(members
128        .into_iter()
129        .map(|member| CircleMemberInfo { is_self: me.as_ref() == Some(&member), member })
130        .collect())
131}
132
133#[cfg(test)]
134#[path = "members_tests.rs"]
135mod tests;