Compare commits

..

4 Commits

Author SHA1 Message Date
Sebastian H. Gabrielli
bf84ff6e33 Implement functionality to check for existing member with NTNU username 2023-12-24 00:27:18 +01:00
Sebastian H. Gabrielli
48bb2ab839 Create and implement add_member function 2023-12-24 00:18:33 +01:00
Sebastian H. Gabrielli
35971d14d1 Rename get_member to get_member_by_id 2023-12-23 23:40:06 +01:00
Sebastian H. Gabrielli
72f73a89e1 Create database link and implement get_member 2023-12-23 23:39:23 +01:00
9 changed files with 1912 additions and 31 deletions

1653
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -8,3 +8,5 @@ edition = "2021"
[dependencies]
rocket = {version = "0.5.0", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
sea-orm = { version = "^0.12.0", features = [ "sqlx-sqlite", "runtime-tokio-native-tls", "macros", "mock" ] }
futures = "0.3.28"

34
src/entities/members.rs Normal file
View File

@ -0,0 +1,34 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "members")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: i32,
#[sea_orm(column_name = "ntnuUsername")]
pub ntnu_username: String,
#[sea_orm(column_name = "firstName")]
pub first_name: String,
#[sea_orm(column_name = "lastName")]
pub last_name: String,
pub email: String,
pub balance: i32,
#[sea_orm(column_name = "imagePreference")]
pub image_preference: String,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::rfid_cards::Entity")]
RfidCards,
}
impl Related<super::rfid_cards::Entity> for Entity {
fn to() -> RelationDef {
Relation::RfidCards.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

6
src/entities/mod.rs Normal file
View File

@ -0,0 +1,6 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
pub mod prelude;
pub mod members;
pub mod rfid_cards;

4
src/entities/prelude.rs Normal file
View File

@ -0,0 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
pub use super::members::Entity as Members;
pub use super::rfid_cards::Entity as RfidCards;

View File

@ -0,0 +1,33 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "rfid_cards")]
pub struct Model {
#[sea_orm(column_name = "cardId", primary_key, auto_increment = false)]
pub card_id: String,
#[sea_orm(column_name = "cardComment")]
pub card_comment: String,
pub member_id: Option<i32>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::members::Entity",
from = "Column::MemberId",
to = "super::members::Column::Id",
on_update = "NoAction",
on_delete = "NoAction"
)]
Members,
}
impl Related<super::members::Entity> for Entity {
fn to() -> RelationDef {
Relation::Members.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -1,9 +1,18 @@
// 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 {
@ -13,40 +22,167 @@ struct RfidCard {
#[derive(Serialize, Deserialize)]
struct Member {
id: u64,
id: i32,
ntnuUsername: String,
firstName: String,
lastName: String,
email: String,
balance: i64,
balance: i32,
imagePreference: String,
rfidCards: Vec<RfidCard>
}
#[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(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("/member/<memberId>")]
fn get_member(memberId: u64) -> Json<Member> {
let sebasthg = Member {
id: 1,
ntnuUsername: "sebasthg".to_string(),
firstName: "Sebastian".to_string(),
lastName: "Gabrielli".to_string(),
email: "sebastian@fastmail.mx".to_string(),
balance: 50,
imagePreference: "Money".to_string(),
rfidCards: vec![ RfidCard { cardId: "0364249683".to_string(), cardComment: "Studentkort".to_string() } ]
#[post("/member", data = "<minimalMemberWithoutId>")]
async fn add_member(db: &State<DatabaseConnection>, minimalMemberWithoutId: 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(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()
};
Json(sebasthg)
// 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/<memberId>")]
async fn get_member_by_id(db: &State<DatabaseConnection>, memberId: i32) -> Result<Json<Member>, 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<rfid_cards::Model> = database_member.find_related(RfidCards).all(db).await?;
let rfids: Vec<RfidCard> = 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]
fn rocket() -> _ {
async fn rocket() -> _ {
let db = match set_up_db().await {
Ok(db) => db,
Err(err) => panic!("{}", err)
};
rocket::build()
.mount("/", routes![index])
.mount("/", routes![get_member])
.manage(db)
.mount("/", routes![
index,
get_member_by_id,
add_member
])
}

39
src/setup.rs Normal file
View File

@ -0,0 +1,39 @@
use sea_orm::*;
const DATABASE_URL: &str = "sqlite:./test.db";
const DB_NAME: &str = "omegav";
pub(super) async fn set_up_db() -> Result<DatabaseConnection, DbErr> {
let db = Database::connect(DATABASE_URL).await?;
let db = match db.get_database_backend() {
DbBackend::MySql => {
db.execute(Statement::from_string(
db.get_database_backend(),
format!("CREATE DATABASE IF NOT EXISTS `{}`;", DB_NAME),
))
.await?;
let url = format!("{}/{}", DATABASE_URL, DB_NAME);
Database::connect(&url).await?
}
DbBackend::Postgres => {
db.execute(Statement::from_string(
db.get_database_backend(),
format!("DROP DATABASE IF EXISTS \"{}\";", DB_NAME),
))
.await?;
db.execute(Statement::from_string(
db.get_database_backend(),
format!("CREATE DATABASE \"{}\";", DB_NAME),
))
.await?;
let url = format!("{}/{}", DATABASE_URL, DB_NAME);
Database::connect(&url).await?
}
DbBackend::Sqlite => db,
};
Ok(db)
}

BIN
test.db

Binary file not shown.