feat: category crud

This commit is contained in:
acx
2024-07-09 17:06:20 +00:00
parent bddf92686c
commit 7270399f35
18 changed files with 1913 additions and 3 deletions

124
src/category/handler.rs Normal file
View File

@@ -0,0 +1,124 @@
// use std::sync::Arc;
use axum::{http::StatusCode, extract::{State,Path}, Json, Router};
use axum::routing::{get, post};
use diesel::prelude::*;
// use diesel::update;
use serde::{Deserialize, Serialize};
// use serde_json::to_string;
use crate::util;
use crate::model::schema;
use crate::model::db_model;
// use crate::model::schema::categories::dsl::categories;
use crate::util::req::CommonResp;
use chrono::prelude::*;
#[derive(Serialize)]
pub struct CreateCategoryResponse {
id: i64,
name: String,
}
pub fn get_nest_handlers() -> Router<crate::AppState> {
Router::new()
.route("/", post(create_category).get(get_all_categories))
.route("/:id", post(update_category).get(get_category))
}
#[derive(Deserialize)]
pub struct CreateCategoryRequest {
name: String,
}
pub async fn create_category(
State(app_state): State<crate::AppState>,
Json(Payload): Json<CreateCategoryRequest>,
) -> Result<Json<db_model::Category>, (StatusCode, String)> {
let uid: i64 = 123214124; // TODO replace with actual user id.
// let ret = CreateCategoryResponse{id: 134132413541, name: "24532452".to_string()};
let conn = app_state.db.get().await.map_err(util::req::internal_error)?;
let new_category = db_model::CategoryForm{
name: Payload.name,
uid: uid,
};
let res = conn
.interact(move |conn| {
diesel::insert_into(schema::categories::table)
.values(&new_category)
.returning(db_model::Category::as_returning())
.get_result(conn)
})
.await
.map_err(util::req::internal_error)?
.map_err(util::req::internal_error)?;
// let ret = CreateCategoryResponse{id: res.id, name: res.name};
Ok(Json(res))
}
pub async fn update_category(
Path(id): Path<i64>,
State(app_state): State<crate::AppState>,
Json(Payload): Json<CreateCategoryRequest>,
) -> Result<Json<CommonResp>, (StatusCode, String)> {
let uid: i64 = 123214124; // TODO replace with actual user id.
// let ret = CreateCategoryResponse{id: 134132413541, name: "24532452".to_string()};
let conn = app_state.db.get().await.map_err(util::req::internal_error)?;
let now = Utc::now().naive_utc();
let res = conn
.interact(move |conn| {
diesel::update(schema::categories::table)
.filter(schema::categories::id.eq(id))
.filter(schema::categories::uid.eq(uid))
.set((
schema::categories::name.eq(Payload.name),
schema::categories::update_at.eq(now),
))
.execute(conn)
})
.await
.map_err(util::req::internal_error)?
.map_err(util::req::internal_error)?;
// let ret = CreateCategoryResponse{id: res.id, name: res.name};
let resp = util::req::CommonResp{
code: 0,
};
Ok(Json(resp))
}
pub async fn get_category(
Path(id): Path<i64>,
State(app_state): State<crate::AppState>,
) -> Result<Json<db_model::Category>, (StatusCode, String)>{
let uid: i64 = 123214124; // TODO replace with actual user id.
let conn = app_state.db.get().await.map_err(util::req::internal_error)?;
let res = conn
.interact(move |conn| {
schema::categories::table
.filter(schema::categories::id.eq(id))
.filter(schema::categories::uid.eq(uid))
.select(db_model::Category::as_select())
.limit(1)
.get_result(conn)
})
.await
.map_err(util::req::internal_error)?
.map_err(util::req::internal_error)?;
Ok(Json(res))
}
pub async fn get_all_categories(
State(app_state): State<crate::AppState>,
) -> Result<Json<Vec<db_model::Category>>, (StatusCode, String)>{
let uid: i64 = 123214124; // TODO replace with actual user id.
let conn = app_state.db.get().await.map_err(util::req::internal_error)?;
let res = conn
.interact(move |conn| {
schema::categories::table
.filter(schema::categories::uid.eq(uid))
.select(db_model::Category::as_select())
.load(conn)
})
.await
.map_err(util::req::internal_error)?
.map_err(util::req::internal_error)?;
Ok(Json(res))
}

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

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

View File

@@ -1,3 +1,46 @@
fn main() {
println!("Hello, world!");
use axum::{
// http::StatusCode,
// routing::{get, post},
// Json,
Router,
};
use serde::{Deserialize, Serialize};
use tracing::info;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
// Project modules
mod category;
mod model;
mod util;
// Passed App State
#[derive(Clone)]
pub struct AppState{
db: deadpool_diesel::postgres::Pool,
}
#[tokio::main]
async fn main() {
dotenvy::dotenv().unwrap();
tracing_subscriber::registry().with(tracing_subscriber::fmt::layer()).init();
// initialize db connection
let db_url = std::env::var("DATABASE_URL").unwrap();
let manager = deadpool_diesel::postgres::Manager::new(db_url, deadpool_diesel::Runtime::Tokio1);
let pool = deadpool_diesel::postgres::Pool::builder(manager)
.build()
.unwrap();
let shared_state = AppState {db: pool,};
// Register routers
let app = Router::new()
// V1 apis
.nest("/api/v1/category", category::handler::get_nest_handlers())
.with_state(shared_state);
let listener = tokio::net::TcpListener::bind("0.0.0.0:8987").await.unwrap();
info!("starting server on 0.0.0.0:8987");
axum::serve(listener, app).await.unwrap();
}

22
src/model/db_model.rs Normal file
View File

@@ -0,0 +1,22 @@
use diesel::prelude::*;
use crate::model::schema;
#[derive(Queryable, Selectable)]
#[derive(serde::Serialize, serde::Deserialize)]
#[diesel(table_name = schema::categories)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct Category {
id: i64,
uid: i64,
name: String,
is_delete: bool,
create_at: chrono::NaiveDateTime,
update_at: chrono::NaiveDateTime,
}
#[derive(serde::Deserialize, Insertable)]
#[diesel(table_name = schema::categories)]
pub struct CategoryForm {
pub uid: i64,
pub name: String,
}

