diff --git a/tasklite-core/source/Cli.hs b/tasklite-core/source/Cli.hs index a50df31..57c776e 100644 --- a/tasklite-core/source/Cli.hs +++ b/tasklite-core/source/Cli.hs @@ -157,6 +157,7 @@ import Lib ( adjustPriority, countTasks, deletableTasks, + deleteNote, deleteTag, deleteTasks, doTasks, @@ -261,6 +262,7 @@ data Command | AddTag TagText [IdText] | DeleteTag TagText [IdText] | AddNote Text [IdText] + | DeleteNote IdText | SetDueUtc DateTime [IdText] | Start [IdText] | Stop [IdText] @@ -382,7 +384,7 @@ aliasWarning alias = <+> "Use" <+> dquotes (pretty alias) <+> "instead." - <> hardline + <> hardline getCommand :: (Text, Text) -> Mod CommandFields Command @@ -574,6 +576,10 @@ commandParser conf = <*> some (strArgument idsVar)) "Add a note to specified tasks") + <> command "deletenote" (toParserInfo (DeleteNote + <$> strArgument (metavar "ULID" <> help "The ULID of the note")) + "Delete the specified note") + <> command "due" (toParserInfo (SetDueUtc <$> argument (maybeReader (parseUtc . T.pack)) (metavar "DUE_UTC" <> help "Due timestamp in UTC") @@ -957,11 +963,11 @@ commandParserInfo conf = header = annotate (bold <> color Blue) "TaskLite" <+> prettyVersion - <> hardline - <> hardline - <> annotate - (color Blue) - "Task-list manager powered by Haskell and SQLite" + <> hardline + <> hardline + <> annotate + (color Blue) + "Task-list manager powered by Haskell and SQLite" examples = do let @@ -1207,6 +1213,7 @@ executeCLiCommand conf now connection progName args = do AddTag tagText ids -> addTag conf connection tagText ids DeleteTag tagText ids -> deleteTag conf connection tagText ids AddNote noteText ids -> addNote conf connection noteText ids + DeleteNote id -> deleteNote conf connection id SetDueUtc datetime ids -> setDueUtc conf connection datetime ids Duplicate ids -> duplicateTasks conf connection ids CountFiltered taskFilter -> countTasks conf connection taskFilter diff --git a/tasklite-core/source/Lib.hs b/tasklite-core/source/Lib.hs index 80d4d8a..07d9c0a 100644 --- a/tasklite-core/source/Lib.hs +++ b/tasklite-core/source/Lib.hs @@ -129,7 +129,11 @@ import Data.Time.Clock (UTCTime) import Data.Time.ISO8601.Duration qualified as Iso import Data.ULID (ULID, getULID) import Data.Yaml as Yaml (encode) -import Database.SQLite.Simple (Error (ErrorConstraint), Only (Only), SQLError (sqlError)) +import Database.SQLite.Simple ( + Error (ErrorConstraint), + Only (Only), + SQLError (sqlError), + ) import Database.SQLite.Simple as Sql ( Connection, FromRow (..), @@ -143,6 +147,7 @@ import Database.SQLite.Simple as Sql ( field, open, query, + queryNamed, query_, toRow, withConnection, @@ -1718,6 +1723,38 @@ addNote conf connection noteBody ids = do pure $ vsep docs +deleteNote :: Config -> Connection -> IdText -> IO (Doc AnsiStyle) +deleteNote _conf connection noteId = do + taskIds :: [Only Text] <- + queryNamed + connection + [sql| + DELETE FROM task_to_note + WHERE ulid == :noteId + RETURNING task_ulid + |] + [":noteId" := noteId] + + case taskIds of + [Only taskId] -> + pure $ + "💥 Deleted note" + <+> dquotes (pretty noteId) + <+> "of task" + <+> dquotes (pretty taskId) + [] -> + pure $ + annotate (color Yellow) $ + "⚠️ Note" <+> dquotes (pretty noteId) <+> "does not exist" + _ -> + pure $ + annotate (color Yellow) $ + ("⚠️ Note" <+> dquotes (pretty noteId) <+> "exists multiple times.") + <++> "This indicates a serious database inconsistency \ + \and you should clean up the database manually \ + \before continuing." + + setDueUtc :: Config -> Connection -> DateTime -> [IdText] -> IO (Doc AnsiStyle) setDueUtc conf connection datetime ids = do let @@ -1925,7 +1962,7 @@ unnoteTasks conf connection ids = do |] [":task_ulid" := task.ulid] - pure $ getResultMsg "💥 Removed all notes" task + pure $ getResultMsg "💥 Deleted all notes" task pure $ vsep docs diff --git a/tasklite-core/test/LibSpec.hs b/tasklite-core/test/LibSpec.hs index e60150b..bf4646c 100644 --- a/tasklite-core/test/LibSpec.hs +++ b/tasklite-core/test/LibSpec.hs @@ -24,8 +24,10 @@ import Test.Hspec ( import Data.Hourglass (DateTime) import Data.Text qualified as T import ImportExport (PreEdit (ApplyPreEdit), editTaskByTask) -import Lib (addTag, countTasks, insertRecord, insertTags, newTasks) +import Lib (addTag, countTasks, deleteNote, insertRecord, insertTags, newTasks) import Task (Task (body, closed_utc, state, ulid), TaskState (Done), zeroTask) +import TaskToNote (TaskToNote (TaskToNote)) +import TaskToNote qualified import TestUtils (withMemoryDb) @@ -109,3 +111,22 @@ spec now = do task1 let errMsg = "Tag \"" <> T.unpack existTag <> "\" is already assigned" show cliOutput `shouldContain` errMsg + + it "lets you delete a note" $ do + withMemoryDb defaultConfig $ \memConn -> do + insertRecord "tasks" memConn task1 + let noteId = "01hwcqk9nnwjypzw9kr646nqce" + insertRecord + "task_to_note" + memConn + TaskToNote + { TaskToNote.ulid = noteId + , TaskToNote.task_ulid = task1.ulid + , TaskToNote.note = "The note content" + } + + cliOutput <- deleteNote defaultConfig memConn noteId + + (show cliOutput :: Text) + `shouldBe` "\128165 Deleted note \"01hwcqk9nnwjypzw9kr646nqce\" \ + \of task \"01hs68z7mdg4ktpxbv0yfafznq\""