Add update_person route

This commit is contained in:
Ben Grant 2023-05-13 17:13:04 +10:00
parent d2a94b078c
commit cd18427d1b
8 changed files with 106 additions and 26 deletions

View file

@ -80,7 +80,7 @@ impl Adaptor for SqlAdaptor {
};
Ok(
match person::Entity::find_by_id((event_id, person.name))
match person::Entity::find_by_id((person.name, event_id))
.one(&self.db)
.await?
{
@ -156,7 +156,16 @@ impl SqlAdaptor {
// Connect to the database
let db = Database::connect(&connection_string).await.unwrap();
println!("Connected to database at {}", connection_string);
println!(
"{} Connected to database at {}",
match db {
DatabaseConnection::SqlxMySqlPoolConnection(_) => "🐬",
DatabaseConnection::SqlxPostgresPoolConnection(_) => "🐘",
DatabaseConnection::SqlxSqlitePoolConnection(_) => "🪶",
DatabaseConnection::Disconnected => panic!("Failed to connect"),
},
connection_string
);
// Setup tables
Migrator::up(&db, None).await.unwrap();

View file

@ -2,7 +2,7 @@ use std::{net::SocketAddr, sync::Arc};
use axum::{
extract,
routing::{get, post},
routing::{get, patch, post},
Router, Server,
};
use routes::*;
@ -43,11 +43,15 @@ async fn main() {
.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))
.route("/event/:event_id/people/:person_name", patch(update_person))
.with_state(shared_state);
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
println!("Crab Fit API listening at http://{} in {} mode", addr, MODE);
println!(
"🦀 Crab Fit API listening at http://{} in {} mode",
addr, MODE
);
Server::bind(&addr)
.serve(app.into_make_service())
.await

View file

@ -1,5 +1,5 @@
use axum::Json;
use common::{event::Event, person::Person};
use common::{event::Event, person::Person, stats::Stats};
use serde::{Deserialize, Serialize};
use crate::errors::ApiError;
@ -19,7 +19,7 @@ pub struct EventResponse {
pub name: String,
pub times: Vec<String>,
pub timezone: String,
pub created: i64,
pub created_at: i64,
}
impl From<Event> for EventResponse {
@ -29,24 +29,33 @@ impl From<Event> for EventResponse {
name: value.name,
times: value.times,
timezone: value.timezone,
created: value.created_at.timestamp(),
created_at: value.created_at.timestamp(),
}
}
}
#[derive(Serialize)]
#[serde(rename_all(serialize = "camelCase"))]
pub struct StatsResponse {
pub event_count: i32,
pub person_count: i32,
pub version: String,
}
impl From<Stats> for StatsResponse {
fn from(value: Stats) -> Self {
Self {
event_count: value.event_count,
person_count: value.person_count,
version: env!("CARGO_PKG_VERSION").to_string(),
}
}
}
#[derive(Serialize)]
pub struct PersonResponse {
pub name: String,
pub availability: Vec<String>,
pub created: i64,
pub created_at: i64,
}
impl From<Person> for PersonResponse {
@ -54,12 +63,18 @@ impl From<Person> for PersonResponse {
Self {
name: value.name,
availability: value.availability,
created: value.created_at.timestamp(),
created_at: value.created_at.timestamp(),
}
}
}
#[derive(Deserialize)]
pub struct PersonInput {
pub struct GetPersonInput {
pub password: Option<String>,
}
#[derive(Deserialize)]
pub struct UpdatePersonInput {
pub password: Option<String>,
pub availability: Vec<String>,
}

View file

@ -22,13 +22,7 @@ pub async fn get_event<A: Adaptor>(
.map_err(ApiError::AdaptorError)?;
match event {
Some(event) => Ok(Json(EventResponse {
id: event.id,
name: event.name,
times: event.times,
timezone: event.timezone,
created: event.created_at.timestamp(),
})),
Some(event) => Ok(Json(event.into())),
None => Err(ApiError::NotFound),
}
}

View file

@ -6,14 +6,14 @@ use common::{adaptor::Adaptor, person::Person};
use crate::{
errors::ApiError,
payloads::{ApiResult, PersonInput, PersonResponse},
payloads::{ApiResult, GetPersonInput, 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>>,
input: Option<Json<GetPersonInput>>,
) -> ApiResult<PersonResponse, A> {
let adaptor = &state.lock().await.adaptor;
@ -77,7 +77,7 @@ pub async fn get_person<A: Adaptor>(
}
}
fn verify_password(person: &Person, raw: Option<String>) -> bool {
pub 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

View file

@ -12,9 +12,5 @@ pub async fn get_stats<A: Adaptor>(extract::State(state): State<A>) -> ApiResult
let stats = adaptor.get_stats().await.map_err(ApiError::AdaptorError)?;
Ok(Json(StatsResponse {
event_count: stats.event_count,
person_count: stats.person_count,
version: env!("CARGO_PKG_VERSION").to_string(),
}))
Ok(Json(stats.into()))
}

View file

@ -12,3 +12,6 @@ pub use get_people::get_people;
mod get_person;
pub use get_person::get_person;
mod update_person;
pub use update_person::update_person;

View file

@ -0,0 +1,59 @@
use axum::{
extract::{self, Path},
Json,
};
use common::{adaptor::Adaptor, person::Person};
use crate::{
errors::ApiError,
payloads::{ApiResult, PersonResponse, UpdatePersonInput},
State,
};
use super::get_person::verify_password;
pub async fn update_person<A: Adaptor>(
extract::State(state): State<A>,
Path((event_id, person_name)): Path<(String, String)>,
Json(input): Json<UpdatePersonInput>,
) -> ApiResult<PersonResponse, A> {
let adaptor = &state.lock().await.adaptor;
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 exists
let existing_person = existing_people
.unwrap()
.into_iter()
.find(|p| p.name == person_name)
.ok_or(ApiError::NotFound)?;
// Verify password (if set)
if !verify_password(&existing_person, input.password) {
return Err(ApiError::NotAuthorized);
}
Ok(Json(
adaptor
.upsert_person(
event_id,
Person {
name: existing_person.name,
password_hash: existing_person.password_hash,
created_at: existing_person.created_at,
availability: input.availability,
},
)
.await
.map_err(ApiError::AdaptorError)?
.into(),
))
}