2
src/model/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
pub mod schema;
pub mod db_model;

109
src/model/schema.rs Normal file
View File

@@ -0,0 +1,109 @@
// @generated automatically by Diesel CLI.
diesel::table! {
accounts (id) {
id -> Int8,
uid -> Int8,
name -> Text,
#[sql_name = "type"]
type_ -> Int8,
is_delete -> Bool,
create_at -> Timestamp,
update_at -> Timestamp,
}
}
diesel::table! {
amounts (id) {
id -> Int8,
uid -> Int8,
transaction_id -> Int8,
value -> Int8,
expo -> Int8,
is_delete -> Bool,
create_at -> Timestamp,
update_at -> Timestamp,
}
}
diesel::table! {
books (id) {
id -> Int8,
uid -> Int8,
name -> Text,
is_delete -> Bool,
create_at -> Timestamp,
update_at -> Timestamp,
}
}
diesel::table! {
categories (id) {
id -> Int8,
uid -> Int8,
name -> Text,
is_delete -> Bool,
create_at -> Timestamp,
update_at -> Timestamp,
}
}
diesel::table! {
tags (id) {
id -> Int8,
uid -> Int8,
name -> Text,
is_delete -> Bool,
create_at -> Timestamp,
update_at -> Timestamp,
}
}
diesel::table! {
transaction_tag_rels (id) {
id -> Int8,
uid -> Int8,
transaction_id -> Int8,
tag_id -> Int8,
is_delete -> Bool,
create_at -> Timestamp,
update_at -> Timestamp,
}
}
diesel::table! {
transactions (id) {
id -> Int8,
uid -> Int8,
book_id -> Int8,
description -> Text,
category_id -> Int8,
is_delete -> Bool,
time -> Timestamptz,
create_at -> Timestamp,
update_at -> Timestamp,
}
}
diesel::table! {
users (id) {
id -> Int8,
username -> Text,
password -> Text,
mail -> Text,
is_delete -> Bool,
create_at -> Timestamp,
update_at -> Timestamp,
}
}
diesel::allow_tables_to_appear_in_same_query!(
accounts,
amounts,
books,
categories,
tags,
transaction_tag_rels,
transactions,
users,
);

109
src/schema.rs Normal file
View File

@@ -0,0 +1,109 @@
// @generated automatically by Diesel CLI.
diesel::table! {
accounts (id) {
id -> Int8,
uid -> Int8,
name -> Text,
#[sql_name = "type"]
type_ -> Int8,
is_delete -> Bool,
create_at -> Timestamp,
update_at -> Timestamp,
}
}
diesel::table! {
amounts (id) {
id -> Int8,
uid -> Int8,
transaction_id -> Int8,
value -> Int8,
expo -> Int8,
is_delete -> Bool,
create_at -> Timestamp,
update_at -> Timestamp,
}
}
diesel::table! {
books (id) {
id -> Int8,
uid -> Int8,
name -> Text,
is_delete -> Bool,
create_at -> Timestamp,
update_at -> Timestamp,
}
}
diesel::table! {
categories (id) {
id -> Int8,
uid -> Int8,
name -> Text,
is_delete -> Bool,
create_at -> Timestamp,
update_at -> Timestamp,
}
}
diesel::table! {
tags (id) {
id -> Int8,
uid -> Int8,
name -> Text,
is_delete -> Bool,
create_at -> Timestamp,
update_at -> Timestamp,
}
}
diesel::table! {
transaction_tag_rels (id) {
id -> Int8,
uid -> Int8,
transaction_id -> Int8,
tag_id -> Int8,
is_delete -> Bool,
create_at -> Timestamp,
update_at -> Timestamp,
}
}
diesel::table! {
transactions (id) {
id -> Int8,
uid -> Int8,
book_id -> Int8,
description -> Text,
category_id -> Int8,
is_delete -> Bool,
time -> Timestamptz,
create_at -> Timestamp,
update_at -> Timestamp,
}
}
diesel::table! {
users (id) {
id -> Int8,
username -> Text,
password -> Text,
mail -> Text,
is_delete -> Bool,
create_at -> Timestamp,
update_at -> Timestamp,
}
}
diesel::allow_tables_to_appear_in_same_query!(
accounts,
amounts,
books,
categories,
tags,
transaction_tag_rels,
transactions,
users,
);

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

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

14
src/util/req.rs Normal file
View File

@@ -0,0 +1,14 @@
use axum::http::StatusCode;
use serde::Serialize;
#[derive(Serialize)]
pub struct CommonResp {
pub code: i64,
}
pub fn internal_error<E>(err: E) -> (StatusCode, String)
where
E: std::error::Error,
{
(StatusCode::INTERNAL_SERVER_ERROR, err.to_string())
}