Skip to content

Commit

Permalink
feat(account): optimize account logic
Browse files Browse the repository at this point in the history
  • Loading branch information
fu050409 committed Nov 21, 2024
1 parent 406e199 commit 90a64e7
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 7 deletions.
51 changes: 46 additions & 5 deletions src/models/account.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,43 @@
use rocket::FromForm;
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,
Admin,
SuperAdmin = 0,
Admin = 1,
#[default]
User,
Reserve,
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)]
Expand Down Expand Up @@ -41,6 +70,11 @@ pub struct Account {

#[derive(FromForm, Serialize, Deserialize, Clone, Debug)]
pub struct Profile {
#[serde(skip_serializing_if = "Option::is_none")]
pub username: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,

#[serde(skip_serializing_if = "Option::is_none")]
pub avatar: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
Expand All @@ -65,6 +99,13 @@ pub struct Profile {
pub college: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub major: Option<String>,

#[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>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
Expand Down
37 changes: 35 additions & 2 deletions src/routes/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ pub async fn upload_content(
success: true,
message: "Content updated successfully".into(),
data: Some(UploadResponse {
uri: format!("/content/{}/{}", data.id, file_name),
uri: format!("/account/content/{}/{}", data.id, file_name),
}),
}))
}
Expand Down Expand Up @@ -235,6 +235,38 @@ pub async fn delete(
.into())
}

#[derive(Deserialize)]
pub struct Login<'r> {
pub identity: &'r str,
pub password: &'r str,
}

#[derive(Serialize, Deserialize)]
pub struct LoginResponse {
pub id: String,
pub token: String,
}

#[post("/login", data = "<login>")]
pub async fn login(
db: &State<Surreal<Client>>,
login: Json<Login<'_>>,
) -> Result<Json<Response<LoginResponse>>, Error> {
let session = session::authenticate(db, login.identity, login.password)
.await
.map_err(|e| Error::ServerError(Json(e.to_string().into())))?
.ok_or(Error::Unauthorized(Json("Invalid credentials".into())))?;
Ok(Response {
success: true,
message: "Login successful".into(),
data: Some(LoginResponse {
id: session.account_id.id.to_string(),
token: session.token.clone(),
}),
}
.into())
}

pub fn routes() -> Vec<rocket::Route> {
use rocket::routes;
routes![
Expand All @@ -243,6 +275,7 @@ pub fn routes() -> Vec<rocket::Route> {
get_profile,
content,
upload_content,
delete
delete,
login
]
}
8 changes: 8 additions & 0 deletions src/utils/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,11 @@ pub async fn delete(db: &Surreal<Client>, id: &str) -> Result<()> {
db.delete::<Option<Account>>(("account", id)).await?;
Ok(())
}

pub async fn get_by_identity(db: &Surreal<Client>, identity: &str) -> Result<Option<Account>> {
Ok(db
.query("SELECT * FROM account WHERE username = $identity OR email = $identity")
.bind(("identity", identity.to_string()))
.await?
.take(0)?)
}
31 changes: 31 additions & 0 deletions src/utils/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use surrealdb::{engine::remote::ws::Client, sql::Thing, Surreal};

use crate::models::account::Session;

use super::account;

pub async fn create(db: &Surreal<Client>, account_id: Thing) -> Result<Option<Session>> {
let session: Option<Session> = db
.upsert(("session", account_id.id.to_string()))
Expand All @@ -21,3 +23,32 @@ pub async fn verify(db: &Surreal<Client>, account_id: &str, token: &str) -> bool
_ => false,
}
}

pub async fn update(db: &Surreal<Client>, account: Thing) -> Result<Option<Session>> {
let session: Option<Session> = db
.upsert(("session", account.id.to_string()))
.content(Session {
id: None,
account_id: account,
token: uuid::Uuid::new_v4().to_string(),
})
.await?;
Ok(session)
}

pub async fn authenticate(
db: &Surreal<Client>,
identity: &str,
password: &str,
) -> Result<Option<Session>> {
let account = account::get_by_identity(db, identity).await?;
if account.is_none() {
return Ok(None);
};
let account = account.unwrap();
if account.password == password {
Ok(update(db, account.id.unwrap()).await?)
} else {
Ok(None)
}
}
5 changes: 5 additions & 0 deletions tests/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ async fn test_register() -> Result<()> {
id: &data.id,
token: &data.token,
profile: Profile {
email: None,
username: None,
avatar: None,
signature: None,
links: None,
Expand All @@ -105,6 +107,9 @@ async fn test_register() -> Result<()> {
school: None,
college: None,
major: None,
rating: None,
active: None,
role: None,
},
})
.dispatch()
Expand Down

0 comments on commit 90a64e7

Please sign in to comment.