diff options
-rw-r--r-- | Cargo.lock | 2 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | README.md | 54 | ||||
-rw-r--r-- | TODO | 2 | ||||
-rw-r--r-- | src/database.rs | 84 | ||||
-rw-r--r-- | src/database/types.rs | 79 | ||||
-rw-r--r-- | src/main.rs | 18 |
7 files changed, 223 insertions, 18 deletions
@@ -1143,7 +1143,7 @@ dependencies = [ [[package]] name = "rocket_test" -version = "0.2.4" +version = "0.2.5" dependencies = [ "chrono", "rocket", @@ -1,6 +1,6 @@ [package] name = "rocket_test" -version = "0.2.4" +version = "0.2.5" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -25,6 +25,13 @@ DELETE `/delete_user` - Return type: JSON - Returns: `Result` +POST `set_user_data` + Sets data in the user fields +- Data type: JSON +- Data: `{user: UID, field: ReceiveDataField, data: String}` +- Return type: JSON +- Returns: `Result` + GET `/get_user/<id>` Gets a user from id - Return type: JSON @@ -60,10 +67,7 @@ GET `/get_message_list/<from_id>/<to_id>` POST `/send_message` Sends a message - Data type: JSON -- Data: - - `"sender": u64` - - `"message": String` - - `"reply_to": Option<u64>` +- Data: ReceiveMessage - Return type: JSON - Returns: `UID` of message @@ -72,4 +76,44 @@ DELETE `/delete_message` - Data type: JSON - Data: `UID` - Return type: JSON -- Returns: `Result`
\ No newline at end of file +- Returns: `Result` + +# Types +- ReceiveDataField + - DisplayName + - StatusText + - Desc + +- ReceiveMessage + - message: String + - sender: UID + - reply_to: Option<UID> + +- UID + - u64 + +- Date + - u64 (date since epoch in UTC) + +- Status + - Online + - Offline + - Away + - Busy + +- User + - id: UID + - username: String (Max 20 chars) + - displayname: String (Max 30 chars) + - desc: String (Max 500 chars) + - statustext: String (Max 60 chars) + - status: Status + - deleted: bool + +- Message + - id: UID + - message: String (Max 6000 chars) + - reply_to: UID (of another message) + - deleted: bool + - date: Date + - sender: UID (of a User)
\ No newline at end of file @@ -10,4 +10,4 @@ Config /api - list functions/routes -Delete is a bit funky - doesn't always report correctly
\ No newline at end of file +Delete + set_set_data is a bit funky - doesn't always report correctly
\ No newline at end of file diff --git a/src/database.rs b/src/database.rs index 0faa0d3..74d9c1a 100644 --- a/src/database.rs +++ b/src/database.rs @@ -168,21 +168,90 @@ impl Database { } } + pub fn set_user_field(&self, f: DataField, user: UID, data: String) -> Result<&'static str, &'static str> { + let query = format!("UPDATE users SET {}=:data WHERE id IS :id AND deleted IS false", f); + let statement = self.db.prepare(query).unwrap().into_iter() + .bind::<&[(_, sqlite::Value)]>(&[ + (":id", user.into()), + (":data", data.into()), + ]); + + let change_count = self.db.change_count(); + + for x in statement.unwrap() { + match x { + Ok(_) => (), + Err(n) => { + match n.code.unwrap_or(0) { + _ => return Err("Unknown error") + } + }, + } + } + + if change_count > 0 { + Ok("Updated") + } else { + Err("Unabled to update") + } + } + + pub fn set_user_status(&self, user: UID, status: Status) -> Result<&'static str, &'static str> { + let query = "UPDATE users SET status=:status WHERE id IS :id AND deleted IS false"; + let statement = self.db.prepare(query).unwrap().into_iter() + .bind::<&[(_, sqlite::Value)]>(&[ + (":id", user.into()), + (":status", i64::from(status).into()), + ]); + + let change_count = self.db.change_count(); + + for x in statement.unwrap() { + match x { + Ok(_) => (), + Err(n) => { + match n.code.unwrap_or(0) { + _ => return Err("Unknown error") + } + }, + } + } + + if change_count > 0 { + Ok("Updated") + } else { + Err("Unabled to update") + } + } + pub fn get_user(&self, id: UID) -> Option<User> { let query = "SELECT * FROM users WHERE id IS :id"; let statement = self.db.prepare(query).unwrap().into_iter().bind::<&[(_, sqlite::Value)]>(&[(":id", id.into())]).unwrap(); for row in statement.map(|row| row.unwrap()) { - let username = row.read::<&str, _>("username"); + let username = row.read::<&str, _>("username").to_string(); let id = row.read::<i64, _>("id").into(); + let desc = match row.read::<Option<&str>, _>("desc") { + Some(n) => n.to_string(), + None => "".to_string(), + }; + let displayname = match row.read::<Option<&str>, _>("displayname") { + Some(n) => n.to_string(), + None => "".to_string(), + }; + let statustext = match row.read::<Option<&str>, _>("statustext") { + Some(n) => n.to_string(), + None => "".to_string(), + }; + let status = row.read::<i64, _>("status"); let deleted = if row.read::<i64, _>("deleted") > 0 { true } else { false }; - return Some(User::construct(String::from(username), id, deleted)) + return Some(User::construct(String::from(username), id, deleted, desc, displayname, statustext, status)) } None @@ -213,7 +282,7 @@ impl Database { let id: UID = self.get_user_count().into(); - let query = "INSERT INTO users (id, username, deleted) VALUES (:id, :name, false)"; + let query = "INSERT INTO users (id, username, deleted, status, displayname) VALUES (:id, :name, false, 0, :name)"; let statement = self.db.prepare(query).unwrap().into_iter() .bind::<&[(_, sqlite::Value)]>(&[(":id", id.into()),(":name", name.into())]); @@ -291,13 +360,18 @@ impl Database { }; // Setup the db - let query = "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY NOT NULL, username UNIQUE NOT NULL, deleted BOOL NOT NULL) WITHOUT ROWID"; + match db.execute("PRAGMA foreign_keys = ON") { + Ok(_) => (), + Err(_) => panic!("Could not enable foreign_keys"), + }; + + let query = "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY NOT NULL, username TEXT UNIQUE NOT NULL CHECK (length(username) <= 20), deleted BOOL NOT NULL, displayname TEXT CHECK (length(displayname) <= 30), desc TEXT CHECK (length(desc) <= 500), statustext TEXT CHECK (length(statustext) <= 60), status INTEGER NOT NULL) WITHOUT ROWID"; match db.execute(query) { Ok(_) => (), Err(n) => panic!("{n}"), } - let query = "CREATE TABLE IF NOT EXISTS messages (id INTEGER PRIMARY KEY NOT NULL, sender INTEGER NOT NULL, date INTEGER NOT NULL, message TEXT, reply_to INTEGER, deleted BOOL NOT NULL) WITHOUT ROWID"; + let query = "CREATE TABLE IF NOT EXISTS messages (id INTEGER PRIMARY KEY NOT NULL, sender INTEGER NOT NULL, date INTEGER NOT NULL, message TEXT CHECK (length(message) <= 6000), reply_to INTEGER, deleted BOOL NOT NULL, FOREIGN KEY (sender) REFERENCES users(id), FOREIGN KEY (reply_to) REFERENCES messages(id)) WITHOUT ROWID"; match db.execute(query) { Ok(_) => (), Err(n) => panic!("{n}"), diff --git a/src/database/types.rs b/src/database/types.rs index 96efd7b..4656e49 100644 --- a/src/database/types.rs +++ b/src/database/types.rs @@ -28,26 +28,91 @@ impl std::convert::From<Date> for sqlite::Value { } } +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub enum Status { + Offline, + Online, + Away, + Busy, +} +impl std::convert::From<Status> for i64 { + fn from(s: Status) -> i64 { + match s { + Status::Offline => 0, + Status::Online => 1, + Status::Away => 2, + Status::Busy => 3, + } + } +} +impl std::convert::From<i64> for Status { + fn from(u: i64) -> Status { + match u { + 1 => Status::Online, + 2 => Status::Away, + 3 => Status::Busy, + _ => Status::Offline + } + } +} + +#[derive(Deserialize, Copy, Clone)] +pub enum DataField { + Desc, + StatusText, + DisplayName, +} +impl std::convert::From<DataField> for &str { + fn from(f: DataField) -> &'static str { + match f { + DataField::Desc => "desc", + DataField::StatusText => "statustext", + DataField::DisplayName => "displayname", + } + } +} +impl std::fmt::Display for DataField { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DataField::Desc => write!(f, "desc"), + DataField::StatusText => write!(f, "statustext"), + DataField::DisplayName => write!(f, "displayname"), + } + } +} + #[derive(Serialize, Clone, Debug)] pub struct User { - username: String, + username: String, //Limit 20 id: UID, deleted: bool, + desc: String, // Limit 500 + displayname: String, // Limit 30 + statustext: String, //Limit 60 + status: Status, } impl User { pub fn new(username: String, id: UID) -> User { User { - username, + username: username.clone(), id, deleted: false, + desc: String::from(""), + displayname: username, + statustext: String::from(""), + status: Status::Offline, } } - pub fn construct(username: String, id: UID, deleted: bool) -> User { + pub fn construct(username: String, id: UID, deleted: bool, desc: String, displayname: String, statustext: String, status: i64) -> User { User { username, id, deleted, + desc, + displayname, + statustext, + status: Status::from(status), } } @@ -163,4 +228,10 @@ impl ReceiveMessage { date: Date::now(), } } -}
\ No newline at end of file +} + +#[derive(Deserialize, Clone)] +pub struct ReceiveUpdateField {pub user: UID, pub field: DataField, pub data: String} + +#[derive(Deserialize, Clone)] +pub struct ReceiveStatus {pub user: UID, pub status: Status}
\ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 1955963..af0234a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ mod database; use rocket::serde::json::Json; use rocket::State; use std::sync::Mutex; -use database::types::{Message, User, Info, UID, ReceiveMessage}; +use database::types::*; struct SharedDB { @@ -119,6 +119,20 @@ fn delete_user(id: Json<UID>, db: &State<SharedDB>) -> Json<Response<&'static st Json(Response(lock.delete_user(id.0))) } +#[post("/set_user_data", format = "application/json", data = "<data>")] +fn set_user_data(data: Json<ReceiveUpdateField>, db: &State<SharedDB>) -> Json<Response<&'static str, &'static str>> { + let lock = db.sdb.lock().unwrap(); + + Json(Response(lock.set_user_field(data.field, data.user, data.data.clone()))) +} + +#[post("/set_user_status", format = "application/json", data = "<data>")] +fn set_user_status(data: Json<ReceiveStatus>, db: &State<SharedDB>) -> Json<Response<&'static str, &'static str>> { + let lock = db.sdb.lock().unwrap(); + + Json(Response(lock.set_user_status(data.user, data.status))) +} + #[get("/ping")] fn ping() -> Json<Response<&'static str, &'static str>> { Json(Response(Ok("pong"))) @@ -151,6 +165,8 @@ fn rocket() -> _ { delete_message, delete_user, get_message_list, + set_user_data, + set_user_status, ]) .mount("/", routes![api_index]) .manage(SharedDB{sdb: Mutex::new(database::Database::new())}) |