feat(api): implemented /project/info

This commit is contained in:
antifallobst 2023-09-10 01:53:22 +02:00
parent 76f371b6ee
commit 8fd807e450
Signed by: antifallobst
GPG Key ID: 2B4F402172791BAF
7 changed files with 137 additions and 13 deletions

View File

@ -4,21 +4,23 @@ Returns the list of public projects the user is part of.
## Urlencoded Parameters
You have to set one parameter.
Setting none or two parameters will result in a _400 Bad Request_ Response.
| Parameter | Description |
|-----------|-----------------------------------------------------------------------|
| name | The projectname of the user on which the operation will be performed. |
| id | The projectid of the user on which the operation will be performed. |
|-----------|--------------------|
| name | The projects name. |
| id | The projects id. |
## Responses
### 200 - Success
__Content - JSON:__
| Field | Description |
|-------------|---------------------------------------------------------------------------|
| id | The projects unique id. |
| name | The projects unique name. |
| description | The projects description. |
| created | The datetime when the project was created. Represented as UNIX timestamp. |
| members | A list of (username, userid) pairs. |
| members | A list of (username, userid, flags) pairs. |
### 400 - Error: Bad Request
The request was malformed.
### 403 - Error: Forbidden

View File

@ -30,8 +30,8 @@ pub struct Account {
impl From<i64> for ID {
fn from(value: i64) -> Self {
Self {
id: (value as u64 & 0x0000FFFFFFFFFFFF),
flags: (value as u64 & 0xFFFF000000000000) as u16,
id: (value as u64 & 0x0000_FFFF_FFFF_FFFF),
flags: ((value as u64 & 0xFFFF_0000_0000_0000) >> 48) as u16,
}
}
}
@ -53,7 +53,7 @@ impl ID {
}
pub fn combined(&self) -> i64 {
(self.id | ((self.flags as u64) << 46)) as i64
(self.id | ((self.flags as u64) << 48)) as i64
}
}

View File

@ -94,6 +94,7 @@ pub async fn start(port: u16, pool: PgPool) -> Result<()> {
.service(account::calls::tokens_delete)
.service(account::calls::tokens_get)
.service(project::calls::create)
.service(project::calls::info)
.app_data(web::Data::new(ApiState { pool: pool.clone() }))
})
.bind(("0.0.0.0", port))?

View File

@ -14,7 +14,37 @@ async fn create(data: web::Data<ApiState>, auth: BearerAuth, body: web::Json<dat
data::CreateResponse::Blocked => HttpResponse::Forbidden().finish(),
},
Err(e) => {
error!("While handling register request: {e}");
error!("While handling create request: {e}");
HttpResponse::InternalServerError().finish()
}
}
}
#[get("/project/info")]
async fn info(data: web::Data<ApiState>, query: web::Query<data::InfoQuery>) -> impl Responder {
let request = if query.id.is_none() || query.name.is_none() {
if let Some(id) = query.id {
data::InfoRequest::Id(id)
} else if let Some(name) = query.into_inner().name {
data::InfoRequest::Name(name)
} else {
// No parameters were supplied
return HttpResponse::BadRequest().finish();
}
} else {
// Too many parameters were supplied
return HttpResponse::BadRequest().finish();
};
match handlers::info(&data.pool, request).await {
Ok(resp) => match resp {
data::InfoResponse::Success(b) => HttpResponse::Ok().json(web::Json(b)),
data::InfoResponse::Blocked => HttpResponse::Forbidden().finish(),
data::InfoResponse::NotFound => HttpResponse::NotFound().finish(),
},
Err(e) => {
error!("While handling info request: {e}");
HttpResponse::InternalServerError().finish()
}
}

View File

@ -18,3 +18,31 @@ pub enum CreateResponse {
Blocked,
Conflict,
}
#[derive(Debug, Deserialize)]
pub struct InfoQuery {
pub name: Option<String>,
pub id: Option<i64>,
}
#[derive(Debug)]
pub enum InfoRequest {
Name(String),
Id(i64),
}
#[derive(Debug, Serialize)]
pub struct InfoSuccess {
pub id: i64,
pub name: String,
pub description: String,
pub created: i64,
pub members: Vec<(String, i64, u16)>,
}
#[derive(Debug)]
pub enum InfoResponse {
Success(InfoSuccess),
Blocked,
NotFound,
}

View File

@ -4,8 +4,9 @@ use crate::{
security::{is_sql_injection, AlphaExt},
tokens::AuthToken,
};
use anyhow::Result;
use anyhow::{Error, Result};
use sqlx::PgPool;
use crate::accounts::Account;
pub async fn create(
pool: &PgPool,
@ -35,3 +36,38 @@ pub async fn create(
id: project.id,
}))
}
pub async fn info(pool: &PgPool, request: data::InfoRequest) -> Result<data::InfoResponse> {
let project = match match request {
data::InfoRequest::Name(name) => {
if is_sql_injection(&name) {
return Ok(data::InfoResponse::Blocked);
}
Project::from_name(pool, &name).await?
}
data::InfoRequest::Id(id) => Project::from_id(pool, id).await?
}
{
Some(p) => p,
None => return Ok(data::InfoResponse::NotFound)
};
let mut members = Vec::new();
members.reserve(project.members.len());
for &id in project.members.iter() {
members.push((match Account::from_id(pool, id.id()).await? {
Some(m) => m.username,
None => return Err(Error::msg(format!("The account with id {} was not found", id.id()))),
},
id.id(),
id.flags()));
}
Ok(data::InfoResponse::Success(data::InfoSuccess {
id: project.id,
name: project.name,
description: project.description,
created: project.created.timestamp(),
members,
}))
}

View File

@ -8,7 +8,7 @@ pub struct Project {
pub name: String,
pub description: String,
pub created: chrono::NaiveDateTime,
pub members: Vec<i64>,
pub members: Vec<ID>,
}
pub enum ProjectMemberFlags {
@ -49,11 +49,38 @@ impl Project {
}
pub async fn from_name(pool: &PgPool, name: &String) -> Result<Option<Self>> {
match sqlx::query_as!(Project, r#"SELECT * FROM Projects WHERE name = $1;"#, name)
match sqlx::query!(r#"SELECT * FROM Projects WHERE name = $1;"#, name)
.fetch_one(pool)
.await
{
Ok(project) => Ok(Some(project)),
Ok(row) => {
Ok(Some(Self {
id: row.id,
name: row.name,
description: row.description,
created: row.created,
members: row.members.iter().map(|&raw_id| raw_id.into()).collect::<Vec<ID>>(),
}))
},
Err(sqlx::Error::RowNotFound) => Ok(None),
Err(e) => Err(Error::new(e)),
}
}
pub async fn from_id(pool: &PgPool, id: i64) -> Result<Option<Self>> {
match sqlx::query!(r#"SELECT * FROM Projects WHERE id = $1;"#, id)
.fetch_one(pool)
.await
{
Ok(row) => {
Ok(Some(Self {
id: row.id,
name: row.name,
description: row.description,
created: row.created,
members: row.members.iter().map(|&raw_id| raw_id.into()).collect::<Vec<ID>>(),
}))
},
Err(sqlx::Error::RowNotFound) => Ok(None),
Err(e) => Err(Error::new(e)),
}