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]