feat(api): implemented /project/delete

This commit is contained in:
antifallobst 2023-09-10 03:02:42 +02:00
parent e93d8675c4
commit 4bccfeef96
Signed by: antifallobst
GPG Key ID: 2B4F402172791BAF
7 changed files with 120 additions and 10 deletions

View File

@ -26,7 +26,7 @@ __(ND)__ -> Not designed yet.
- [ ] `/projects`
- `/project`
- [X] `/create`
- [ ] `/delete`
- [X] `/delete`
- [X] `/info`
- [ ] `/join`
- `/vault`

View File

@ -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.
Blocked for security reasons.
### 404 - Error: Not Found
The project wasn't found.

View File

@ -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))?

View File

@ -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<ApiState>, auth: BearerAuth, body: web::Json<dat
}
#[get("/project/info")]
async fn info(data: web::Data<ApiState>, query: web::Query<data::InfoQuery>) -> impl Responder {
async fn info(data: web::Data<ApiState>, query: web::Query<data::ProjectIdQuery>) -> 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<ApiState>, query: web::Query<data::InfoQuery>) ->
}
}
}
#[delete("/project/delete")]
async fn delete(data: web::Data<ApiState>, auth: BearerAuth, query: web::Query<data::ProjectIdQuery>) -> 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()
}
}
}

View File

@ -1,5 +1,12 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]
pub struct ProjectIdQuery {
pub name: Option<String>,
pub id: Option<i64>,
}
#[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<String>,
pub id: Option<i64>,
}
#[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,
}

View File

@ -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<data::Inf
created: project.created.timestamp(),
members,
}))
}
pub async fn delete(
pool: &PgPool,
auth: String,
request: data::DeleteRequest,
) -> Result<data::DeleteResponse> {
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)
}

View File

@ -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(())
}
}