feat(api): implemented /project/create

This commit is contained in:
antifallobst 2023-08-23 18:16:36 +02:00
parent 45cdf93536
commit 83a2dabf87
Signed by: antifallobst
GPG Key ID: 2B4F402172791BAF
14 changed files with 216 additions and 19 deletions

View File

@ -0,0 +1,12 @@
{
"db_name": "PostgreSQL",
"query": "\n CREATE TABLE IF NOT EXISTS Projects (\n id SERIAL8 NOT NULL,\n name VARCHAR(32) NOT NULL,\n description TEXT NOT NULL,\n created TIMESTAMP NOT NULL,\n members BIGINT[] NOT NULL,\n PRIMARY KEY(id)\n );\n ",
"describe": {
"columns": [],
"parameters": {
"Left": []
},
"nullable": []
},
"hash": "12a6fa98cf544f2aad6c2fcf6f2b895ddf17ef53fd6dfd8d7373ad8e4cdfcb1e"
}

View File

@ -0,0 +1,46 @@
{
"db_name": "PostgreSQL",
"query": "SELECT * FROM Projects WHERE name = $1;",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "name",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "description",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "created",
"type_info": "Timestamp"
},
{
"ordinal": 4,
"name": "members",
"type_info": "Int8Array"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false,
false,
false,
false,
false
]
},
"hash": "2b90457c4ec97c1559cab9dba5f2547e27a2778debfc158106e365678a0f2141"
}

View File

@ -1,12 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n CREATE TABLE IF NOT EXISTS Projects (\n id SERIAL8 NOT NULL,\n name VARCHAR(32) NOT NULL,\n desription TEXT,\n created TIMESTAMP NOT NULL,\n members BIGINT[][] NOT NULL,\n PRIMARY KEY(id)\n );\n ",
"describe": {
"columns": [],
"parameters": {
"Left": []
},
"nullable": []
},
"hash": "5a65288ceaee7044d547a04600d50408249ae7f03356121968abf1039407ec3e"
}

View File

@ -0,0 +1,17 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO Projects (name, description, created, members) VALUES ($1, $2, $3, $4);",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Text",
"Timestamp",
"Int8Array"
]
},
"nullable": []
},
"hash": "714a62e127cb828940bf21d21ad1fb2a37a8ad7a7458e6d727009a3513f8879c"
}

View File

@ -21,4 +21,4 @@ sqlx = { version = "0.7.1", features = ["runtime-tokio", "postgres", "chrono"] }
uuid = { version = "1.4.1", features = ["v4"] }
chrono = "0.4"
mail-send = "0.4.0"
regex = "1.9.3"
regex = "1.9.3"

View File

