todoist-sdk-0.1.2.1: src/Web/Todoist/Domain/Section.hs
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE RecordWildCards #-}
{- |
Module : Web.Todoist.Domain.Section
Description : Section API types and operations for Todoist REST API
Copyright : (c) 2025 Sam S. Almahri
License : MIT
Maintainer : sam.salmahri@gmail.com
This module provides types and operations for working with Todoist sections.
Sections are used to organize tasks within a project into logical groups.
= Usage Example
@
import Web.Todoist.Domain.Section
import Web.Todoist.Runner
import Web.Todoist.Util.Builder
main :: IO ()
main = do
let config = newTodoistConfig "your-api-token"
-- Create a section in a project
let newSec = runBuilder (newSectionBuilder "To Do" "project-id-123") mempty
section <- todoist config (addSection newSec)
-- Get all sections in a project with builder pattern
let params = runBuilder sectionParamBuilder (withProjectId "project-id-123" <> withLimit 50)
sections <- todoist config (getSections params)
@
For more details, see: <https://developer.todoist.com/rest/v2/#sections>
-}
module Web.Todoist.Domain.Section
( -- * Types
Section (..)
, SectionId (..)
, SectionCreate
, SectionUpdate
, SectionParam (..)
-- * Type Class
, TodoistSectionM (..)
-- * Constructors
, newSectionBuilder
, updateSectionBuilder
, sectionParamBuilder
-- * Lenses
, sectionId
, sectionName
, sectionProjectId
, sectionIsCollapsed
, sectionOrder
) where
import Data.Aeson
( FromJSON (..)
, ToJSON (..)
, Value
, genericParseJSON
, genericToJSON
)
import Data.Aeson.Types (Parser)
import Data.Bool (Bool (True))
import Data.Text (Text)
import qualified Data.Text as T
import GHC.Generics (Generic)
import Lens.Micro (to)
import Web.Todoist.Lens (Getter)
import qualified Data.Aeson as A
import qualified Data.List as L
import Control.Monad (Monad)
import Data.Eq (Eq)
import Data.Function (($))
import Data.Int (Int)
import Data.Maybe (Maybe (..))
import Data.Monoid ((<>))
import Text.Show (Show (..))
import Web.Todoist.Domain.Types (IsCollapsed, Name (..), Order (..), ProjectId (..), getProjectId)
import Web.Todoist.Util.Builder
( HasCursor (..)
, HasLimit (..)
, HasName (..)
, HasOrder (..)
, HasProjectId (..)
, Initial
, seed
)
import Web.Todoist.Util.QueryParam (QueryParam (..))
-- | Unique identifier for a Section
newtype SectionId = SectionId {_id :: Text}
deriving (Eq, Show, Generic)
instance FromJSON SectionId where
parseJSON :: Value -> Parser SectionId
parseJSON = genericParseJSON A.defaultOptions {A.fieldLabelModifier = L.drop 1}
instance ToJSON SectionId where
toJSON :: SectionId -> Value
toJSON (SectionId txt) = toJSON txt
{- | Simplified domain representation of a Section (5 essential fields)
Note: API returns 11 fields, but we only expose the essential ones
-}
data Section = Section
{ _id :: SectionId
, _name :: Name
, _project_id :: ProjectId
, _is_collapsed :: IsCollapsed
, _order :: Order -- Maps from section_order in API
}
deriving (Show, Generic)
-- | Lenses for Section
sectionId :: Getter Section SectionId
sectionId = to (\(Section {_id = x}) -> x)
{-# INLINE sectionId #-}
sectionName :: Getter Section Name
sectionName = to (\(Section {_name = x}) -> x)
{-# INLINE sectionName #-}
sectionProjectId :: Getter Section ProjectId
sectionProjectId = to (\(Section {_project_id = x}) -> x)
{-# INLINE sectionProjectId #-}
sectionIsCollapsed :: Getter Section IsCollapsed
sectionIsCollapsed = to (\(Section {_is_collapsed = x}) -> x)
{-# INLINE sectionIsCollapsed #-}
sectionOrder :: Getter Section Order
sectionOrder = to (\(Section {_order = x}) -> x)
{-# INLINE sectionOrder #-}
-- | Request body for creating a new Section
data SectionCreate = SectionCreate
{ _name :: Name
, _project_id :: ProjectId
, _order :: Maybe Order
}
deriving (Show, Generic)
instance ToJSON SectionCreate where
toJSON :: SectionCreate -> Value
toJSON = genericToJSON A.defaultOptions {A.fieldLabelModifier = L.drop 1}
{- | Request body for updating a Section (partial updates)
Uses omitNothingFields to only send fields that are set
-}
newtype SectionUpdate = SectionUpdate
{ _name :: Maybe Name
}
deriving (Show, Generic)
instance ToJSON SectionUpdate where
toJSON :: SectionUpdate -> Value
toJSON = genericToJSON A.defaultOptions {A.fieldLabelModifier = L.drop 1, A.omitNothingFields = True}
-- | Query parameters for filtering and paginating sections
data SectionParam = SectionParam
{ project_id :: Maybe ProjectId
, cursor :: Maybe Text
, limit :: Maybe Int
}
deriving (Show, Generic)
instance QueryParam SectionParam where
toQueryParam :: SectionParam -> [(Text, Text)]
toQueryParam SectionParam {..} =
let projectIdParam = case project_id of
Just pid -> [("project_id", getProjectId pid)]
Nothing -> []
cursorParam = case cursor of
Just c -> [("cursor", c)]
Nothing -> []
limitParam = case limit of
Just l -> [("limit", T.pack $ show l)]
Nothing -> []
in projectIdParam <> cursorParam <> limitParam
-- | Type class defining Section operations
class (Monad m) => TodoistSectionM m where
-- | Get all sections (automatically fetches all pages)
getSections :: SectionParam -> m [Section]
-- | Get a single section by ID
getSection :: SectionId -> m Section
-- | Create a new section
addSection :: SectionCreate -> m SectionId
-- | Update a section
updateSection :: SectionUpdate -> SectionId -> m Section
-- | Delete a section (and all its tasks)
deleteSection :: SectionId -> m ()
{- | Get sections with manual pagination control
Returns a tuple of (results, next_cursor) for the requested page
-}
getSectionsPaginated :: SectionParam -> m ([Section], Maybe Text)
-- | Smart constructor for creating a new section
newSectionBuilder :: Text -> Text -> Initial SectionCreate
newSectionBuilder name projectId =
seed
SectionCreate
{ _name = Name name
, _project_id = ProjectId projectId
, _order = Nothing
}
-- | Empty section update for builder pattern
updateSectionBuilder :: Initial SectionUpdate
updateSectionBuilder =
seed
SectionUpdate
{ _name = Nothing
}
-- | Create new SectionParam for use with builder pattern
sectionParamBuilder :: Initial SectionParam
sectionParamBuilder =
seed
SectionParam
{ project_id = Nothing
, cursor = Nothing
, limit = Nothing
}
-- Builder instances for ergonomic construction
instance HasName SectionCreate where
hasName :: Text -> SectionCreate -> SectionCreate
hasName name SectionCreate {..} = SectionCreate {_name = Name name, ..}
instance HasOrder SectionCreate where
hasOrder :: Int -> SectionCreate -> SectionCreate
hasOrder order SectionCreate {..} = SectionCreate {_order = Just (Order order), ..}
instance HasName SectionUpdate where
hasName :: Text -> SectionUpdate -> SectionUpdate
hasName name SectionUpdate {} = SectionUpdate {_name = Just (Name name), ..}
-- HasX instances for SectionParam
instance HasProjectId SectionParam where
hasProjectId :: Text -> SectionParam -> SectionParam
hasProjectId pid SectionParam {..} = SectionParam {project_id = Just (ProjectId pid), ..}
instance HasCursor SectionParam where
hasCursor :: Text -> SectionParam -> SectionParam
hasCursor c SectionParam {..} = SectionParam {cursor = Just c, ..}
instance HasLimit SectionParam where
hasLimit :: Int -> SectionParam -> SectionParam
hasLimit l SectionParam {..} = SectionParam {limit = Just l, ..}