fb-2.0.0: src/Facebook/TestUsers.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings #-}
module Facebook.TestUsers
( TestUser(..)
, CreateTestUser(..)
, CreateTestUserInstalled(..)
, getTestUsers
, disassociateTestuser
, removeTestUser
, createTestUser
, makeFriendConn
, incompleteTestUserAccessToken
) where
import Control.Applicative ((<$>), (<*>))
import Control.Monad (unless, mzero)
import Control.Monad.IO.Class
import Data.ByteString.Lazy (fromStrict)
import Data.Default
import Data.Text
import Data.Text.Encoding (encodeUtf8)
import Data.Time (UTCTime(..), Day(..))
import Data.Typeable (Typeable)
import Data.Aeson
import Data.Aeson.Types
import qualified UnliftIO.Exception as E
import qualified Control.Monad.Trans.Resource as R
import qualified Data.Aeson as A
import qualified Data.ByteString.Char8 as B
import Facebook.Auth
import Facebook.Base
import Facebook.Graph
import Facebook.Monad
import Facebook.Types
import Facebook.Pager
-- | A Facebook test user.
-- Ref: https://developers.facebook.com/docs/graph-api/reference/v2.8/app/accounts/test-users
data TestUser = TestUser
{ tuId :: UserId
, tuAccessToken :: Maybe AccessTokenData
, tuLoginUrl :: Maybe Text
, tuEmail :: Maybe Text
, tuPassword :: Maybe Text
} deriving (Eq, Ord, Show, Read, Typeable)
instance A.FromJSON TestUser where
parseJSON (A.Object v) =
TestUser <$> v A..: "id" <*> v A..:? "access_token" <*> v A..:? "login_url" <*>
v A..:? "email" <*>
v A..:? "password"
parseJSON _ = mzero
-- | Data type used to hold information of a new test user. This type
-- also accepts a Data.Default value.
data CreateTestUser = CreateTestUser
{ ctuInstalled :: CreateTestUserInstalled
, ctuName :: Maybe Text
, ctuLocale :: Maybe Text
}
-- | Specify if the app is to be installed on the new test user. If
-- it is, then you must tell what permissions should be given.
data CreateTestUserInstalled
= CreateTestUserNotInstalled
| CreateTestUserInstalled { ctuiPermissions :: [Permission]}
| CreateTestUserFbDefault -- ^ Uses Facebook's default. It seems that this is equivalent to
-- @CreateTestUserInstalled []@, but Facebook's documentation is
-- not clear about it.
-- | Default instance for 'CreateTestUser'.
instance Default CreateTestUser where
def = CreateTestUser def def def
-- | Default instance for 'CreateTestUserInstalled'.
instance Default CreateTestUserInstalled where
def = CreateTestUserFbDefault
-- | Construct a query from a 'CreateTestUser'.
createTestUserQueryArgs :: CreateTestUser -> [Argument]
createTestUserQueryArgs (CreateTestUser installed name locale) =
forInst installed ++ forField "name" name ++ forField "locale" locale
where
forInst (CreateTestUserInstalled p) =
["installed" #= True, "permissions" #= p]
forInst CreateTestUserNotInstalled = ["installed" #= False]
forInst CreateTestUserFbDefault = []
forField _ Nothing = []
forField fieldName (Just f) = [fieldName #= f]
-- | Create a new test user.
-- Ref: https://developers.facebook.com/docs/graph-api/reference/v2.8/app/accounts/test-users#publish
createTestUser
:: (R.MonadResource m, R.MonadUnliftIO m, R.MonadThrow m, MonadIO m)
=> CreateTestUser -- ^ How the test user should be
-- created.
-> AppAccessToken -- ^ Access token for your app.
-> FacebookT Auth m TestUser
createTestUser userInfo token = do
creds <- getCreds
let query = ("method", "post") : createTestUserQueryArgs userInfo
getObject ("/" <> appId creds <> "/accounts/test-users") query (Just token)
-- | Get a list of test users.
getTestUsers
:: (R.MonadResource m, R.MonadUnliftIO m, R.MonadThrow m, MonadIO m)
=> AppAccessToken -- ^ Access token for your app.
-> FacebookT Auth m (Pager TestUser)
getTestUsers token = do
creds <- getCreds
getObject ("/" <> appId creds <> "/accounts/test-users") [] (Just token)
disassociateTestuser
:: (R.MonadUnliftIO m, R.MonadThrow m, R.MonadResource m, MonadIO m)
=> TestUser -> AppAccessToken -> FacebookT Auth m Bool
disassociateTestuser testUser _token = do
creds <- getCreds
getObjectBool
("/" <> (appId creds) <> "/accounts/test-users")
[("uid", encodeUtf8 $ idCode $ tuId testUser), ("method", "delete")]
(Just _token)
-- | Remove an existing test user.
removeTestUser
:: (R.MonadResource m, R.MonadUnliftIO m, R.MonadThrow m)
=> TestUser -- ^ The TestUser to be removed.
-> AppAccessToken -- ^ Access token for your app (ignored since fb 0.14.7).
-> FacebookT Auth m Bool
removeTestUser testUser _token = do
getObjectBool
("/" <> (idCode $ tuId testUser))
[("method", "delete")]
(Just _token)
-- | Make a friend connection between two test users.
--
-- This is how Facebook's API work: two calls must be made. The first
-- call has the format: \"\/userA_id\/friends\/userB_id\" with the
-- access token of user A as query parameter. The second call has the
-- format: \"\/userB_id\/friends\/userA_id\" with the access token of
-- user B as query parameter. The first call creates a friend request
-- and the second call accepts the friend request.
makeFriendConn
:: (R.MonadResource m, R.MonadUnliftIO m, R.MonadThrow m)
=> TestUser -> TestUser -> FacebookT Auth m ()
makeFriendConn (TestUser {tuAccessToken = Nothing}) _ =
E.throwIO $
FbLibraryException
"The test user passed on the first argument doesn't have a token. Both users must have a token."
makeFriendConn _ (TestUser {tuAccessToken = Nothing}) =
E.throwIO $
FbLibraryException
"The test user passed on the second argument doesn't have a token. Both users must have a token."
makeFriendConn (TestUser {tuId = id1
,tuAccessToken = (Just token1)}) (TestUser {tuId = id2
,tuAccessToken = (Just token2)}) = do
let friendReq userId1 userId2 token =
getObjectBool
("/" <> idCode userId1 <> "/friends/" <> idCode userId2)
["method" #= ("post" :: B.ByteString), "access_token" #= token]
Nothing
r1 <- friendReq id1 id2 token1
r2 <- friendReq id2 id1 token2
unless r1 $ E.throwIO $ FbLibraryException "Couldn't make friend request."
unless r2 $ E.throwIO $ FbLibraryException "Couldn't accept friend request."
return ()
-- | Create an 'UserAccessToken' from a 'TestUser'. It's incomplete
-- because it will not have the right expiration time.
incompleteTestUserAccessToken :: TestUser -> Maybe UserAccessToken
incompleteTestUserAccessToken t = do
tokenData <- tuAccessToken t
let farFuture = UTCTime (ModifiedJulianDay 100000) 0
return (UserAccessToken (tuId t) tokenData farFuture)
-- | Same as 'getObject', but instead of parsing the result
-- as a JSON, it tries to parse either as "true" or "false".
-- Used only by the Test User API bindings.
getObjectBool
:: (R.MonadResource m, R.MonadUnliftIO m, R.MonadThrow m)
=> Text
-- ^ Path (should begin with a slash @\/@).
-> [Argument]
-- ^ Arguments to be passed to Facebook.
-> Maybe (AccessToken anyKind)
-- ^ Optional access token.
-> FacebookT anyAuth m Bool
getObjectBool path query mtoken =
runResourceInFb $
do req <- fbreq path mtoken query
response <- fbhttp req
bs <- asBS response
let respJson :: Maybe Value = decode (fromStrict bs)
maybe
(return False)
(\val -> maybe (return False) return (parseMaybe isTrue val))
respJson
where
isTrue :: Value -> Parser Bool
isTrue val =
withObject
"success"
(\obj -> do
(status :: Bool) <- obj .: "success"
return status)
val