-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #60 from vklachkov/backend-api-refactoring
backend: Рефакторинг API
- Loading branch information
Showing
10 changed files
with
610 additions
and
515 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
use crate::{ | ||
api::{auth, error::*, payloads::*, BackendState}, | ||
datasource::DataSourceError, | ||
domain::*, | ||
}; | ||
use axum::{ | ||
extract::{Path, Query, State}, | ||
Json, | ||
}; | ||
use std::sync::Arc; | ||
|
||
pub async fn all_participants( | ||
auth_session: auth::AuthSession, | ||
State(state): State<Arc<BackendState>>, | ||
Query(GetJuryParticipantsQuery { order }): Query<GetJuryParticipantsQuery>, | ||
) -> Result<Json<Vec<JuryParticipant>>> { | ||
let jury_id = auth_session.user.as_ref().unwrap().0.id; | ||
|
||
let participants = state | ||
.datasource | ||
.participants | ||
.get_all(None, Sort::Id, order, false) | ||
.await?; | ||
|
||
let participants = participants | ||
.into_iter() | ||
.filter(|p| is_free_or_in_team(p, jury_id)) | ||
.map(|p| rebuild_participant(p, jury_id)) | ||
.collect(); | ||
|
||
Ok(Json(participants)) | ||
} | ||
|
||
pub async fn get_participant( | ||
auth_session: auth::AuthSession, | ||
State(state): State<Arc<BackendState>>, | ||
Path(id): Path<ParticipantId>, | ||
) -> Result<Json<JuryParticipant>> { | ||
let jury_id = auth_session.user.as_ref().unwrap().0.id; | ||
|
||
let Some(participant) = state.datasource.participants.get(id).await? else { | ||
return Err(ApiError::from(DataSourceError::UnknownParticipant(id))); | ||
}; | ||
|
||
if !is_free_or_in_team(&participant, jury_id) { | ||
return Err(ApiError::from(DataSourceError::UnknownParticipant(id))); | ||
} | ||
|
||
Ok(Json(rebuild_participant(participant, jury_id))) | ||
} | ||
|
||
fn is_free_or_in_team(participant: &Participant, jury_id: AdultId) -> bool { | ||
let Some(ref jury) = participant.jury else { | ||
return true; // Участник не находится ни в чьей команде. | ||
}; | ||
|
||
jury.id == jury_id | ||
} | ||
|
||
fn rebuild_participant(participant: Participant, jury_id: AdultId) -> JuryParticipant { | ||
assert!(is_free_or_in_team(&participant, jury_id)); | ||
|
||
let Participant { | ||
id, | ||
info, | ||
jury, | ||
answers, | ||
mut rates, | ||
.. | ||
} = participant; | ||
|
||
JuryParticipant { | ||
id, | ||
in_command: jury.is_some(), | ||
info: jury.is_some().then_some(info), | ||
answers, | ||
rate: rates | ||
.remove(&jury_id) | ||
.expect("rate for jury should be presented"), | ||
} | ||
} | ||
|
||
pub async fn set_rate( | ||
auth_session: auth::AuthSession, | ||
State(state): State<Arc<BackendState>>, | ||
Path(id): Path<ParticipantId>, | ||
Json(payload): Json<SetParticipantRate>, | ||
) -> Result<()> { | ||
let jury_id = auth_session.user.as_ref().unwrap().0.id; | ||
|
||
state | ||
.datasource | ||
.participants | ||
.set_jury_rate(id, jury_id, payload.rate) | ||
.await | ||
.map_err(Into::into) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
use crate::api::{auth, payloads::*}; | ||
use axum::{http::StatusCode, response::IntoResponse, Json}; | ||
|
||
pub async fn login( | ||
mut auth_session: auth::AuthSession, | ||
Json(payload): Json<LoginPayload>, | ||
) -> impl IntoResponse { | ||
let creds = payload.clone().into(); | ||
let user = match auth_session.authenticate(creds).await { | ||
Ok(Some(user)) => user, | ||
Ok(None) => return StatusCode::UNAUTHORIZED.into_response(), | ||
Err(_) => return StatusCode::INTERNAL_SERVER_ERROR.into_response(), | ||
}; | ||
|
||
if let Err(err) = auth_session.login(&user).await { | ||
tracing::error!("Failed to login user {user}: {err}", user = user.0.name); | ||
return StatusCode::INTERNAL_SERVER_ERROR.into_response(); | ||
} | ||
|
||
Json(user.0).into_response() | ||
} | ||
|
||
pub async fn logout(mut auth_session: auth::AuthSession) -> StatusCode { | ||
match auth_session.logout().await { | ||
Ok(Some(_)) => StatusCode::OK, | ||
Ok(None) => StatusCode::UNAUTHORIZED, | ||
Err(_) => StatusCode::INTERNAL_SERVER_ERROR, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
mod jury; | ||
mod login; | ||
mod org; | ||
mod report; | ||
mod tilda; | ||
|
||
use super::{auth, BackendState, BackendTokens, Services}; | ||
use crate::{datasource::DataSource, domain::*}; | ||
use axum::{ | ||
routing::{delete, get, post}, | ||
Router, | ||
}; | ||
use std::sync::Arc; | ||
|
||
pub fn router( | ||
datasource: Arc<DataSource>, | ||
services: Arc<Services>, | ||
tokens: Arc<BackendTokens>, | ||
) -> Router { | ||
let state = BackendState { | ||
datasource, | ||
services, | ||
tokens, | ||
}; | ||
|
||
Router::new() | ||
.route("/login", post(login::login)) | ||
.route("/logout", post(login::logout)) | ||
.route("/webhook/new_application", post(tilda::new_application)) | ||
.nest("/org", orgs_methods()) | ||
.nest("/jury", juries_methods()) | ||
.with_state(Arc::new(state)) | ||
} | ||
|
||
fn orgs_methods() -> Router<Arc<BackendState>> { | ||
Router::new() | ||
.route("/participants", get(org::all_participants)) | ||
.route("/participants/report", get(report::build_report)) | ||
.route("/participant", post(org::create_participant)) | ||
.route( | ||
"/participant/:id", | ||
get(org::get_participant) | ||
.delete(org::delete_participant) | ||
.patch(org::patch_participant), | ||
) | ||
.route("/participant/:id/command", post(org::set_command)) | ||
.route("/adults", get(org::all_adults)) | ||
.route("/adult", post(org::create_adult)) | ||
.route("/adult/:id", delete(org::delete_adult)) | ||
.route_layer(axum_login::permission_required!( | ||
auth::Backend, | ||
AdultRole::Org, | ||
)) | ||
} | ||
|
||
fn juries_methods() -> Router<Arc<BackendState>> { | ||
Router::new() | ||
.route("/participants", get(jury::all_participants)) | ||
.route("/participant/:id", get(jury::get_participant)) | ||
.route("/participant/:id/rate", post(jury::set_rate)) | ||
.route_layer(axum_login::permission_required!( | ||
auth::Backend, | ||
AdultRole::Jury, | ||
)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
use crate::{ | ||
api::{auth, error::*, payloads::*, BackendState}, | ||
datasource::DataSourceError, | ||
domain::*, | ||
}; | ||
use axum::{ | ||
extract::{Path, Query, State}, | ||
Json, | ||
}; | ||
use std::sync::Arc; | ||
|
||
pub async fn all_participants( | ||
State(state): State<Arc<BackendState>>, | ||
Query(GetParticipantsQuery { | ||
search, | ||
sort, | ||
order, | ||
deleted, | ||
}): Query<GetParticipantsQuery>, | ||
) -> Result<Json<Vec<Participant>>> { | ||
Ok(Json( | ||
state | ||
.datasource | ||
.participants | ||
.get_all(search, sort, order, deleted) | ||
.await?, | ||
)) | ||
} | ||
|
||
pub async fn create_participant( | ||
State(state): State<Arc<BackendState>>, | ||
Json(payload): Json<NewParticipantPayload>, | ||
) -> Result<()> { | ||
let NewParticipantPayload { | ||
code, | ||
jury, | ||
info, | ||
answers, | ||
rates, | ||
} = payload; | ||
|
||
state | ||
.datasource | ||
.participants | ||
.create(code, jury, info, answers, rates) | ||
.await | ||
.map(|_| ()) | ||
.map_err(Into::into) | ||
} | ||
|
||
pub async fn get_participant( | ||
State(state): State<Arc<BackendState>>, | ||
Path(id): Path<ParticipantId>, | ||
) -> Result<Json<Participant>> { | ||
if let Some(participant) = state.datasource.participants.get(id).await? { | ||
Ok(Json(participant)) | ||
} else { | ||
Err(ApiError::DataSource(DataSourceError::UnknownParticipant( | ||
id, | ||
))) | ||
} | ||
} | ||
|
||
pub async fn delete_participant( | ||
auth_session: auth::AuthSession, | ||
State(state): State<Arc<BackendState>>, | ||
Path(id): Path<ParticipantId>, | ||
) -> Result<()> { | ||
let authed_user_id = auth_session.user.unwrap().0.id; | ||
state | ||
.datasource | ||
.participants | ||
.delete(id, authed_user_id) | ||
.await | ||
.map_err(Into::into) | ||
} | ||
|
||
pub async fn patch_participant( | ||
State(state): State<Arc<BackendState>>, | ||
Path(id): Path<ParticipantId>, | ||
Json(UpdateParticipantPayload { info, answers }): Json<UpdateParticipantPayload>, | ||
) -> Result<()> { | ||
state | ||
.datasource | ||
.participants | ||
.patch(id, info, answers) | ||
.await | ||
.map_err(Into::into) | ||
} | ||
|
||
pub async fn set_command( | ||
State(state): State<Arc<BackendState>>, | ||
Path(id): Path<ParticipantId>, | ||
Json(SetParticipantCommandPayload { jury_id }): Json<SetParticipantCommandPayload>, | ||
) -> Result<()> { | ||
state | ||
.datasource | ||
.participants | ||
.set_jury(id, jury_id) | ||
.await | ||
.map_err(Into::into) | ||
} | ||
|
||
pub async fn all_adults(State(state): State<Arc<BackendState>>) -> Result<Json<Vec<Adult>>> { | ||
Ok(Json(state.datasource.adults.get_all().await?)) | ||
} | ||
|
||
pub async fn create_adult( | ||
State(state): State<Arc<BackendState>>, | ||
Json(NewAdultPayload { | ||
name, | ||
password, | ||
role, | ||
}): Json<NewAdultPayload>, | ||
) -> Result<()> { | ||
state | ||
.datasource | ||
.adults | ||
.create(name, password, role) | ||
.await | ||
.map_err(Into::into) | ||
} | ||
|
||
pub async fn delete_adult( | ||
auth_session: auth::AuthSession, | ||
State(state): State<Arc<BackendState>>, | ||
Path(id): Path<AdultId>, | ||
) -> Result<()> { | ||
let authed_user_id = auth_session.user.unwrap().0.id; | ||
if authed_user_id == id { | ||
return Err(ApiError::InvalidParameter( | ||
"can not delete yourself".to_owned(), | ||
)); | ||
} | ||
|
||
state.datasource.adults.delete(id).await.map_err(Into::into) | ||
} |
Oops, something went wrong.