[fractal/backend: 7/9] backend: Member model and relation with room



commit e2c64f23108b4c5d87c80963ae773cdc74bdfdea
Author: Daniel GarcĂ­a Moreno <danigm wadobo com>
Date:   Sun Jan 6 21:09:11 2019 +0100

    backend: Member model and relation with room

 fractal-backend/src/model/member.rs | 197 ++++++++++++++++++++++++++++++++++++
 fractal-backend/src/model/mod.rs    |   8 ++
 fractal-backend/src/model/room.rs   |   1 -
 fractal-backend/tests/models.rs     | 100 ++++++++++++++++++
 4 files changed, 305 insertions(+), 1 deletion(-)
---
diff --git a/fractal-backend/src/model/member.rs b/fractal-backend/src/model/member.rs
new file mode 100644
index 00000000..e929df98
--- /dev/null
+++ b/fractal-backend/src/model/member.rs
@@ -0,0 +1,197 @@
+pub use api::types::Member;
+use failure::err_msg;
+use failure::Error;
+
+use rusqlite::types::ToSql;
+use rusqlite::Row;
+use rusqlite::NO_PARAMS;
+
+use super::conn;
+use super::Model;
+
+impl Model for Member {
+    fn table_name() -> &'static str {
+        "member"
+    }
+
+    fn get_id(&self) -> &str {
+        &self.uid
+    }
+
+    fn fields() -> Vec<&'static str> {
+        vec!["id", "alias", "avatar"]
+    }
+
+    fn create_sql() -> String {
+        format!(
+            "
+        CREATE TABLE if not exists {table} (
+            id TEXT PRIMARY KEY,
+            alias TEXT,
+            avatar TEXT
+        )
+        ",
+            table = Self::table_name()
+        )
+    }
+
+    fn store(&self) -> Result<(), Error> {
+        let fields = Self::fields().join(",");
+        let questions = Self::fields()
+            .iter()
+            .map(|_| "?")
+            .collect::<Vec<&str>>()
+            .join(",");
+        let query = format!(
+            "INSERT INTO {} ({}) VALUES ({})",
+            Self::table_name(),
+            fields,
+            questions
+        );
+
+        conn(
+            move |c| {
+                c.execute(&query, &[&self.uid, &self.alias as &ToSql, &self.avatar])
+                    .map(|_| ())
+                    .map_err(|e| err_msg(e.to_string()))
+            },
+            Err(err_msg("Connection not init")),
+        )
+    }
+
+    fn map_row(row: &Row) -> Self {
+        Self {
+            uid: row.get(0),
+            alias: row.get(1),
+            avatar: row.get(2),
+        }
+    }
+}
+
+pub trait MemberModel: Sized {
+    fn store_relation(&self, room: &str) -> Result<(), Error>;
+    fn delete_relation(&self, room: &str) -> Result<usize, Error>;
+    fn update_relation(&self, room: &str) -> Result<(), Error>;
+    fn get_range(room: &str, limit: Option<u32>, offset: Option<u32>) -> Result<Vec<Self>, Error>;
+    fn delete_relations(room: &str) -> Result<usize, Error>;
+    fn create_relation_table() -> Result<(), Error>;
+}
+
+impl MemberModel for Member {
+    fn create_relation_table() -> Result<(), Error> {
+        let query = format!(
+            "
+        CREATE TABLE if not exists {table}_room (
+            uid TEXT NOT NULL,
+            room TEXT NOT NULL,
+
+            FOREIGN KEY(room) REFERENCES room(id),
+            FOREIGN KEY(uid) REFERENCES {table}(id)
+        )
+        ",
+            table = Self::table_name()
+        );
+
+        conn(
+            move |c| {
+                c.execute(&query, NO_PARAMS)
+                    .map(|_| ())
+                    .map_err(|e| err_msg(e.to_string()))
+            },
+            Err(err_msg("Connection not init")),
+        )
+    }
+
+    fn store_relation(&self, room: &str) -> Result<(), Error> {
+        let query = format!(
+            "INSERT INTO {table}_room (uid, room) VALUES (?, ?)",
+            table = Self::table_name(),
+        );
+
+        conn(
+            move |c| {
+                c.execute(&query, &[&self.uid, room])
+                    .map(|_| ())
+                    .map_err(|e| err_msg(e.to_string()))
+            },
+            Err(err_msg("Connection not init")),
+        )
+    }
+
+    fn delete_relation(&self, room: &str) -> Result<usize, Error> {
+        let query = format!(
+            "DELETE from {table}_room WHERE uid = ? and room = ?",
+            table = Self::table_name()
+        );
+
+        conn(
+            move |c| {
+                c.execute(&query, &[self.get_id(), room])
+                    .map_err(|e| err_msg(e.to_string()))
+            },
+            Err(err_msg("Connection not init")),
+        )
+    }
+
+    fn delete_relations(room: &str) -> Result<usize, Error> {
+        let query = format!(
+            "DELETE from {table}_room WHERE room = ?",
+            table = Self::table_name()
+        );
+
+        conn(
+            move |c| {
+                c.execute(&query, &[room])
+                    .map_err(|e| err_msg(e.to_string()))
+            },
+            Err(err_msg("Connection not init")),
+        )
+    }
+
+    fn update_relation(&self, room: &str) -> Result<(), Error> {
+        self.delete_relation(room)?;
+        self.store_relation(room)
+    }
+
+    /// Returns a list of Members from filtering by `room` roomid ordered by
+    /// date
+    ///
+    /// The param `limit` defines the number of members to return, if it's
+    /// None, all members will be returned
+    ///
+    /// The param `offset` is used to ignore that number of members and start
+    /// to return from that. if it's None, the return will be done from the end
+    /// of the list.
+    fn get_range(room: &str, limit: Option<u32>, offset: Option<u32>) -> Result<Vec<Self>, Error> {
+        let fields = Self::fields().join(",");
+        let mut query = format!(
+            "SELECT {fields} FROM {table} INNER JOIN
+                {table}_room ON uid=id
+                WHERE room = ? ORDER BY uid desc",
+            fields = fields,
+            table = Self::table_name()
+        );
+
+        if let Some(l) = limit {
+            query = query + &format!(" LIMIT {}", l);
+        }
+
+        if let Some(o) = offset {
+            query = query + &format!(" OFFSET {}", o);
+        }
+
+        conn(
+            move |c| {
+                let mut stmt = c.prepare(&query)?;
+                let iter = stmt.query_map(&[room], Self::map_row)?;
+
+                let array = iter
+                    .filter(|r| r.is_ok())
+                    .map(|r| r.unwrap())
+                    .collect::<Vec<Self>>();
+                Ok(array)
+            },
+            Err(err_msg("Connection not init")),
+        )
+    }
+}
diff --git a/fractal-backend/src/model/mod.rs b/fractal-backend/src/model/mod.rs
index 2864fd87..f1c6bef7 100644
--- a/fractal-backend/src/model/mod.rs
+++ b/fractal-backend/src/model/mod.rs
@@ -4,9 +4,12 @@ use failure::Error;
 use rusqlite::Row;
 use rusqlite::NO_PARAMS;
 
