V54JCKJX4WL5UGJBCX7VR5O6QKABGUHPLYB4MD2NQQW45OFH5OBAC
H2ABVZI2NFTERQMJ2Z7WGMRNORV3OQQWCCFEN6YO5GAUT2ONM2MAC
GCVQD44VRPQVKPZEPIC4AOIXLJIG2ZMV3QI2Y7KALUT6NVUBSGSAC
GMYPBCWEB6NKURRILAHR3TJUKDOGR2ZMK5I6MS6P5G2LAGH36P3QC
64C6AWH66FDKU6UE6Z6JPX2J2GBM2JOPTH2GL6LHKAIUBGNGDZ5AC
NJNMO72S7VIUV22JXB4IFPZMHWTJAOTP6EC6Z4QSKYIROSXT52MQC
PPW6ROC5U7FZCJCH2RX7UJ3PJYNPUMNEZ6KKO3375VFRUM4VT3VQC
QH4UB73NUR2XPHZQ2RGJBKKUBN43RKC7ZJBCFPP4ESUIIEDDR5XQC
JXG3FCXYBDKMUD77DOM7RCIJYKB7BILC43OHHDZBE7YQRGAMUCCAC
KET5QGQPM5STWGRDL72HTZ5T57QRKQQ3L564PST2PNG4YJHKATSAC
ANDJ6GEY2IRDNKPVXESYEZKU24BAXFB5PPSZFIJRMBGL57A622FQC
27H4DECZW4CEDSV5XYJQA5HOMUW73K5G2DBQNLQB7AFZXXVXCFCAC
KKJSBWO6RNORAPTJPCCUJJNVI2OYTGLQKB3XJGOASH43GNTJBMKAC
VTZT2ILU7VWP5EY4526HU72Z5HZB6VRVQIVJJTB6Q5NL2AUFZRSAC
3PFXXJTLLGDWIFVI32VDUSVGGQL73F6KBACLD2GGJO2AAIS4VPJAC
NAFJ6RB3KYDBSTSNB3WQSVUQEPUGG2RZCBWRF4XNT2UKSOXDNMDQC
3HTCTHHULQUAHAQFUKDIFO3S7FVVFXMAQLLS3T44MLHGDIT5DZGAC
YBLHJFCNW52TJ37UIHPZ6UD22SQVGG27SP5UQR7YAIJ7F7SYJZSAC
4GOBY5NQYPISPYKVN74SM7JYWV7PALUDWWGVXWRHW2J2CPPMC42QC
T2DN23M7W53UMRV46SKDP6UDMCZB7VG2J772LXKMAJNL6NA62MKAC
N6FG4EW6QU7V6QV7UHHYRA3EDKPGVCAEAT7IS3QI45N3GRRV2V7AC
QU5FW67RGCWOWT2YFM4NYMJFFHWIRPQANQBBAHBKZUY7UYMCSIMQC
OUR4PAOTXXKXQPMAR5TIYX7MBRRJS2WVTZS7SN4SOGML7SPJIJGQC
IPG33FAWXGEQ2PO6OXRT2PWWXHRNMPVUKKADL6UKKN5GD2CNZ25AC
X3ES7NUA42D2BF7CQDDKXM5CLMVCYA3H5YU5KXLPTGDBFPE2LNVAC
M4PWY5RUV72AEDCNC4O7UKBPHBIACR4354YTSC3SUZGWFV5UBJBQC
U256ZALIPTVWLNACYPIMWLNEYDQWP7CHF4Y4CGMILQTONJHMGQVQC
QMRKFEPGFBCEWAIXPEIG5ILKAJ2JH5L3TOITHR4HNJXK5FN3KXBQC
U7YAT2ZK6GMS7KVFFEQTDRFX6GIN7HVHNWGKIFDGJGE2G2IXSF6QC
DFOBMSAODB3NKW37B272ZXA2ML5HMIH3N3C4GT2DPEQS7ZFK4SNAC
BROSTG5KP3NUNLSYPVQID254TE47E5RKQAKLPIY7PGWETE6JNMTAC
2XQD6KKKD6QVHFHAEMVE3XXY7X2T7BLTLL7XIILZAXNJJH2YONUQC
import Data.Traversable (traverse, sequence)
import Data.UUID (UUID, parseUUID)
import Data.Traversable (class Traversable, traverse, sequence)
import Data.UUID (UUID, parseUUID, toString)
createBillable :: ProjectId -> Billable -> Aff (Either APIError BillableId)
createBillable pid billable = do
let body = RB.json $ billableJSON billable
response <- post RF.json ("/api/projects/" <> pidStr pid <> "/billables") (Just body)
parseResponse decodeJson response
listProjectBillables :: ProjectId -> Aff (Either APIError (Array (Tuple BillableId Billable)))
listProjectBillables pid = do
response <- get RF.json ("/api/projects/" <> pidStr pid <> "/billables")
parseResponse (traverse parseBillableJSON <=< decodeJson) response
instance paymentRequestFoldable :: Foldable PaymentRequest' where
foldr f b (PaymentRequest r) =
f r.expires_at b
foldl f b (PaymentRequest r) =
f b r.expires_at
foldMap = foldMapDefaultR
instance paymentRequestTraversable :: Traversable PaymentRequest' where
traverse f (PaymentRequest r) =
map (\b -> PaymentRequest (r { expires_at = b })) (f r.expires_at)
sequence = traverse identity
createBillable :: ProjectId -> Billable -> Aff (Either APIError BillableId)
createBillable pid billable = do
let body = RB.json $ billableJSON billable
response <- post RF.json ("/api/projects/" <> pidStr pid <> "/billables") (Just body)
parseResponse decodeJson response
type PaymentRequestMeta =
{ requestName :: String
, requestDesc :: Maybe String
}
listProjectBillables :: ProjectId -> Aff (Either APIError (Array (Tuple BillableId Billable)))
listProjectBillables pid = do
response <- get RF.json ("/api/projects/" <> pidStr pid <> "/billables")
parseResponse (traverse parseBillableJSON <=< decodeJson) response
decodePaymentRequest :: Json -> Either JsonDecodeError (PaymentRequest' String)
decodePaymentRequest json = do
obj <- decodeJson json
payment_request_id <- obj .: "payment_request_id"
native_request <- obj .: "native_request"
expires_at <- obj .: "expires_at"
total <- parseZatoshi =<< (obj .: "total")
pure $ PaymentRequest { payment_request_id, native_request, expires_at, total }
listUnpaidPaymentRequests :: BillableId -> Aff (Either APIError (Array (Tuple PaymentRequestId PaymentRequest)))
createPaymentRequest ::
ProjectId ->
BillableId ->
PaymentRequestMeta ->
Aff (Either APIError PaymentRequest)
createPaymentRequest pid bid m = do
let body = RB.json (encodeJson m)
uri = "/api/projects/" <> pidStr pid <> "/billables/" <> billableIdStr bid <> "/paymentRequests"
response <- post RF.json uri (Just body)
liftEffect
<<< runExceptT
<<< map (map toDateTime)
$ parseDatedResponse decodePaymentRequest response
listUnpaidPaymentRequests ::
BillableId ->
Aff (Either APIError (Array (Tuple PaymentRequestId PaymentRequest)))
parseZatoshi :: Object Json -> Either JsonDecodeError Zatoshi
parseZatoshi obj =
map Zatoshi
$ (note (TypeMismatch "Failed to decode as Zatoshi") <<< BigInt.fromNumber)
=<< (obj .: "zatoshi")
import Data.Int as Int
import Data.Maybe (Maybe(..), maybe)
import Data.Newtype (unwrap)
import Data.Number (fromString) as Number
import Data.Number.Format (toString) as Number
-- import Data.Unfoldable as U
import Data.Maybe (Maybe(..))
import Data.Tuple (Tuple(..))
]
, formGroup st
[ DescField ]
[ HH.label
[ P.for "billableDesc"]
[ HH.text "Product Description" ]
, HH.input
[ P.type_ P.InputText
, P.classes [ C.formControlSm ]
, P.id_ "billableDesc"
, P.placeholder "Description of the product or service"
, E.onValueInput (Just <<< SetDesc)
]
, P.id_ "billableMsg"
, P.placeholder "Enter your message here"
, E.onValueInput (Just <<< SetMessage)
]
]
, formGroup st
[MonthlyRecurrenceField, WeeklyRecurrenceField]
[ HH.label_
[ HH.input
([ P.type_ P.InputRadio
, P.name "recurType"
, E.onClick \_ -> Just (SetRecurrenceType RTAnnual)
] <> (if st.recurrenceType == RTAnnual then [P.checked true] else []))
, HH.text " Annual"
]
, HH.label_
[ HH.input
([ P.type_ P.InputRadio
, P.name "recurType"
, E.onClick \_ -> Just (SetRecurrenceType RTMonthly)
] <> (if st.recurrenceType == RTMonthly then [P.checked true] else []))
, HH.text " every "
, HH.input
[ P.type_ P.InputNumber
, P.classes [ C.formControlSm ]
, P.value (if st.recurrenceType == RTMonthly
then maybe "" show st.recurrenceValue
else "")
, P.min 1.0
, P.max 12.0
, E.onValueInput (Just <<< SetRecurrenceMonths)
]
, HH.text " Months"
]
, HH.label_
[ HH.input
([ P.type_ P.InputRadio
, P.name "recurType"
, E.onClick \_ -> Just (SetRecurrenceType RTWeekly)
] <> (if st.recurrenceType == RTWeekly then [P.checked true] else []))
, HH.text " every "
, HH.input
[ P.type_ P.InputNumber
, P.classes [ C.formControlSm ]
, P.value (if st.recurrenceType == RTWeekly
then maybe "" show st.recurrenceValue
else "")
, P.min 1.0
, P.max 12.0
, E.onValueInput (Just <<< SetRecurrenceWeeks)
]
, HH.text " Weeks"
]
, HH.label_
[ HH.input
([ P.type_ P.InputRadio
, P.name "recurType"
, E.onClick \_ -> Just (SetRecurrenceType RTOneTime)
] <> (if st.recurrenceType == RTOneTime then [P.checked true] else []))
, HH.text " One-Time"
]
]
, formGroup st
[AmountField]
[ HH.label
[ P.for "billableAmount"]
[ HH.text "Amount" ]
, HH.input
[ P.type_ P.InputNumber
, P.classes [ C.formControlSm ]
, P.id_ "billableAmount"
, P.value (maybe "" (Fixed.toString <<< unwrap) st.amount)
, P.placeholder "1.0"
, P.min 0.0
, E.onValueInput (Just <<< SetBillingAmount)
, P.classes [ C.formControl, C.formControlSm ]
, P.id_ "requestDesc"
, P.placeholder "Additional descriptive information"
, E.onValueInput (Just <<< SetDesc)
, formGroup st
[GracePeriodField]
[ HH.label
[ P.for "gracePeriod"]
[ HH.text "Grace Period (Days)" ]
, HH.input
[ P.type_ P.InputNumber
, P.id_ "gracePeriod"
, P.classes [ C.formControlSm ]
, P.value (maybe "" (Number.toString <<< unwrap) st.gracePeriod)
, P.placeholder "Days until a bill is considered overdue"
, P.min 0.0
, E.onValueInput (Just <<< SetGracePeriod)
]
]
, formGroup st
[RequestExpiryField]
[ HH.label
[ P.for "requestExpiry"]
[ HH.text "Request Expiry Period (Hours)" ]
, HH.input
[ P.type_ P.InputNumber
, P.id_ "gracePeriod"
, P.classes [ C.formControlSm ]
, P.value (maybe "" (Number.toString <<< unwrap) st.requestExpiry)
, P.placeholder "Hours until a payment request expires"
, P.min 0.0
, E.onValueInput (Just <<< SetRequestExpiry)
]
]
NameField -> err "The name field is required"
DescField -> err "The description field is required"
MessageField -> err "The message field is required"
MonthlyRecurrenceField -> err "You must enter a valid number of months."
WeeklyRecurrenceField -> err "You must enter a valid number of weeks."
AmountField -> err "You must enter a valid amount of ZEC"
GracePeriodField -> err "You must enter a valid number of hours."
RequestExpiryField -> err "You must enter a valid number of hours."
NameRequired -> err "The name field is required"
BillableIdNotSet -> err "The billable id is missing. Close this dialog and try again."
eval :: forall slots. Action -> H.HalogenM CState Action slots Output m Unit
eval = case _ of
ProjectChanged pid ->
H.modify_ (_ { projectId = pid })
SetName name ->
H.modify_ (_ { name = Just name })
SetDesc desc ->
H.modify_ (_ { description = Just desc })
SetMessage msg ->
H.modify_ (_ { message = Just msg })
SetRecurrenceType rtype -> do
curRecurType <- H.gets _.recurrenceType
curDuration <- H.gets _.recurrenceValue
let rdur = case curRecurType of
RTMonthly | rtype == RTMonthly -> curDuration
RTWeekly | rtype == RTWeekly -> curDuration
_ -> Nothing
H.modify_ (_ { recurrenceType = rtype, recurrenceValue = rdur })
SetRecurrenceMonths dur ->
case Int.fromString dur of
(Just n) -> H.modify_ (_ { recurrenceType = RTMonthly, recurrenceValue = Just n })
(Nothing) -> pure unit
SetRecurrenceWeeks dur ->
case Int.fromString dur of
(Just n) -> H.modify_ (_ { recurrenceType = RTWeekly, recurrenceValue = Just n })
(Nothing) -> pure unit
SetBillingAmount amt ->
case Fixed.fromString amt of
(Just zec) -> H.modify_ (_ { amount = Just (ZEC zec) })
(Nothing) -> pure unit
SetGracePeriod dur ->
case Number.fromString dur of
(Just n) -> H.modify_ (_ { gracePeriod = Just (Days n) })
(Nothing) -> pure unit
SetRequestExpiry dur ->
case Number.fromString dur of
(Just n) -> H.modify_ (_ { requestExpiry = Just (Hours n) })
(Nothing) -> pure unit
SaveBillable -> do
nameV <- V <<< note [NameField] <$> H.gets (_.name)
descV <- V <<< note [DescField] <$> H.gets (_.description)
msgV <- V <<< note [MessageField] <$> H.gets (_.message)
rtype <- H.gets (_.recurrenceType)
rvalueV <- case rtype of
RTAnnual -> pure $ V (Right Annually)
RTMonthly -> V <<< maybe (Left [MonthlyRecurrenceField]) (Right <<< Monthly) <$> H.gets (_.recurrenceValue)
RTWeekly -> V <<< maybe (Left [WeeklyRecurrenceField]) (Right <<< Weekly) <$> H.gets (_.recurrenceValue)
RTOneTime -> pure $ V (Right OneTime)
zatsV <- V <<< maybe (Left [AmountField]) (Right <<< toZatoshi) <$> H.gets (_.amount)
gperV <- V <<< note [GracePeriodField] <$> H.gets (_.gracePeriod)
expiryV <- V <<< note [RequestExpiryField] <$> H.gets (_.requestExpiry)
let toBillable = { name: _
, description: _
, message: _
, recurrence: _
, amount: _
, gracePeriod: _
, expiryPeriod: _
}
reqV :: V (Array Field) Billable
reqV =
toBillable <$> nameV
<*> descV
<*> msgV
<*> rvalueV
<*> zatsV
<*> gperV
<*> expiryV
handleQuery :: forall slots a. Query a -> H.HalogenM CState Action slots Output m (Maybe a)
handleQuery = case _ of
SetBillableId bid a -> do
H.modify_ (_ { billableId = Just bid })
pure (Just a)
case toEither reqV of
Left errors -> do
H.modify_ (_ { fieldErrors = errors })
Right billable -> do
pid <- H.gets (_.projectId)
res <- lift $ caps.createBillable pid billable
case res of
Right bid -> do
H.raise (Tuple bid billable)
lift $ system.toggleModal "createBillable" ModalFFI.HideModal
Left errs ->
lift $ system.error (show errs)
handleAction :: forall slots. Action -> H.HalogenM CState Action slots Output m Unit
handleAction = case _ of
SetName name ->
H.modify_ (_ { name = Just name })
SetDesc desc ->
H.modify_ (_ { description = Just desc })
SavePaymentRequest -> do
bidV <- V <<< note [BillableIdNotSet] <$> H.gets (_.billableId)
nameV <- V <<< note [NameRequired] <$> H.gets (_.name)
descV <- H.gets (_.description)
let reqV = { requestName: _, requestDesc: _ } <$> nameV <*> pure descV
breqV = Tuple <$> bidV <*> reqV
case toEither breqV of
Left errors -> do
H.modify_ (_ { fieldErrors = errors })
Right (Tuple bid reqMeta) -> do
pid <- H.gets (_.projectId)
res <- lift $ caps.createPaymentRequest pid bid reqMeta
case res of
Right content -> do
lift $ system.log "Request created."
H.raise content
lift $ system.toggleModal "createPaymentRequest" ModalFFI.HideModal
Left errs ->
lift $ system.error (show errs)
{ createBillable: \_ _ -> pure $ Left Forbidden }
{ createPaymentRequest: \_ _ _ -> pure $ Left Forbidden }
type QrState = Maybe PaymentRequest
data QrQuery a
= QrRender PaymentRequest a
data QrAction
= QrInit
type QrSlot id
= H.Slot QrQuery Unit id
qrModalId :: String
qrModalId = "paymentRequestQR"
qrcomponent ::
forall m output.
Monad m =>
System m ->
H.Component HH.HTML QrQuery (Maybe PaymentRequest) output m
qrcomponent system =
H.mkComponent
{ initialState
, render
, eval:
H.mkEval
$ H.defaultEval
{ handleAction = handleAction
, handleQuery = handleQuery
, initialize = Just QrInit
}
}
where
initialState :: Maybe PaymentRequest -> Maybe PaymentRequest
initialState input = input
render :: forall slots. QrState -> H.ComponentHTML QrAction slots m
render st =
Modals.modalWithClose qrModalId "Payment Request"
[ HH.div_
[ HH.div [P.id_ "paymentRequestQRCode"] []
]
]
handleQuery :: forall slots a. QrQuery a -> H.HalogenM QrState QrAction slots output m (Maybe a)
handleQuery = case _ of
QrRender r a -> do
lift $ renderQR r
pure (Just a)
handleAction :: forall slots. QrAction -> H.HalogenM QrState QrAction slots output m Unit
handleAction = case _ of
QrInit -> do
traverse_ (lift <<< renderQR) =<< get
renderQR :: PaymentRequest -> m Unit
renderQR (PaymentRequest r) =
system.renderQR "paymentRequestQRCode" { content: r.native_request.zip321_request }
CreatePaymentRequest bid -> do
H.modify_ (_ { selectedBillable = Just bid })
_ <- H.query _createPaymentRequest unit $ H.tell (PaymentRequest.SetBillableId bid)
lift $ system.toggleModal PaymentRequest.modalId ModalFFI.ShowModal
PaymentRequestCreated req -> do
lift $ system.log "Created payment request, closing modal."
lift $ system.toggleModal PaymentRequest.modalId ModalFFI.HideModal
lift $ system.log "About to show QR code modal"
lift $ system.toggleModal PaymentRequest.qrModalId ModalFFI.ShowModal
_ <- H.query _showPaymentRequest unit $ H.tell (PaymentRequest.QrRender req)
pure unit
exports.renderQRInternal = selector => content => () => {
$('#' + selector).kjua(content)
}
module Aftok.HTML.KjuaQR
( QRType(..)
, QROpts
, renderQR
)
where
import Prelude
import Effect (Effect)
data QRType
= Canvas
| Image
| SVG
data ErrorCorrection = L | M | Q | H
renderQR :: String -> QROpts -> Effect Unit
renderQR = renderQRInternal
type QROpts =
{ content :: String
}
-- -- render method: 'canvas', 'image' or 'svg'
-- render :: QRType,
-- -- render pixel-perfect lines
-- crisp :: Boolean,
-- -- minimum version: 1..40
-- minVersion :: Int,
-- -- error correction level: 'L', 'M', 'Q' or 'H'
-- ecLevel :: ErrorCorrection,
-- -- size in pixel: 200
-- size :: Int,
-- -- pixel-ratio, null for devicePixelRatio
-- -- ratio :: null,
--
-- --code color: '#333',
-- fill :: String -- hack, fine for now
-- -- background color '#fff'
-- back :: String,
--
-- -- content
-- text :: String,
--
-- -- roundend corners in pc: 0..100
-- rounded: Int,
--
-- -- quiet zone in modules: 0
-- quiet: Int,
--
-- -- modes: 'plain', 'label' or 'image'
-- mode: 'plain',
--
-- -- label/image size and pos in pc: 0..100
-- mSize: 30,
-- mPosX: 50,
-- mPosY: 50,
--
-- -- label
-- label: 'no label',
-- fontname: 'sans',
-- fontcolor: '#333',
--
-- -- image element
-- image: null
--
-- type QROptsInternal =
-- {
-- -- render method: 'canvas', 'image' or 'svg'
-- render :: String,
--
-- -- render pixel-perfect lines
-- crisp :: Boolean,
--
-- -- minimum version: 1..40
-- minVersion :: Int
--
-- -- error correction level: 'L', 'M', 'Q' or 'H'
-- ecLevel: 'L',
--
-- -- size in pixel
-- size: 200,
--
-- -- pixel-ratio, null for devicePixelRatio
-- ratio: null,
--
-- -- code color
-- fill: '#333',
--
-- -- background color
-- back: '#fff',
--
-- -- content
-- text: 'no text',
--
-- -- roundend corners in pc: 0..100
-- rounded: 0,
--
-- -- quiet zone in modules
-- quiet: 0,
--
-- -- modes: 'plain', 'label' or 'image'
-- mode: 'plain',
--
-- -- label/image size and pos in pc: 0..100
-- mSize: 30,
-- mPosX: 50,
-- mPosY: 50,
--
-- -- label
-- label: 'no label',
-- fontname: 'sans',
-- fontcolor: '#333',
--
-- -- image element
-- image: null
--
--
-- }
foreign import renderQRInternal :: String -> QROpts -> Effect Unit
modalWithClose ::
forall i w.
String ->
String ->
Array (HH.HTML i w) ->
HH.HTML i w
modalWithClose modalId title contents =
HH.div
[ P.classes [ C.modal ]
, P.id_ modalId
, P.tabIndex (negate 1)
, ARIA.role "dialog"
, ARIA.labelledBy (modalId <> "Title")
, ARIA.hidden "true"
]
[ HH.div
[ P.classes [C.modalDialog], ARIA.role "document" ]
[ HH.div
[ P.classes [C.modalContent] ]
[ HH.div
[ P.classes [C.modalHeader] ]
[ HH.h5 [P.classes [C.modalTitle], P.id_ (modalId <>"Title") ] [HH.text title]
, HH.button
[ P.classes [ C.close ]
, AP.dataDismiss "modal"
, ARIA.label "Close"
, P.type_ ButtonButton
]
[ HH.span [ARIA.hidden "true"] [HH.text "×"]]
]
, HH.div
[ P.classes [C.modalBody] ]
contents
, HH.div
[ P.classes [C.modalFooter] ]
[ HH.button
[ P.type_ ButtonButton
, P.classes [ C.btn, C.btnSecondary]
, AP.dataDismiss "modal"
]
[ HH.text "Close" ]
]
]
]
]
findAccountPaymentAddress :: (MonadDB m) => AccountId -> Currency a c -> MaybeT m a
findAccountPaymentAddress uid n = MaybeT . liftdb $ FindAccountPaymentAddress uid n
findAccountPaymentAddress :: (MonadDB m) => AccountId -> Currency a c -> MaybeT m (AccountId, a)
findAccountPaymentAddress aid n = fmap (aid,) . MaybeT . liftdb $ FindAccountPaymentAddress aid n
-- TODO: Return a richer type that can include per-item uniqueness that can
-- be used for tracking payments. A payment request, though it's a request for
-- a single transaction, is really a request for multiple payments that we need
-- to be able to verify individually since they'll be independent notes.
--
-- However, this doesn't really become important until we start generating addresses
-- from Zcash IVKs, so it's not essential for right now.
toPaymentItem :: (Address, Zatoshi) -> PaymentItem
toPaymentItem (a, z) =
PaymentItem
toPaymentItem :: ((AccountId, Address), Zatoshi) -> m PaymentItem
toPaymentItem ((aid, a), z) = do
memo <- memoGen billable billingDay billTime aid
pure $ PaymentItem
let ops = Zcash.paymentOps (cfg ^. zcashPaymentsConfig)
res <- snapEval . runExceptT $ createPaymentRequest ops now bid (b & B.amount .~ v) billDay
let ops = Zcash.paymentOps (cfg ^. zcashBillingOps) (cfg ^. zcashPaymentsConfig)
res <- runExceptT . mapExceptT qdbmEval $ createPaymentRequest ops now bid (b & B.amount .~ v) billDay
either handleDBError pure e
qdbmEval ::
(MonadSnap m, HasPostgres m, HasNetworkMode m) => QDBM a -> m a
qdbmEval p = do
let handleDBError (OpForbidden (UserId uid) reason) =
snapError 403 $ show reason <> " (User " <> show uid <> ")"
handleDBError (SubjectNotFound) =
snapError
404
"The subject of the requested operation could not be found."
handleDBError (EventStorageFailed) =
snapError 500 "The event submitted could not be saved to the log."
nmode <- getNetworkMode
e <- liftPG $
\conn -> liftIO $ runExceptT (runQDBM nmode conn p)