A simple to-do app focused on tasks that can be completed within a specific time span.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

add category crud

tobinio 8cb23d47 ced9d6e4

+218 -4
+5 -1
api/migration/src/lib.rs
··· 1 1 pub use sea_orm_migration::prelude::*; 2 2 3 3 mod m20260503_093223_add_tag_table; 4 + mod m20260503_125344_add_user_category; 4 5 5 6 pub struct Migrator; 6 7 7 8 #[async_trait::async_trait] 8 9 impl MigratorTrait for Migrator { 9 10 fn migrations() -> Vec<Box<dyn MigrationTrait>> { 10 - vec![Box::new(m20260503_093223_add_tag_table::Migration)] 11 + vec![ 12 + Box::new(m20260503_093223_add_tag_table::Migration), 13 + Box::new(m20260503_125344_add_user_category::Migration), 14 + ] 11 15 } 12 16 }
+39
api/migration/src/m20260503_125344_add_user_category.rs
··· 1 + use sea_orm_migration::{prelude::*, schema::*}; 2 + 3 + #[derive(DeriveMigrationName)] 4 + pub struct Migration; 5 + 6 + #[async_trait::async_trait] 7 + impl MigrationTrait for Migration { 8 + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { 9 + manager 10 + .create_table( 11 + Table::create() 12 + .table(Category::Table) 13 + .if_not_exists() 14 + .col(pk_uuid(Category::Id)) 15 + .col(string(Category::Name)) 16 + .col(string(Category::Color)) 17 + .col(string(Category::Icon)) 18 + .col(uuid(Category::OwnerId)) 19 + .to_owned(), 20 + ) 21 + .await 22 + } 23 + 24 + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { 25 + manager 26 + .drop_table(Table::drop().table(Category::Table).to_owned()) 27 + .await 28 + } 29 + } 30 + 31 + #[derive(DeriveIden)] 32 + enum Category { 33 + Table, 34 + Id, 35 + OwnerId, 36 + Name, 37 + Color, 38 + Icon, 39 + }
+19
api/src/entities/category.rs
··· 1 + //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.20 2 + 3 + use sea_orm::entity::prelude::*; 4 + 5 + #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] 6 + #[sea_orm(table_name = "category")] 7 + pub struct Model { 8 + #[sea_orm(primary_key, auto_increment = false)] 9 + pub id: Uuid, 10 + pub name: String, 11 + pub color: String, 12 + pub icon: String, 13 + pub owner_id: Uuid, 14 + } 15 + 16 + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] 17 + pub enum Relation {} 18 + 19 + impl ActiveModelBehavior for ActiveModel {}
+1
api/src/entities/mod.rs
··· 2 2 3 3 pub mod prelude; 4 4 5 + pub mod category; 5 6 pub mod tag;
+1
api/src/entities/prelude.rs
··· 1 1 //! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.20 2 2 3 + pub use super::category::Entity as Category; 3 4 pub use super::tag::Entity as Tag;
+147
api/src/routes/categories.rs
··· 1 + use axum::extract::Path; 2 + use axum::routing::{delete, post}; 3 + use axum::{Json, Router, extract::State, routing::get}; 4 + use http::StatusCode; 5 + use sea_orm::ActiveValue::Set; 6 + use sea_orm::{ColumnTrait, EntityTrait, ModelTrait, QueryFilter}; 7 + use tracing::warn; 8 + use types::{Category as CategoryModel, HexColor}; 9 + use uuid::Uuid; 10 + 11 + use crate::{AppState, auth::User}; 12 + 13 + use crate::entities::{category, prelude::*}; 14 + 15 + pub fn routes(state: AppState) -> Router { 16 + Router::new() 17 + .route("/", get(get_all_categories)) 18 + .route("/", post(add_category)) 19 + .route("/{category_uuid}", delete(delete_category)) 20 + .with_state(state) 21 + } 22 + 23 + async fn add_category( 24 + state: State<AppState>, 25 + user: User, 26 + Json(category): Json<CategoryModel>, 27 + ) -> Result<Json<CategoryModel>, (StatusCode, &'static str)> { 28 + let existing_category = Category::find_by_id(category.uuid) 29 + .one(&state.db_connection) 30 + .await 31 + .map_err(|e| { 32 + warn!("failed to find category: {}", e); 33 + (StatusCode::INTERNAL_SERVER_ERROR, "failed to find category") 34 + })?; 35 + 36 + let category = category::ActiveModel { 37 + id: Set(category.uuid), 38 + owner_id: Set(user.uuid), 39 + name: Set(category.name), 40 + color: Set(category.color.as_str().to_string()), 41 + icon: Set(category.icon), 42 + }; 43 + 44 + let new_category = match existing_category { 45 + Some(existing_category) => { 46 + Category::update(category) 47 + .exec(&state.db_connection) 48 + .await 49 + .map_err(|e| { 50 + warn!("failed to add category: {}", e); 51 + (StatusCode::INTERNAL_SERVER_ERROR, "failed to add category") 52 + })?; 53 + 54 + CategoryModel { 55 + uuid: existing_category.id, 56 + name: existing_category.name, 57 + color: HexColor::from_str(&existing_category.color).unwrap(), 58 + icon: existing_category.icon, 59 + } 60 + } 61 + None => { 62 + let new_category = Category::insert(category) 63 + .exec_with_returning(&state.db_connection) 64 + .await 65 + .map_err(|e| { 66 + warn!("failed to add category: {}", e); 67 + (StatusCode::INTERNAL_SERVER_ERROR, "failed to add category") 68 + })?; 69 + 70 + CategoryModel { 71 + uuid: new_category.id, 72 + name: new_category.name, 73 + color: HexColor::from_str(&new_category.color).unwrap(), 74 + icon: new_category.icon, 75 + } 76 + } 77 + }; 78 + 79 + Ok(Json(new_category)) 80 + } 81 + 82 + async fn get_all_categories( 83 + state: State<AppState>, 84 + user: User, 85 + ) -> Result<Json<Vec<CategoryModel>>, (StatusCode, &'static str)> { 86 + let categories = Category::find() 87 + .filter(category::Column::OwnerId.eq(user.uuid)) 88 + .all(&state.db_connection) 89 + .await 90 + .map_err(|e| { 91 + warn!("failed to fetch category: {}", e); 92 + ( 93 + StatusCode::INTERNAL_SERVER_ERROR, 94 + "failed to fetch categories", 95 + ) 96 + })?; 97 + 98 + Ok(Json( 99 + categories 100 + .into_iter() 101 + .map(|category| CategoryModel { 102 + uuid: category.id, 103 + name: category.name, 104 + color: HexColor::from_str(&category.color).unwrap(), 105 + icon: category.icon, 106 + }) 107 + .collect(), 108 + )) 109 + } 110 + 111 + async fn delete_category( 112 + state: State<AppState>, 113 + Path(category_uuid): Path<Uuid>, 114 + user: User, 115 + ) -> Result<Json<CategoryModel>, (StatusCode, &'static str)> { 116 + let deleted_category = Category::find_by_id(category_uuid) 117 + .filter(category::Column::OwnerId.eq(user.uuid)) 118 + .one(&state.db_connection) 119 + .await 120 + .map_err(|e| { 121 + warn!("failed to fetch category: {}", e); 122 + ( 123 + StatusCode::INTERNAL_SERVER_ERROR, 124 + "failed to fetch categories", 125 + ) 126 + })?; 127 + 128 + let Some(category) = deleted_category else { 129 + return Err((StatusCode::NOT_FOUND, "category not found")); 130 + }; 131 + 132 + let deleted_category = CategoryModel { 133 + uuid: category.id, 134 + name: category.name.to_string(), 135 + color: HexColor::from_str(&category.color).unwrap(), 136 + icon: category.icon.to_string(), 137 + }; 138 + 139 + category.delete(&state.db_connection).await.map_err(|_| { 140 + ( 141 + StatusCode::INTERNAL_SERVER_ERROR, 142 + "failed to delete category", 143 + ) 144 + })?; 145 + 146 + Ok(Json(deleted_category)) 147 + }
+4 -1
api/src/routes/mod.rs
··· 2 2 3 3 use crate::AppState; 4 4 5 + mod categories; 5 6 mod tags; 6 7 7 8 pub fn routes(state: AppState) -> Router { 8 - Router::new().nest("/tags", tags::routes(state.clone())) 9 + Router::new() 10 + .nest("/tags", tags::routes(state.clone())) 11 + .nest("/categories", categories::routes(state.clone())) 9 12 }
+2 -2
api/tests/categories/add.rs
··· 55 55 } 56 56 )) 57 57 .await; 58 - assert_eq!(response.status().as_str(), "400"); 58 + assert_eq!(response.status().as_str(), "422"); 59 59 60 60 let response = client 61 61 .add_category(json!( ··· 67 67 } 68 68 )) 69 69 .await; 70 - assert_eq!(response.status().as_str(), "400"); 70 + assert_eq!(response.status().as_str(), "422"); 71 71 }