packages feed

imm-2.0.0.0: src/main/Database/Record.hs

{-# LANGUAGE DeriveGeneric        #-}
{-# LANGUAGE FlexibleContexts     #-}
{-# LANGUAGE FlexibleInstances    #-}
{-# LANGUAGE OverloadedStrings    #-}
{-# LANGUAGE RecordWildCards      #-}
{-# LANGUAGE StandaloneDeriving   #-}
{-# LANGUAGE TypeApplications     #-}
{-# LANGUAGE TypeFamilies         #-}
{-# LANGUAGE UndecidableInstances #-}
module Database.Record
  ( FeedStatus(..)
  , markFeedAsUnprocessed
  , touchFeed
  , FeedItemStatus(..)
  , markItemAsProcessed
  , markItemAsUnprocessed
  , FeedRecord(..)
  , mkFeedRecord
  , FeedItemRecord(..)
  , prettyItemRecord
  , mkFeedItemRecord
  , Inserted
  , NotInserted
  ) where

import           Control.Monad.Time
import           Data.Aeson
import           Data.Time
import           Imm.Feed
import           Imm.Pretty


-- | Stateful information on a feed
data FeedStatus = FeedStatus
  { _feedTags       :: Set Text
  , _feedLastUpdate :: Maybe UTCTime
  } deriving(Eq, Generic, Ord, Read, Show, Typeable)

feedStatusOptions :: Options
feedStatusOptions = defaultOptions
  { fieldLabelModifier = camelTo2 '_' . drop (length @[] "_feed")
  , omitNothingFields = True
  }

instance ToJSON FeedStatus where
  toJSON     = genericToJSON feedStatusOptions
  toEncoding = genericToEncoding feedStatusOptions

instance FromJSON FeedStatus where
  parseJSON = genericParseJSON feedStatusOptions

instance Pretty FeedStatus where
  pretty FeedStatus{..} = tags <++> "Last update:" <+> lastUpdate
    where tags = sep $ map ((<>) "#" . pretty) $ toList _feedTags
          lastUpdate = maybe "never" prettyTime _feedLastUpdate

markFeedAsUnprocessed :: FeedStatus -> FeedStatus
markFeedAsUnprocessed status = status
  { _feedLastUpdate = Nothing
  }

touchFeed :: FeedStatus -> IO FeedStatus
touchFeed status = do
  utcTime <- currentTime
  return $ status { _feedLastUpdate = Just utcTime }


-- | Stateful information on a feed item
newtype FeedItemStatus = FeedItemStatus
  { _isProcessed :: Bool
  } deriving(Eq, Generic, Ord, Read, Show, Typeable)

instance Pretty FeedItemStatus where
  pretty FeedItemStatus{..} = pretty _isProcessed

feedItemStatusOptions :: Options
feedItemStatusOptions = defaultOptions
  { fieldLabelModifier = camelTo2 '_'
  , omitNothingFields = True
  }

instance ToJSON FeedItemStatus where
  toJSON     = genericToJSON feedItemStatusOptions
  toEncoding = genericToEncoding feedItemStatusOptions

instance FromJSON FeedItemStatus where
  parseJSON = genericParseJSON feedItemStatusOptions

markItemAsProcessed :: FeedItemStatus -> FeedItemStatus
markItemAsProcessed status = status { _isProcessed = True }

markItemAsUnprocessed :: FeedItemStatus -> FeedItemStatus
markItemAsUnprocessed status = status { _isProcessed = False }


-- | Consistent record of information for a single feed
data FeedRecord status = FeedRecord
  { _feedKey        :: StatusUID status
  , _feedLocation   :: FeedLocation
  , _feedDefinition :: FeedDefinition
  , _feedStatus     :: FeedStatus
  }

deriving instance (Eq (StatusUID s)) => Eq (FeedRecord s)
deriving instance (Ord (StatusUID s)) => Ord (FeedRecord s)
deriving instance (Show (StatusUID s)) => Show (FeedRecord s)

instance Pretty (PrettyKey (FeedRecord Inserted)) where
  pretty (PrettyKey record) = pretty $ _feedKey record

instance Pretty (PrettyKey (FeedRecord NotInserted)) where
  pretty (PrettyKey record) = pretty $ _feedLocation record

mkFeedRecord :: FeedLocation -> FeedDefinition -> FeedStatus -> FeedRecord NotInserted
mkFeedRecord = FeedRecord ()


-- | Consistent record of information for a single feed item
data FeedItemRecord status = FeedItemRecord
  { _itemKey        :: StatusUID status
  , _itemFeedKey    :: UID
  , _itemDefinition :: FeedItem
  , _itemStatus     :: FeedItemStatus
  }

deriving instance (Eq (StatusUID s)) => Eq (FeedItemRecord s)
deriving instance (Ord (StatusUID s)) => Ord (FeedItemRecord s)
deriving instance (Show (StatusUID s)) => Show (FeedItemRecord s)

instance Pretty (PrettyKey (FeedItemRecord Inserted)) where
  pretty (PrettyKey record) = pretty $ _itemKey record

instance Pretty (PrettyName (FeedItemRecord a)) where
  pretty (PrettyName record) = pretty $ _itemTitle $ _itemDefinition record

prettyItemRecord :: Pretty (StatusUID s) => FeedItemRecord s -> Doc AnsiStyle
prettyItemRecord FeedItemRecord{..} = pretty _itemKey <+> magenta (pretty _itemFeedKey)
  <++> pretty _itemDefinition
  <++> pretty _itemStatus

mkFeedItemRecord :: UID -> FeedItem -> FeedItemStatus -> FeedItemRecord NotInserted
mkFeedItemRecord = FeedItemRecord ()


data Inserted
data NotInserted

type family StatusUID s where
  StatusUID Inserted = UID
  StatusUID NotInserted = ()