Start implementing actual methods
This commit is contained in:
		
							parent
							
								
									202ce82c9d
								
							
						
					
					
						commit
						68cc867d1d
					
				| 
						 | 
					@ -2,6 +2,6 @@ CREATE TABLE work_periods (
 | 
				
			||||||
    id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
 | 
					    id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
 | 
				
			||||||
    project VARCHAR NOT NULL,
 | 
					    project VARCHAR NOT NULL,
 | 
				
			||||||
    start_time TIMESTAMP NOT NULL,
 | 
					    start_time TIMESTAMP NOT NULL,
 | 
				
			||||||
    end_time TIMESTAMP NOT NULL,
 | 
					    end_time TIMESTAMP,
 | 
				
			||||||
    description VARCHAR
 | 
					    description VARCHAR
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										162
									
								
								src/main.rs
								
								
								
								
							
							
						
						
									
										162
									
								
								src/main.rs
								
								
								
								
							| 
						 | 
					@ -1,19 +1,22 @@
 | 
				
			||||||
 | 
					use chrono::TimeZone;
 | 
				
			||||||
use diesel::prelude::*;
 | 
					use diesel::prelude::*;
 | 
				
			||||||
use dotenvy::dotenv;
 | 
					use dotenvy::dotenv;
 | 
				
			||||||
use self::models::*;
 | 
					use self::models::*;
 | 
				
			||||||
use std::net::SocketAddr;
 | 
					use std::net::SocketAddr;
 | 
				
			||||||
use std::env;
 | 
					use std::env;
 | 
				
			||||||
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
use axum::{
 | 
					use axum::{
 | 
				
			||||||
    extract::State,
 | 
					    extract::{State, Path, Query},
 | 
				
			||||||
    routing::{get, post},
 | 
					    routing::{get, post, delete, put},
 | 
				
			||||||
    http::StatusCode,
 | 
					    http::StatusCode,
 | 
				
			||||||
    Json, Router,
 | 
					    Json, Router,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
 | 
					use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
 | 
				
			||||||
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations/");
 | 
					use crate::schema::work_periods;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub mod models;
 | 
					pub mod models;
 | 
				
			||||||
pub mod schema;
 | 
					pub mod schema;
 | 
				
			||||||
 | 
					pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations/");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[tokio::main]
 | 
					#[tokio::main]
 | 
				
			||||||
async fn main() {
 | 
					async fn main() {
 | 
				
			||||||
| 
						 | 
					@ -39,7 +42,16 @@ async fn main() {
 | 
				
			||||||
    let app = Router::new()
 | 
					    let app = Router::new()
 | 
				
			||||||
        // `GET /` goes to `root`
 | 
					        // `GET /` goes to `root`
 | 
				
			||||||
        .route("/", get(root))
 | 
					        .route("/", get(root))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .route("/history", post(add_period))
 | 
					        .route("/history", post(add_period))
 | 
				
			||||||
 | 
					        .route("/history", get(get_history))
 | 
				
			||||||
 | 
					        .route("/history/:id", put(update_period))
 | 
				
			||||||
 | 
					        .route("/history/:id", delete(delete_period))
 | 
				
			||||||
 | 
					        .route("/history/:id", get(get_period))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .route("/tracking", get(get_tracking))
 | 
				
			||||||
 | 
					        .route("/tracking", post(start_tracking))
 | 
				
			||||||
 | 
					        .route("/tracking", delete(stop_tracking))
 | 
				
			||||||
        .with_state(pool);
 | 
					        .with_state(pool);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // run our app with hyper, listening globally on port 3000
 | 
					    // run our app with hyper, listening globally on port 3000
 | 
				
			||||||
| 
						 | 
					@ -49,14 +61,79 @@ async fn main() {
 | 
				
			||||||
    axum::serve(listener, app).await.unwrap();
 | 
					    axum::serve(listener, app).await.unwrap();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Deserialize)]
 | 
				
			||||||
 | 
					struct HistoryQuery {
 | 
				
			||||||
 | 
					    count: Option<i64>,
 | 
				
			||||||
 | 
					    since: Option<chrono::NaiveDateTime>,
 | 
				
			||||||
 | 
					    until: Option<chrono::NaiveDateTime>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn get_history(
 | 
				
			||||||
 | 
					    State(pool): State<deadpool_diesel::sqlite::Pool>,
 | 
				
			||||||
 | 
					    query: Query<HistoryQuery>,
 | 
				
			||||||
 | 
					) -> Result<Json<Vec<WorkPeriod>>, (StatusCode, Json<Error>)> {
 | 
				
			||||||
 | 
					    let count = query.count.unwrap_or(10);
 | 
				
			||||||
 | 
					    let start = query.since.unwrap_or_else(|| chrono::Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap().naive_utc());
 | 
				
			||||||
 | 
					    let end = query.until.unwrap_or_else(|| chrono::Utc::now().naive_utc());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let conn = pool.get().await.map_err(internal_error)?;
 | 
				
			||||||
 | 
					    let res = conn.interact(move |conn|
 | 
				
			||||||
 | 
					        work_periods::table.select(WorkPeriod::as_select())
 | 
				
			||||||
 | 
					        .filter(work_periods::start_time.between(start, end))
 | 
				
			||||||
 | 
					        .order(work_periods::start_time.desc())
 | 
				
			||||||
 | 
					        .limit(count)
 | 
				
			||||||
 | 
					        .load(conn)
 | 
				
			||||||
 | 
					    ).await
 | 
				
			||||||
 | 
					        .map_err(internal_error)?
 | 
				
			||||||
 | 
					        .map_err(internal_error)?;
 | 
				
			||||||
 | 
					    Ok(Json(res))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn get_tracking(
 | 
				
			||||||
 | 
					    State(pool): State<deadpool_diesel::sqlite::Pool>,
 | 
				
			||||||
 | 
					) -> Result<Json<Vec<WorkPeriod>>, (StatusCode, Json<Error>)> {
 | 
				
			||||||
 | 
					    let conn = pool.get().await.map_err(internal_error)?;
 | 
				
			||||||
 | 
					    let res = conn
 | 
				
			||||||
 | 
					        .interact(|conn| work_periods::table.filter(work_periods::end_time.is_null()).select(WorkPeriod::as_select()).load(conn))
 | 
				
			||||||
 | 
					        .await
 | 
				
			||||||
 | 
					        .map_err(internal_error)?
 | 
				
			||||||
 | 
					        .map_err(internal_error)?;
 | 
				
			||||||
 | 
					    Ok(Json(res))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// basic handler that responds with a static string
 | 
					// basic handler that responds with a static string
 | 
				
			||||||
async fn root() -> &'static str {
 | 
					async fn root() -> &'static str {
 | 
				
			||||||
    "Hello, World!"
 | 
					    "Hello, World!"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async fn add_period(State(pool): State<deadpool_diesel::sqlite::Pool>, Json(payload): Json<NewPeriod>) -> Result<(StatusCode, Json<WorkPeriod>), (StatusCode, String)> {
 | 
					async fn stop_tracking(State(pool): State<deadpool_diesel::sqlite::Pool>, payload: Option<Json<WorkPeriod>>) -> Result<StatusCode, (StatusCode, Json<Error>)> {
 | 
				
			||||||
    use crate::schema::work_periods;
 | 
					    pool.get().await.map_err(internal_error)?.interact(|conn|
 | 
				
			||||||
 | 
					        match payload {
 | 
				
			||||||
 | 
					            Some(Json(payload)) => {
 | 
				
			||||||
 | 
					                diesel::update(work_periods::table.filter(work_periods::end_time.is_null()).find(payload.id))
 | 
				
			||||||
 | 
					                    .set(work_periods::end_time.eq(Some(chrono::Utc::now().naive_utc())))
 | 
				
			||||||
 | 
					                    .execute(conn)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            None => {
 | 
				
			||||||
 | 
					                diesel::update(work_periods::table.filter(work_periods::end_time.is_null()))
 | 
				
			||||||
 | 
					                    .set(work_periods::end_time.eq(Some(chrono::Utc::now().naive_utc())))
 | 
				
			||||||
 | 
					                    .execute(conn)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ).await.map_err(internal_error)?.map_err(internal_error)?;
 | 
				
			||||||
 | 
					    Ok(StatusCode::OK)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn start_tracking(State(pool): State<deadpool_diesel::sqlite::Pool>, Json(payload): Json<NewPeriod>) -> Result<(StatusCode, Json<WorkPeriod>), (StatusCode, Json<Error>)> {
 | 
				
			||||||
 | 
					    if payload.end_time.is_some() || payload.start_time.is_some() {
 | 
				
			||||||
 | 
					        return Err((StatusCode::BAD_REQUEST, Json(Error {
 | 
				
			||||||
 | 
					            success: false,
 | 
				
			||||||
 | 
					            value: "Timestamps (start or end) cannot be specified when starting tracking.".to_string()
 | 
				
			||||||
 | 
					        })));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    let mut payload = payload.clone();
 | 
				
			||||||
 | 
					    payload.start_time = Some(chrono::Utc::now().naive_utc());
 | 
				
			||||||
    let conn = pool.get().await.map_err(internal_error)?;
 | 
					    let conn = pool.get().await.map_err(internal_error)?;
 | 
				
			||||||
    // insert your application logic here
 | 
					    // insert your application logic here
 | 
				
			||||||
    let res = conn
 | 
					    let res = conn
 | 
				
			||||||
| 
						 | 
					@ -75,9 +152,80 @@ async fn add_period(State(pool): State<deadpool_diesel::sqlite::Pool>, Json(payl
 | 
				
			||||||
    Ok((StatusCode::CREATED, Json(res)))
 | 
					    Ok((StatusCode::CREATED, Json(res)))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn internal_error<E>(err: E) -> (StatusCode, String)
 | 
					async fn add_period(State(pool): State<deadpool_diesel::sqlite::Pool>, Json(payload): Json<NewPeriod>) -> Result<(StatusCode, Json<WorkPeriod>), (StatusCode, Json<Error>)> {
 | 
				
			||||||
 | 
					    let conn = pool.get().await.map_err(internal_error)?;
 | 
				
			||||||
 | 
					    // insert your application logic here
 | 
				
			||||||
 | 
					    let res = conn
 | 
				
			||||||
 | 
					        .interact(move |conn| {
 | 
				
			||||||
 | 
					            diesel::insert_into(work_periods::table)
 | 
				
			||||||
 | 
					                .values(&payload)
 | 
				
			||||||
 | 
					                .returning(WorkPeriod::as_returning())
 | 
				
			||||||
 | 
					                .get_result(conn)
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    .await
 | 
				
			||||||
 | 
					        .map_err(internal_error)?
 | 
				
			||||||
 | 
					        .map_err(internal_error)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // this will be converted into a JSON response
 | 
				
			||||||
 | 
					    // with a status code of `201 Created`
 | 
				
			||||||
 | 
					    Ok((StatusCode::CREATED, Json(res)))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn update_period(State(pool): State<deadpool_diesel::sqlite::Pool>,
 | 
				
			||||||
 | 
					    Path(period_id): Path<i32>,
 | 
				
			||||||
 | 
					    Json(payload): Json<WorkPeriod>
 | 
				
			||||||
 | 
					    ) -> Result<(StatusCode, Json<WorkPeriod>), (StatusCode, Json<Error>)> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let conn = pool.get().await.map_err(internal_error)?;
 | 
				
			||||||
 | 
					    let res = conn.interact(move |conn| {
 | 
				
			||||||
 | 
					            diesel::update(work_periods::table.find(period_id))
 | 
				
			||||||
 | 
					                .set(payload)
 | 
				
			||||||
 | 
					                .get_result(conn)
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    .await
 | 
				
			||||||
 | 
					        .map_err(internal_error)?
 | 
				
			||||||
 | 
					        .map_err(internal_error)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok((StatusCode::OK, Json(res)))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn get_period(State(pool): State<deadpool_diesel::sqlite::Pool>, Path(period_id): Path<i32>) -> Result<Json<WorkPeriod>, (StatusCode, Json<Error>)> {
 | 
				
			||||||
 | 
					    let conn = pool.get().await.map_err(internal_error)?;
 | 
				
			||||||
 | 
					    let res = conn.interact(move |conn|
 | 
				
			||||||
 | 
					        work_periods::table.select(WorkPeriod::as_select()).find(period_id).first(conn)
 | 
				
			||||||
 | 
					    ).await
 | 
				
			||||||
 | 
					        .map_err(internal_error)?
 | 
				
			||||||
 | 
					        .map_err(internal_error)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(Json(res))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn delete_period(State(pool): State<deadpool_diesel::sqlite::Pool>, Path(period_id): Path<i32>) -> Result<(StatusCode, Json<WorkPeriod>), (StatusCode, Json<Error>)> {
 | 
				
			||||||
 | 
					    let conn = pool.get().await.map_err(internal_error)?;
 | 
				
			||||||
 | 
					    // insert your application logic here
 | 
				
			||||||
 | 
					    let res = conn
 | 
				
			||||||
 | 
					        .interact(move |conn| {
 | 
				
			||||||
 | 
					            diesel::delete(work_periods::table.find(period_id))
 | 
				
			||||||
 | 
					                .get_result(conn)
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    .await
 | 
				
			||||||
 | 
					        .map_err(internal_error)?
 | 
				
			||||||
 | 
					        .map_err(internal_error)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok((StatusCode::OK, Json(res)))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Serialize)]
 | 
				
			||||||
 | 
					struct Error {
 | 
				
			||||||
 | 
					    success: bool,
 | 
				
			||||||
 | 
					    value: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn internal_error<E>(err: E) -> (StatusCode, Json<Error>)
 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
    E: std::error::Error,
 | 
					    E: std::error::Error,
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    (StatusCode::INTERNAL_SERVER_ERROR, err.to_string())
 | 
					    (StatusCode::INTERNAL_SERVER_ERROR, Json(Error{success: false, value: err.to_string()}))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,23 +3,23 @@ use serde::{Deserialize, Serialize};
 | 
				
			||||||
use super::schema::work_periods;
 | 
					use super::schema::work_periods;
 | 
				
			||||||
use chrono::NaiveDateTime;
 | 
					use chrono::NaiveDateTime;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Queryable, Debug, Serialize, Deserialize, Selectable)]
 | 
					#[derive(Queryable, Debug, Serialize, Deserialize, Selectable, AsChangeset)]
 | 
				
			||||||
#[diesel(table_name = crate::schema::work_periods)]
 | 
					#[diesel(table_name = crate::schema::work_periods)]
 | 
				
			||||||
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
 | 
					#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
 | 
				
			||||||
pub struct WorkPeriod {
 | 
					pub struct WorkPeriod {
 | 
				
			||||||
    pub id: i32,
 | 
					    pub id: i32,
 | 
				
			||||||
    pub project: String,
 | 
					    pub project: String,
 | 
				
			||||||
    pub start_time: NaiveDateTime,
 | 
					    pub start_time: NaiveDateTime,
 | 
				
			||||||
    pub end_time: NaiveDateTime,
 | 
					    pub end_time: Option<NaiveDateTime>,
 | 
				
			||||||
    pub description: Option<String>,
 | 
					    pub description: Option<String>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Insertable, Serialize, Deserialize, Debug)]
 | 
					#[derive(Insertable, Serialize, Deserialize, Debug, Clone)]
 | 
				
			||||||
#[diesel(table_name = work_periods)]
 | 
					#[diesel(table_name = work_periods)]
 | 
				
			||||||
pub struct NewPeriod {
 | 
					pub struct NewPeriod {
 | 
				
			||||||
    pub project: String,
 | 
					    pub project: String,
 | 
				
			||||||
    pub start_time: NaiveDateTime,
 | 
					    pub start_time: Option<NaiveDateTime>,
 | 
				
			||||||
    pub end_time: NaiveDateTime,
 | 
					    pub end_time: Option<NaiveDateTime>,
 | 
				
			||||||
    pub description: Option<String>,
 | 
					    pub description: Option<String>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,7 @@ diesel::table! {
 | 
				
			||||||
        id -> Integer,
 | 
					        id -> Integer,
 | 
				
			||||||
        project -> Text,
 | 
					        project -> Text,
 | 
				
			||||||
        start_time -> Timestamp,
 | 
					        start_time -> Timestamp,
 | 
				
			||||||
        end_time -> Timestamp,
 | 
					        end_time -> Nullable<Timestamp>,
 | 
				
			||||||
        description -> Nullable<Text>,
 | 
					        description -> Nullable<Text>,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue