packages feed

notion-client-0.6.0.0: src/Notion/V1/Blocks.hs

-- | @\/v1\/blocks@
module Notion.V1.Blocks
  ( -- * Main types
    BlockID,
    BlockObject (..),
    BlockUpdate (..),
    AppendBlockChildren (..),
    Position (..),

    -- * Re-exported from BlockContent
    module Notion.V1.BlockContent,

    -- * Servant
    API,
  )
where

import Control.Applicative ((<|>))
import Data.Aeson ((.:), (.:?), (.=))
import Data.Aeson qualified as Aeson
import Data.Aeson.Key qualified as Key
import Notion.Prelude
import Notion.V1.BlockContent
import Notion.V1.Common (BlockID, ObjectType (..), Parent, UUID (..))
import Notion.V1.ListOf (ListOf)
import Notion.V1.Users (UserReference)
import Prelude hiding (id)

-- | Notion block object
data BlockObject = BlockObject
  { id :: BlockID,
    parent :: Parent,
    createdTime :: POSIXTime,
    lastEditedTime :: POSIXTime,
    createdBy :: UserReference,
    lastEditedBy :: UserReference,
    hasChildren :: Bool,
    inTrash :: Bool,
    type_ :: Text,
    content :: BlockContent,
    object :: ObjectType
  }
  deriving stock (Generic, Show)

instance FromJSON BlockObject where
  parseJSON = \case
    Object o -> do
      id <- o .: "id"
      parent <- o .: "parent"
      createdTimeStr <- o .: "created_time"
      createdTime <- parseISO8601 createdTimeStr
      lastEditedTimeStr <- o .: "last_edited_time"
      lastEditedTime <- parseISO8601 lastEditedTimeStr
      createdBy <- o .: "created_by"
      lastEditedBy <- o .: "last_edited_by"
      hasChildren <- o .: "has_children"
      inTrash <- (o .: "in_trash") <|> (o .: "is_archived") <|> (o .: "archived") <|> pure False
      type_ <- o .: "type"
      contentVal <- o .: Key.fromText type_
      content <- parseBlockContent type_ contentVal
      object <- o .: "object"
      return BlockObject {..}
    _ -> fail "Expected object for BlockObject"

instance ToJSON BlockObject where
  toJSON BlockObject {..} =
    Aeson.object
      [ "id" .= id,
        "parent" .= parent,
        "created_time" .= posixToISO8601 createdTime,
        "last_edited_time" .= posixToISO8601 lastEditedTime,
        "created_by" .= createdBy,
        "last_edited_by" .= lastEditedBy,
        "has_children" .= hasChildren,
        "in_trash" .= inTrash,
        "type" .= type_,
        Key.fromText type_ .= snd (blockContentFields content),
        "object" .= object
      ]

-- | Block insertion position for the Append Block Children endpoint.
--
-- In API version 2026-03-11, the old @after@ parameter was replaced by a
-- @position@ object supporting three placement types.
data Position
  = -- | Insert after a specific block (replaces the old @after@ parameter)
    AfterBlock UUID
  | -- | Insert at the beginning of the parent
    Start
  | -- | Insert at the end of the parent (the default when @position@ is omitted)
    End
  deriving stock (Generic, Show)

instance ToJSON Position where
  toJSON (AfterBlock blockId) =
    Aeson.object
      [ "type" .= ("after_block" :: Text),
        "after_block" .= Aeson.object ["id" .= text blockId]
      ]
  toJSON Start =
    Aeson.object
      [ "type" .= ("start" :: Text),
        "start" .= Aeson.object []
      ]
  toJSON End =
    Aeson.object
      [ "type" .= ("end" :: Text),
        "end" .= Aeson.object []
      ]

-- | Append children to a block
data AppendBlockChildren = AppendBlockChildren
  { children :: Vector BlockContent,
    position :: Maybe Position
  }
  deriving stock (Generic, Show)

instance ToJSON AppendBlockChildren where
  toJSON AppendBlockChildren {..} =
    Aeson.object $
      ["children" .= children]
        <> maybe [] (\p -> ["position" .= p]) position

-- | Servant API
type API =
  "blocks"
    :> ( Capture "block_id" BlockID
           :> Get '[JSON] BlockObject
           :<|> Capture "block_id" BlockID
           :> ReqBody '[JSON] BlockUpdate
           :> Patch '[JSON] BlockObject
           :<|> Capture "block_id" BlockID
           :> "children"
           :> QueryParam "page_size" Natural
           :> QueryParam "start_cursor" Text
           :> Get '[JSON] (ListOf BlockObject)
           :<|> Capture "block_id" BlockID
           :> "children"
           :> ReqBody '[JSON] AppendBlockChildren
           :> Patch '[JSON] (ListOf BlockObject)
           :<|> Capture "block_id" BlockID
           :> Delete '[JSON] BlockObject
       )