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.

move db logic to Service struct

ToBinio 30b9415e 2b486091

+135 -82
+2 -2
api/src/auth.rs
··· 7 7 8 8 use crate::AppState; 9 9 10 - #[derive(Debug)] 10 + #[derive(Clone, Debug)] 11 11 pub struct User { 12 12 pub uuid: Uuid, 13 13 } ··· 25 25 .get("Authorization") 26 26 .and_then(|h| h.to_str().ok()) 27 27 .and_then(|h| h.strip_prefix("Bearer ")) 28 - .ok_or_else(|| (StatusCode::UNAUTHORIZED, "Authorization header missing"))?; 28 + .ok_or((StatusCode::UNAUTHORIZED, "Authorization header missing"))?; 29 29 30 30 info!(auth_header); 31 31
+4 -4
api/src/routes/categories.rs
··· 54 54 CategoryModel { 55 55 uuid: existing_category.id, 56 56 name: existing_category.name, 57 - color: HexColor::from_str(&existing_category.color).unwrap(), 57 + color: HexColor::from_text(&existing_category.color).unwrap(), 58 58 icon: existing_category.icon, 59 59 } 60 60 } ··· 70 70 CategoryModel { 71 71 uuid: new_category.id, 72 72 name: new_category.name, 73 - color: HexColor::from_str(&new_category.color).unwrap(), 73 + color: HexColor::from_text(&new_category.color).unwrap(), 74 74 icon: new_category.icon, 75 75 } 76 76 } ··· 101 101 .map(|category| CategoryModel { 102 102 uuid: category.id, 103 103 name: category.name, 104 - color: HexColor::from_str(&category.color).unwrap(), 104 + color: HexColor::from_text(&category.color).unwrap(), 105 105 icon: category.icon, 106 106 }) 107 107 .collect(), ··· 132 132 let deleted_category = CategoryModel { 133 133 uuid: category.id, 134 134 name: category.name.to_string(), 135 - color: HexColor::from_str(&category.color).unwrap(), 135 + color: HexColor::from_text(&category.color).unwrap(), 136 136 icon: category.icon.to_string(), 137 137 }; 138 138
+19 -68
api/src/routes/tags.rs
··· 8 8 use axum::{Json, Router, extract::State, routing::get}; 9 9 use futures::Stream; 10 10 use http::StatusCode; 11 - use sea_orm::ActiveValue::Set; 12 - use sea_orm::{ColumnTrait, EntityTrait, ModelTrait, QueryFilter}; 13 11 use tokio_stream::StreamExt; 14 12 use tokio_stream::wrappers::BroadcastStream; 15 13 use tracing::warn; 16 - use types::{HexColor, Tag as TagModel}; 14 + use types::Tag as TagModel; 17 15 use uuid::Uuid; 18 16 19 17 use crate::services::events::{self}; 18 + use crate::services::tags::TagService; 20 19 use crate::{AppState, auth::User}; 21 20 22 - use crate::entities::{prelude::*, tag}; 23 - 24 21 pub fn routes(state: AppState) -> Router { 25 22 Router::new() 26 23 .route("/", get(get_all_tags)) ··· 35 32 user: User, 36 33 Json(tag): Json<TagModel>, 37 34 ) -> Result<Json<TagModel>, (StatusCode, &'static str)> { 38 - let existing_tag = Tag::find_by_id(tag.uuid) 39 - .one(&state.db_connection) 35 + let existing_tag = TagService::get_by_id(&user, &state.db_connection, tag.uuid) 40 36 .await 41 37 .map_err(|e| { 42 38 warn!("failed to find tag: {}", e); 43 39 (StatusCode::INTERNAL_SERVER_ERROR, "failed to find tag") 44 40 })?; 45 41 46 - let tag = tag::ActiveModel { 47 - id: Set(tag.uuid), 48 - owner_id: Set(user.uuid), 49 - name: Set(tag.name), 50 - color: Set(tag.color.as_str().to_string()), 51 - }; 52 - 53 42 let new_tag = match existing_tag { 54 43 Some(existing_tag) => { 55 - Tag::update(tag) 56 - .exec(&state.db_connection) 57 - .await 58 - .map_err(|e| { 59 - warn!("failed to add tag: {}", e); 60 - (StatusCode::INTERNAL_SERVER_ERROR, "failed to add tag") 61 - })?; 62 - 63 - existing_tag.into() 64 - } 65 - None => { 66 - let new_tag = Tag::insert(tag) 67 - .exec_with_returning(&state.db_connection) 44 + TagService::update(&user, &state.db_connection, tag) 68 45 .await 69 46 .map_err(|e| { 70 47 warn!("failed to add tag: {}", e); 71 48 (StatusCode::INTERNAL_SERVER_ERROR, "failed to add tag") 72 49 })?; 73 50 74 - new_tag.into() 51 + existing_tag 75 52 } 53 + None => TagService::create(&user, &state.db_connection, tag) 54 + .await 55 + .map_err(|e| { 56 + warn!("failed to add tag: {}", e); 57 + (StatusCode::INTERNAL_SERVER_ERROR, "failed to add tag") 58 + })?, 76 59 }; 77 60 78 61 state.event_service.broadcast(events::Event::Tag(user.uuid)); ··· 83 66 state: State<AppState>, 84 67 user: User, 85 68 ) -> Result<Json<Vec<TagModel>>, (StatusCode, &'static str)> { 86 - let tags = Tag::find() 87 - .filter(tag::Column::OwnerId.eq(user.uuid)) 88 - .all(&state.db_connection) 69 + let tags = TagService::get_all(&user, &state.db_connection) 89 70 .await 90 71 .map_err(|e| { 91 72 warn!("failed to fetch tag: {}", e); ··· 93 74 })?; 94 75 95 76 state.event_service.broadcast(events::Event::Tag(user.uuid)); 96 - Ok(Json(tags.into_iter().map(|tag| tag.into()).collect())) 77 + Ok(Json(tags)) 97 78 } 98 79 99 80 async fn delete_tag( ··· 101 82 Path(tag_uuid): Path<Uuid>, 102 83 user: User, 103 84 ) -> Result<Json<TagModel>, (StatusCode, &'static str)> { 104 - let deleted_tag = Tag::find_by_id(tag_uuid) 105 - .filter(tag::Column::OwnerId.eq(user.uuid)) 106 - .one(&state.db_connection) 85 + let to_be_deleted_tag = TagService::get_by_id(&user, &state.db_connection, tag_uuid) 107 86 .await 108 87 .map_err(|e| { 109 88 warn!("failed to fetch tag: {}", e); 110 89 (StatusCode::INTERNAL_SERVER_ERROR, "failed to fetch tags") 111 90 })?; 112 91 113 - let Some(tag) = deleted_tag else { 92 + let Some(deleted_tag) = to_be_deleted_tag else { 114 93 return Err((StatusCode::NOT_FOUND, "tag not found")); 115 94 }; 116 95 117 - let deleted_tag = TagModel::from(&tag); 118 - 119 - tag.delete(&state.db_connection) 96 + TagService::delete_by_id(&user, &state.db_connection, tag_uuid) 120 97 .await 121 98 .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "failed to delete tag"))?; 122 99 ··· 140 117 }) 141 118 .then(move |_| { 142 119 let db = state.db_connection.clone(); 120 + let user = user.clone(); 143 121 144 122 async move { 145 - Tag::find() 146 - .filter(tag::Column::OwnerId.eq(user.uuid)) 147 - .all(&db) 123 + TagService::get_all(&user, &db) 148 124 .await 149 125 .map_err(|e| { 150 126 warn!("failed to fetch tag: {}", e); 151 127 "failed to fetch tags" 152 128 }) 153 - .map(|tags| { 154 - tags.into_iter() 155 - .map(|tag| TagModel::from(tag)) 156 - .collect::<Vec<_>>() 157 - }) 158 - .map(|tags| Event::default().data(&serde_json::to_string(&tags).unwrap())) 129 + .map(|tags| Event::default().data(serde_json::to_string(&tags).unwrap())) 159 130 .unwrap() 160 131 } 161 132 }) ··· 167 138 .text("keep-alive-text"), 168 139 ) 169 140 } 170 - 171 - impl From<tag::Model> for TagModel { 172 - fn from(value: tag::Model) -> Self { 173 - TagModel { 174 - uuid: value.id, 175 - name: value.name, 176 - color: HexColor::from_str(&value.color).unwrap(), 177 - } 178 - } 179 - } 180 - 181 - impl From<&tag::Model> for TagModel { 182 - fn from(value: &tag::Model) -> Self { 183 - TagModel { 184 - uuid: value.id, 185 - name: value.name.to_string(), 186 - color: HexColor::from_str(&value.color).unwrap(), 187 - } 188 - } 189 - }
+1
api/src/services/mod.rs
··· 1 1 pub mod events; 2 + pub mod tags;
+101
api/src/services/tags.rs
··· 1 + use crate::{ 2 + auth::User, 3 + entities::{prelude::*, tag}, 4 + }; 5 + 6 + use sea_orm::{ 7 + ActiveValue::Set, ColumnTrait, DatabaseConnection, DeleteResult, EntityTrait, QueryFilter, 8 + }; 9 + use types::{HexColor, Tag as TagModel}; 10 + use uuid::Uuid; 11 + 12 + pub struct TagService; 13 + 14 + impl TagService { 15 + pub async fn get_all( 16 + user: &User, 17 + db: &DatabaseConnection, 18 + ) -> Result<Vec<TagModel>, sea_orm::DbErr> { 19 + Tag::find() 20 + .filter(tag::Column::OwnerId.eq(user.uuid)) 21 + .all(db) 22 + .await 23 + .map(|tags| tags.into_iter().map(|tag| tag.into()).collect()) 24 + } 25 + 26 + pub async fn get_by_id( 27 + user: &User, 28 + db: &DatabaseConnection, 29 + tag_id: Uuid, 30 + ) -> Result<Option<TagModel>, sea_orm::DbErr> { 31 + Tag::find_by_id(tag_id) 32 + .filter(tag::Column::OwnerId.eq(user.uuid)) 33 + .one(db) 34 + .await 35 + .map(|tag| tag.map(|tag| tag.into())) 36 + } 37 + 38 + pub async fn delete_by_id( 39 + user: &User, 40 + db: &DatabaseConnection, 41 + tag_id: Uuid, 42 + ) -> Result<DeleteResult, sea_orm::DbErr> { 43 + Tag::delete_by_id(tag_id) 44 + .filter(tag::Column::OwnerId.eq(user.uuid)) 45 + .exec(db) 46 + .await 47 + } 48 + 49 + pub async fn create( 50 + user: &User, 51 + db: &DatabaseConnection, 52 + tag: TagModel, 53 + ) -> Result<TagModel, sea_orm::DbErr> { 54 + Tag::insert(tag::ActiveModel { 55 + id: Set(tag.uuid), 56 + owner_id: Set(user.uuid), 57 + name: Set(tag.name), 58 + color: Set(tag.color.as_str().to_string()), 59 + }) 60 + .exec_with_returning(db) 61 + .await 62 + .map(|tag| tag.into()) 63 + } 64 + 65 + pub async fn update( 66 + user: &User, 67 + db: &DatabaseConnection, 68 + tag: TagModel, 69 + ) -> Result<TagModel, sea_orm::DbErr> { 70 + Tag::update(tag::ActiveModel { 71 + id: Set(tag.uuid), 72 + owner_id: Set(user.uuid), 73 + name: Set(tag.name), 74 + color: Set(tag.color.as_str().to_string()), 75 + }) 76 + .filter(tag::Column::OwnerId.eq(user.uuid)) 77 + .exec(db) 78 + .await 79 + .map(|tag| tag.into()) 80 + } 81 + } 82 + 83 + impl From<tag::Model> for TagModel { 84 + fn from(value: tag::Model) -> Self { 85 + TagModel { 86 + uuid: value.id, 87 + name: value.name, 88 + color: HexColor::from_text(&value.color).unwrap(), 89 + } 90 + } 91 + } 92 + 93 + impl From<&tag::Model> for TagModel { 94 + fn from(value: &tag::Model) -> Self { 95 + TagModel { 96 + uuid: value.id, 97 + name: value.name.to_string(), 98 + color: HexColor::from_text(&value.color).unwrap(), 99 + } 100 + } 101 + }
+1 -1
api/tests/categories/add.rs
··· 14 14 let category = Category { 15 15 uuid: category_uuid, 16 16 name: "Test Tag".to_string(), 17 - color: HexColor::from_str("#233212").unwrap(), 17 + color: HexColor::from_text("#233212").unwrap(), 18 18 icon: "icon".to_string(), 19 19 }; 20 20
+2 -2
api/tests/categories/update.rs
··· 37 37 assert_eq!(categories.len(), 1); 38 38 assert_eq!(categories[0].uuid, category_uuid); 39 39 assert_eq!(categories[0].name, "Updated Category"); 40 - assert_eq!(categories[0].color, HexColor::from_str("#000000").unwrap()); 40 + assert_eq!(categories[0].color, HexColor::from_text("#000000").unwrap()); 41 41 assert_eq!(categories[0].icon, "updated icon"); 42 42 } 43 43 ··· 74 74 assert_eq!(categories.len(), 1); 75 75 assert_eq!(categories[0].uuid, category_uuid); 76 76 assert_eq!(categories[0].name, "Test Category"); 77 - assert_eq!(categories[0].color, HexColor::from_str("#233212").unwrap()); 77 + assert_eq!(categories[0].color, HexColor::from_text("#233212").unwrap()); 78 78 assert_eq!(categories[0].icon, "icon"); 79 79 }
+1 -1
api/tests/tags/add.rs
··· 14 14 let tag = Tag { 15 15 uuid: tag_uuid, 16 16 name: "Test Tag".to_string(), 17 - color: HexColor::from_str("#233212").unwrap(), 17 + color: HexColor::from_text("#233212").unwrap(), 18 18 }; 19 19 20 20 let response = client
+2 -2
api/tests/tags/update.rs
··· 35 35 assert_eq!(tags.len(), 1); 36 36 assert_eq!(tags[0].uuid, tag_uuid); 37 37 assert_eq!(tags[0].name, "Updated Tag"); 38 - assert_eq!(tags[0].color, HexColor::from_str("#000000").unwrap()); 38 + assert_eq!(tags[0].color, HexColor::from_text("#000000").unwrap()); 39 39 } 40 40 41 41 #[tokio::test] ··· 69 69 assert_eq!(tags.len(), 1); 70 70 assert_eq!(tags[0].uuid, tag_uuid); 71 71 assert_eq!(tags[0].name, "Test Tag"); 72 - assert_eq!(tags[0].color, HexColor::from_str("#233212").unwrap()); 72 + assert_eq!(tags[0].color, HexColor::from_text("#233212").unwrap()); 73 73 }
+2 -2
libs/types/src/lib.rs
··· 132 132 &self.0 133 133 } 134 134 135 - pub fn from_str(s: &str) -> Option<Self> { 135 + pub fn from_text(s: &str) -> Option<Self> { 136 136 if is_valid_hex_color(s) { 137 137 Some(HexColor(s.to_string())) 138 138 } else { ··· 147 147 D: Deserializer<'de>, 148 148 { 149 149 let s = String::deserialize(deserializer)?; 150 - HexColor::from_str(&s).ok_or(serde::de::Error::custom("invalid hex color")) 150 + HexColor::from_text(&s).ok_or(serde::de::Error::custom("invalid hex color")) 151 151 } 152 152 } 153 153