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 +71/−39
- ghc-examples/Example.hs +21/−0
- ghc-src/Web/HackerNews.hs +215/−0
- ghc-tests/Test.hs +111/−0
- ghcjs-examples/Example.hs +4/−0
- ghcjs-src/Web/HackerNews.hs +123/−0
- ghcjs-tests/Test.hs +110/−0
- hackernews.cabal +82/−53
- jsbits/options.js +30/−0
- src/Web/HackerNews.hs +0/−96
- src/Web/HackerNews/Client.hs +0/−163
- src/Web/HackerNews/Comment.hs +0/−62
- src/Web/HackerNews/Endpoint.hs +0/−32
- src/Web/HackerNews/Item.hs +0/−72
- src/Web/HackerNews/Job.hs +0/−62
- src/Web/HackerNews/Person.hs +0/−54
- src/Web/HackerNews/Poll.hs +0/−103
- src/Web/HackerNews/Story.hs +0/−80
- src/Web/HackerNews/Types.hs +205/−12
- src/Web/HackerNews/Update.hs +0/−46
- src/Web/HackerNews/User.hs +0/−59
- src/Web/HackerNews/Util.hs +0/−29
- tests/Test.hs +0/−34
README.md view
@@ -1,10 +1,10 @@-hackernews +hackernews ==========   --++[](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- ----------