diff --git a/api/services/src/todos.rs b/api/services/src/todos.rs index 8d186ff..b7f90c4 100644 --- a/api/services/src/todos.rs +++ b/api/services/src/todos.rs @@ -395,6 +395,23 @@ impl TodoService { Ok(()) } + pub async fn reactivate_by_id( + user: &User, + db: &OrmConnection, + id: Uuid, + ) -> Result<(), TodoServiceError> { + Todo::update(todo::ActiveModel { + id: Set(id), + deleted: Set(None), + ..Default::default() + }) + .filter(todo::Column::OwnerId.eq(user.id.clone())) + .exec(db) + .await?; + + Ok(()) + } + pub async fn add_check( user: &User, db: &OrmConnection, diff --git a/api/src/routes/todo.rs b/api/src/routes/todo.rs index 0e270f3..8556987 100644 --- a/api/src/routes/todo.rs +++ b/api/src/routes/todo.rs @@ -54,6 +54,7 @@ pub fn routes(state: AppState) -> Router { .route("/", get(get_all_todos)) .route("/", put(add_todo)) .route("/", post(update_todo)) + .route("/reactivate/{todo_uuid}", post(reactivate_todo)) .route("/{todo_uuid}", delete(delete_todo)) .route("/sse", get(sse_handler)) .route("/check/{todo_uuid}", post(add_check)) @@ -144,6 +145,23 @@ async fn delete_todo( Ok(Json(deleted_todo)) } +async fn reactivate_todo( + state: State, + Path(todo_uuid): Path, + user: User, +) -> Result, ApiError> { + let to_be_deleted_todo = TodoService::get_by_id(&user, &state.db_connection, todo_uuid).await?; + + let Some(deleted_todo) = to_be_deleted_todo else { + return Err(ApiError::NotFound); + }; + + TodoService::reactivate_by_id(&user, &state.db_connection, todo_uuid).await?; + + state.event_service.broadcast(events::Event::Todo(user.id)); + Ok(Json(deleted_todo)) +} + async fn sse_handler( state: State, user: User, diff --git a/api/tests/common/client.rs b/api/tests/common/client.rs index 0a662ed..1e8e3de 100644 --- a/api/tests/common/client.rs +++ b/api/tests/common/client.rs @@ -74,6 +74,10 @@ impl Client { Self::json(self.update_todo(todo).await) } + pub async fn reactivate_todo(&self, todo_uuid: Uuid) -> TestResponse { + self.post(&format!("todos/reactivate/{}", todo_uuid)).await + } + pub async fn delete_todo(&self, todo_uuid: Uuid) -> TestResponse { self.delete(&format!("todos/{}", todo_uuid)).await } diff --git a/api/tests/todos/delete.rs b/api/tests/todos/delete.rs index 865958c..83cfebe 100644 --- a/api/tests/todos/delete.rs +++ b/api/tests/todos/delete.rs @@ -70,6 +70,42 @@ async fn get_deleted_todo() { assert_eq!(todos.len(), 1); } +#[tokio::test] +async fn reactivate_deleted_todo() { + let client = get_client().await; + + let todo_uuid = Uuid::new_v4(); + client + .add_todo_json(json!( + { + "data": { + "uuid": todo_uuid, + "title": "Test Todo", + "note": "Test Note", + "tags": [], + "checks": [], + } + })) + .await; + + let response = client.delete_todo(todo_uuid).await; + response.assert_status_success(); + + let todos = client.get_deleted_todos_json().await; + assert_eq!(todos.len(), 1); + + client + .reactivate_todo(todo_uuid) + .await + .assert_status_success(); + + let todos = client.get_deleted_todos_json().await; + assert_eq!(todos.len(), 0); + + let todos = client.get_todos_json().await; + assert_eq!(todos.len(), 1); +} + #[tokio::test] async fn get_deleted_todo_with_labels() { let client = get_client().await; diff --git a/app/components/Todo/DeletedTodo.vue b/app/components/Todo/DeletedTodo.vue new file mode 100644 index 0000000..d69c17c --- /dev/null +++ b/app/components/Todo/DeletedTodo.vue @@ -0,0 +1,21 @@ + + + diff --git a/app/components/Todo/DeletedTodos.vue b/app/components/Todo/DeletedTodos.vue new file mode 100644 index 0000000..fd8fe6f --- /dev/null +++ b/app/components/Todo/DeletedTodos.vue @@ -0,0 +1,29 @@ + + + diff --git a/app/components/Todo/Todo.vue b/app/components/Todo/Todo.vue index f068a89..7057bd5 100644 --- a/app/components/Todo/Todo.vue +++ b/app/components/Todo/Todo.vue @@ -1,9 +1,8 @@ diff --git a/app/components/Todo/TodoShell.vue b/app/components/Todo/TodoShell.vue new file mode 100644 index 0000000..ce177bc --- /dev/null +++ b/app/components/Todo/TodoShell.vue @@ -0,0 +1,105 @@ + + + diff --git a/app/pages/deleted.vue b/app/pages/deleted.vue new file mode 100644 index 0000000..7bf1c44 --- /dev/null +++ b/app/pages/deleted.vue @@ -0,0 +1,11 @@ + + diff --git a/app/stores/useDeletedTodoStore.ts b/app/stores/useDeletedTodoStore.ts new file mode 100644 index 0000000..1f8ef6a --- /dev/null +++ b/app/stores/useDeletedTodoStore.ts @@ -0,0 +1,37 @@ +import { defineStore } from "pinia"; +import type { Todo, UUID } from "~/utils/types"; + +export const useDeletedTodoStore = defineStore("deletedTodos", { + state: (): { data: Todo[] } => ({ + data: [], + }), + actions: { + async fetch() { + const { showError } = useToast(); + + const { $api } = useNuxtApp(); + const data = await $api("/api/todos?deleted=true").catch( + async (err) => { + showError(err.message); + return []; + }, + ); + + this.data = data; + }, + + async reActivateTodo(uuid: UUID) { + const { showError } = useToast(); + + this.data = this.data.filter((todo) => todo.uuid !== uuid); + + const { $api } = useNuxtApp(); + await $api(`/api/todos/reactivate/${uuid}`, { + method: "POST", + }).catch(async (err) => { + showError(err.message); + await this.fetch(); + }); + }, + }, +});