diff --git a/docs/README.md b/docs/README.md index ac8fc61..d3e5c2e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -26,7 +26,7 @@ __(ND)__ -> Not designed yet. - [ ] `/projects` - `/project` - [X] `/create` - - [ ] `/delete` + - [X] `/delete` - [X] `/info` - [ ] `/join` - `/vault` diff --git a/docs/project/delete.md b/docs/project/delete.md index 91accec..ca72241 100644 --- a/docs/project/delete.md +++ b/docs/project/delete.md @@ -23,4 +23,6 @@ The request was malformed. ### 401 - Error: Unauthorized The provided access token doesn't allow you to perform this operation. ### 403 - Error: Forbidden -Blocked for security reasons. \ No newline at end of file +Blocked for security reasons. +### 404 - Error: Not Found +The project wasn't found. \ No newline at end of file diff --git a/src/api/mod.rs b/src/api/mod.rs index 7967a40..a79d19e 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -95,6 +95,7 @@ pub async fn start(port: u16, pool: PgPool) -> Result<()> { .service(account::calls::tokens_get) .service(project::calls::create) .service(project::calls::info) + .service(project::calls::delete) .app_data(web::Data::new(ApiState { pool: pool.clone() })) }) .bind(("0.0.0.0", port))? diff --git a/src/api/project/calls.rs b/src/api/project/calls.rs index aa99109..1d9bd4a 100644 --- a/src/api/project/calls.rs +++ b/src/api/project/calls.rs @@ -1,6 +1,6 @@ use crate::api::ApiState; use crate::api::project::{data, handlers}; -use actix_web::{get, post, web, HttpResponse, Responder}; +use actix_web::{get, post, web, HttpResponse, Responder, delete}; use actix_web_httpauth::extractors::bearer::BearerAuth; use log::error; @@ -21,7 +21,7 @@ async fn create(data: web::Data, auth: BearerAuth, body: web::Json, query: web::Query) -> impl Responder { +async fn info(data: web::Data, query: web::Query) -> impl Responder { let request = if query.id.is_none() || query.name.is_none() { if let Some(id) = query.id { @@ -49,3 +49,34 @@ async fn info(data: web::Data, query: web::Query) -> } } } + +#[delete("/project/delete")] +async fn delete(data: web::Data, auth: BearerAuth, query: web::Query) -> impl Responder { + + let request = if query.id.is_none() || query.name.is_none() { + if let Some(id) = query.id { + data::DeleteRequest::Id(id) + } else if let Some(name) = query.into_inner().name { + data::DeleteRequest::Name(name) + } else { + // No parameters were supplied + return HttpResponse::BadRequest().finish(); + } + } else { + // Too many parameters were supplied + return HttpResponse::BadRequest().finish(); + }; + + match handlers::delete(&data.pool, auth.token().to_string(), request).await { + Ok(resp) => match resp { + data::DeleteResponse::Success => HttpResponse::Ok().finish(), + data::DeleteResponse::Unauthorized => HttpResponse::Unauthorized().finish(), + data::DeleteResponse::Blocked => HttpResponse::Forbidden().finish(), + data::DeleteResponse::NotFound => HttpResponse::NotFound().finish(), + }, + Err(e) => { + error!("While handling info request: {e}"); + HttpResponse::InternalServerError().finish() + } + } +} diff --git a/src/api/project/data.rs b/src/api/project/data.rs index 890215a..5806b5b 100644 --- a/src/api/project/data.rs +++ b/src/api/project/data.rs @@ -1,5 +1,12 @@ use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize)] +pub struct ProjectIdQuery { + pub name: Option, + pub id: Option, +} + #[derive(Debug, Deserialize)] pub struct CreateRequest { pub name: String, @@ -19,12 +26,6 @@ pub enum CreateResponse { Conflict, } -#[derive(Debug, Deserialize)] -pub struct InfoQuery { - pub name: Option, - pub id: Option, -} - #[derive(Debug)] pub enum InfoRequest { Name(String), @@ -45,4 +46,18 @@ pub enum InfoResponse { Success(InfoSuccess), Blocked, NotFound, +} + +#[derive(Debug)] +pub enum DeleteRequest { + Name(String), + Id(i64), +} + +#[derive(Debug)] +pub enum DeleteResponse { + Success, + Unauthorized, + Blocked, + NotFound, } \ No newline at end of file diff --git a/src/api/project/handlers.rs b/src/api/project/handlers.rs index c8f1d9f..0ad5d1b 100644 --- a/src/api/project/handlers.rs +++ b/src/api/project/handlers.rs @@ -7,6 +7,7 @@ use crate::{ use anyhow::{Error, Result}; use sqlx::PgPool; use crate::accounts::Account; +use crate::projects::ProjectMemberFlags; pub async fn create( pool: &PgPool, @@ -70,4 +71,41 @@ pub async fn info(pool: &PgPool, request: data::InfoRequest) -> Result Result { + if !auth.is_alpha() { + return Ok(data::DeleteResponse::Blocked); + } + + let token = match AuthToken::check(pool, &auth).await? { + Some(t) => t, + None => return Ok(data::DeleteResponse::Unauthorized), + }; + + let project = match match request { + data::DeleteRequest::Name(name) => { + if is_sql_injection(&name) { + return Ok(data::DeleteResponse::Blocked); + } + Project::from_name(pool, &name).await? + } + data::DeleteRequest::Id(id) => Project::from_id(pool, id).await? + } + { + Some(p) => p, + None => return Ok(data::DeleteResponse::NotFound) + }; + + if !project.member_has_flag(&token.account, ProjectMemberFlags::Owner).await { + return Ok(data::DeleteResponse::Unauthorized); + } + + project.delete(pool).await?; + + Ok(data::DeleteResponse::Success) } \ No newline at end of file diff --git a/src/projects.rs b/src/projects.rs index f6c1724..a6e0377 100644 --- a/src/projects.rs +++ b/src/projects.rs @@ -85,4 +85,27 @@ impl Project { Err(e) => Err(Error::new(e)), } } + + pub async fn member_has_flag(&self, member: &ID, flag: ProjectMemberFlags) -> bool { + let account_id = member.id(); + let flag_mask: u16 = flag.into(); + + for &id in self.members.iter() { + if id.id() == account_id && (id.flags() & flag_mask) > 0 { + return true; + } + } + false + } + + pub async fn delete(&self, pool: &PgPool) -> Result<()> { + sqlx::query!( + r#"DELETE FROM Projects WHERE id = $1;"#, + self.id + ) + .execute(pool) + .await?; + + Ok(()) + } }