feat(api): implemented /project/create
This commit is contained in:
parent
45cdf93536
commit
83a2dabf87
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))?
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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,
|
||||
}))
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
pub mod calls;
|
||||
pub mod data;
|
||||
mod handlers;
|
|
@ -1,5 +1,6 @@
|
|||
mod accounts;
|
||||
mod api;
|
||||
mod projects;
|
||||
mod security;
|
||||
mod tokens;
|
||||
|
||||
|
|
|
@ -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)),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue