algohub_server/routes/
problem.rs

1use rocket::{serde::json::Json, tokio::fs::remove_file, State};
2use serde::{Deserialize, Serialize};
3use surrealdb::{engine::remote::ws::Client, Surreal};
4
5use crate::{
6    models::{
7        account::Account,
8        error::Error,
9        problem::{CreateProblem, Problem, ProblemVisibility, UserProblem},
10        response::{Empty, Response},
11        Credentials, OwnedCredentials, OwnedId,
12    },
13    utils::{account, problem, session},
14    Result,
15};
16
17#[derive(Serialize, Deserialize, Debug)]
18#[serde(crate = "rocket::serde")]
19pub struct ProblemResponse {
20    pub id: String,
21}
22
23#[post("/create", data = "<problem>")]
24pub async fn create(
25    db: &State<Surreal<Client>>,
26    problem: Json<CreateProblem<'_>>,
27) -> Result<OwnedId> {
28    if !session::verify(db, problem.id, problem.token).await {
29        return Err(Error::Unauthorized(Json("Invalid token".into())));
30    }
31
32    let problem = problem::create(db, problem.into_inner())
33        .await?
34        .ok_or(Error::ServerError(Json(
35            "Failed to create problem, please try again later.".into(),
36        )))?;
37
38    Ok(Json(Response {
39        success: true,
40        message: "Problem created successfully".to_string(),
41        data: Some(OwnedId {
42            id: problem.id.unwrap().id.to_string(),
43        }),
44    }))
45}
46
47#[post("/get/<id>", data = "<auth>")]
48pub async fn get(
49    db: &State<Surreal<Client>>,
50    id: &str,
51    auth: Json<Option<Credentials<'_>>>,
52) -> Result<UserProblem> {
53    let problem = problem::get::<Problem>(db, id)
54        .await?
55        .ok_or(Error::NotFound(Json(
56            "Problem with specified id not found".into(),
57        )))?;
58
59    let authed_id = if let Some(auth) = auth.into_inner() {
60        if !session::verify(db, auth.id, auth.token).await {
61            return Err(Error::Unauthorized(Json("Invalid credentials".into())));
62        } else {
63            Some(auth.id)
64        }
65    } else {
66        None
67    };
68
69    let has_permission = if authed_id.is_none() && problem.visibility != ProblemVisibility::Public {
70        false
71    } else {
72        match problem.visibility {
73            ProblemVisibility::ContestOnly => {
74                // Check for contest access
75                todo!()
76            }
77            ProblemVisibility::Public => true,
78            ProblemVisibility::Private => problem.owner.id.to_string() == authed_id.unwrap(),
79            ProblemVisibility::Internal => {
80                // Check for internal access
81                todo!()
82            }
83        }
84    };
85
86    if !has_permission {
87        return Err(Error::Unauthorized(Json(
88            "You have no permission to access this problem".into(),
89        )));
90    }
91
92    Ok(Json(Response {
93        success: true,
94        message: "Problem found".to_string(),
95        data: Some(problem.into()),
96    }))
97}
98
99#[derive(Serialize, Deserialize)]
100#[serde(crate = "rocket::serde")]
101pub struct ListProblem {
102    pub identity: Option<String>,
103    pub auth: Option<OwnedCredentials>,
104    pub limit: Option<u32>,
105}
106
107#[post("/list", data = "<data>")]
108pub async fn list(
109    db: &State<Surreal<Client>>,
110    data: Json<ListProblem>,
111) -> Result<Vec<UserProblem>> {
112    let authed_id = if let Some(auth) = &data.auth {
113        if !session::verify(db, &auth.id, &auth.token).await {
114            return Err(Error::Unauthorized(Json("Invalid credentials".into())));
115        };
116        Some(auth.id.clone())
117    } else {
118        None
119    };
120
121    let data = data.into_inner();
122
123    let account_id = if let Some(identity) = data.identity.clone() {
124        Some(
125            account::get_by_identity::<Account>(db, &identity)
126                .await?
127                .ok_or(Error::Unauthorized(Json("Invalid identity".into())))?
128                .id
129                .unwrap()
130                .id
131                .to_string(),
132        )
133    } else {
134        None
135    };
136
137    let problems =
138        problem::list_for_account::<Problem>(db, account_id, authed_id, data.limit).await?;
139
140    Ok(Json(Response {
141        success: true,
142        message: "Problems found".to_string(),
143        data: Some(problems.into_iter().map(|p| p.into()).collect()),
144    }))
145}
146
147#[post("/update/<id>", data = "<problem>")]
148pub async fn update(
149    db: &State<Surreal<Client>>,
150    id: &str,
151    problem: Json<CreateProblem<'_>>,
152) -> Result<Empty> {
153    if !session::verify(db, problem.id, problem.token).await {
154        return Err(Error::Unauthorized(Json("Invalid credentials".into())));
155    }
156
157    problem::update(db, id, problem.into_inner())
158        .await?
159        .ok_or(Error::ServerError(Json(
160            "Failed to update problem, please try again later.".into(),
161        )))?;
162
163    Ok(Json(Response {
164        success: true,
165        message: "Problem updated successfully".to_string(),
166        data: None,
167    }))
168}
169
170#[delete("/delete/<id>", data = "<auth>")]
171pub async fn delete(
172    db: &State<Surreal<Client>>,
173    id: &str,
174    auth: Json<Credentials<'_>>,
175) -> Result<Empty> {
176    if !session::verify(db, auth.id, auth.token).await {
177        return Err(Error::Unauthorized(Json("Invalid credentials".into())));
178    }
179
180    for test_case in problem::get_test_cases_by_id(db, id).await? {
181        remove_file(test_case.input).await?;
182        remove_file(test_case.output).await?;
183    }
184    println!("Down");
185
186    problem::delete(db, id).await?.ok_or(Error::NotFound(Json(
187        "Problem with specified id not found".into(),
188    )))?;
189
190    Ok(Json(Response {
191        success: true,
192        message: "Problem deleted successfully".to_string(),
193        data: None,
194    }))
195}
196
197pub fn routes() -> Vec<rocket::Route> {
198    use rocket::routes;
199    routes![create, get, update, list, delete]
200}