diff --git a/lib/mobility-core/mobility-core.cabal b/lib/mobility-core/mobility-core.cabal index 64784f5c0..876d84a14 100644 --- a/lib/mobility-core/mobility-core.cabal +++ b/lib/mobility-core/mobility-core.cabal @@ -275,7 +275,13 @@ library Kernel.External.SOS.Interface Kernel.External.SOS.Interface.ERSS Kernel.External.SOS.Interface.GJ112 + Kernel.External.SOS.Interface.Trinity Kernel.External.SOS.Interface.Types + Kernel.External.SOS.Trinity.API + Kernel.External.SOS.Trinity.Auth + Kernel.External.SOS.Trinity.Config + Kernel.External.SOS.Trinity.Flow + Kernel.External.SOS.Trinity.Types Kernel.External.SOS.Types Kernel.External.Ticket.Interface Kernel.External.Ticket.Interface.Kapture diff --git a/lib/mobility-core/src/Kernel/External/SOS/Interface.hs b/lib/mobility-core/src/Kernel/External/SOS/Interface.hs index 92d038773..3f9d5303b 100644 --- a/lib/mobility-core/src/Kernel/External/SOS/Interface.hs +++ b/lib/mobility-core/src/Kernel/External/SOS/Interface.hs @@ -24,6 +24,7 @@ where import Kernel.External.Encryption import qualified Kernel.External.SOS.Interface.ERSS as ERSS import qualified Kernel.External.SOS.Interface.GJ112 as GJ112 +import qualified Kernel.External.SOS.Interface.Trinity as Trinity import Kernel.External.SOS.Interface.Types as Reexport import Kernel.Prelude import qualified Kernel.Storage.Hedis as Redis @@ -46,6 +47,7 @@ sendInitialSOS :: sendInitialSOS config req = case config of ERSSConfig erssCfg -> ERSS.sendInitialSOS erssCfg req GJ112Config gj112Cfg -> GJ112.sendInitialSOS gj112Cfg req + TrinityConfig trCfg -> Trinity.sendInitialSOS trCfg req -- | Send SOS Trace (location update) - dispatches to appropriate provider sendSOSTrace :: @@ -62,6 +64,7 @@ sendSOSTrace :: sendSOSTrace config req = case config of ERSSConfig erssCfg -> ERSS.sendSOSTrace erssCfg req GJ112Config gj112Cfg -> GJ112.sendSOSTrace gj112Cfg req + TrinityConfig trCfg -> Trinity.sendSOSTrace trCfg req -- | Update SOS Status - dispatches to appropriate provider updateSOSStatus :: @@ -78,6 +81,7 @@ updateSOSStatus :: updateSOSStatus config req = case config of ERSSConfig erssCfg -> ERSS.updateSOSStatus erssCfg req GJ112Config gj112Cfg -> GJ112.updateSOSStatus gj112Cfg req + TrinityConfig trCfg -> Trinity.updateSOSStatus trCfg req -- | Upload Media File - dispatches to appropriate provider uploadMedia :: @@ -96,3 +100,4 @@ uploadMedia :: uploadMedia config phoneNumber fileName filePath = case config of ERSSConfig erssCfg -> ERSS.uploadMedia erssCfg phoneNumber fileName filePath GJ112Config _gj112Cfg -> pure $ SOSMediaUploadRes False (Just "Media upload not implemented for GJ112") + TrinityConfig _trCfg -> pure $ SOSMediaUploadRes False (Just "Media upload not implemented for Trinity") diff --git a/lib/mobility-core/src/Kernel/External/SOS/Interface/Trinity.hs b/lib/mobility-core/src/Kernel/External/SOS/Interface/Trinity.hs new file mode 100644 index 000000000..7de8b9d42 --- /dev/null +++ b/lib/mobility-core/src/Kernel/External/SOS/Interface/Trinity.hs @@ -0,0 +1,127 @@ +{- + Copyright 2022-23, Juspay India Pvt Ltd + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License + + as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is + + distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + + FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero + + General Public License along with this program. If not, see . +-} + +module Kernel.External.SOS.Interface.Trinity + ( sendInitialSOS, + sendSOSTrace, + updateSOSStatus, + ) +where + +import Kernel.External.Encryption +import qualified Kernel.External.SOS.Interface.Types as Interface +import Kernel.External.SOS.Trinity.Config +import qualified Kernel.External.SOS.Trinity.Flow as TRFlow +import qualified Kernel.External.SOS.Trinity.Types as TR +import Kernel.Prelude +import qualified Kernel.Storage.Hedis as Redis +import Kernel.Tools.Metrics.CoreMetrics (CoreMetrics) +import Kernel.Types.Common +import Kernel.Utils.Common (HasRequestId) + +-- | Send Initial SOS - converts interface types to Trinity types and delegates +sendInitialSOS :: + ( EncFlow m r, + CoreMetrics m, + Redis.HedisFlow m r, + MonadFlow m, + HasRequestId r, + MonadReader r m + ) => + TrinityCfg -> + Interface.InitialSOSReq -> + m Interface.InitialSOSRes +sendInitialSOS config req = do + let trReq = toTrinitySOSReq config req + trRes <- TRFlow.sendSOS config trReq + pure $ fromTrinitySOSRes trRes + +-- | Send SOS Trace - Trinity does not support trace, return success +sendSOSTrace :: + ( EncFlow m r, + CoreMetrics m, + Redis.HedisFlow m r, + MonadFlow m, + HasRequestId r, + MonadReader r m + ) => + TrinityCfg -> + Interface.SOSTraceReq -> + m Interface.SOSTraceRes +sendSOSTrace _config _req = + pure $ + Interface.SOSTraceRes + { success = True, + errorMessage = Nothing + } + +-- | Update SOS Status - Trinity does not support status update, return success +updateSOSStatus :: + ( EncFlow m r, + CoreMetrics m, + Redis.HedisFlow m r, + MonadFlow m, + HasRequestId r, + MonadReader r m + ) => + TrinityCfg -> + Interface.SOSStatusUpdateReq -> + m Interface.SOSStatusUpdateRes +updateSOSStatus _config _req = + pure $ + Interface.SOSStatusUpdateRes + { success = True, + errorMessage = Nothing + } + +-- Conversion helpers + +toTrinitySOSReq :: TrinityCfg -> Interface.InitialSOSReq -> TR.TrinitySOSReq +toTrinitySOSReq config Interface.InitialSOSReq {..} = + TR.TrinitySOSReq + { clientId = Just config.clientId, + clientCode = Just config.clientCode, + name = fromMaybe "" senderName, + city = city, + address = address, + deviceUuid = imeiNo, + email = email, + relativeName1 = emergencyContact1Name, + relativeName2 = emergencyContact2Name, + relativeContact1 = emergencyContact1Phone, + relativeContact2 = emergencyContact2Phone, + gender = gender, + simNo = mobileNo, + datetime = Just dateTime, + emergencyMessage = emergencyMessage, + latitude = show latitude, + longitude = show longitude, + videoPath = videoPath, + driverName = driverName, + driverContactNo = driverContactNo, + vehicleNo = vehicleNo, + vehicleModel = vehicleModel, + vehLat = maybe (show latitude) show vehicleLat, + vehLng = maybe (show longitude) show vehicleLon, + deviceType = fromMaybe 6 deviceType, + vehLocUrl = vehicleLocationUrl + } + +fromTrinitySOSRes :: TR.TrinitySOSRes -> Interface.InitialSOSRes +fromTrinitySOSRes res = + Interface.InitialSOSRes + { success = res.status, + trackingId = res.caseId, + errorMessage = if res.status then Nothing else res.message + } diff --git a/lib/mobility-core/src/Kernel/External/SOS/Interface/Types.hs b/lib/mobility-core/src/Kernel/External/SOS/Interface/Types.hs index 8cf3404b0..6abe345e5 100644 --- a/lib/mobility-core/src/Kernel/External/SOS/Interface/Types.hs +++ b/lib/mobility-core/src/Kernel/External/SOS/Interface/Types.hs @@ -33,6 +33,7 @@ import Data.Aeson import Deriving.Aeson import qualified Kernel.External.SOS.ERSS.Config as ERSSConfig import qualified Kernel.External.SOS.GJ112.Config as GJ112Config +import qualified Kernel.External.SOS.Trinity.Config as TrinityConfig import Kernel.Prelude -- | Handler pattern for SOS services @@ -44,6 +45,7 @@ data SOSHandler m = SOSHandler data SOSServiceConfig = ERSSConfig ERSSConfig.ERSSCfg | GJ112Config GJ112Config.GJ112Cfg + | TrinityConfig TrinityConfig.TrinityCfg deriving (Show, Eq, Generic) deriving (FromJSON, ToJSON) via CustomJSON '[SumTaggedObject "tag" "content"] SOSServiceConfig diff --git a/lib/mobility-core/src/Kernel/External/SOS/Trinity/API.hs b/lib/mobility-core/src/Kernel/External/SOS/Trinity/API.hs new file mode 100644 index 000000000..1a2d79f6c --- /dev/null +++ b/lib/mobility-core/src/Kernel/External/SOS/Trinity/API.hs @@ -0,0 +1,45 @@ +{- + Copyright 2022-23, Juspay India Pvt Ltd + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License + + as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is + + distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + + FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero + + General Public License along with this program. If not, see . +-} + +module Kernel.External.SOS.Trinity.API where + +import Kernel.External.SOS.Trinity.Types +import Kernel.Prelude +import Servant + +-- | Authentication API +-- Endpoint: POST /trinityPlatform/1.0.0/getAccessToken +type TrinityAuthAPI = + "trinityPlatform" + :> "1.0.0" + :> "getAccessToken" + :> ReqBody '[JSON] TrinityAuthReq + :> Post '[JSON] TrinityAuthRes + +-- | SOS Trigger API +-- Endpoint: POST /ngcadIntService/1.0.0/externalIntegration/triggerSOSMessage +type TrinitySOSAPI = + "ngcadIntService" + :> "1.0.0" + :> "externalIntegration" + :> "triggerSOSMessage" + :> Header "Authorization" TrinityAuthToken + :> ReqBody '[JSON] TrinitySOSReq + :> Post '[JSON] TrinitySOSRes + +trinityAuthAPI :: Proxy TrinityAuthAPI +trinityAuthAPI = Proxy + +trinitySOSAPI :: Proxy TrinitySOSAPI +trinitySOSAPI = Proxy diff --git a/lib/mobility-core/src/Kernel/External/SOS/Trinity/Auth.hs b/lib/mobility-core/src/Kernel/External/SOS/Trinity/Auth.hs new file mode 100644 index 000000000..6c82fc117 --- /dev/null +++ b/lib/mobility-core/src/Kernel/External/SOS/Trinity/Auth.hs @@ -0,0 +1,121 @@ +{- + Copyright 2022-23, Juspay India Pvt Ltd + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License + + as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is + + distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + + FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero + + General Public License along with this program. If not, see . +-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeApplications #-} + +module Kernel.External.SOS.Trinity.Auth + ( getTrinityToken, + TrinityToken (..), + redisTrinityKey, + ) +where + +import qualified Data.Time.Clock as Time +import qualified EulerHS.Types as ET +import Kernel.External.Encryption +import Kernel.External.SOS.Trinity.API +import Kernel.External.SOS.Trinity.Config +import Kernel.External.SOS.Trinity.Types +import Kernel.Prelude +import qualified Kernel.Storage.Hedis as Redis +import Kernel.Tools.Metrics.CoreMetrics (CoreMetrics) +import Kernel.Types.Common +import Kernel.Utils.Common + +-- | Cached token with expiry timestamp +data TrinityToken = TrinityToken + { token :: Text, + expiresAtUTC :: UTCTime + } + deriving (Show, Eq, Generic, ToJSON, FromJSON) + +redisTrinityKey :: Text +redisTrinityKey = "Core:trinity_token" + +-- | Get valid Trinity token (check cache, validate, get new if needed) +getTrinityToken :: + ( EncFlow m r, + CoreMetrics m, + Redis.HedisFlow m r, + MonadFlow m, + HasRequestId r, + MonadReader r m + ) => + TrinityCfg -> + m TrinityToken +getTrinityToken config = do + let redisKey = config.tokenKeyPrefix <> ":" <> redisTrinityKey + cached <- Redis.get redisKey + now <- liftIO Time.getCurrentTime + case cached of + Nothing -> getNewToken config + Just cachedToken -> + if now < cachedToken.expiresAtUTC + then pure cachedToken + else getNewToken config + +-- | Get new token using authorizationKey + credentials +getNewToken :: + ( EncFlow m r, + CoreMetrics m, + Redis.HedisFlow m r, + MonadFlow m, + HasRequestId r, + MonadReader r m + ) => + TrinityCfg -> + m TrinityToken +getNewToken config = do + authKeyDecrypted <- decrypt config.authorizationKey + usernameDecrypted <- decrypt config.username + passwordDecrypted <- decrypt config.password + + let req = + TrinityAuthReq + { authorizationKey = authKeyDecrypted, + username = usernameDecrypted, + password = passwordDecrypted, + grantType = "password", + scope = "default", + apim = "subscribe" + } + + res <- + callTrinityAuthAPI + config.baseUrl + (ET.client trinityAuthAPI req) + "trinityAuth" + trinityAuthAPI + + now <- liftIO Time.getCurrentTime + let expiresIn = fromMaybe 3600 res.expires_in + bufferedExpiry = Time.addUTCTime (fromIntegral expiresIn - 60) now + + let trinityToken = + TrinityToken + { token = res.access_token, + expiresAtUTC = bufferedExpiry + } + + let redisKey = config.tokenKeyPrefix <> ":" <> redisTrinityKey + Redis.set redisKey trinityToken + pure trinityToken + +callTrinityAuthAPI :: CallAPI m r api a +callTrinityAuthAPI = + callApiUnwrappingApiError + (identity @TrinityError) + Nothing + (Just "TRINITY_AUTH_ERROR") + Nothing diff --git a/lib/mobility-core/src/Kernel/External/SOS/Trinity/Config.hs b/lib/mobility-core/src/Kernel/External/SOS/Trinity/Config.hs new file mode 100644 index 000000000..b23ff123c --- /dev/null +++ b/lib/mobility-core/src/Kernel/External/SOS/Trinity/Config.hs @@ -0,0 +1,31 @@ +{- + Copyright 2022-23, Juspay India Pvt Ltd + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License + + as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is + + distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + + FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero + + General Public License along with this program. If not, see . +-} + +module Kernel.External.SOS.Trinity.Config where + +import Kernel.External.Encryption +import Kernel.Prelude + +-- | Configuration for Trinity (Bangalore Safe City) +data TrinityCfg = TrinityCfg + { baseUrl :: BaseUrl, + sosUrl :: BaseUrl, + authorizationKey :: EncryptedField 'AsEncrypted Text, + username :: EncryptedField 'AsEncrypted Text, + password :: EncryptedField 'AsEncrypted Text, + clientId :: Text, + clientCode :: Text, + tokenKeyPrefix :: Text + } + deriving (Show, Eq, Generic, ToJSON, FromJSON) diff --git a/lib/mobility-core/src/Kernel/External/SOS/Trinity/Flow.hs b/lib/mobility-core/src/Kernel/External/SOS/Trinity/Flow.hs new file mode 100644 index 000000000..8e2298140 --- /dev/null +++ b/lib/mobility-core/src/Kernel/External/SOS/Trinity/Flow.hs @@ -0,0 +1,76 @@ +{- + Copyright 2022-23, Juspay India Pvt Ltd + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License + + as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is + + distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + + FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero + + General Public License along with this program. If not, see . +-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeApplications #-} + +module Kernel.External.SOS.Trinity.Flow + ( sendSOS, + validateTrinityResponse, + ) +where + +import qualified EulerHS.Types as ET +import Kernel.External.Encryption +import Kernel.External.SOS.Trinity.API +import Kernel.External.SOS.Trinity.Auth +import Kernel.External.SOS.Trinity.Config +import Kernel.External.SOS.Trinity.Types +import Kernel.Prelude +import qualified Kernel.Storage.Hedis as Redis +import Kernel.Tools.Metrics.CoreMetrics (CoreMetrics) +import Kernel.Types.Common +import Kernel.Utils.Common + +-- | Validate Trinity API response. +-- Trinity may return HTTP 200 with status=false in the body. +validateTrinityResponse :: + (MonadFlow m) => + TrinitySOSRes -> + m TrinitySOSRes +validateTrinityResponse res + | isTrinitySuccess res = pure res + | otherwise = do + let errMsg = fromMaybe "Unknown error" res.message + throwM $ TrinityOperationFailure errMsg + +-- | Send SOS Event to Trinity +sendSOS :: + ( EncFlow m r, + CoreMetrics m, + Redis.HedisFlow m r, + MonadFlow m, + HasRequestId r, + MonadReader r m + ) => + TrinityCfg -> + TrinitySOSReq -> + m TrinitySOSRes +sendSOS config req = do + trinityToken <- getTrinityToken config + let authToken = TrinityAuthToken trinityToken.token + res <- + callTrinityAPI + config.sosUrl + (ET.client trinitySOSAPI (Just authToken) req) + "trinitySOSTrigger" + trinitySOSAPI + validateTrinityResponse res + +callTrinityAPI :: CallAPI m r api a +callTrinityAPI = + callApiUnwrappingApiError + (identity @TrinityError) + Nothing + (Just "TRINITY_API_ERROR") + Nothing diff --git a/lib/mobility-core/src/Kernel/External/SOS/Trinity/Types.hs b/lib/mobility-core/src/Kernel/External/SOS/Trinity/Types.hs new file mode 100644 index 000000000..4f7a8a8fc --- /dev/null +++ b/lib/mobility-core/src/Kernel/External/SOS/Trinity/Types.hs @@ -0,0 +1,273 @@ +{- + Copyright 2022-23, Juspay India Pvt Ltd + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License + + as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is + + distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + + FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero + + General Public License along with this program. If not, see . +-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedLists #-} +{-# LANGUAGE TemplateHaskell #-} + +module Kernel.External.SOS.Trinity.Types where + +import Control.Applicative ((<|>)) +import Data.Aeson +import Kernel.Prelude +import Kernel.Types.Error.BaseError +import Kernel.Types.Error.BaseError.HTTPError +import Kernel.Types.Error.BaseError.HTTPError.FromResponse (FromResponse (fromResponse)) +import Servant.Client (ResponseF (responseBody)) +import Web.Internal.HttpApiData (ToHttpApiData (..)) + +-- | Authentication request +data TrinityAuthReq = TrinityAuthReq + { authorizationKey :: Text, + username :: Text, + password :: Text, + grantType :: Text, + scope :: Text, + apim :: Text + } + deriving (Show, Eq, Generic, ToJSON, FromJSON) + +-- | Authentication response +data TrinityAuthRes = TrinityAuthRes + { access_token :: Text, + refresh_token :: Maybe Text, + scope :: Maybe Text, + token_type :: Maybe Text, + expires_in :: Maybe Int + } + deriving (Show, Eq, Generic, FromJSON, ToJSON) + +-- | SOS Trigger request with mixed-case JSON keys matching the Trinity API spec +data TrinitySOSReq = TrinitySOSReq + { clientId :: Maybe Text, + clientCode :: Maybe Text, + name :: Text, + city :: Maybe Text, + address :: Maybe Text, + deviceUuid :: Maybe Text, + email :: Maybe Text, + relativeName1 :: Maybe Text, + relativeName2 :: Maybe Text, + relativeContact1 :: Maybe Text, + relativeContact2 :: Maybe Text, + gender :: Maybe Text, + simNo :: Text, + datetime :: Maybe Text, + emergencyMessage :: Maybe Text, + latitude :: Text, + longitude :: Text, + videoPath :: Maybe Text, + driverName :: Maybe Text, + driverContactNo :: Maybe Text, + vehicleNo :: Maybe Text, + vehicleModel :: Maybe Text, + vehLat :: Text, + vehLng :: Text, + deviceType :: Int, + vehLocUrl :: Maybe Text + } + deriving (Show, Eq, Generic, ToSchema) + +instance ToJSON TrinitySOSReq where + toJSON TrinitySOSReq {..} = + object $ + filter + ((/= Null) . snd) + [ "client_id" .= clientId, + "client_code" .= clientCode, + "Name" .= name, + "City" .= city, + "Address" .= address, + "Device_UUID" .= deviceUuid, + "Email" .= email, + "Relative_Name1" .= relativeName1, + "Relative_Name2" .= relativeName2, + "Relative_Contact1" .= relativeContact1, + "Relative_Contact2" .= relativeContact2, + "Gender" .= gender, + "SIM_No" .= simNo, + "Datetime" .= datetime, + "Emergency_Message" .= emergencyMessage, + "Latitude" .= latitude, + "Longitude" .= longitude, + "VideoPath" .= videoPath, + "Driver_name" .= driverName, + "Driver_ContactNo" .= driverContactNo, + "Vehicle_No" .= vehicleNo, + "Vehicle_Model" .= vehicleModel, + "Veh_lat" .= vehLat, + "Veh_lng" .= vehLng, + "deviceType" .= deviceType, + "Veh_loc_url" .= vehLocUrl + ] + +instance FromJSON TrinitySOSReq where + parseJSON = withObject "TrinitySOSReq" $ \o -> + TrinitySOSReq + <$> o .:? "client_id" + <*> o .:? "client_code" + <*> o .: "Name" + <*> o .:? "City" + <*> o .:? "Address" + <*> o .:? "Device_UUID" + <*> o .:? "Email" + <*> o .:? "Relative_Name1" + <*> o .:? "Relative_Name2" + <*> o .:? "Relative_Contact1" + <*> o .:? "Relative_Contact2" + <*> o .:? "Gender" + <*> o .: "SIM_No" + <*> o .:? "Datetime" + <*> o .:? "Emergency_Message" + <*> o .: "Latitude" + <*> o .: "Longitude" + <*> o .:? "VideoPath" + <*> o .:? "Driver_name" + <*> o .:? "Driver_ContactNo" + <*> o .:? "Vehicle_No" + <*> o .:? "Vehicle_Model" + <*> o .: "Veh_lat" + <*> o .: "Veh_lng" + <*> o .: "deviceType" + <*> o .:? "Veh_loc_url" + +-- | SOS Trigger success response +data TrinitySOSRes = TrinitySOSRes + { caseId :: Maybe Text, + message :: Maybe Text, + status :: Bool + } + deriving (Show, Eq, Generic, FromJSON, ToJSON, ToSchema) + +isTrinitySuccess :: TrinitySOSRes -> Bool +isTrinitySuccess = (.status) + +-- | Trinity API Error types +data TrinityError + = TrinityOperationFailure Text + | TrinityAuthError Text + | TrinityUnknownError Text + deriving (Eq, Show, IsBecknAPIError) + +instanceExceptionWithParent 'HTTPException ''TrinityError + +instance IsBaseError TrinityError where + toMessage = \case + TrinityOperationFailure msg -> Just $ "Trinity Operation Failed: " <> msg + TrinityAuthError msg -> Just $ "Trinity Auth Error: " <> msg + TrinityUnknownError msg -> Just $ "Trinity Error: " <> msg + +instance IsHTTPError TrinityError where + toErrorCode = \case + TrinityOperationFailure _ -> "TRINITY_OPERATION_FAILURE" + TrinityAuthError _ -> "TRINITY_AUTH_ERROR" + TrinityUnknownError _ -> "TRINITY_UNKNOWN_ERROR" + + toHttpCode = \case + TrinityOperationFailure _ -> E500 + TrinityAuthError _ -> E401 + TrinityUnknownError _ -> E500 + +instance IsAPIError TrinityError + +-- | Generic container for various possible error fields Trinity might send. +data TrinityGenericError = TrinityGenericError + { message :: Maybe Text, + errorValue :: Maybe Text, -- JSON key: "error" (avoids clash with Prelude.error) + error_description :: Maybe Text, + errorMessage :: Maybe Text, + msg :: Maybe Text, + detail :: Maybe Text, + errorMessageCap :: Maybe Text -- JSON key: "ERROR_MESSAGE" + } + deriving (Show, Eq, Generic) + +instance FromJSON TrinityGenericError where + parseJSON = withObject "TrinityGenericError" $ \o -> + TrinityGenericError + <$> o .:? "message" + <*> o .:? "error" + <*> o .:? "error_description" + <*> o .:? "errorMessage" + <*> o .:? "msg" + <*> o .:? "detail" + <*> o .:? "ERROR_MESSAGE" + +firstJust :: [Maybe a] -> Maybe a +firstJust = \case + [] -> Nothing + x : xs -> case x of + Just v -> Just v + Nothing -> firstJust xs + +instance FromResponse TrinityError where + fromResponse resp = do + let body = responseBody resp + case (decode body :: Maybe TrinityError) of + Just err -> Just err + Nothing -> + case (decode body :: Maybe TrinityGenericError) of + Just g -> + let msgText = + firstJust + [ g.message, + g.error_description, + g.errorValue, + g.errorMessage, + g.msg, + g.detail, + g.errorMessageCap + ] + in Just $ TrinityUnknownError (fromMaybe "Unknown error response" msgText) + Nothing -> + Just $ TrinityUnknownError "Failed to parse Trinity error response (non-JSON or empty)" + +instance FromJSON TrinityError where + parseJSON = withObject "TrinityError" $ \o -> do + mStatus <- o .:? "status" + mMessage <- o .:? "message" + mError <- o .:? "Error" + mErrorDesc <- o .:? "error_description" + mErrorCode <- o .:? "error" + case (mStatus :: Maybe Bool) of + Just False -> + case (mError :: Maybe Object) of + Just errObj -> do + errMsg <- errObj .:? "ERROR_MESSAGE" + pure $ TrinityAuthError (fromMaybe (fromMaybe "Unknown auth error" mMessage) errMsg) + Nothing -> + pure $ TrinityOperationFailure (fromMaybe "Unknown error" mMessage) + _ -> + pure $ + TrinityUnknownError $ + fromMaybe "Unknown error" $ + mMessage <|> mErrorDesc <|> (mErrorCode >>= \c -> Just $ "error: " <> c) + +instance ToJSON TrinityError where + toJSON err = + object ["message" .= toErrMsg err, "status" .= False] + where + toErrMsg = \case + TrinityOperationFailure msg -> msg + TrinityAuthError msg -> msg + TrinityUnknownError msg -> msg + +-- | Bearer token for Authorization header +newtype TrinityAuthToken = TrinityAuthToken Text + deriving (Show, Eq, Generic) + +instance ToHttpApiData TrinityAuthToken where + toUrlPiece (TrinityAuthToken token) = "Bearer " <> token diff --git a/lib/mobility-core/src/Kernel/External/SOS/Types.hs b/lib/mobility-core/src/Kernel/External/SOS/Types.hs index c5cee9600..894b54b0f 100644 --- a/lib/mobility-core/src/Kernel/External/SOS/Types.hs +++ b/lib/mobility-core/src/Kernel/External/SOS/Types.hs @@ -28,8 +28,10 @@ data SOSService ERSS | -- | Gujarat 112 SOS GJ112 + | -- | Trinity (Bangalore Safe City) + Trinity deriving (Show, Read, Eq, Ord, Generic, ToJSON, FromJSON) -- | List of all available SOS services availableSOSServices :: [SOSService] -availableSOSServices = [ERSS, GJ112] +availableSOSServices = [ERSS, GJ112, Trinity]