packages feed

todoist-sdk-0.1.2.1: src/Web/Todoist/Domain/Label.hs

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE RecordWildCards #-}

{- |
Module      : Web.Todoist.Domain.Label
Description : Label 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 labels.
Labels are tags that can be applied to tasks for categorization and filtering.
Supports both personal and shared labels.

= Usage Example

@
import Web.Todoist.Domain.Label
import Web.Todoist.Runner
import Web.Todoist.Util.Builder

main :: IO ()
main = do
    let config = newTodoistConfig "your-api-token"

    -- Create a label
    let newLbl = runBuilder (createLabelBuilder "urgent") mempty
    label <- todoist config (addLabel newLbl)

    -- Get all labels with builder pattern
    let params = runBuilder labelParamBuilder (withLimit 50)
    labels <- todoist config (getLabels params)
@

For more details, see: <https://developer.todoist.com/rest/v2/#labels>
-}
module Web.Todoist.Domain.Label
    ( -- * Types
      Label (..)
    , LabelId (..)
    , LabelCreate
    , LabelUpdate
    , LabelParam (..)
    , SharedLabelParam (..)
    , SharedLabelRemove
    , SharedLabelRename

      -- * Type Class
    , TodoistLabelM (..)

      -- * Constructors
    , createLabelBuilder
    , updateLabelBuilder
    , labelParamBuilder
    , sharedLabelParamBuilder
    , mkSharedLabelRename
    , mkSharedLabelRemove

      -- * Lenses
    , labelId
    , labelName
    , labelColor
    , labelOrder
    , labelIsFavorite
    ) where

import Control.Applicative ((<$>))
import Control.Monad (Monad)
import Data.Aeson (FromJSON (..), ToJSON (..), Value, genericToJSON)
import qualified Data.Aeson as A
import Data.Aeson.Types (Parser)
import Data.Bool (Bool (..))
import Data.Eq (Eq)
import Data.Function (($))
import Data.Int (Int)
import qualified Data.List as L
import Data.Maybe (Maybe (..), maybe)
import Data.Monoid ((<>))
import Data.Text (Text)
import qualified Data.Text as T
import GHC.Generics (Generic)
import Lens.Micro (to)
import Text.Show (Show, show)
import Web.Todoist.Domain.Types (Color (..), IsFavorite (..), Name (..), Order (..))
import Web.Todoist.Lens (Getter)
import Web.Todoist.Internal.Types (Params)
import Web.Todoist.Util.Builder
    ( HasColor (..)
    , HasCursor (..)
    , HasIsFavorite (..)
    , HasLimit (..)
    , HasName (..)
    , HasOmitPersonal (..)
    , HasOrder (..)
    , Initial
    , seed
    )
import Web.Todoist.Util.QueryParam (QueryParam (..))

-- | Unique identifier for a Label
newtype LabelId = LabelId {getLabelId :: Text}
    deriving (Eq, Show, Generic)

instance ToJSON LabelId where
    toJSON :: LabelId -> Value
    toJSON (LabelId txt) = A.object ["id" A..= txt]

instance FromJSON LabelId where
    parseJSON :: Value -> Parser LabelId
    parseJSON = A.withObject "LabelId" $ \obj ->
        LabelId <$> obj A..: "id"

data Label = Label
    { _id :: LabelId
    , _name :: Name
    , _color :: Color
    , _order :: Maybe Order
    , _is_favorite :: IsFavorite
    }
    deriving (Show, Generic)

