# TodoistSDK [![Hackage](https://img.shields.io/hackage/v/todoist-sdk.svg)](https://hackage.haskell.org/package/todoist-sdk) An unofficial Haskell SDK for the [Todoist REST API](https://developer.todoist.com/rest/v2/). Manage projects, tasks, comments, sections, and labels with type-safe, ergonomic Haskell functions. ## Features - **Complete API Coverage**: Projects, Tasks, Comments, Sections, and Labels - **Type-Safe**: Leverages Haskell's type system to prevent common errors - **Getter-Only Lenses**: Read-only lenses for convenient field access and composition - **Ergonomic Builder Pattern**: Easily construct API requests with optional fields - **Automatic Pagination**: Transparently fetches all pages for list operations - **Flexible Error Handling**: Operations return `Either TodoistError a` for explicit error handling - **Testing Support (In Progress)**: Includes Trace interpreter for testing without API calls ## Installation ### Using Stack Add to your `stack.yaml`: ```yaml extra-deps: - todoist-sdk-0.1.2.1 ``` Add to your `package.yaml` or `.cabal` file: ```yaml dependencies: - todoist-sdk ``` ### Using Cabal Add to your `.cabal` file's `build-depends`: ```cabal build-depends: base >=4.7 && <5 , todoist-sdk ``` Then install: ```bash cabal update cabal install todoist-sdk ``` ## Quick Start Get your API token from [Todoist Settings → Integrations → Developer](https://todoist.com/prefs/integrations). ### Try it in the REPL ```bash $ stack repl ``` ```haskell >>> import Web.Todoist >>> let config = newTodoistConfig "your-api-token-here" >>> result <- todoist config getAllProjects >>> case result of Left err -> print err Right projects -> mapM_ print projects ``` ### Complete Example Create a file `example.hs`: ```haskell #!/usr/bin/env stack -- stack --resolver lts-24.7 script --package todoist-sdk --package text {-# LANGUAGE OverloadedStrings #-} import Web.Todoist main :: IO () main = do -- Configure with your API token let config = newTodoistConfig "your-api-token-here" result <- todoist config $ do -- Create a new project let newProj = runBuilder (createProjectBuilder "My Haskell Project") (withDescription "Learning Haskell SDK" <> withViewStyle Board) project <- addProject newProj -- Create a task in the project let newTask = runBuilder (newTaskBuilder "Read documentation") (withProjectId (_id project) <> withPriority 2) task <- createTask newTask -- Get all tasks tasks <- getTasks taskParamBuilder pure (project, task, tasks) case result of Left err -> putStrLn $ "Error: " ++ show err Right (proj, task, tasks) -> do putStrLn $ "Created project: " ++ show (_name proj) putStrLn $ "Created task: " ++ show (_content task) putStrLn $ "Total tasks: " ++ show (length tasks) ``` Run it: ```bash chmod +x example.hs ./example.hs ``` ## Common Usage Examples ### Working with Projects ```haskell import Web.Todoist let config = newTodoistConfig "your-api-token" -- Get all projects result <- todoist config getAllProjects -- Create a project with optional fields let newProject = runBuilder (createProjectBuilder "Shopping List") (withColor "blue" <> withViewStyle List <> withIsFavorite True) project <- todoist config (addProject createProjectBuilder) -- Update a project let update = runBuilder emptyProjectUpdate (withName "Updated Name") updated <- todoist config (updateProject update projectId) -- Delete a project todoist config (deleteProject projectId) ``` ### Working with Tasks ```haskell -- Create a task with due date let task = runBuilder (newTaskBuilder "Buy milk") (withProjectId "project-123" <> withDueString "tomorrow" <> withPriority 3 <> withLabels ["grocery", "urgent"]) result <- todoist config (createTask task) -- Get tasks with filters let params = TaskParam { project_id = Just "project-123" , filter = Nothing , label_id = Nothing , cursor = Nothing , limit = Nothing } tasks <- todoist config (getTasks params) -- Complete a task todoist config (closeTask taskId) -- Update a task let update = runBuilder updateTaskBuilder (withContent "Buy 2% milk") updated <- todoist config (updateTask update taskId) ``` ### Working with Comments ```haskell -- Add a comment to a task let comment = runBuilder (newCommentBuilder "Don't forget organic!") (withTaskId "task-456") result <- todoist config (addComment comment) -- Get all comments for a project let params = CommentParam { project_id = Just "project-123" , task_id = Nothing , cursor = Nothing , limit = Nothing , public_key = Nothing } comments <- todoist config (getComments params) ``` ### Working with Sections ```haskell -- Create a section let section = runBuilder (newSection "In Progress" "project-123") mempty result <- todoist config (addSection section) -- Get sections for a project with builder pattern let params = runBuilder newSectionParam (withProjectId "project-123" <> withLimit 50) sections <- todoist config (getSections params) ``` ### Working with Labels ```haskell -- Create a label let label = runBuilder (createLabelBuilder "urgent") mempty result <- todoist config (addLabel label) -- Get all labels let params = runBuilder labelParamBuilder (withLimit 50) labels <- todoist config (getLabels params) ``` ## Working with Lenses TodoistSDK provides **getter-only lenses** for all domain types, enabling convenient field access and composition. All lenses are read-only; for mutations, use the builder pattern. ### Basic Usage ```haskell import Web.Todoist import Web.Todoist.Lens ((^.)) -- Import the getter operator -- Get all projects Right projects <- todoist config getAllProjects -- Extract fields using lenses let firstProject = head projects let projectName = firstProject ^. name let projectColor = firstProject ^. color let isShared = firstProject ^. isShared print projectName -- Prints: Name "My Project" ``` ### Composing Lenses Lenses can be composed to access nested fields: ```haskell -- Get tasks Right tasks <- todoist config (getTasks taskParamBuilder) -- Access nested fields let task = head tasks let maybeDueDate = task ^. taskDue >>= (^. dueDate) let taskContent = task ^. taskContent -- Check if task has a deadline case task ^. taskDeadline of Just deadline -> putStrLn $ "Deadline: " ++ show (deadline ^. deadlineDate) Nothing -> putStrLn "No deadline set" ``` ### Available Lenses All domain types provide lenses for their fields: **Project**: `projectId`, `name`, `description`, `order`, `color`, `isCollapsed`, `isShared`, `isFavorite`, `isArchived`, `canAssignTasks`, `viewStyle`, `createdAt`, `updatedAt` **Task**: `taskId`, `taskContent`, `taskDescription`, `taskProjectId`, `taskSectionId`, `taskParentId`, `taskLabels`, `taskPriority`, `taskDue`, `taskDeadline`, `taskDuration`, `taskIsCollapsed`, `taskOrder`, `taskAssigneeId`, `taskAssignerId`, `taskCompletedAt`, `taskCreatorId`, `taskCreatedAt`, `taskUpdatedAt` **Comment**: `commentId`, `commentContent`, `commentPosterId`, `commentPostedAt`, `commentTaskId`, `commentProjectId`, `commentAttachment` **Section**: `sectionId`, `sectionName`, `sectionProjectId`, `sectionIsCollapsed`, `sectionOrder` **Label**: `labelId`, `labelName`, `labelColor`, `labelOrder`, `labelIsFavorite` **Due**: `dueDate`, `dueString`, `dueLang`, `dueIsRecurring`, `dueTimezone` **Deadline**: `deadlineDate`, `deadlineLang` **Duration**: `amount`, `unit` ### Mutation with Builders Lenses are **read-only**. For field mutations, use the builder pattern: ```haskell -- Reading with lenses let oldName = project ^. name -- Updating with builders (not lenses) let update = runBuilder updateProjectBuilder (withName "New Name") Right updatedProject <- todoist config (updateProject update projectId) ``` ## Error Handling All operations return `Either TodoistError a`. The `TodoistError` type includes: - `BadRequest` - Invalid request parameters - `Unauthorized` - Invalid or missing API token - `Forbidden` - Insufficient permissions - `NotFound` - Resource doesn't exist - `HttpError String` - Other HTTP errors Example: ```haskell result <- todoist config (getProject projectId) case result of Left BadRequest -> putStrLn "Invalid project ID" Left Unauthorized -> putStrLn "Check your API token" Left NotFound -> putStrLn "Project not found" Left (HttpError msg) -> putStrLn $ "HTTP error: " ++ msg Right project -> print project ``` ## Documentation Full API documentation is available on [Hackage](https://hackage.haskell.org/package/todoist-sdk). For details on the Todoist REST API, see the [official documentation](https://developer.todoist.com/rest/v2/). ## License This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. ## Contributing Contributions are welcome! Please feel free to submit a Pull Request. ## Acknowledgments This library is a labor of love to the Haskell community and to the Todoist app. It is an unofficial SDK and is not affiliated with or endorsed by Doist.