@ -56,7 +56,7 @@ Date: Sun, 20 Aug 2023 13:37:35 GMT
Server: nginx/1.24.0
Strict-Transport-Security: max-age=31536000; includeSubDomains
```
This sends an verification token to the email you specified in the request body.
This sends a verification token to the email you specified in the request body.
Such a token looks like this: `f68b0ee33bbe4850991993c361997003`.
### 2. Verify the created account

View File

@ -21,6 +21,8 @@ __Content - JSON:__
| id | The created projects unique id. |
### 400 - Error: Bad Request
The request was malformed.
### 401 - Error: Unauthorized
The provided access token doesn't allow you to perfrom this operation.
### 403 - Error: Forbidden
Blocked for security reasons.
### 409 - Error: Conflict

View File

@ -1,4 +1,5 @@
mod account;
mod project;
use actix_web::{web, App, HttpServer};
use anyhow::Result;
@ -59,11 +60,11 @@ pub async fn start(port: u16, pool: PgPool) -> Result<()> {
sqlx::query!(
r#"
CREATE TABLE IF NOT EXISTS Projects (
id SERIAL8 NOT NULL,
name VARCHAR(32) NOT NULL,
desription TEXT,
created TIMESTAMP NOT NULL,
members BIGINT[][] NOT NULL,
id SERIAL8 NOT NULL,
name VARCHAR(32) NOT NULL,
description TEXT NOT NULL,
created TIMESTAMP NOT NULL,
members BIGINT[] NOT NULL,
PRIMARY KEY(id)
);
"#
@ -90,6 +91,7 @@ pub async fn start(port: u16, pool: PgPool) -> Result<()> {
.service(account::calls::delete)
.service(account::calls::tokens_delete)
.service(account::calls::tokens_get)
.service(project::calls::create)
.app_data(web::Data::new(ApiState { pool: pool.clone() }))
})
.bind(("0.0.0.0", port))?

21
src/api/project/calls.rs Normal file
View File

@ -0,0 +1,21 @@
use crate::api::ApiState;
use crate::api::project::{data, handlers};
use actix_web::{get, post, web, HttpResponse, Responder};
use actix_web_httpauth::extractors::bearer::BearerAuth;
use log::error;
#[post("/project/create")]
async fn create(data: web::Data<ApiState>, auth: BearerAuth, body: web::Json<data::CreateRequest>) -> impl Responder {
match handlers::create(&data.pool, auth.token().to_string(), body.into_inner()).await {
Ok(resp) => match resp {
data::CreateResponse::Success(b) => HttpResponse::Ok().json(web::Json(b)),
data::CreateResponse::Conflict => HttpResponse::Conflict().finish(),
data::CreateResponse::Unauthorized => HttpResponse::Unauthorized().finish(),
data::CreateResponse::Blocked => HttpResponse::Forbidden().finish(),
},
Err(e) => {
error!("While handling register request: {e}");
HttpResponse::InternalServerError().finish()
}
}
}

20
src/api/project/data.rs Normal file
View File

@ -0,0 +1,20 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]
pub struct CreateRequest {
pub name: String,
pub description: String,
}
#[derive(Debug, Serialize)]
pub struct CreateSuccess {
pub id: i64,
}
#[derive(Debug)]
pub enum CreateResponse {
Success(CreateSuccess),
Unauthorized,
Blocked,
Conflict,
}

View File

@ -0,0 +1,37 @@
use crate::{
api::project::data,
projects::Project,
security::{is_sql_injection, AlphaExt},
tokens::AuthToken,
};
use anyhow::Result;
use sqlx::PgPool;
pub async fn create(
pool: &PgPool,
auth: String,
request: data::CreateRequest,
) -> Result<data::CreateResponse> {
if !auth.is_alpha() {
return Ok(data::CreateResponse::Blocked);
}
let token = match AuthToken::check(pool, &auth).await? {
Some(t) => t,
None => return Ok(data::CreateResponse::Unauthorized),
};
if is_sql_injection(&request.name) || is_sql_injection(&request.description) {
return Ok(data::CreateResponse::Blocked);
}
if let Some(_) = Project::from_name(pool, &request.name).await? {
return Ok(data::CreateResponse::Conflict);
}
let project = Project::new(pool, token.account, request.name, request.description).await?;
Ok(data::CreateResponse::Success(data::CreateSuccess {
id: project.id,
}))
}

3
src/api/project/mod.rs Normal file
View File

@ -0,0 +1,3 @@
pub mod calls;
pub mod data;
mod handlers;

View File

@ -1,5 +1,6 @@
mod accounts;
mod api;
mod projects;
mod security;
mod tokens;

48
src/projects.rs Normal file
View File

@ -0,0 +1,48 @@
use anyhow::{Error, Result};
use sqlx::postgres::PgPool;
#[derive(Debug)]
pub struct Project {
pub id: i64,
pub name: String,
pub description: String,
pub created: chrono::NaiveDateTime,
pub members: Vec<i64>,
}
impl Project {
pub async fn new(
pool: &PgPool,
owner_id: i64,
name: String,
description: String,
) -> Result<Self> {
let members = vec![owner_id];
sqlx::query!(
r#"INSERT INTO Projects (name, description, created, members) VALUES ($1, $2, $3, $4);"#,
name,
description,
chrono::Utc::now().naive_utc(),
&members,
).execute(pool).await?;
match Project::from_name(pool, &name).await? {
Some(project) => Ok(project),
None => Err(Error::msg(
"The just created project can't be found in the database!",
)),
}
}
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)
.fetch_one(pool)
.await
{
Ok(project) => Ok(Some(project)),
Err(sqlx::Error::RowNotFound) => Ok(None),
Err(e) => Err(Error::new(e)),
}
}
}