Skip to content

Commit

Permalink
feat: ✨ education endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
Eason0729 committed Nov 26, 2023
1 parent 8dc7c4f commit 942dd62
Show file tree
Hide file tree
Showing 9 changed files with 279 additions and 63 deletions.
7 changes: 6 additions & 1 deletion backend/entity/src/education.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use super::problem;
pub struct Model {
#[sea_orm(primary_key, auto_increment = true)]
pub id: i32,
#[sea_orm(indexed, nullable)]
pub problem_id: Option<i32>,
pub user_id: i32,
pub tags: String,
pub title: String,
Expand All @@ -25,7 +27,10 @@ pub enum Relation {
impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
match self {
Relation::Problem => Entity::has_one(problem::Entity).into(),
Relation::Problem => Entity::belongs_to(super::problem::Entity)
.from(Column::ProblemId)
.to(super::problem::Column::Id)
.into(),
Relation::User => Entity::belongs_to(user::Entity)
.from(Column::UserId)
.to(user::Column::Id)
Expand Down
2 changes: 1 addition & 1 deletion backend/entity/src/problem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ impl RelationTrait for Relation {
Self::TestCase => Entity::has_many(test::Entity).into(),
}
}
}
}

impl Related<user::Entity> for Entity {
fn to() -> RelationDef {
Expand Down
36 changes: 18 additions & 18 deletions backend/src/controller/submit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::sync::Arc;
use sea_orm::{ActiveModelTrait, ActiveValue, EntityTrait, QueryOrder};
use thiserror::Error;
use tokio_stream::StreamExt;
use tonic::Status;
use uuid::Uuid;

use crate::{
Expand All @@ -20,8 +21,7 @@ use super::util::{
};
use entity::*;

type TonicStream<T> =
std::pin::Pin<Box<dyn tokio_stream::Stream<Item = Result<T, tonic::Status>> + Send>>;
type TonicStream<T> = std::pin::Pin<Box<dyn tokio_stream::Stream<Item = Result<T, Status>> + Send>>;

#[derive(Debug, Error)]
pub enum Error {
Expand All @@ -30,7 +30,7 @@ pub enum Error {
#[error("judger health check failed")]
HealthCheck,
#[error("`{0}`")]
GrpcReport(#[from] tonic::Status),
GrpcReport(#[from] Status),
#[error("payload.`{0}` is not a vaild argument")]
BadArgument(&'static str),
#[error("`{0}`")]
Expand All @@ -50,11 +50,10 @@ impl From<Error> for super::Error {
super::Error::Internal("no judger available(for such lang)")
}
Error::HealthCheck => super::Error::Internal("judger health check failed"),
Error::BadArgument(x) => tonic::Status::invalid_argument(format!(
"Client sent invaild argument: payload.{}",
x
))
.into(),
Error::BadArgument(x) => {
Status::invalid_argument(format!("Client sent invaild argument: payload.{}", x))
.into()
}
Error::Database(x) => super::Error::Database(x),
Error::Tonic(x) => super::Error::Tonic(x),
Error::Internal(x) => super::Error::Internal(x),
Expand Down Expand Up @@ -97,7 +96,7 @@ impl From<JudgeResult> for SubmitStatus {
#[derive(Clone)]
pub struct SubmitController {
router: Arc<Router>,
pubsub: Arc<PubSub<Result<SubmitStatus, tonic::Status>, i32>>,
pubsub: Arc<PubSub<Result<SubmitStatus, Status>, i32>>,
}

impl SubmitController {
Expand All @@ -109,7 +108,7 @@ impl SubmitController {
})
}
async fn stream(
ps_guard: PubGuard<Result<SubmitStatus, tonic::Status>, i32>,
ps_guard: PubGuard<Result<SubmitStatus, Status>, i32>,
mut stream: tonic::Streaming<JudgeResponse>,
mut model: submit::ActiveModel,
mut scores: Vec<u32>,
Expand Down Expand Up @@ -228,16 +227,17 @@ impl SubmitController {
}
}

impl From<Error> for tonic::Status {
impl From<Error> for Status {
fn from(value: Error) -> Self {
match value {
Error::JudgerUnavailable => todo!(),
Error::HealthCheck => todo!(),
Error::GrpcReport(_) => todo!(),
Error::BadArgument(_) => todo!(),
Error::Database(_) => todo!(),
Error::Tonic(_) => todo!(),
Error::Internal(_) => todo!(),
Error::JudgerUnavailable | Error::HealthCheck => {
Status::unavailable("no judger available(for such lang)")
}
Error::GrpcReport(x) => x,
_ => {
let err = crate::endpoint::util::error::Error::Upstream(super::Error::from(value));
err.into()
}
}
}
}
248 changes: 220 additions & 28 deletions backend/src/endpoint/education.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,51 +5,243 @@ use crate::grpc::backend::education_set_server::*;
use crate::grpc::backend::*;

use entity::{education::*, *};

impl Filter for Entity {
fn write_filter<S: QueryFilter + Send>(query: S, auth: &Auth) -> Result<S, Error> {
let (user_id, perm) = auth.ok_or_default()?;
if perm.can_root() {
return Ok(query);
}
if perm.can_manage_education() {
return Ok(query.filter(education::Column::UserId.eq(user_id)));
}
Err(Error::Unauthenticated)
}
}

#[async_trait]
impl EducationSet for Arc<Server> {
async fn list(
&self,
req: Request<ListRequest>,
) -> Result<Response<ListEducationResponse>, Status> {
Err(Status::unimplemented("unimplemented"))
impl ParentalFilter for Entity {
fn publish_filter<S: QueryFilter + Send>(query: S, auth: &Auth) -> Result<S, Error> {
if let Some(perm) = auth.user_perm() {
if perm.can_root() {
return Ok(query);
}
if perm.can_publish() {
let user_id = auth.user_id().unwrap();
return Ok(query.filter(Column::UserId.eq(user_id)));
}
}
Err(Error::PremissionDeny("Can't publish education"))
}
async fn search_by_text(
&self,
req: Request<TextSearchRequest>,
) -> Result<Response<ListEducationResponse>, Status> {
Err(Status::unimplemented("unimplemented"))

fn link_filter<S: QueryFilter + Send>(query: S, auth: &Auth) -> Result<S, Error> {
if let Some(perm) = auth.user_perm() {
if perm.can_root() {
return Ok(query);
}
if perm.can_link() {
let user_id = auth.user_id().unwrap();
return Ok(query.filter(Column::UserId.eq(user_id)));
}
}
Err(Error::PremissionDeny("Can't link education"))
}
async fn search_by_tag(
&self,
req: Request<TextSearchRequest>,
) -> Result<Response<ListEducationResponse>, Status> {
Err(Status::unimplemented("unimplemented"))
}

impl From<i32> for EducationId {
fn from(value: i32) -> Self {
Self { id: value }
}
async fn full_info(
&self,
req: Request<EducationId>,
) -> Result<Response<EducationFullInfo>, Status> {
Err(Status::unimplemented("unimplemented"))
}

impl From<EducationId> for i32 {
fn from(value: EducationId) -> Self {
value.id
}
async fn create(&self, req: Request<CreateEducationRequest>) -> Result<Response<()>, Status> {
Err(Status::unimplemented("unimplemented"))
}

impl From<Model> for EducationFullInfo {
fn from(value: Model) -> Self {
todo!()
}
}
impl From<Model> for EducationInfo {
fn from(value: Model) -> Self {
todo!()
}
}

#[async_trait]
impl EducationSet for Arc<Server> {
async fn create(
&self,
req: Request<CreateEducationRequest>,
) -> Result<Response<EducationId>, Status> {
let db = DB.get().unwrap();
let (auth, req) = self.parse_request(req).await?;
let (user_id, perm) = auth.ok_or_default()?;

let uuid = Uuid::parse_str(&req.request_id).map_err(Error::InvaildUUID)?;
if let Some(x) = self.dup.check(user_id, &uuid) {
return Ok(Response::new(x.into()));
};

if !(perm.can_root() || perm.can_manage_problem()) {
return Err(Error::PremissionDeny("Can't create education").into());
}

let mut model: ActiveModel = Default::default();
model.user_id = ActiveValue::Set(user_id);

fill_active_model!(model, req.info, title, content);

let model = model.save(db).await.map_err(Into::<Error>::into)?;

self.dup.store(user_id, uuid, model.id.clone().unwrap());

Ok(Response::new(model.id.unwrap().into()))
}
async fn update(&self, req: Request<UpdateEducationRequest>) -> Result<Response<()>, Status> {
Err(Status::unimplemented("unimplemented"))
let db = DB.get().unwrap();
let (auth, req) = self.parse_request(req).await?;

let (user_id, perm) = auth.ok_or_default()?;

let uuid = Uuid::parse_str(&req.request_id).map_err(Error::InvaildUUID)?;
if self.dup.check(user_id, &uuid).is_some() {
return Ok(Response::new(()));
};

if !(perm.can_root() || perm.can_manage_problem()) {
return Err(Error::PremissionDeny("Can't update problem").into());
}

let mut model = Entity::write_filter(Entity::find_by_id(req.id), &auth)?
.one(db)
.await
.map_err(Into::<Error>::into)?
.ok_or(Error::NotInDB("problem"))?
.into_active_model();

fill_exist_active_model!(model, req.info, title, content);

let model = model.update(db).await.map_err(Into::<Error>::into)?;

self.dup.store(user_id, uuid, model.id);

Ok(Response::new(()))
}
async fn remove(&self, req: Request<EducationId>) -> Result<Response<()>, Status> {
Err(Status::unimplemented("unimplemented"))
let db = DB.get().unwrap();
let (auth, req) = self.parse_request(req).await?;

Entity::write_filter(Entity::delete_by_id(Into::<i32>::into(req.id)), &auth)?
.exec(db)
.await
.map_err(Into::<Error>::into)?;

Ok(Response::new(()))
}
async fn link(&self, req: Request<EducationLink>) -> Result<Response<()>, Status> {
Err(Status::unimplemented("unimplemented"))
let db = DB.get().unwrap();
let (auth, req) = self.parse_request(req).await?;

let (_, perm) = auth.ok_or_default()?;

if !(perm.can_root() || perm.can_link()) {
return Err(Error::PremissionDeny("Can't link problem").into());
}

let mut model = Entity::link_filter(Entity::find_by_id(req.problem_id.id), &auth)?
.columns([Column::Id, Column::ProblemId])
.one(db)
.await
.map_err(Into::<Error>::into)?
.ok_or(Error::NotInDB("problem"))?
.into_active_model();

model.problem_id = ActiveValue::Set(Some(req.problem_id.id));

model.save(db).await.map_err(Into::<Error>::into)?;

Ok(Response::new(()))
}
async fn unlink(&self, req: Request<EducationLink>) -> Result<Response<()>, Status> {
Err(Status::unimplemented("unimplemented"))
let db = DB.get().unwrap();
let (auth, req) = self.parse_request(req).await?;

let (_, perm) = auth.ok_or_default()?;

if !(perm.can_root() || perm.can_link()) {
return Err(Error::PremissionDeny("Can't link problem").into());
}

let mut model = Entity::link_filter(Entity::find_by_id(req.problem_id.id), &auth)?
.columns([Column::Id, Column::ProblemId])
.one(db)
.await
.map_err(Into::<Error>::into)?
.ok_or(Error::NotInDB("problem"))?
.into_active_model();

model.problem_id = ActiveValue::Set(None);

model.save(db).await.map_err(Into::<Error>::into)?;

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

async fn list_by_problem(
&self,
req: Request<ListByRequest>,
) -> Result<Response<ListEducationResponse>, Status> {
let (auth, req) = self.parse_request(req).await?;

let mut reverse = false;
let mut pager: Pager<Entity> = match req.request.ok_or(Error::NotInPayload("request"))? {
list_by_request::Request::ParentId(ppk) => Pager::parent_search(ppk),
list_by_request::Request::Pager(old) => {
reverse = old.reverse;
<Pager<Entity> as HasParentPager<problem::Entity, Entity>>::from_raw(old.session)?
}
};

let list = pager
.fetch(req.size, req.offset.unwrap_or_default(), reverse, &auth)
.await?
.into_iter()
.map(|x| x.into())
.collect();

let next_session = pager.into_raw();

Ok(Response::new(ListEducationResponse { list, next_session }))
}
async fn full_info_by_problem(
&self,
req: Request<EducationLink>,
) -> Result<Response<EducationFullInfo>, Status> {
Err(Status::unimplemented("unimplemented"))
let db = DB.get().unwrap();
let (auth, req) = self.parse_request(req).await?;

let parent = auth
.get_user(db)
.await?
.find_related(problem::Entity)
.columns([contest::Column::Id])
.one(db)
.await
.map_err(Into::<Error>::into)?
.ok_or(Error::NotInDB("problem"))?;

let model = parent
.find_related(Entity)
.filter(Column::Id.eq(Into::<i32>::into(req.problem_id)))
.one(db)
.await
.map_err(Into::<Error>::into)?
.ok_or(Error::NotInDB("education"))?;

Ok(Response::new(model.into()))
}
}
Loading

0 comments on commit 942dd62

Please sign in to comment.