+pub mod member;
 pub mod message;
 pub mod room;
 
+pub use self::member::Member;
+pub use self::member::MemberModel;
 pub use self::message::Message;
 pub use self::message::MessageModel;
 pub use self::room::Room;
@@ -66,6 +69,11 @@ pub trait Model: Sized {
         )
     }
 
+    fn update(&self) -> Result<(), Error> {
+        self.delete()?;
+        self.store()
+    }
+
     fn create_table() -> Result<usize, Error> {
         conn(
             move |c| {
diff --git a/fractal-backend/src/model/room.rs b/fractal-backend/src/model/room.rs
index 77171596..4b389bd5 100644
--- a/fractal-backend/src/model/room.rs
+++ b/fractal-backend/src/model/room.rs
@@ -44,7 +44,6 @@ impl Model for Room {
 
     fn create_sql() -> String {
         //TODO: implements relations for:
-        //  members: MemberList,
         //  inv_sender: Option<Member>,
         format!(
             "
diff --git a/fractal-backend/tests/models.rs b/fractal-backend/tests/models.rs
index 4289f088..684b41f9 100644
--- a/fractal-backend/tests/models.rs
+++ b/fractal-backend/tests/models.rs
@@ -2,6 +2,8 @@ extern crate chrono;
 extern crate fractal_backend;
 
 use fractal_backend::init_local as init;
+use fractal_backend::model::Member;
+use fractal_backend::model::MemberModel;
 use fractal_backend::model::Message;
 use fractal_backend::model::MessageModel;
 use fractal_backend::model::Model;
@@ -107,3 +109,101 @@ fn message_room_relation() {
     let items = Message::get_range(&r.id, Some(10), Some(100)).unwrap();
     assert_eq!(items.len(), 0);
 }
+
+#[test]
+fn member_model() {
+    let _ = init("").unwrap();
+
+    assert!(Room::create_table().is_ok());
+    let created = Member::create_table();
+    assert!(created.is_ok());
+
+    let m1 = Member {
+        uid: String::from("UID"),
+        alias: None,
+        avatar: None,
+    };
+    let m2 = Member {
+        uid: String::from("UID2"),
+        alias: None,
+        avatar: None,
+    };
+    let m3 = Member {
+        uid: String::from("UID3"),
+        alias: None,
+        avatar: None,
+    };
+    assert!(m1.store().is_ok());
+    assert!(m2.store().is_ok());
+    assert!(m3.store().is_ok());
+
+    let newm = Member::get("UID").unwrap();
+    assert_eq!(m1, newm);
+
+    let deleted = m1.delete();
+    assert!(deleted.is_ok());
+
+    let really_deleted = Member::get("UID");
+    assert!(really_deleted.is_err());
+}
+
+#[test]
+fn member_room_relation() {
+    let _ = init("").unwrap();
+
+    let created = Room::create_table();
+    assert!(created.is_ok());
+    let created = Member::create_table();
+    assert!(created.is_ok());
+    assert!(Member::create_relation_table().is_ok());
+
+    let r = Room::new("ROOM ID".to_string(), Some("ROOM NAME".to_string()));
+    let stored = r.store();
+    assert!(stored.is_ok());
+
+    let mut m = Member {
+        uid: String::from("UID"),
+        alias: None,
+        avatar: None,
+    };
+
+    for i in 0..100 {
+        m.uid = format!("USER {:04}", i);
+        assert!(m.store().is_ok());
+        assert!(m.store_relation("ROOM ID").is_ok());
+    }
+
+    for i in 0..100 {
+        m.uid = format!("USER ROOM2 {:04}", i);
+        assert!(m.store().is_ok());
+        assert!(m.store_relation("ROOM ID 2").is_ok());
+    }
+
+    for i in 0..10 {
+        let items = Member::get_range(&r.id, Some(10), Some(i * 10)).unwrap();
+        for (j, m) in items.iter().enumerate() {
+            let idx = 99 - (10 * i as usize + j);
+            assert_eq!(m.uid, format!("USER {:04}", idx));
+        }
+    }
+
+    let items = Member::get_range(&r.id, Some(10), Some(95)).unwrap();
+    assert_eq!(items.len(), 5);
+
+    let items = Member::get_range(&r.id, Some(10), Some(100)).unwrap();
+    assert_eq!(items.len(), 0);
+
+    let items = Member::get_range("ROOM ID 2", Some(10), None).unwrap();
+    assert_eq!(items.len(), 10);
+    for m in items {
+        assert!(m.delete_relation("ROOM ID 2").is_ok());
+    }
+
+    assert_eq!(
+        90,
+        Member::get_range("ROOM ID 2", None, None).unwrap().len()
+    );
+    assert!(Member::delete_relations("ROOM ID 2").is_ok());
+    assert_eq!(0, Member::get_range("ROOM ID 2", None, None).unwrap().len());
+    assert_eq!(100, Member::get_range("ROOM ID", None, None).unwrap().len());
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]