feat(api): implemented a basic api skeleton, that matches the api docs
This commit is contained in:
parent
e84ec87ea0
commit
9db5c04cb6
2
API.md
2
API.md
|
@ -101,5 +101,5 @@ Deletes the account.
|
|||
#### Responses
|
||||
##### 200 - Success
|
||||
The account was deleted.
|
||||
##### 403 - Error: Forbidden
|
||||
##### 401 - Error: Unauthorized
|
||||
The provided token doesn't allow you to perform this operation.
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "nerdcult_api"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.29", features = ["macros", "rt-multi-thread", "sync"] }
|
||||
anyhow = "1.0"
|
||||
actix-web = "4"
|
||||
serde = { version = "1.0.183", features = ["derive"] }
|
||||
libinjection = "0.3.2"
|
||||
sha2 = "0.10.2"
|
||||
pbkdf2 = { version = "0.12", features = ["simple"] }
|
||||
env_logger = "0.10"
|
||||
log = "0.4"
|
||||
clap = { version = "4.3.21", features = ["derive"] }
|
||||
actix-web-httpauth = "0.8.0"
|
|
@ -0,0 +1,70 @@
|
|||
use crate::api::account::{data, handlers};
|
||||
use crate::api::ApiState;
|
||||
use actix_web::{delete, post, web, HttpResponse, Responder};
|
||||
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||
use log::error;
|
||||
|
||||
#[post("/account/register")]
|
||||
async fn register(
|
||||
data: web::Data<ApiState>,
|
||||
body: web::Json<data::RegisterRequest>,
|
||||
) -> impl Responder {
|
||||
match handlers::register(body.into_inner()).await {
|
||||
Ok(resp) => match resp {
|
||||
data::RegisterResponse::Success => HttpResponse::Ok().finish(),
|
||||
data::RegisterResponse::PasswordTooWeak => HttpResponse::Forbidden().finish(),
|
||||
data::RegisterResponse::Conflict(b) => HttpResponse::Conflict().json(web::Json(b)),
|
||||
data::RegisterResponse::MalformedEmail => HttpResponse::UnprocessableEntity().finish(),
|
||||
},
|
||||
Err(e) => {
|
||||
error!("While handling register request: {e}");
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/account/verify")]
|
||||
async fn verify(data: web::Data<ApiState>, body: web::Json<data::VerifyRequest>) -> impl Responder {
|
||||
match handlers::verify(body.into_inner()).await {
|
||||
Ok(resp) => match resp {
|
||||
data::VerifyResponse::Success => HttpResponse::Ok().finish(),
|
||||
data::VerifyResponse::TokenUnknown => HttpResponse::Forbidden().finish(),
|
||||
},
|
||||
Err(e) => {
|
||||
error!("While handling verify request: {e}");
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/account/authenticate")]
|
||||
async fn authenticate(
|
||||
data: web::Data<ApiState>,
|
||||
body: web::Json<data::AuthenticateRequest>,
|
||||
) -> impl Responder {
|
||||
match handlers::authenticate(body.into_inner()).await {
|
||||
Ok(resp) => match resp {
|
||||
data::AuthenticateResponse::Success(b) => HttpResponse::Ok().json(web::Json(b)),
|
||||
data::AuthenticateResponse::WrongPassword => HttpResponse::Forbidden().finish(),
|
||||
data::AuthenticateResponse::UserNotFound => HttpResponse::NotFound().finish(),
|
||||
},
|
||||
Err(e) => {
|
||||
error!("While handling authenticate request: {e}");
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[delete("/account/delete")]
|
||||
async fn delete(data: web::Data<ApiState>, auth: BearerAuth) -> impl Responder {
|
||||
match handlers::delete(auth.token().to_string()).await {
|
||||
Ok(resp) => match resp {
|
||||
data::DeleteResponse::Success => HttpResponse::Ok().finish(),
|
||||
data::DeleteResponse::Unauthorized => HttpResponse::Unauthorized().finish(),
|
||||
},
|
||||
Err(e) => {
|
||||
error!("While handling delete request: {e}");
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct RegisterRequest {
|
||||
username: String,
|
||||
password: String,
|
||||
email: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(tag = "conflict", rename_all = "snake_case")]
|
||||
pub enum RegisterConflict {
|
||||
Username,
|
||||
Email,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RegisterResponse {
|
||||
Success,
|
||||
PasswordTooWeak,
|
||||
Conflict(RegisterConflict),
|
||||
MalformedEmail,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct VerifyRequest {
|
||||
token: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum VerifyResponse {
|
||||
Success,
|
||||
TokenUnknown,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct AuthenticateRequest {
|
||||
username: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct AuthenticateSuccess {
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AuthenticateResponse {
|
||||
Success(AuthenticateSuccess),
|
||||
WrongPassword,
|
||||
UserNotFound,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DeleteResponse {
|
||||
Success,
|
||||
Unauthorized,
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
use crate::api::account::data;
|
||||
use anyhow::Result;
|
||||
use log::info;
|
||||
|
||||
pub async fn register(request: data::RegisterRequest) -> Result<data::RegisterResponse> {
|
||||
Ok(data::RegisterResponse::Success)
|
||||
}
|
||||
|
||||
pub async fn verify(request: data::VerifyRequest) -> Result<data::VerifyResponse> {
|
||||
Ok(data::VerifyResponse::Success)
|
||||
}
|
||||
|
||||
pub async fn authenticate(
|
||||
request: data::AuthenticateRequest,
|
||||
) -> Result<data::AuthenticateResponse> {
|
||||
Ok(data::AuthenticateResponse::Success(
|
||||
data::AuthenticateSuccess {
|
||||
token: "not_a_valid_token".to_string(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn delete(token: String) -> Result<data::DeleteResponse> {
|
||||
info!("Token: {}", token);
|
||||
Ok(data::DeleteResponse::Success)
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
pub mod calls;
|
||||
pub mod data;
|
||||
mod handlers;
|
|
@ -0,0 +1,25 @@
|
|||
mod account;
|
||||
|
||||
use actix_web::{web, App, HttpServer};
|
||||
use anyhow::Result;
|
||||
|
||||
use log::info;
|
||||
|
||||
struct ApiState {}
|
||||
|
||||
pub async fn start(port: u16) -> Result<()> {
|
||||
info!("HTTP server starting on port {} ...", port);
|
||||
let _ = HttpServer::new(move || {
|
||||
App::new()
|
||||
.service(account::calls::register)
|
||||
.service(account::calls::verify)
|
||||
.service(account::calls::authenticate)
|
||||
.service(account::calls::delete)
|
||||
.app_data(web::Data::new(ApiState {}))
|
||||
})
|
||||
.bind(("127.0.0.1", port))?
|
||||
.run()
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
mod api;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// Sets the port on which the API listens
|
||||
#[arg(short, long)]
|
||||
port: Option<u16>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
||||
let args = Args::parse();
|
||||
|
||||
info!("Starting NerdcultAPI v0.1");
|
||||
|
||||
let mut port: u16 = 8080;
|
||||
if let Some(p) = args.port {
|
||||
port = p;
|
||||
}
|
||||
|
||||
api::start(port).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue