Initial rust API for a task tracking app.

This commit is contained in:
Drew Galbraith 2024-07-02 16:56:02 -07:00
commit f507825f5f
9 changed files with 1988 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
target/
.env
*.db

1858
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

12
Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "captains-log"
version = "0.1.0"
edition = "2021"
[dependencies]
axum = "0.7"
dotenvy = "0.15"
serde = "1.0"
serde_json = "1.0"
sqlx = { version = "0.7", features=[ "runtime-tokio", "sqlite" ] }
tokio = { version = "1.38", features=[ "full" ] }

8
src/global.rs Normal file
View File

@ -0,0 +1,8 @@
use std::sync::Arc;
use sqlx::SqlitePool;
#[derive(Clone)]
pub struct AppState {
pub db_pool: Arc<SqlitePool>,
}

41
src/main.rs Normal file
View File

@ -0,0 +1,41 @@
mod global;
mod models;
mod routes;
use axum::http::{StatusCode, Uri};
use axum::response::IntoResponse;
use axum::routing::get;
use dotenvy::dotenv;
use sqlx::SqlitePool;
#[tokio::main]
async fn main() {
if let Err(e) = dotenv() {
println!("WARN: did not read .env file: {}", e);
}
let database_url = std::env::var("DATABASE_URL").unwrap();
let db_pool = SqlitePool::connect(&database_url).await.unwrap();
let state = global::AppState {
db_pool: db_pool.into(),
};
let app = axum::Router::new()
.fallback(handle404)
.route(
"/tasks",
get(routes::tasks::list).post(routes::tasks::create),
)
.with_state(state);
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
.await
.unwrap();
let server = axum::serve(listener, app);
println!("Listening on http://127.0.0.1:3000");
server.await.unwrap();
}
async fn handle404(uri: Uri) -> impl IntoResponse {
(StatusCode::NOT_FOUND, format!("URI: {} Not Found", uri))
}

3
src/models/mod.rs Normal file
View File

@ -0,0 +1,3 @@
mod task;
pub use task::Task;

35
src/models/task.rs Normal file
View File

@ -0,0 +1,35 @@
use serde::{Deserialize, Serialize};
use sqlx::{FromRow, SqlitePool};
#[derive(Serialize, Deserialize, FromRow)]
pub struct Task {
id: i64,
title: String,
description: Option<String>,
}
impl Task {
pub fn new(title: String, description: Option<String>) -> Task {
Task {
id: 0,
title,
description,
}
}
pub async fn all(pool: &SqlitePool) -> Result<Vec<Task>, sqlx::Error> {
sqlx::query_as::<_, Task>("SELECT id, title, description FROM task")
.fetch_all(pool)
.await
}
pub async fn insert(&self, pool: &SqlitePool) -> Result<Task, sqlx::Error> {
sqlx::query_as!(
Task,
"INSERT INTO task (title, description) VALUES (?1, ?2) RETURNING *",
self.title,
self.description,
)
.fetch_one(pool)
.await
}
}

1
src/routes/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod tasks;

26
src/routes/tasks.rs Normal file
View File

@ -0,0 +1,26 @@
use axum::extract::{Json, State};
use axum::http::StatusCode;
use axum::response::IntoResponse;
use serde::Deserialize;
use crate::{global::AppState, models::Task};
pub async fn list(state: State<AppState>) -> impl IntoResponse {
let tasks = Task::all(&state.db_pool).await.unwrap();
(StatusCode::OK, serde_json::to_string(&tasks).unwrap())
}
#[derive(Deserialize)]
pub struct NewTask {
title: String,
description: Option<String>,
}
pub async fn create(state: State<AppState>, Json(req): Json<NewTask>) -> impl IntoResponse {
let task = Task::new(req.title, req.description);
let new_task = task.insert(&state.db_pool).await.unwrap();
(StatusCode::OK, serde_json::to_string(&new_task).unwrap())
}