Skip to content

Commit

Permalink
Merge pull request #60 from vklachkov/backend-api-refactoring
Browse files Browse the repository at this point in the history
backend: Рефакторинг API
  • Loading branch information
vklachkov authored Oct 10, 2024
2 parents d2144e2 + 7baa101 commit a5e3136
Show file tree
Hide file tree
Showing 10 changed files with 610 additions and 515 deletions.
513 changes: 4 additions & 509 deletions backend/src/api/mod.rs

Large diffs are not rendered by default.

97 changes: 97 additions & 0 deletions backend/src/api/v1/jury.rs
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)
}
29 changes: 29 additions & 0 deletions backend/src/api/v1/login.rs
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,
}
}
65 changes: 65 additions & 0 deletions backend/src/api/v1/mod.rs
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,
))
}
137 changes: 137 additions & 0 deletions backend/src/api/v1/org.rs
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)
}
Loading

0 comments on commit a5e3136

Please sign in to comment.