packages feed

hackernews 0.5.0.1 → 1.0.0.0

raw patch · 23 files changed

+972/−996 lines, 23 filesdep +QuickCheckdep +basic-sopdep +generics-sopdep −HsOpenSSLdep −bytestringdep −eitherdep ~aesondep ~attoparsecdep ~basenew-component:exe:examplenew-component:exe:ghcjs-testsPVP ok

version bump matches the API change (PVP)

Dependencies added: QuickCheck, basic-sop, generics-sop, hspec-core, http-client, http-client-tls, http-types, quickcheck-instances, servant, servant-client, string-conversions

Dependencies removed: HsOpenSSL, bytestring, either, http-streams, io-streams, time

Dependency ranges changed: aeson, attoparsec, base, hackernews, hspec, text, transformers

API changes (from Hackage documentation)

- Web.HackerNews: CommentId :: Int -> CommentId
- Web.HackerNews: ConnectionError :: HackerNewsError
- Web.HackerNews: ItemComment :: Comment -> Item
- Web.HackerNews: ItemJob :: Job -> Item
- Web.HackerNews: ItemPoll :: Poll -> Item
- Web.HackerNews: ItemPollOpt :: PollOpt -> Item
- Web.HackerNews: ItemStory :: Story -> Item
- Web.HackerNews: JobId :: Int -> JobId
- Web.HackerNews: ParseError :: HackerNewsError
- Web.HackerNews: PollId :: Int -> PollId
- Web.HackerNews: PollOptId :: Int -> PollOptId
- Web.HackerNews: RequestError :: HackerNewsError
- Web.HackerNews: StoryId :: Int -> StoryId
- Web.HackerNews: Update :: [Int] -> [Text] -> Bool -> Bool -> Update
- Web.HackerNews: commentBy :: Comment -> Text
- Web.HackerNews: commentDead :: Comment -> Bool
- Web.HackerNews: commentDeleted :: Comment -> Bool
- Web.HackerNews: commentId :: Comment -> CommentId
- Web.HackerNews: commentKids :: Comment -> Maybe [Int]
- Web.HackerNews: commentParent :: Comment -> Int
- Web.HackerNews: commentText :: Comment -> Text
- Web.HackerNews: commentTime :: Comment -> UTCTime
- Web.HackerNews: commentType :: Comment -> Text
- Web.HackerNews: data Comment
- Web.HackerNews: data Job
- Web.HackerNews: data Poll
- Web.HackerNews: data PollOpt
- Web.HackerNews: data Story
- Web.HackerNews: data Update
- Web.HackerNews: getComment :: CommentId -> HackerNews Comment
- Web.HackerNews: getJob :: JobId -> HackerNews Job
- Web.HackerNews: getPoll :: PollId -> HackerNews Poll
- Web.HackerNews: getPollOpt :: PollOptId -> HackerNews PollOpt
- Web.HackerNews: getStory :: StoryId -> HackerNews Story
- Web.HackerNews: hackerNews :: FromJSON a => HackerNews a -> IO (Either HackerNewsError a)
- Web.HackerNews: jobBy :: Job -> Text
- Web.HackerNews: jobDead :: Job -> Bool
- Web.HackerNews: jobDeleted :: Job -> Bool
- Web.HackerNews: jobId :: Job -> JobId
- Web.HackerNews: jobScore :: Job -> Int
- Web.HackerNews: jobText :: Job -> Text
- Web.HackerNews: jobTime :: Job -> UTCTime
- Web.HackerNews: jobTitle :: Job -> Text
- Web.HackerNews: jobType :: Job -> Text
- Web.HackerNews: jobUrl :: Job -> Text
- Web.HackerNews: newtype CommentId
- Web.HackerNews: newtype JobId
- Web.HackerNews: newtype PollId
- Web.HackerNews: newtype PollOptId
- Web.HackerNews: newtype StoryId
- Web.HackerNews: pollBy :: Poll -> Text
- Web.HackerNews: pollDead :: Poll -> Bool
- Web.HackerNews: pollDeleted :: Poll -> Bool
- Web.HackerNews: pollId :: Poll -> PollId
- Web.HackerNews: pollKids :: Poll -> [Int]
- Web.HackerNews: pollOptBy :: PollOpt -> Text
- Web.HackerNews: pollOptDead :: PollOpt -> Bool
- Web.HackerNews: pollOptDeleted :: PollOpt -> Bool
- Web.HackerNews: pollOptId :: PollOpt -> PollOptId
- Web.HackerNews: pollOptParent :: PollOpt -> Int
- Web.HackerNews: pollOptScore :: PollOpt -> Int
- Web.HackerNews: pollOptText :: PollOpt -> Text
- Web.HackerNews: pollOptTime :: PollOpt -> UTCTime
- Web.HackerNews: pollOptType :: PollOpt -> Text
- Web.HackerNews: pollParts :: Poll -> [Int]
- Web.HackerNews: pollScore :: Poll -> Int
- Web.HackerNews: pollText :: Poll -> Text
- Web.HackerNews: pollTime :: Poll -> UTCTime
- Web.HackerNews: pollTitle :: Poll -> Text
- Web.HackerNews: pollType :: Poll -> Text
- Web.HackerNews: storyBy :: Story -> Text
- Web.HackerNews: storyDead :: Story -> Bool
- Web.HackerNews: storyDeleted :: Story -> Bool
- Web.HackerNews: storyId :: Story -> StoryId
- Web.HackerNews: storyKids :: Story -> [Int]
- Web.HackerNews: storyScore :: Story -> Int
- Web.HackerNews: storyTime :: Story -> UTCTime
- Web.HackerNews: storyTitle :: Story -> Text
- Web.HackerNews: storyType :: Story -> Text
- Web.HackerNews: storyUrl :: Story -> Text
- Web.HackerNews: type HackerNews a = EitherT HackerNewsError (ReaderT Connection IO) a
- Web.HackerNews: updateDead :: Update -> Bool
- Web.HackerNews: updateDeleted :: Update -> Bool
- Web.HackerNews: updateItems :: Update -> [Int]
- Web.HackerNews: updateProfiles :: Update -> [Text]
- Web.HackerNews: userAbout :: User -> Maybe Text
- Web.HackerNews: userCreated :: User -> UTCTime
- Web.HackerNews: userDead :: User -> Bool
- Web.HackerNews: userDelay :: User -> Int
- Web.HackerNews: userDeleted :: User -> Bool
- Web.HackerNews: userId :: User -> UserId
- Web.HackerNews: userKarma :: User -> Int
- Web.HackerNews: userSubmitted :: User -> [Int]
+ Web.HackerNews: About :: Text -> About
+ Web.HackerNews: AskStories :: [Int] -> AskStories
+ Web.HackerNews: BestStories :: [Int] -> BestStories
+ Web.HackerNews: Created :: Int -> Created
+ Web.HackerNews: Dead :: Bool -> Dead
+ Web.HackerNews: DecodeFailureError :: Text -> Text -> HackerNewsError
+ Web.HackerNews: Delay :: Int -> Delay
+ Web.HackerNews: Deleted :: Int -> Deleted
+ Web.HackerNews: Descendants :: Int -> Descendants
+ Web.HackerNews: FailureResponseError :: Int -> Text -> Text -> HackerNewsError
+ Web.HackerNews: HNConnectionError :: Text -> HackerNewsError
+ Web.HackerNews: InvalidContentTypeHeaderError :: Text -> Text -> HackerNewsError
+ Web.HackerNews: Item :: Maybe ItemId -> Maybe Deleted -> ItemType -> Maybe UserName -> Maybe Time -> Maybe ItemText -> Maybe Dead -> Maybe Parent -> Maybe Kids -> Maybe URL -> Maybe Score -> Maybe Title -> Maybe Parts -> Maybe Descendants -> Item
+ Web.HackerNews: ItemText :: Text -> ItemText
+ Web.HackerNews: JobStories :: [Int] -> JobStories
+ Web.HackerNews: Karma :: Int -> Karma
+ Web.HackerNews: Kids :: [Int] -> Kids
+ Web.HackerNews: NewStories :: [Int] -> NewStories
+ Web.HackerNews: Parent :: Int -> Parent
+ Web.HackerNews: Parts :: [Int] -> Parts
+ Web.HackerNews: Score :: Int -> Score
+ Web.HackerNews: ShowStories :: [Int] -> ShowStories
+ Web.HackerNews: Submitted :: [Int] -> Submitted
+ Web.HackerNews: Time :: Integer -> Time
+ Web.HackerNews: Title :: Text -> Title
+ Web.HackerNews: URL :: Text -> URL
+ Web.HackerNews: UnsupportedContentTypeError :: Text -> HackerNewsError
+ Web.HackerNews: Updates :: [Int] -> [UserName] -> Updates
+ Web.HackerNews: UserName :: Text -> UserName
+ Web.HackerNews: [itemBy] :: Item -> Maybe UserName
+ Web.HackerNews: [itemDead] :: Item -> Maybe Dead
+ Web.HackerNews: [itemDeleted] :: Item -> Maybe Deleted
+ Web.HackerNews: [itemDescendants] :: Item -> Maybe Descendants
+ Web.HackerNews: [itemId] :: Item -> Maybe ItemId
+ Web.HackerNews: [itemKids] :: Item -> Maybe Kids
+ Web.HackerNews: [itemParent] :: Item -> Maybe Parent
+ Web.HackerNews: [itemParts] :: Item -> Maybe Parts
+ Web.HackerNews: [itemScore] :: Item -> Maybe Score
+ Web.HackerNews: [itemText] :: Item -> Maybe ItemText
+ Web.HackerNews: [itemTime] :: Item -> Maybe Time
+ Web.HackerNews: [itemTitle] :: Item -> Maybe Title
+ Web.HackerNews: [itemType] :: Item -> ItemType
+ Web.HackerNews: [itemURL] :: Item -> Maybe URL
+ Web.HackerNews: [items] :: Updates -> [Int]
+ Web.HackerNews: [profiles] :: Updates -> [UserName]
+ Web.HackerNews: [userAbout] :: User -> Maybe About
+ Web.HackerNews: [userCreated] :: User -> Created
+ Web.HackerNews: [userDelay] :: User -> Maybe Delay
+ Web.HackerNews: [userId] :: User -> UserId
+ Web.HackerNews: [userKarma] :: User -> Karma
+ Web.HackerNews: [userSubmitted] :: User -> Maybe Submitted
+ Web.HackerNews: data HackerCapture (a :: *)
+ Web.HackerNews: data ItemType
+ Web.HackerNews: data Updates
+ Web.HackerNews: getAskStories :: Manager -> IO (Either HackerNewsError AskStories)
+ Web.HackerNews: getBestStories :: Manager -> IO (Either HackerNewsError BestStories)
+ Web.HackerNews: getJobStories :: Manager -> IO (Either HackerNewsError JobStories)
+ Web.HackerNews: getNewStories :: Manager -> IO (Either HackerNewsError NewStories)
+ Web.HackerNews: getShowStories :: Manager -> IO (Either HackerNewsError ShowStories)
+ Web.HackerNews: instance (Web.HttpApiData.Internal.ToHttpApiData a, Servant.Client.HasClient api) => Servant.Client.HasClient (Web.HackerNews.HackerCapture a Servant.API.Sub.:> api)
+ Web.HackerNews: newtype About
+ Web.HackerNews: newtype AskStories
+ Web.HackerNews: newtype BestStories
+ Web.HackerNews: newtype Created
+ Web.HackerNews: newtype Dead
+ Web.HackerNews: newtype Delay
+ Web.HackerNews: newtype Deleted
+ Web.HackerNews: newtype Descendants
+ Web.HackerNews: newtype ItemText
+ Web.HackerNews: newtype JobStories
+ Web.HackerNews: newtype Karma
+ Web.HackerNews: newtype Kids
+ Web.HackerNews: newtype NewStories
+ Web.HackerNews: newtype Parent
+ Web.HackerNews: newtype Parts
+ Web.HackerNews: newtype Score
+ Web.HackerNews: newtype ShowStories
+ Web.HackerNews: newtype Submitted
+ Web.HackerNews: newtype Time
+ Web.HackerNews: newtype Title
+ Web.HackerNews: newtype URL
+ Web.HackerNews: newtype UserName
+ Web.HackerNews: type HackerNewsAPI = ("item" :> (HackerCapture ItemId :> Get '[JSON] Item)) :<|> (("user" :> (HackerCapture UserId :> Get '[JSON] User)) :<|> (("maxitem.json" :> Get '[JSON] MaxItem) :<|> (("topstories.json" :> Get '[JSON] TopStories) :<|> (("newstories.json" :> Get '[JSON] NewStories) :<|> (("beststories.json" :> Get '[JSON] BestStories) :<|> (("askstories.json" :> Get '[JSON] AskStories) :<|> (("showstories.json" :> Get '[JSON] ShowStories) :<|> (("jobstories.json" :> Get '[JSON] JobStories) :<|> ("updates.json" :> Get '[JSON] Updates)))))))))
+ Web.HackerNews.Types: About :: Text -> About
+ Web.HackerNews.Types: AskStories :: [Int] -> AskStories
+ Web.HackerNews.Types: BestStories :: [Int] -> BestStories
+ Web.HackerNews.Types: Comment :: ItemType
+ Web.HackerNews.Types: Created :: Int -> Created
+ Web.HackerNews.Types: Dead :: Bool -> Dead
+ Web.HackerNews.Types: DecodeFailureError :: Text -> Text -> HackerNewsError
+ Web.HackerNews.Types: Delay :: Int -> Delay
+ Web.HackerNews.Types: Deleted :: Int -> Deleted
+ Web.HackerNews.Types: Descendants :: Int -> Descendants
+ Web.HackerNews.Types: FailureResponseError :: Int -> Text -> Text -> HackerNewsError
+ Web.HackerNews.Types: HNConnectionError :: Text -> HackerNewsError
+ Web.HackerNews.Types: InvalidContentTypeHeaderError :: Text -> Text -> HackerNewsError
+ Web.HackerNews.Types: Item :: Maybe ItemId -> Maybe Deleted -> ItemType -> Maybe UserName -> Maybe Time -> Maybe ItemText -> Maybe Dead -> Maybe Parent -> Maybe Kids -> Maybe URL -> Maybe Score -> Maybe Title -> Maybe Parts -> Maybe Descendants -> Item
+ Web.HackerNews.Types: ItemId :: Int -> ItemId
+ Web.HackerNews.Types: ItemText :: Text -> ItemText
+ Web.HackerNews.Types: Job :: ItemType
+ Web.HackerNews.Types: JobStories :: [Int] -> JobStories
+ Web.HackerNews.Types: Karma :: Int -> Karma
+ Web.HackerNews.Types: Kids :: [Int] -> Kids
+ Web.HackerNews.Types: MaxItem :: Int -> MaxItem
+ Web.HackerNews.Types: NewStories :: [Int] -> NewStories
+ Web.HackerNews.Types: NotFound :: HackerNewsError
+ Web.HackerNews.Types: Parent :: Int -> Parent
+ Web.HackerNews.Types: Parts :: [Int] -> Parts
+ Web.HackerNews.Types: Poll :: ItemType
+ Web.HackerNews.Types: PollOpt :: ItemType
+ Web.HackerNews.Types: Score :: Int -> Score
+ Web.HackerNews.Types: ShowStories :: [Int] -> ShowStories
+ Web.HackerNews.Types: Story :: ItemType
+ Web.HackerNews.Types: Submitted :: [Int] -> Submitted
+ Web.HackerNews.Types: Time :: Integer -> Time
+ Web.HackerNews.Types: Title :: Text -> Title
+ Web.HackerNews.Types: TopStories :: [Int] -> TopStories
+ Web.HackerNews.Types: URL :: Text -> URL
+ Web.HackerNews.Types: UnsupportedContentTypeError :: Text -> HackerNewsError
+ Web.HackerNews.Types: Updates :: [Int] -> [UserName] -> Updates
+ Web.HackerNews.Types: User :: UserId -> Maybe Delay -> Created -> Karma -> Maybe About -> Maybe Submitted -> User
+ Web.HackerNews.Types: UserId :: Text -> UserId
+ Web.HackerNews.Types: UserName :: Text -> UserName
+ Web.HackerNews.Types: [itemBy] :: Item -> Maybe UserName
+ Web.HackerNews.Types: [itemDead] :: Item -> Maybe Dead
+ Web.HackerNews.Types: [itemDeleted] :: Item -> Maybe Deleted
+ Web.HackerNews.Types: [itemDescendants] :: Item -> Maybe Descendants
+ Web.HackerNews.Types: [itemId] :: Item -> Maybe ItemId
+ Web.HackerNews.Types: [itemKids] :: Item -> Maybe Kids
+ Web.HackerNews.Types: [itemParent] :: Item -> Maybe Parent
+ Web.HackerNews.Types: [itemParts] :: Item -> Maybe Parts
+ Web.HackerNews.Types: [itemScore] :: Item -> Maybe Score
+ Web.HackerNews.Types: [itemText] :: Item -> Maybe ItemText
+ Web.HackerNews.Types: [itemTime] :: Item -> Maybe Time
+ Web.HackerNews.Types: [itemTitle] :: Item -> Maybe Title
+ Web.HackerNews.Types: [itemType] :: Item -> ItemType
+ Web.HackerNews.Types: [itemURL] :: Item -> Maybe URL
+ Web.HackerNews.Types: [items] :: Updates -> [Int]
+ Web.HackerNews.Types: [profiles] :: Updates -> [UserName]
+ Web.HackerNews.Types: [userAbout] :: User -> Maybe About
+ Web.HackerNews.Types: [userCreated] :: User -> Created
+ Web.HackerNews.Types: [userDelay] :: User -> Maybe Delay
+ Web.HackerNews.Types: [userId] :: User -> UserId
+ Web.HackerNews.Types: [userKarma] :: User -> Karma
+ Web.HackerNews.Types: [userSubmitted] :: User -> Maybe Submitted
+ Web.HackerNews.Types: data HackerNewsError
+ Web.HackerNews.Types: data Item
+ Web.HackerNews.Types: data ItemType
+ Web.HackerNews.Types: data Updates
+ Web.HackerNews.Types: data User
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.About
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.AskStories
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.BestStories
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.Created
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.Dead
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.Delay
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.Deleted
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.Descendants
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.Item
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.ItemId
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.ItemText
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.ItemType
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.JobStories
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.Karma
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.Kids
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.MaxItem
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.NewStories
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.Parent
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.Parts
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.Score
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.ShowStories
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.Submitted
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.Time
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.Title
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.TopStories
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.URL
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.Updates
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.User
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.UserId
+ Web.HackerNews.Types: instance Data.Aeson.Types.FromJSON.FromJSON Web.HackerNews.Types.UserName
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.About
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.AskStories
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.BestStories
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.Created
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.Dead
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.Delay
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.Deleted
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.Descendants
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.Item
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.ItemId
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.ItemText
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.ItemType
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.JobStories
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.Karma
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.Kids
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.MaxItem
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.NewStories
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.Parent
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.Parts
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.Score
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.ShowStories
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.Submitted
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.Time
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.Title
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.TopStories
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.URL
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.Updates
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.User
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.UserId
+ Web.HackerNews.Types: instance Data.Aeson.Types.ToJSON.ToJSON Web.HackerNews.Types.UserName
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.About
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.AskStories
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.BestStories
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.Created
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.Dead
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.Delay
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.Deleted
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.Descendants
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.HackerNewsError
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.Item
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.ItemId
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.ItemText
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.ItemType
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.JobStories
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.Karma
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.Kids
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.MaxItem
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.NewStories
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.Parent
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.Parts
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.Score
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.ShowStories
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.Submitted
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.Time
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.Title
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.TopStories
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.URL
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.Updates
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.User
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.UserId
+ Web.HackerNews.Types: instance GHC.Classes.Eq Web.HackerNews.Types.UserName
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.About
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.AskStories
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.BestStories
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.Created
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.Dead
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.Delay
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.Deleted
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.Descendants
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.Item
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.ItemId
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.ItemText
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.ItemType
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.JobStories
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.Karma
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.Kids
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.MaxItem
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.NewStories
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.Parent
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.Parts
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.Score
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.ShowStories
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.Submitted
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.Time
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.Title
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.TopStories
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.URL
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.Updates
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.User
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.UserId
+ Web.HackerNews.Types: instance GHC.Generics.Generic Web.HackerNews.Types.UserName
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.About
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.AskStories
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.BestStories
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.Created
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.Dead
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.Delay
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.Deleted
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.Descendants
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.HackerNewsError
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.Item
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.ItemId
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.ItemText
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.ItemType
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.JobStories
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.Karma
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.Kids
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.MaxItem
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.NewStories
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.Parent
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.Parts
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.Score
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.ShowStories
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.Submitted
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.Time
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.Title
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.TopStories
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.URL
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.Updates
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.User
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.UserId
+ Web.HackerNews.Types: instance GHC.Show.Show Web.HackerNews.Types.UserName
+ Web.HackerNews.Types: instance Web.HttpApiData.Internal.ToHttpApiData Web.HackerNews.Types.ItemId
+ Web.HackerNews.Types: instance Web.HttpApiData.Internal.ToHttpApiData Web.HackerNews.Types.UserId
+ Web.HackerNews.Types: newtype About
+ Web.HackerNews.Types: newtype AskStories
+ Web.HackerNews.Types: newtype BestStories
+ Web.HackerNews.Types: newtype Created
+ Web.HackerNews.Types: newtype Dead
+ Web.HackerNews.Types: newtype Delay
+ Web.HackerNews.Types: newtype Deleted
+ Web.HackerNews.Types: newtype Descendants
+ Web.HackerNews.Types: newtype ItemId
+ Web.HackerNews.Types: newtype ItemText
+ Web.HackerNews.Types: newtype JobStories
+ Web.HackerNews.Types: newtype Karma
+ Web.HackerNews.Types: newtype Kids
+ Web.HackerNews.Types: newtype MaxItem
+ Web.HackerNews.Types: newtype NewStories
+ Web.HackerNews.Types: newtype Parent
+ Web.HackerNews.Types: newtype Parts
+ Web.HackerNews.Types: newtype Score
+ Web.HackerNews.Types: newtype ShowStories
+ Web.HackerNews.Types: newtype Submitted
+ Web.HackerNews.Types: newtype Time
+ Web.HackerNews.Types: newtype Title
+ Web.HackerNews.Types: newtype TopStories
+ Web.HackerNews.Types: newtype URL
+ Web.HackerNews.Types: newtype UserId
+ Web.HackerNews.Types: newtype UserName
- Web.HackerNews: Comment :: Text -> CommentId -> Maybe [Int] -> Int -> Text -> UTCTime -> Text -> Bool -> Bool -> Comment
+ Web.HackerNews: Comment :: ItemType
- Web.HackerNews: Job :: Text -> JobId -> Int -> Text -> UTCTime -> Text -> Text -> Text -> Bool -> Bool -> Job
+ Web.HackerNews: Job :: ItemType
- Web.HackerNews: Poll :: Text -> PollId -> [Int] -> [Int] -> Int -> Text -> UTCTime -> Text -> Text -> Bool -> Bool -> Poll
+ Web.HackerNews: Poll :: ItemType
- Web.HackerNews: PollOpt :: Text -> PollOptId -> Int -> Int -> Text -> UTCTime -> Text -> Bool -> Bool -> PollOpt
+ Web.HackerNews: PollOpt :: ItemType
- Web.HackerNews: Story :: Text -> StoryId -> [Int] -> Int -> UTCTime -> Text -> Text -> Text -> Bool -> Bool -> Story
+ Web.HackerNews: Story :: ItemType
- Web.HackerNews: User :: Maybe Text -> UTCTime -> Int -> UserId -> Int -> [Int] -> Bool -> Bool -> User
+ Web.HackerNews: User :: UserId -> Maybe Delay -> Created -> Karma -> Maybe About -> Maybe Submitted -> User
- Web.HackerNews: getItem :: ItemId -> HackerNews Item
+ Web.HackerNews: getItem :: Manager -> ItemId -> IO (Either HackerNewsError Item)
- Web.HackerNews: getMaxItem :: HackerNews MaxItem
+ Web.HackerNews: getMaxItem :: Manager -> IO (Either HackerNewsError MaxItem)
- Web.HackerNews: getTopStories :: HackerNews TopStories
+ Web.HackerNews: getTopStories :: Manager -> IO (Either HackerNewsError TopStories)
- Web.HackerNews: getUpdates :: HackerNews Update
+ Web.HackerNews: getUpdates :: Manager -> IO (Either HackerNewsError Updates)
- Web.HackerNews: getUser :: UserId -> HackerNews User
+ Web.HackerNews: getUser :: Manager -> UserId -> IO (Either HackerNewsError User)

