algohub_server/utils/
contest.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
use anyhow::Result;
use surrealdb::{engine::remote::ws::Client, sql::Thing, Surreal};

use crate::models::contest::{Contest, ContestData, ContestProblem, ContestRank};

pub async fn create(
    db: &Surreal<Client>,
    creator_id: &str,
    contest: ContestData,
) -> Result<Option<Contest>> {
    Ok(db
        .create("contest")
        .content(Contest {
            id: None,
            name: contest.name.to_string(),
            mode: contest.mode,
            visibility: contest.visibility,
            description: contest.description,
            announcement: None,
            start_time: contest.start_time,
            end_time: contest.end_time,
            owner: contest.owner.clone().into(),
            creator: ("account", creator_id).into(),
            updaters: vec![("account", creator_id).into()],
            participants: vec![],
            created_at: chrono::Utc::now().naive_utc(),
            updated_at: chrono::Utc::now().naive_utc(),
        })
        .await?)
}

pub async fn get(db: &Surreal<Client>, id: &str) -> Result<Option<Contest>> {
    Ok(db.select(("contest", id)).await?)
}

pub async fn list_all(db: &Surreal<Client>) -> Result<Vec<Contest>> {
    Ok(db.query("SELECT * FROM contest").await?.take(0)?)
}

pub async fn list_by_owner(db: &Surreal<Client>, id: &str) -> Result<Vec<Contest>> {
    Ok(db
        .query("SELECT * FROM contest WHERE record::id(owner) = $id")
        .bind(("id", id.to_string()))
        .await?
        .take(0)?)
}

const ADD_PROBLEM: &str = r#"
UPDATE type::thing("contest", $id)
SET problems = array::union(problems, $problems);
"#;
pub async fn add_problems(
    db: &Surreal<Client>,
    id: String,
    problems: Vec<Thing>,
) -> Result<Option<Contest>> {
    Ok(db
        .query(ADD_PROBLEM)
        .bind(("id", id))
        .bind(("problems", problems))
        .await?
        .take(0)?)
}

const LIST_PROBLEMS: &str = r#"
SELECT title, record::id(id) AS id, count(
    SELECT VALUE true
    FROM submission WHERE record::id(creator) == $account_id AND problem == $parent.id
    AND judge_result.status.type == "accepted"
) > 0 AS solved,
count(
    SELECT record::id(creator) FROM submission WHERE problem == $parent.id
) AS submittedCount,
count(
    SELECT record::id(creator)
    FROM submission WHERE problem == $parent.id
    AND judge_result.status.type == "accepted"
) AS acceptedCount
FROM type::thing("contest", $id).problems;
"#;
pub async fn list_problems(
    db: &Surreal<Client>,
    id: &str,
    account_id: &str,
) -> Result<Vec<ContestProblem>> {
    Ok(db
        .query(LIST_PROBLEMS)
        .bind(("id", id.to_string()))
        .bind(("account_id", account_id.to_string()))
        .await?
        .take(0)?)
}

const REMOVE_PROBLEM: &str =
    "UPDATE contest SET problems -= type::thing(\"problem\", $problem) WHERE record::id(id) = $id";
pub async fn remove_problem(
    db: &Surreal<Client>,
    id: String,
    problem: Thing,
) -> Result<Option<Contest>> {
    Ok(db
        .query(REMOVE_PROBLEM)
        .bind(("id", id))
        .bind(("problem", problem))
        .await?
        .take(0)?)
}

const RANK_QUERY: &str = r#"
SELECT VALUE array::map((SELECT VALUE id FROM type::thing("contest", $id).problems), |$problem| {
    LET $submissions = SELECT judge_result.status.type AS status, created_at
    FROM submission WHERE problem.id == $problem AND $parent.id == creator ORDER BY created_at ASC;
    LET $first_accepted = array::find_index($submissions, |$submission| {
        RETURN $submission.status == "accepted"
    });
    RETURN IF $first_accepted {
        RETURN {
            name: $problem.title,
            problem_id: record::id($problem),
            accepted: true,
            wrongs: count($submissions) - $first_accepted - 1,
        }
    } ELSE {
        RETURN {
            name: $problem.title,
            problem_id: record::id($problem),
            accepted: false,
            wrongs: count($submissions),
        }
    }
}) FROM array::distinct(SELECT VALUE creator FROM submission)
"#;
pub async fn rank(db: &Surreal<Client>, id: &str) -> Result<Vec<ContestRank>> {
    Ok(db
        .query(RANK_QUERY)
        .bind(("id", id.to_string()))
        .await?
        .take(0)?)
}