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,
|
||||
project VARCHAR NOT NULL,
|
||||
start_time TIMESTAMP NOT NULL,
|
||||
end_time TIMESTAMP NOT NULL,
|
||||
end_time TIMESTAMP,
|
||||
description VARCHAR
|
||||
)
|
||||
|
|
162
src/main.rs
162
src/main.rs
|
@ -1,19 +1,22 @@
|
|||
use chrono::TimeZone;
|
||||
use diesel::prelude::*;
|
||||
use dotenvy::dotenv;
|
||||
use self::models::*;
|
||||
use std::net::SocketAddr;
|
||||
use std::env;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use axum::{
|
||||
extract::State,
|
||||
routing::{get, post},
|
||||
extract::{State, Path, Query},
|
||||
routing::{get, post, delete, put},
|
||||
http::StatusCode,
|
||||
Json, Router,
|
||||
};
|
||||
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
|
||||
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations/");
|
||||
use crate::schema::work_periods;
|
||||
|
||||
pub mod models;
|
||||
pub mod schema;
|
||||
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations/");
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
|
@ -39,7 +42,16 @@ async fn main() {
|
|||
let app = Router::new()
|
||||
// `GET /` goes to `root`
|
||||
.route("/", get(root))
|
||||
|
||||
.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);
|
||||
|
||||
// run our app with hyper, listening globally on port 3000
|
||||
|
@ -49,14 +61,79 @@ async fn main() {
|
|||
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
|
||||
async fn root() -> &'static str {
|
||||
"Hello, World!"
|
||||
}
|
||||
|
||||
async fn add_period(State(pool): State<deadpool_diesel::sqlite::Pool>, Json(payload): Json<NewPeriod>) -> Result<(StatusCode, Json<WorkPeriod>), (StatusCode, String)> {
|
||||
use crate::schema::work_periods;
|
||||
async fn stop_tracking(State(pool): State<deadpool_diesel::sqlite::Pool>, payload: Option<Json<WorkPeriod>>) -> Result<StatusCode, (StatusCode, Json<Error>)> {
|
||||
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)?;
|
||||
// insert your application logic here
|
||||
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)))
|
||||
}
|
||||
|
||||
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
|
||||
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 chrono::NaiveDateTime;
|
||||
|
||||
#[derive(Queryable, Debug, Serialize, Deserialize, Selectable)]
|
||||
#[derive(Queryable, Debug, Serialize, Deserialize, Selectable, AsChangeset)]
|
||||
#[diesel(table_name = crate::schema::work_periods)]
|
||||
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
|
||||
pub struct WorkPeriod {
|
||||
pub id: i32,
|
||||
pub project: String,
|
||||
pub start_time: NaiveDateTime,
|
||||
pub end_time: NaiveDateTime,
|
||||
pub end_time: Option<NaiveDateTime>,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Insertable, Serialize, Deserialize, Debug)]
|
||||
#[derive(Insertable, Serialize, Deserialize, Debug, Clone)]
|
||||
#[diesel(table_name = work_periods)]
|
||||
pub struct NewPeriod {
|
||||
pub project: String,
|
||||
pub start_time: NaiveDateTime,
|
||||
pub end_time: NaiveDateTime,
|
||||
pub start_time: Option<NaiveDateTime>,
|
||||
pub end_time: Option<NaiveDateTime>,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ diesel::table! {
|
|||
id -> Integer,
|
||||
project -> Text,
|
||||
start_time -> Timestamp,
|
||||
end_time -> Timestamp,
|
||||
end_time -> Nullable<Timestamp>,
|
||||
description -> Nullable<Text>,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue