Add get_person route

This commit is contained in:
Ben Grant 2023-05-13 16:30:16 +10:00
parent 7770ab958a
commit d2a94b078c
8 changed files with 164 additions and 12 deletions

51
backend/Cargo.lock generated
View file

@ -295,12 +295,31 @@ version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "base64"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
[[package]]
name = "base64ct"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "bcrypt"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9df288bec72232f78c1ec5fe4e8f1d108aa0265476e93097593c803c8c02062a"
dependencies = [
"base64 0.21.0",
"blowfish",
"getrandom",
"subtle",
"zeroize",
]
[[package]]
name = "bigdecimal"
version = "0.3.1"
@ -342,6 +361,16 @@ dependencies = [
"log",
]
[[package]]
name = "blowfish"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7"
dependencies = [
"byteorder",
"cipher",
]
[[package]]
name = "borsh"
version = "0.10.3"
@ -455,6 +484,16 @@ dependencies = [
"winapi",
]
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "clap"
version = "3.2.25"
@ -554,6 +593,7 @@ name = "crabfit_backend"
version = "1.1.0"
dependencies = [
"axum",
"bcrypt",
"chrono",
"common",
"dotenv",
@ -1119,6 +1159,15 @@ dependencies = [
"hashbrown 0.12.3",
]
[[package]]
name = "inout"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
"generic-array",
]
[[package]]
name = "instant"
version = "0.1.12"
@ -2280,7 +2329,7 @@ checksum = "fa8241483a83a3f33aa5fff7e7d9def398ff9990b2752b6c6112b83c6d246029"
dependencies = [
"ahash 0.7.6",
"atoi",
"base64",
"base64 0.13.1",
"bigdecimal",
"bitflags",
"byteorder",

View file

@ -20,3 +20,4 @@ regex = "1.8.1"
tracing = "0.1.37"
tracing-subscriber = "0.3.17"
chrono = "0.4.24"
bcrypt = "0.14.0"

View file

@ -71,17 +71,23 @@ impl Adaptor for SqlAdaptor {
}
async fn upsert_person(&self, event_id: String, person: Person) -> Result<Person, Self::Error> {
Ok(person::ActiveModel {
name: Set(person.name),
let data = person::ActiveModel {
name: Set(person.name.clone()),
password_hash: Set(person.password_hash),
created_at: Set(person.created_at.naive_utc()),
availability: Set(serde_json::to_value(person.availability).unwrap_or(json!([]))),
event_id: Set(event_id),
}
.save(&self.db)
.await?
.try_into_model()?
.into())
event_id: Set(event_id.clone()),
};
Ok(
match person::Entity::find_by_id((event_id, person.name))
.one(&self.db)
.await?
{
Some(_) => data.update(&self.db).await?.try_into_model()?.into(),
None => data.insert(&self.db).await?.try_into_model()?.into(),
},
)
}
async fn get_event(&self, id: String) -> Result<Option<Event>, Self::Error> {

View file

@ -4,7 +4,7 @@ use common::adaptor::Adaptor;
pub enum ApiError<A: Adaptor> {
AdaptorError(A::Error),
NotFound,
// NotAuthorized,
NotAuthorized,
}
// Define what the error types above should return
@ -16,7 +16,7 @@ impl<A: Adaptor> IntoResponse for ApiError<A> {
StatusCode::INTERNAL_SERVER_ERROR.into_response()
}
ApiError::NotFound => StatusCode::NOT_FOUND.into_response(),
// ApiError::NotAuthorized => StatusCode::UNAUTHORIZED.into_response(),
ApiError::NotAuthorized => StatusCode::UNAUTHORIZED.into_response(),
}
}
}

View file

@ -39,9 +39,10 @@ async fn main() {
let app = Router::new()
.route("/", get(get_root))
.route("/stats", get(get_stats))
.route("/event/:event_id", get(get_event))
.route("/event", post(create_event))
.route("/event/:event_id", get(get_event))
.route("/event/:event_id/people", get(get_people))
.route("/event/:event_id/people/:person_name", get(get_person))
.with_state(shared_state);
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));

View file

@ -58,3 +58,8 @@ impl From<Person> for PersonResponse {
}
}
}
#[derive(Deserialize)]
pub struct PersonInput {
pub password: Option<String>,
}

View file

@ -0,0 +1,87 @@
use axum::{
extract::{self, Path},
Json,
};
use common::{adaptor::Adaptor, person::Person};
use crate::{
errors::ApiError,
payloads::{ApiResult, PersonInput, PersonResponse},
State,
};
pub async fn get_person<A: Adaptor>(
extract::State(state): State<A>,
Path((event_id, person_name)): Path<(String, String)>,
input: Option<Json<PersonInput>>,
) -> ApiResult<PersonResponse, A> {
let adaptor = &state.lock().await.adaptor;
// Get inputted password
let password = match input {
Some(Json(i)) => i.password,
None => None,
};
let existing_people = adaptor
.get_people(event_id.clone())
.await
.map_err(ApiError::AdaptorError)?;
// Event not found
if existing_people.is_none() {
return Err(ApiError::NotFound);
}
// Check if the user already exists
let existing_person = existing_people
.unwrap()
.into_iter()
.find(|p| p.name == person_name);
match existing_person {
// Login
Some(p) => {
// Verify password (if set)
if verify_password(&p, password) {
Ok(Json(p.into()))
} else {
Err(ApiError::NotAuthorized)
}
}
// Signup
None => {
// Update stats
adaptor
.increment_stat_person_count()
.await
.map_err(ApiError::AdaptorError)?;
Ok(Json(
adaptor
.upsert_person(
event_id,
Person {
name: person_name,
password_hash: password
.map(|raw| bcrypt::hash(raw, 10).unwrap_or(String::from(""))),
created_at: chrono::offset::Utc::now(),
availability: vec![],
},
)
.await
.map_err(ApiError::AdaptorError)?
.into(),
))
}
}
}
fn verify_password(person: &Person, raw: Option<String>) -> bool {
match &person.password_hash {
Some(hash) => bcrypt::verify(raw.unwrap_or(String::from("")), hash).unwrap_or(false),
// Specifically allow a user who doesn't have a password
// set to log in with or without any password input
None => true,
}
}

View file

@ -9,3 +9,6 @@ pub use create_event::create_event;
mod get_people;
pub use get_people::get_people;
mod get_person;
pub use get_person::get_person;