// Webserver #[macro_use] extern crate rocket; 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<::Model> for Member { fn from(member: ::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(), } } } #[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, minimalMemberWithoutId: 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(minimalMemberWithoutId.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 { ntnu_username: ActiveValue::Set(minimalMemberWithoutId.ntnuUsername.to_owned()), first_name: ActiveValue::Set(minimalMemberWithoutId.firstName.to_owned()), last_name: ActiveValue::Set(minimalMemberWithoutId.lastName.to_owned()), email: ActiveValue::Set(minimalMemberWithoutId.email.to_owned()), balance: ActiveValue::Set(0), image_preference: ActiveValue::Set("Money".to_string()), ..Default::default() }; // 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 { id: new_member.id, ntnuUsername: new_member.ntnu_username, firstName: new_member.first_name, lastName: new_member.last_name, email: new_member.email, }; Ok(Json(member)) } #[get("/member/")] async fn get_member_by_id(db: &State, memberId: i32) -> Result, ErrorResponder> { let db = db as &DatabaseConnection; let database_member; match Members::find_by_id(memberId).one(db).await? { Some(model) => database_member = model, None => { return Err( ErrorResponder { message: format!("Failed to fetch member by ID. memberId: {}", memberId) } ); }, }; let member_rfid_cards: Vec = database_member.find_related(RfidCards).all(db).await?; let rfids: Vec = member_rfid_cards.iter().map(|model| RfidCard { cardId: model.card_id.clone(), cardComment: model.card_comment.clone() }).collect(); let member = Member { id: database_member.id, ntnuUsername: database_member.ntnu_username, firstName: database_member.first_name, lastName: database_member.last_name, email: database_member.email, balance: database_member.balance, imagePreference: database_member.image_preference, rfidCards: rfids }; Ok(Json(member)) } #[launch] async fn rocket() -> _ { let db = match set_up_db().await { Ok(db) => db, Err(err) => panic!("{}", err) }; rocket::build() .manage(db) .mount("/", routes![ index, get_member_by_id, add_member, get_members ]) }