diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..868d3ca --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "rust-analyzer.linkedProjects": [ + "./Cargo.toml", + "./Cargo.toml", + "./Cargo.toml" + ] +} \ No newline at end of file diff --git a/src/setup.rs b/src/database.rs similarity index 100% rename from src/setup.rs rename to src/database.rs diff --git a/src/main.rs b/src/main.rs index 6f6a7ea..845b563 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,248 +1,11 @@ // Webserver #[macro_use] extern crate rocket; +mod database; +pub mod models; +use database::set_up_db; -use entities::members::ActiveModel; -use rocket::{State, Error}; -use rocket::response::status; -use rocket::serde::{Serialize, Deserialize, json::Json}; - -// Database -use sea_orm::*; - -mod setup; -use setup::set_up_db; - -mod entities; -use entities::{prelude::*, *}; - -#[derive(Serialize, Deserialize)] -struct RfidCard { - cardId: String, - cardComment: String -} - -// Implement `.into()` to auto map values from the database model to the local struct -impl From<::Model> for RfidCard { - fn from(rfid_card: ::Model) -> RfidCard { - RfidCard { - cardId: rfid_card.card_id, - cardComment: rfid_card.card_comment, - } - } -} - -#[derive(Serialize, Deserialize)] -struct Member { - id: i32, - ntnuUsername: String, - firstName: String, - lastName: String, - email: String, - balance: i32, - imagePreference: String, - rfidCards: Vec -} - -// Create the `.into()` functionality to auto map values from the database model to the local struct -// Don't be scared by the long model name, I typed `Members::Model` and applied the auto fix vscode suggested -impl From for Member { - fn from(member: entities::members::Model) -> Member { - Member { - id: member.id, - ntnuUsername: member.ntnu_username, - firstName: member.first_name, - lastName: member.last_name, - email: member.email, - balance: member.balance, - imagePreference: member.image_preference, - rfidCards: Vec::new(), - } - } -} - -// Implement DB Member Model -> MinimalMember -impl From for MinimalMember { - fn from(member: entities::members::Model) -> MinimalMember { - MinimalMember { - id: member.id, - ntnuUsername: member.ntnu_username, - firstName: member.first_name, - lastName: member.last_name, - email: member.email, - } - } -} - -// Create the functionality to create a database model member from the local struct model member -impl Into for rocket::serde::json::Json { - fn into(self) -> entities::members::ActiveModel { - entities::members::ActiveModel { - ntnu_username: ActiveValue::Set(self.ntnuUsername.to_owned()), - first_name: ActiveValue::Set(self.firstName.to_owned()), - last_name: ActiveValue::Set(self.lastName.to_owned()), - email: ActiveValue::Set(self.email.to_owned()), - balance: ActiveValue::Set(0), - image_preference: ActiveValue::Set("".to_string()), - ..Default::default() - } - } -} - -#[derive(Serialize, Deserialize)] -struct MinimalMember { - id: i32, - ntnuUsername: String, - firstName: String, - lastName: String, - email: String, -} - -#[derive(Serialize, Deserialize)] -struct MinimalMemberWithoutId { - ntnuUsername: String, - firstName: String, - lastName: String, - email: String, -} - -#[derive(Serialize)] -struct MultipleMembersStruct { - members: Vec, -} - -#[derive(Responder)] -#[response(status = 500, content_type = "text/plain")] -struct ErrorResponder { - message: String -} - -impl From for ErrorResponder { - fn from(err: DbErr) -> ErrorResponder { - ErrorResponder { message: err.to_string() } - } -} -impl From for ErrorResponder { - fn from(string: String) -> ErrorResponder { - ErrorResponder { message: string } - } -} -impl From<&str> for ErrorResponder { - fn from(str: &str) -> ErrorResponder { - ErrorResponder { message: str.to_owned().into() } - } -} - -#[get("/")] -fn index() -> &'static str { - "Hello, world!\nNothing useful is served here." -} - -#[get("/members")] -async fn get_members(db: &State) -> Result, ErrorResponder> { - let db = db as &DatabaseConnection; - - // Get a list of all members from the database - let members = Members::find().all(db).await?; - - // Convert this vector from the database model to the local struct model - let mut members: Vec = members.into_iter().map(Member::from).collect(); - - // Fetch the member's RFID card - for member in &mut members { - // Look up all RFID cards associated with this member - let rfid_cards = RfidCards::find() - .filter(rfid_cards::Column::MemberId.eq(member.id)) - .all(db) - .await?; - - // Convert the RFID cards vector from the database model to the local struct model - let rfid_cards: Vec = rfid_cards.into_iter().map(RfidCard::from).collect(); - - // Add these cards to the member's rfidCards field - member.rfidCards = rfid_cards; - } - - - // Put this into the `MultipleMembersStruct` so that the aquired JSON will look as the API demands - let output = MultipleMembersStruct { members }; - - // Return the output - Ok(Json(output)) -} - -#[post("/member", data = "")] -async fn add_member(db: &State, minimal_member_without_id: Json) -> Result, ErrorResponder> { - // Grab the database connection - let db = db as &DatabaseConnection; - - // Check if a member with the same NTNU username already exists - let matching_member: Option = Members::find() // Find a member in the "Members" table - .filter(members::Column::NtnuUsername.eq(minimal_member_without_id.ntnuUsername.to_owned())) // Filter by the provided username in the NtnuUsername column - .one(db) // We only care about one result - .await?; // Wait for the result - - // If a member exists return an error - if matching_member.is_some() { - return Err( - ErrorResponder { - message: "A member with this NTNU username already exists".to_string(), - } - ) - } - - // Create the new member info from the provided JSON - let new_member: members::ActiveModel = minimal_member_without_id.into(); - - // Add the new member to the database - let res = Members::insert(new_member).exec(db).await?; - - // Fetch the member's info back from the DB to verify - let new_member; - match Members::find_by_id(res.last_insert_id).one(db).await? { - Some(model) => new_member = model, - None => { - return Err( - ErrorResponder { - message: format!("Failed to fetch member for verification of creating new member with ID. memberId: {}", res.last_insert_id) - } - ); - }, - } - - // Put the fetched info into a minimal member for returning - let member: MinimalMember = new_member.into(); - - Ok(Json(member)) -} - -#[get("/member/")] -async fn get_member_by_id(db: &State, memberId: i32) -> Result, ErrorResponder> { - let db = db as &DatabaseConnection; - - // Create an empty variable to store the resulting member info - let database_member; - // Search the database for the member based on the ID - match Members::find_by_id(memberId).one(db).await? { - Some(model) => database_member = model, // If the member is found, add the info to the previously created variable - None => { - return Err( - ErrorResponder { - message: format!("Failed to fetch member by ID. memberId: {}", memberId) // If it is not found return with this error - } - ); - }, - }; - - // Get the member's RFID cards - let member_rfid_cards: Vec = database_member.find_related(RfidCards).all(db).await?; - let rfids: Vec = member_rfid_cards.into_iter().map(RfidCard::from).collect(); - - // Create a member from the database model and append the RFID cards - let mut member: Member = database_member.into(); - member.rfidCards = rfids; - - Ok(Json(member)) -} +mod webserver_member; +use webserver_member::*; #[launch] async fn rocket() -> _ { diff --git a/src/entities/members.rs b/src/models/entities/members.rs similarity index 100% rename from src/entities/members.rs rename to src/models/entities/members.rs diff --git a/src/entities/mod.rs b/src/models/entities/mod.rs similarity index 100% rename from src/entities/mod.rs rename to src/models/entities/mod.rs diff --git a/src/entities/prelude.rs b/src/models/entities/prelude.rs similarity index 100% rename from src/entities/prelude.rs rename to src/models/entities/prelude.rs diff --git a/src/entities/rfid_cards.rs b/src/models/entities/rfid_cards.rs similarity index 100% rename from src/entities/rfid_cards.rs rename to src/models/entities/rfid_cards.rs diff --git a/src/models/member.rs b/src/models/member.rs new file mode 100644 index 0000000..a602bce --- /dev/null +++ b/src/models/member.rs @@ -0,0 +1,99 @@ +use rocket::serde::{Serialize, Deserialize}; +use sea_orm::*; + +use super::entities::{prelude::*, *}; + +#[derive(Serialize, Deserialize)] +pub struct RfidCard { + pub cardId: String, + pub cardComment: String +} + +// Implement `.into()` to auto map values from the database model to the local struct +impl From for RfidCard { + fn from(rfid_card: rfid_cards::Model) -> RfidCard { + RfidCard { + cardId: rfid_card.card_id, + cardComment: rfid_card.card_comment, + } + } +} + +#[derive(Serialize, Deserialize)] +pub struct Member { + pub id: i32, + pub ntnuUsername: String, + pub firstName: String, + pub lastName: String, + pub email: String, + pub balance: i32, + pub imagePreference: String, + pub rfidCards: Vec +} + +// Create the `.into()` functionality to auto map values from the database model to the local struct +// Don't be scared by the long model name, I typed `Members::Model` and applied the auto fix vscode suggested +impl From for Member { + fn from(member: members::Model) -> Member { + Member { + id: member.id, + ntnuUsername: member.ntnu_username, + firstName: member.first_name, + lastName: member.last_name, + email: member.email, + balance: member.balance, + imagePreference: member.image_preference, + rfidCards: Vec::new(), + } + } +} + +// Implement DB Member Model -> MinimalMember +impl From for MinimalMember { + fn from(member: members::Model) -> MinimalMember { + MinimalMember { + id: member.id, + ntnuUsername: member.ntnu_username, + firstName: member.first_name, + lastName: member.last_name, + email: member.email, + } + } +} + +// Create the functionality to create a database model member from the local struct model member +impl Into for rocket::serde::json::Json { + fn into(self) -> members::ActiveModel { + members::ActiveModel { + ntnu_username: ActiveValue::Set(self.ntnuUsername.to_owned()), + first_name: ActiveValue::Set(self.firstName.to_owned()), + last_name: ActiveValue::Set(self.lastName.to_owned()), + email: ActiveValue::Set(self.email.to_owned()), + balance: ActiveValue::Set(0), + image_preference: ActiveValue::Set("".to_string()), + ..Default::default() + } + } +} + +#[derive(Serialize, Deserialize)] +pub struct MinimalMember { + pub id: i32, + pub ntnuUsername: String, + pub firstName: String, + pub lastName: String, + pub email: String, +} + +#[derive(Serialize, Deserialize)] +pub struct MinimalMemberWithoutId { + pub ntnuUsername: String, + pub firstName: String, + pub lastName: String, + pub email: String, +} + +#[derive(Serialize)] +pub struct MultipleMembersStruct { + pub members: Vec, +} diff --git a/src/models/mod.rs b/src/models/mod.rs new file mode 100644 index 0000000..9eb1269 --- /dev/null +++ b/src/models/mod.rs @@ -0,0 +1,2 @@ +pub mod member; +pub mod entities; \ No newline at end of file diff --git a/src/webserver_member.rs b/src/webserver_member.rs new file mode 100644 index 0000000..0bcbeee --- /dev/null +++ b/src/webserver_member.rs @@ -0,0 +1,148 @@ +// Import my models +// This feels like a really hacky way to import stuff, but it works +mod models { + include!("models/mod.rs"); +} +use crate::webserver_member::models::member::*; +use crate::webserver_member::models::entities::{prelude::*, *}; + +// Webserver functions +use rocket::State; +use rocket::serde::json::Json; + +// DB functions +use sea_orm::*; + +#[derive(Responder)] +#[response(status = 500, content_type = "text/plain")] +struct ErrorResponder { + message: String +} + +impl From for ErrorResponder { + fn from(err: DbErr) -> ErrorResponder { + ErrorResponder { message: err.to_string() } + } +} +impl From for ErrorResponder { + fn from(string: String) -> ErrorResponder { + ErrorResponder { message: string } + } +} +impl From<&str> for ErrorResponder { + fn from(str: &str) -> ErrorResponder { + ErrorResponder { message: str.to_owned().into() } + } +} + +#[get("/")] +pub fn index() -> &'static str { + "Hello, world!\nNothing useful is served here." +} + +#[get("/members")] +pub async fn get_members(db: &State) -> Result, ErrorResponder> { + let db = db as &DatabaseConnection; + + // Get a list of all members from the database + let members = Members::find().all(db).await?; + + // Convert this vector from the database model to the local struct model + let mut members: Vec = members.into_iter().map(Member::from).collect(); + + // Fetch the member's RFID card + for member in &mut members { + // Look up all RFID cards associated with this member + let rfid_cards = RfidCards::find() + .filter(rfid_cards::Column::MemberId.eq(member.id)) + .all(db) + .await?; + + // Convert the RFID cards vector from the database model to the local struct model + let rfid_cards: Vec = rfid_cards.into_iter().map(RfidCard::from).collect(); + + // Add these cards to the member's rfidCards field + member.rfidCards = rfid_cards; + } + + + // Put this into the `MultipleMembersStruct` so that the aquired JSON will look as the API demands + let output = MultipleMembersStruct { members }; + + // Return the output + Ok(Json(output)) +} + +#[post("/member", data = "")] +pub async fn add_member(db: &State, minimal_member_without_id: Json) -> Result, ErrorResponder> { + // Grab the database connection + let db = db as &DatabaseConnection; + + // Check if a member with the same NTNU username already exists + let matching_member: Option = Members::find() // Find a member in the "Members" table + .filter(members::Column::NtnuUsername.eq(minimal_member_without_id.ntnuUsername.to_owned())) // Filter by the provided username in the NtnuUsername column + .one(db) // We only care about one result + .await?; // Wait for the result + + // If a member exists return an error + if matching_member.is_some() { + return Err( + ErrorResponder { + message: "A member with this NTNU username already exists".to_string(), + } + ) + } + + // Create the new member info from the provided JSON + let new_member: members::ActiveModel = minimal_member_without_id.into(); + + // Add the new member to the database + let res = Members::insert(new_member).exec(db).await?; + + // Fetch the member's info back from the DB to verify + let new_member; + match Members::find_by_id(res.last_insert_id).one(db).await? { + Some(model) => new_member = model, + None => { + return Err( + ErrorResponder { + message: format!("Failed to fetch member for verification of creating new member with ID. memberId: {}", res.last_insert_id) + } + ); + }, + } + + // Put the fetched info into a minimal member for returning + let member: MinimalMember = new_member.into(); + + Ok(Json(member)) +} + +#[get("/member/")] +pub async fn get_member_by_id(db: &State, memberId: i32) -> Result, ErrorResponder> { + let db = db as &DatabaseConnection; + + // Create an empty variable to store the resulting member info + let database_member; + // Search the database for the member based on the ID + match Members::find_by_id(memberId).one(db).await? { + Some(model) => database_member = model, // If the member is found, add the info to the previously created variable + None => { + return Err( + ErrorResponder { + message: format!("Failed to fetch member by ID. memberId: {}", memberId) // If it is not found return with this error + } + ); + }, + }; + + // Get the member's RFID cards + let member_rfid_cards: Vec = database_member.find_related(RfidCards).all(db).await?; + let rfids: Vec = member_rfid_cards.into_iter().map(RfidCard::from).collect(); + + // Create a member from the database model and append the RFID cards + let mut member: Member = database_member.into(); + member.rfidCards = rfids; + + Ok(Json(member)) +} \ No newline at end of file