From d6a995d7a44c8559bf69b2210f74d9bd21b6dc40 Mon Sep 17 00:00:00 2001 From: zakie Date: Sun, 23 Nov 2025 01:16:33 +0900 Subject: [PATCH 01/23] =?UTF-8?q?feat:=20todo=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EB=AA=A8=EB=93=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/todo-domain/Cargo.toml | 4 + backend/todo-domain/src/lib.rs | 16 +--- backend/todo-domain/src/todo/error.rs | 5 ++ backend/todo-domain/src/todo/mod.rs | 5 ++ backend/todo-domain/src/todo/models/mod.rs | 3 + backend/todo-domain/src/todo/models/todo.rs | 57 ++++++++++++++ .../todo-domain/src/todo/models/todo_item.rs | 76 +++++++++++++++++++ .../src/todo/models/todo_item_status.rs | 7 ++ .../todo-domain/src/todo/repository/mod.rs | 2 + .../todo/repository/todo_item_repository.rs | 5 ++ .../src/todo/repository/todo_repository.rs | 7 ++ 11 files changed, 173 insertions(+), 14 deletions(-) create mode 100644 backend/todo-domain/src/todo/error.rs create mode 100644 backend/todo-domain/src/todo/mod.rs create mode 100644 backend/todo-domain/src/todo/models/mod.rs create mode 100644 backend/todo-domain/src/todo/models/todo.rs create mode 100644 backend/todo-domain/src/todo/models/todo_item.rs create mode 100644 backend/todo-domain/src/todo/models/todo_item_status.rs create mode 100644 backend/todo-domain/src/todo/repository/mod.rs create mode 100644 backend/todo-domain/src/todo/repository/todo_item_repository.rs create mode 100644 backend/todo-domain/src/todo/repository/todo_repository.rs diff --git a/backend/todo-domain/Cargo.toml b/backend/todo-domain/Cargo.toml index 9092dfe..fa39acc 100644 --- a/backend/todo-domain/Cargo.toml +++ b/backend/todo-domain/Cargo.toml @@ -4,4 +4,8 @@ version = "0.1.0" edition = "2024" [dependencies] +derive_builder = "0.20" +chrono = { version = "0.4", features = ["clock"] } + common = { path = "../todo-common" } +anyhow = "1.0.100" diff --git a/backend/todo-domain/src/lib.rs b/backend/todo-domain/src/lib.rs index b93cf3f..88599c8 100644 --- a/backend/todo-domain/src/lib.rs +++ b/backend/todo-domain/src/lib.rs @@ -1,14 +1,2 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} +pub mod todo; +pub mod user; diff --git a/backend/todo-domain/src/todo/error.rs b/backend/todo-domain/src/todo/error.rs new file mode 100644 index 0000000..9b07508 --- /dev/null +++ b/backend/todo-domain/src/todo/error.rs @@ -0,0 +1,5 @@ +#[derive(Debug)] +pub enum TodoError { + MaxItemLimit, + EmptyContent, +} diff --git a/backend/todo-domain/src/todo/mod.rs b/backend/todo-domain/src/todo/mod.rs new file mode 100644 index 0000000..67c11e4 --- /dev/null +++ b/backend/todo-domain/src/todo/mod.rs @@ -0,0 +1,5 @@ +pub mod error; +pub mod models; +pub mod repository; + +pub use error::TodoError::*; diff --git a/backend/todo-domain/src/todo/models/mod.rs b/backend/todo-domain/src/todo/models/mod.rs new file mode 100644 index 0000000..ac49538 --- /dev/null +++ b/backend/todo-domain/src/todo/models/mod.rs @@ -0,0 +1,3 @@ +pub mod todo; +pub mod todo_item; +mod todo_item_status; diff --git a/backend/todo-domain/src/todo/models/todo.rs b/backend/todo-domain/src/todo/models/todo.rs new file mode 100644 index 0000000..f7ad6c4 --- /dev/null +++ b/backend/todo-domain/src/todo/models/todo.rs @@ -0,0 +1,57 @@ +use crate::todo::error; +use crate::todo::models::todo_item::TodoItem; +use chrono::{DateTime, NaiveDate, Utc}; +use derive_builder::Builder; +use error::TodoError; + +#[derive(Debug, Clone, Builder)] +pub struct Todo { + id: Option, + user_id: i64, + date: NaiveDate, + items: Vec, + created_at: DateTime, + modified_at: DateTime, +} + +impl Todo { + pub fn new(user_id: i64, date: NaiveDate) -> Self { + Todo { + id: None, + user_id, + date, + items: vec![], + created_at: Utc::now(), + modified_at: Utc::now(), + } + } + + pub fn add_item(&mut self, item: TodoItem) -> Result<(), TodoError> { + if self.items.len() >= 3 { + return Err(TodoError::MaxItemLimit); + } + + self.items.push(item); + self.modified_at = Utc::now(); + Ok(()) + } + + pub fn id(&self) -> Option { + self.id + } + pub fn user_id(&self) -> i64 { + self.user_id + } + pub fn date(&self) -> NaiveDate { + self.date + } + pub fn items(&self) -> &Vec { + &self.items + } + pub fn created_at(&self) -> DateTime { + self.created_at + } + pub fn modified_at(&self) -> DateTime { + self.modified_at + } +} diff --git a/backend/todo-domain/src/todo/models/todo_item.rs b/backend/todo-domain/src/todo/models/todo_item.rs new file mode 100644 index 0000000..2734d4d --- /dev/null +++ b/backend/todo-domain/src/todo/models/todo_item.rs @@ -0,0 +1,76 @@ +use crate::todo::error::TodoError; +use crate::todo::models::todo_item_status::TodoItemStatus; +use chrono::{DateTime, Utc}; +use derive_builder::Builder; + +#[derive(Debug, Clone, Builder)] +pub struct TodoItem { + id: Option, + todo_id: i64, + content: String, + status: TodoItemStatus, + altered_plan: Option, + image_url: Option, + created_at: DateTime, + modified_at: DateTime, +} + +impl TodoItem { + pub fn new(todo_id: i64, content: String) -> Result { + if content.trim().is_empty() { + return Err(TodoError::EmptyContent); + } + + Ok(Self { + id: None, + todo_id, + content, + status: TodoItemStatus::Pending, + altered_plan: None, + image_url: None, + created_at: Utc::now(), + modified_at: Utc::now(), + }) + } + + pub fn completed(&mut self) { + self.status = TodoItemStatus::Completed; + self.modified_at = Utc::now(); + } + + pub fn altered(&mut self, content: String) { + self.status = TodoItemStatus::Altered; + self.altered_plan = Some(content); + self.modified_at = Utc::now(); + } + + pub fn failed(&mut self) { + self.status = TodoItemStatus::Failed; + self.modified_at = Utc::now(); + } + + pub fn id(&self) -> Option { + self.id + } + pub fn todo_id(&self) -> i64 { + self.todo_id + } + pub fn content(&self) -> &str { + &self.content + } + pub fn status(&self) -> TodoItemStatus { + self.status + } + pub fn altered_plan(&self) -> Option<&String> { + self.altered_plan.as_ref() + } + pub fn image_url(&self) -> Option<&String> { + self.image_url.as_ref() + } + pub fn created_at(&self) -> DateTime { + self.created_at + } + pub fn modified_at(&self) -> DateTime { + self.modified_at + } +} diff --git a/backend/todo-domain/src/todo/models/todo_item_status.rs b/backend/todo-domain/src/todo/models/todo_item_status.rs new file mode 100644 index 0000000..11c43f5 --- /dev/null +++ b/backend/todo-domain/src/todo/models/todo_item_status.rs @@ -0,0 +1,7 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TodoItemStatus { + Pending, // 아직 수행 전 + Completed, // 정상 완료 + Altered, // 대체 업무 수행 + Failed, // 실패 +} diff --git a/backend/todo-domain/src/todo/repository/mod.rs b/backend/todo-domain/src/todo/repository/mod.rs new file mode 100644 index 0000000..f25e904 --- /dev/null +++ b/backend/todo-domain/src/todo/repository/mod.rs @@ -0,0 +1,2 @@ +pub mod todo_item_repository; +pub mod todo_repository; diff --git a/backend/todo-domain/src/todo/repository/todo_item_repository.rs b/backend/todo-domain/src/todo/repository/todo_item_repository.rs new file mode 100644 index 0000000..6b399f6 --- /dev/null +++ b/backend/todo-domain/src/todo/repository/todo_item_repository.rs @@ -0,0 +1,5 @@ +use crate::todo::models::todo_item::TodoItem; + +pub trait TodoItemRepository { + fn save(&self, item: &TodoItem, parent_todo_id: i64) -> Result; +} diff --git a/backend/todo-domain/src/todo/repository/todo_repository.rs b/backend/todo-domain/src/todo/repository/todo_repository.rs new file mode 100644 index 0000000..b994cb8 --- /dev/null +++ b/backend/todo-domain/src/todo/repository/todo_repository.rs @@ -0,0 +1,7 @@ +use chrono::NaiveDate; +use crate::todo::models::todo::Todo; + +pub trait TodoRepository { + fn find_by_user_and_date(&self, user_id: i64, date: NaiveDate) -> Result, anyhow::Error>; + fn save(&self, todo: &Todo) -> Result; +} From 8097ba7f4ae715a2f65b4993107f1f150b7ebea2 Mon Sep 17 00:00:00 2001 From: zakie Date: Sun, 23 Nov 2025 01:24:41 +0900 Subject: [PATCH 02/23] =?UTF-8?q?fix:=20cargo=20=ED=8F=AC=EB=A7=B7=20?= =?UTF-8?q?=EA=B7=9C=EC=B9=99=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../todo-domain/src/todo/repository/todo_repository.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/backend/todo-domain/src/todo/repository/todo_repository.rs b/backend/todo-domain/src/todo/repository/todo_repository.rs index b994cb8..0fb407b 100644 --- a/backend/todo-domain/src/todo/repository/todo_repository.rs +++ b/backend/todo-domain/src/todo/repository/todo_repository.rs @@ -1,7 +1,11 @@ -use chrono::NaiveDate; use crate::todo::models::todo::Todo; +use chrono::NaiveDate; pub trait TodoRepository { - fn find_by_user_and_date(&self, user_id: i64, date: NaiveDate) -> Result, anyhow::Error>; + fn find_by_user_and_date( + &self, + user_id: i64, + date: NaiveDate, + ) -> Result, anyhow::Error>; fn save(&self, todo: &Todo) -> Result; } From c11caeb25a29b4df9a5c16f65466ac703e23a6cd Mon Sep 17 00:00:00 2001 From: zakie Date: Sun, 23 Nov 2025 01:55:13 +0900 Subject: [PATCH 03/23] =?UTF-8?q?feat:=20user=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EB=AA=A8=EB=93=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/todo-domain/src/user/error.rs | 6 ++ backend/todo-domain/src/user/mod.rs | 9 +++ backend/todo-domain/src/user/models/mod.rs | 3 + .../src/user/models/oauth_provider.rs | 16 ++++++ .../src/user/models/social_account.rs | 56 +++++++++++++++++++ backend/todo-domain/src/user/models/user.rs | 46 +++++++++++++++ .../todo-domain/src/user/repository/mod.rs | 2 + .../repository/social_account_repository.rs | 20 +++++++ .../src/user/repository/user_repository.rs | 7 +++ 9 files changed, 165 insertions(+) create mode 100644 backend/todo-domain/src/user/error.rs create mode 100644 backend/todo-domain/src/user/mod.rs create mode 100644 backend/todo-domain/src/user/models/mod.rs create mode 100644 backend/todo-domain/src/user/models/oauth_provider.rs create mode 100644 backend/todo-domain/src/user/models/social_account.rs create mode 100644 backend/todo-domain/src/user/models/user.rs create mode 100644 backend/todo-domain/src/user/repository/mod.rs create mode 100644 backend/todo-domain/src/user/repository/social_account_repository.rs create mode 100644 backend/todo-domain/src/user/repository/user_repository.rs diff --git a/backend/todo-domain/src/user/error.rs b/backend/todo-domain/src/user/error.rs new file mode 100644 index 0000000..f2bb16f --- /dev/null +++ b/backend/todo-domain/src/user/error.rs @@ -0,0 +1,6 @@ +#[derive(Debug)] +pub enum UserError { + InvalidNickname, + InvalidProvider, + InvalidProviderUserId, +} diff --git a/backend/todo-domain/src/user/mod.rs b/backend/todo-domain/src/user/mod.rs new file mode 100644 index 0000000..49fa0f0 --- /dev/null +++ b/backend/todo-domain/src/user/mod.rs @@ -0,0 +1,9 @@ +pub mod error; +pub mod models; +pub mod repository; + +pub use error::UserError::*; +pub use models::{oauth_provider::OAuthProvider, social_account::SocialAccount, user::User}; +pub use repository::{ + social_account_repository::SocialAccountRepository, user_repository::UserRepository, +}; diff --git a/backend/todo-domain/src/user/models/mod.rs b/backend/todo-domain/src/user/models/mod.rs new file mode 100644 index 0000000..9cea013 --- /dev/null +++ b/backend/todo-domain/src/user/models/mod.rs @@ -0,0 +1,3 @@ +pub mod oauth_provider; +pub mod social_account; +pub mod user; diff --git a/backend/todo-domain/src/user/models/oauth_provider.rs b/backend/todo-domain/src/user/models/oauth_provider.rs new file mode 100644 index 0000000..a6c9cc2 --- /dev/null +++ b/backend/todo-domain/src/user/models/oauth_provider.rs @@ -0,0 +1,16 @@ +use crate::user::error::UserError; +use UserError::InvalidProvider; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum OAuthProvider { + Kakao, +} + +impl OAuthProvider { + pub fn from_str(provider: &str) -> Result { + match provider.to_lowercase().as_str() { + "kakao" => Ok(OAuthProvider::Kakao), + _ => Err(InvalidProvider), + } + } +} diff --git a/backend/todo-domain/src/user/models/social_account.rs b/backend/todo-domain/src/user/models/social_account.rs new file mode 100644 index 0000000..641be96 --- /dev/null +++ b/backend/todo-domain/src/user/models/social_account.rs @@ -0,0 +1,56 @@ +use crate::user::error::UserError; +use crate::user::models::oauth_provider::OAuthProvider; +use chrono::{DateTime, Utc}; +use derive_builder::Builder; + +#[derive(Debug, Clone, Builder)] +pub struct SocialAccount { + id: Option, + user_id: i64, + provider: OAuthProvider, + provider_user_id: String, + created_at: DateTime, + modified_at: DateTime, +} + +impl SocialAccount { + pub fn new( + user_id: i64, + provider: OAuthProvider, + provider_user_id: String, + ) -> Result { + if provider_user_id.trim().is_empty() { + return Err(UserError::InvalidProviderUserId); + } + + let now = Utc::now(); + + Ok(Self { + id: None, + user_id, + provider, + provider_user_id, + created_at: now, + modified_at: now, + }) + } + + pub fn id(&self) -> Option { + self.id + } + pub fn user_id(&self) -> i64 { + self.user_id + } + pub fn provider(&self) -> &OAuthProvider { + &self.provider + } + pub fn provider_user_id(&self) -> &str { + &self.provider_user_id + } + pub fn created_at(&self) -> DateTime { + self.created_at + } + pub fn modified_at(&self) -> DateTime { + self.modified_at + } +} diff --git a/backend/todo-domain/src/user/models/user.rs b/backend/todo-domain/src/user/models/user.rs new file mode 100644 index 0000000..9304119 --- /dev/null +++ b/backend/todo-domain/src/user/models/user.rs @@ -0,0 +1,46 @@ +use crate::user::error::UserError; +use chrono::{DateTime, Utc}; +use derive_builder::Builder; + +#[derive(Debug, Clone, Builder)] +pub struct User { + id: Option, + nickname: String, + profile_image_url: Option, + created_at: DateTime, + modified_at: DateTime, +} + +impl User { + pub fn new(nickname: String, profile_image_url: Option) -> Result { + if nickname.trim().is_empty() { + return Err(UserError::InvalidNickname); + } + + let now = Utc::now(); + + Ok(User { + id: None, + nickname, + profile_image_url, + created_at: now, + modified_at: now, + }) + } + + pub fn id(&self) -> Option { + self.id + } + pub fn nickname(&self) -> &str { + &self.nickname + } + pub fn profile_image_url(&self) -> Option<&String> { + self.profile_image_url.as_ref() + } + pub fn created_at(&self) -> DateTime { + self.created_at + } + pub fn modified_at(&self) -> DateTime { + self.modified_at + } +} diff --git a/backend/todo-domain/src/user/repository/mod.rs b/backend/todo-domain/src/user/repository/mod.rs new file mode 100644 index 0000000..e629be3 --- /dev/null +++ b/backend/todo-domain/src/user/repository/mod.rs @@ -0,0 +1,2 @@ +pub mod social_account_repository; +pub mod user_repository; diff --git a/backend/todo-domain/src/user/repository/social_account_repository.rs b/backend/todo-domain/src/user/repository/social_account_repository.rs new file mode 100644 index 0000000..a6555e9 --- /dev/null +++ b/backend/todo-domain/src/user/repository/social_account_repository.rs @@ -0,0 +1,20 @@ +use crate::user::models::oauth_provider::OAuthProvider; +use crate::user::models::social_account::SocialAccount; + +pub trait SocialAccountRepository { + /// 로그인용 — 소셜 인증 → 내부 회원 찾기 + fn find_by_provider_and_user_id( + &self, + provider: OAuthProvider, + provider_user_id: &str, + ) -> Result, anyhow::Error>; + + /// 계정 연동/조회용 + fn find_by_user_id_and_provider( + &self, + user_id: i64, + provider: &OAuthProvider, + ) -> Result, anyhow::Error>; + + fn save(&self, social_account: SocialAccount) -> Result; +} diff --git a/backend/todo-domain/src/user/repository/user_repository.rs b/backend/todo-domain/src/user/repository/user_repository.rs new file mode 100644 index 0000000..bfa6438 --- /dev/null +++ b/backend/todo-domain/src/user/repository/user_repository.rs @@ -0,0 +1,7 @@ +use crate::user::models::user::User; + +pub trait UserRepository { + fn find_by_id(&self, id: i64) -> Result, anyhow::Error>; + fn find_by_nickname(&self, nickname: &str) -> Result, anyhow::Error>; + fn save(&self, user: &mut User) -> Result<(), anyhow::Error>; +} From 52aef90e18d8e8f9b03018baa7b2b7f6d0250014 Mon Sep 17 00:00:00 2001 From: zakie Date: Sun, 23 Nov 2025 02:18:25 +0900 Subject: [PATCH 04/23] =?UTF-8?q?test:=20todo=5Fitem=20=EB=8B=A8=EC=9C=84?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../todo-domain/src/todo/models/todo_item.rs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/backend/todo-domain/src/todo/models/todo_item.rs b/backend/todo-domain/src/todo/models/todo_item.rs index 2734d4d..6589824 100644 --- a/backend/todo-domain/src/todo/models/todo_item.rs +++ b/backend/todo-domain/src/todo/models/todo_item.rs @@ -74,3 +74,55 @@ impl TodoItem { self.modified_at } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::todo::models::todo_item_status::TodoItemStatus; + + #[test] + fn new_todo_item_success() { + let item = TodoItem::new(1, "벤치 프레스 80kg 10회 10세트".to_string()).unwrap(); + + assert_eq!(item.todo_id(), 1); + assert_eq!(item.content(), "벤치 프레스 80kg 10회 10세트"); + assert_eq!(item.status(), TodoItemStatus::Pending); + assert!(item.id().is_none()); + } + + #[test] + fn new_todo_item_empty_content_should_fail() { + let res = TodoItem::new(1, " ".to_string()); + assert!(matches!(res, Err(TodoError::EmptyContent))); + } + + #[test] + fn complete_should_update_status_and_modified_at() { + let mut item = TodoItem::new(1, "todo 프로젝트 Rust Axum 백엔드 서버 구축".to_string()).unwrap(); + let old_time = item.modified_at(); + + item.completed(); + + assert_eq!(item.status(), TodoItemStatus::Completed); + assert!(item.modified_at() > old_time); + } + + #[test] + fn altered_should_update_status_and_plan() { + let mut item = TodoItem::new(1, "벤치 프레스 80kg 10회 10세트".to_string()).unwrap(); + + item.altered("오늘 벤치 사람 너무 많아서, 스쿼트 100kg 10회 10세트로 대체".to_string()); + + assert_eq!(item.status(), TodoItemStatus::Altered); + assert_eq!(item.altered_plan(), Some(&"오늘 벤치 사람 너무 많아서, 스쿼트 100kg 10회 10세트로 대체".to_string())); + } + + #[test] + fn failed_should_update_status() { + let mut item = TodoItem::new(1, "todo 프로젝트 Svelte 프론트 제작".to_string()).unwrap(); + + item.failed(); + + assert_eq!(item.status(), TodoItemStatus::Failed); + } +} From b259829a180ffb5bced5d9842c0a59e0d6598a80 Mon Sep 17 00:00:00 2001 From: zakie Date: Sun, 23 Nov 2025 02:24:43 +0900 Subject: [PATCH 05/23] =?UTF-8?q?test:=20todo=20=EB=8B=A8=EC=9C=84=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/todo-domain/src/todo/models/todo.rs | 45 +++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/backend/todo-domain/src/todo/models/todo.rs b/backend/todo-domain/src/todo/models/todo.rs index f7ad6c4..6f3be6a 100644 --- a/backend/todo-domain/src/todo/models/todo.rs +++ b/backend/todo-domain/src/todo/models/todo.rs @@ -55,3 +55,48 @@ impl Todo { self.modified_at } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::todo::models::todo_item::TodoItem; + + #[test] + fn new_todo_success() { + let date = NaiveDate::from_ymd_opt(2025, 11, 22).unwrap(); + let todo = Todo::new(818, date); + + assert_eq!(todo.user_id(), 818); + assert_eq!(todo.date(), date); + assert!(todo.items().is_empty()); + assert!(todo.id().is_none()); + } + + #[test] + fn add_item_success() { + let date = NaiveDate::from_ymd_opt(2025, 11, 22).unwrap(); + let mut todo = Todo::new(1, date); + + let item = TodoItem::new(1, "데드 100kg 10회 10세트".to_string()).unwrap(); + let prev_time = todo.modified_at(); + + todo.add_item(item).unwrap(); + + assert_eq!(todo.items().len(), 1); + assert!(todo.modified_at() > prev_time); + } + + #[test] + fn add_item_should_fail_when_exceeds_limit() { + let date = NaiveDate::from_ymd_opt(2025, 11, 22).unwrap(); + let mut todo = Todo::new(1, date); + + todo.add_item(TodoItem::new(1, "벤치 80kg 10회 10세트".to_string()).unwrap()).unwrap(); + todo.add_item(TodoItem::new(1, "스쿼트 100kg 10회 10세트".to_string()).unwrap()).unwrap(); + todo.add_item(TodoItem::new(1, "데드 100kg 10회 10세트".to_string()).unwrap()).unwrap(); + + let result = todo.add_item(TodoItem::new(1, "OHP 40kg 10회 10세트".to_string()).unwrap()); + + assert!(matches!(result, Err(TodoError::MaxItemLimit))); + } +} From b32cc63c3e9c861ec203287989fa55b2676d4186 Mon Sep 17 00:00:00 2001 From: zakie Date: Sun, 23 Nov 2025 04:21:41 +0900 Subject: [PATCH 06/23] =?UTF-8?q?test:=20todo=20=EB=B9=84=EC=A6=88?= =?UTF-8?q?=EB=8B=88=EC=8A=A4=20=EA=B7=9C=EC=B9=99=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/todo-domain/src/todo/error.rs | 3 + backend/todo-domain/src/todo/models/todo.rs | 298 ++++++++++++++++-- .../todo-domain/src/todo/models/todo_item.rs | 81 +++-- 3 files changed, 328 insertions(+), 54 deletions(-) diff --git a/backend/todo-domain/src/todo/error.rs b/backend/todo-domain/src/todo/error.rs index 9b07508..912a13a 100644 --- a/backend/todo-domain/src/todo/error.rs +++ b/backend/todo-domain/src/todo/error.rs @@ -2,4 +2,7 @@ pub enum TodoError { MaxItemLimit, EmptyContent, + PastDateNotAllowed, + ItemNotFound, + StateChangeNotAllowed, } diff --git a/backend/todo-domain/src/todo/models/todo.rs b/backend/todo-domain/src/todo/models/todo.rs index 6f3be6a..40cfd63 100644 --- a/backend/todo-domain/src/todo/models/todo.rs +++ b/backend/todo-domain/src/todo/models/todo.rs @@ -1,8 +1,10 @@ -use crate::todo::error; +use crate::todo::error::TodoError; use crate::todo::models::todo_item::TodoItem; +use crate::todo::{ + EmptyContent, ItemNotFound, MaxItemLimit, PastDateNotAllowed, StateChangeNotAllowed, +}; use chrono::{DateTime, NaiveDate, Utc}; use derive_builder::Builder; -use error::TodoError; #[derive(Debug, Clone, Builder)] pub struct Todo { @@ -15,27 +17,116 @@ pub struct Todo { } impl Todo { - pub fn new(user_id: i64, date: NaiveDate) -> Self { - Todo { + pub fn new(user_id: i64, date: NaiveDate) -> Result { + let today = Utc::now().date_naive(); + + if date < today { + return Err(PastDateNotAllowed); + } + + Ok(Todo { id: None, user_id, date, items: vec![], created_at: Utc::now(), modified_at: Utc::now(), - } + }) } - pub fn add_item(&mut self, item: TodoItem) -> Result<(), TodoError> { + pub fn add_item(&mut self, content: &str) -> Result<(), TodoError> { + self.validate_creatable()?; + if self.items.len() >= 3 { - return Err(TodoError::MaxItemLimit); + return Err(MaxItemLimit); } + let item = TodoItem::new(self.user_id, content)?; self.items.push(item); self.modified_at = Utc::now(); Ok(()) } + pub fn update_item_content( + &mut self, + item_id: i64, + new_content: &str, + ) -> Result<(), TodoError> { + self.validate_editable()?; + + if new_content.trim().is_empty() { + return Err(EmptyContent); + } + + let item = self.find_mut_item(item_id)?; + item.update_content(new_content); + + self.modified_at = Utc::now(); + Ok(()) + } + + pub fn complete_item(&mut self, item_id: i64) -> Result<(), TodoError> { + self.validate_state_modifiable()?; + let item = self.find_mut_item(item_id)?; + item.completed(); + self.modified_at = Utc::now(); + Ok(()) + } + + pub fn alter_item(&mut self, item_id: i64, content: &str) -> Result<(), TodoError> { + self.validate_state_modifiable()?; + let item = self.find_mut_item(item_id)?; + item.altered(content); + self.modified_at = Utc::now(); + Ok(()) + } + + pub fn fail_item(&mut self, item_id: i64) -> Result<(), TodoError> { + self.validate_state_modifiable()?; + let item = self.find_mut_item(item_id)?; + item.failed(); + self.modified_at = Utc::now(); + Ok(()) + } + + /// 오늘이나 미래의 할 일만 추가할 수 있다. + fn validate_creatable(&self) -> Result<(), TodoError> { + let today = Utc::now().date_naive(); + + if self.date < today { + Err(PastDateNotAllowed) + } else { + Ok(()) + } + } + + /// 당일이 되기 전에는 얼마든지 수정/삭제 가능하다. + fn validate_editable(&self) -> Result<(), TodoError> { + let today = Utc::now().date_naive(); + if self.date <= today { + Err(PastDateNotAllowed) + } else { + Ok(()) + } + } + + /// 할 일의 상태 변경은 당일부터 가능하다. + fn validate_state_modifiable(&self) -> Result<(), TodoError> { + let today = Utc::now().date_naive(); + if self.date > today { + Err(StateChangeNotAllowed) + } else { + Ok(()) + } + } + + fn find_mut_item(&mut self, item_id: i64) -> Result<&mut TodoItem, TodoError> { + self.items + .iter_mut() + .find(|item| item.id() == Some(item_id)) + .ok_or(ItemNotFound) + } + pub fn id(&self) -> Option { self.id } @@ -59,12 +150,23 @@ impl Todo { #[cfg(test)] mod tests { use super::*; - use crate::todo::models::todo_item::TodoItem; + use crate::todo::models::todo_item::TodoItemBuilder; + use crate::todo::models::todo_item_status::TodoItemStatus; + use TodoError::MaxItemLimit; + use TodoItemStatus::{Altered, Completed, Failed}; + + fn past_date() -> NaiveDate { + NaiveDate::from_ymd_opt(2000, 1, 1).unwrap() + } + + fn future_date() -> NaiveDate { + NaiveDate::from_ymd_opt(2100, 1, 1).unwrap() + } #[test] fn new_todo_success() { - let date = NaiveDate::from_ymd_opt(2025, 11, 22).unwrap(); - let todo = Todo::new(818, date); + let date = future_date(); + let todo = Todo::new(818, date).unwrap(); assert_eq!(todo.user_id(), 818); assert_eq!(todo.date(), date); @@ -73,30 +175,178 @@ mod tests { } #[test] - fn add_item_success() { - let date = NaiveDate::from_ymd_opt(2025, 11, 22).unwrap(); - let mut todo = Todo::new(1, date); + fn new_todo_should_fail_for_past_date() { + let result = Todo::new(1, past_date()); + assert!(matches!(result, Err(PastDateNotAllowed))); + } - let item = TodoItem::new(1, "데드 100kg 10회 10세트".to_string()).unwrap(); - let prev_time = todo.modified_at(); + #[test] + fn add_item_success() { + let date = future_date(); + let mut todo = Todo::new(1, date).unwrap(); - todo.add_item(item).unwrap(); + todo.add_item("데드 100kg 10회 10세트").unwrap(); + let past_time = todo.modified_at(); assert_eq!(todo.items().len(), 1); - assert!(todo.modified_at() > prev_time); + assert!(todo.modified_at() >= past_time); + } + + #[test] + fn add_item_should_fail_on_past_todo() { + let mut todo = Todo { + id: None, + user_id: 1, + date: past_date(), + items: vec![], + created_at: Utc::now(), + modified_at: Utc::now(), + }; + + let result = todo.add_item("벤치 80kg 10회 10세트"); + assert!(matches!(result, Err(PastDateNotAllowed))); } #[test] fn add_item_should_fail_when_exceeds_limit() { - let date = NaiveDate::from_ymd_opt(2025, 11, 22).unwrap(); - let mut todo = Todo::new(1, date); + let date = future_date(); + let mut todo = Todo::new(1, date).unwrap(); + + todo.add_item("벤치 80kg 10회 10세트").unwrap(); + todo.add_item("스쿼트 100kg 10회 10세트").unwrap(); + todo.add_item("데드 100kg 10회 10세트").unwrap(); + + let result = todo.add_item("OHP 40kg 10회 10세트"); + + assert!(matches!(result, Err(MaxItemLimit))); + } + + #[test] + fn complete_item_success() { + let date = Utc::now().date_naive(); + let mut todo = Todo::new(1, date).unwrap(); + + // 원래 add_item 메서드 써서 넣어야 되는데, 테스트용으로 직접 아이템을 추가 + let item = TodoItemBuilder::default() + .id(Some(10)) + .todo_id(todo.user_id()) + .content("벤치".into()) + .status(TodoItemStatus::Pending) + .created_at(Utc::now()) + .modified_at(Utc::now()) + .build() + .unwrap(); + + todo.items.push(item); + + todo.complete_item(10).unwrap(); + + assert_eq!(todo.items[0].status(), Completed); + } + + #[test] + fn complete_item_should_fail_for_not_found() { + let date = Utc::now().date_naive(); + let mut todo = Todo::new(1, date).unwrap(); + + let result = todo.complete_item(999); + + assert!(matches!(result, Err(ItemNotFound))); + } + + #[test] + fn alter_item_success() { + let date = Utc::now().date_naive(); + let mut todo = Todo::new(1, date).unwrap(); + + let item = TodoItemBuilder::default() + .id(Some(5)) + .todo_id(todo.user_id()) + .content("벤치".into()) + .status(TodoItemStatus::Pending) + .created_at(Utc::now()) + .modified_at(Utc::now()) + .build() + .unwrap(); + + todo.items.push(item); + + todo.alter_item(5, "사람 많아서 스쿼트로 변경").unwrap(); + + assert_eq!(todo.items[0].status(), Altered); + assert_eq!( + todo.items[0].altered_plan().unwrap().as_str(), + "사람 많아서 스쿼트로 변경" + ); + } + + #[test] + fn fail_item_success() { + let date = Utc::now().date_naive(); + let mut todo = Todo::new(1, date).unwrap(); + + let item = TodoItemBuilder::default() + .id(Some(3)) + .todo_id(todo.user_id()) + .content("벤치".into()) + .status(TodoItemStatus::Pending) + .created_at(Utc::now()) + .modified_at(Utc::now()) + .build() + .unwrap(); + + todo.items.push(item); + + todo.fail_item(3).unwrap(); + + assert_eq!(todo.items[0].status(), Failed); + } + + #[test] + fn update_item_content_should_succeed_until_that_date() { + let mut todo = Todo::new(1, future_date()).unwrap(); + + let item = TodoItemBuilder::default() + .id(Some(10)) + .todo_id(1) + .content("벤치".into()) + .status(TodoItemStatus::Pending) + .created_at(Utc::now()) + .modified_at(Utc::now()) + .build() + .unwrap(); + + todo.items.push(item); + + let res = todo.update_item_content(10, "스쿼트"); + assert!(res.is_ok()); + assert_eq!(todo.items[0].content(), "스쿼트"); + } + + #[test] + fn update_item_content_should_fail_for_today_or_past_todo() { + let mut todo = Todo { + id: None, + user_id: 1, + date: past_date(), + items: vec![], + created_at: Utc::now(), + modified_at: Utc::now(), + }; - todo.add_item(TodoItem::new(1, "벤치 80kg 10회 10세트".to_string()).unwrap()).unwrap(); - todo.add_item(TodoItem::new(1, "스쿼트 100kg 10회 10세트".to_string()).unwrap()).unwrap(); - todo.add_item(TodoItem::new(1, "데드 100kg 10회 10세트".to_string()).unwrap()).unwrap(); + let item = TodoItemBuilder::default() + .id(Some(5)) + .todo_id(1) + .content("벤치 80kg 10회 10세트".into()) + .status(TodoItemStatus::Pending) + .created_at(Utc::now()) + .modified_at(Utc::now()) + .build() + .unwrap(); - let result = todo.add_item(TodoItem::new(1, "OHP 40kg 10회 10세트".to_string()).unwrap()); + todo.items.push(item); - assert!(matches!(result, Err(TodoError::MaxItemLimit))); + let res = todo.update_item_content(5, "데드 100kg 10회 10세트"); + assert!(matches!(res, Err(PastDateNotAllowed))); } } diff --git a/backend/todo-domain/src/todo/models/todo_item.rs b/backend/todo-domain/src/todo/models/todo_item.rs index 6589824..be742ee 100644 --- a/backend/todo-domain/src/todo/models/todo_item.rs +++ b/backend/todo-domain/src/todo/models/todo_item.rs @@ -1,7 +1,8 @@ -use crate::todo::error::TodoError; +use crate::todo::error::TodoError::{self, EmptyContent}; use crate::todo::models::todo_item_status::TodoItemStatus; use chrono::{DateTime, Utc}; use derive_builder::Builder; +use TodoItemStatus::{Altered, Completed, Failed, Pending}; #[derive(Debug, Clone, Builder)] pub struct TodoItem { @@ -9,43 +10,50 @@ pub struct TodoItem { todo_id: i64, content: String, status: TodoItemStatus, - altered_plan: Option, + #[builder(default)] + altered_content: Option, + #[builder(default)] image_url: Option, created_at: DateTime, modified_at: DateTime, } impl TodoItem { - pub fn new(todo_id: i64, content: String) -> Result { + pub(crate) fn new(todo_id: i64, content: &str) -> Result { if content.trim().is_empty() { - return Err(TodoError::EmptyContent); + return Err(EmptyContent); } Ok(Self { id: None, todo_id, - content, - status: TodoItemStatus::Pending, - altered_plan: None, + content: content.to_string(), + status: Pending, + altered_content: None, image_url: None, created_at: Utc::now(), modified_at: Utc::now(), }) } - pub fn completed(&mut self) { - self.status = TodoItemStatus::Completed; + pub(crate) fn update_content(&mut self, new_content: &str) { + self.content = new_content.to_string(); self.modified_at = Utc::now(); } - pub fn altered(&mut self, content: String) { - self.status = TodoItemStatus::Altered; - self.altered_plan = Some(content); + pub(crate) fn completed(&mut self) { + self.status = Completed; self.modified_at = Utc::now(); } - pub fn failed(&mut self) { - self.status = TodoItemStatus::Failed; + pub(crate) fn altered(&mut self, content: &str) { + self.status = Altered; + self.altered_content = Some(content.to_string()); + self.modified_at = Utc::now(); + } + + pub(crate) fn failed(&mut self) { + self.status = Failed; self.modified_at = Utc::now(); } @@ -62,7 +70,7 @@ impl TodoItem { self.status } pub fn altered_plan(&self) -> Option<&String> { - self.altered_plan.as_ref() + self.altered_content.as_ref() } pub fn image_url(&self) -> Option<&String> { self.image_url.as_ref() @@ -78,51 +86,64 @@ impl TodoItem { #[cfg(test)] mod tests { use super::*; - use crate::todo::models::todo_item_status::TodoItemStatus; #[test] fn new_todo_item_success() { - let item = TodoItem::new(1, "벤치 프레스 80kg 10회 10세트".to_string()).unwrap(); + let item = TodoItem::new(1, "벤치 프레스 80kg 10회 10세트").unwrap(); assert_eq!(item.todo_id(), 1); assert_eq!(item.content(), "벤치 프레스 80kg 10회 10세트"); - assert_eq!(item.status(), TodoItemStatus::Pending); + assert_eq!(item.status(), Pending); assert!(item.id().is_none()); } #[test] fn new_todo_item_empty_content_should_fail() { - let res = TodoItem::new(1, " ".to_string()); - assert!(matches!(res, Err(TodoError::EmptyContent))); + let res = TodoItem::new(1, " "); + assert!(matches!(res, Err(EmptyContent))); + } + + #[test] + fn update_content_should_change_content_and_modified_at() { + let mut item = TodoItem::new(1, "벤치 프레스 80kg 10회 10세트").unwrap(); + let past_time = item.modified_at(); + + item.update_content("딥스 10회 10세트"); + + assert_eq!(item.content(), "딥스 10회 10세트"); + assert!(item.modified_at() > past_time); } #[test] fn complete_should_update_status_and_modified_at() { - let mut item = TodoItem::new(1, "todo 프로젝트 Rust Axum 백엔드 서버 구축".to_string()).unwrap(); - let old_time = item.modified_at(); + let mut item = TodoItem::new(1, "todo 프로젝트 Rust Axum 백엔드 서버 구축").unwrap(); + let past_time = item.modified_at(); item.completed(); - assert_eq!(item.status(), TodoItemStatus::Completed); - assert!(item.modified_at() > old_time); + assert_eq!(item.status(), Completed); + assert!(item.modified_at() > past_time); } #[test] fn altered_should_update_status_and_plan() { - let mut item = TodoItem::new(1, "벤치 프레스 80kg 10회 10세트".to_string()).unwrap(); + let mut item = TodoItem::new(1, "벤치 프레스 80kg 10회 10세트").unwrap(); - item.altered("오늘 벤치 사람 너무 많아서, 스쿼트 100kg 10회 10세트로 대체".to_string()); + item.altered("오늘 벤치 사람 너무 많아서, 스쿼트 100kg 10회 10세트로 대체"); - assert_eq!(item.status(), TodoItemStatus::Altered); - assert_eq!(item.altered_plan(), Some(&"오늘 벤치 사람 너무 많아서, 스쿼트 100kg 10회 10세트로 대체".to_string())); + assert_eq!(item.status(), Altered); + assert_eq!( + item.altered_plan(), + Some(&"오늘 벤치 사람 너무 많아서, 스쿼트 100kg 10회 10세트로 대체".to_string()) + ); } #[test] fn failed_should_update_status() { - let mut item = TodoItem::new(1, "todo 프로젝트 Svelte 프론트 제작".to_string()).unwrap(); + let mut item = TodoItem::new(1, "todo 프로젝트 Svelte 프론트 제작").unwrap(); item.failed(); - assert_eq!(item.status(), TodoItemStatus::Failed); + assert_eq!(item.status(), Failed); } } From 103bc599afce6457139cd8422b71dbf367b9e79b Mon Sep 17 00:00:00 2001 From: zakie Date: Sun, 23 Nov 2025 09:29:51 +0900 Subject: [PATCH 07/23] =?UTF-8?q?refactor:=20aggregate=20=EB=8B=A8?= =?UTF-8?q?=EC=9C=84=EB=A1=9C=20=EB=AC=B6=EC=96=B4=20=ED=95=98=EB=82=98?= =?UTF-8?q?=EC=9D=98=20todo=20repository=20=ED=98=95=ED=83=9C=EB=A1=9C=20?= =?UTF-8?q?=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../todo-domain/src/todo/repository/mod.rs | 1 - .../todo/repository/todo_item_repository.rs | 5 ---- .../src/todo/repository/todo_repository.rs | 29 +++++++++++++++++-- 3 files changed, 27 insertions(+), 8 deletions(-) delete mode 100644 backend/todo-domain/src/todo/repository/todo_item_repository.rs diff --git a/backend/todo-domain/src/todo/repository/mod.rs b/backend/todo-domain/src/todo/repository/mod.rs index f25e904..f1a7327 100644 --- a/backend/todo-domain/src/todo/repository/mod.rs +++ b/backend/todo-domain/src/todo/repository/mod.rs @@ -1,2 +1 @@ -pub mod todo_item_repository; pub mod todo_repository; diff --git a/backend/todo-domain/src/todo/repository/todo_item_repository.rs b/backend/todo-domain/src/todo/repository/todo_item_repository.rs deleted file mode 100644 index 6b399f6..0000000 --- a/backend/todo-domain/src/todo/repository/todo_item_repository.rs +++ /dev/null @@ -1,5 +0,0 @@ -use crate::todo::models::todo_item::TodoItem; - -pub trait TodoItemRepository { - fn save(&self, item: &TodoItem, parent_todo_id: i64) -> Result; -} diff --git a/backend/todo-domain/src/todo/repository/todo_repository.rs b/backend/todo-domain/src/todo/repository/todo_repository.rs index 0fb407b..1b8a2e9 100644 --- a/backend/todo-domain/src/todo/repository/todo_repository.rs +++ b/backend/todo-domain/src/todo/repository/todo_repository.rs @@ -1,11 +1,36 @@ use crate::todo::models::todo::Todo; use chrono::NaiveDate; +use crate::todo::models::todo_item::TodoItem; pub trait TodoRepository { - fn find_by_user_and_date( + fn find_todo_by_user_and_date( &self, user_id: i64, date: NaiveDate, ) -> Result, anyhow::Error>; - fn save(&self, todo: &Todo) -> Result; + + fn insert_todo(&self, todo: &Todo) -> Result; + + fn update_todo(&self, todo: &Todo) -> Result; + + /// item + fn find_item_by_id( + &self, + item_id: i64, + ) -> Result, anyhow::Error>; + + fn find_items_by_todo_id( + &self, + todo_id: i64, + ) -> Result, anyhow::Error>; + + fn insert_item( + &self, + item: &TodoItem, + parent_todo_id: i64, + ) -> Result; + + fn update_item(&self, item: &TodoItem) -> Result; + + fn delete_item(&self, item_id: i64) -> Result<(), anyhow::Error>; } From fd80e9ec4f6dc5a74a47430aee78f369aaf2e780 Mon Sep 17 00:00:00 2001 From: zakie Date: Sun, 23 Nov 2025 09:37:43 +0900 Subject: [PATCH 08/23] =?UTF-8?q?fix:=20cargo=20fmt=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../todo-domain/src/todo/models/todo_item.rs | 2 +- .../src/todo/repository/todo_repository.rs | 18 ++++-------------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/backend/todo-domain/src/todo/models/todo_item.rs b/backend/todo-domain/src/todo/models/todo_item.rs index be742ee..e6ec8fe 100644 --- a/backend/todo-domain/src/todo/models/todo_item.rs +++ b/backend/todo-domain/src/todo/models/todo_item.rs @@ -1,8 +1,8 @@ use crate::todo::error::TodoError::{self, EmptyContent}; use crate::todo::models::todo_item_status::TodoItemStatus; +use TodoItemStatus::{Altered, Completed, Failed, Pending}; use chrono::{DateTime, Utc}; use derive_builder::Builder; -use TodoItemStatus::{Altered, Completed, Failed, Pending}; #[derive(Debug, Clone, Builder)] pub struct TodoItem { diff --git a/backend/todo-domain/src/todo/repository/todo_repository.rs b/backend/todo-domain/src/todo/repository/todo_repository.rs index 1b8a2e9..712b4b6 100644 --- a/backend/todo-domain/src/todo/repository/todo_repository.rs +++ b/backend/todo-domain/src/todo/repository/todo_repository.rs @@ -1,6 +1,6 @@ use crate::todo::models::todo::Todo; -use chrono::NaiveDate; use crate::todo::models::todo_item::TodoItem; +use chrono::NaiveDate; pub trait TodoRepository { fn find_todo_by_user_and_date( @@ -14,21 +14,11 @@ pub trait TodoRepository { fn update_todo(&self, todo: &Todo) -> Result; /// item - fn find_item_by_id( - &self, - item_id: i64, - ) -> Result, anyhow::Error>; + fn find_item_by_id(&self, item_id: i64) -> Result, anyhow::Error>; - fn find_items_by_todo_id( - &self, - todo_id: i64, - ) -> Result, anyhow::Error>; + fn find_items_by_todo_id(&self, todo_id: i64) -> Result, anyhow::Error>; - fn insert_item( - &self, - item: &TodoItem, - parent_todo_id: i64, - ) -> Result; + fn insert_item(&self, item: &TodoItem, parent_todo_id: i64) -> Result; fn update_item(&self, item: &TodoItem) -> Result; From 3f9e9eb7d3a81b0f822df56b9aea4ca538185559 Mon Sep 17 00:00:00 2001 From: zakie Date: Sun, 23 Nov 2025 09:53:15 +0900 Subject: [PATCH 09/23] =?UTF-8?q?chore:=20todoItemStatus=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20public=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/todo-domain/src/todo/models/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/todo-domain/src/todo/models/mod.rs b/backend/todo-domain/src/todo/models/mod.rs index ac49538..123c696 100644 --- a/backend/todo-domain/src/todo/models/mod.rs +++ b/backend/todo-domain/src/todo/models/mod.rs @@ -1,3 +1,3 @@ pub mod todo; pub mod todo_item; -mod todo_item_status; +pub mod todo_item_status; From 5bb64f50373bcf8117dcf5187e5c88e639ff3ce9 Mon Sep 17 00:00:00 2001 From: zakie Date: Sun, 23 Nov 2025 17:00:21 +0900 Subject: [PATCH 10/23] =?UTF-8?q?feat:=20status=20vo=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=20=EB=B3=80=ED=99=98=20(&str=20<->=20status)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/todo-domain/src/todo/error.rs | 1 + .../src/todo/models/todo_item_status.rs | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/backend/todo-domain/src/todo/error.rs b/backend/todo-domain/src/todo/error.rs index 912a13a..e5fc097 100644 --- a/backend/todo-domain/src/todo/error.rs +++ b/backend/todo-domain/src/todo/error.rs @@ -5,4 +5,5 @@ pub enum TodoError { PastDateNotAllowed, ItemNotFound, StateChangeNotAllowed, + InvalidStatus, } diff --git a/backend/todo-domain/src/todo/models/todo_item_status.rs b/backend/todo-domain/src/todo/models/todo_item_status.rs index 11c43f5..81823d0 100644 --- a/backend/todo-domain/src/todo/models/todo_item_status.rs +++ b/backend/todo-domain/src/todo/models/todo_item_status.rs @@ -1,3 +1,5 @@ +use crate::todo::error::TodoError; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TodoItemStatus { Pending, // 아직 수행 전 @@ -5,3 +7,29 @@ pub enum TodoItemStatus { Altered, // 대체 업무 수행 Failed, // 실패 } + +impl TodoItemStatus { + pub fn as_str(&self) -> &'static str { + match self { + TodoItemStatus::Pending => "PENDING", + TodoItemStatus::Completed => "COMPLETED", + TodoItemStatus::Altered => "ALTERED", + TodoItemStatus::Failed => "FAILED", + } + } +} + +impl TryFrom<&str> for TodoItemStatus { + type Error = TodoError; + + fn try_from(value: &str) -> Result { + match value { + "PENDING" => Ok(TodoItemStatus::Pending), + "COMPLETED" => Ok(TodoItemStatus::Completed), + "ALTERED" => Ok(TodoItemStatus::Altered), + "FAILED" => Ok(TodoItemStatus::Failed), + _ => Err(TodoError::InvalidStatus), + } + } +} + From ae8d06594d4d95515961f6a5b1d87ad8c3ece147 Mon Sep 17 00:00:00 2001 From: zakie Date: Sun, 23 Nov 2025 18:47:42 +0900 Subject: [PATCH 11/23] =?UTF-8?q?chore:=20postgres=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=EB=A5=BC=20database=20=EC=97=90=EB=9F=AC=EB=A1=9C=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/common/error/database_error_wrapper.rs | 4 ++++ .../src/common/error/error_into_response.rs | 10 +++++----- backend/todo-api/src/common/error/mod.rs | 2 +- .../src/common/error/postgres_error_wrapper.rs | 4 ---- backend/todo-infra/Cargo.toml | 2 ++ backend/todo-infra/src/database/error.rs | 16 ++++++++++++++++ backend/todo-infra/src/database/mod.rs | 1 + .../todo-infra/src/database/postgres/error.rs | 7 ------- backend/todo-infra/src/database/postgres/mod.rs | 4 +--- 9 files changed, 30 insertions(+), 20 deletions(-) create mode 100644 backend/todo-api/src/common/error/database_error_wrapper.rs delete mode 100644 backend/todo-api/src/common/error/postgres_error_wrapper.rs create mode 100644 backend/todo-infra/src/database/error.rs delete mode 100644 backend/todo-infra/src/database/postgres/error.rs diff --git a/backend/todo-api/src/common/error/database_error_wrapper.rs b/backend/todo-api/src/common/error/database_error_wrapper.rs new file mode 100644 index 0000000..8cf6183 --- /dev/null +++ b/backend/todo-api/src/common/error/database_error_wrapper.rs @@ -0,0 +1,4 @@ +use infra::database::error::DatabaseError; + +#[derive(Debug)] +pub struct DatabaseApiError(pub DatabaseError); diff --git a/backend/todo-api/src/common/error/error_into_response.rs b/backend/todo-api/src/common/error/error_into_response.rs index f66915b..128e990 100644 --- a/backend/todo-api/src/common/error/error_into_response.rs +++ b/backend/todo-api/src/common/error/error_into_response.rs @@ -6,9 +6,9 @@ use axum::{ use serde_json::json; use crate::common::error::app_error::AppError; -use crate::common::error::postgres_error_wrapper::PostgresApiError; +use crate::common::error::database_error_wrapper::DatabaseApiError; use common::error::{CommonErrorCode, ErrorCode}; -use infra::database::postgres::PostgresError; +use infra::database::error::DatabaseError; // AppError -> HTTP Response impl IntoResponse for AppError { @@ -26,12 +26,12 @@ impl IntoResponse for AppError { } // PostgresError -> HTTP Response -impl IntoResponse for PostgresApiError { +impl IntoResponse for DatabaseApiError { fn into_response(self) -> Response { let app_error: AppError = match self.0 { - PostgresError::UniqueViolation => AppError::new(CommonErrorCode::Conflict), + DatabaseError::UniqueViolation => AppError::new(CommonErrorCode::Conflict), - PostgresError::NotFound => AppError::new(CommonErrorCode::NotFound), + DatabaseError::NotFound => AppError::new(CommonErrorCode::NotFound), _ => AppError::new(CommonErrorCode::InternalServerError), }; diff --git a/backend/todo-api/src/common/error/mod.rs b/backend/todo-api/src/common/error/mod.rs index e9001d4..025731c 100644 --- a/backend/todo-api/src/common/error/mod.rs +++ b/backend/todo-api/src/common/error/mod.rs @@ -1,3 +1,3 @@ pub mod app_error; +pub mod database_error_wrapper; pub mod error_into_response; -mod postgres_error_wrapper; diff --git a/backend/todo-api/src/common/error/postgres_error_wrapper.rs b/backend/todo-api/src/common/error/postgres_error_wrapper.rs deleted file mode 100644 index 071b964..0000000 --- a/backend/todo-api/src/common/error/postgres_error_wrapper.rs +++ /dev/null @@ -1,4 +0,0 @@ -use infra::database::postgres::PostgresError; - -#[derive(Debug)] -pub struct PostgresApiError(pub PostgresError); diff --git a/backend/todo-infra/Cargo.toml b/backend/todo-infra/Cargo.toml index b968082..e474cd4 100644 --- a/backend/todo-infra/Cargo.toml +++ b/backend/todo-infra/Cargo.toml @@ -12,3 +12,5 @@ anyhow = "1.0" domain = { path = "../todo-domain" } common = { path = "../todo-common" } +chrono = "0.4.42" +thiserror = "2.0.17" diff --git a/backend/todo-infra/src/database/error.rs b/backend/todo-infra/src/database/error.rs new file mode 100644 index 0000000..7446f18 --- /dev/null +++ b/backend/todo-infra/src/database/error.rs @@ -0,0 +1,16 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum DatabaseError { + #[error("database connection error")] + ConnectionError, + + #[error("query execution error")] + QueryError, + + #[error("unique constraint violated")] + UniqueViolation, + + #[error("record not found")] + NotFound, +} diff --git a/backend/todo-infra/src/database/mod.rs b/backend/todo-infra/src/database/mod.rs index 26e9103..041863e 100644 --- a/backend/todo-infra/src/database/mod.rs +++ b/backend/todo-infra/src/database/mod.rs @@ -1 +1,2 @@ +pub mod error; pub mod postgres; diff --git a/backend/todo-infra/src/database/postgres/error.rs b/backend/todo-infra/src/database/postgres/error.rs deleted file mode 100644 index d7cea4e..0000000 --- a/backend/todo-infra/src/database/postgres/error.rs +++ /dev/null @@ -1,7 +0,0 @@ -#[derive(Debug)] -pub enum PostgresError { - ConnectionError, - Timeout, - UniqueViolation, - NotFound, -} diff --git a/backend/todo-infra/src/database/postgres/mod.rs b/backend/todo-infra/src/database/postgres/mod.rs index 5c67827..ff6eb8e 100644 --- a/backend/todo-infra/src/database/postgres/mod.rs +++ b/backend/todo-infra/src/database/postgres/mod.rs @@ -1,3 +1 @@ -pub mod error; - -pub use error::PostgresError; +pub mod todo; From f2d288071b5737ef02a650e326df3b7ef8731b3a Mon Sep 17 00:00:00 2001 From: zakie Date: Sun, 23 Nov 2025 20:00:19 +0900 Subject: [PATCH 12/23] =?UTF-8?q?feat:=20todo=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0(seaORM=20=EC=9E=90=EB=8F=99=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EC=8B=9C=20=EA=B5=90=EC=B2=B4=20=EC=98=88=EC=A0=95)=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20repository=20trait=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84,=20=EC=97=94=ED=8B=B0=ED=8B=B0-=EB=AA=A8=EB=8D=B8=20?= =?UTF-8?q?=EB=A7=A4=ED=8D=BC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/common/error/error_into_response.rs | 2 +- backend/todo-domain/Cargo.toml | 2 + backend/todo-domain/src/todo/error.rs | 10 +- .../src/todo/models/todo_item_status.rs | 3 +- .../src/todo/repository/todo_repository.rs | 22 ++- backend/todo-infra/src/database/error.rs | 18 +-- .../src/database/postgres/todo/mod.rs | 4 + .../src/database/postgres/todo/todo_entity.rs | 33 +++++ .../postgres/todo/todo_item_entity.rs | 40 +++++ .../src/database/postgres/todo/todo_mapper.rs | 87 +++++++++++ .../postgres/todo/todo_repository_impl.rs | 138 ++++++++++++++++++ 11 files changed, 337 insertions(+), 22 deletions(-) create mode 100644 backend/todo-infra/src/database/postgres/todo/mod.rs create mode 100644 backend/todo-infra/src/database/postgres/todo/todo_entity.rs create mode 100644 backend/todo-infra/src/database/postgres/todo/todo_item_entity.rs create mode 100644 backend/todo-infra/src/database/postgres/todo/todo_mapper.rs create mode 100644 backend/todo-infra/src/database/postgres/todo/todo_repository_impl.rs diff --git a/backend/todo-api/src/common/error/error_into_response.rs b/backend/todo-api/src/common/error/error_into_response.rs index 128e990..273b639 100644 --- a/backend/todo-api/src/common/error/error_into_response.rs +++ b/backend/todo-api/src/common/error/error_into_response.rs @@ -29,7 +29,7 @@ impl IntoResponse for AppError { impl IntoResponse for DatabaseApiError { fn into_response(self) -> Response { let app_error: AppError = match self.0 { - DatabaseError::UniqueViolation => AppError::new(CommonErrorCode::Conflict), + DatabaseError::UniqueViolation(_) => AppError::new(CommonErrorCode::Conflict), DatabaseError::NotFound => AppError::new(CommonErrorCode::NotFound), diff --git a/backend/todo-domain/Cargo.toml b/backend/todo-domain/Cargo.toml index fa39acc..ca4d40f 100644 --- a/backend/todo-domain/Cargo.toml +++ b/backend/todo-domain/Cargo.toml @@ -9,3 +9,5 @@ chrono = { version = "0.4", features = ["clock"] } common = { path = "../todo-common" } anyhow = "1.0.100" +async-trait = "0.1.89" +thiserror = "2.0.17" diff --git a/backend/todo-domain/src/todo/error.rs b/backend/todo-domain/src/todo/error.rs index e5fc097..6108169 100644 --- a/backend/todo-domain/src/todo/error.rs +++ b/backend/todo-domain/src/todo/error.rs @@ -1,9 +1,17 @@ -#[derive(Debug)] +use thiserror::Error; + +#[derive(Debug, Error)] pub enum TodoError { + #[error("하루 할 일 제한(3개) 초과")] MaxItemLimit, + #[error("내용이 비어 있을 수 없음")] EmptyContent, + #[error("과거 날짜엔 할 일 추가/변경/삭제 불가능")] PastDateNotAllowed, + #[error("해당 할 일을 찾을 수 없음")] ItemNotFound, + #[error("상태를 변경할 수 없음")] StateChangeNotAllowed, + #[error("유효하지 않은 상태값")] InvalidStatus, } diff --git a/backend/todo-domain/src/todo/models/todo_item_status.rs b/backend/todo-domain/src/todo/models/todo_item_status.rs index 81823d0..7fb481e 100644 --- a/backend/todo-domain/src/todo/models/todo_item_status.rs +++ b/backend/todo-domain/src/todo/models/todo_item_status.rs @@ -22,7 +22,7 @@ impl TodoItemStatus { impl TryFrom<&str> for TodoItemStatus { type Error = TodoError; - fn try_from(value: &str) -> Result { + fn try_from(value: &str) -> Result { match value { "PENDING" => Ok(TodoItemStatus::Pending), "COMPLETED" => Ok(TodoItemStatus::Completed), @@ -32,4 +32,3 @@ impl TryFrom<&str> for TodoItemStatus { } } } - diff --git a/backend/todo-domain/src/todo/repository/todo_repository.rs b/backend/todo-domain/src/todo/repository/todo_repository.rs index 712b4b6..edaadd8 100644 --- a/backend/todo-domain/src/todo/repository/todo_repository.rs +++ b/backend/todo-domain/src/todo/repository/todo_repository.rs @@ -1,26 +1,32 @@ use crate::todo::models::todo::Todo; use crate::todo::models::todo_item::TodoItem; +use async_trait::async_trait; use chrono::NaiveDate; +#[async_trait] pub trait TodoRepository { - fn find_todo_by_user_and_date( + async fn find_todo_by_user_and_date( &self, user_id: i64, date: NaiveDate, ) -> Result, anyhow::Error>; - fn insert_todo(&self, todo: &Todo) -> Result; + async fn insert_todo(&self, todo: &Todo) -> Result; - fn update_todo(&self, todo: &Todo) -> Result; + async fn update_todo(&self, todo: &Todo) -> Result; /// item - fn find_item_by_id(&self, item_id: i64) -> Result, anyhow::Error>; + async fn find_item_by_id(&self, item_id: i64) -> Result, anyhow::Error>; - fn find_items_by_todo_id(&self, todo_id: i64) -> Result, anyhow::Error>; + async fn find_items_by_todo_id(&self, todo_id: i64) -> Result, anyhow::Error>; - fn insert_item(&self, item: &TodoItem, parent_todo_id: i64) -> Result; + async fn insert_item( + &self, + item: &TodoItem, + parent_todo_id: i64, + ) -> Result; - fn update_item(&self, item: &TodoItem) -> Result; + async fn update_item(&self, item: &TodoItem) -> Result; - fn delete_item(&self, item_id: i64) -> Result<(), anyhow::Error>; + async fn delete_item(&self, item_id: i64) -> Result<(), anyhow::Error>; } diff --git a/backend/todo-infra/src/database/error.rs b/backend/todo-infra/src/database/error.rs index 7446f18..269f853 100644 --- a/backend/todo-infra/src/database/error.rs +++ b/backend/todo-infra/src/database/error.rs @@ -1,16 +1,14 @@ +use sea_orm::DbErr; use thiserror::Error; #[derive(Debug, Error)] pub enum DatabaseError { - #[error("database connection error")] - ConnectionError, - - #[error("query execution error")] - QueryError, - - #[error("unique constraint violated")] - UniqueViolation, - - #[error("record not found")] + #[error("데이터베이스에 연결할 수 없음")] + ConnectionError(#[source] DbErr), + #[error("쿼리 실행 중 오류 발생")] + QueryError(#[source] DbErr), + #[error("이미 존재하는 값")] + UniqueViolation(#[source] DbErr), + #[error("데이터를 찾을 수 없음")] NotFound, } diff --git a/backend/todo-infra/src/database/postgres/todo/mod.rs b/backend/todo-infra/src/database/postgres/todo/mod.rs new file mode 100644 index 0000000..4b6ed98 --- /dev/null +++ b/backend/todo-infra/src/database/postgres/todo/mod.rs @@ -0,0 +1,4 @@ +pub mod todo_entity; +pub mod todo_item_entity; +pub mod todo_mapper; +pub mod todo_repository_impl; diff --git a/backend/todo-infra/src/database/postgres/todo/todo_entity.rs b/backend/todo-infra/src/database/postgres/todo/todo_entity.rs new file mode 100644 index 0000000..27b68f0 --- /dev/null +++ b/backend/todo-infra/src/database/postgres/todo/todo_entity.rs @@ -0,0 +1,33 @@ +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel)] +#[sea_orm(table_name = "todos")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + pub user_id: i64, + pub date: Date, + pub created_at: DateTimeWithTimeZone, + pub modified_at: DateTimeWithTimeZone, +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + TodoItems, +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Relation::TodoItems => Entity::has_many(super::todo_item_entity::Entity).into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::TodoItems.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/backend/todo-infra/src/database/postgres/todo/todo_item_entity.rs b/backend/todo-infra/src/database/postgres/todo/todo_item_entity.rs new file mode 100644 index 0000000..0c7f059 --- /dev/null +++ b/backend/todo-infra/src/database/postgres/todo/todo_item_entity.rs @@ -0,0 +1,40 @@ +use super::todo_entity; +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel)] +#[sea_orm(table_name = "todo_items")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + pub todo_id: i64, + pub content: String, + pub status: String, + pub altered_content: Option, + pub image_url: Option, + pub created_at: DateTimeWithTimeZone, + pub modified_at: DateTimeWithTimeZone, +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + Todo, +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Relation::Todo => Entity::belongs_to(todo_entity::Entity) + .from(Column::TodoId) + .to(todo_entity::Column::Id) + .into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Todo.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/backend/todo-infra/src/database/postgres/todo/todo_mapper.rs b/backend/todo-infra/src/database/postgres/todo/todo_mapper.rs new file mode 100644 index 0000000..06bf880 --- /dev/null +++ b/backend/todo-infra/src/database/postgres/todo/todo_mapper.rs @@ -0,0 +1,87 @@ +use super::{todo_entity, todo_item_entity}; +use domain::todo::error::TodoError; +use domain::todo::models::{todo::Todo, todo_item::TodoItem}; +use domain::todo::models::{ + todo::TodoBuilder, todo_item::TodoItemBuilder, todo_item_status::TodoItemStatus, +}; +use sea_orm::{NotSet, Set}; + +pub struct TodoMapper; + +impl TodoMapper { + pub fn map_todo_to_entity(todo: &Todo) -> todo_entity::ActiveModel { + todo_entity::ActiveModel { + id: todo.id().map(Set).unwrap_or(NotSet), + user_id: Set(todo.user_id()), + date: Set(todo.date()), + created_at: Set(todo.created_at().into()), + modified_at: Set(todo.modified_at().into()), + } + } + + pub fn map_item_to_entity(item: &TodoItem) -> todo_item_entity::ActiveModel { + todo_item_entity::ActiveModel { + id: item.id().map(Set).unwrap_or(NotSet), + todo_id: Set(item.todo_id()), + content: Set(item.content().to_string()), + status: Set(item.status().as_str().to_string()), + altered_content: Set(item.altered_plan().cloned()), + image_url: Set(item.image_url().cloned()), + created_at: Set(item.created_at().into()), + modified_at: Set(item.modified_at().into()), + } + } + + pub fn map_entity_to_todo( + todo: todo_entity::Model, + items: Vec, + ) -> Result { + let mut item_models = Vec::new(); + + for i in items { + let status = TodoItemStatus::try_from(i.status.as_str())?; + + let item = TodoItemBuilder::default() + .id(Some(i.id)) + .todo_id(i.todo_id) + .content(i.content) + .status(status) + .altered_content(i.altered_content) + .image_url(i.image_url) + .created_at(i.created_at.into()) + .modified_at(i.modified_at.into()) + .build() + .map_err(|_| TodoError::InvalidStatus)?; + + item_models.push(item); + } + + let todo_model = TodoBuilder::default() + .id(Some(todo.id)) + .user_id(todo.user_id) + .date(todo.date) + .items(item_models) + .created_at(todo.created_at.into()) + .modified_at(todo.modified_at.into()) + .build() + .map_err(|_| TodoError::InvalidStatus)?; + + Ok(todo_model) + } + + pub fn map_entity_to_item(entity: todo_item_entity::Model) -> Result { + let status = TodoItemStatus::try_from(entity.status.as_str())?; + + TodoItemBuilder::default() + .id(Some(entity.id)) + .todo_id(entity.todo_id) + .content(entity.content) + .status(status) + .altered_content(entity.altered_content) + .image_url(entity.image_url) + .created_at(entity.created_at.into()) + .modified_at(entity.modified_at.into()) + .build() + .map_err(Into::into) + } +} diff --git a/backend/todo-infra/src/database/postgres/todo/todo_repository_impl.rs b/backend/todo-infra/src/database/postgres/todo/todo_repository_impl.rs new file mode 100644 index 0000000..160c0fe --- /dev/null +++ b/backend/todo-infra/src/database/postgres/todo/todo_repository_impl.rs @@ -0,0 +1,138 @@ +use async_trait::async_trait; +use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set}; + +use super::todo_mapper::TodoMapper; +use super::{todo_entity, todo_item_entity}; +use crate::database::error::DatabaseError; +use domain::todo::models::{todo::Todo, todo_item::TodoItem}; +use domain::todo::repository::todo_repository::TodoRepository; + +pub struct TodoRepositoryImpl { + pub db: sea_orm::DatabaseConnection, +} + +impl TodoRepositoryImpl { + pub fn new(db: sea_orm::DatabaseConnection) -> Self { + Self { db } + } +} + +#[async_trait] +impl TodoRepository for TodoRepositoryImpl { + async fn find_todo_by_user_and_date( + &self, + user_id: i64, + date: chrono::NaiveDate, + ) -> Result, anyhow::Error> { + let todo = todo_entity::Entity::find() + .filter(todo_entity::Column::UserId.eq(user_id)) + .filter(todo_entity::Column::Date.eq(date)) + .one(&self.db) + .await + .map_err(DatabaseError::QueryError)?; + + let Some(todo) = todo else { + return Ok(None); + }; + + let items = todo_item_entity::Entity::find() + .filter(todo_item_entity::Column::TodoId.eq(todo.id)) + .all(&self.db) + .await + .map_err(DatabaseError::QueryError)?; + + let mapped = TodoMapper::map_entity_to_todo(todo, items)?; + + Ok(Some(mapped)) + } + + async fn insert_todo(&self, todo: &Todo) -> Result { + let active = TodoMapper::map_todo_to_entity(todo); + + let saved = active + .insert(&self.db) + .await + .map_err(DatabaseError::QueryError)?; + + Ok(TodoMapper::map_entity_to_todo(saved, vec![])?) + } + + async fn update_todo(&self, todo: &Todo) -> Result { + let active = TodoMapper::map_todo_to_entity(todo); + + let saved = active + .update(&self.db) + .await + .map_err(DatabaseError::QueryError)?; + + let items = todo_item_entity::Entity::find() + .filter(todo_item_entity::Column::TodoId.eq(saved.id)) + .all(&self.db) + .await + .map_err(DatabaseError::QueryError)?; + + Ok(TodoMapper::map_entity_to_todo(saved, items)?) + } + + async fn find_item_by_id(&self, item_id: i64) -> Result, anyhow::Error> { + let model = todo_item_entity::Entity::find_by_id(item_id) + .one(&self.db) + .await + .map_err(DatabaseError::QueryError)?; + + Ok(model + .map(|m| TodoMapper::map_entity_to_item(m)) + .transpose()?) + } + + async fn find_items_by_todo_id(&self, todo_id: i64) -> Result, anyhow::Error> { + let models = todo_item_entity::Entity::find() + .filter(todo_item_entity::Column::TodoId.eq(todo_id)) + .all(&self.db) + .await + .map_err(DatabaseError::QueryError)?; + + let items = models + .into_iter() + .map(TodoMapper::map_entity_to_item) + .collect::, _>>()?; + + Ok(items) + } + + async fn insert_item( + &self, + item: &TodoItem, + parent_todo_id: i64, + ) -> Result { + let mut model = TodoMapper::map_item_to_entity(item); + model.todo_id = Set(parent_todo_id); + + let saved = model + .insert(&self.db) + .await + .map_err(DatabaseError::QueryError)?; + + TodoMapper::map_entity_to_item(saved) + } + + async fn update_item(&self, item: &TodoItem) -> Result { + let active = TodoMapper::map_item_to_entity(item); + + let saved = active + .update(&self.db) + .await + .map_err(DatabaseError::QueryError)?; + + TodoMapper::map_entity_to_item(saved) + } + + async fn delete_item(&self, item_id: i64) -> Result<(), anyhow::Error> { + todo_item_entity::Entity::delete_by_id(item_id) + .exec(&self.db) + .await + .map_err(DatabaseError::QueryError)?; + + Ok(()) + } +} From a2e770b2de5783ce700ad53b76c183990a988065 Mon Sep 17 00:00:00 2001 From: zakie Date: Mon, 24 Nov 2025 02:27:46 +0900 Subject: [PATCH 13/23] =?UTF-8?q?feat:=20user=20=EB=B0=8F=20=EC=86=8C?= =?UTF-8?q?=EC=85=9C=20=EA=B3=84=EC=A0=95=20=EC=97=94=ED=8B=B0=ED=8B=B0=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=B0=8F=20repository=20trait=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84,=20=EB=AA=A8=EB=8D=B8-=EC=97=94=ED=8B=B0=ED=8B=B0=20?= =?UTF-8?q?=EB=A7=A4=ED=8D=BC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/todo-domain/src/user/error.rs | 7 +- .../src/user/models/oauth_provider.rs | 15 +++- .../repository/social_account_repository.rs | 8 +- .../src/user/repository/user_repository.rs | 11 ++- .../todo-infra/src/database/postgres/mod.rs | 1 + .../src/database/postgres/user/mod.rs | 6 ++ .../postgres/user/social_account_entity.rs | 24 ++++++ .../postgres/user/social_account_mapper.rs | 35 +++++++++ .../user/social_account_repository_impl.rs | 65 +++++++++++++++++ .../src/database/postgres/user/user_entity.rs | 23 ++++++ .../src/database/postgres/user/user_mapper.rs | 27 +++++++ .../postgres/user/user_repository_impl.rs | 73 +++++++++++++++++++ 12 files changed, 286 insertions(+), 9 deletions(-) create mode 100644 backend/todo-infra/src/database/postgres/user/mod.rs create mode 100644 backend/todo-infra/src/database/postgres/user/social_account_entity.rs create mode 100644 backend/todo-infra/src/database/postgres/user/social_account_mapper.rs create mode 100644 backend/todo-infra/src/database/postgres/user/social_account_repository_impl.rs create mode 100644 backend/todo-infra/src/database/postgres/user/user_entity.rs create mode 100644 backend/todo-infra/src/database/postgres/user/user_mapper.rs create mode 100644 backend/todo-infra/src/database/postgres/user/user_repository_impl.rs diff --git a/backend/todo-domain/src/user/error.rs b/backend/todo-domain/src/user/error.rs index f2bb16f..2e728c6 100644 --- a/backend/todo-domain/src/user/error.rs +++ b/backend/todo-domain/src/user/error.rs @@ -1,6 +1,11 @@ -#[derive(Debug)] +use thiserror::Error; + +#[derive(Debug, Error)] pub enum UserError { + #[error("유효하지 않은 닉네임")] InvalidNickname, + #[error("유효하지 않은 OAuth 제공자")] InvalidProvider, + #[error("유효하지 않은 OAuth 유저 ID")] InvalidProviderUserId, } diff --git a/backend/todo-domain/src/user/models/oauth_provider.rs b/backend/todo-domain/src/user/models/oauth_provider.rs index a6c9cc2..d483fa4 100644 --- a/backend/todo-domain/src/user/models/oauth_provider.rs +++ b/backend/todo-domain/src/user/models/oauth_provider.rs @@ -1,3 +1,4 @@ +use std::str::FromStr; use crate::user::error::UserError; use UserError::InvalidProvider; @@ -7,8 +8,18 @@ pub enum OAuthProvider { } impl OAuthProvider { - pub fn from_str(provider: &str) -> Result { - match provider.to_lowercase().as_str() { + pub fn as_str(&self) -> &'static str { + match self { + OAuthProvider::Kakao => "KAKAO", + } + } +} + +impl FromStr for OAuthProvider { + type Err = UserError; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { "kakao" => Ok(OAuthProvider::Kakao), _ => Err(InvalidProvider), } diff --git a/backend/todo-domain/src/user/repository/social_account_repository.rs b/backend/todo-domain/src/user/repository/social_account_repository.rs index a6555e9..7b0932b 100644 --- a/backend/todo-domain/src/user/repository/social_account_repository.rs +++ b/backend/todo-domain/src/user/repository/social_account_repository.rs @@ -1,20 +1,22 @@ +use async_trait::async_trait; use crate::user::models::oauth_provider::OAuthProvider; use crate::user::models::social_account::SocialAccount; +#[async_trait] pub trait SocialAccountRepository { /// 로그인용 — 소셜 인증 → 내부 회원 찾기 - fn find_by_provider_and_user_id( + async fn find_by_provider_and_user_id( &self, provider: OAuthProvider, provider_user_id: &str, ) -> Result, anyhow::Error>; /// 계정 연동/조회용 - fn find_by_user_id_and_provider( + async fn find_by_user_id_and_provider( &self, user_id: i64, provider: &OAuthProvider, ) -> Result, anyhow::Error>; - fn save(&self, social_account: SocialAccount) -> Result; + async fn save(&self, social_account: &SocialAccount) -> Result; } diff --git a/backend/todo-domain/src/user/repository/user_repository.rs b/backend/todo-domain/src/user/repository/user_repository.rs index bfa6438..aadd08a 100644 --- a/backend/todo-domain/src/user/repository/user_repository.rs +++ b/backend/todo-domain/src/user/repository/user_repository.rs @@ -1,7 +1,12 @@ +use async_trait::async_trait; use crate::user::models::user::User; +#[async_trait] pub trait UserRepository { - fn find_by_id(&self, id: i64) -> Result, anyhow::Error>; - fn find_by_nickname(&self, nickname: &str) -> Result, anyhow::Error>; - fn save(&self, user: &mut User) -> Result<(), anyhow::Error>; + async fn find_by_id(&self, id: i64) -> Result, anyhow::Error>; + async fn find_by_nickname(&self, nickname: &str) -> Result, anyhow::Error>; + + async fn insert(&self, user: &mut User) -> Result; + async fn update(&self, user: &User) -> Result; + async fn delete(&self, id: i64) -> Result<(), anyhow::Error>; } diff --git a/backend/todo-infra/src/database/postgres/mod.rs b/backend/todo-infra/src/database/postgres/mod.rs index ff6eb8e..7b697c5 100644 --- a/backend/todo-infra/src/database/postgres/mod.rs +++ b/backend/todo-infra/src/database/postgres/mod.rs @@ -1 +1,2 @@ pub mod todo; +mod user; diff --git a/backend/todo-infra/src/database/postgres/user/mod.rs b/backend/todo-infra/src/database/postgres/user/mod.rs new file mode 100644 index 0000000..049f7b6 --- /dev/null +++ b/backend/todo-infra/src/database/postgres/user/mod.rs @@ -0,0 +1,6 @@ +pub mod social_account_entity; +pub mod social_account_mapper; +pub mod social_account_repository_impl; +pub mod user_entity; +pub mod user_mapper; +pub mod user_repository_impl; diff --git a/backend/todo-infra/src/database/postgres/user/social_account_entity.rs b/backend/todo-infra/src/database/postgres/user/social_account_entity.rs new file mode 100644 index 0000000..e51ce2c --- /dev/null +++ b/backend/todo-infra/src/database/postgres/user/social_account_entity.rs @@ -0,0 +1,24 @@ +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel)] +#[sea_orm(table_name = "social_accounts")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + pub user_id: i64, + pub provider: String, + pub provider_user_id: String, + pub created_at: DateTimeUtc, + pub modified_at: DateTimeUtc, +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation {} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + unreachable!() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/backend/todo-infra/src/database/postgres/user/social_account_mapper.rs b/backend/todo-infra/src/database/postgres/user/social_account_mapper.rs new file mode 100644 index 0000000..4380bb2 --- /dev/null +++ b/backend/todo-infra/src/database/postgres/user/social_account_mapper.rs @@ -0,0 +1,35 @@ +use crate::database::postgres::user::social_account_entity; +use domain::user::models::social_account::SocialAccountBuilder; +use domain::user::{OAuthProvider, SocialAccount}; +use sea_orm::{NotSet, Set}; +use std::str::FromStr; + +pub struct SocialAccountMapper; + +impl SocialAccountMapper { + pub fn map_to_entity(model: &SocialAccount) -> social_account_entity::ActiveModel { + social_account_entity::ActiveModel { + id: model.id().map(Set).unwrap_or(NotSet), + user_id: Set(model.user_id()), + provider: Set(model.provider().as_str().to_string()), + provider_user_id: Set(model.provider_user_id().to_string()), + created_at: Set(model.created_at().into()), + modified_at: Set(model.modified_at().into()), + } + } + + pub fn map_to_model( + entity: social_account_entity::Model, + ) -> Result { + let provider = OAuthProvider::from_str(entity.provider.as_str())?; + + Ok(SocialAccountBuilder::default() + .id(Some(entity.id)) + .user_id(entity.user_id) + .provider(provider) + .provider_user_id(entity.provider_user_id) + .created_at(entity.created_at.into()) + .modified_at(entity.modified_at.into()) + .build()?) + } +} diff --git a/backend/todo-infra/src/database/postgres/user/social_account_repository_impl.rs b/backend/todo-infra/src/database/postgres/user/social_account_repository_impl.rs new file mode 100644 index 0000000..9011624 --- /dev/null +++ b/backend/todo-infra/src/database/postgres/user/social_account_repository_impl.rs @@ -0,0 +1,65 @@ +use crate::database::error::DatabaseError; +use crate::database::postgres::user::social_account_entity; +use crate::database::postgres::user::social_account_mapper::SocialAccountMapper; +use anyhow::Error; +use async_trait::async_trait; +use domain::user::{SocialAccount, SocialAccountRepository}; +use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter}; + +pub struct SocialAccountRepositoryImpl { + pub db: sea_orm::DatabaseConnection, +} + +impl SocialAccountRepositoryImpl { + pub fn new(db: sea_orm::DatabaseConnection) -> Self { + Self { db } + } +} + +#[async_trait] +impl SocialAccountRepository for SocialAccountRepositoryImpl { + async fn find_by_provider_and_user_id( + &self, + provider: domain::user::OAuthProvider, + provider_user_id: &str, + ) -> Result, anyhow::Error> { + let social_account = social_account_entity::Entity::find() + .filter(social_account_entity::Column::Provider.eq(provider.as_str().to_string())) + .filter(social_account_entity::Column::ProviderUserId.eq(provider_user_id)) + .one(&self.db) + .await + .map_err(DatabaseError::QueryError)?; + + Ok(social_account + .map(|s| SocialAccountMapper::map_to_model(s)) + .transpose()?) + } + + async fn find_by_user_id_and_provider( + &self, + user_id: i64, + provider: &domain::user::OAuthProvider, + ) -> Result, anyhow::Error> { + let social_account = social_account_entity::Entity::find() + .filter(social_account_entity::Column::UserId.eq(user_id)) + .filter(social_account_entity::Column::Provider.eq(provider.as_str().to_string())) + .one(&self.db) + .await + .map_err(DatabaseError::QueryError)?; + + Ok(social_account + .map(|s| SocialAccountMapper::map_to_model(s)) + .transpose()?) + } + + async fn save(&self, social_account: &SocialAccount) -> Result { + let social_account = SocialAccountMapper::map_to_entity(social_account); + + let saved = social_account + .insert(&self.db) + .await + .map_err(DatabaseError::QueryError)?; + + Ok(SocialAccountMapper::map_to_model(saved)?) + } +} diff --git a/backend/todo-infra/src/database/postgres/user/user_entity.rs b/backend/todo-infra/src/database/postgres/user/user_entity.rs new file mode 100644 index 0000000..6107d76 --- /dev/null +++ b/backend/todo-infra/src/database/postgres/user/user_entity.rs @@ -0,0 +1,23 @@ +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel)] +#[sea_orm(table_name = "users")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + pub nickname: String, + pub profile_image_url: Option, + pub created_at: DateTimeUtc, + pub modified_at: DateTimeUtc, +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation {} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + unreachable!() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/backend/todo-infra/src/database/postgres/user/user_mapper.rs b/backend/todo-infra/src/database/postgres/user/user_mapper.rs new file mode 100644 index 0000000..e069270 --- /dev/null +++ b/backend/todo-infra/src/database/postgres/user/user_mapper.rs @@ -0,0 +1,27 @@ +use super::user_entity; +use domain::user::models::user::{User, UserBuilder}; +use sea_orm::{NotSet, Set}; + +pub struct UserMapper; + +impl UserMapper { + pub fn map_to_entity(model: &User) -> user_entity::ActiveModel { + user_entity::ActiveModel { + id: model.id().map(Set).unwrap_or(NotSet), + nickname: Set(model.nickname().to_string()), + profile_image_url: Set(model.profile_image_url().cloned()), + created_at: Set(model.created_at().into()), + modified_at: Set(model.modified_at().into()), + } + } + + pub fn map_to_model(entity: user_entity::Model) -> Result { + Ok(UserBuilder::default() + .id(Some(entity.id)) + .nickname(entity.nickname) + .profile_image_url(entity.profile_image_url) + .created_at(entity.created_at.into()) + .modified_at(entity.modified_at.into()) + .build()?) + } +} diff --git a/backend/todo-infra/src/database/postgres/user/user_repository_impl.rs b/backend/todo-infra/src/database/postgres/user/user_repository_impl.rs new file mode 100644 index 0000000..df39c68 --- /dev/null +++ b/backend/todo-infra/src/database/postgres/user/user_repository_impl.rs @@ -0,0 +1,73 @@ +use anyhow::Error; +use async_trait::async_trait; +use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter}; + +use domain::user::models::user::User; +use domain::user::repository::user_repository::UserRepository; + +use crate::database::error::DatabaseError; +use crate::database::postgres::user::user_entity; +use crate::database::postgres::user::user_mapper::UserMapper; + +pub struct UserRepositoryImpl { + pub db: sea_orm::DatabaseConnection, +} + +impl UserRepositoryImpl { + pub fn new(db: sea_orm::DatabaseConnection) -> Self { + Self { db } + } +} + +#[async_trait] +impl UserRepository for UserRepositoryImpl { + async fn find_by_id(&self, id: i64) -> Result, Error> { + let user = user_entity::Entity::find_by_id(id) + .one(&self.db) + .await + .map_err(DatabaseError::QueryError)?; + + Ok(user.map(|u| UserMapper::map_to_model(u)).transpose()?) + } + + async fn find_by_nickname(&self, nickname: &str) -> Result, Error> { + let user = user_entity::Entity::find() + .filter(user_entity::Column::Nickname.eq(nickname)) + .one(&self.db) + .await + .map_err(DatabaseError::QueryError)?; + + Ok(user.map(|u| UserMapper::map_to_model(u)).transpose()?) + } + + async fn insert(&self, user: &mut User) -> Result { + let user = UserMapper::map_to_entity(user); + + let saved = user + .insert(&self.db) + .await + .map_err(DatabaseError::QueryError)?; + + Ok(UserMapper::map_to_model(saved)?) + } + + async fn update(&self, user: &User) -> Result { + let user = UserMapper::map_to_entity(user); + + let updated = user + .update(&self.db) + .await + .map_err(DatabaseError::QueryError)?; + + Ok(UserMapper::map_to_model(updated)?) + } + + async fn delete(&self, id: i64) -> Result<(), Error> { + user_entity::Entity::delete_by_id(id) + .exec(&self.db) + .await + .map_err(DatabaseError::QueryError)?; + + Ok(()) + } +} From 71a2a8af06f713919fb49509047b896aea0748c4 Mon Sep 17 00:00:00 2001 From: zakie Date: Mon, 24 Nov 2025 03:00:31 +0900 Subject: [PATCH 14/23] =?UTF-8?q?feat:=20seaORM=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=EA=B4=80=EA=B3=84=20=EC=84=A4=EC=A0=95=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/social_account_repository.rs | 2 +- .../postgres/user/social_account_entity.rs | 18 ++++++++++++++++-- .../postgres/user/social_account_mapper.rs | 2 +- .../user/social_account_repository_impl.rs | 2 +- .../src/database/postgres/user/user_entity.rs | 15 +++++++++++++-- 5 files changed, 32 insertions(+), 7 deletions(-) diff --git a/backend/todo-domain/src/user/repository/social_account_repository.rs b/backend/todo-domain/src/user/repository/social_account_repository.rs index 7b0932b..c649d00 100644 --- a/backend/todo-domain/src/user/repository/social_account_repository.rs +++ b/backend/todo-domain/src/user/repository/social_account_repository.rs @@ -18,5 +18,5 @@ pub trait SocialAccountRepository { provider: &OAuthProvider, ) -> Result, anyhow::Error>; - async fn save(&self, social_account: &SocialAccount) -> Result; + async fn insert(&self, social_account: &SocialAccount) -> Result; } diff --git a/backend/todo-infra/src/database/postgres/user/social_account_entity.rs b/backend/todo-infra/src/database/postgres/user/social_account_entity.rs index e51ce2c..fc2d817 100644 --- a/backend/todo-infra/src/database/postgres/user/social_account_entity.rs +++ b/backend/todo-infra/src/database/postgres/user/social_account_entity.rs @@ -1,4 +1,5 @@ use sea_orm::entity::prelude::*; +use crate::database::postgres::user::user_entity; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "social_accounts")] @@ -13,11 +14,24 @@ pub struct Model { } #[derive(Copy, Clone, Debug, EnumIter)] -pub enum Relation {} +pub enum Relation { + Users +} impl RelationTrait for Relation { fn def(&self) -> RelationDef { - unreachable!() + match self { + Relation::Users => Entity::belongs_to(user_entity::Entity) + .from(Column::UserId) + .to(user_entity::Column::Id) + .into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Users.def() } } diff --git a/backend/todo-infra/src/database/postgres/user/social_account_mapper.rs b/backend/todo-infra/src/database/postgres/user/social_account_mapper.rs index 4380bb2..dd84f9f 100644 --- a/backend/todo-infra/src/database/postgres/user/social_account_mapper.rs +++ b/backend/todo-infra/src/database/postgres/user/social_account_mapper.rs @@ -11,7 +11,7 @@ impl SocialAccountMapper { social_account_entity::ActiveModel { id: model.id().map(Set).unwrap_or(NotSet), user_id: Set(model.user_id()), - provider: Set(model.provider().as_str().to_string()), + provider: Set(model.provider().as_str().into()), provider_user_id: Set(model.provider_user_id().to_string()), created_at: Set(model.created_at().into()), modified_at: Set(model.modified_at().into()), diff --git a/backend/todo-infra/src/database/postgres/user/social_account_repository_impl.rs b/backend/todo-infra/src/database/postgres/user/social_account_repository_impl.rs index 9011624..8e68298 100644 --- a/backend/todo-infra/src/database/postgres/user/social_account_repository_impl.rs +++ b/backend/todo-infra/src/database/postgres/user/social_account_repository_impl.rs @@ -52,7 +52,7 @@ impl SocialAccountRepository for SocialAccountRepositoryImpl { .transpose()?) } - async fn save(&self, social_account: &SocialAccount) -> Result { + async fn insert(&self, social_account: &SocialAccount) -> Result { let social_account = SocialAccountMapper::map_to_entity(social_account); let saved = social_account diff --git a/backend/todo-infra/src/database/postgres/user/user_entity.rs b/backend/todo-infra/src/database/postgres/user/user_entity.rs index 6107d76..22bdfdf 100644 --- a/backend/todo-infra/src/database/postgres/user/user_entity.rs +++ b/backend/todo-infra/src/database/postgres/user/user_entity.rs @@ -1,4 +1,5 @@ use sea_orm::entity::prelude::*; +use crate::database::postgres::user::social_account_entity; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "users")] @@ -12,11 +13,21 @@ pub struct Model { } #[derive(Copy, Clone, Debug, EnumIter)] -pub enum Relation {} +pub enum Relation { + SocialAccounts, +} impl RelationTrait for Relation { fn def(&self) -> RelationDef { - unreachable!() + match self { + Relation::SocialAccounts => Entity::has_many(social_account_entity::Entity).into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::SocialAccounts.def() } } From f1c044973e600b0a1164672ef463c127aaaed03e Mon Sep 17 00:00:00 2001 From: zakie Date: Mon, 24 Nov 2025 04:16:37 +0900 Subject: [PATCH 15/23] =?UTF-8?q?feat:=20=EA=B8=B0=EC=A1=B4=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20ErrorCode=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20-Code?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/error/database_error_wrapper.rs | 4 +- .../src/common/error/error_into_response.rs | 6 +- backend/todo-domain/src/todo/error.rs | 17 ------ backend/todo-domain/src/todo/mod.rs | 4 +- backend/todo-domain/src/todo/models/todo.rs | 24 ++++---- .../todo-domain/src/todo/models/todo_item.rs | 4 +- .../src/todo/models/todo_item_status.rs | 8 +-- .../todo-domain/src/todo/todo_error_code.rs | 57 +++++++++++++++++++ backend/todo-domain/src/user/error.rs | 11 ---- backend/todo-domain/src/user/mod.rs | 4 +- .../src/user/models/oauth_provider.rs | 6 +- .../src/user/models/social_account.rs | 6 +- backend/todo-domain/src/user/models/user.rs | 6 +- .../todo-domain/src/user/user_error_code.rs | 35 ++++++++++++ .../src/database/database_error_code.rs | 43 ++++++++++++++ backend/todo-infra/src/database/error.rs | 14 ----- backend/todo-infra/src/database/mod.rs | 2 +- .../src/database/postgres/todo/todo_mapper.rs | 8 +-- .../postgres/todo/todo_repository_impl.rs | 22 +++---- .../user/social_account_repository_impl.rs | 8 +-- .../postgres/user/user_repository_impl.rs | 12 ++-- 21 files changed, 197 insertions(+), 104 deletions(-) delete mode 100644 backend/todo-domain/src/todo/error.rs create mode 100644 backend/todo-domain/src/todo/todo_error_code.rs delete mode 100644 backend/todo-domain/src/user/error.rs create mode 100644 backend/todo-domain/src/user/user_error_code.rs create mode 100644 backend/todo-infra/src/database/database_error_code.rs delete mode 100644 backend/todo-infra/src/database/error.rs diff --git a/backend/todo-api/src/common/error/database_error_wrapper.rs b/backend/todo-api/src/common/error/database_error_wrapper.rs index 8cf6183..6e2a828 100644 --- a/backend/todo-api/src/common/error/database_error_wrapper.rs +++ b/backend/todo-api/src/common/error/database_error_wrapper.rs @@ -1,4 +1,4 @@ -use infra::database::error::DatabaseError; +use infra::database::database_error_code::DatabaseErrorCode; #[derive(Debug)] -pub struct DatabaseApiError(pub DatabaseError); +pub struct DatabaseApiError(pub DatabaseErrorCode); diff --git a/backend/todo-api/src/common/error/error_into_response.rs b/backend/todo-api/src/common/error/error_into_response.rs index 273b639..70d3d94 100644 --- a/backend/todo-api/src/common/error/error_into_response.rs +++ b/backend/todo-api/src/common/error/error_into_response.rs @@ -8,7 +8,7 @@ use serde_json::json; use crate::common::error::app_error::AppError; use crate::common::error::database_error_wrapper::DatabaseApiError; use common::error::{CommonErrorCode, ErrorCode}; -use infra::database::error::DatabaseError; +use infra::database::database_error_code::DatabaseErrorCode; // AppError -> HTTP Response impl IntoResponse for AppError { @@ -29,9 +29,9 @@ impl IntoResponse for AppError { impl IntoResponse for DatabaseApiError { fn into_response(self) -> Response { let app_error: AppError = match self.0 { - DatabaseError::UniqueViolation(_) => AppError::new(CommonErrorCode::Conflict), + DatabaseErrorCode::UniqueViolation(_) => AppError::new(CommonErrorCode::Conflict), - DatabaseError::NotFound => AppError::new(CommonErrorCode::NotFound), + DatabaseErrorCode::NotFound => AppError::new(CommonErrorCode::NotFound), _ => AppError::new(CommonErrorCode::InternalServerError), }; diff --git a/backend/todo-domain/src/todo/error.rs b/backend/todo-domain/src/todo/error.rs deleted file mode 100644 index 6108169..0000000 --- a/backend/todo-domain/src/todo/error.rs +++ /dev/null @@ -1,17 +0,0 @@ -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum TodoError { - #[error("하루 할 일 제한(3개) 초과")] - MaxItemLimit, - #[error("내용이 비어 있을 수 없음")] - EmptyContent, - #[error("과거 날짜엔 할 일 추가/변경/삭제 불가능")] - PastDateNotAllowed, - #[error("해당 할 일을 찾을 수 없음")] - ItemNotFound, - #[error("상태를 변경할 수 없음")] - StateChangeNotAllowed, - #[error("유효하지 않은 상태값")] - InvalidStatus, -} diff --git a/backend/todo-domain/src/todo/mod.rs b/backend/todo-domain/src/todo/mod.rs index 67c11e4..e2f5c5b 100644 --- a/backend/todo-domain/src/todo/mod.rs +++ b/backend/todo-domain/src/todo/mod.rs @@ -1,5 +1,5 @@ -pub mod error; +pub mod todo_error_code; pub mod models; pub mod repository; -pub use error::TodoError::*; +pub use todo_error_code::TodoErrorCode::*; diff --git a/backend/todo-domain/src/todo/models/todo.rs b/backend/todo-domain/src/todo/models/todo.rs index 40cfd63..bde61a8 100644 --- a/backend/todo-domain/src/todo/models/todo.rs +++ b/backend/todo-domain/src/todo/models/todo.rs @@ -1,4 +1,4 @@ -use crate::todo::error::TodoError; +use crate::todo::todo_error_code::TodoErrorCode; use crate::todo::models::todo_item::TodoItem; use crate::todo::{ EmptyContent, ItemNotFound, MaxItemLimit, PastDateNotAllowed, StateChangeNotAllowed, @@ -17,7 +17,7 @@ pub struct Todo { } impl Todo { - pub fn new(user_id: i64, date: NaiveDate) -> Result { + pub fn new(user_id: i64, date: NaiveDate) -> Result { let today = Utc::now().date_naive(); if date < today { @@ -34,7 +34,7 @@ impl Todo { }) } - pub fn add_item(&mut self, content: &str) -> Result<(), TodoError> { + pub fn add_item(&mut self, content: &str) -> Result<(), TodoErrorCode> { self.validate_creatable()?; if self.items.len() >= 3 { @@ -51,7 +51,7 @@ impl Todo { &mut self, item_id: i64, new_content: &str, - ) -> Result<(), TodoError> { + ) -> Result<(), TodoErrorCode> { self.validate_editable()?; if new_content.trim().is_empty() { @@ -65,7 +65,7 @@ impl Todo { Ok(()) } - pub fn complete_item(&mut self, item_id: i64) -> Result<(), TodoError> { + pub fn complete_item(&mut self, item_id: i64) -> Result<(), TodoErrorCode> { self.validate_state_modifiable()?; let item = self.find_mut_item(item_id)?; item.completed(); @@ -73,7 +73,7 @@ impl Todo { Ok(()) } - pub fn alter_item(&mut self, item_id: i64, content: &str) -> Result<(), TodoError> { + pub fn alter_item(&mut self, item_id: i64, content: &str) -> Result<(), TodoErrorCode> { self.validate_state_modifiable()?; let item = self.find_mut_item(item_id)?; item.altered(content); @@ -81,7 +81,7 @@ impl Todo { Ok(()) } - pub fn fail_item(&mut self, item_id: i64) -> Result<(), TodoError> { + pub fn fail_item(&mut self, item_id: i64) -> Result<(), TodoErrorCode> { self.validate_state_modifiable()?; let item = self.find_mut_item(item_id)?; item.failed(); @@ -90,7 +90,7 @@ impl Todo { } /// 오늘이나 미래의 할 일만 추가할 수 있다. - fn validate_creatable(&self) -> Result<(), TodoError> { + fn validate_creatable(&self) -> Result<(), TodoErrorCode> { let today = Utc::now().date_naive(); if self.date < today { @@ -101,7 +101,7 @@ impl Todo { } /// 당일이 되기 전에는 얼마든지 수정/삭제 가능하다. - fn validate_editable(&self) -> Result<(), TodoError> { + fn validate_editable(&self) -> Result<(), TodoErrorCode> { let today = Utc::now().date_naive(); if self.date <= today { Err(PastDateNotAllowed) @@ -111,7 +111,7 @@ impl Todo { } /// 할 일의 상태 변경은 당일부터 가능하다. - fn validate_state_modifiable(&self) -> Result<(), TodoError> { + fn validate_state_modifiable(&self) -> Result<(), TodoErrorCode> { let today = Utc::now().date_naive(); if self.date > today { Err(StateChangeNotAllowed) @@ -120,7 +120,7 @@ impl Todo { } } - fn find_mut_item(&mut self, item_id: i64) -> Result<&mut TodoItem, TodoError> { + fn find_mut_item(&mut self, item_id: i64) -> Result<&mut TodoItem, TodoErrorCode> { self.items .iter_mut() .find(|item| item.id() == Some(item_id)) @@ -152,7 +152,7 @@ mod tests { use super::*; use crate::todo::models::todo_item::TodoItemBuilder; use crate::todo::models::todo_item_status::TodoItemStatus; - use TodoError::MaxItemLimit; + use TodoErrorCode::MaxItemLimit; use TodoItemStatus::{Altered, Completed, Failed}; fn past_date() -> NaiveDate { diff --git a/backend/todo-domain/src/todo/models/todo_item.rs b/backend/todo-domain/src/todo/models/todo_item.rs index e6ec8fe..c35df89 100644 --- a/backend/todo-domain/src/todo/models/todo_item.rs +++ b/backend/todo-domain/src/todo/models/todo_item.rs @@ -1,4 +1,4 @@ -use crate::todo::error::TodoError::{self, EmptyContent}; +use crate::todo::todo_error_code::TodoErrorCode::{self, EmptyContent}; use crate::todo::models::todo_item_status::TodoItemStatus; use TodoItemStatus::{Altered, Completed, Failed, Pending}; use chrono::{DateTime, Utc}; @@ -19,7 +19,7 @@ pub struct TodoItem { } impl TodoItem { - pub(crate) fn new(todo_id: i64, content: &str) -> Result { + pub(crate) fn new(todo_id: i64, content: &str) -> Result { if content.trim().is_empty() { return Err(EmptyContent); } diff --git a/backend/todo-domain/src/todo/models/todo_item_status.rs b/backend/todo-domain/src/todo/models/todo_item_status.rs index 7fb481e..c4f0779 100644 --- a/backend/todo-domain/src/todo/models/todo_item_status.rs +++ b/backend/todo-domain/src/todo/models/todo_item_status.rs @@ -1,4 +1,4 @@ -use crate::todo::error::TodoError; +use crate::todo::todo_error_code::TodoErrorCode; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TodoItemStatus { @@ -20,15 +20,15 @@ impl TodoItemStatus { } impl TryFrom<&str> for TodoItemStatus { - type Error = TodoError; + type Error = TodoErrorCode; - fn try_from(value: &str) -> Result { + fn try_from(value: &str) -> Result { match value { "PENDING" => Ok(TodoItemStatus::Pending), "COMPLETED" => Ok(TodoItemStatus::Completed), "ALTERED" => Ok(TodoItemStatus::Altered), "FAILED" => Ok(TodoItemStatus::Failed), - _ => Err(TodoError::InvalidStatus), + _ => Err(TodoErrorCode::InvalidStatus), } } } diff --git a/backend/todo-domain/src/todo/todo_error_code.rs b/backend/todo-domain/src/todo/todo_error_code.rs new file mode 100644 index 0000000..8323338 --- /dev/null +++ b/backend/todo-domain/src/todo/todo_error_code.rs @@ -0,0 +1,57 @@ +use thiserror::Error; +use common::constant::status::{BAD_REQUEST, CONFLICT, FORBIDDEN, NOT_FOUND}; +use common::error::{ErrorCode, ErrorReason}; + +#[derive(Debug, Error)] +pub enum TodoErrorCode { + #[error("하루 할 일 제한(3개) 초과")] + MaxItemLimit, + #[error("내용이 비어 있을 수 없음")] + EmptyContent, + #[error("과거 날짜엔 할 일 추가/변경/삭제 불가능")] + PastDateNotAllowed, + #[error("해당 할 일을 찾을 수 없음")] + ItemNotFound, + #[error("상태를 변경할 수 없음")] + StateChangeNotAllowed, + #[error("유효하지 않은 상태값")] + InvalidStatus, +} + +impl ErrorCode for TodoErrorCode { + fn reason(&self) -> ErrorReason { + match self { + TodoErrorCode::MaxItemLimit => ErrorReason { + status: CONFLICT, + code: "TODO_409_1", + message: "하루 최대 3개까지 등록 가능합니다.", + }, + TodoErrorCode::EmptyContent => ErrorReason { + status: BAD_REQUEST, + code: "TODO_400_1", + message: "내용이 비어 있을 수 없습니다.", + }, + TodoErrorCode::PastDateNotAllowed => ErrorReason { + status: FORBIDDEN, + code: "TODO_403_1", + message: "과거 날짜에는 작업할 수 없습니다.", + }, + TodoErrorCode::ItemNotFound => ErrorReason { + status: NOT_FOUND, + code: "TODO_404_1", + message: "등록된 할 일을 찾을 수 없습니다.", + }, + TodoErrorCode::StateChangeNotAllowed => ErrorReason { + status: FORBIDDEN, + code: "TODO_403_2", + message: "해당 할 일의 상태 변경이 불가능합니다.", + }, + TodoErrorCode::InvalidStatus => ErrorReason { + status: BAD_REQUEST, + code: "TODO_400_2", + message: "유효하지 않은 상태값입니다.", + }, + } + } +} + diff --git a/backend/todo-domain/src/user/error.rs b/backend/todo-domain/src/user/error.rs deleted file mode 100644 index 2e728c6..0000000 --- a/backend/todo-domain/src/user/error.rs +++ /dev/null @@ -1,11 +0,0 @@ -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum UserError { - #[error("유효하지 않은 닉네임")] - InvalidNickname, - #[error("유효하지 않은 OAuth 제공자")] - InvalidProvider, - #[error("유효하지 않은 OAuth 유저 ID")] - InvalidProviderUserId, -} diff --git a/backend/todo-domain/src/user/mod.rs b/backend/todo-domain/src/user/mod.rs index 49fa0f0..747f35d 100644 --- a/backend/todo-domain/src/user/mod.rs +++ b/backend/todo-domain/src/user/mod.rs @@ -1,8 +1,8 @@ -pub mod error; +pub mod user_error_code; pub mod models; pub mod repository; -pub use error::UserError::*; +pub use user_error_code::UserErrorCode::*; pub use models::{oauth_provider::OAuthProvider, social_account::SocialAccount, user::User}; pub use repository::{ social_account_repository::SocialAccountRepository, user_repository::UserRepository, diff --git a/backend/todo-domain/src/user/models/oauth_provider.rs b/backend/todo-domain/src/user/models/oauth_provider.rs index d483fa4..a36627e 100644 --- a/backend/todo-domain/src/user/models/oauth_provider.rs +++ b/backend/todo-domain/src/user/models/oauth_provider.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use crate::user::error::UserError; -use UserError::InvalidProvider; +use crate::user::user_error_code::UserErrorCode; +use UserErrorCode::InvalidProvider; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum OAuthProvider { @@ -16,7 +16,7 @@ impl OAuthProvider { } impl FromStr for OAuthProvider { - type Err = UserError; + type Err = UserErrorCode; fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { diff --git a/backend/todo-domain/src/user/models/social_account.rs b/backend/todo-domain/src/user/models/social_account.rs index 641be96..edda16e 100644 --- a/backend/todo-domain/src/user/models/social_account.rs +++ b/backend/todo-domain/src/user/models/social_account.rs @@ -1,4 +1,4 @@ -use crate::user::error::UserError; +use crate::user::user_error_code::UserErrorCode; use crate::user::models::oauth_provider::OAuthProvider; use chrono::{DateTime, Utc}; use derive_builder::Builder; @@ -18,9 +18,9 @@ impl SocialAccount { user_id: i64, provider: OAuthProvider, provider_user_id: String, - ) -> Result { + ) -> Result { if provider_user_id.trim().is_empty() { - return Err(UserError::InvalidProviderUserId); + return Err(UserErrorCode::InvalidProviderUserId); } let now = Utc::now(); diff --git a/backend/todo-domain/src/user/models/user.rs b/backend/todo-domain/src/user/models/user.rs index 9304119..0d57364 100644 --- a/backend/todo-domain/src/user/models/user.rs +++ b/backend/todo-domain/src/user/models/user.rs @@ -1,4 +1,4 @@ -use crate::user::error::UserError; +use crate::user::user_error_code::UserErrorCode; use chrono::{DateTime, Utc}; use derive_builder::Builder; @@ -12,9 +12,9 @@ pub struct User { } impl User { - pub fn new(nickname: String, profile_image_url: Option) -> Result { + pub fn new(nickname: String, profile_image_url: Option) -> Result { if nickname.trim().is_empty() { - return Err(UserError::InvalidNickname); + return Err(UserErrorCode::InvalidNickname); } let now = Utc::now(); diff --git a/backend/todo-domain/src/user/user_error_code.rs b/backend/todo-domain/src/user/user_error_code.rs new file mode 100644 index 0000000..bd26298 --- /dev/null +++ b/backend/todo-domain/src/user/user_error_code.rs @@ -0,0 +1,35 @@ +use thiserror::Error; +use common::constant::status::BAD_REQUEST; +use common::error::{ErrorCode, ErrorReason}; + +#[derive(Debug, Error)] +pub enum UserErrorCode { + #[error("유효하지 않은 닉네임")] + InvalidNickname, + #[error("유효하지 않은 OAuth 제공자")] + InvalidProvider, + #[error("유효하지 않은 OAuth 유저 ID")] + InvalidProviderUserId, +} + +impl ErrorCode for UserErrorCode { + fn reason(&self) -> ErrorReason { + match self { + UserErrorCode::InvalidNickname => ErrorReason { + status: BAD_REQUEST, + code: "USER_400_1", + message: "유효하지 않은 닉네임입니다.", + }, + UserErrorCode::InvalidProvider => ErrorReason { + status: BAD_REQUEST, + code: "USER_400_2", + message: "유효하지 않은 OAuth 제공자입니다.", + }, + UserErrorCode::InvalidProviderUserId => ErrorReason { + status: BAD_REQUEST, + code: "USER_400_3", + message: "유효하지 않은 OAuth 유저 ID입니다.", + }, + } + } +} diff --git a/backend/todo-infra/src/database/database_error_code.rs b/backend/todo-infra/src/database/database_error_code.rs new file mode 100644 index 0000000..731c4dd --- /dev/null +++ b/backend/todo-infra/src/database/database_error_code.rs @@ -0,0 +1,43 @@ +use sea_orm::DbErr; +use thiserror::Error; +use common::constant::status::{CONFLICT, INTERNAL_SERVER_ERROR, NOT_FOUND}; +use common::error::{ErrorCode, ErrorReason}; + +#[derive(Debug, Error)] +pub enum DatabaseErrorCode { + #[error("데이터베이스에 연결할 수 없음")] + ConnectionError(#[source] DbErr), + #[error("쿼리 실행 중 오류 발생")] + QueryError(#[source] DbErr), + #[error("이미 존재하는 값")] + UniqueViolation(#[source] DbErr), + #[error("데이터를 찾을 수 없음")] + NotFound, +} + +impl ErrorCode for DatabaseErrorCode { + fn reason(&self) -> ErrorReason { + match self { + DatabaseErrorCode::ConnectionError(_) => ErrorReason { + status: INTERNAL_SERVER_ERROR, + code: "DB_500_1", + message: "데이터베이스에 연결할 수 없습니다.", + }, + DatabaseErrorCode::QueryError(_) => ErrorReason { + status: INTERNAL_SERVER_ERROR, + code: "DB_500_2", + message: "쿼리 실행 중 오류가 발생했습니다.", + }, + DatabaseErrorCode::UniqueViolation(_) => ErrorReason { + status: CONFLICT, + code: "DB_409_1", + message: "이미 존재하는 값입니다.", + }, + DatabaseErrorCode::NotFound => ErrorReason { + status: NOT_FOUND, + code: "DB_404_1", + message: "데이터를 찾을 수 없습니다.", + }, + } + } +} diff --git a/backend/todo-infra/src/database/error.rs b/backend/todo-infra/src/database/error.rs deleted file mode 100644 index 269f853..0000000 --- a/backend/todo-infra/src/database/error.rs +++ /dev/null @@ -1,14 +0,0 @@ -use sea_orm::DbErr; -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum DatabaseError { - #[error("데이터베이스에 연결할 수 없음")] - ConnectionError(#[source] DbErr), - #[error("쿼리 실행 중 오류 발생")] - QueryError(#[source] DbErr), - #[error("이미 존재하는 값")] - UniqueViolation(#[source] DbErr), - #[error("데이터를 찾을 수 없음")] - NotFound, -} diff --git a/backend/todo-infra/src/database/mod.rs b/backend/todo-infra/src/database/mod.rs index 041863e..3fda8d8 100644 --- a/backend/todo-infra/src/database/mod.rs +++ b/backend/todo-infra/src/database/mod.rs @@ -1,2 +1,2 @@ -pub mod error; +pub mod database_error_code; pub mod postgres; diff --git a/backend/todo-infra/src/database/postgres/todo/todo_mapper.rs b/backend/todo-infra/src/database/postgres/todo/todo_mapper.rs index 06bf880..e46348f 100644 --- a/backend/todo-infra/src/database/postgres/todo/todo_mapper.rs +++ b/backend/todo-infra/src/database/postgres/todo/todo_mapper.rs @@ -1,5 +1,5 @@ use super::{todo_entity, todo_item_entity}; -use domain::todo::error::TodoError; +use domain::todo::todo_error_code::TodoErrorCode; use domain::todo::models::{todo::Todo, todo_item::TodoItem}; use domain::todo::models::{ todo::TodoBuilder, todo_item::TodoItemBuilder, todo_item_status::TodoItemStatus, @@ -35,7 +35,7 @@ impl TodoMapper { pub fn map_entity_to_todo( todo: todo_entity::Model, items: Vec, - ) -> Result { + ) -> Result { let mut item_models = Vec::new(); for i in items { @@ -51,7 +51,7 @@ impl TodoMapper { .created_at(i.created_at.into()) .modified_at(i.modified_at.into()) .build() - .map_err(|_| TodoError::InvalidStatus)?; + .map_err(|_| TodoErrorCode::InvalidStatus)?; item_models.push(item); } @@ -64,7 +64,7 @@ impl TodoMapper { .created_at(todo.created_at.into()) .modified_at(todo.modified_at.into()) .build() - .map_err(|_| TodoError::InvalidStatus)?; + .map_err(|_| TodoErrorCode::InvalidStatus)?; Ok(todo_model) } diff --git a/backend/todo-infra/src/database/postgres/todo/todo_repository_impl.rs b/backend/todo-infra/src/database/postgres/todo/todo_repository_impl.rs index 160c0fe..9c02f2b 100644 --- a/backend/todo-infra/src/database/postgres/todo/todo_repository_impl.rs +++ b/backend/todo-infra/src/database/postgres/todo/todo_repository_impl.rs @@ -3,7 +3,7 @@ use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set}; use super::todo_mapper::TodoMapper; use super::{todo_entity, todo_item_entity}; -use crate::database::error::DatabaseError; +use crate::database::database_error_code::DatabaseErrorCode; use domain::todo::models::{todo::Todo, todo_item::TodoItem}; use domain::todo::repository::todo_repository::TodoRepository; @@ -29,7 +29,7 @@ impl TodoRepository for TodoRepositoryImpl { .filter(todo_entity::Column::Date.eq(date)) .one(&self.db) .await - .map_err(DatabaseError::QueryError)?; + .map_err(DatabaseErrorCode::QueryError)?; let Some(todo) = todo else { return Ok(None); @@ -39,7 +39,7 @@ impl TodoRepository for TodoRepositoryImpl { .filter(todo_item_entity::Column::TodoId.eq(todo.id)) .all(&self.db) .await - .map_err(DatabaseError::QueryError)?; + .map_err(DatabaseErrorCode::QueryError)?; let mapped = TodoMapper::map_entity_to_todo(todo, items)?; @@ -52,7 +52,7 @@ impl TodoRepository for TodoRepositoryImpl { let saved = active .insert(&self.db) .await - .map_err(DatabaseError::QueryError)?; + .map_err(DatabaseErrorCode::QueryError)?; Ok(TodoMapper::map_entity_to_todo(saved, vec![])?) } @@ -63,13 +63,13 @@ impl TodoRepository for TodoRepositoryImpl { let saved = active .update(&self.db) .await - .map_err(DatabaseError::QueryError)?; + .map_err(DatabaseErrorCode::QueryError)?; let items = todo_item_entity::Entity::find() .filter(todo_item_entity::Column::TodoId.eq(saved.id)) .all(&self.db) .await - .map_err(DatabaseError::QueryError)?; + .map_err(DatabaseErrorCode::QueryError)?; Ok(TodoMapper::map_entity_to_todo(saved, items)?) } @@ -78,7 +78,7 @@ impl TodoRepository for TodoRepositoryImpl { let model = todo_item_entity::Entity::find_by_id(item_id) .one(&self.db) .await - .map_err(DatabaseError::QueryError)?; + .map_err(DatabaseErrorCode::QueryError)?; Ok(model .map(|m| TodoMapper::map_entity_to_item(m)) @@ -90,7 +90,7 @@ impl TodoRepository for TodoRepositoryImpl { .filter(todo_item_entity::Column::TodoId.eq(todo_id)) .all(&self.db) .await - .map_err(DatabaseError::QueryError)?; + .map_err(DatabaseErrorCode::QueryError)?; let items = models .into_iter() @@ -111,7 +111,7 @@ impl TodoRepository for TodoRepositoryImpl { let saved = model .insert(&self.db) .await - .map_err(DatabaseError::QueryError)?; + .map_err(DatabaseErrorCode::QueryError)?; TodoMapper::map_entity_to_item(saved) } @@ -122,7 +122,7 @@ impl TodoRepository for TodoRepositoryImpl { let saved = active .update(&self.db) .await - .map_err(DatabaseError::QueryError)?; + .map_err(DatabaseErrorCode::QueryError)?; TodoMapper::map_entity_to_item(saved) } @@ -131,7 +131,7 @@ impl TodoRepository for TodoRepositoryImpl { todo_item_entity::Entity::delete_by_id(item_id) .exec(&self.db) .await - .map_err(DatabaseError::QueryError)?; + .map_err(DatabaseErrorCode::QueryError)?; Ok(()) } diff --git a/backend/todo-infra/src/database/postgres/user/social_account_repository_impl.rs b/backend/todo-infra/src/database/postgres/user/social_account_repository_impl.rs index 8e68298..e2ba766 100644 --- a/backend/todo-infra/src/database/postgres/user/social_account_repository_impl.rs +++ b/backend/todo-infra/src/database/postgres/user/social_account_repository_impl.rs @@ -1,4 +1,4 @@ -use crate::database::error::DatabaseError; +use crate::database::database_error_code::DatabaseErrorCode; use crate::database::postgres::user::social_account_entity; use crate::database::postgres::user::social_account_mapper::SocialAccountMapper; use anyhow::Error; @@ -28,7 +28,7 @@ impl SocialAccountRepository for SocialAccountRepositoryImpl { .filter(social_account_entity::Column::ProviderUserId.eq(provider_user_id)) .one(&self.db) .await - .map_err(DatabaseError::QueryError)?; + .map_err(DatabaseErrorCode::QueryError)?; Ok(social_account .map(|s| SocialAccountMapper::map_to_model(s)) @@ -45,7 +45,7 @@ impl SocialAccountRepository for SocialAccountRepositoryImpl { .filter(social_account_entity::Column::Provider.eq(provider.as_str().to_string())) .one(&self.db) .await - .map_err(DatabaseError::QueryError)?; + .map_err(DatabaseErrorCode::QueryError)?; Ok(social_account .map(|s| SocialAccountMapper::map_to_model(s)) @@ -58,7 +58,7 @@ impl SocialAccountRepository for SocialAccountRepositoryImpl { let saved = social_account .insert(&self.db) .await - .map_err(DatabaseError::QueryError)?; + .map_err(DatabaseErrorCode::QueryError)?; Ok(SocialAccountMapper::map_to_model(saved)?) } diff --git a/backend/todo-infra/src/database/postgres/user/user_repository_impl.rs b/backend/todo-infra/src/database/postgres/user/user_repository_impl.rs index df39c68..221b1da 100644 --- a/backend/todo-infra/src/database/postgres/user/user_repository_impl.rs +++ b/backend/todo-infra/src/database/postgres/user/user_repository_impl.rs @@ -5,7 +5,7 @@ use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter}; use domain::user::models::user::User; use domain::user::repository::user_repository::UserRepository; -use crate::database::error::DatabaseError; +use crate::database::database_error_code::DatabaseErrorCode; use crate::database::postgres::user::user_entity; use crate::database::postgres::user::user_mapper::UserMapper; @@ -25,7 +25,7 @@ impl UserRepository for UserRepositoryImpl { let user = user_entity::Entity::find_by_id(id) .one(&self.db) .await - .map_err(DatabaseError::QueryError)?; + .map_err(DatabaseErrorCode::QueryError)?; Ok(user.map(|u| UserMapper::map_to_model(u)).transpose()?) } @@ -35,7 +35,7 @@ impl UserRepository for UserRepositoryImpl { .filter(user_entity::Column::Nickname.eq(nickname)) .one(&self.db) .await - .map_err(DatabaseError::QueryError)?; + .map_err(DatabaseErrorCode::QueryError)?; Ok(user.map(|u| UserMapper::map_to_model(u)).transpose()?) } @@ -46,7 +46,7 @@ impl UserRepository for UserRepositoryImpl { let saved = user .insert(&self.db) .await - .map_err(DatabaseError::QueryError)?; + .map_err(DatabaseErrorCode::QueryError)?; Ok(UserMapper::map_to_model(saved)?) } @@ -57,7 +57,7 @@ impl UserRepository for UserRepositoryImpl { let updated = user .update(&self.db) .await - .map_err(DatabaseError::QueryError)?; + .map_err(DatabaseErrorCode::QueryError)?; Ok(UserMapper::map_to_model(updated)?) } @@ -66,7 +66,7 @@ impl UserRepository for UserRepositoryImpl { user_entity::Entity::delete_by_id(id) .exec(&self.db) .await - .map_err(DatabaseError::QueryError)?; + .map_err(DatabaseErrorCode::QueryError)?; Ok(()) } From 1b9d3128546d5bcaaa7d259929f0b04eaced6f43 Mon Sep 17 00:00:00 2001 From: zakie Date: Mon, 24 Nov 2025 13:45:36 +0900 Subject: [PATCH 16/23] =?UTF-8?q?fix:=20=EB=A6=B0=ED=8A=B8=20cargo=20fmt?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/todo-api/src/domain/mod.rs | 2 ++ backend/todo-domain/src/todo/mod.rs | 2 +- backend/todo-domain/src/todo/models/todo.rs | 2 +- backend/todo-domain/src/todo/models/todo_item.rs | 2 +- backend/todo-domain/src/todo/todo_error_code.rs | 3 +-- backend/todo-domain/src/user/mod.rs | 4 ++-- backend/todo-domain/src/user/models/oauth_provider.rs | 2 +- backend/todo-domain/src/user/models/social_account.rs | 2 +- .../src/user/repository/social_account_repository.rs | 2 +- backend/todo-domain/src/user/repository/user_repository.rs | 2 +- backend/todo-domain/src/user/user_error_code.rs | 2 +- backend/todo-infra/src/database/database_error_code.rs | 4 ++-- backend/todo-infra/src/database/postgres/todo/todo_mapper.rs | 2 +- .../src/database/postgres/user/social_account_entity.rs | 4 ++-- backend/todo-infra/src/database/postgres/user/user_entity.rs | 2 +- 15 files changed, 19 insertions(+), 18 deletions(-) diff --git a/backend/todo-api/src/domain/mod.rs b/backend/todo-api/src/domain/mod.rs index ac77f63..6b3bf2f 100644 --- a/backend/todo-api/src/domain/mod.rs +++ b/backend/todo-api/src/domain/mod.rs @@ -1 +1,3 @@ pub mod system; +pub mod todo; +pub mod user; diff --git a/backend/todo-domain/src/todo/mod.rs b/backend/todo-domain/src/todo/mod.rs index e2f5c5b..74ec38c 100644 --- a/backend/todo-domain/src/todo/mod.rs +++ b/backend/todo-domain/src/todo/mod.rs @@ -1,5 +1,5 @@ -pub mod todo_error_code; pub mod models; pub mod repository; +pub mod todo_error_code; pub use todo_error_code::TodoErrorCode::*; diff --git a/backend/todo-domain/src/todo/models/todo.rs b/backend/todo-domain/src/todo/models/todo.rs index bde61a8..88e768f 100644 --- a/backend/todo-domain/src/todo/models/todo.rs +++ b/backend/todo-domain/src/todo/models/todo.rs @@ -1,5 +1,5 @@ -use crate::todo::todo_error_code::TodoErrorCode; use crate::todo::models::todo_item::TodoItem; +use crate::todo::todo_error_code::TodoErrorCode; use crate::todo::{ EmptyContent, ItemNotFound, MaxItemLimit, PastDateNotAllowed, StateChangeNotAllowed, }; diff --git a/backend/todo-domain/src/todo/models/todo_item.rs b/backend/todo-domain/src/todo/models/todo_item.rs index c35df89..98fc2e9 100644 --- a/backend/todo-domain/src/todo/models/todo_item.rs +++ b/backend/todo-domain/src/todo/models/todo_item.rs @@ -1,5 +1,5 @@ -use crate::todo::todo_error_code::TodoErrorCode::{self, EmptyContent}; use crate::todo::models::todo_item_status::TodoItemStatus; +use crate::todo::todo_error_code::TodoErrorCode::{self, EmptyContent}; use TodoItemStatus::{Altered, Completed, Failed, Pending}; use chrono::{DateTime, Utc}; use derive_builder::Builder; diff --git a/backend/todo-domain/src/todo/todo_error_code.rs b/backend/todo-domain/src/todo/todo_error_code.rs index 8323338..bdc3a6d 100644 --- a/backend/todo-domain/src/todo/todo_error_code.rs +++ b/backend/todo-domain/src/todo/todo_error_code.rs @@ -1,6 +1,6 @@ -use thiserror::Error; use common::constant::status::{BAD_REQUEST, CONFLICT, FORBIDDEN, NOT_FOUND}; use common::error::{ErrorCode, ErrorReason}; +use thiserror::Error; #[derive(Debug, Error)] pub enum TodoErrorCode { @@ -54,4 +54,3 @@ impl ErrorCode for TodoErrorCode { } } } - diff --git a/backend/todo-domain/src/user/mod.rs b/backend/todo-domain/src/user/mod.rs index 747f35d..58a35be 100644 --- a/backend/todo-domain/src/user/mod.rs +++ b/backend/todo-domain/src/user/mod.rs @@ -1,9 +1,9 @@ -pub mod user_error_code; pub mod models; pub mod repository; +pub mod user_error_code; -pub use user_error_code::UserErrorCode::*; pub use models::{oauth_provider::OAuthProvider, social_account::SocialAccount, user::User}; pub use repository::{ social_account_repository::SocialAccountRepository, user_repository::UserRepository, }; +pub use user_error_code::UserErrorCode::*; diff --git a/backend/todo-domain/src/user/models/oauth_provider.rs b/backend/todo-domain/src/user/models/oauth_provider.rs index a36627e..7c78bba 100644 --- a/backend/todo-domain/src/user/models/oauth_provider.rs +++ b/backend/todo-domain/src/user/models/oauth_provider.rs @@ -1,6 +1,6 @@ -use std::str::FromStr; use crate::user::user_error_code::UserErrorCode; use UserErrorCode::InvalidProvider; +use std::str::FromStr; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum OAuthProvider { diff --git a/backend/todo-domain/src/user/models/social_account.rs b/backend/todo-domain/src/user/models/social_account.rs index edda16e..843edfb 100644 --- a/backend/todo-domain/src/user/models/social_account.rs +++ b/backend/todo-domain/src/user/models/social_account.rs @@ -1,5 +1,5 @@ -use crate::user::user_error_code::UserErrorCode; use crate::user::models::oauth_provider::OAuthProvider; +use crate::user::user_error_code::UserErrorCode; use chrono::{DateTime, Utc}; use derive_builder::Builder; diff --git a/backend/todo-domain/src/user/repository/social_account_repository.rs b/backend/todo-domain/src/user/repository/social_account_repository.rs index c649d00..cbc009e 100644 --- a/backend/todo-domain/src/user/repository/social_account_repository.rs +++ b/backend/todo-domain/src/user/repository/social_account_repository.rs @@ -1,6 +1,6 @@ -use async_trait::async_trait; use crate::user::models::oauth_provider::OAuthProvider; use crate::user::models::social_account::SocialAccount; +use async_trait::async_trait; #[async_trait] pub trait SocialAccountRepository { diff --git a/backend/todo-domain/src/user/repository/user_repository.rs b/backend/todo-domain/src/user/repository/user_repository.rs index aadd08a..476452f 100644 --- a/backend/todo-domain/src/user/repository/user_repository.rs +++ b/backend/todo-domain/src/user/repository/user_repository.rs @@ -1,5 +1,5 @@ -use async_trait::async_trait; use crate::user::models::user::User; +use async_trait::async_trait; #[async_trait] pub trait UserRepository { diff --git a/backend/todo-domain/src/user/user_error_code.rs b/backend/todo-domain/src/user/user_error_code.rs index bd26298..096112c 100644 --- a/backend/todo-domain/src/user/user_error_code.rs +++ b/backend/todo-domain/src/user/user_error_code.rs @@ -1,6 +1,6 @@ -use thiserror::Error; use common::constant::status::BAD_REQUEST; use common::error::{ErrorCode, ErrorReason}; +use thiserror::Error; #[derive(Debug, Error)] pub enum UserErrorCode { diff --git a/backend/todo-infra/src/database/database_error_code.rs b/backend/todo-infra/src/database/database_error_code.rs index 731c4dd..97b1fc9 100644 --- a/backend/todo-infra/src/database/database_error_code.rs +++ b/backend/todo-infra/src/database/database_error_code.rs @@ -1,7 +1,7 @@ -use sea_orm::DbErr; -use thiserror::Error; use common::constant::status::{CONFLICT, INTERNAL_SERVER_ERROR, NOT_FOUND}; use common::error::{ErrorCode, ErrorReason}; +use sea_orm::DbErr; +use thiserror::Error; #[derive(Debug, Error)] pub enum DatabaseErrorCode { diff --git a/backend/todo-infra/src/database/postgres/todo/todo_mapper.rs b/backend/todo-infra/src/database/postgres/todo/todo_mapper.rs index e46348f..291fbb0 100644 --- a/backend/todo-infra/src/database/postgres/todo/todo_mapper.rs +++ b/backend/todo-infra/src/database/postgres/todo/todo_mapper.rs @@ -1,9 +1,9 @@ use super::{todo_entity, todo_item_entity}; -use domain::todo::todo_error_code::TodoErrorCode; use domain::todo::models::{todo::Todo, todo_item::TodoItem}; use domain::todo::models::{ todo::TodoBuilder, todo_item::TodoItemBuilder, todo_item_status::TodoItemStatus, }; +use domain::todo::todo_error_code::TodoErrorCode; use sea_orm::{NotSet, Set}; pub struct TodoMapper; diff --git a/backend/todo-infra/src/database/postgres/user/social_account_entity.rs b/backend/todo-infra/src/database/postgres/user/social_account_entity.rs index fc2d817..fbcd12c 100644 --- a/backend/todo-infra/src/database/postgres/user/social_account_entity.rs +++ b/backend/todo-infra/src/database/postgres/user/social_account_entity.rs @@ -1,5 +1,5 @@ -use sea_orm::entity::prelude::*; use crate::database::postgres::user::user_entity; +use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "social_accounts")] @@ -15,7 +15,7 @@ pub struct Model { #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { - Users + Users, } impl RelationTrait for Relation { diff --git a/backend/todo-infra/src/database/postgres/user/user_entity.rs b/backend/todo-infra/src/database/postgres/user/user_entity.rs index 22bdfdf..17f44b2 100644 --- a/backend/todo-infra/src/database/postgres/user/user_entity.rs +++ b/backend/todo-infra/src/database/postgres/user/user_entity.rs @@ -1,5 +1,5 @@ -use sea_orm::entity::prelude::*; use crate::database::postgres::user::social_account_entity; +use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "users")] From 8cf8ae6f53d27e785aaf65af716065bc20f5d6aa Mon Sep 17 00:00:00 2001 From: zakie Date: Mon, 24 Nov 2025 13:47:45 +0900 Subject: [PATCH 17/23] =?UTF-8?q?chore:=20api=20=ED=81=AC=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20=EA=B5=AC=EC=A1=B0=EB=A7=8C=20=EC=9E=A1?= =?UTF-8?q?=EA=B3=A0,=20=EB=82=A8=EC=9D=80=20api=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=EB=8A=94=20=EB=8B=A4=EC=9D=8C=20=EC=9D=B4?= =?UTF-8?q?=EC=8A=88=EC=97=90=EC=84=9C=20=EC=A7=84=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/todo-api/src/domain/todo/handlers.rs | 20 ++++++++++++++++++++ backend/todo-api/src/domain/todo/mod.rs | 2 ++ backend/todo-api/src/domain/todo/routes.rs | 9 +++++++++ backend/todo-api/src/domain/user/handlers.rs | 1 + backend/todo-api/src/domain/user/mod.rs | 2 ++ backend/todo-api/src/domain/user/routes.rs | 1 + backend/todo-api/src/routes.rs | 13 ++++++++----- 7 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 backend/todo-api/src/domain/todo/handlers.rs create mode 100644 backend/todo-api/src/domain/todo/mod.rs create mode 100644 backend/todo-api/src/domain/todo/routes.rs create mode 100644 backend/todo-api/src/domain/user/handlers.rs create mode 100644 backend/todo-api/src/domain/user/mod.rs create mode 100644 backend/todo-api/src/domain/user/routes.rs diff --git a/backend/todo-api/src/domain/todo/handlers.rs b/backend/todo-api/src/domain/todo/handlers.rs new file mode 100644 index 0000000..3491d2d --- /dev/null +++ b/backend/todo-api/src/domain/todo/handlers.rs @@ -0,0 +1,20 @@ +// use axum::extract::{Query, State}; +// use axum::response::IntoResponse; +// use serde::Deserialize; +// use domain::todo::todo_error_code::TodoErrorCode; +// use crate::bootstrap::AppState; +// use crate::common::error::app_error::AppError; +// use crate::common::response::api_response::ApiResponse; +// +// #[derive(Deserialize)] +// pub struct MonthlyQuery { +// pub year: i32, +// pub month: u32, +// } +// +// pub async fn get_monthly_todos( +// State(state): State(AppState), +// Query(q): Query, +// ) -> Result, AppError> { +// +// } diff --git a/backend/todo-api/src/domain/todo/mod.rs b/backend/todo-api/src/domain/todo/mod.rs new file mode 100644 index 0000000..c0c696a --- /dev/null +++ b/backend/todo-api/src/domain/todo/mod.rs @@ -0,0 +1,2 @@ +pub mod handlers; +pub mod routes; diff --git a/backend/todo-api/src/domain/todo/routes.rs b/backend/todo-api/src/domain/todo/routes.rs new file mode 100644 index 0000000..10be5bf --- /dev/null +++ b/backend/todo-api/src/domain/todo/routes.rs @@ -0,0 +1,9 @@ +// use axum::Router; +// use axum::routing::{get, post, patch, delete}; +// +// pub fn todo_routes() -> Router { +// Router::new() +// .route("/", get(get_monthly_todos).post(create_todo)) +// .route("/:id", patch(update_todo).delete(delete_todo)) +// .route("/:id/status", patch(update_todo_status)) +// } diff --git a/backend/todo-api/src/domain/user/handlers.rs b/backend/todo-api/src/domain/user/handlers.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/backend/todo-api/src/domain/user/handlers.rs @@ -0,0 +1 @@ + diff --git a/backend/todo-api/src/domain/user/mod.rs b/backend/todo-api/src/domain/user/mod.rs new file mode 100644 index 0000000..c0c696a --- /dev/null +++ b/backend/todo-api/src/domain/user/mod.rs @@ -0,0 +1,2 @@ +pub mod handlers; +pub mod routes; diff --git a/backend/todo-api/src/domain/user/routes.rs b/backend/todo-api/src/domain/user/routes.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/backend/todo-api/src/domain/user/routes.rs @@ -0,0 +1 @@ + diff --git a/backend/todo-api/src/routes.rs b/backend/todo-api/src/routes.rs index df0fed8..7bf6106 100644 --- a/backend/todo-api/src/routes.rs +++ b/backend/todo-api/src/routes.rs @@ -6,9 +6,12 @@ use crate::domain::system::routes::system_routes; use axum::Router; pub fn create_app_router(config: &AppConfig) -> Router { - Router::new() - .nest("/system", system_routes()) - .layer(build_compression_layer()) - .layer(build_concurrency_limit_layer()) - .layer(build_cors(config)) + Router::new().nest( + "/api/v1", + Router::new() + .nest("/system", system_routes()) + .layer(build_compression_layer()) + .layer(build_concurrency_limit_layer()) + .layer(build_cors(config)), + ) } From 3544569a89bef7c185bc98f10d261147ddbd48cf Mon Sep 17 00:00:00 2001 From: Jaeyeong Choi Date: Mon, 24 Nov 2025 17:20:16 +0900 Subject: [PATCH 18/23] =?UTF-8?q?chore:=20=EB=A0=88=ED=8F=AC=EC=A7=80?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=20=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20sonar=20cloud=20=EC=84=B8=ED=8C=85=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/sonarcloud.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 2a5b957..791ade8 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -50,6 +50,6 @@ jobs: with: args: > -Dsonar.projectBaseDir=backend - -Dsonar.projectKey=zzaekkii_challenge-to-do - -Dsonar.organization=zzaekkii + -Dsonar.projectKey=morutine_backend + -Dsonar.organization=morutine -Dsonar.javascript.lcov.reportPaths=coverage.lcov From 9a384c30eab131a7effcb00a1f3dce0693fd7a01 Mon Sep 17 00:00:00 2001 From: Jaeyeong Choi Date: Mon, 24 Nov 2025 17:32:59 +0900 Subject: [PATCH 19/23] =?UTF-8?q?fix:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=ED=82=A4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/sonarcloud.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 791ade8..d2a4a43 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -50,6 +50,6 @@ jobs: with: args: > -Dsonar.projectBaseDir=backend - -Dsonar.projectKey=morutine_backend + -Dsonar.projectKey=backend -Dsonar.organization=morutine -Dsonar.javascript.lcov.reportPaths=coverage.lcov From df9cab6b42f2ba237ba16daf57b61d84a4734270 Mon Sep 17 00:00:00 2001 From: Jaeyeong Choi Date: Mon, 24 Nov 2025 17:44:22 +0900 Subject: [PATCH 20/23] =?UTF-8?q?fix:=20sonar=20cloud=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=BC=20=EB=82=B4=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EA=B2=BD=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/sonarcloud.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index d2a4a43..1db5749 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -49,7 +49,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: args: > - -Dsonar.projectBaseDir=backend + -Dsonar.projectBaseDir=backend/backend -Dsonar.projectKey=backend -Dsonar.organization=morutine -Dsonar.javascript.lcov.reportPaths=coverage.lcov From b77987f75ed25663dfd2e4fe16d238e7385ff040 Mon Sep 17 00:00:00 2001 From: Jaeyeong Choi Date: Mon, 24 Nov 2025 17:57:26 +0900 Subject: [PATCH 21/23] =?UTF-8?q?fix:=20sonar=20cloud=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/sonarcloud.yml | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 1db5749..1f47d34 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -25,23 +25,6 @@ jobs: - name: Install grcov run: cargo install grcov - - name: Run tests with coverage instrumentation - working-directory: backend - run: | - CARGO_INCREMENTAL=0 \ - RUSTFLAGS="-Cinstrument-coverage" \ - LLVM_PROFILE_FILE="coverage-%p-%m.profraw" \ - cargo test --all --verbose - - - name: Generate coverage report (lcov) - working-directory: backend - run: | - grcov . \ - --binary-path ./target/debug/ \ - -s . -t lcov \ - --branch --ignore-not-existing \ - -o coverage.lcov - - name: SonarCloud Scan uses: SonarSource/sonarcloud-github-action@v2 env: From a43be2a1bc47bb68bd7e322e5d44f642a74daa04 Mon Sep 17 00:00:00 2001 From: Jaeyeong Choi Date: Mon, 24 Nov 2025 18:03:09 +0900 Subject: [PATCH 22/23] =?UTF-8?q?fix:=20sonar=20cloud=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20=EB=82=B4=20=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EC=B5=9C=EC=A2=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/sonarcloud.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 1f47d34..6dba084 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -32,7 +32,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: args: > - -Dsonar.projectBaseDir=backend/backend + -Dsonar.projectBaseDir=backend -Dsonar.projectKey=backend -Dsonar.organization=morutine -Dsonar.javascript.lcov.reportPaths=coverage.lcov From 5ed18ac5fee53ed755559b242b3c9fe024c951aa Mon Sep 17 00:00:00 2001 From: Jaeyeong Choi Date: Mon, 24 Nov 2025 18:25:07 +0900 Subject: [PATCH 23/23] =?UTF-8?q?chore:=20sonar=20cloud=20=EC=9E=AC?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/sonarcloud.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 6dba084..90cfa60 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -30,7 +30,7 @@ jobs: env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: + with: args: > -Dsonar.projectBaseDir=backend -Dsonar.projectKey=backend