Skip to content

Commit

Permalink
feat(org): suport get, remove, add, update (#71)
Browse files Browse the repository at this point in the history
* feat: suport get, remove, add, update

* fix: resolve merge conflict

* refactor: modify var type

* chore: delete .zsh_history

* chore: delete 1000

* chore: add bump version

* fix: correct the method used to destructure thing.
  • Loading branch information
K0nnyaku authored Jan 2, 2025
1 parent 48edf26 commit 5fd4555
Show file tree
Hide file tree
Showing 7 changed files with 326 additions and 29 deletions.
5 changes: 5 additions & 0 deletions .changes/organization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"algohub-server": patch:feat
---

Support remove, create, and update organizations and add members to organizations.
5 changes: 2 additions & 3 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@
"algohub",
"chrono",
"covector",
"farmfe",
"ICPC",
"serde",
"dtolnay",
"farmfe",
"ICPC",
"jbolda",
"serde",
"signin",
"surrealdb"
]
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

73 changes: 71 additions & 2 deletions src/models/organization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ pub struct Organization {

pub owners: Vec<Thing>,
pub members: Vec<Thing>,

pub creator: String,
pub creator: Option<Thing>,

pub created_at: chrono::NaiveDateTime,
pub updated_at: chrono::NaiveDateTime,
Expand All @@ -22,6 +21,14 @@ pub struct Organization {
#[serde(crate = "rocket::serde")]
pub struct OrganizationData<'c> {
pub name: &'c str,
pub display_name: Option<&'c str>,
pub description: Option<&'c str>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct UpdateOrg {
pub name: String,
pub display_name: Option<String>,
pub description: Option<String>,
}
Expand All @@ -34,3 +41,65 @@ pub struct CreateOrganization<'r> {

pub org: OrganizationData<'r>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct UserOrganization {
pub name: String,
pub display_name: Option<String>,
pub description: Option<String>,

pub owners: Vec<String>,
pub members: Vec<String>,

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

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

impl From<CreateOrganization<'_>> for Organization {
fn from(val: CreateOrganization) -> Self {
Organization {
id: None,
name: val.org.name.to_string(),
display_name: val.org.display_name.map(|s| s.to_string()),
description: val.org.description.map(|s| s.to_string()),
owners: vec![("account", val.id).into()],
members: vec![],
creator: Some(("account".to_string(), val.id.to_string()).into()),
created_at: chrono::Utc::now().naive_utc(),
updated_at: chrono::Utc::now().naive_utc(),
}
}
}

impl From<Organization> for UserOrganization {
fn from(val: Organization) -> Self {
UserOrganization {
name: val.name,
display_name: val.display_name,
description: val.description,
owners: val.owners.iter().map(|thing| thing.id.to_raw()).collect(),
members: val.members.iter().map(|thing| thing.id.to_raw()).collect(),
created_at: val.created_at,
updated_at: val.updated_at,
}
}
}

impl From<OrganizationData<'_>> for UpdateOrg {
fn from(val: OrganizationData) -> Self {
UpdateOrg {
name: val.name.to_string(),
display_name: val.display_name.map(|s| s.to_string()),
description: val.description.map(|s| s.to_string()),
}
}
}
110 changes: 107 additions & 3 deletions src/routes/organization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use surrealdb::{engine::remote::ws::Client, Surreal};
use crate::{
models::{
error::Error,
organization::CreateOrganization,
organization::{ChangeMember, CreateOrganization, Organization, UserOrganization},
response::{Empty, Response},
Credentials, OwnedId,
},
Expand All @@ -24,7 +24,7 @@ pub async fn create(
)));
}

let org = organization::create(db, org.id, org.into_inner().org)
let org = organization::create(db, org.into_inner())
.await?
.ok_or(Error::ServerError(Json(
"Failed to create a new organization".into(),
Expand All @@ -39,6 +39,110 @@ pub async fn create(
}))
}

#[post("/get/<id>", data = "<auth>")]
pub async fn get(
db: &State<Surreal<Client>>,
id: &str,
auth: Json<Option<Credentials<'_>>>,
) -> Result<UserOrganization> {
if let Some(auth) = auth.into_inner() {
if !session::verify(db, auth.id, auth.token).await {
return Err(Error::Unauthorized(Json(
"Failed to grant permission".into(),
)));
}
} else {
return Err(Error::Unauthorized(Json(
"Failed to grant permission".into(),
)));
}

let org = organization::get_by_id::<Organization>(db, id)
.await?
.ok_or(Error::NotFound(Json("Organization not found".into())))?;

Ok(Json(Response {
success: true,
message: "Organization found".to_string(),
data: Some(org.into()),
}))
}