Files

README.md view
@@ -1,10 +1,10 @@-hackernews +hackernews ========== ![Hackage](https://img.shields.io/hackage/v/hackernews.svg) ![Hackage Dependencies](https://img.shields.io/hackage-deps/v/hackernews.svg) ![Haskell Programming Language](https://img.shields.io/badge/language-Haskell-blue.svg)-![BSD3 License](http://img.shields.io/badge/license-BSD3-brightgreen.svg)-![Build Status](https://img.shields.io/circleci/project/dmjio/hackernews.svg)+![MIT License](http://img.shields.io/badge/license-MIT-brightgreen.svg)+[![Build Status](https://travis-ci.org/dmjio/hackernews.svg?branch=master)](https://travis-ci.org/dmjio/hackernews)  Hacker News API for Haskell @@ -13,7 +13,7 @@  Now it supports GHCJS and can be used in the browser! Just install it using: ```bash-cabal install --ghcjs --flags=ghcjs+cabal install --ghcjs ```  ###Tests@@ -22,49 +22,81 @@ ```  ```bash-Hacker News API Tests-  - Retrieves a Story-  - Retrieves a Comment-  - Retrieves a User-  - Retrieves a Poll-  - Retrieves a Pollopt-  - Retrieves Top Stories-  - Retrieves Max Item-  - Retrieves Updates+HackerNews API tests+  should round trip Updates JSON+  should round trip Item JSON+  should round trip User JSON+  should retrieve item+  should retrieve user+  should retrieve max item+  should retrieve top stories+  should retrieve new stories+  should retrieve best stories+  should retrieve ask stories+  should retrieve show stories+  should retrieve job stories+  should retrieve updates -Finished in 0.0019 seconds-8 examples, 0 failures+  Finished in 1.2129 seconds+  13 examples, 0 failures ```  ###Usage-```haskell -module Example where+```haskell+module Main where -import           Control.Monad  (liftM3)-import           Web.HackerNews (UserId (..), getUser, hackerNews)+import Network.HTTP.Client+import Network.HTTP.Client.TLS +import Web.HackerNews+ main :: IO ()-main = print =<< hackerNews (liftM3 (,,) one two three)-  where-    one   = getUser (UserId "dmjio")-    two   = getUser (UserId "dmj")-    three = getUser (UserId "abs")+main = do+ print =<< getItem mgr (ItemId 1000)+ print =<< getUser mgr (UserId "dmjio")+ print =<< getMaxItem mgr+ print =<< getTopStories mgr+ print =<< getNewStories mgr+ print =<< getBestStories mgr+ print =<< getAskStories mgr+ print =<< getShowStories mgr+ print =<< getJobStories mgr+ print =<< getUpdates mgr ```  ```bash-(Just (User { userAbout = Nothing-            , userCreated = 2013-08-06 16:49:23 UTC-            , userDelay = 0-            , userId = UserId "dmjio"-            , userKarma = 6-            , userSubmitted = [8433827,8429256,8429161,8429069,8374809,8341570,7919268,7825469,7350544,7327291,6495994,6352317,6168527,6168524,6167639]})-      , -Just (User { userAbout = Nothing-           , userCreated = 2007-04-11 05:57:35 UTC-           , userDelay = 0-           , userId = UserId "dmj"-           , userKarma = 1-           , userSubmitted = [11737]-           }),-Nothing)+Right ( Item {+	 itemId = Just (ItemId 1000)+   , itemDeleted = Nothing+   , itemType = Story+   , itemBy = Just (UserName "python_kiss")+   , itemTime = Just (Time 1172394646)+   , itemText = Nothing+   , itemDead = Nothing+   , itemParent = Nothing+   , itemKids = Nothing+   , itemURL = Just (URL "http://www.netbusinessblog.com/2007/02/19/how-important-is-the-dot-com/")+   , itemScore = Just (Score 4)+   , itemTitle = Just (Title "How Important is the .com TLD?")+   , itemParts = Nothing+   , itemDescendants = Just (Descendants 0)+   })+Right (User {userId = UserId "dmjio"+		   , userDelay = Nothing+		   , userCreated = Created 1375807763+		   , userKarma = Karma 7+		   , userAbout = Nothing+		   , userSubmitted = Just (Submitted [11966297,9355613, ...])+		   })+Right (MaxItem 12695220)+Right (TopStories [12694004,12692190,12691597,...])+Right (NewStories [12695214,12695213,12695195,...])+Right (BestStories [12649414,12637126,12684980, ...])+Right (AskStories [12694706,12694401,12694038, ...])+Right (ShowStories [12694004,12692190,12695037, ...])+Right (JobStories [12693320,12691627,12690539,...])+Right (Updates { items = [12694916,12694478,12693674,..],+				 profiles = [UserName "stefano", UserName "chillydawg", ...]+			   })+ ```
+ ghc-examples/Example.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE OverloadedStrings #-}+module Main where++import Network.HTTP.Client+import Network.HTTP.Client.TLS++import Web.HackerNews++main :: IO ()+main = do+ mgr <- newManager tlsManagerSettings+ print =<< getItem mgr (ItemId 1000)+ print =<< getUser mgr (UserId "dmjio")+ print =<< getMaxItem mgr+ print =<< getTopStories mgr+ print =<< getNewStories mgr+ print =<< getBestStories mgr+ print =<< getAskStories mgr+ print =<< getShowStories mgr+ print =<< getJobStories mgr+ print =<< getUpdates mgr
+ ghc-src/Web/HackerNews.hs view
@@ -0,0 +1,215 @@+{-# LANGUAGE RecordWildCards            #-}+{-# LANGUAGE OverloadedStrings          #-}+{-# LANGUAGE TypeFamilies               #-}+{-# LANGUAGE FlexibleInstances          #-}+{-# LANGUAGE KindSignatures             #-}+{-# LANGUAGE DeriveGeneric              #-}+{-# LANGUAGE TypeOperators              #-}+{-# LANGUAGE DataKinds                  #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE ScopedTypeVariables        #-}+------------------------------------------------------------------------------+-- |+-- Module      : Web.HackerNews+-- Copyright   : (c) David Johnson, 2014-2016+-- Maintainer  : djohnson.m@gmail.com+-- Stability   : experimental+-- Portability : POSIX+--+-- Haskell port of <https://github.com/HackerNews/API>+--+------------------------------------------------------------------------------+module Web.HackerNews+  ( -- * Hacker News API+    HackerNewsAPI+   -- * Custom combinators+  , HackerCapture+   -- * API functions+  , getItem+  , getUser+  , getMaxItem+  , getTopStories+  , getNewStories+  , getBestStories+  , getAskStories+  , getShowStories+  , getJobStories+  , getUpdates+  -- * Core Types+  , Item        (..)+  , User        (..)+  , Updates     (..)+  , MaxItem     (..)+  , TopStories  (..)+  , NewStories  (..)+  , BestStories (..)+  , AskStories  (..)+  , ShowStories (..)+  , JobStories  (..)+  --- * Supporting Types+  , UserId      (..)+  , ItemId      (..)+  , Deleted     (..)+  , ItemType    (..)+  , UserName    (..)+  , Time        (..)+  , ItemText    (..)+  , Dead        (..)+  , Parent      (..)+  , Kids        (..)+  , URL         (..)+  , Score       (..)+  , Title       (..)+  , Parts       (..)+  , Descendants (..)+  , Delay       (..)+  , Created     (..)+  , Karma       (..)+  , About       (..)+  , Submitted   (..)+  --- * Error handling+  , HackerNewsError (..)+  ) where++import           Control.Monad.Trans.Except+import           Data.Bifunctor+import           Data.Monoid+import           Data.Proxy+import           Data.String.Conversions+import qualified Data.Text                  as T+import           Network.HTTP.Client        (Manager)+import           Network.HTTP.Types.Status+import           Servant.API+import           Servant.Client+import           Servant.Common.Req++import           Web.HackerNews.Types++-- | HackerNews API+type HackerNewsAPI =+       "item" :> HackerCapture ItemId :> Get '[JSON] Item+  :<|> "user" :> HackerCapture UserId :> Get '[JSON] User+  :<|> "maxitem.json" :> Get '[JSON] MaxItem+  :<|> "topstories.json" :> Get '[JSON] TopStories+  :<|> "newstories.json" :> Get '[JSON] NewStories+  :<|> "beststories.json" :> Get '[JSON] BestStories+  :<|> "askstories.json" :> Get '[JSON] AskStories+  :<|> "showstories.json" :> Get '[JSON] ShowStories+  :<|> "jobstories.json" :> Get '[JSON] JobStories+  :<|> "updates.json" :> Get '[JSON] Updates++-- | Custom combinator for appending '.json' to `Item` query+data HackerCapture (a :: *)++-- | Custom combinator `HasClient` instance+instance (ToHttpApiData a, HasClient api) => HasClient (HackerCapture a :> api) where+  type Client (HackerCapture a :> api) = a -> Client api+  clientWithRoute Proxy req val =+    clientWithRoute (Proxy :: Proxy api)+       (appendToPath p req)+    where+      p = T.unpack $ toUrlPiece val <> ".json"++-- | HN `BaseURL`+hackerNewsURL :: BaseUrl+hackerNewsURL = BaseUrl Https "hacker-news.firebaseio.com" 443 "/v0"++-- | Convert ServantError to HackerNewsError+toError :: Either ServantError ok -> Either HackerNewsError ok+toError = first go+  where+    go :: ServantError -> HackerNewsError+    go (FailureResponse Status{..} _ body) =+      FailureResponseError statusCode (cs statusMessage) (cs body)+    go (DecodeFailure _ _ "null") = NotFound+    go (DecodeFailure err _ body) =+      DecodeFailureError (cs err) (cs body)+    go (UnsupportedContentType _ body) =+      UnsupportedContentTypeError (cs body)+    go (InvalidContentTypeHeader header body) =+      InvalidContentTypeHeaderError (cs header) (cs body)+    go (ConnectionError ex) =+      HNConnectionError $ cs (show ex)++-- | Retrieve `Item`+getItem :: Manager -> ItemId -> IO (Either HackerNewsError Item)+getItem mgr itemId =+  toError <$> do+    runExceptT $ getItem' itemId mgr hackerNewsURL++-- | Retrieve `User`+getUser :: Manager -> UserId -> IO (Either HackerNewsError User)+getUser mgr userId =+  toError <$> do+    runExceptT $ getUser' userId mgr hackerNewsURL++-- | Retrieve `MaxItem`+getMaxItem :: Manager -> IO (Either HackerNewsError MaxItem)+getMaxItem mgr =+  toError <$> do+    runExceptT $ getMaxItem' mgr hackerNewsURL++-- | Retrieve `TopStories`+getTopStories :: Manager -> IO (Either HackerNewsError TopStories)+getTopStories mgr =+  toError <$> do+    runExceptT $ getTopStories' mgr hackerNewsURL++-- | Retrieve `NewStories`+getNewStories :: Manager -> IO (Either HackerNewsError NewStories)+getNewStories mgr =+  toError <$> do+    runExceptT $ getNewStories' mgr hackerNewsURL++-- | Retrieve `BestStories`+getBestStories :: Manager -> IO (Either HackerNewsError BestStories)+getBestStories mgr =+  toError <$> do+    runExceptT $ getBestStories' mgr hackerNewsURL++-- | Retrieve `AskStories`+getAskStories :: Manager -> IO (Either HackerNewsError AskStories)+getAskStories mgr =+  toError <$> do+    runExceptT $ getAskStories' mgr hackerNewsURL++-- | Retrieve `ShowStories`+getShowStories :: Manager -> IO (Either HackerNewsError ShowStories)+getShowStories mgr =+  toError <$> do+    runExceptT $ getShowStories' mgr hackerNewsURL++-- | Retrieve `JobStories`+getJobStories :: Manager -> IO (Either HackerNewsError JobStories)+getJobStories mgr =+  toError <$> do+    runExceptT $ getJobStories' mgr hackerNewsURL++-- | Retrieve `Updates`+getUpdates :: Manager -> IO (Either HackerNewsError Updates)+getUpdates mgr =+  toError <$> do+    runExceptT $ getUpdates' mgr hackerNewsURL++getItem' :: ItemId -> Manager -> BaseUrl -> ClientM Item+getUser' :: UserId -> Manager -> BaseUrl -> ClientM User+getMaxItem' :: Manager -> BaseUrl -> ClientM MaxItem+getTopStories' :: Manager -> BaseUrl -> ClientM TopStories+getNewStories' :: Manager -> BaseUrl -> ClientM NewStories+getBestStories' :: Manager -> BaseUrl -> ClientM BestStories+getAskStories' :: Manager -> BaseUrl -> ClientM AskStories+getShowStories' :: Manager -> BaseUrl -> ClientM ShowStories+getJobStories' :: Manager -> BaseUrl -> ClientM JobStories+getUpdates' :: Manager -> BaseUrl -> ClientM Updates++getItem'+  :<|> getUser'+  :<|> getMaxItem'+  :<|> getTopStories'+  :<|> getNewStories'+  :<|> getBestStories'+  :<|> getAskStories'+  :<|> getShowStories'+  :<|> getJobStories'+  :<|> getUpdates' = client (Proxy :: Proxy HackerNewsAPI)+
+ ghc-tests/Test.hs view
@@ -0,0 +1,111 @@+{-# LANGUAGE OverloadedStrings    #-}+{-# LANGUAGE ScopedTypeVariables  #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+module Main where++import Control.Applicative+import Data.Aeson+import Data.Either               (isRight)+import Generics.SOP.Arbitrary+import Generics.SOP.Universe+import Network.HTTP.Client+import Network.HTTP.Client.TLS+import Test.Hspec                (it, hspec, describe, shouldSatisfy, shouldBe)+import Test.QuickCheck+import Test.QuickCheck.Instances ()+import Web.HackerNews++instance Generic Item+instance Generic Updates+instance Generic User+instance Generic UserName+instance Generic UserId+instance Generic Delay+instance Generic Created+instance Generic Karma+instance Generic About+instance Generic Submitted++instance Arbitrary Item where arbitrary = garbitrary+instance Arbitrary User where arbitrary = garbitrary+instance Arbitrary Updates where arbitrary = garbitrary++instance Arbitrary UserName where arbitrary = garbitrary+instance Arbitrary UserId  where arbitrary = garbitrary+instance Arbitrary Delay where arbitrary = garbitrary+instance Arbitrary Created where arbitrary = garbitrary+instance Arbitrary Karma where arbitrary = garbitrary+instance Arbitrary About where arbitrary = garbitrary+instance Arbitrary Submitted where arbitrary = garbitrary++instance Arbitrary ItemId where arbitrary = garbitrary+instance Generic ItemId+instance Arbitrary Deleted where arbitrary = garbitrary+instance Generic Deleted+instance Arbitrary ItemType where arbitrary = garbitrary+instance Generic ItemType where+instance Arbitrary Time where arbitrary = garbitrary+instance Generic Time+instance Arbitrary ItemText where arbitrary = garbitrary+instance Generic ItemText+instance Arbitrary Dead where arbitrary = garbitrary+instance Generic Dead+instance Arbitrary Parent where arbitrary = garbitrary+instance Generic Parent+instance Arbitrary Kids where arbitrary = garbitrary+instance Generic Kids+instance Arbitrary URL where arbitrary = garbitrary+instance Generic URL+instance Arbitrary Score where arbitrary = garbitrary+instance Generic Score+instance Arbitrary Title where arbitrary = garbitrary+instance Generic Title+instance Arbitrary Parts where arbitrary = garbitrary+instance Generic Parts+instance Arbitrary Descendants where arbitrary = garbitrary+instance Generic Descendants++main :: IO ()+main = do+ mgr <- newManager tlsManagerSettings+ hspec $ do+  describe "HackerNews API tests" $ do+    it "should round trip Updates JSON" $ property $ \(x :: Updates) ->+       Just x == decode (encode x)+    it "should round trip Item JSON" $ property $ \(x :: Item) ->+       Just x == decode (encode x)+    it "should round trip User JSON" $ property $ \(x :: User) ->+       Just x == decode (encode x)+    it "should retrieve item" $ do+      (`shouldSatisfy` isRight) =<< getItem mgr (ItemId 1000)+    it "should return NotFound " $ do+      Left x <- getItem mgr (ItemId 0)+      x `shouldBe` NotFound+    it "should retrieve user" $ do+      (`shouldSatisfy` isRight) =<< getUser mgr (UserId "dmjio")+    it "should retrieve max item" $ do+      (`shouldSatisfy` isRight) =<< getMaxItem mgr+    it "should retrieve top stories" $ do+      (`shouldSatisfy` isRight) =<< getTopStories mgr+    it "should retrieve new stories" $ do+      (`shouldSatisfy` isRight) =<< getNewStories mgr+    it "should retrieve best stories" $ do+      (`shouldSatisfy` isRight) =<< getBestStories mgr+    it "should retrieve ask stories" $ do+      (`shouldSatisfy` isRight) =<< getAskStories mgr+    it "should retrieve show stories" $ do+      (`shouldSatisfy` isRight) =<< getShowStories mgr+    it "should retrieve job stories" $ do+      (`shouldSatisfy` isRight) =<< getJobStories mgr+    it "should retrieve updates" $ do+      (`shouldSatisfy` isRight) =<< getUpdates mgr++++++++++
+ ghcjs-examples/Example.hs view
@@ -0,0 +1,4 @@+module Main where++main :: IO ()+main = putStrLn "hi"
+ ghcjs-src/Web/HackerNews.hs view
@@ -0,0 +1,123 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE OverloadedStrings   #-}+------------------------------------------------------------------------------+-- |+-- Module      : Web.HackerNews+-- Copyright   : (c) David Johnson, 2014-2016+-- Maintainer  : djohnson.m@gmail.com+-- Stability   : experimental+-- Portability : POSIX+--+------------------------------------------------------------------------------+module Web.HackerNews+       ( -- * API functions+         getItem+       , getUser+       , getMaxItem+       , getTopStories+       , getNewStories+       , getBestStories+       , getAskStories+       , getShowStories+       , getJobStories+       , getUpdates+       -- * Core Types+       , Item        (..)+       , User        (..)+       , Updates     (..)+       , MaxItem     (..)+       , TopStories  (..)+       , NewStories  (..)+       , BestStories (..)+       , AskStories  (..)+       , ShowStories (..)+       , JobStories  (..)+       --- * Supporting Types+       , UserId      (..)+       , ItemId      (..)+       , Deleted     (..)+       , ItemType    (..)+       , UserName    (..)+       , Time        (..)+       , ItemText    (..)+       , Dead        (..)+       , Parent      (..)+       , Kids        (..)+       , URL         (..)+       , Score       (..)+       , Title       (..)+       , Parts       (..)+       , Descendants (..)+       , Delay       (..)+       , Created     (..)+       , Karma       (..)+       , About       (..)+       , Submitted   (..)+       ) where++------------------------------------------------------------------------------+import Data.Aeson+import Data.Monoid+import JavaScript.Web.XMLHttpRequest+import Data.String.Conversions+import Data.JSString+import Data.JSString.Text++import Web.HackerNews.Types++-- | Retrieve `Item`+getItem :: ItemId -> IO (Either HackerNewsError Item)+getItem (ItemId x) = issueAjax (Just "item") $ textToJSString $ cs (show x)++-- | Retrieve `User`+getUser :: UserId -> IO (Either HackerNewsError User)+getUser (UserId u) = issueAjax (Just "user") (textToJSString u)++-- | Retrieve `MaxItem`+getMaxItem :: IO (Either HackerNewsError MaxItem)+getMaxItem = issueAjax Nothing "maxitem"++-- | Retrieve `TopStories`+getTopStories :: IO (Either HackerNewsError TopStories)+getTopStories = issueAjax Nothing "topstories"++-- | Retrieve `NewStories`+getNewStories :: IO (Either HackerNewsError NewStories)+getNewStories = issueAjax Nothing "newstories"++-- | Retrieve `BestStories`+getBestStories :: IO (Either HackerNewsError BestStories)+getBestStories = issueAjax Nothing "beststories"++-- | Retrieve `AskStories`+getAskStories :: IO (Either HackerNewsError AskStories)+getAskStories = issueAjax Nothing "askstories"++-- | Retrieve `ShowStories`+getShowStories :: IO (Either HackerNewsError ShowStories)+getShowStories = issueAjax Nothing "showstories"++-- | Retrieve `JobStories`+getJobStories :: IO (Either HackerNewsError JobStories)+getJobStories = issueAjax Nothing "jobstories"++-- | Retrieve `Updates`+getUpdates :: IO (Either HackerNewsError Updates)+getUpdates = issueAjax Nothing "updates"++issueAjax :: FromJSON a => Maybe JSString -> JSString -> IO (Either HackerNewsError a)+issueAjax maybePath uri = do+  response <- xhrByteString request+  pure $ case contents response of+    Nothing -> Left NotFound+    Just "null" -> Left NotFound+    Just x ->+      case eitherDecode (cs x) of+        Left l -> Left $ DecodeFailureError (cs l) mempty+        Right r -> Right r+    where+      request = Request GET url Nothing [] False NoData+      url = "https://hacker-news.firebaseio.com/v0/"+              <> maybe mempty (\path -> path <> "/") maybePath+              <> uri+              <> ".json"
+ ghcjs-tests/Test.hs view
@@ -0,0 +1,110 @@+{-# LANGUAGE OverloadedStrings    #-}+{-# LANGUAGE ScopedTypeVariables  #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-# LANGUAGE RecordWildCards      #-}+module Main where++import Control.Applicative+import Data.Aeson+import Data.Either               (isRight)+import Data.JSString+import Generics.SOP.Arbitrary+import Generics.SOP.Universe+import System.Exit+import Test.Hspec (it, hspec, describe, shouldSatisfy, shouldBe)+import Test.Hspec.Core.Runner (hspecResult, Summary(..))+import Test.QuickCheck+import Test.QuickCheck.Instances ()++import Web.HackerNews.Types+import Web.HackerNews++instance Generic Item+instance Generic Updates+instance Generic User+instance Generic UserName+instance Generic UserId+instance Generic Delay+instance Generic Created+instance Generic Karma+instance Generic About+instance Generic Submitted++instance Arbitrary Item where arbitrary = garbitrary+instance Arbitrary User where arbitrary = garbitrary+instance Arbitrary Updates where arbitrary = garbitrary++instance Arbitrary UserName where arbitrary = garbitrary+instance Arbitrary UserId  where arbitrary = garbitrary+instance Arbitrary Delay where arbitrary = garbitrary+instance Arbitrary Created where arbitrary = garbitrary+instance Arbitrary Karma where arbitrary = garbitrary+instance Arbitrary About where arbitrary = garbitrary+instance Arbitrary Submitted where arbitrary = garbitrary++instance Arbitrary ItemId where arbitrary = garbitrary+instance Generic ItemId+instance Arbitrary Deleted where arbitrary = garbitrary+instance Generic Deleted+instance Arbitrary ItemType where arbitrary = garbitrary+instance Generic ItemType where+instance Arbitrary Time where arbitrary = garbitrary+instance Generic Time+instance Arbitrary ItemText where arbitrary = garbitrary+instance Generic ItemText+instance Arbitrary Dead where arbitrary = garbitrary+instance Generic Dead+instance Arbitrary Parent where arbitrary = garbitrary+instance Generic Parent+instance Arbitrary Kids where arbitrary = garbitrary+instance Generic Kids+instance Arbitrary URL where arbitrary = garbitrary+instance Generic URL+instance Arbitrary Score where arbitrary = garbitrary+instance Generic Score+instance Arbitrary Title where arbitrary = garbitrary+instance Generic Title+instance Arbitrary Parts where arbitrary = garbitrary+instance Generic Parts+instance Arbitrary Descendants where arbitrary = garbitrary+instance Generic Descendants++main :: IO ()+main = do+ Summary{..} <- hspecResult $ do+  describe "HackerNews API tests" $ do+    it "should round trip Updates JSON" $ property $ \(x :: Updates) ->+       Just x == decode (encode x)+    it "should round trip Item JSON" $ property $ \(x :: Item) ->+       Just x == decode (encode x)+    it "should round trip User JSON" $ property $ \(x :: User) ->+       Just x == decode (encode x)+    it "should retrieve item" $ do+      (`shouldSatisfy` isRight) =<< getItem (ItemId 1000)+    it "should return NotFound " $ do+      Left x <- getItem (ItemId 0)+      x `shouldBe` NotFound+    it "should retrieve user" $ do+      (`shouldSatisfy` isRight) =<< getUser (UserId "dmjio")+    it "should retrieve max item" $ do+      (`shouldSatisfy` isRight) =<< getMaxItem+    it "should retrieve top stories" $ do+      (`shouldSatisfy` isRight) =<< getTopStories+    it "should retrieve new stories" $ do+      (`shouldSatisfy` isRight) =<< getNewStories+    it "should retrieve best stories" $ do+      (`shouldSatisfy` isRight) =<< getBestStories+    it "should retrieve ask stories" $ do+      (`shouldSatisfy` isRight) =<< getAskStories+    it "should retrieve show stories" $ do+      (`shouldSatisfy` isRight) =<< getShowStories+    it "should retrieve job stories" $ do+      (`shouldSatisfy` isRight) =<< getJobStories+    it "should retrieve updates" $ do+      (`shouldSatisfy` isRight) =<< getUpdates+ case summaryFailures of+   x | x > 0 -> putStrLn "error"+     | otherwise -> putStrLn "done"+++
hackernews.cabal view
@@ -1,5 +1,5 @@ name:                hackernews-version:             0.5.0.1+version:             1.0.0.0 description:         API for news.ycombinator.com license:             MIT synopsis:            API for Hacker News@@ -10,64 +10,93 @@ build-type:          Simple cabal-version:       >=1.10 extra-source-files:-    README.md       +    jsbits/options.js+    README.md+    LICENSE+    ghc-examples/Example.hs+    ghcjs-examples/Example.hs+    ghc-tests/Test.hs+    ghcjs-tests/Test.hs+executable example+  main-is: Example.hs+  default-language: Haskell2010+  if impl (ghcjs)+    build-depends:+        base+      , hackernews == 1.0.*+      , ghcjs-base+    hs-source-dirs: ghcjs-examples+  else+    build-depends:+         base+       , hackernews == 1.0.*+       , http-client-tls+       , http-client+    hs-source-dirs: ghc-examples -flag ghcjs-     description:    Tell cabal we're using GHCJS-     default:        False+executable ghcjs-tests+  main-is: Test.hs+  if impl(ghcjs)+    hs-source-dirs: ghcjs-tests+    build-depends: base+               , hackernews == 1.0.*+               , ghcjs-base+               , hspec+               , hspec-core+               , basic-sop+               , generics-sop+               , quickcheck-instances+               , aeson+                 , QuickCheck+  else+    buildable: False+  default-language: Haskell2010  library-  exposed-modules:     Web.HackerNews-  other-modules:       Web.HackerNews.Types-                     , Web.HackerNews.Client-                     , Web.HackerNews.User-                     , Web.HackerNews.Poll-                     , Web.HackerNews.Person-                     , Web.HackerNews.Update-                     , Web.HackerNews.Comment-                     , Web.HackerNews.Story-                     , Web.HackerNews.Job-                     , Web.HackerNews.Item-                     , Web.HackerNews.Util-                     , Web.HackerNews.Endpoint-  hs-source-dirs:      src--  if flag(ghcjs)-     ghcjs-options:  -O2-     build-depends:  ghcjs-base-                   , aeson       >= 0.8.0.1-                   , base        == 4.*-                   , attoparsec  >= 0.12.1.2-                   , either      >= 4.3.1-                   , text        >= 1.2.0.0-                   , time        >= 1.4.2-                   , transformers >= 0.3.0.0+  exposed-modules:  Web.HackerNews+                  , Web.HackerNews.Types+  hs-source-dirs: src+  build-depends: servant == 0.8.*+  if impl(ghcjs)+    ghcjs-options:  -Wall+    hs-source-dirs: ghcjs-src+    build-depends:  aeson == 0.9.*+                  , attoparsec == 0.13.*+                  , base < 5+                  , ghcjs-base+                  , string-conversions == 0.4.*+                  , text == 1.2.*+                  , transformers == 0.4.*   else-     ghc-options:         -Wall -rtsopts-     build-depends:  HsOpenSSL    >= 0.10.5-                     , aeson        >= 0.8.0.1-                     , attoparsec   >= 0.12.1.2-                     , base        == 4.*-                     , bytestring   >= 0.10.4.0-                     , either       >= 4.3.1-                     , http-streams >= 0.7.2.2-                     , io-streams   >= 1.1.4.6-                     , text         >= 1.2.0.0-                     , time         >= 1.4.2-                     , transformers >= 0.3.0.0+    ghc-options:  -Wall+    hs-source-dirs: ghc-src+    build-depends: aeson+                 , base < 5+                 , servant-client == 0.8.*+                 , http-client == 0.4.*+                 , string-conversions == 0.4.*+                 , http-types == 0.9.*+                 , text == 1.2.*+                 , transformers == 0.5.*   default-language:    Haskell2010 -Test-Suite tests-    type:                exitcode-stdio-1.0-    ghc-options:         -rtsopts -threaded-    hs-source-dirs:      tests-    main-is:             Test.hs-    build-depends:       base-                       , hackernews-                       , hspec >= 1.11.4-                       , transformers-    default-language:    Haskell2010-    ghc-options:         -Wall+Test-Suite ghc-tests+  type:                exitcode-stdio-1.0+  default-language:    Haskell2010+  main-is:             Test.hs+  ghc-options:         -rtsopts -threaded -Wall+  hs-source-dirs:      ghc-tests+  build-depends:       aeson+                     , base+                     , basic-sop+                     , generics-sop == 0.2.*+                     , hackernews == 1.0.*+                     , hspec == 2.2.*+                     , http-client-tls == 0.2.*+                     , http-client+                     , QuickCheck == 2.8.*+                     , quickcheck-instances == 0.3.*+                     , transformers  source-repository head   type:     git
+ jsbits/options.js view
@@ -0,0 +1,30 @@+"use strict";+var system = require('system');+if (system.args.length !== 2) {+    console.log('Usage: phantomjsOpen.js URL');+    phantom.exit(1);+}++var page = require('webpage').create();++page.onError = function (msg, trace) {+  if (msg.match("error").length > 0) {+    phantom.exit(1);+  }+};++page.open(system.args[1], function (status) {+    page.onConsoleMessage = function (msg) {+	if (msg.match("done")) {+	    phantom.exit(0);+	} else if (msg.match("error")) {+	    phantom.exit(1);+	} else {+	    console.log(msg);+	}+    };+    if (status !== "success") {+        console.log("Unable to open " + system.args[1]);+        phantom.exit(1);+    }+});
− src/Web/HackerNews.hs
@@ -1,96 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--- |--- Module      : Web.HackerNews--- Copyright   : (c) David Johnson, Konstantin Zudov, 2014--- Maintainer  : djohnson.m@gmail.com--- Stability   : experimental--- Portability : POSIX-module Web.HackerNews-       ( -- * Hacker News Monad-         hackerNews-         -- * API Calls-       , getItem-       , getStory-       , getComment-       , getPoll-       , getPollOpt-       , getUser-       , getJob-       , getTopStories-       , getMaxItem-       , getUpdates-         -- * Types-       , HackerNews-       , HackerNewsError (..)-       , Item      (..)-       , ItemId    (..)-       , Comment   (..)-       , CommentId (..)-       , Poll      (..)-       , PollId    (..)-       , PollOpt   (..)-       , PollOptId (..)-       , Story     (..)-       , StoryId   (..)-       , User      (..)-       , UserId    (..)-       , Job       (..)-       , JobId     (..)-       , Update    (..)-       , MaxItem   (..)-       , TopStories (..)-       ) where--import           Web.HackerNews.Types-import           Web.HackerNews.Client (HackerNews, hackerNews, HackerNewsError(..))----------------------------------------------------------------------------------- | Retrieve a `Item` by `ItemId`-getItem :: ItemId -> HackerNews Item-getItem = getEndpoint----------------------------------------------------------------------------------- | Retrieve a `Story` by `StoryId`-getStory :: StoryId -> HackerNews Story-getStory = getEndpoint----------------------------------------------------------------------------------- | Retrieve a `Comment` by `CommentId`-getComment :: CommentId -> HackerNews Comment-getComment = getEndpoint----------------------------------------------------------------------------------- | Retrieve a `Poll` by `PollId`-getPoll :: PollId -> HackerNews Poll-getPoll = getEndpoint----------------------------------------------------------------------------------- | Retrieve a `PollOpt` by `PollOptId`-getPollOpt :: PollOptId -> HackerNews PollOpt-getPollOpt = getEndpoint----------------------------------------------------------------------------------- | Retrieve a `User` by `UserId`-getUser :: UserId -> HackerNews User-getUser = getEndpoint----------------------------------------------------------------------------------- | Retrieve a Job-getJob :: JobId -> HackerNews Job-getJob = getEndpoint----------------------------------------------------------------------------------- | Retrieve the Top Stories on Hacker News-getTopStories :: HackerNews TopStories-getTopStories = getEndpoint TopStoriesId----------------------------------------------------------------------------------- | Retrieve the largest ItemId-getMaxItem :: HackerNews MaxItem-getMaxItem = getEndpoint MaxItemId----------------------------------------------------------------------------------- | Retrieve the latest updates-getUpdates :: HackerNews Update-getUpdates = getEndpoint UpdateId-
− src/Web/HackerNews/Client.hs
@@ -1,163 +0,0 @@-{-# LANGUAGE CPP, OverloadedStrings, ForeignFunctionInterface, JavaScriptFFI#-}---------------------------------------------------------------------------------- |--- Module      : Web.HackerNews.Client--- Copyright   : (c) David Johnson, 2014--- Maintainer  : djohnson.m@gmail.com--- Stability   : experimental--- Portability : POSIX--- | --------------------------------------------------------------------------------module Web.HackerNews.Client-       ( hackerNews-       , buildHNRequest-       , HackerNews-       , HackerNewsError (..)-       ) where---------------------------------------------------------------------------------import           Data.Aeson                 hiding (Result)-import           Data.Aeson.Parser          (value)-import qualified Data.Text.Encoding         as T-import           Data.Text                  (Text)-import           Data.Monoid                ((<>))-import           Control.Monad.Trans.Either-import           Data.Either                (rights)-import           Data.Maybe-import           Control.Monad.IO.Class     (liftIO)-import           Data.Attoparsec.ByteString (parseOnly)-import           Control.Monad              (when)-#ifdef __GHCJS__-import           GHCJS.Types-import           GHCJS.Foreign as F-#else-import           Control.Exception          (try, SomeException)-import           Control.Monad.Trans.Class  (lift)-import           Control.Monad.Trans.Reader (ReaderT, ask, runReaderT)-import           Network.Http.Client-import           OpenSSL                    (withOpenSSL)-import qualified System.IO.Streams          as Streams-#endif------------------------------------------------------------------------------------------------------------------------------------------------------------------- | Debug flag-debug :: Bool-debug = False----------------------------------------------------------------------------------- | Core Type-#ifdef __GHCJS__-type HackerNews a = EitherT HackerNewsError IO a-#else-type HackerNews a = EitherT HackerNewsError (ReaderT Connection IO) a-#endif---------------------------------------------------------------------------------- | Error Types-data HackerNewsError =-    ConnectionError-  | ParseError-  | NotFound-  | RequestError-  deriving (Show, Eq)--#ifdef __GHCJS__--- | HackerNews API request method-hackerNews :: FromJSON a => HackerNews a -> IO (Either HackerNewsError a)-hackerNews = runEitherT------------------------------------------------------------------------------------ | Request Builder for API-buildHNRequest :: FromJSON a => Text -> HackerNews a-buildHNRequest path = do-  let url = "https://hacker-news.firebaseio.com/v0/" <> path <> ".json"-  res <- liftIO $ ajax url-  case (arError res) of-   Just et -> case et of-     "connection-error" -> left ConnectionError-     "request-error" -> left RequestError-     _ -> left NotFound-   Nothing -> do-     let t = T.encodeUtf8 $ fromMaybe "" $ arData res-         xs = rights [parseOnly value t, parseOnly json t]-     when debug $ liftIO . print $ t-     case xs of-      [] -> left ParseError-      x : _ ->-        case fromJSON x of-         Success jsonBody -> right jsonBody-         _                -> left NotFound---data AjaxResult = AjaxResult { arData :: Maybe Text,-                               arError :: Maybe Text-                             } deriving (Ord, Eq, Show)--ajax :: Text -> IO AjaxResult-ajax url = do-  res <- js_ajax (toJSString url)-  err <- F.getProp ("error" :: Text) res-  dat <- F.getProp ("data" :: Text) res-  let d = getTextDat dat-      e = getTextDat err-  return (AjaxResult d e)-  where getTextDat dt = if isNull dt then Nothing else Just (fromJSString dt)---foreign import javascript interruptible "var req = new XMLHttpRequest(); \-  if (!req)\-    $c({error: 'connection-error', data: null});\-  req.onreadystatechange = function() {\-    if (req.readyState === 4) {\-      if (req.status === 200) {\-        $c({data: req.responseText, error: null});\-      } else\-        $c({error: 'request-error', data: null});\-    }\-  };\-  req.open('GET', $1, true);\-  req.send();"-  js_ajax :: JSString -> IO (JSRef ajaxResult)--#else---------------------------------------------------------------------------------- | HackerNews API request method-hackerNews :: FromJSON a => HackerNews a -> IO (Either HackerNewsError a)-hackerNews requests =-  withOpenSSL $ do-    ctx <- baselineContextSSL-    con <- try (openConnectionSSL ctx "hacker-news.firebaseio.com" 443) :: IO (Either SomeException Connection)-    case con of-     Left _ -> return $ Left ConnectionError-     Right conn -> do-       result <- flip runReaderT conn $ runEitherT requests-       closeConnection conn-       return result----------------------------------------------------------------------------------- | Request Builder for API-buildHNRequest :: FromJSON a => Text -> HackerNews a-buildHNRequest url = do-    con <- lift ask-    bytes <- liftIO $ do-      req <- buildRequest $ do-        http GET $ "/v0/" <> T.encodeUtf8 url <> ".json"-        setHeader "Connection" "Keep-Alive"-        setAccept "application/json"-      sendRequest con req emptyBody-      receiveResponse con $ const Streams.read-    case bytes of-      Nothing -> left RequestError-      Just bs -> do-        when debug $ liftIO . print $ bs-        let xs = rights [parseOnly value bs, parseOnly json bs]-        case xs of-          []    -> left ParseError-          x : _ ->-            case fromJSON x of-             Success jsonBody -> right jsonBody-             _                -> left NotFound-    -#endif
− src/Web/HackerNews/Comment.hs
@@ -1,62 +0,0 @@-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings     #-}--- |--- Module      : Web.HackerNews.Comment--- Copyright   : (c) David Johnson, Konstantin Zudov 2014--- Maintainer  : djohnson.m@gmail.com--- Stability   : experimental--- Portability : POSIX-module Web.HackerNews.Comment where--import           Control.Applicative     ((<$>), (<*>))-import           Control.Monad           (MonadPlus (mzero))-import           Data.Aeson              (FromJSON (parseJSON), Value (Object),-                                          (.!=), (.:), (.:?))-import           Data.Text               (Text)-import           Data.Time               (UTCTime)--import           Web.HackerNews.Endpoint (Endpoint (endpoint), itemEndpoint)-import           Web.HackerNews.Util     (fromSeconds)----------------------------------------------------------------------------------- | Comment Object-data Comment = Comment {-    commentBy      :: Text-  , commentId      :: CommentId-  , commentKids    :: Maybe [Int]-  , commentParent  :: Int-  , commentText    :: Text-  , commentTime    :: UTCTime-  , commentType    :: Text-  , commentDeleted :: Bool-  , commentDead    :: Bool-  } deriving Show----------------------------------------------------------------------------------- | `CommentId` for a `Comment` Object-newtype CommentId-  = CommentId Int-  deriving (Show, Eq)----------------------------------------------------------------------------------- | Endpoint instances-instance Endpoint CommentId Comment where-    endpoint (CommentId id') = itemEndpoint id'----------------------------------------------------------------------------------- | JSON Instances-instance FromJSON Comment where-  parseJSON (Object o) =-     Comment <$> o .: "by"-             <*> (CommentId <$> o .: "id")-             <*> o .:? "kids"-             <*> o .: "parent"-             <*> o .: "text"-             <*> (fromSeconds <$> o .: "time")-             <*> o .: "type"-             <*> o .:? "deleted" .!= False-             <*> o .:? "dead" .!= False-  parseJSON _ = mzero---
− src/Web/HackerNews/Endpoint.hs
@@ -1,32 +0,0 @@-{-# LANGUAGE FunctionalDependencies #-}-{-# LANGUAGE MultiParamTypeClasses  #-}-{-# LANGUAGE OverloadedStrings      #-}--- |--- Module      : Web.HackerNews.Endpoint--- Copyright   : (c) David Johnson, Konstantin Zudov, 2014--- Maintainer  : djohnson.m@gmail.com--- Stability   : experimental--- Portability : POSIX-module Web.HackerNews.Endpoint where--import           Data.Aeson            (FromJSON)-import           Data.Text             (Text, append)--import           Web.HackerNews.Client (HackerNews, buildHNRequest)-import           Web.HackerNews.Util   (toText)----------------------------------------------------------------------------------- | Endpoint maps the id to the returned type on a type level--- The function dependency @id -> resp@ specifies that @id@ uniquely determines @resp@-class Endpoint id resp | id -> resp where-    endpoint :: id -> Text -- ^ Turn @id@ into path that points to resource----------------------------------------------------------------------------------- | Endpoint for `Item`-itemEndpoint :: Int -> Text-itemEndpoint = append "item/" . toText----------------------------------------------------------------------------------- | Generic function for making requests-getEndpoint :: (Endpoint a b, FromJSON b) => a -> HackerNews b-getEndpoint id' = buildHNRequest $ endpoint id'
− src/Web/HackerNews/Item.hs
@@ -1,72 +0,0 @@-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings     #-}--- |--- Module      : Web.Stripe.Stripe--- Copyright   : (c) David Johnson, Konstantin Zudov, 2014--- Maintainer  : djohnson.m@gmail.com--- Stability   : experimental--- Portability : POSIX-module Web.HackerNews.Item where--import           Control.Applicative     ((<$>))-import           Control.Monad           (MonadPlus (mzero))-import           Data.Aeson              (FromJSON (parseJSON), Value (Object),-                                          (.:))-import           Data.Text               (Text)--import           Web.HackerNews.Comment  (Comment)-import           Web.HackerNews.Endpoint (Endpoint (endpoint), itemEndpoint)-import           Web.HackerNews.Job      (Job)-import           Web.HackerNews.Poll     (Poll, PollOpt)-import           Web.HackerNews.Story    (Story)----------------------------------------------------------------------------------- | Item Type-data Item = ItemComment Comment-          | ItemPoll Poll-          | ItemPollOpt PollOpt-          | ItemStory Story-          | ItemJob Job-          deriving (Show)----------------------------------------------------------------------------------- | Item ID for a `Item` object-newtype ItemId = ItemId Int deriving (Show,Eq)----------------------------------------------------------------------------------- | Max Item ID for a `Item`-data MaxItemId = MaxItemId deriving (Show, Eq)----------------------------------------------------------------------------------- | Max Item Int-newtype MaxItem = MaxItem Int deriving (Show, Eq)----------------------------------------------------------------------------------- | Endpoint Instances for `MaxItemId` and `MaxItem`-instance Endpoint MaxItemId MaxItem where-    endpoint _ = "maxitem"----------------------------------------------------------------------------------- | Endpoint Instances for `ItemId` & `Item`-instance Endpoint ItemId Item where-    endpoint (ItemId id') = itemEndpoint id'----------------------------------------------------------------------------------- | JSON Instances-instance FromJSON Item where-    parseJSON v@(Object o) = do-        itemType <- o .: "type"-        case (itemType :: Text) of-            "job"     -> ItemJob     <$> parseJSON v-            "story"   -> ItemStory   <$> parseJSON v-            "comment" -> ItemComment <$> parseJSON v-            "poll"    -> ItemPoll    <$> parseJSON v-            "pollopt" -> ItemPollOpt <$> parseJSON v-            _         -> mzero-    parseJSON _ = mzero----------------------------------------------------------------------------------- | JSON MaxItem Instance-instance FromJSON MaxItem where-    parseJSON = fmap MaxItem . parseJSON-
− src/Web/HackerNews/Job.hs
@@ -1,62 +0,0 @@-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings     #-}--- |--- Module      : Web.HackerNews.Job--- Copyright   : (c) David Johnson, Konstantin Zudov, 2014--- Maintainer  : djohnson.m@gmail.com--- Stability   : experimental--- Portability : POSIX-module Web.HackerNews.Job where--import           Control.Applicative     ((<$>), (<*>))-import           Control.Monad           (MonadPlus (mzero))-import           Data.Aeson              (FromJSON (parseJSON), Value (Object),-                                          (.!=), (.:), (.:?))-import           Data.Text               (Text)-import           Data.Time               (UTCTime)--import           Web.HackerNews.Endpoint (Endpoint (endpoint), itemEndpoint)-import           Web.HackerNews.Util     (fromSeconds)----------------------------------------------------------------------------------- | Types-data Job = Job {-    jobBy      :: Text-  , jobId      :: JobId-  , jobScore   :: Int-  , jobText    :: Text-  , jobTime    :: UTCTime-  , jobTitle   :: Text-  , jobType    :: Text-  , jobUrl     :: Text-  , jobDeleted :: Bool-  , jobDead    :: Bool-  } deriving (Show)----------------------------------------------------------------------------------- | ID for a `Job` type-newtype JobId-      = JobId Int-      deriving (Show, Eq)----------------------------------------------------------------------------------- | Endpoint Instances-instance Endpoint JobId Job where-    endpoint (JobId id') = itemEndpoint id'----------------------------------------------------------------------------------- | JSON Instances-instance FromJSON Job where-  parseJSON (Object o) =-      Job <$> o .: "by"-          <*> (JobId <$> o .: "id")-          <*> o .: "score"-          <*> o .: "text"-          <*> (fromSeconds <$> o .: "time")-          <*> o .: "title"-          <*> o .: "type"-          <*> o .: "url"-          <*> o .:? "deleted" .!= False-          <*> o .:? "dead" .!= False-  parseJSON _ = mzero-
− src/Web/HackerNews/Person.hs
@@ -1,54 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--- |--- Module      : Web.HackerNews.Person--- Copyright   : (c) David Johnson, 2014--- Maintainer  : djohnson.m@gmail.com--- Stability   : experimental--- Portability : POSIX-module Web.HackerNews.Person where--import           Control.Applicative ((<$>), (<*>))-import           Control.Monad       (MonadPlus (mzero))-import           Data.Aeson          (FromJSON (parseJSON), Value (Object),-                                      (.!=), (.:), (.:?))-import           Data.Text           (Text)-import           Data.Time           (UTCTime)--import           Web.HackerNews.Util (fromSeconds)----------------------------------------------------------------------------------- | Person Object-data Person = Person {-    personBy      :: Text-  , personId      :: PersonId-  , personKids    :: Maybe [Int]-  , personScore   :: Maybe Int-  , personTime    :: UTCTime-  , personTitle   :: Maybe Text-  , personType    :: Text-  , personUrl     :: Maybe Text-  , personDeleted :: Bool-  , personDead    :: Bool-  } deriving (Show, Eq)----------------------------------------------------------------------------------- | Person ID for a `Person` Object-newtype PersonId-  = PersonId Text-  deriving (Show, Eq)----------------------------------------------------------------------------------- | JSON Instances-instance FromJSON Person where-   parseJSON (Object o) =-     Person <$> o .: "by"-            <*> (PersonId <$> o .: "id")-            <*> o .:? "kids"-            <*> o .:? "score"-            <*> (fromSeconds <$> o .: "time")-            <*> o .:? "title"-            <*> o .: "type"-            <*> o .:? "url"-            <*> o .:? "deleted" .!= False-            <*> o .:? "dead" .!= False-   parseJSON _ = mzero
− src/Web/HackerNews/Poll.hs
@@ -1,103 +0,0 @@-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings     #-}--- |--- Module      : Web.HackerNews.Poll--- Copyright   : (c) David Johnson, Konstantin Zudov, 2014--- Maintainer  : djohnson.m@gmail.com--- Stability   : experimental--- Portability : POSIX-module Web.HackerNews.Poll where--import           Control.Applicative     ((<$>), (<*>))-import           Control.Monad           (MonadPlus (mzero))-import           Data.Aeson              (FromJSON (parseJSON), Value (Object),-                                          (.!=), (.:), (.:?))-import           Data.Text               (Text)-import           Data.Time               (UTCTime)--import           Web.HackerNews.Endpoint (Endpoint (endpoint), itemEndpoint)-import           Web.HackerNews.Util     (fromSeconds)----------------------------------------------------------------------------------- | Poll Object-data Poll = Poll {-    pollBy      :: Text-  , pollId      :: PollId-  , pollKids    :: [Int]-  , pollParts   :: [Int]-  , pollScore   :: Int-  , pollText    :: Text-  , pollTime    :: UTCTime-  , pollTitle   :: Text-  , pollType    :: Text-  , pollDeleted :: Bool-  , pollDead :: Bool-  } deriving (Show, Eq)----------------------------------------------------------------------------------- | Poll Opt Object-data PollOpt = PollOpt {-    pollOptBy      :: Text-  , pollOptId      :: PollOptId-  , pollOptParent  :: Int-  , pollOptScore   :: Int-  , pollOptText    :: Text-  , pollOptTime    :: UTCTime-  , pollOptType    :: Text-  , pollOptDeleted :: Bool-  , pollOptDead       :: Bool  -  } deriving (Show, Eq)----------------------------------------------------------------------------------- | Poll Option Id for a `PollOpt`-newtype PollOptId-  = PollOptId Int-  deriving (Show, Eq)----------------------------------------------------------------------------------- | Poll Id for a `Poll`-newtype PollId-  = PollId Int-  deriving (Show, Eq)----------------------------------------------------------------------------------- | `Endpoint` Instance for `PollOptId`-instance Endpoint PollOptId PollOpt where-    endpoint (PollOptId id') = itemEndpoint id'----------------------------------------------------------------------------------- | `Endpoint` Instance for `PollId`-instance Endpoint PollId Poll where-    endpoint (PollId id') = itemEndpoint id'----------------------------------------------------------------------------------- | Poll JSON Instances-instance FromJSON Poll where-  parseJSON (Object o) =-     Poll <$> o .: "by"-          <*> (PollId <$> o .: "id")-          <*> o .: "kids"-          <*> o .: "parts"-          <*> o .: "score"-          <*> o .: "text"-          <*> (fromSeconds <$> o .: "time")-          <*> o .: "title"-          <*> o .: "type"-          <*> o .:? "deleted" .!= False-          <*> o .:? "dead" .!= False          -  parseJSON _ = mzero----------------------------------------------------------------------------------- | Poll JSON Instances-instance FromJSON PollOpt where-  parseJSON (Object o) =-     PollOpt <$> o .: "by"-             <*> (PollOptId <$> o .: "id")-             <*> o .: "parent"-             <*> o .: "score"-             <*> o .: "text"-             <*> (fromSeconds <$> o .: "time")-             <*> o .: "type"-             <*> o .:? "deleted" .!= False-             <*> o .:? "dead" .!= False             -  parseJSON _ = mzero
− src/Web/HackerNews/Story.hs
@@ -1,80 +0,0 @@-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings     #-}--- |--- Module      : Web.HackerNews.Story--- Copyright   : (c) David Johnson, Konstantin Zudov, 2014--- Maintainer  : djohnson.m@gmail.com--- Stability   : experimental--- Portability : POSIX-module Web.HackerNews.Story where--import           Control.Applicative     ((<$>), (<*>))-import           Control.Monad           (MonadPlus (mzero))-import           Data.Aeson              (FromJSON (parseJSON), Value (Object),-                                          (.!=), (.:), (.:?))-import           Data.Text               (Text)-import           Data.Time               (UTCTime)--import           Web.HackerNews.Endpoint (Endpoint (endpoint), itemEndpoint)-import           Web.HackerNews.Util     (fromSeconds)----------------------------------------------------------------------------------- | Story Object-data Story = Story {-    storyBy      :: Text-  , storyId      :: StoryId-  , storyKids    :: [Int]-  , storyScore   :: Int-  , storyTime    :: UTCTime-  , storyTitle   :: Text-  , storyType    :: Text-  , storyUrl     :: Text-  , storyDeleted :: Bool-  , storyDead    :: Bool-  } deriving Show----------------------------------------------------------------------------------- | ID for a `Story`-newtype StoryId-  = StoryId Int-  deriving (Show, Eq)----------------------------------------------------------------------------------- | `Story` ID-data TopStoriesId  = TopStoriesId deriving (Show, Eq)----------------------------------------------------------------------------------- | TopStories List-newtype TopStories = TopStories [Int] deriving (Show, Eq)----------------------------------------------------------------------------------- | Endpoint Instances for `StoryID` & `Story`-instance Endpoint StoryId Story where-    endpoint (StoryId id') = itemEndpoint id'----------------------------------------------------------------------------------- | Endpoint Instances for `TopStoriesID` & `TopStories`-instance Endpoint TopStoriesId TopStories where-    endpoint _ = "topstories"----------------------------------------------------------------------------------- | JSON Instances-instance FromJSON Story where-   parseJSON (Object o) =-     Story <$> o .: "by"-           <*> (StoryId <$> o .: "id")-           <*> o .: "kids"-           <*> o .: "score"-           <*> (fromSeconds <$> o .: "time")-           <*> o .: "title"-           <*> o .: "type"-           <*> o .: "url"-           <*> o .:? "deleted" .!= False-           <*> o .:? "dead" .!= False-   parseJSON _ = mzero----------------------------------------------------------------------------------- | JSON instances `TopStories`-instance FromJSON TopStories where-   parseJSON = fmap TopStories . parseJSON-
src/Web/HackerNews/Types.hs view
@@ -1,24 +1,217 @@+{-# LANGUAGE DeriveGeneric              #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+------------------------------------------------------------------------------ -- | -- Module      : Web.HackerNews.Types--- Copyright   : (c) David Johnson, Konstantin Zudov, 2014+-- Copyright   : (c) David Johnson, 2014-2016 -- Maintainer  : djohnson.m@gmail.com -- Stability   : experimental -- Portability : POSIX-module Web.HackerNews.Types-       ( module H-       ) where+--+-- Haskell port of <https://github.com/HackerNews/API>+--+------------------------------------------------------------------------------+module Web.HackerNews.Types where -import           Web.HackerNews.Comment as H-import           Web.HackerNews.Poll    as H-import           Web.HackerNews.Story   as H-import           Web.HackerNews.User    as H-import           Web.HackerNews.Update  as H-import           Web.HackerNews.Job     as H-import           Web.HackerNews.Item    as H-import           Web.HackerNews.Endpoint as H+import           Data.Aeson+import           Data.Aeson.Types+import           Data.Char+import qualified Data.Text        as T+import           GHC.Generics+import           Servant.API +-- | The item and profile changes are at <https://hacker-news.firebaseio.com/v0/updates>+data Updates = Updates  {+      items :: [Int]+       -- ^ Updated `Item`s+    , profiles :: [UserName]+       -- ^ Updated `UserName`s+   } deriving (Show, Eq, Generic) +instance ToJSON Updates+instance FromJSON Updates +-- | The current largest item id is at <https://hacker-news.firebaseio.com/v0/maxitem>.+-- You can walk backward from here to discover all items.+newtype MaxItem = MaxItem Int+  deriving (Show, Eq, ToJSON, FromJSON, Generic) +-- | <https://hacker-news.firebaseio.com/v0/topstories>+newtype TopStories = TopStories [Int]+  deriving (Show, Eq, ToJSON, FromJSON, Generic) +-- | <https://hacker-news.firebaseio.com/v0/newstories>+newtype NewStories = NewStories [Int]+  deriving (Show, Eq, ToJSON, FromJSON, Generic) +-- | <https://hacker-news.firebaseio.com/v0/beststories>+newtype BestStories = BestStories [Int]+  deriving (Show, Eq, ToJSON, FromJSON, Generic)++-- | <https://hacker-news.firebaseio.com/v0/askstories>+newtype AskStories = AskStories [Int]+  deriving (Show, Eq, ToJSON, FromJSON, Generic)++-- | <https://hacker-news.firebaseio.com/v0/showstories>+newtype ShowStories = ShowStories [Int]+  deriving (Show, Eq, ToJSON, FromJSON, Generic)++-- | <https://hacker-news.firebaseio.com/v0/jobstories>+newtype JobStories = JobStories [Int]+  deriving (Show, Eq, ToJSON, FromJSON, Generic)++-- | Users are identified by case-sensitive ids, and live under+-- <https://hacker-news.firebaseio.com/v0/user/>.+-- Only users that have public activity (comments or story submissions)+-- on the site are available through the API.+data User = User {+    userId :: UserId+    -- ^ The user's unique username. Case-sensitive. Required.+  , userDelay :: Maybe Delay+    -- ^ Delay in minutes between a comment's creation and its visibility to other users.+  , userCreated :: Created+    -- ^ Creation date of the user, in Unix Time.+  , userKarma :: Karma+    -- ^ The user's karma+  , userAbout :: Maybe About+    -- ^ The user's optional self-description. HTML.+  , userSubmitted :: Maybe Submitted+    -- ^ List of the user's stories, polls and comments.+  } deriving (Show, Eq, Generic)++instance ToJSON User where+  toJSON = genericToJSON defaultOptions {+    fieldLabelModifier = map toLower . drop 4+  }++instance FromJSON User where+  parseJSON = genericParseJSON defaultOptions {+    fieldLabelModifier = map toLower . drop 4+  }++-- | The user's karma.+newtype Karma = Karma Int+  deriving (Show, Eq, ToJSON, FromJSON, Generic)++-- | The user's unique username. Case-sensitive. Required.+newtype UserId = UserId T.Text+  deriving (Show, Eq, ToJSON, FromJSON, ToHttpApiData, Generic)++-- | Delay in minutes between a comment's creation and its visibility to other users.+newtype Delay = Delay Int+  deriving (Show, Eq, ToJSON, FromJSON, Generic)++-- | Creation date of the user, in Unix Time.+newtype Created = Created Int+  deriving (Show, Eq, ToJSON, FromJSON, Generic)++-- | The user's optional self-description. HTML.+newtype About = About T.Text+  deriving (Show, Eq, ToJSON, FromJSON, Generic)++-- | List of the user's stories, polls and comments.+newtype Submitted = Submitted [Int]+  deriving (Show, Eq, ToJSON, FromJSON, Generic)++-- | The item's unique id.+newtype ItemId = ItemId Int+  deriving (Show, Eq, ToJSON, FromJSON, ToHttpApiData, Generic)++-- | `true` if the item is deleted.+newtype Deleted = Deleted Int+  deriving (Show, Eq, ToJSON, FromJSON, Generic)++-- | The type of item. One of "job", "story", "comment", "poll", or "pollopt"+data ItemType = Job | Story | Comment | Poll | PollOpt+  deriving (Show, Eq, Generic)++instance ToJSON ItemType where+  toJSON = genericToJSON+    defaultOptions { constructorTagModifier = map toLower }++instance FromJSON ItemType where+  parseJSON = genericParseJSON+    defaultOptions { constructorTagModifier = map toLower }++-- | The username of the item's author.+newtype UserName = UserName T.Text+  deriving (Show, Eq, ToJSON, FromJSON, Generic)++-- | The comment, story or poll text. HTML.+newtype ItemText = ItemText T.Text+  deriving (Show, Eq, ToJSON, FromJSON, Generic)++-- | `true` if the item is dead.+newtype Dead = Dead Bool+  deriving (Show, Eq, ToJSON, FromJSON, Generic)++-- | The item's parent. For comments, either another comment or the relevant story.+-- For pollopts, the relevant poll.+newtype Parent = Parent Int+  deriving (Show, Eq, ToJSON, FromJSON, Generic)++-- | Creation date of the item, in Unix Time.+newtype Time = Time Integer+  deriving (Show, Eq, ToJSON, FromJSON, Generic)++-- | The ids of the item's comments, in ranked display order.+newtype Kids = Kids [Int]+  deriving (Show, Eq, ToJSON, FromJSON, Generic)++-- | The URL of the story.+newtype URL = URL T.Text+  deriving (Show, Eq, ToJSON, FromJSON, Generic)++-- | The story's score, or the votes for a pollopt.+newtype Score = Score Int+  deriving (Show, Eq, ToJSON, FromJSON, Generic)++-- | The title of the story, poll or job.+newtype Title = Title T.Text+  deriving (Show, Eq, ToJSON, FromJSON, Generic)++-- | A list of related pollopts, in display order.+newtype Parts = Parts [Int]+  deriving (Show, Eq, ToJSON, FromJSON, Generic)++-- | In the case of stories or polls, the total comment count.+newtype Descendants = Descendants Int+  deriving (Show, Eq, ToJSON, FromJSON, Generic)++-- | Stories, comments, jobs, Ask HNs and even polls are just items.+-- They're identified by their ids, which are unique integers, and live under+-- <https://hacker-news.firebaseio.com/v0/item/>.+data Item = Item {+      itemId          :: Maybe ItemId+    , itemDeleted     :: Maybe Deleted+    , itemType        :: ItemType+    , itemBy          :: Maybe UserName+    , itemTime        :: Maybe Time+    , itemText        :: Maybe ItemText+    , itemDead        :: Maybe Dead+    , itemParent      :: Maybe Parent+    , itemKids        :: Maybe Kids+    , itemURL         :: Maybe URL+    , itemScore       :: Maybe Score+    , itemTitle       :: Maybe Title+    , itemParts       :: Maybe Parts+    , itemDescendants :: Maybe Descendants+    } deriving (Show, Eq, Generic)++instance ToJSON Item where+  toJSON = genericToJSON+    defaultOptions { fieldLabelModifier = map toLower . drop 4 }++instance FromJSON Item where+  parseJSON = genericParseJSON+    defaultOptions { fieldLabelModifier = map toLower . drop 4 }++-- | Error handling for `HackerNewsAPI`+data HackerNewsError+  = NotFound+  | FailureResponseError Int T.Text T.Text+  | HNConnectionError T.Text+  | DecodeFailureError T.Text T.Text+  | InvalidContentTypeHeaderError T.Text T.Text+  | UnsupportedContentTypeError T.Text+  deriving (Show, Eq)
− src/Web/HackerNews/Update.hs
@@ -1,46 +0,0 @@-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings     #-}--- |--- Module      : Web.HackerNews.Update--- Copyright   : (c) David Johnson, Konstantin Zudov, 2014--- Maintainer  : djohnson.m@gmail.com--- Stability   : experimental--- Portability : POSIX-module Web.HackerNews.Update where--import           Control.Applicative     ((<$>), (<*>))-import           Control.Monad           (MonadPlus (mzero))-import           Data.Aeson              (FromJSON (parseJSON), Value (Object),-                                          (.!=), (.:), (.:?))-import           Data.Text               (Text)--import           Web.HackerNews.Endpoint (Endpoint (endpoint))----------------------------------------------------------------------------------- | Update Object-data Update = Update {-    updateItems    :: [Int]-  , updateProfiles :: [Text]-  , updateDeleted  :: Bool-  , updateDead     :: Bool-  } deriving (Show, Eq)----------------------------------------------------------------------------------- | Update ID for an `Updated` Object-data UpdateId = UpdateId deriving (Show, Eq)----------------------------------------------------------------------------------- | Endpoint Instances-instance Endpoint UpdateId Update where-    endpoint _ = "updates"----------------------------------------------------------------------------------- | JSON Instances-instance FromJSON Update where-  parseJSON (Object o) =-     Update <$> o .: "items"-            <*> o .: "profiles"-            <*> o .:? "deleted" .!= False-            <*> o .:? "dead" .!= False-  parseJSON _ = mzero-
− src/Web/HackerNews/User.hs
@@ -1,59 +0,0 @@-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings     #-}--- |--- Module      : Web.HackerNews.User--- Copyright   : (c) David Johnson, Konstantin Zudov, 2014--- Maintainer  : djohnson.m@gmail.com--- Stability   : experimental--- Portability : POSIX-module Web.HackerNews.User where--import           Control.Applicative     ((<$>), (<*>))-import           Control.Monad           (MonadPlus (mzero))-import           Data.Aeson              (FromJSON (parseJSON), Value (Object),-                                          (.!=), (.:), (.:?))-import           Data.Monoid             ((<>))-import           Data.Text               (Text)-import           Data.Time               (UTCTime)--import           Web.HackerNews.Endpoint (Endpoint (endpoint))-import           Web.HackerNews.Util     (fromSeconds, monoidToMaybe)----------------------------------------------------------------------------------- | `User` Object-data User = User {-    userAbout     :: Maybe Text-  , userCreated   :: UTCTime-  , userDelay     :: Int-  , userId        :: UserId-  , userKarma     :: Int-  , userSubmitted :: [Int]-  , userDeleted   :: Bool-  , userDead      :: Bool-  } deriving (Show)----------------------------------------------------------------------------------- | User ID for a `User` Object-newtype UserId-      = UserId Text-      deriving (Show, Eq)----------------------------------------------------------------------------------- | Endpoint instances-instance Endpoint UserId User where-    endpoint (UserId id') = "user/" <> id'----------------------------------------------------------------------------------- | JSON Instances-instance FromJSON User where-  parseJSON (Object o) =-     User <$> ((monoidToMaybe =<<) <$> (o .:? "about"))-          <*> (fromSeconds <$> o .: "created")-          <*> o .: "delay"-          <*> (UserId <$> o .: "id")-          <*> o .: "karma"-          <*> o .: "submitted"-          <*> o .:? "deleted" .!= False-          <*> o .:? "dead" .!= False-  parseJSON _ = mzero-
− src/Web/HackerNews/Util.hs
@@ -1,29 +0,0 @@--- |--- Module      : Web.HackerNews.Util--- Copyright   : (c) David Johnson, Konstantin Zudov, 2014--- Maintainer  : djohnson.m@gmail.com--- Stability   : experimental--- Portability : POSIX-module Web.HackerNews.Util where--import           Data.Monoid           (Monoid, mempty)-import           Data.Text             (Text)-import qualified Data.Text             as T-import           Data.Time             (UTCTime)-import           Data.Time.Clock.POSIX (posixSecondsToUTCTime)----------------------------------------------------------------------------------- | Convert `Integer` to `UTCTime`-fromSeconds :: Integer -> UTCTime-fromSeconds = posixSecondsToUTCTime . fromInteger----------------------------------------------------------------------------------- | Convert `Show` constrained a to `Text`-toText :: Show a => a -> Text-toText = T.pack . show----------------------------------------------------------------------------------- | Turns empty monoids into Nothing--- `listToMaybe` generalized for monoids-monoidToMaybe :: (Eq a, Monoid a) => a -> Maybe a-monoidToMaybe m = if m == mempty then Nothing else Just m
− tests/Test.hs
@@ -1,34 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-module Main where--import           Data.Either    (isRight)-import           Test.Hspec     (it, hspec, describe, shouldSatisfy)-import           Web.HackerNews-import           Control.Applicative--main :: IO ()-main = hspec $ do-  describe "Hacker News API Tests" $ do-    it "Retrieves all" $ do-      result <- hackerNews $ (,,,,,,,,) <$> -        getStory   (StoryId 8863)       <*>-        getComment (CommentId 2921983)  <*>-        getUser    (UserId "dmjio")     <*>-        getJob     (JobId 8437631)      <*>-        getPoll    (PollId 126809)      <*>-        getPollOpt (PollOptId 160705)   <*>-        getTopStories                   <*>-        getMaxItem                      <*>-        getUpdates-      result `shouldSatisfy` isRight-  ----------