Skip to content

Commit 942dd62

Browse files
committed
feat: ✨ education endpoints
1 parent 8dc7c4f commit 942dd62

File tree

9 files changed

+279
-63
lines changed

9 files changed

+279
-63
lines changed

backend/entity/src/education.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use super::problem;
1010
pub struct Model {
1111
#[sea_orm(primary_key, auto_increment = true)]
1212
pub id: i32,
13+
#[sea_orm(indexed, nullable)]
14+
pub problem_id: Option<i32>,
1315
pub user_id: i32,
1416
pub tags: String,
1517
pub title: String,
@@ -25,7 +27,10 @@ pub enum Relation {
2527
impl RelationTrait for Relation {
2628
fn def(&self) -> RelationDef {
2729
match self {
28-
Relation::Problem => Entity::has_one(problem::Entity).into(),
30+
Relation::Problem => Entity::belongs_to(super::problem::Entity)
31+
.from(Column::ProblemId)
32+
.to(super::problem::Column::Id)
33+
.into(),
2934
Relation::User => Entity::belongs_to(user::Entity)
3035
.from(Column::UserId)
3136
.to(user::Column::Id)

backend/entity/src/problem.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ impl RelationTrait for Relation {
6060
Self::TestCase => Entity::has_many(test::Entity).into(),
6161
}
6262
}
63-
}
63+
}
6464

6565
impl Related<user::Entity> for Entity {
6666
fn to() -> RelationDef {

backend/src/controller/submit.rs

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::sync::Arc;
33
use sea_orm::{ActiveModelTrait, ActiveValue, EntityTrait, QueryOrder};
44
use thiserror::Error;
55
use tokio_stream::StreamExt;
6+
use tonic::Status;
67
use uuid::Uuid;
78

89
use crate::{
@@ -20,8 +21,7 @@ use super::util::{
2021
};
2122
use entity::*;
2223

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

2626
#[derive(Debug, Error)]
2727
pub enum Error {
@@ -30,7 +30,7 @@ pub enum Error {
3030
#[error("judger health check failed")]
3131
HealthCheck,
3232
#[error("`{0}`")]
33-
GrpcReport(#[from] tonic::Status),
33+
GrpcReport(#[from] Status),
3434
#[error("payload.`{0}` is not a vaild argument")]
3535
BadArgument(&'static str),
3636
#[error("`{0}`")]
@@ -50,11 +50,10 @@ impl From<Error> for super::Error {
5050
super::Error::Internal("no judger available(for such lang)")
5151
}
5252
Error::HealthCheck => super::Error::Internal("judger health check failed"),
53-
Error::BadArgument(x) => tonic::Status::invalid_argument(format!(
54-
"Client sent invaild argument: payload.{}",
55-
x
56-
))
57-
.into(),
53+
Error::BadArgument(x) => {
54+
Status::invalid_argument(format!("Client sent invaild argument: payload.{}", x))
55+
.into()
56+
}
5857
Error::Database(x) => super::Error::Database(x),
5958
Error::Tonic(x) => super::Error::Tonic(x),
6059
Error::Internal(x) => super::Error::Internal(x),
@@ -97,7 +96,7 @@ impl From<JudgeResult> for SubmitStatus {
9796
#[derive(Clone)]
9897
pub struct SubmitController {
9998
router: Arc<Router>,
100-
pubsub: Arc<PubSub<Result<SubmitStatus, tonic::Status>, i32>>,
99+
pubsub: Arc<PubSub<Result<SubmitStatus, Status>, i32>>,
101100
}
102101

103102
impl SubmitController {
@@ -109,7 +108,7 @@ impl SubmitController {
109108
})
110109
}
111110
async fn stream(
112-
ps_guard: PubGuard<Result<SubmitStatus, tonic::Status>, i32>,
111+
ps_guard: PubGuard<Result<SubmitStatus, Status>, i32>,
113112
mut stream: tonic::Streaming<JudgeResponse>,
114113
mut model: submit::ActiveModel,
115114
mut scores: Vec<u32>,
@@ -228,16 +227,17 @@ impl SubmitController {
228227
}
229228
}
230229

231-
impl From<Error> for tonic::Status {
230+
impl From<Error> for Status {
232231
fn from(value: Error) -> Self {
233232
match value {
234-
Error::JudgerUnavailable => todo!(),
235-
Error::HealthCheck => todo!(),
236-
Error::GrpcReport(_) => todo!(),
237-
Error::BadArgument(_) => todo!(),
238-
Error::Database(_) => todo!(),
239-
Error::Tonic(_) => todo!(),
240-
Error::Internal(_) => todo!(),
233+
Error::JudgerUnavailable | Error::HealthCheck => {
234+
Status::unavailable("no judger available(for such lang)")
235+
}
236+
Error::GrpcReport(x) => x,
237+
_ => {
238+
let err = crate::endpoint::util::error::Error::Upstream(super::Error::from(value));
239+
err.into()
240+
}
241241
}
242242
}
243243
}

backend/src/endpoint/education.rs

Lines changed: 220 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,51 +5,243 @@ use crate::grpc::backend::education_set_server::*;
55
use crate::grpc::backend::*;
66

77
use entity::{education::*, *};
8+
9+
impl Filter for Entity {
10+
fn write_filter<S: QueryFilter + Send>(query: S, auth: &Auth) -> Result<S, Error> {
11+
let (user_id, perm) = auth.ok_or_default()?;
12+
if perm.can_root() {
13+
return Ok(query);
14+
}
15+
if perm.can_manage_education() {
16+
return Ok(query.filter(education::Column::UserId.eq(user_id)));
17+
}
18+
Err(Error::Unauthenticated)
19+
}
20+
}
21+
822
#[async_trait]
9-
impl EducationSet for Arc<Server> {
10-
async fn list(
11-
&self,
12-
req: Request<ListRequest>,
13-
) -> Result<Response<ListEducationResponse>, Status> {
14-
Err(Status::unimplemented("unimplemented"))
23+
impl ParentalFilter for Entity {
24+
fn publish_filter<S: QueryFilter + Send>(query: S, auth: &Auth) -> Result<S, Error> {
25+
if let Some(perm) = auth.user_perm() {
26+
if perm.can_root() {
27+
return Ok(query);
28+
}
29+
if perm.can_publish() {
30+
let user_id = auth.user_id().unwrap();
31+
return Ok(query.filter(Column::UserId.eq(user_id)));
32+
}
33+
}
34+
Err(Error::PremissionDeny("Can't publish education"))
1535
}
16-
async fn search_by_text(
17-
&self,
18-
req: Request<TextSearchRequest>,
19-
) -> Result<Response<ListEducationResponse>, Status> {
20-
Err(Status::unimplemented("unimplemented"))
36+
37+
fn link_filter<S: QueryFilter + Send>(query: S, auth: &Auth) -> Result<S, Error> {
38+
if let Some(perm) = auth.user_perm() {
39+
if perm.can_root() {
40+
return Ok(query);
41+
}
42+
if perm.can_link() {
43+
let user_id = auth.user_id().unwrap();
44+
return Ok(query.filter(Column::UserId.eq(user_id)));
45+
}
46+
}
47+
Err(Error::PremissionDeny("Can't link education"))
2148
}
22-
async fn search_by_tag(
23-
&self,
24-
req: Request<TextSearchRequest>,
25-
) -> Result<Response<ListEducationResponse>, Status> {
26-
Err(Status::unimplemented("unimplemented"))
49+
}
50+
51+
impl From<i32> for EducationId {
52+
fn from(value: i32) -> Self {
53+
Self { id: value }
2754
}
28-
async fn full_info(
29-
&self,
30-
req: Request<EducationId>,
31-
) -> Result<Response<EducationFullInfo>, Status> {
32-
Err(Status::unimplemented("unimplemented"))
55+
}
56+
57+
impl From<EducationId> for i32 {
58+
fn from(value: EducationId) -> Self {
59+
value.id
3360
}
34-
async fn create(&self, req: Request<CreateEducationRequest>) -> Result<Response<()>, Status> {
35-
Err(Status::unimplemented("unimplemented"))
61+
}
62+
63+
impl From<Model> for EducationFullInfo {
64+
fn from(value: Model) -> Self {
65+
todo!()
66+
}
67+
}
68+
impl From<Model> for EducationInfo {
69+
fn from(value: Model) -> Self {
70+
todo!()
71+
}
72+
}
73+
74+
#[async_trait]
75+
impl EducationSet for Arc<Server> {
76+
async fn create(
77+
&self,
78+
req: Request<CreateEducationRequest>,
79+
) -> Result<Response<EducationId>, Status> {
80+
let db = DB.get().unwrap();
81+
let (auth, req) = self.parse_request(req).await?;
82+
let (user_id, perm) = auth.ok_or_default()?;
83+
84+
let uuid = Uuid::parse_str(&req.request_id).map_err(Error::InvaildUUID)?;
85+
if let Some(x) = self.dup.check(user_id, &uuid) {
86+
return Ok(Response::new(x.into()));
87+
};
88+
89+
if !(perm.can_root() || perm.can_manage_problem()) {
90+
return Err(Error::PremissionDeny("Can't create education").into());
91+
}
92+
93+
let mut model: ActiveModel = Default::default();
94+
model.user_id = ActiveValue::Set(user_id);
95+
96+
fill_active_model!(model, req.info, title, content);
97+
98+
let model = model.save(db).await.map_err(Into::<Error>::into)?;
99+
100+
self.dup.store(user_id, uuid, model.id.clone().unwrap());
101+
102+
Ok(Response::new(model.id.unwrap().into()))
36103
}
37104
async fn update(&self, req: Request<UpdateEducationRequest>) -> Result<Response<()>, Status> {
38-
Err(Status::unimplemented("unimplemented"))
105+
let db = DB.get().unwrap();
106+
let (auth, req) = self.parse_request(req).await?;
107+
108+
let (user_id, perm) = auth.ok_or_default()?;
109+
110+
let uuid = Uuid::parse_str(&req.request_id).map_err(Error::InvaildUUID)?;
111+
if self.dup.check(user_id, &uuid).is_some() {
112+
return Ok(Response::new(()));
113+
};
114+
115+
if !(perm.can_root() || perm.can_manage_problem()) {
116+
return Err(Error::PremissionDeny("Can't update problem").into());
117+
}
118+
119+
let mut model = Entity::write_filter(Entity::find_by_id(req.id), &auth)?
120+
.one(db)
121+
.await
122+
.map_err(Into::<Error>::into)?
123+
.ok_or(Error::NotInDB("problem"))?
124+
.into_active_model();
125+
126+
fill_exist_active_model!(model, req.info, title, content);
127+
128+
let model = model.update(db).await.map_err(Into::<Error>::into)?;
129+
130+
self.dup.store(user_id, uuid, model.id);
131+
132+
Ok(Response::new(()))
39133
}
40134
async fn remove(&self, req: Request<EducationId>) -> Result<Response<()>, Status> {
41-
Err(Status::unimplemented("unimplemented"))
135+
let db = DB.get().unwrap();
136+
let (auth, req) = self.parse_request(req).await?;
137+
138+
Entity::write_filter(Entity::delete_by_id(Into::<i32>::into(req.id)), &auth)?
139+
.exec(db)
140+
.await
141+
.map_err(Into::<Error>::into)?;
142+
143+
Ok(Response::new(()))
42144
}
43145
async fn link(&self, req: Request<EducationLink>) -> Result<Response<()>, Status> {
44-
Err(Status::unimplemented("unimplemented"))
146+
let db = DB.get().unwrap();
147+
let (auth, req) = self.parse_request(req).await?;
148+
149+
let (_, perm) = auth.ok_or_default()?;
150+
151+
if !(perm.can_root() || perm.can_link()) {
152+
return Err(Error::PremissionDeny("Can't link problem").into());
153+
}
154+
155+
let mut model = Entity::link_filter(Entity::find_by_id(req.problem_id.id), &auth)?
156+
.columns([Column::Id, Column::ProblemId])
157+
.one(db)
158+
.await
159+
.map_err(Into::<Error>::into)?
160+
.ok_or(Error::NotInDB("problem"))?
161+
.into_active_model();
162+
163+
model.problem_id = ActiveValue::Set(Some(req.problem_id.id));
164+
165+
model.save(db).await.map_err(Into::<Error>::into)?;
166+
167+
Ok(Response::new(()))
45168
}
46169
async fn unlink(&self, req: Request<EducationLink>) -> Result<Response<()>, Status> {
47-
Err(Status::unimplemented("unimplemented"))
170+
let db = DB.get().unwrap();
171+
let (auth, req) = self.parse_request(req).await?;
172+
173+
let (_, perm) = auth.ok_or_default()?;
174+
175+
if !(perm.can_root() || perm.can_link()) {
176+
return Err(Error::PremissionDeny("Can't link problem").into());
177+
}
178+
179+
let mut model = Entity::link_filter(Entity::find_by_id(req.problem_id.id), &auth)?
180+
.columns([Column::Id, Column::ProblemId])
181+
.one(db)
182+
.await
183+
.map_err(Into::<Error>::into)?
184+
.ok_or(Error::NotInDB("problem"))?
185+
.into_active_model();
186+
187+
model.problem_id = ActiveValue::Set(None);
188+
189+
model.save(db).await.map_err(Into::<Error>::into)?;
190+
191+
Ok(Response::new(()))
192+
}
193+
194+
async fn list_by_problem(
195+
&self,
196+
req: Request<ListByRequest>,
197+
) -> Result<Response<ListEducationResponse>, Status> {
198+
let (auth, req) = self.parse_request(req).await?;
199+
200+
let mut reverse = false;
201+
let mut pager: Pager<Entity> = match req.request.ok_or(Error::NotInPayload("request"))? {
202+
list_by_request::Request::ParentId(ppk) => Pager::parent_search(ppk),
203+
list_by_request::Request::Pager(old) => {
204+
reverse = old.reverse;
205+
<Pager<Entity> as HasParentPager<problem::Entity, Entity>>::from_raw(old.session)?
206+
}
207+
};
208+
209+
let list = pager
210+
.fetch(req.size, req.offset.unwrap_or_default(), reverse, &auth)
211+
.await?
212+
.into_iter()
213+
.map(|x| x.into())
214+
.collect();
215+
216+
let next_session = pager.into_raw();
217+
218+
Ok(Response::new(ListEducationResponse { list, next_session }))
48219
}
49220
async fn full_info_by_problem(
50221
&self,
51222
req: Request<EducationLink>,
52223
) -> Result<Response<EducationFullInfo>, Status> {
53-
Err(Status::unimplemented("unimplemented"))
224+
let db = DB.get().unwrap();
225+
let (auth, req) = self.parse_request(req).await?;
226+
227+
let parent = auth
228+
.get_user(db)
229+
.await?
230+
.find_related(problem::Entity)
231+
.columns([contest::Column::Id])
232+
.one(db)
233+
.await
234+
.map_err(Into::<Error>::into)?
235+
.ok_or(Error::NotInDB("problem"))?;
236+
237+
let model = parent
238+
.find_related(Entity)
239+
.filter(Column::Id.eq(Into::<i32>::into(req.problem_id)))
240+
.one(db)
241+
.await
242+
.map_err(Into::<Error>::into)?
243+
.ok_or(Error::NotInDB("education"))?;
244+
245+
Ok(Response::new(model.into()))
54246
}
55247
}

0 commit comments

Comments
 (0)