Add the ability to create users and to get profiles
This commit is contained in:
parent
b2a7577c9d
commit
d9f1efb8d3
@ -126,11 +126,19 @@ impl Core {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn user(&self, user_id: UserId) -> ResultExt<Option<User>, AppError, FatalError> {
|
||||
let users = return_error!(self.list_users().await);
|
||||
ok(users.into_iter().find(|user| user.id == user_id))
|
||||
}
|
||||
|
||||
pub async fn create_user(&self, username: &str) -> ResultExt<(), AppError, FatalError> {
|
||||
let state = self.0.read().await;
|
||||
match return_error!(self.user_by_username(username).await) {
|
||||
Some(_) => error(AppError::UsernameUnavailable),
|
||||
None => unimplemented!(),
|
||||
None => match state.db.save_user(None, username, "", false, true).await {
|
||||
Ok(_) => ok(()),
|
||||
Err(err) => fatal(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,12 @@ impl FromSql for UserId {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for UserId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
f.write_str(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
pub struct SessionId(String);
|
||||
|
||||
|
@ -95,17 +95,19 @@ pub async fn admin_required<F, A, Fut>(
|
||||
core: Core,
|
||||
headers: HeaderMap,
|
||||
f: F,
|
||||
) -> (StatusCode, Json<Option<A>>)
|
||||
) -> (StatusCode, Json<Option<A>>)
|
||||
where
|
||||
F: FnOnce(User) -> Fut,
|
||||
Fut: Future<Output = (StatusCode, Json<Option<A>>)>,
|
||||
{
|
||||
match check_session(&core, headers).await {
|
||||
ResultExt::Ok(Some(user)) => if user.admin {
|
||||
ResultExt::Ok(Some(user)) => {
|
||||
if user.admin {
|
||||
f(user).await
|
||||
} else {
|
||||
(StatusCode::FORBIDDEN, Json(None))
|
||||
},
|
||||
}
|
||||
}
|
||||
ResultExt::Ok(None) => (StatusCode::UNAUTHORIZED, Json(None)),
|
||||
ResultExt::Err(_err) => (StatusCode::BAD_REQUEST, Json(None)),
|
||||
ResultExt::Fatal(err) => panic!("{}", err),
|
||||
@ -120,16 +122,35 @@ pub struct UserProfile {
|
||||
pub is_admin: bool,
|
||||
}
|
||||
|
||||
pub async fn get_user(core: Core, headers: HeaderMap) -> (StatusCode, Json<Option<UserProfile>>) {
|
||||
pub async fn get_user(
|
||||
core: Core,
|
||||
headers: HeaderMap,
|
||||
user_id: Option<UserId>,
|
||||
) -> (StatusCode, Json<Option<UserProfile>>) {
|
||||
auth_required(core.clone(), headers, |user| async move {
|
||||
(
|
||||
match user_id {
|
||||
Some(user_id) => match core.user(user_id).await {
|
||||
ResultExt::Ok(Some(user)) => (
|
||||
StatusCode::OK,
|
||||
Json(Some(UserProfile {
|
||||
userid: UserId::from(user.id),
|
||||
username: user.name,
|
||||
is_admin: user.admin,
|
||||
})),
|
||||
)
|
||||
),
|
||||
ResultExt::Ok(None) => (StatusCode::NOT_FOUND, Json(None)),
|
||||
ResultExt::Err(_err) => (StatusCode::BAD_REQUEST, Json(None)),
|
||||
ResultExt::Fatal(err) => panic!("{}", err),
|
||||
}
|
||||
None => (
|
||||
StatusCode::OK,
|
||||
Json(Some(UserProfile {
|
||||
userid: UserId::from(user.id),
|
||||
username: user.name,
|
||||
is_admin: user.admin,
|
||||
})),
|
||||
),
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
@ -137,17 +158,22 @@ pub async fn get_user(core: Core, headers: HeaderMap) -> (StatusCode, Json<Optio
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[typeshare]
|
||||
pub struct CreateUserRequest {
|
||||
username: String,
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
pub async fn create_user(core: Core, headers: HeaderMap, req: CreateUserRequest) -> (StatusCode, Json<Option<()>>) {
|
||||
auth_required(core.clone(), headers, |_admin| async {
|
||||
pub async fn create_user(
|
||||
core: Core,
|
||||
headers: HeaderMap,
|
||||
req: CreateUserRequest,
|
||||
) -> (StatusCode, Json<Option<()>>) {
|
||||
admin_required(core.clone(), headers, |_admin| async {
|
||||
match core.create_user(&req.username).await {
|
||||
ResultExt::Ok(_) => (StatusCode::OK, Json(None)),
|
||||
ResultExt::Err(err) => (StatusCode::BAD_REQUEST, Json(None)),
|
||||
ResultExt::Fatal(fatal) => panic!("{}", fatal),
|
||||
}
|
||||
}).await
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1,6 +1,7 @@
|
||||
use std::fmt;
|
||||
|
||||
use axum::{
|
||||
extract::Path,
|
||||
http::{HeaderMap, StatusCode},
|
||||
routing::{get, post},
|
||||
Json, Router,
|
||||
@ -8,6 +9,7 @@ use axum::{
|
||||
|
||||
use crate::{
|
||||
core::Core,
|
||||
database::UserId,
|
||||
handlers::{
|
||||
check_password, create_user, get_user, healthcheck, AuthRequest, CreateUserRequest,
|
||||
},
|
||||
@ -34,7 +36,7 @@ pub fn routes(core: Core) -> Router {
|
||||
"/api/v1/user",
|
||||
get({
|
||||
let core = core.clone();
|
||||
move |headers: HeaderMap| get_user(core, headers)
|
||||
move |headers: HeaderMap| get_user(core, headers, None)
|
||||
})
|
||||
.put({
|
||||
let core = core.clone();
|
||||
@ -44,6 +46,16 @@ pub fn routes(core: Core) -> Router {
|
||||
}
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/api/v1/user/:user_id",
|
||||
get({
|
||||
let core = core.clone();
|
||||
move |user_id: Path<UserId>, headers: HeaderMap| {
|
||||
let Path(user_id) = user_id;
|
||||
get_user(core, headers, Some(user_id))
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -84,6 +96,32 @@ mod test {
|
||||
(core, server)
|
||||
}
|
||||
|
||||
async fn setup_with_user() -> (Core, TestServer) {
|
||||
let (core, server) = setup_admin_enabled().await;
|
||||
let response = server
|
||||
.post("/api/v1/auth")
|
||||
.json(&AuthRequest {
|
||||
username: "admin".to_owned(),
|
||||
password: "aoeu".to_owned(),
|
||||
})
|
||||
.await;
|
||||
response.assert_status_ok();
|
||||
let session_id: Option<SessionId> = response.json();
|
||||
let session_id = session_id.unwrap();
|
||||
|
||||
let response = server
|
||||
.put("/api/v1/user")
|
||||
.add_header("Authorization", format!("Bearer {}", session_id))
|
||||
.json(&CreateUserRequest {
|
||||
username: "savanni".to_owned(),
|
||||
})
|
||||
.await;
|
||||
|
||||
response.assert_status_ok();
|
||||
|
||||
(core, server)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn it_returns_a_healthcheck() {
|
||||
let (core, server) = setup_without_admin();
|
||||
@ -170,6 +208,54 @@ mod test {
|
||||
|
||||
#[tokio::test]
|
||||
async fn an_admin_can_create_a_user() {
|
||||
// All of the contents of this test are basically required for any test on individual
|
||||
// users, so I moved it all into the setup code.
|
||||
let (_core, _server) = setup_with_user().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_user_can_get_any_user_profile() {
|
||||
let (core, server) = setup_with_user().await;
|
||||
|
||||
let savanni = match core.user_by_username("savanni").await {
|
||||
ResultExt::Ok(Some(savanni)) => savanni,
|
||||
ResultExt::Ok(None) => panic!("user was not initialized"),
|
||||
ResultExt::Err(err) => panic!("{:?}", err),
|
||||
ResultExt::Fatal(err) => panic!("{:?}", err),
|
||||
};
|
||||
|
||||
let response = server
|
||||
.post("/api/v1/auth")
|
||||
.json(&AuthRequest {
|
||||
username: "savanni".to_owned(),
|
||||
password: "".to_owned(),
|
||||
})
|
||||
.await;
|
||||
let session_id: Option<SessionId> = response.json();
|
||||
let session_id = session_id.unwrap();
|
||||
|
||||
let response = server
|
||||
.get(&format!("/api/v1/user/{}", savanni.id))
|
||||
.add_header("Authorization", format!("Bearer {}", session_id))
|
||||
.await;
|
||||
response.assert_status_ok();
|
||||
let profile: Option<UserProfile> = response.json();
|
||||
let profile = profile.unwrap();
|
||||
assert_eq!(profile.username, "savanni");
|
||||
|
||||
let response = server
|
||||
.get("/api/v1/user/admin")
|
||||
.add_header("Authorization", format!("Bearer {}", session_id))
|
||||
.await;
|
||||
response.assert_status_ok();
|
||||
let profile: Option<UserProfile> = response.json();
|
||||
let profile = profile.unwrap();
|
||||
assert_eq!(profile.username, "admin");
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[tokio::test]
|
||||
async fn a_user_can_get_change_their_password() {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user