#[post("/add/<id>", data = "<member>")]
pub async fn add(
db: &State<Surreal<Client>>,
id: &str,
member: Json<ChangeMember<'_>>,
) -> Result<Empty> {
if !session::verify(db, member.id, member.token).await {
return Err(Error::Unauthorized(Json(
"Failed to grant permission".into(),
)));
}

organization::add(db, id, member.into_inner().members)
.await?
.ok_or(Error::ServerError(Json(
"Failed to add members to the organization".into(),
)))?;

Ok(Json(Response {
success: true,
message: "Members added successfully".into(),
data: None,
}))
}

#[post("/remove/<id>", data = "<member>")]
pub async fn remove(
db: &State<Surreal<Client>>,
id: &str,
member: Json<ChangeMember<'_>>,
) -> Result<Empty> {
if !session::verify(db, member.id, member.token).await {
return Err(Error::Unauthorized(Json(
"Failed to grant permission".into(),
)));
}

organization::remove(db, id, member.into_inner().members)
.await?
.ok_or(Error::ServerError(Json(
"Failed to remove members to the organization".into(),
)))?;

Ok(Json(Response {
success: true,
message: "Members removed successfully".into(),
data: None,
}))
}

#[post("/update/<id>", data = "<org>")]
pub async fn update(
db: &State<Surreal<Client>>,
id: &str,
org: Json<CreateOrganization<'_>>,
) -> Result<Empty> {
if !session::verify(db, org.id, org.token).await {
return Err(Error::Unauthorized(Json(
"Failed to grant permission".into(),
)));
}

organization::update(db, id, org.into_inner().org)
.await?
.ok_or(Error::ServerError(Json(
"Failed to update the organization".into(),
)))?;

Ok(Json(Response {
success: true,
message: "Organization updated successfully".to_string(),
data: None,
}))
}

#[post("/delete/<id>", data = "<org>")]
pub async fn delete(
db: &State<Surreal<Client>>,
Expand All @@ -64,5 +168,5 @@ pub async fn delete(

pub fn routes() -> Vec<rocket::Route> {
use rocket::routes;
routes![create, delete]
routes![create, add, update, get, delete, remove]
}
74 changes: 59 additions & 15 deletions src/utils/organization.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,16 @@
use anyhow::Result;
use serde::Deserialize;
use surrealdb::{engine::remote::ws::Client, Surreal};
use surrealdb::{engine::remote::ws::Client, sql::Thing, Surreal};

use crate::models::organization::{Organization, OrganizationData};
use crate::models::organization::{CreateOrganization, Organization, OrganizationData, UpdateOrg};

pub async fn create(
db: &Surreal<Client>,
id: &str,
org: OrganizationData<'_>,
org: CreateOrganization<'_>,
) -> Result<Option<Organization>> {
Ok(db
.create("organization")
.content(Organization {
id: None,
name: org.name.to_string(),
display_name: org.display_name,
description: org.description,
owners: vec![("account", id).into()],
members: vec![],
creator: id.to_string(),
created_at: chrono::Local::now().naive_local(),
updated_at: chrono::Local::now().naive_local(),
})
.content(Into::<Organization>::into(org))
.await?)
}

Expand All @@ -32,6 +21,61 @@ where
Ok(db.select(("organization", id)).await?)
}

pub async fn update(
db: &Surreal<Client>,
id: &str,
org: OrganizationData<'_>,
) -> Result<Option<Organization>> {
Ok(db
.update(("organization", id))
.merge(Into::<UpdateOrg>::into(org))
.await?)
}

const ADD_MEMBERS_QUERY: &str = r#"
UPDATE type::thing("organization", $id)
SET members = array::union(members, $new_members)
"#;
pub async fn add(
db: &Surreal<Client>,
id: &str,
member: Vec<&str>,
) -> Result<Option<Organization>> {
let members_to_add: Vec<Thing> = member
.into_iter()
.map(|id| ("account", id).into())
.collect();

Ok(db
.query(ADD_MEMBERS_QUERY)
.bind(("id", id.to_string()))
.bind(("new_members", members_to_add))
.await?
.take(0)?)
}

const REMOVE_MEMBERS_QUERY: &str = r#"
UPDATE type::thing("organization", $id)
SET members = array::complement(members, $new_members)
"#;
pub async fn remove(
db: &Surreal<Client>,
id: &str,
member: Vec<&str>,
) -> Result<Option<Organization>> {
let members_to_remove: Vec<Thing> = member
.into_iter()
.map(|id| ("account", id).into())
.collect();

Ok(db
.query(REMOVE_MEMBERS_QUERY)
.bind(("id", id.to_string()))
.bind(("new_members", members_to_remove))
.await?
.take(0)?)
}

pub async fn delete(db: &Surreal<Client>, id: &str) -> Result<Option<Organization>> {
Ok(db.delete(("organization", id)).await?)
}
Loading

0 comments on commit 5fd4555

Please sign in to comment.