Skip to content

Commit

Permalink
feat(Backend): ✨ use cursor paginator for user rank in contest
Browse files Browse the repository at this point in the history
  • Loading branch information
Eason0729 committed Feb 6, 2024
1 parent 0df0c55 commit c436b19
Show file tree
Hide file tree
Showing 16 changed files with 307 additions and 157 deletions.
2 changes: 1 addition & 1 deletion backend/src/controller/judger/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ impl JudgerController {
.await
{
Ok(submit) => {
score::ScoreUpload::new(req.user, problem, submit.score, submit.accept)
score::ScoreUpload::new(req.user, problem, submit)
.upload(&db)
.await;
}
Expand Down
56 changes: 44 additions & 12 deletions backend/src/controller/judger/score.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use super::{user_contest, DebugName};
use std::cmp;

use super::{submit, user_contest, DebugName};
use crate::{
entity::{contest, problem, user},
util::error::Error,
};
use chrono::Local;
use sea_orm::{
ActiveModelTrait, ActiveValue, DatabaseConnection, EntityTrait, IntoActiveModel, ModelTrait,
TransactionTrait,
ActiveModelTrait, ActiveValue, ColumnTrait, DatabaseConnection, EntityTrait, IntoActiveModel,
ModelTrait, QueryFilter, QueryOrder, TransactionTrait,
};
use tracing::instrument;

Expand All @@ -14,18 +17,16 @@ const MAX_RETRY: usize = 32;
pub struct ScoreUpload {
user_id: i32,
problem: problem::Model,
score: u32,
pass: bool,
submit: submit::Model,
}

impl ScoreUpload {
#[instrument(skip(problem))]
pub fn new(user_id: i32, problem: problem::Model, score: u32, pass: bool) -> Self {
pub fn new(user_id: i32, problem: problem::Model, submit: submit::Model) -> Self {
Self {
user_id,
problem,
score,
pass,
submit,
}
}
#[instrument(skip(self))]
Expand Down Expand Up @@ -58,7 +59,7 @@ impl ScoreUpload {
let txn = db.begin().await?;

let submit_count = model.submit_count.unwrap().saturating_add(1);
let accept_count = match self.pass {
let accept_count = match self.submit.accept {
true => model.accept_count.unwrap().saturating_add(1),
false => model.accept_count.unwrap(),
};
Expand All @@ -71,15 +72,18 @@ impl ScoreUpload {
txn.commit().await.map_err(Into::<Error>::into)
}
async fn upload_user(&self, db: &DatabaseConnection) -> Result<(), Error> {
if !self.pass {
if !self.submit.accept {
tracing::trace!(reason = "not acceptted", "score_user");
return Ok(());
}
let txn = db.begin().await?;

if self.user_id == self.problem.user_id {
tracing::trace!(reason = "problem owner score bypass", "score_user");
return Ok(());
}
if !self.problem.public {
tracing::trace!(reason = "private problem score bypass", "score_user");
return Ok(());
}

Expand All @@ -94,7 +98,7 @@ impl ScoreUpload {
let user_score = user.score;
let mut user = user.into_active_model();

user.score = ActiveValue::Set(user_score.saturating_add_unsigned(self.score as u64));
user.score = ActiveValue::Set(user_score.saturating_add_unsigned(self.submit.score as u64));
user.update(&txn).await.map_err(Into::<Error>::into)?;

txn.commit().await.map_err(Into::<Error>::into)
Expand All @@ -103,9 +107,16 @@ impl ScoreUpload {
let txn = db.begin().await?;

if self.user_id == self.problem.user_id {
tracing::trace!(reason = "problem owner score bypass", "score_contest");
return Ok(());
}
if self.problem.contest_id.is_none() {
tracing::trace!(reason = "not under contest", "score_contest");
return Ok(());
}

if self.submit.score == 0 {
tracing::trace!(reason = "no score to add", "score_contest");
return Ok(());
}

Expand All @@ -118,15 +129,36 @@ impl ScoreUpload {
.map_err(Into::<Error>::into)?
.ok_or(Error::NotInDB(user::Entity::DEBUG_NAME))?;

let now = Local::now().naive_local();

if contest.end < now {
tracing::trace!(reason = "contest ended", "score_contest");
return Ok(());
}

if contest.hoster == self.user_id {
tracing::trace!(reason = "owner score bypass", "score_contest");
return Ok(());
}

let mut linker = linker
.ok_or(Error::Unreachable("user_contest should exist"))?
.into_active_model();

linker.score = ActiveValue::Set(self.score);
let mut score = linker.score.unwrap();

let submit = self
.problem
.find_related(submit::Entity)
.filter(submit::Column::UserId.eq(self.user_id))
.order_by_desc(submit::Column::Score)
.one(&txn)
.await?;

score = score.saturating_sub(submit.map(|x| x.score).unwrap_or_default());
score = score.saturating_add(self.submit.score);

linker.score = ActiveValue::Set(score);
linker.update(&txn).await.map_err(Into::<Error>::into)?;

txn.commit().await.map_err(Into::<Error>::into)
Expand Down
20 changes: 0 additions & 20 deletions backend/src/endpoint/contest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,24 +316,4 @@ impl ContestSet for Arc<Server> {

Ok(Response::new(()))
}

#[doc = " return up to 10 user"]
#[instrument(skip_all, level = "debug")]
async fn rank(&self, req: Request<ContestId>) -> Result<Response<Users>, Status> {
let (auth, req) = self.parse_request(req).await?;

let contest: IdModel = Entity::related_read_by_id(&auth, req.id, &self.db).await?;

let list = user_contest::Entity::find()
.filter(user_contest::Column::ContestId.eq(contest.id))
.order_by_desc(user_contest::Column::Score)
.limit(10)
.all(self.db.deref())
.await
.map_err(Into::<Error>::into)?;

let list: Vec<UserRank> = list.into_iter().map(|x| x.into()).collect();

Ok(Response::new(Users { list }))
}
}
2 changes: 2 additions & 0 deletions backend/src/endpoint/problem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ impl From<PartialModel> for ProblemInfo {
title: value.title,
submit_count: value.submit_count,
ac_rate: value.ac_rate,
difficulty: value.difficulty,
}
}
}
Expand All @@ -42,6 +43,7 @@ impl From<Model> for ProblemFullInfo {
title: value.title,
submit_count: value.submit_count,
ac_rate: value.ac_rate,
difficulty: value.difficulty,
},
author: value.user_id.into(),
}
Expand Down
33 changes: 33 additions & 0 deletions backend/src/endpoint/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,39 @@ impl UserSet for Arc<Server> {

Ok(Response::new(ListUserResponse { list, next_session }))
}
async fn list_by_contest(
&self,
req: Request<ListByRequest>,
) -> Result<Response<ListUserResponse>, Status> {
let (auth, req) = self.parse_request(req).await?;

let (rev, size) = split_rev(req.size);
let size = bound!(size, 64);
let offset = bound!(req.offset(), 1024);

let (pager, models) = match req.request.ok_or(Error::NotInPayload("request"))? {
list_by_request::Request::Create(create) => {
ParentPaginator::new_fetch(
(0, create.parent_id),
&auth,
size,
offset,
create.start_from_end,
&self.db,
)
.await
}
list_by_request::Request::Pager(old) => {
let pager: ParentPaginator = self.crypto.decode(old.session)?;
pager.fetch(&auth, size, offset, rev, &self.db).await
}
}?;

let next_session = self.crypto.encode(pager)?;
let list = models.into_iter().map(|x| x.into()).collect();

Ok(Response::new(ListUserResponse { list, next_session }))
}
#[instrument(skip_all, level = "debug")]
async fn full_info(&self, _req: Request<UserId>) -> Result<Response<UserFullInfo>, Status> {
Err(Status::cancelled("deprecated"))
Expand Down
32 changes: 16 additions & 16 deletions backend/src/entity/announcement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,14 @@ impl PagerReflect<Entity> for PartialModel {

pub struct PagerTrait;

impl PagerData for PagerTrait {
type Data = ();
}

#[async_trait]
impl PagerSource for PagerTrait {
const ID: <Self::Entity as EntityTrait>::Column = Column::Id;

type Entity = Entity;

type Data = ();

const TYPE_NUMBER: u8 = 4;

async fn filter(
Expand All @@ -131,14 +131,14 @@ pub type Paginator = PkPager<PagerTrait, PartialModel>;

pub struct TextPagerTrait;

impl PagerData for TextPagerTrait {
type Data = String;
}

#[async_trait]
impl PagerSource for TextPagerTrait {
const ID: <Self::Entity as EntityTrait>::Column = Column::Id;

type Entity = Entity;

type Data = String;

const TYPE_NUMBER: u8 = 4;

async fn filter(
Expand All @@ -154,14 +154,14 @@ pub type TextPaginator = PkPager<TextPagerTrait, PartialModel>;

pub struct ParentPagerTrait;

impl PagerData for ParentPagerTrait {
type Data = (i32, chrono::NaiveDateTime);
}

#[async_trait]
impl PagerSource for ParentPagerTrait {
const ID: <Self::Entity as EntityTrait>::Column = Column::Id;

type Entity = Entity;

type Data = (i32, chrono::NaiveDateTime);

const TYPE_NUMBER: u8 = 8;

async fn filter(
Expand Down Expand Up @@ -193,14 +193,14 @@ pub type ParentPaginator = ColPager<ParentPagerTrait, PartialModel>;

pub struct ColPagerTrait;

impl PagerData for ColPagerTrait {
type Data = (AnnouncementSortBy, String);
}

#[async_trait]
impl PagerSource for ColPagerTrait {
const ID: <Self::Entity as EntityTrait>::Column = Column::Id;

type Entity = Entity;

type Data = (AnnouncementSortBy, String);

const TYPE_NUMBER: u8 = 8;

async fn filter(
Expand Down
8 changes: 4 additions & 4 deletions backend/src/entity/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,14 @@ impl PagerReflect<Entity> for Model {

pub struct ParentPagerTrait;

impl PagerData for ParentPagerTrait {
type Data = (i32, chrono::NaiveDateTime);
}

#[async_trait]
impl PagerSource for ParentPagerTrait {
const ID: <Self::Entity as EntityTrait>::Column = Column::Id;

type Entity = Entity;

type Data = (i32, chrono::NaiveDateTime);

const TYPE_NUMBER: u8 = 8;

async fn filter(
Expand Down
28 changes: 14 additions & 14 deletions backend/src/entity/contest.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::ops::Deref;

use chrono::Local;
use sea_orm::{DatabaseBackend, Statement};

use crate::{grpc::backend::ContestSortBy, partial_union};
Expand Down Expand Up @@ -129,22 +130,22 @@ impl super::ParentalTrait<IdModel> for Entity {
) -> Result<IdModel, Error> {
match user::Model::new_with_auth(auth) {
Some(user) => {
// user.find_related(Entity).select_only().columns(col);
let (query, param) = {
let builder = db.get_database_backend().get_query_builder();
let now = Local::now().naive_local();

partial_union!(
[Column::Id, Column::Hoster, Column::Public],
[Column::Id, Column::Hoster, Column::Public, Column::Begin],
user.find_related(Entity),
Entity::find().filter(Column::Public.eq(true)),
Entity::find()
.filter(Column::Public.eq(true))
.filter(Column::Begin.lte(now)),
Entity::find().filter(Column::Hoster.eq(user.id))
)
.and_where(Column::Id.eq(id))
.build_any(builder.deref())
};

// user.find_related(Entity).into_query()

IdModel::find_by_statement(Statement::from_sql_and_values(
DatabaseBackend::Sqlite,
query,
Expand Down Expand Up @@ -203,14 +204,14 @@ impl PagerReflect<Entity> for PartialModel {

pub struct TextPagerTrait;

impl PagerData for TextPagerTrait {
type Data = String;
}

#[async_trait]
impl PagerSource for TextPagerTrait {
const ID: <Self::Entity as EntityTrait>::Column = Column::Id;

type Entity = Entity;

type Data = String;

const TYPE_NUMBER: u8 = 4;

async fn filter(
Expand All @@ -226,15 +227,14 @@ pub type TextPaginator = PkPager<TextPagerTrait, PartialModel>;

pub struct ColPagerTrait;

impl PagerData for ColPagerTrait {
type Data = (ContestSortBy, String);
}

#[async_trait]
impl PagerSource for ColPagerTrait {
const ID: <Self::Entity as EntityTrait>::Column = Column::Id;

type Entity = Entity;

// FIXME: we need optional support
type Data = (ContestSortBy, String);

const TYPE_NUMBER: u8 = 8;

async fn filter(
Expand Down
Loading

0 comments on commit c436b19

Please sign in to comment.