Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: use UserRecordId instead of Thing #15

Merged
merged 2 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changes/refactor-thing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"algohub-server": patch:refactor
---

Refactor `surrealdb::sql::Thing` in user interface data structures.

- Use `UserRecordId` instead `surreal::sql::Thing` in structs.
- Remove `role` from `Account` definition.
- Refactor and re-export `ProblemDetail` in user interface.
44 changes: 1 addition & 43 deletions src/models/account.rs
Original file line number Diff line number Diff line change
@@ -1,45 +1,6 @@
use rocket::{form::FromFormField, FromForm};
use serde::{Deserialize, Serialize};
use surrealdb::sql::Thing;

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Role {
SuperAdmin = 0,
Admin = 1,
#[default]
User = 2,
Reserve = 3,
Inactive = 4,
}

impl TryFrom<i8> for Role {
type Error = anyhow::Error;

fn try_from(value: i8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Role::SuperAdmin),
1 => Ok(Role::Admin),
2 => Ok(Role::User),
3 => Ok(Role::Reserve),
4 => Ok(Role::Inactive),
_ => Err(anyhow::anyhow!("Invalid role: {}", value)),
}
}
}

#[rocket::async_trait]
impl<'v> FromFormField<'v> for Role {
fn from_value(field: rocket::form::ValueField<'v>) -> rocket::form::Result<'v, Self> {
let value = field.value.parse::<i8>()?;
Ok(Role::try_from(value).map_err(|_| field.unexpected())?)
}

fn default() -> Option<Self> {
Some(Self::User)
}
}

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct Account {
pub id: Option<Thing>,
Expand All @@ -61,14 +22,13 @@ pub struct Account {
pub major: Option<String>,

pub rating: i8,
pub role: Role,
pub active: bool,

pub created_at: chrono::NaiveDateTime,
pub updated_at: chrono::NaiveDateTime,
}

#[derive(FromForm, Serialize, Deserialize, Clone, Debug)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Profile {
#[serde(skip_serializing_if = "Option::is_none")]
pub username: Option<String>,
Expand Down Expand Up @@ -103,8 +63,6 @@ pub struct Profile {
#[serde(skip_serializing_if = "Option::is_none")]
pub rating: Option<i8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub role: Option<Role>,
#[serde(skip_serializing_if = "Option::is_none")]
pub active: Option<bool>,
}

Expand Down
12 changes: 3 additions & 9 deletions src/models/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,15 @@ pub struct ErrorResponse {
pub message: String,
}

impl From<String> for ErrorResponse {
fn from(message: String) -> Self {
impl<T: ToString> From<T> for ErrorResponse {
fn from(message: T) -> Self {
Self {
success: false,
message,
message: message.to_string(),
}
}
}

impl From<&str> for ErrorResponse {
fn from(message: &str) -> Self {
Self::from(message.to_string())
}
}

#[derive(Responder)]
pub enum Error {
#[response(status = 500, content_type = "json")]
Expand Down
45 changes: 36 additions & 9 deletions src/models/problem.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use serde::{Deserialize, Serialize};
use surrealdb::sql::Thing;

use crate::routes::problem::ProblemData;
use crate::routes::problem::CreateProblem;

use super::UserRecordId;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Sample {
Expand Down Expand Up @@ -43,8 +45,8 @@ pub struct Problem {
pub updated_at: chrono::NaiveDateTime,
}

impl From<ProblemData<'_>> for Problem {
fn from(val: ProblemData<'_>) -> Self {
impl From<CreateProblem<'_>> for Problem {
fn from(val: CreateProblem<'_>) -> Self {
Problem {
id: None,
title: val.title.to_string(),
Expand All @@ -69,9 +71,9 @@ impl From<ProblemData<'_>> for Problem {
}
}

#[derive(Serialize, Deserialize, Debug)]
#[derive(Debug, Deserialize, Serialize)]
pub struct ProblemDetail {
pub id: Thing,
pub id: String,

pub title: String,
pub description: String,
Expand All @@ -80,12 +82,12 @@ pub struct ProblemDetail {
pub samples: Vec<Sample>,
pub hint: Option<String>,

pub time_limit: i32,
pub memory_limit: i32,
pub time_limit: u64,
pub memory_limit: u64,
pub test_cases: Vec<Sample>,

pub creator: Thing,
pub owner: Thing,
pub creator: UserRecordId,
pub owner: UserRecordId,
pub categories: Vec<String>,
pub tags: Vec<String>,

Expand All @@ -95,3 +97,28 @@ pub struct ProblemDetail {
pub created_at: chrono::NaiveDateTime,
pub updated_at: chrono::NaiveDateTime,
}

impl From<Problem> for ProblemDetail {
fn from(value: Problem) -> Self {
ProblemDetail {
id: value.id.unwrap().id.to_string(),
title: value.title,
description: value.description,
input: value.input,
output: value.output,
samples: value.samples,
hint: value.hint,
time_limit: value.time_limit,
memory_limit: value.memory_limit,
test_cases: value.test_cases,
creator: value.creator.into(),
owner: value.owner.into(),
categories: value.categories,
tags: value.tags,
mode: value.mode,
private: value.private,
created_at: value.created_at,
updated_at: value.updated_at,
}
}
}
21 changes: 21 additions & 0 deletions src/models/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,27 @@ pub struct Record {
id: Option<Thing>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserRecordId {
tb: String,
id: String,
}

impl From<Thing> for UserRecordId {
fn from(thing: Thing) -> Self {
UserRecordId {
tb: thing.tb,
id: thing.id.to_string(),
}
}
}

impl Into<Thing> for UserRecordId {
fn into(self) -> Thing {
Thing::from((self.tb, self.id))
}
}

#[derive(Serialize)]
pub struct UpdateAt {
pub updated_at: chrono::NaiveDateTime,
Expand Down
10 changes: 4 additions & 6 deletions src/routes/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,14 @@ pub async fn register(

#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(crate = "rocket::serde")]
pub struct ProfileData<'r> {
pub struct MergeProfile<'r> {
pub id: &'r str,
pub token: &'r str,
pub profile: Profile,
}

#[post("/profile", data = "<profile>")]
pub async fn profile(db: &State<Surreal<Client>>, profile: Json<ProfileData<'_>>) -> Result<Empty> {
pub async fn profile(db: &State<Surreal<Client>>, profile: Json<MergeProfile<'_>>) -> Result<Empty> {
account::get_by_id::<Record>(db, profile.id)
.await
.map_err(|e| Error::ServerError(Json(e.to_string().into())))?
Expand All @@ -108,10 +108,8 @@ pub async fn profile(db: &State<Surreal<Client>>, profile: Json<ProfileData<'_>>
pub async fn get_profile(db: &State<Surreal<Client>>, id: &str) -> Result<Profile> {
let profile = account::get_by_id::<Profile>(db, id)
.await
.map_err(|e| Error::NotFound(Json(e.to_string().into())))?
.ok_or(Error::ServerError(Json(
"Filter returned None unexpectedly".into(),
)))?;
.map_err(|e| Error::ServerError(Json(e.to_string().into())))?
.ok_or(Error::NotFound(Json("Account not found".into())))?;

Ok(Response {
success: true,
Expand Down
23 changes: 11 additions & 12 deletions src/routes/problem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use crate::{

#[derive(Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct ProblemData<'r> {
pub struct CreateProblem<'r> {
pub id: &'r str,
pub token: &'r str,

Expand Down Expand Up @@ -60,7 +60,7 @@ pub struct ProblemResponse {
#[post("/create", data = "<problem>")]
pub async fn create(
db: &State<Surreal<Client>>,
problem: Json<ProblemData<'_>>,
problem: Json<CreateProblem<'_>>,
) -> Result<ProblemResponse> {
if !session::verify(db, problem.id, problem.token).await {
return Err(Error::Unauthorized(Json("Invalid token".into())));
Expand Down Expand Up @@ -95,22 +95,21 @@ pub async fn get(
id: &str,
auth: Json<Authenticate<'_>>,
) -> Result<ProblemDetail> {
let problem = problem::get::<ProblemDetail>(db, id)
let problem = problem::get::<Problem>(db, id)
.await
.map_err(|e| Error::ServerError(Json(e.to_string().into())))?
.ok_or(Error::NotFound(Json(
"Problem with specified id not found".into(),
)))?;

let has_permission = if problem.private {
if auth.id.is_none() || auth.token.is_none() {
if auth.id.is_none()
|| auth.token.is_none()
|| !session::verify(db, auth.id.unwrap(), auth.token.unwrap()).await
{
false
} else {
if !session::verify(db, auth.id.unwrap(), auth.token.unwrap()).await {
false
} else {
auth.id.unwrap() == problem.owner.id.to_string()
}
auth.id.unwrap() == problem.owner.id.to_string()
}
} else {
true
Expand All @@ -125,7 +124,7 @@ pub async fn get(
Ok(Json(Response {
success: true,
message: "Problem found".to_string(),
data: Some(problem),
data: Some(problem.into()),
}))
}

Expand Down Expand Up @@ -153,14 +152,14 @@ pub async fn list(

let data = data.into_inner();

let problems = problem::list_for_account(db, data.id, authed_id, data.limit)
let problems = problem::list_for_account::<Problem>(db, data.id, authed_id, data.limit)
.await
.map_err(|e| Error::ServerError(Json(e.to_string().into())))?;

Ok(Json(Response {
success: true,
message: "Problems found".to_string(),
data: Some(problems),
data: Some(problems.into_iter().map(|p| p.into()).collect()),
}))
}

Expand Down
3 changes: 1 addition & 2 deletions src/utils/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use serde::Deserialize;
use surrealdb::engine::remote::ws::Client;
use surrealdb::Surreal;

use crate::models::account::{Account, Profile, Role};
use crate::models::account::{Account, Profile};
use crate::models::UpdateAt;
use crate::routes::account::RegisterData;

Expand All @@ -24,7 +24,6 @@ pub async fn create(db: &Surreal<Client>, register: RegisterData) -> Result<Opti
username: register.username,
password: register.password,
email: register.email,
role: Role::User,
created_at: chrono::Local::now().naive_local(),
..Default::default()
})
Expand Down
4 changes: 2 additions & 2 deletions src/utils/problem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ use anyhow::Result;
use serde::Deserialize;
use surrealdb::{engine::remote::ws::Client, Surreal};

use crate::{models::problem::Problem, routes::problem::ProblemData};
use crate::{models::problem::Problem, routes::problem::CreateProblem};

pub async fn create(db: &Surreal<Client>, problem: ProblemData<'_>) -> Result<Option<Problem>> {
pub async fn create(db: &Surreal<Client>, problem: CreateProblem<'_>) -> Result<Option<Problem>> {
Ok(db
.create("problem")
.content(Into::<Problem>::into(problem))
Expand Down
5 changes: 2 additions & 3 deletions tests/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use algohub_server::{
response::{Empty, Response},
Token,
},
routes::account::{ProfileData, RegisterData, RegisterResponse, UploadResponse},
routes::account::{MergeProfile, RegisterData, RegisterResponse, UploadResponse},
};
use anyhow::Result;
use rocket::{http::ContentType, local::asynchronous::Client};
Expand Down Expand Up @@ -91,7 +91,7 @@ async fn test_register() -> Result<()> {

let response = client
.post("/account/profile")
.json(&ProfileData {
.json(&MergeProfile {
id: &data.id,
token: &data.token,
profile: Profile {
Expand All @@ -110,7 +110,6 @@ async fn test_register() -> Result<()> {
major: None,
rating: None,
active: None,
role: None,
},
})
.dispatch()
Expand Down
4 changes: 2 additions & 2 deletions tests/problem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use algohub_server::{
},
routes::{
account::{RegisterData, RegisterResponse},
problem::{ListProblem, ProblemData, ProblemResponse},
problem::{ListProblem, CreateProblem, ProblemResponse},
},
};
use anyhow::Result;
Expand Down Expand Up @@ -47,7 +47,7 @@ async fn test_problem() -> Result<()> {
for i in 0..10 {
let response = client
.post("/problem/create")
.json(&ProblemData {
.json(&CreateProblem {
id: &id,
token: &token,
title: &format!("Test Problem #{}", i),
Expand Down