-- | Lenses for Label
labelId :: Getter Label LabelId
labelId = to (\(Label {_id = x}) -> x)
{-# INLINE labelId #-}

labelName :: Getter Label Name
labelName = to (\(Label {_name = x}) -> x)
{-# INLINE labelName #-}

labelColor :: Getter Label Color
labelColor = to (\(Label {_color = x}) -> x)
{-# INLINE labelColor #-}

labelOrder :: Getter Label (Maybe Order)
labelOrder = to (\(Label {_order = x}) -> x)
{-# INLINE labelOrder #-}

labelIsFavorite :: Getter Label IsFavorite
labelIsFavorite = to (\(Label {_is_favorite = x}) -> x)
{-# INLINE labelIsFavorite #-}

-- | Request body for creating a new Label
data LabelCreate = LabelCreate
    { _name :: Name
    , _order :: Maybe Order
    , _color :: Maybe Color -- Can be string or integer per API, we'll use Text
    , _is_favorite :: Maybe IsFavorite
    }
    deriving (Show, Generic)

instance ToJSON LabelCreate where
    toJSON :: LabelCreate -> A.Value
    toJSON = genericToJSON A.defaultOptions {A.fieldLabelModifier = L.drop 1, A.omitNothingFields = True}

-- | Request body for updating a Label (partial updates)
data LabelUpdate = LabelUpdate
    { _name :: Maybe Name
    , _order :: Maybe Order
    , _color :: Maybe Color
    , _is_favorite :: Maybe IsFavorite
    }
    deriving (Show, Generic)

instance ToJSON LabelUpdate where
    toJSON :: LabelUpdate -> A.Value
    toJSON = genericToJSON A.defaultOptions {A.fieldLabelModifier = L.drop 1, A.omitNothingFields = True}

-- | Query parameters for filtering and paginating labels
data LabelParam = LabelParam
    { cursor :: Maybe Text
    , limit :: Maybe Int
    }
    deriving (Show, Generic)

instance QueryParam LabelParam where
    toQueryParam :: LabelParam -> Params
    toQueryParam LabelParam {..} =
        maybe [] (\c -> [("cursor", c)]) cursor
            <> maybe [] (\l -> [("limit", T.pack $ show l)]) limit

-- | Query parameters for shared labels
data SharedLabelParam = SharedLabelParam
    { omit_personal :: Maybe Bool
    , cursor :: Maybe Text
    , limit :: Maybe Int
    }
    deriving (Show, Generic)

instance QueryParam SharedLabelParam where
    toQueryParam :: SharedLabelParam -> Params
    toQueryParam SharedLabelParam {..} =
        let omitParam = case omit_personal of
                Just True -> [("omit_personal", "true")]
                Just False -> [("omit_personal", "false")]
                Nothing -> []
         in omitParam
                <> maybe [] (\c -> [("cursor", c)]) cursor
                <> maybe [] (\l -> [("limit", T.pack $ show l)]) limit

-- | Request body for removing a shared label
newtype SharedLabelRemove = SharedLabelRemove
    { _name :: Name
    }
    deriving (Show, Generic)

instance ToJSON SharedLabelRemove where
    toJSON :: SharedLabelRemove -> A.Value
    toJSON = genericToJSON A.defaultOptions {A.fieldLabelModifier = L.drop 1}

-- | Request body for renaming a shared label
data SharedLabelRename = SharedLabelRename
    { _name :: Name
    , _new_name :: Name
    }
    deriving (Show, Generic)

instance ToJSON SharedLabelRename where
    toJSON :: SharedLabelRename -> A.Value
    toJSON = genericToJSON A.defaultOptions {A.fieldLabelModifier = L.drop 1}

-- | Type class defining Label operations
class (Monad m) => TodoistLabelM m where
    -- | Get all labels (automatically fetches all pages)
    getLabels :: LabelParam -> m [Label]

    -- | Get a single label by ID
    getLabel :: LabelId -> m Label

    -- | Create a new label
    addLabel :: LabelCreate -> m LabelId

    -- | Update a label
    updateLabel :: LabelUpdate -> LabelId -> m Label

    -- | Delete a label (removes from all tasks)
    deleteLabel :: LabelId -> m ()

    -- | Get labels with manual pagination control
    getLabelsPaginated :: LabelParam -> m ([Label], Maybe Text)

    -- | Get shared labels (automatically fetches all pages)
    getSharedLabels :: SharedLabelParam -> m [Text]

    -- | Get shared labels with manual pagination control
    getSharedLabelsPaginated :: SharedLabelParam -> m ([Text], Maybe Text)

    -- | Remove a shared label from all active tasks
    removeSharedLabels :: SharedLabelRemove -> m ()

    -- | Rename a shared label on all active tasks
    renameSharedLabels :: SharedLabelRename -> m ()

-- | Smart constructor for creating a new label
createLabelBuilder :: Text -> Initial LabelCreate
createLabelBuilder name =
    seed
        LabelCreate
            { _name = Name name
            , _order = Nothing
            , _color = Nothing
            , _is_favorite = Nothing
            }

-- | Empty label update for builder pattern
updateLabelBuilder :: Initial LabelUpdate
updateLabelBuilder =
    seed
        LabelUpdate
            { _name = Nothing
            , _order = Nothing
            , _color = Nothing
            , _is_favorite = Nothing
            }

-- | Create new LabelParam for use with builder pattern
labelParamBuilder :: Initial LabelParam
labelParamBuilder = seed $ LabelParam {cursor = Nothing, limit = Nothing}

-- | Create new SharedLabelRemove for use with builder pattern
mkSharedLabelRename :: Text -> Text -> SharedLabelRename
mkSharedLabelRename name newName= SharedLabelRename {_name = Name name, _new_name = Name newName}

-- | Create new SharedLabelRemove
mkSharedLabelRemove :: Text -> SharedLabelRemove
mkSharedLabelRemove name = SharedLabelRemove {_name = Name name}

-- | Create new SharedLabelParam for use with builder pattern
sharedLabelParamBuilder :: Initial SharedLabelParam
sharedLabelParamBuilder = seed $ SharedLabelParam {omit_personal = Nothing, cursor = Nothing, limit = Nothing}

-- Builder instances for ergonomic construction
instance HasName LabelCreate where
    hasName :: Text -> LabelCreate -> LabelCreate
    hasName name LabelCreate {..} = LabelCreate {_name = Name name, ..}

instance HasOrder LabelCreate where
    hasOrder :: Int -> LabelCreate -> LabelCreate
    hasOrder order LabelCreate {..} = LabelCreate {_order = Just (Order order), ..}

instance HasColor LabelCreate where
    hasColor :: Text -> LabelCreate -> LabelCreate
    hasColor color LabelCreate {..} = LabelCreate {_color = Just (Color color), ..}

instance HasIsFavorite LabelCreate where
    hasIsFavorite :: Bool -> LabelCreate -> LabelCreate
    hasIsFavorite fav LabelCreate {..} = LabelCreate {_is_favorite = Just (IsFavorite fav), ..}

instance HasName LabelUpdate where
    hasName :: Text -> LabelUpdate -> LabelUpdate
    hasName name LabelUpdate {..} = LabelUpdate {_name = Just (Name name), ..}

instance HasOrder LabelUpdate where
    hasOrder :: Int -> LabelUpdate -> LabelUpdate
    hasOrder order LabelUpdate {..} = LabelUpdate {_order = Just (Order order), ..}

instance HasColor LabelUpdate where
    hasColor :: Text -> LabelUpdate -> LabelUpdate
    hasColor color LabelUpdate {..} = LabelUpdate {_color = Just (Color color), ..}

instance HasIsFavorite LabelUpdate where
    hasIsFavorite :: Bool -> LabelUpdate -> LabelUpdate
    hasIsFavorite fav LabelUpdate {..} = LabelUpdate {_is_favorite = Just (IsFavorite fav), ..}

-- HasX instances for LabelParam
instance HasCursor LabelParam where
    hasCursor :: Text -> LabelParam -> LabelParam
    hasCursor c LabelParam {..} = LabelParam {cursor = Just c, ..}

instance HasLimit LabelParam where
    hasLimit :: Int -> LabelParam -> LabelParam
    hasLimit l LabelParam {..} = LabelParam {limit = Just l, ..}

-- HasX instances for SharedLabelParam
instance HasOmitPersonal SharedLabelParam where
    hasOmitPersonal :: Bool -> SharedLabelParam -> SharedLabelParam
    hasOmitPersonal omit SharedLabelParam {..} = SharedLabelParam {omit_personal = Just omit, ..}

instance HasCursor SharedLabelParam where
    hasCursor :: Text -> SharedLabelParam -> SharedLabelParam
    hasCursor c SharedLabelParam {..} = SharedLabelParam {cursor = Just c, ..}

instance HasLimit SharedLabelParam where
    hasLimit :: Int -> SharedLabelParam -> SharedLabelParam
    hasLimit l SharedLabelParam {..} = SharedLabelParam {limit = Just l, ..}