Compare commits

..

2 Commits

Author SHA1 Message Date
Sebastian H. Gabrielli
9d6e27415e Add README.md 2023-12-25 15:47:08 +01:00
Sebastian H. Gabrielli
57191c043e Split project into separate files 2023-12-25 15:40:00 +01:00
11 changed files with 281 additions and 242 deletions

7
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,7 @@
{
"rust-analyzer.linkedProjects": [
"./Cargo.toml",
"./Cargo.toml",
"./Cargo.toml"
]
}

20
README.md Normal file
View File

@ -0,0 +1,20 @@
# Rocket server test
This is a test of a webserver honoring the sample OmegaV V2 members API.
## File structure
```
├── Cargo.toml ................... Dependencies
├── README.md ................... This file :D
├── src .......................... Source files
│   ├── database.rs .............. Database connection
│   ├── main.rs .................. Running the webserver and attaching the endpoints
│   ├── models ................... Define the structs, both custom ones and DB ones
│   │   ├── entities ............. DB Structs, generated by SeaORM
│   │   │   ├── members.rs
│   │   │   ├── mod.rs
│   │   │   ├── prelude.rs
│   │   │   └── rfid_cards.rs
│   │   ├── member.rs ............ My custom structs relating to member functionality
│   │   └── mod.rs ............... Allow for importing using the `mod` keyword
│   └── webserver_member.rs ...... The webserver functions relating to the member functionality
```

View File

@ -1,248 +1,11 @@
// Webserver // Webserver
#[macro_use] extern crate rocket; #[macro_use] extern crate rocket;
mod database;
pub mod models;
use database::set_up_db;
use entities::members::ActiveModel; mod webserver_member;
use rocket::{State, Error}; use webserver_member::*;
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<<entities::rfid_cards::Entity as sea_orm::EntityTrait>::Model> for RfidCard {
fn from(rfid_card: <entities::rfid_cards::Entity as sea_orm::EntityTrait>::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<RfidCard>
}
// 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<entities::members::Model> 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<entities::members::Model> 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<entities::members::ActiveModel> for rocket::serde::json::Json<MinimalMemberWithoutId> {
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<Member>,
}
#[derive(Responder)]
#[response(status = 500, content_type = "text/plain")]
struct ErrorResponder {
message: String
}
impl From<DbErr> for ErrorResponder {
fn from(err: DbErr) -> ErrorResponder {
ErrorResponder { message: err.to_string() }
}
}
impl From<String> 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<DatabaseConnection>) -> Result<Json<MultipleMembersStruct>, 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<Member> = 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<RfidCard> = 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 = "<minimal_member_without_id>")]
async fn add_member(db: &State<DatabaseConnection>, minimal_member_without_id: Json<MinimalMemberWithoutId>) -> Result<Json<MinimalMember>, 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::Model> = 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/<memberId>")]
async fn get_member_by_id(db: &State<DatabaseConnection>, memberId: i32) -> Result<Json<Member>, 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<rfid_cards::Model> = database_member.find_related(RfidCards).all(db).await?;
let rfids: Vec<RfidCard> = 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))
}
#[launch] #[launch]
async fn rocket() -> _ { async fn rocket() -> _ {

99
src/models/member.rs Normal file
View File

@ -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<rfid_cards::Model> 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<RfidCard>
}
// 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<members::Model> 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<members::Model> 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<members::ActiveModel> for rocket::serde::json::Json<MinimalMemberWithoutId> {
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<Member>,
}

2
src/models/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod member;
pub mod entities;

148
src/webserver_member.rs Normal file
View File

@ -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<DbErr> for ErrorResponder {
fn from(err: DbErr) -> ErrorResponder {
ErrorResponder { message: err.to_string() }
}
}
impl From<String> 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<DatabaseConnection>) -> Result<Json<MultipleMembersStruct>, 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<Member> = 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<RfidCard> = 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 = "<minimal_member_without_id>")]
pub async fn add_member(db: &State<DatabaseConnection>, minimal_member_without_id: Json<MinimalMemberWithoutId>) -> Result<Json<MinimalMember>, 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::Model> = 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/<memberId>")]
pub async fn get_member_by_id(db: &State<DatabaseConnection>, memberId: i32) -> Result<Json<Member>, 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<rfid_cards::Model> = database_member.find_related(RfidCards).all(db).await?;
let rfids: Vec<RfidCard> = 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))
}