Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion server/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use openapi_generated::{
types::Object,
};

use crate::repository::RepositoryError;
use crate::{problem::ProblemProjectionError, repository::RepositoryError};

type BoxError = Box<dyn Error + Send + Sync>;

Expand Down Expand Up @@ -40,6 +40,10 @@ pub(crate) enum AppError {
#[source]
source: BoxError,
},
#[error("active run was not found")]
RunNotFound,
#[error("problem is locked")]
ProblemLocked,
}

impl AppError {
Expand Down Expand Up @@ -68,6 +72,12 @@ impl From<RepositoryError> for AppError {
}
}

impl From<ProblemProjectionError> for AppError {
fn from(error: ProblemProjectionError) -> Self {
Self::internal(error)
}
}

impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, code, message) = match &self {
Expand All @@ -93,6 +103,16 @@ impl IntoResponse for AppError {
"internal server error".to_owned(),
)
}
Self::RunNotFound => (
StatusCode::NOT_FOUND,
"RUN_NOT_FOUND",
"挑戦中のrunが見つかりません".to_owned(),
),
Self::ProblemLocked => (
StatusCode::CONFLICT,
"PROBLEM_LOCKED",
"この問題はまだ解放されていません".to_owned(),
),
};

let body = ErrorResponse::new(ErrorResponseError::new(
Expand Down
37 changes: 36 additions & 1 deletion server/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ use axum::{
};
use axum_extra::extract::cookie::{Cookie, CookieJar};
use chrono::Utc;
use openapi_generated::models::{ActiveRunResponse, GuestLoginRequest, GuestLoginResponse, User};
use openapi_generated::models::{
ActiveRunResponse, GuestLoginRequest, GuestLoginResponse, ProblemResponse, User,
};
use uuid::Uuid;

use crate::{
AppState, OPENAPI_DOCUMENT, auth::current_user::CurrentUser, config::AuthMode, error::AppError,
problem::build_problem_response,
};

pub(crate) async fn ping() -> Response {
Expand Down Expand Up @@ -155,3 +158,35 @@ pub(crate) async fn start_or_resume_run(

Ok(Json(response))
}

pub(crate) async fn get_problem(
State(state): State<AppState>,
user: CurrentUser,
Path((room_id, problem_id)): Path<(String, String)>,
) -> Result<Json<ProblemResponse>, AppError> {
let room_id =
Uuid::parse_str(&room_id).map_err(|_| AppError::bad_request("invalid room_id"))?;

let problem_id =
Uuid::parse_str(&problem_id).map_err(|_| AppError::bad_request("invalid problem_id"))?;

let run = state
.auth_repository
.find_active_run(user.user_id, room_id)
.await?
.ok_or(AppError::RunNotFound)?;

let problem = state
.auth_repository
.find_problem_for_run(run.id, room_id, problem_id)
.await?
.ok_or_else(|| AppError::not_found("problem not found"))?;

if problem.status == "locked" {
return Err(AppError::ProblemLocked);
}

let response = build_problem_response(problem, state.asset_url_resolver.as_ref())?;

Ok(Json(response))
}
16 changes: 16 additions & 0 deletions server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use axum::{
routing::{get, post},
};
use config::AuthMode;
use problem::{AssetUrlResolver, UnconfiguredAssetUrlResolver};
use repository::AuthRepository;
use sqlx::MySqlPool;
use tower_http::trace::TraceLayer;
Expand Down Expand Up @@ -37,6 +38,7 @@ pub const OPENAPI_DOCUMENT: &str = include_str!(concat!(env!("OUT_DIR"), "/opena
pub struct AppState {
pub(crate) auth_mode: AuthMode,
pub(crate) auth_repository: Arc<dyn AuthRepository>,
pub(crate) asset_url_resolver: Arc<dyn AssetUrlResolver>,
}

impl AppState {
Expand All @@ -45,8 +47,18 @@ impl AppState {
Self {
auth_mode,
auth_repository,
asset_url_resolver: Arc::new(UnconfiguredAssetUrlResolver),
}
}

#[must_use]
pub fn with_asset_url_resolver(
mut self,
asset_url_resolver: Arc<dyn AssetUrlResolver>,
) -> Self {
self.asset_url_resolver = asset_url_resolver;
self
}
}

pub fn app(state: AppState) -> Router {
Expand Down Expand Up @@ -75,6 +87,10 @@ pub fn app(state: AppState) -> Router {
"/api/rooms/{room_id}/runs",
post(handler::start_or_resume_run).fallback(handler::method_not_allowed),
)
.route(
"/api/rooms/{room_id}/problems/{problem_id}",
get(handler::get_problem).fallback(handler::method_not_allowed),
)
.fallback(handler::not_found)
.layer(TraceLayer::new_for_http())
.with_state(state)
Expand Down
5 changes: 5 additions & 0 deletions server/src/problem.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
mod asset_url;
mod loader;
mod model;
mod public;
mod seeder;
mod validation;

use std::{io, path::PathBuf};

use thiserror::Error;

pub(crate) use asset_url::UnconfiguredAssetUrlResolver;
pub use asset_url::{AssetUrlResolveError, AssetUrlResolver};
pub use loader::load_problem_data;
pub use model::{
Asset, InputSchema, JudgeConfig, Operation, Problem, ProblemCatalog, ProblemType, Room,
SubmissionType,
};
pub use public::{ProblemProjectionError, build_problem_response};
pub use seeder::{ProblemSeedError, SeedSummary, seed_problem_data};

#[derive(Debug, Error)]
Expand Down
17 changes: 17 additions & 0 deletions server/src/problem/asset_url.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use thiserror::Error;

#[derive(Debug, Error)]
#[error("asset URL could not be resolved")]
pub struct AssetUrlResolveError;

pub trait AssetUrlResolver: Send + Sync {
fn resolve(&self, object_key: &str) -> Result<String, AssetUrlResolveError>;
}

pub(crate) struct UnconfiguredAssetUrlResolver;

impl AssetUrlResolver for UnconfiguredAssetUrlResolver {
fn resolve(&self, _object_key: &str) -> Result<String, AssetUrlResolveError> {
Err(AssetUrlResolveError)
}
}
Loading