EFSXYZPOGA5M4DN65IEIDO7JK6U34DMQELAPHOIL2UAT6HRC66BAC
4R7XIYK3BP664CO3YJ2VM64ES2JYN27UTQG5KS34OTEPAIODSZLQC
IPG33FAWXGEQ2PO6OXRT2PWWXHRNMPVUKKADL6UKKN5GD2CNZ25AC
B6HWAPDPXIWH7CHK5VLMWLL6EQN6NOFZEFYO47BPUY2ZO4SL7VDAC
EKI57EJR65DA5FPILAHGHHAIU5ITVGHA6V3775OX7GV5XD67OWRQC
LAROLAYUGJ4Q5AEFV5EJMIA2ZKBNCBWHHHPCJ3CKCNIUIYUKRFVQC
NAS4BFL43SIUAAC663R6VFBHQ2DKI45K6Y6ZKVQI7S5547HBAN7QC
NEDDHXUK3GNFMOFO3KLU7NRIHCTYNWBT3D6HTKZAOXFDE6HMDZ6AC
IZEVQF627FA7VV25KJAWYWGGC35LZUUBBQRPN5ZAINTQLPEDRTEAC
LHJ2HFXVUQ4VG25I7DADWU73G5K5WNZBDQ3SVNKFYLZ5BEYM4XCQC
F2XLL7XWGUV4TJD4X2MJADYAQHCSB4HD2TPPEYVHEKHOQIOOFISAC
HBULCDN6E75FAPILFVLTQIKABDEWL3HZTBLICLCWOIKDRYM6UIBQC
WO2MINIF4TXOHWSE7JWXRZYN64XRVLYIRFMF4SMPSOXKA2V77KMQC
GLFF5ZDKWI7WKPZSAEE3IUM27LL6DFOPIL4VPODXYXV3BCSCJ6GQC
ZP62WC472OTQETO2HTHIQIPO57XZIWVKPA4KL62GYU4OZDMB6NSAC
BSIUHCGFDFDFGWYMHZB7OVU3Z3IHPEUXRISIOPGZI2RUXZFDS2EQC
Q5X5RYQLP5K7REYD6VLHOKC4W36ZELJYA45V6YFKTD5S6MPN3NDQC
4FDQGIXN3Z4J55DILCSI5EOLIIA7R5CADTGFMW5X7N7MH6JIMBWAC
SEWTRB6S5PO5MQBLCPVBD7XT2BDYNZUE2RO6Z2XENZRIOCN6OZJAC
JFOEOFGA4CQR2LW43IVQGDZSPVJAD4KDN2DZMZXGM2QDIUD7AVCAC
Y3LIJ5USPMYBG7HKCIQBE7MWVGLQJPJSQD3KPZCTKXP22GOB535QC
RN7EI6INGUUHGMNY5RU3NH56WPLRZY5ZMYDNFMNE3TGV4ESFLQIAC
73NDXDEZRMK672GHSTC3CI6YHXFZ2GGJI5IKQGHKFDZKTNSQXLLQC
HMDM3B557TO5RYP2IGFFC2C2VN6HYZTDQ47CJY2O37BW55DSMFZAC
A6HKMINBNGQLLX4QJMYWKQ4JAEHVJ4HIRVDKPPDI3FJUO2AAB7OQC
DFOBMSAODB3NKW37B272ZXA2ML5HMIH3N3C4GT2DPEQS7ZFK4SNAC
Z7KS5XHHC6PAMTVBHXY7KUSS3BWAOU6FSYIITUCFOOJZU4OUJHBAC
GCVQD44VRPQVKPZEPIC4AOIXLJIG2ZMV3QI2Y7KALUT6NVUBSGSAC
2XQD6KKKD6QVHFHAEMVE3XXY7X2T7BLTLL7XIILZAXNJJH2YONUQC
TNR3TEHKVADAEZSTOD2XLSUTSW5AWST2YUW4CWK5KE7DSC6XHZNAC
AL37SVTCKRSG4HG2PCYK5Z7QSIZZH5JHH4Q2VLMXFAXSAQRFFG4QC
WZFQDWW4XK6M4A4PQ7WQJUTZUPRGQR7V7ZVZY5ZTL5AMGIFMHB2QC
O5FVTOM6YFBLEPF3S576K6IMT6ZZ5VQCSB3YVXNS4CKBITKCRZ7AC
O227CEAV7BTKSE3SSC7XHC5IWEBXZL2AOOKJMBMOOFNTLINBLQMAC
EW2XN7KUMCAQNVFJJ5YTAVDZCPHNWDOEDMRFBUGLY6IE2HKNNX5AC
WAIX6AGNDVJOKTWZ7OP7QOYSJHAJSX5EOWXZHOAO2IG6ALWUCJ6QC
3QVT6MA6I2CILQH3LUZABS4YQ7MN6CNRYTDRVS376OOHTPLYTFJAC
QADKFHAR3KWQCNYU25Z7PJUGMD5WL26IU3DOAHBTRN2A7NKPUPKAC
64C6AWH66FDKU6UE6Z6JPX2J2GBM2JOPTH2GL6LHKAIUBGNGDZ5AC
NLZ3JXLOOIL37O3RRQWXHNPNSNEOOLPD6MCB754BEBECQB3KGR2AC
QMRKFEPGFBCEWAIXPEIG5ILKAJ2JH5L3TOITHR4HNJXK5FN3KXBQC
5OI44E4EEVYOMHDWNK2WA7K4L4JWRWCUJUNN2UAUGE5VY4W7GTNAC
MB5SHULBN3WP7TGUWZDP6BRGP423FTYKF67T5IF5YHHLNXKQ5REAC
FXJQACESPGTLPG5ELXBU3M3OQXUZQQIR7HPIEHQ3FNUTMWVH4WBAC
ASF3UPJLCX7KIUCNJD5KAXSPDXCUEJHLL4HBRTRUBPCW73IXOCWQC
4U7F3CPIDTK6JSEDMNMHVKSR7HOQDLZQD2PPVMDLHO5SFSIMUXZAC
4SCFOJGNDAN4XZEAPWQQCBJ3CGZCJP3HUADRQLYZ2ITAKA7EJJTQC
EQXRXRZDYCM7BDAVBOXQYPG6C7IJT3OFGNIXCDAHJJBRKAXNGL7AC
RPAJLHMTUJU4AYNBOHVGHGGB4NY2NLY3BVPYN5FMWB3ZIMAUQHCQC
SCXG6TJWYIPRUMT27KGKIIF6FYKTUTY74UNZ2FQTT63XZ6HIF3AAC
N4NDAZYTLSI2W22KT3SYXL257DBMSH3UT2BXOYM7LH7FSZAY4RLAC
W35DDBFYF6Z4ZPCFEO5RPAONZLYCSNTXUSTS6FIUUVZHCI6Q7GHAC
Z3MK2PJ5U222DXRS22WCDHVPZ7HVAR3HOCUNXIGX6VMEPBQDF6PQC
BWN72T44GRRZ6K2OPN56FTLNEB7J7AGC7T2U5HSMLEKUPGJP2NUAC
POX3UAMTGCNS3SU5I6IKDNCCSHEAUSFBZ3WCMQ3EXKPNXETOI6BAC
7VGYLTMURLVSVUYFW7TCRZTDQ6RE2EPSPPA43XKHDOBFWYVVSJHQC
XTBSG4C7SCZUFOU2BTNFR6B6TCGYI35BWUV4PVTS3N7KNH5VEARQC
O722AOKEWXWJPRHGJREU6QPW7HEFPPRETZIAADZ2RMAXHARCNEKAC
HALRDT2F22DAQ54M334YSKD2XJCAXDXEZPDKGMOWKH7XELBAS6MAC
KEP5WUFJXTMKRRNZLYTGYYWA4VLFCMHTKTJYF5EA5IWBYFMU6WYQC
PBD7LZYQHXAA3KLH2ZUX5GW4UFML6BQ32KXZF4KZ6OYFASUYFJ5QC
7HPY3QPFPN35PSPUBVNW2GTFB3CBQZBST4J2BAVJ7QMXLIUN52JAC
V2VDN77HCSRYYWXDJJ2XOVHV4P6PVWNJZLXZ7JUYPQEZQIH5BZ3QC
GMYPBCWEB6NKURRILAHR3TJUKDOGR2ZMK5I6MS6P5G2LAGH36P3QC
6L5BK5EHPAOQX3JCKUJ273UDNAC23LPQL4HIJGM4AV3P3QK5OKIQC
4B66XH43UYRVNTX57ORJ7U6IJTRFKSUS6IJ3CXVODMEF7NA7UHVQC
5ZSKPQ3KY6T6O5S6T6HW4OHJMQXA72WKJSJJMGKGX2WMFTNZ7EGAC
I2KHGVD44KT4MQJXGCTVSQKMBO6TVCY72F26TLXGWRL6PHGF6RNQC
2MNO5FUYXF6GHHWTIDLW2JGMFC3UY54BHJKUYVF7SZCUJQWKZ4DQC
DXIGERDTERUIG7QHHRPKTSJHSQEPJPDJVLUW7YVC7URXBQ4ZJVOAC
MMRVIM3FRSHP3XB37L7YAQVF4DXDSRRIC5UJS6NSD2LGTQSZJIMQC
BROSTG5KP3NUNLSYPVQID254TE47E5RKQAKLPIY7PGWETE6JNMTAC
BXGLKYRXO2O4NRM3BLNWQ7AWVPQXAMFS57MFYHJNOZZEZZW5BH6AC
UILI6PILCRDPZ3XYA54LGIGPSU7ERWNHCE7R3CE64ZEC7ONOEMOQC
KNSI575VAW6HRCZYXOEPQ4DTSML4EORML5MV4DJBRKE7TXCPS4EAC
EZQG2APB36DDMIAYDPPDGOIXOD7K2RZZSGC2NKGZIHB2HZBTW7EQC
7KZP4RHZ3QSYTPPQ257A65Z5UPX44TF2LAI2U5EMULQCLDCEUK2AC
ADMKQQGCGVSHHIMVQ4XFRDCG544SBJCYALSKZV45CQQBZ4ACUH2AC
EMVTF2IWNQGRL44FC4JNG5FYYQTZSFPNM6SOM7IAEH6T7PPK2NVAC
HO2PFRABW6BBTE4MUKUTEGXCMJS46WGVBCNWOHO4OL52DVAB4YDAC
Z3M53KTLZMPOISMHE25SZJSWX5TA37IV33IRE7KNRAD3PKEAEJXQC
4ZLEDBK7VGLKFUPENAFLUJYNFLKFYJ3TREPQ7P6PKMYGJUXB55HQC
PGZJ736CG2E4HXIRYTZTGOMJRX2CHPIFG6H45PPO57EONOWJJ74QC
MGOF7IUFGXYQKZOKMM2GGULFFVAULEHLZDSHMUW6B5DBKVXXR74AC
UUR6SMCAJMA7O3ZFUCQMPZFDDIPUVQ5IHUAC5F252YVD6H3JIKPQC
3GBSDS5PDSTTJTJOLEKZRRTAONS3T3JFZ3FQGFGS3AOXDBZ6SPLAC
NMWWP4ZNOKHZKSJ6F5KYEREWXXR5F4UD35WOKI3EH42AZWVCTCJAC
NVOCQVASZWTKQJG7GPH7KHKZZR7NUG4WLV5YY4KAIRPCJRWCZPIAC
Y35QCWYW2OTZ27ZVTH2BA3XJTUCJ2WMLKU32ZCOCDY3AW7TIZXRAC
5DRIWGLUKMQZU2ZPBXSTLAWJKAMOD5YXAHM5LEDQHDFGYYLHWCDQC
P6NR2CGXCWAW6GXXSIXCGOBIRAS2BM4LEM6D5ADPN4IL7TMW6UVAC
OV5AKJHA773ETIJPTMQ7K64U7BRQE34OXJ6FJNH6TZG22WS5QTIAC
2KZPOGRBY6KBMO76F55ZKIVOLSG3O63VP3RHRZVANXYT3OLZ3OWQC
WJO37T74RYR5DRMSVNCXAQBOV42FQB63EG43XDZUU5TA354AIJRAC
import Control.Error.Util (maybeT)
import Control.Lens ((^.), makeLenses, makeClassyPrisms, traverseOf, to)
import Control.Monad.IO.Class (MonadIO(..))
import Control.Monad.Except (MonadError, throwError)
import Control.Monad.Reader (MonadReader)
import Control.Monad.Trans.Except (ExceptT, withExceptT, runExceptT)
import Control.Monad.Trans.Reader (mapReaderT, withReaderT)
import Control.Error.Util ( maybeT )
import Control.Lens ( (^.)
, makeLenses
, makeClassyPrisms
, traverseOf
, to
)
import Control.Monad.IO.Class ( MonadIO(..) )
import Control.Monad.Except ( MonadError
, throwError
)
import Control.Monad.Reader ( MonadReader )
import Control.Monad.Trans.Except ( ExceptT
, withExceptT
, runExceptT
)
import Control.Monad.Trans.Reader ( mapReaderT
, withReaderT
)
import Database.PostgreSQL.Simple (Connection, connect)
import Data.Thyme.Clock as C
import Data.Thyme.Time as C
import qualified Data.Text as T
import qualified Network.Mail.Mime as Mime
import qualified Network.Mail.SMTP as SMTP
import Network.URI (URI, parseURI)
import Network.Haskoin.Address (Address)
import Text.StringTemplate (directoryGroup, newSTMP, getStringTemplate, setManyAttrib, render)
import Filesystem.Path.CurrentOS (encodeString)
import Database.PostgreSQL.Simple ( Connection
, connect
)
import Data.Thyme.Clock as C
import Data.Thyme.Time as C
import qualified Data.Text as T
import qualified Network.Mail.Mime as Mime
import qualified Network.Mail.SMTP as SMTP
import Network.URI ( URI
, parseURI
)
import Network.Haskoin.Address ( Address )
import Text.StringTemplate ( directoryGroup
, newSTMP
, getStringTemplate
, setManyAttrib
, render
)
import Filesystem.Path.CurrentOS ( encodeString )
import Aftok.Types (User, UserId, ProjectId(..), userEmail, _Email)
import Aftok.Currency.Bitcoin (NetworkId, satoshi)
import qualified Aftok.Config as AC
import Aftok.Billables (Billable, Billable', Subscription', customer, name, billable, project, paymentRequestEmailTemplate, paymentRequestMemoTemplate)
import qualified Aftok.Database as DB
import Aftok.Database.PostgreSQL (QDBM(..))
import qualified Aftok.Payments as P
import Aftok.Payments.Types (PaymentKey(..), subscription, paymentRequestTotal, paymentKey)
import Aftok.Project (Project, projectName)
import qualified AftokD as D
import Aftok.Types ( User
, UserId
, ProjectId(..)
, userEmail
, _Email
)
import Aftok.Currency.Bitcoin ( NetworkId
, satoshi
)
import qualified Aftok.Config as AC
import Aftok.Billables ( Billable
, Billable'
, Subscription'
, customer
, name
, billable
, project
, paymentRequestEmailTemplate
, paymentRequestMemoTemplate
)
import qualified Aftok.Database as DB
import Aftok.Database.PostgreSQL ( QDBM(..) )
import qualified Aftok.Payments as P
import Aftok.Payments.Types ( PaymentKey(..)
, subscription
, paymentRequestTotal
, paymentKey
)
import Aftok.Project ( Project
, projectName
)
import qualified AftokD as D
now <- liftIO C.getCurrentTime
let ops = P.BillingOps memoGen (fmap Just . paymentURL) payloadGen
subscribers <- liftQDBM $ DB.findSubscribers pid
requests <- traverse (\uid -> P.createPaymentRequests ops now uid pid) $ subscribers
traverse_ sendPaymentRequestEmail (join requests)
now <- liftIO C.getCurrentTime
let ops = P.BillingOps memoGen (fmap Just . paymentURL) payloadGen
subscribers <- liftQDBM $ DB.findSubscribers pid
requests <-
traverse (\uid -> P.createPaymentRequests ops now uid pid) $ subscribers
traverse_ sendPaymentRequestEmail (join requests)
let AC.SmtpConfig{..} = cfg ^. (dcfg . D.smtpConfig)
preqCfg = cfg ^. (dcfg . D.paymentRequestConfig)
reqMay = do
preq <- DB.findPaymentRequestId reqId
preq' <- traverseOf P.subscription DB.findSubscriptionBillable preq
let AC.SmtpConfig {..} = cfg ^. (dcfg . D.smtpConfig)
preqCfg = cfg ^. (dcfg . D.paymentRequestConfig)
reqMay = do
preq <- DB.findPaymentRequestId reqId
preq' <- traverseOf P.subscription DB.findSubscriptionBillable preq
mail <- buildPaymentRequestEmail preqCfg req bip70URL
let mailer = maybe (SMTP.sendMailWithLogin _smtpHost) (SMTP.sendMailWithLogin' _smtpHost) _smtpPort
mail <- buildPaymentRequestEmail preqCfg req bip70URL
let mailer = maybe (SMTP.sendMailWithLogin _smtpHost)
(SMTP.sendMailWithLogin' _smtpHost)
_smtpPort
buildPaymentRequestEmail :: (MonadIO m, MonadError AftokDErr m)
=> D.PaymentRequestConfig
-> P.PaymentRequest' (Subscription' (User (NetworkId, Address)) (Billable' Project UserId Satoshi))
-> URI
-> m Mime.Mail
buildPaymentRequestEmail
:: (MonadIO m, MonadError AftokDErr m)
=> D.PaymentRequestConfig
-> P.PaymentRequest'
( Subscription'
(User (NetworkId, Address))
(Billable' Project UserId Satoshi)
)
-> URI
-> m Mime.Mail
toEmail = req ^. (subscription . customer . userEmail)
pname = req ^. (subscription . billable . project . projectName)
total = req ^. (P.paymentRequest . to paymentRequestTotal)
setAttrs = setManyAttrib
[ ("from_email", fromEmail ^. _Email)
toEmail = req ^. (subscription . customer . userEmail)
pname = req ^. (subscription . billable . project . projectName)
total = req ^. (P.paymentRequest . to paymentRequestTotal)
setAttrs = setManyAttrib
[ ("from_email" , fromEmail ^. _Email)
memoGen :: Subscription' UserId Billable
-> C.Day
-> C.UTCTime
-> AftokM (Maybe Text)
memoGen
:: Subscription' UserId Billable -> C.Day -> C.UTCTime -> AftokM (Maybe Text)
import Database.PostgreSQL.Simple (ConnectInfo)
import Filesystem.Path.CurrentOS (fromText, encodeString)
import qualified Filesystem.Path.CurrentOS as P
import Database.PostgreSQL.Simple ( ConnectInfo )
import Filesystem.Path.CurrentOS ( fromText
, encodeString
)
import qualified Filesystem.Path.CurrentOS as P
readConfig cfg = Config
<$> (AC.readSmtpConfig $ C.subconfig "smtp" cfg)
<*> (AC.readBillingConfig $ C.subconfig "billing" cfg)
<*> (AC.readConnectInfo $ C.subconfig "db" cfg)
<*> (readPaymentRequestConfig $ C.subconfig "payment_requests" cfg)
readConfig cfg =
Config
<$> (AC.readSmtpConfig $ C.subconfig "smtp" cfg)
<*> (AC.readBillingConfig $ C.subconfig "billing" cfg)
<*> (AC.readConnectInfo $ C.subconfig "db" cfg)
<*> (readPaymentRequestConfig $ C.subconfig "payment_requests" cfg)
readPaymentRequestConfig cfg = PaymentRequestConfig
<$> C.require cfg "aftok_host"
<*> (fromText <$> C.require cfg "template_path")
<*> (Email <$> C.require cfg "payment_from_email")
readPaymentRequestConfig cfg =
PaymentRequestConfig
<$> C.require cfg "aftok_host"
<*> (fromText <$> C.require cfg "template_path")
<*> (Email <$> C.require cfg "payment_from_email")
import Control.Exception (try)
import System.Environment (getEnv)
import System.IO.Error (IOError)
import Filesystem.Path.CurrentOS (decodeString)
import Control.Exception ( try )
import System.Environment ( getEnv )
import System.IO.Error ( IOError )
import Filesystem.Path.CurrentOS ( decodeString )
import Data.Hourglass (Seconds(..))
import Data.Ratio ((%))
import Data.Traversable (for)
import Data.Thyme.Clock as C
import Data.Thyme.Format ()
import Data.Hourglass ( Seconds(..) )
import Data.Ratio ( (%) )
import Data.Traversable ( for )
import Data.Thyme.Clock as C
import Data.Thyme.Format ( )
bidOrder =
comparing costRatio `mappend` comparing (^. bidTime)
where
secs bid = toRational $ bid ^. bidSeconds
btc bid = toRational $ bid ^. bidAmount . satoshi
costRatio bid = secs bid / btc bid
bidOrder = comparing costRatio `mappend` comparing (^. bidTime)
where
secs bid = toRational $ bid ^. bidSeconds
btc bid = toRational $ bid ^. bidAmount . satoshi
costRatio bid = secs bid / btc bid
let takeWinningBids :: Satoshi -> [Bid] -> [Bid]
takeWinningBids total (bid : xs)
-- if the total is fully within the raise amount
| total <> (bid ^. bidAmount) < raiseAmount' =
bid : takeWinningBids (total <> (bid ^. bidAmount)) xs
let
takeWinningBids :: Satoshi -> [Bid] -> [Bid]
takeWinningBids total (bid : xs)
|
-- if the total is fully within the raise amount
total <> (bid ^. bidAmount) < raiseAmount'
= bid : takeWinningBids (total <> (bid ^. bidAmount)) xs
|
-- if the last bid will exceed the raise amount, reduce it to fit
| total < raiseAmount' =
let winFraction r = r % (bid ^. bidAmount . satoshi)
remainderSeconds (Satoshi r) = Seconds . round $ winFraction r * fromIntegral (bid ^. bidSeconds)
adjustBid r = bid & bidSeconds .~ remainderSeconds r & bidAmount .~ r
in toList $ adjustBid <$> raiseAmount' `ssub` total
| otherwise = []
-- if the last bid will exceed the raise amount, reduce it to fit
total < raiseAmount'
= let
winFraction r = r % (bid ^. bidAmount . satoshi)
remainderSeconds (Satoshi r) =
Seconds . round $ winFraction r * fromIntegral (bid ^. bidSeconds)
adjustBid r = bid & bidSeconds .~ remainderSeconds r & bidAmount .~ r
in
toList $ adjustBid <$> raiseAmount' `ssub` total
| otherwise
= []
submittedTotal = bidsTotal bids
in maybe
(WinningBids $ takeWinningBids (Satoshi 0) $ sortBy bidOrder bids)
InsufficientBids
(raiseAmount' `ssub` submittedTotal)
submittedTotal = bidsTotal bids
in
maybe (WinningBids $ takeWinningBids (Satoshi 0) $ sortBy bidOrder bids)
InsufficientBids
(raiseAmount' `ssub` submittedTotal)
put (x <> bid ^. bidAmount) >>
(pure . Just $ Commitment bid (bid ^. bidSeconds) (bid ^. bidAmount))
put (x <> bid ^. bidAmount)
>> (pure . Just $ Commitment bid (bid ^. bidSeconds) (bid ^. bidAmount))
import Data.X509.File (readKeyFile, readSignedObject)
import Database.PostgreSQL.Simple (ConnectInfo(..))
import Filesystem.Path.CurrentOS (fromText, encodeString)
import qualified Filesystem.Path.CurrentOS as P
import Safe (headMay)
import Data.X509.File ( readKeyFile
, readSignedObject
)
import Database.PostgreSQL.Simple ( ConnectInfo(..) )
import Filesystem.Path.CurrentOS ( fromText
, encodeString
)
import qualified Filesystem.Path.CurrentOS as P
import Safe ( headMay )
SmtpConfig <$> C.require cfg "smtpHost"
<*> ((fmap . fmap) fromInteger $ C.lookup cfg "smtpPort")
<*> C.require cfg "smtpUser"
<*> C.require cfg "smtpKey"
SmtpConfig
<$> C.require cfg "smtpHost"
<*> ((fmap . fmap) fromInteger $ C.lookup cfg "smtpPort")
<*> C.require cfg "smtpUser"
<*> C.require cfg "smtpKey"
BillingConfig <$> C.require cfg "networkMode"
<*> (fromText <$> C.require cfg "signingKeyFile")
<*> (fromText <$> C.require cfg "certsFile")
<*> C.require cfg "exchangeRateServiceURI"
BillingConfig
<$> C.require cfg "networkMode"
<*> (fromText <$> C.require cfg "signingKeyFile")
<*> (fromText <$> C.require cfg "certsFile")
<*> C.require cfg "exchangeRateServiceURI"
ConnectInfo <$> C.require cfg "host"
<*> C.require cfg "port"
<*> C.require cfg "user"
<*> C.require cfg "password"
<*> C.require cfg "database"
ConnectInfo
<$> C.require cfg "host"
<*> C.require cfg "port"
<*> C.require cfg "user"
<*> C.require cfg "password"
<*> C.require cfg "database"
Just _ -> fail $ "Only RSA keys are currently supported for payment request signing."
Nothing -> fail $ "No keys found in private key file " <> encodeString (c ^. signingKeyFile)
Just _ ->
fail
$ "Only RSA keys are currently supported for payment request signing."
Nothing -> fail $ "No keys found in private key file " <> encodeString
(c ^. signingKeyFile)
import Data.Aeson (FromJSON(..), ToJSON(..))
import Aftok.TimeLog.Serialization (depfFromJSON, depfToJSON)
import Aftok.Types (DepreciationFunction)
import Data.Aeson ( FromJSON(..)
, ToJSON(..)
)
import Aftok.TimeLog.Serialization ( depfFromJSON
, depfToJSON
)
import Aftok.Types ( DepreciationFunction )
import Control.Monad.Trans.Except (ExceptT(..), throwE, runExceptT)
import Crypto.Random.Types (MonadRandom,
getRandomBytes)
import Data.Aeson (Value, toJSON)
import Control.Monad.Trans.Except ( ExceptT(..)
, throwE
, runExceptT
)
import Crypto.Random.Types ( MonadRandom
, getRandomBytes
)
import Data.Aeson ( Value
, toJSON
)
import qualified Data.List as L
import Data.ProtocolBuffers (decodeMessage,
encodeMessage)
import Data.Serialize.Get (runGet)
import Data.Serialize.Put (runPut)
import Data.Thyme.Clock as C
import qualified Data.List as L
import Data.ProtocolBuffers ( decodeMessage
, encodeMessage
)
import Data.Serialize.Get ( runGet )
import Data.Serialize.Put ( runPut )
import Data.Thyme.Clock as C
import Database.PostgreSQL.Simple.SqlQQ (sql)
import Database.PostgreSQL.Simple.Types (Null)
import Safe (headMay)
import Database.PostgreSQL.Simple.SqlQQ
( sql )
import Database.PostgreSQL.Simple.Types
( Null )
import Safe ( headMay )
import Aftok.Json (billableJSON,
createSubscriptionJSON,
paymentJSON,
paymentRequestJSON)
import Aftok.Json ( billableJSON
, createSubscriptionJSON
, paymentJSON
, paymentRequestJSON
)
import Network.Bippy.Types (Satoshi(..))
import Network.Haskoin.Address (Address, stringToAddr, addrToString)
import Network.Haskoin.Constants (Network)
import Network.Bippy.Types ( Satoshi(..) )
import Network.Haskoin.Address ( Address
, stringToAddr
, addrToString
)
import Network.Haskoin.Constants ( Network )
else maybe (returnError UnexpectedNull f "event type may not be null")
(maybe (returnError Incompatible f "unrecognized event type value") pure . nameEvent . decodeUtf8)
v
else maybe
(returnError UnexpectedNull f "event type may not be null")
( maybe (returnError Incompatible f "unrecognized event type value") pure
. nameEvent
. decodeUtf8
)
v
let parser :: Text -> RowParser (CreditTo (NetworkId, Address))
parser "credit_to_address" =
CreditToCurrency <$> (addressParser mode <* nullField <* nullField)
parser "credit_to_user" =
CreditToUser <$> (nullField *> nullField *> idParser UserId <* nullField)
parser "credit_to_project" =
CreditToProject <$> (nullField *> nullField *> nullField *> idParser ProjectId)
parser _ = empty
in do
tn <- typename f
if tn /= "credit_to_t"
then returnError Incompatible f "column was not of type credit_to_t"
else maybe empty (pure . parser . decodeUtf8) v
let
parser :: Text -> RowParser (CreditTo (NetworkId, Address))
parser "credit_to_address" =
CreditToCurrency <$> (addressParser mode <* nullField <* nullField)
parser "credit_to_user" =
CreditToUser <$> (nullField *> nullField *> idParser UserId <* nullField)
parser "credit_to_project" =
CreditToProject
<$> (nullField *> nullField *> nullField *> idParser ProjectId)
parser _ = empty
in
do
tn <- typename f
if tn /= "credit_to_t"
then returnError Incompatible f "column was not of type credit_to_t"
else maybe empty (pure . parser . decodeUtf8) v
LogEntry <$> creditToParser mode
<*> (fieldWith eventTypeParser <*> utcParser)
<*> field
LogEntry
<$> creditToParser mode
<*> (fieldWith eventTypeParser <*> utcParser)
<*> field
A.Auction <$> idParser ProjectId
<*> idParser UserId
<*> utcParser
<*> btcParser
<*> utcParser
<*> utcParser
A.Auction
<$> idParser ProjectId
<*> idParser UserId
<*> utcParser
<*> btcParser
<*> utcParser
<*> utcParser
User <$> (UserName <$> field)
<*> ((null *> null *> pure Nothing) <|> fmap Just (addressParser mode))
<*> (Email <$> field)
User
<$> (UserName <$> field)
<*> ((null *> null *> pure Nothing) <|> fmap Just (addressParser mode))
<*> (Email <$> field)
P.Project <$> field
<*> utcParser
<*> idParser UserId
<*> (unSerDepFunction <$> fieldWith fromJSONField)
P.Project
<$> field
<*> utcParser
<*> idParser UserId
<*> (unSerDepFunction <$> fieldWith fromJSONField)
P.Invitation <$> idParser ProjectId
<*> idParser UserId
<*> fmap Email field
<*> utcParser
<*> fmap (fmap toThyme) field
P.Invitation
<$> idParser ProjectId
<*> idParser UserId
<*> fmap Email field
<*> utcParser
<*> fmap (fmap toThyme) field
B.Billable <$> idParser ProjectId
<*> idParser UserId
<*> field
<*> field
<*> recurrenceParser
<*> btcParser
<*> field
<*> fieldWith (optionalField nominalDiffTimeParser)
<*> field
<*> field
B.Billable
<$> idParser ProjectId
<*> idParser UserId
<*> field
<*> field
<*> recurrenceParser
<*> btcParser
<*> field
<*> fieldWith (optionalField nominalDiffTimeParser)
<*> field
<*> field
B.Subscription <$> idParser UserId
<*> idParser B.BillableId
<*> (toThyme <$> field)
<*> ((fmap toThyme) <$> field)
B.Subscription
<$> idParser UserId
<*> idParser B.BillableId
<*> (toThyme <$> field)
<*> ((fmap toThyme) <$> field)
PaymentRequest <$> fmap B.SubscriptionId field
<*> ((either (const empty) pure . runGet decodeMessage) =<< field)
<*> fmap PaymentKey field
<*> fmap toThyme field
<*> fmap toThyme field
PaymentRequest
<$> fmap B.SubscriptionId field
<*> ((either (const empty) pure . runGet decodeMessage) =<< field)
<*> fmap PaymentKey field
<*> fmap toThyme field
<*> fmap toThyme field
Payment <$> (PaymentRequestId <$> field)
<*> (field >>= (either (const empty) pure . runGet decodeMessage))
<*> (toThyme <$> field)
<*> field
Payment
<$> (PaymentRequestId <$> field)
<*> (field >>= (either (const empty) pure . runGet decodeMessage))
<*> (toThyme <$> field)
<*> field
storeEvent (CreateSubscription uid bid t) =
Just $ storeEventJSON (Just uid) "create_subscription" (createSubscriptionJSON uid bid t)
storeEvent (CreateSubscription uid bid t) = Just $ storeEventJSON
(Just uid)
"create_subscription"
(createSubscriptionJSON uid bid t)
pgEval (CreateEvent (ProjectId pid) (UserId uid) (LogEntry c e m)) =
case c of
CreditToCurrency (nid, addr) -> do
mode <- askNetworkMode
let network = toNetwork mode nid
pinsert EventId
[sql| INSERT INTO work_events
pgEval (CreateEvent (ProjectId pid) (UserId uid) (LogEntry c e m)) = case c of
CreditToCurrency (nid, addr) -> do
mode <- askNetworkMode
let network = toNetwork mode nid
pinsert
EventId
[sql| INSERT INTO work_events
let q (Before e) = pquery (logEntryParser mode)
[sql| SELECT credit_to_type, credit_to_address, credit_to_user_id, credit_to_project_id,
let
q (Before e) = pquery
(logEntryParser mode)
[sql| SELECT credit_to_type, credit_to_address, credit_to_user_id, credit_to_project_id,
(pid, uid, fromThyme e)
q (During s e) = pquery (logEntryParser mode)
[sql| SELECT credit_to_type, credit_to_address, credit_to_user_id, credit_to_project_id,
(pid, uid, fromThyme e)
q (During s e) = pquery
(logEntryParser mode)
[sql| SELECT credit_to_type, credit_to_address, credit_to_user_id, credit_to_project_id,
(pid, uid, fromThyme s, fromThyme e)
q (After s) = pquery (logEntryParser mode)
[sql| SELECT credit_to_type, credit_to_address, credit_to_user_id, credit_to_project_id,
(pid, uid, fromThyme s, fromThyme e)
q (After s) = pquery
(logEntryParser mode)
[sql| SELECT credit_to_type, credit_to_address, credit_to_user_id, credit_to_project_id,
pgEval (AmendEvent (EventId eid) (TimeChange mt t)) =
pinsert AmendmentId
[sql| INSERT INTO event_time_amendments
pgEval (AmendEvent (EventId eid) (TimeChange mt t)) = pinsert
AmendmentId
[sql| INSERT INTO event_time_amendments
pgEval (AmendEvent (EventId eid) (MetadataChange mt v)) =
pinsert AmendmentId
[sql| INSERT INTO event_metadata_amendments
pgEval (AmendEvent (EventId eid) (MetadataChange mt v)) = pinsert
AmendmentId
[sql| INSERT INTO event_metadata_amendments
pgEval (CreateAuction auc) =
pinsert A.AuctionId
[sql| INSERT INTO auctions (project_id, initiator_id, raise_amount, end_time)
pgEval (CreateAuction auc) = pinsert
A.AuctionId
[sql| INSERT INTO auctions (project_id, initiator_id, raise_amount, end_time)
( auc ^. (A.projectId . _ProjectId)
, auc ^. (A.initiator . _UserId)
, auc ^. (A.raiseAmount . satoshi)
, auc ^. (A.auctionEnd . to fromThyme)
)
( auc ^. (A.projectId . _ProjectId)
, auc ^. (A.initiator . _UserId)
, auc ^. (A.raiseAmount . satoshi)
, auc ^. (A.auctionEnd . to fromThyme)
)
pgEval (FindAuction aucId) =
headMay <$> pquery auctionParser
[sql| SELECT project_id, initiator_id, created_at, raise_amount, start_time, end_time
pgEval (FindAuction aucId) = headMay <$> pquery
auctionParser
[sql| SELECT project_id, initiator_id, created_at, raise_amount, start_time, end_time
pgEval (CreateBid (A.AuctionId aucId) bid) =
pinsert A.BidId
[sql| INSERT INTO bids (auction_id, bidder_id, bid_seconds, bid_amount, bid_time)
pgEval (CreateBid (A.AuctionId aucId) bid) = pinsert
A.BidId
[sql| INSERT INTO bids (auction_id, bidder_id, bid_seconds, bid_amount, bid_time)
( aucId
, bid ^. (A.bidUser . _UserId)
, case bid ^. A.bidSeconds of (Seconds i) -> i
, bid ^. (A.bidAmount . satoshi)
, bid ^. (A.bidTime . to fromThyme)
)
( aucId
, bid ^. (A.bidUser . _UserId)
, case bid ^. A.bidSeconds of
(Seconds i) -> i
, bid ^. (A.bidAmount . satoshi)
, bid ^. (A.bidTime . to fromThyme)
)
pgEval (FindBids aucId) =
pquery ((,) <$> idParser A.BidId <*> bidParser)
[sql| SELECT id, bidder_id, bid_seconds, bid_amount, bid_time FROM bids WHERE auction_id = ? |]
(Only (aucId ^. A._AuctionId))
pgEval (FindBids aucId) = pquery
((,) <$> idParser A.BidId <*> bidParser)
[sql| SELECT id, bidder_id, bid_seconds, bid_amount, bid_time FROM bids WHERE auction_id = ? |]
(Only (aucId ^. A._AuctionId))
pgEval (FindInvitation ic) =
headMay <$> pquery invitationParser
[sql| SELECT project_id, invitor_id, invitee_email, invitation_time, acceptance_time
pgEval (FindInvitation ic) = headMay <$> pquery
invitationParser
[sql| SELECT project_id, invitor_id, invitee_email, invitation_time, acceptance_time
pgEval (CreateProject p) =
pinsert ProjectId
[sql| INSERT INTO projects (project_name, inception_date, initiator_id, depreciation_fn)
pgEval (CreateProject p) = pinsert
ProjectId
[sql| INSERT INTO projects (project_name, inception_date, initiator_id, depreciation_fn)
( p ^. P.projectName
, p ^. (P.inceptionDate . to fromThyme)
, p ^. (P.initiator . _UserId)
, toJSON $ p ^. P.depf . to SerDepFunction
)
( p ^. P.projectName
, p ^. (P.inceptionDate . to fromThyme)
, p ^. (P.initiator . _UserId)
, toJSON $ p ^. P.depf . to SerDepFunction
)
pgEval (FindProject (ProjectId pid)) =
headMay <$> pquery projectParser
[sql| SELECT project_name, inception_date, initiator_id, depreciation_fn FROM projects WHERE id = ? |]
(Only pid)
pgEval (FindProject (ProjectId pid)) = headMay <$> pquery
projectParser
[sql| SELECT project_name, inception_date, initiator_id, depreciation_fn FROM projects WHERE id = ? |]
(Only pid)
pgEval (FindUserProjects (UserId uid)) =
pquery ((,) <$> idParser ProjectId <*> projectParser)
[sql| SELECT p.id, p.project_name, p.inception_date, p.initiator_id, p.depreciation_fn
pgEval (FindUserProjects (UserId uid)) = pquery
((,) <$> idParser ProjectId <*> projectParser)
[sql| SELECT p.id, p.project_name, p.inception_date, p.initiator_id, p.depreciation_fn
pgEval (AddUserToProject pid current new) = void $
pexec
[sql| INSERT INTO project_companions (project_id, user_id, invited_by) VALUES (?, ?, ?) |]
(pid ^. _ProjectId, new ^. _UserId, current ^. _UserId)
pgEval (AddUserToProject pid current new) = void $ pexec
[sql| INSERT INTO project_companions (project_id, user_id, invited_by) VALUES (?, ?, ?) |]
(pid ^. _ProjectId, new ^. _UserId, current ^. _UserId)
pgEval (FindBillable bid) =
headMay <$> pquery billableParser
[sql| SELECT b.project_id, e.created_by, b.name, b.description,
pgEval (FindBillable bid) = headMay <$> pquery
billableParser
[sql| SELECT b.project_id, e.created_by, b.name, b.description,
pgEval (FindBillables pid) =
pquery ((,) <$> idParser B.BillableId <*> billableParser)
[sql| SELECT b.id, b.project_id, e.created_by, b.name, b.description,
pgEval (FindBillables pid) = pquery
((,) <$> idParser B.BillableId <*> billableParser)
[sql| SELECT b.id, b.project_id, e.created_by, b.name, b.description,
pgEval (FindSubscription sid) =
headMay <$> pquery subscriptionParser
[sql| SELECT id, billable_id, start_date, end_date
pgEval (FindSubscription sid) = headMay <$> pquery
subscriptionParser
[sql| SELECT id, billable_id, start_date, end_date
pgEval (FindSubscriptions uid pid) =
pquery ((,) <$> idParser B.SubscriptionId <*> subscriptionParser)
[sql| SELECT s.id, user_id, billable_id, start_date, end_date
pgEval (FindSubscriptions uid pid) = pquery
((,) <$> idParser B.SubscriptionId <*> subscriptionParser)
[sql| SELECT s.id, user_id, billable_id, start_date, end_date
pgEval (FindPaymentRequest (PaymentKey k)) =
headMay <$> pquery ((,) <$> idParser PaymentRequestId <*> paymentRequestParser)
pgEval (FindPaymentRequest (PaymentKey k)) = headMay <$> pquery
((,) <$> idParser PaymentRequestId <*> paymentRequestParser)
rowp = (,,,) <$> (PaymentKey <$> field)
<*> paymentRequestParser
<*> subscriptionParser
<*> billableParser
in pquery rowp
[sql| SELECT r.url_key,
rowp =
(,,,)
<$> (PaymentKey <$> field)
<*> paymentRequestParser
<*> subscriptionParser
<*> billableParser
in pquery
rowp
[sql| SELECT r.url_key,
import Control.Lens (view, (^.), makeClassyPrisms, traverseOf)
import Control.Monad.Trans.Maybe (MaybeT (..))
import Control.Lens ( view
, (^.)
, makeClassyPrisms
, traverseOf
)
import Control.Monad.Trans.Maybe ( MaybeT(..) )
CreateUser :: BTCUser -> DBOp UserId
FindUser :: UserId -> DBOp (Maybe BTCUser)
FindUserByName :: UserName -> DBOp (Maybe (UserId, BTCUser))
CreateUser ::BTCUser -> DBOp UserId
FindUser ::UserId -> DBOp (Maybe BTCUser)
FindUserByName ::UserName -> DBOp (Maybe (UserId, BTCUser))
CreateProject :: Project -> DBOp ProjectId
FindProject :: ProjectId -> DBOp (Maybe Project)
ListProjects :: DBOp [ProjectId]
FindSubscribers :: ProjectId -> DBOp [UserId]
FindUserProjects :: UserId -> DBOp [(ProjectId, Project)]
AddUserToProject :: ProjectId -> InvitingUID -> InvitedUID -> DBOp ()
CreateInvitation :: ProjectId -> InvitingUID -> Email -> C.UTCTime -> DBOp InvitationCode
FindInvitation :: InvitationCode -> DBOp (Maybe Invitation)
AcceptInvitation :: UserId -> InvitationCode -> C.UTCTime -> DBOp ()
CreateProject ::Project -> DBOp ProjectId
FindProject ::ProjectId -> DBOp (Maybe Project)
ListProjects ::DBOp [ProjectId]
FindSubscribers ::ProjectId -> DBOp [UserId]
FindUserProjects ::UserId -> DBOp [(ProjectId, Project)]
AddUserToProject ::ProjectId -> InvitingUID -> InvitedUID -> DBOp ()
CreateInvitation ::ProjectId -> InvitingUID -> Email -> C.UTCTime -> DBOp InvitationCode
FindInvitation ::InvitationCode -> DBOp (Maybe Invitation)
AcceptInvitation ::UserId -> InvitationCode -> C.UTCTime -> DBOp ()
CreateEvent :: ProjectId -> UserId -> LogEntry BTCNet -> DBOp EventId
AmendEvent :: EventId -> EventAmendment BTCNet -> DBOp AmendmentId
FindEvent :: EventId -> DBOp (Maybe (KeyedLogEntry BTCNet))
FindEvents :: ProjectId -> UserId -> Interval' -> DBOp [LogEntry BTCNet]
ReadWorkIndex :: ProjectId -> DBOp (WorkIndex BTCNet)
CreateEvent ::ProjectId -> UserId -> LogEntry BTCNet -> DBOp EventId
AmendEvent ::EventId -> EventAmendment BTCNet -> DBOp AmendmentId
FindEvent ::EventId -> DBOp (Maybe (KeyedLogEntry BTCNet))
FindEvents ::ProjectId -> UserId -> Interval' -> DBOp [LogEntry BTCNet]
ReadWorkIndex ::ProjectId -> DBOp (WorkIndex BTCNet)
CreateAuction :: Auction -> DBOp AuctionId
FindAuction :: AuctionId -> DBOp (Maybe Auction)
CreateBid :: AuctionId -> Bid -> DBOp BidId
FindBids :: AuctionId -> DBOp [(BidId, Bid)]
CreateAuction ::Auction -> DBOp AuctionId
FindAuction ::AuctionId -> DBOp (Maybe Auction)
CreateBid ::AuctionId -> Bid -> DBOp BidId
FindBids ::AuctionId -> DBOp [(BidId, Bid)]
CreateBillable :: UserId -> Billable -> DBOp BillableId
FindBillable :: BillableId -> DBOp (Maybe Billable)
FindBillables :: ProjectId -> DBOp [(BillableId, Billable)]
CreateBillable ::UserId -> Billable -> DBOp BillableId
FindBillable ::BillableId -> DBOp (Maybe Billable)
FindBillables ::ProjectId -> DBOp [(BillableId, Billable)]
CreateSubscription :: UserId -> BillableId -> T.Day -> DBOp SubscriptionId
FindSubscription :: SubscriptionId -> DBOp (Maybe Subscription)
FindSubscriptions :: UserId -> ProjectId -> DBOp [(SubscriptionId, Subscription)]
CreateSubscription ::UserId -> BillableId -> T.Day -> DBOp SubscriptionId
FindSubscription ::SubscriptionId -> DBOp (Maybe Subscription)
FindSubscriptions ::UserId -> ProjectId -> DBOp [(SubscriptionId, Subscription)]
CreatePaymentRequest :: PaymentRequest -> DBOp PaymentRequestId
FindPaymentRequests :: SubscriptionId -> DBOp [(PaymentRequestId, PaymentRequest)]
FindUnpaidRequests :: SubscriptionId -> DBOp [BillDetail]
FindPaymentRequest :: PaymentKey -> DBOp (Maybe (PaymentRequestId, PaymentRequest))
FindPaymentRequestId :: PaymentRequestId -> DBOp (Maybe PaymentRequest)
CreatePaymentRequest ::PaymentRequest -> DBOp PaymentRequestId
FindPaymentRequests ::SubscriptionId -> DBOp [(PaymentRequestId, PaymentRequest)]
FindUnpaidRequests ::SubscriptionId -> DBOp [BillDetail]
FindPaymentRequest ::PaymentKey -> DBOp (Maybe (PaymentRequestId, PaymentRequest))
FindPaymentRequestId ::PaymentRequestId -> DBOp (Maybe PaymentRequest)
CreatePayment :: Payment -> DBOp PaymentId
FindPayments :: PaymentRequestId -> DBOp [(PaymentId, Payment)]
CreatePayment ::Payment -> DBOp PaymentId
FindPayments ::PaymentRequestId -> DBOp [(PaymentId, Payment)]
auc <- MaybeT $ liftdb findOp
_ <- lift $ checkProjectAuth (auc ^. A.projectId) uid findOp
pure auc
auc <- MaybeT $ liftdb findOp
_ <- lift $ checkProjectAuth (auc ^. A.projectId) uid findOp
pure auc
maybeAuc <- liftdb findOp
_ <- traverse (\auc -> checkProjectAuth (auc ^. A.projectId) uid findOp) maybeAuc
maybe (raiseSubjectNotFound findOp) pure maybeAuc
maybeAuc <- liftdb findOp
_ <- traverse
(\auc -> checkProjectAuth (auc ^. A.projectId) uid findOp)
maybeAuc
maybe (raiseSubjectNotFound findOp) pure maybeAuc
auc <- findAuction' aid uid
if view bidTime bid > view auctionEnd auc
then raiseOpForbidden uid AuctionEnded createOp
else liftdb createOp
auc <- findAuction' aid uid
if view bidTime bid > view auctionEnd auc
then raiseOpForbidden uid AuctionEnded createOp
else liftdb createOp
( Interval(..), interval, start, end, ilen
, Interval'(..), interval', start', end'
, intervalJSON, parseIntervalJSON
( Interval(..)
, interval
, start
, end
, ilen
, Interval'(..)
, interval'
, start'
, end'
, intervalJSON
, parseIntervalJSON
import Control.FromSum (fromMaybeM, fromEitherM)
import Control.Lens hiding ((.=))
import Control.Monad.Fail (MonadFail(..))
import Control.FromSum ( fromMaybeM
, fromEitherM
)
import Control.Lens hiding ( (.=) )
import Control.Monad.Fail ( MonadFail(..) )
import qualified Data.Attoparsec.ByteString.Char8 as PC
import qualified Data.ByteString.Base64 as B64
import qualified Data.ByteString.Char8 as C
import qualified Data.Attoparsec.ByteString.Char8
as PC
import qualified Data.ByteString.Base64 as B64
import qualified Data.ByteString.Char8 as C
import Data.HashMap.Strict as O
import Data.List.NonEmpty as L
import Data.Map.Strict as MS
import Data.ProtocolBuffers (encodeMessage)
import Data.Serialize.Put (runPut)
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Data.Thyme.Calendar (showGregorian)
import Data.Thyme.Clock as Clock
import Data.Thyme.Time (Day)
import Data.UUID as U
import Data.HashMap.Strict as O
import Data.List.NonEmpty as L
import Data.Map.Strict as MS
import Data.ProtocolBuffers ( encodeMessage )
import Data.Serialize.Put ( runPut )
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Data.Thyme.Calendar ( showGregorian )
import Data.Thyme.Clock as Clock
import Data.Thyme.Time ( Day )
import Data.UUID as U
v = QuasiQuoter { quoteExp = quoteVersionExp
, quotePat = error "Pattern quasiquotation of versions not supported."
, quoteType = error "Type quasiquotation of versions not supported."
, quoteDec = error "Dec quasiquotation of versions not supported."
}
v = QuasiQuoter
{ quoteExp = quoteVersionExp
, quotePat = error "Pattern quasiquotation of versions not supported."
, quoteType = error "Type quasiquotation of versions not supported."
, quoteDec = error "Dec quasiquotation of versions not supported."
}
projectJSON p = v1 $
obj [ "projectName" .= (p ^. projectName)
, "inceptionDate" .= (p ^. inceptionDate)
, "initiator" .= (p ^. P.initiator . _UserId)
]
projectJSON p = v1 $ obj
[ "projectName" .= (p ^. projectName)
, "inceptionDate" .= (p ^. inceptionDate)
, "initiator" .= (p ^. P.initiator . _UserId)
]
auctionJSON x = v1 $
obj [ "projectId" .= idValue (A.projectId._ProjectId) x
, "initiator" .= idValue (A.initiator._UserId) x
, "raiseAmount" .= (x ^. (raiseAmount . satoshi))
]
auctionJSON x = v1 $ obj
[ "projectId" .= idValue (A.projectId . _ProjectId) x
, "initiator" .= idValue (A.initiator . _UserId) x
, "raiseAmount" .= (x ^. (raiseAmount . satoshi))
]
creditToJSON nmode (CreditToCurrency (netId, addr)) =
v2 $ obj [ "creditToAddress" .= addrToJSON (toNetwork nmode netId) addr
, "creditToNetwork" .= renderNetworkId netId
]
creditToJSON nmode (CreditToCurrency (netId, addr)) = v2 $ obj
[ "creditToAddress" .= addrToJSON (toNetwork nmode netId) addr
, "creditToNetwork" .= renderNetworkId netId
]
:: NetworkMode
-> NetworkId
-> Text
-> Parser (CreditTo (NetworkId, Address))
parseBtcAddr nmode net addrText =
maybe
(fail . T.unpack $ "Address " <> addrText <> " cannot be parsed as a BTC network address.")
(pure . CreditToCurrency . (net,))
(stringToAddr (toNetwork nmode net) addrText)
:: NetworkMode -> NetworkId -> Text -> Parser (CreditTo (NetworkId, Address))
parseBtcAddr nmode net addrText = maybe
( fail
. T.unpack
$ "Address "
<> addrText
<> " cannot be parsed as a BTC network address."
)
(pure . CreditToCurrency . (net, ))
(stringToAddr (toNetwork nmode net) addrText)
let parseCreditToAddr = do
netName <- o .: "creditToNetwork"
net <- fromMaybeM
(fail . T.unpack $ "Currency network " <> netName <> " not recognized.")
(parseNetworkId netName)
addrValue <- o .: "creditToAddress"
CreditToCurrency . (net,) <$> addrFromJSON (toNetwork nmode net) addrValue
let
parseCreditToAddr = do
netName <- o .: "creditToNetwork"
net <- fromMaybeM
(fail . T.unpack $ "Currency network " <> netName <> " not recognized.")
(parseNetworkId netName)
addrValue <- o .: "creditToAddress"
CreditToCurrency
. (net, )
<$> addrFromJSON (toNetwork nmode net) addrValue
payoutsJSON :: NetworkMode -> Payouts (NetworkId, Address)-> Value
payoutsJSON nmode (Payouts m) = v2 $
let payoutsRec :: (CreditTo (NetworkId, Address), Rational) -> Value
payoutsRec (c, r) = object [ "creditTo" .= creditToJSON nmode c
, "payoutRatio" .= r
]
in obj $ [ "payouts" .= fmap payoutsRec (MS.assocs m) ]
payoutsJSON :: NetworkMode -> Payouts (NetworkId, Address) -> Value
payoutsJSON nmode (Payouts m) =
v2
$ let payoutsRec :: (CreditTo (NetworkId, Address), Rational) -> Value
payoutsRec (c, r) =
object ["creditTo" .= creditToJSON nmode c, "payoutRatio" .= r]
in obj $ ["payouts" .= fmap payoutsRec (MS.assocs m)]
let parsePayoutRecord x = (,) <$> (parseCreditToV2 nmode =<< (x .: "creditTo"))
<*> (x .: "payoutRatio")
in Payouts . MS.fromList <$> (traverse parsePayoutRecord =<< parseJSON (Object val))
let parsePayoutRecord x =
(,)
<$> (parseCreditToV2 nmode =<< (x .: "creditTo"))
<*> (x .: "payoutRatio")
in Payouts
. MS.fromList
<$> (traverse parsePayoutRecord =<< parseJSON (Object val))
workIndexJSON nmode (WorkIndex widx) = v2 $
let widxRec :: (CreditTo (NetworkId, Address), NonEmpty Interval) -> Value
widxRec (c, l) = object [ "creditTo" .= creditToJSON nmode c
, "intervals" .= (intervalJSON <$> L.toList l)
]
in obj $ [ "workIndex" .= fmap widxRec (MS.assocs widx) ]
workIndexJSON nmode (WorkIndex widx) =
v2
$ let widxRec :: (CreditTo (NetworkId, Address), NonEmpty Interval) -> Value
widxRec (c, l) = object
[ "creditTo" .= creditToJSON nmode c
, "intervals" .= (intervalJSON <$> L.toList l)
]
in obj $ ["workIndex" .= fmap widxRec (MS.assocs widx)]
logEntryJSON nmode (LogEntry c ev m) = v2 $
obj [ "creditTo" .= creditToJSON nmode c
, "event" .= logEventJSON' ev
, "eventMeta" .= m
]
logEntryJSON nmode (LogEntry c ev m) = v2 $ obj
[ "creditTo" .= creditToJSON nmode c
, "event" .= logEventJSON' ev
, "eventMeta" .= m
]
recurrenceJSON' B.Annually = object [ "annually" .= Null ]
recurrenceJSON' (B.Monthly i) = object [ "monthly " .= object [ "months" .= i ] ]
recurrenceJSON' B.Annually = object ["annually" .= Null]
recurrenceJSON' (B.Monthly i) = object ["monthly " .= object ["months" .= i]]
recurrenceJSON' (B.Weekly i) = object [ "weekly " .= object [ "weeks" .= i ] ]
recurrenceJSON' B.OneTime = object [ "onetime" .= Null ]
recurrenceJSON' (B.Weekly i) = object ["weekly " .= object ["weeks" .= i]]
recurrenceJSON' B.OneTime = object ["onetime" .= Null]
createSubscriptionJSON uid bid d = v1 $
obj [ "user_id" .= idValue _UserId uid
, "billable_id" .= idValue B._BillableId bid
, "start_date" .= showGregorian d
]
createSubscriptionJSON uid bid d = v1 $ obj
[ "user_id" .= idValue _UserId uid
, "billable_id" .= idValue B._BillableId bid
, "start_date" .= showGregorian d
]
billDetailJSON r =
obj $ concat
[ ["payment_request_id" .= view (_1 . _PaymentKey) r]
, paymentRequestKV $ view _2 r
, subscriptionKV $ view _3 r
, billableKV $ view _4 r
]
billDetailJSON r = obj $ concat
[ ["payment_request_id" .= view (_1 . _PaymentKey) r]
, paymentRequestKV $ view _2 r
, subscriptionKV $ view _3 r
, billableKV $ view _4 r
]
paymentJSON r = v1 $
obj [ "payment_request_id" .= idValue (request . _PaymentRequestId) r
, "payment_protobuf_64" .= view paymentBytes r
, "payment_date" .= (r ^. paymentDate)
]
where
paymentBytes = payment . to (T.decodeUtf8 . B64.encode . runPut . encodeMessage)
paymentJSON r = v1 $ obj
[ "payment_request_id" .= idValue (request . _PaymentRequestId) r
, "payment_protobuf_64" .= view paymentBytes r
, "payment_date" .= (r ^. paymentDate)
]
where
paymentBytes =
payment . to (T.decodeUtf8 . B64.encode . runPut . encodeMessage)
let parseA :: Text -> Parser (EventAmendment (NetworkId, Address))
parseA "timeChange" = TimeChange t <$> o .: "eventTime"
parseA "addrChange" = CreditToChange t <$> parseCreditToV1 nmode o
parseA "metadataChange" = MetadataChange t <$> o .: "eventMeta"
parseA tid = fail . T.unpack $ "Amendment type " <> tid <> " not recognized."
in o .: "amendment" >>= parseA
let
parseA :: Text -> Parser (EventAmendment (NetworkId, Address))
parseA "timeChange" = TimeChange t <$> o .: "eventTime"
parseA "addrChange" = CreditToChange t <$> parseCreditToV1 nmode o
parseA "metadataChange" = MetadataChange t <$> o .: "eventMeta"
parseA tid =
fail . T.unpack $ "Amendment type " <> tid <> " not recognized."
in
o .: "amendment" >>= parseA
let parseA :: Text -> Parser (EventAmendment (NetworkId, Address))
parseA "timeChange" = TimeChange t <$> o .: "eventTime"
parseA "creditToChange" = CreditToChange t <$> parseCreditToV2 nmode o
parseA "metadataChange" = MetadataChange t <$> o .: "eventMeta"
parseA tid = fail . T.unpack $ "Amendment type " <> tid <> " not recognized."
in o .: "amendment" >>= parseA
let
parseA :: Text -> Parser (EventAmendment (NetworkId, Address))
parseA "timeChange" = TimeChange t <$> o .: "eventTime"
parseA "creditToChange" = CreditToChange t <$> parseCreditToV2 nmode o
parseA "metadataChange" = MetadataChange t <$> o .: "eventMeta"
parseA tid =
fail . T.unpack $ "Amendment type " <> tid <> " not recognized."
in
o .: "amendment" >>= parseA
let parseAnnually o' = const (pure B.Annually) <$> O.lookup "annually" o'
parseMonthly o' = fmap B.Monthly . parseJSON <$> O.lookup "monthly" o'
parseWeekly o' = fmap B.Weekly . parseJSON <$> O.lookup "weekly" o'
parseOneTime o' = const (pure B.OneTime) <$> O.lookup "one-time" o'
let
parseAnnually o' = const (pure B.Annually) <$> O.lookup "annually" o'
parseMonthly o' = fmap B.Monthly . parseJSON <$> O.lookup "monthly" o'
parseWeekly o' = fmap B.Weekly . parseJSON <$> O.lookup "weekly" o'
parseOneTime o' = const (pure B.OneTime) <$> O.lookup "one-time" o'
notFound = fail $ "Value " <> show o <> " does not represent a Recurrence value."
parseV val = parseAnnually val
<|> parseMonthly val
<|> parseWeekly val
<|> parseOneTime val
in fromMaybe notFound $ parseV o
notFound =
fail $ "Value " <> show o <> " does not represent a Recurrence value."
parseV val =
parseAnnually val
<|> parseMonthly val
<|> parseWeekly val
<|> parseOneTime val
in
fromMaybe notFound $ parseV o
import Data.Aeson (Value)
import Data.Thyme.Clock as C
import Data.Thyme.Time as C
import qualified Data.Text as T
import Data.Aeson ( Value )
import Data.Thyme.Clock as C
import Data.Thyme.Time as C
import qualified Data.Text as T
import qualified Network.Bippy.Proto as P
import Network.Bippy.Types (expiryTime, getExpires,
getPaymentDetails, Satoshi(..))
import Network.Haskoin.Address.Base58 (decodeBase58Check)
import qualified Network.Bippy.Proto as P
import Network.Bippy.Types ( expiryTime
, getExpires
, getPaymentDetails
, Satoshi(..)
)
import Network.Haskoin.Address.Base58 ( decodeBase58Check )
-- using error here is reasonable since it would indicate
-- a serialization problem
in either (error . T.pack) (check . getExpires) $ getPaymentDetails (view paymentRequest req)
-- using error here is reasonable since it would indicate
-- a serialization problem
in either (error . T.pack) (check . getExpires)
$ getPaymentDetails (view paymentRequest req)
import Control.Lens (makeLenses, makePrisms)
import Crypto.Random.Types (MonadRandom, getRandomBytes)
import Control.Lens ( makeLenses
, makePrisms
)
import Crypto.Random.Types ( MonadRandom
, getRandomBytes
)
import qualified Data.ByteString as BS
import Data.ByteString.Base64.URL as B64
import Data.Thyme.Clock as C
import qualified Data.ByteString as BS
import Data.ByteString.Base64.URL as B64
import Data.Thyme.Clock as C
import Data.Aeson (Value(..), (.=), (.:), object)
import Data.Aeson.Types (Parser)
import Data.Functor ((<$>))
import Data.Text (unpack)
import Data.Aeson ( Value(..)
, (.=)
, (.:)
, object
)
import Data.Aeson.Types ( Parser )
import Data.Functor ( (<$>) )
import Data.Text ( unpack )
LinearDepreciation (Months up) (Months dp) ->
object [ "type" .= ("LinearDepreciation" :: Text)
, "arguments" .= object [ "undep" .= up
, "dep" .= dp
]
]
LinearDepreciation (Months up) (Months dp) -> object
[ "type" .= ("LinearDepreciation" :: Text)
, "arguments" .= object ["undep" .= up, "dep" .= dp]
]
import Control.Lens (makeLenses, makePrisms)
import Data.Maybe (Maybe)
import Data.Eq (Eq)
import Data.Functor (Functor)
import Data.Ord (Ord)
import Data.Text (Text)
import Data.UUID (UUID)
import Prelude (Integer)
import Text.Show (Show)
import Control.Lens ( makeLenses
, makePrisms
)
import Data.Maybe ( Maybe )
import Data.Eq ( Eq )
import Data.Functor ( Functor )
import Data.Ord ( Ord )
import Data.Text ( Text )
import Data.UUID ( UUID )
import Prelude ( Integer )
import Text.Show ( Show )
import System.Environment (getEnvironment)
import Filesystem.Path.CurrentOS (fromText, encodeString)
import qualified Filesystem.Path.CurrentOS as P
import System.Environment ( getEnvironment )
import Filesystem.Path.CurrentOS ( fromText
, encodeString
)
import qualified Filesystem.Path.CurrentOS as P
QConfig <$> C.lookupDefault "localhost" cfg "hostname"
<*> C.lookupDefault 8000 cfg "port"
<*> (fromText <$> C.require cfg "siteKey")
<*> C.lookup cfg "cookieTimeout"
<*> maybe (mkPGSConfig $ C.subconfig "db" cfg) pure pc
<*> readSmtpConfig cfg
<*> (readBillingConfig $ C.subconfig "billing" cfg)
<*> (fromText <$> C.lookupDefault "/opt/aftok/server/templates/" cfg "templatePath")
<*> (fromText <$> C.lookupDefault "/opt/aftok/server/static/" cfg "staticAssetPath")
QConfig
<$> C.lookupDefault "localhost" cfg "hostname"
<*> C.lookupDefault 8000 cfg "port"
<*> (fromText <$> C.require cfg "siteKey")
<*> C.lookup cfg "cookieTimeout"
<*> maybe (mkPGSConfig $ C.subconfig "db" cfg) pure pc
<*> readSmtpConfig cfg
<*> (readBillingConfig $ C.subconfig "billing" cfg)
<*> (fromText <$> C.lookupDefault "/opt/aftok/server/templates/"
cfg
"templatePath"
)
<*> (fromText <$> C.lookupDefault "/opt/aftok/server/static/"
cfg
"staticAssetPath"
)
import Aftok.Types (UserId)
import Aftok.Auction (Auction (..), AuctionId, Bid (..), BidId)
import Aftok.Database (createAuction, createBid, findAuction)
import Aftok.Types ( UserId )
import Aftok.Auction ( Auction(..)
, AuctionId
, Bid(..)
, BidId
)
import Aftok.Database ( createAuction
, createBid
, findAuction
)
snapEval . createAuction $
Auction pid uid t (Satoshi . raiseAmount $ req) (auctionStart req) (auctionEnd req)
snapEval . createAuction $ Auction pid
uid
t
(Satoshi . raiseAmount $ req)
(auctionStart req)
(auctionEnd req)
fromMaybeT
(snapError 404 $ "Auction not found for id " <> show aid)
(mapMaybeT snapEval $ findAuction aid uid) -- this will verify auction access
fromMaybeT (snapError 404 $ "Auction not found for id " <> show aid)
(mapMaybeT snapEval $ findAuction aid uid) -- this will verify auction access
import Control.Error.Util (maybeT)
import Control.Monad.Trans.Maybe (mapMaybeT)
import Data.Attoparsec.ByteString (parseOnly)
import Control.Error.Util ( maybeT )
import Control.Monad.Trans.Maybe ( mapMaybeT )
import Data.Attoparsec.ByteString ( parseOnly )
(uname, pwd) <- either (throwDenied . AU.AuthError) pure $ parseOnly authHeaderParser rawHeader
authResult <- with auth $ AU.loginByUsername uname (AU.ClearText pwd) False
(uname, pwd) <- either (throwDenied . AU.AuthError) pure
$ parseOnly authHeaderParser rawHeader
authResult <- with auth $ AU.loginByUsername uname (AU.ClearText pwd) False
modifyResponse $ (setResponseStatus 401 "Unauthorized") .
(setHeader "WWW-Authenticate" "Basic realm=aftok")
getResponse >>= finishWith
modifyResponse
$ (setResponseStatus 401 "Unauthorized")
. (setHeader "WWW-Authenticate" "Basic realm=aftok")
getResponse >>= finishWith
modifyResponse $ setResponseStatus 403 "Access Denied"
writeText $ "Access Denied: " <> show failure
getResponse >>= finishWith
modifyResponse $ setResponseStatus 403 "Access Denied"
writeText $ "Access Denied: " <> show failure
getResponse >>= finishWith
Billable <$> pure pid
<*> pure uid
<*> o .: "name"
<*> o .: "description"
<*> (parseRecurrence' =<< o .: "recurrence")
<*> (Satoshi <$> o .: "amount")
<*> o .: "gracePeriod"
<*> (fmap toThyme <$> o .: "requestExpiryPeriod")
<*> o .:? "paymentRequestEmailTemplate"
<*> o .:? "paymentRequestMemoTemplate"
Billable
<$> pure pid
<*> pure uid
<*> o
.: "name"
<*> o
.: "description"
<*> (parseRecurrence' =<< o .: "recurrence")
<*> (Satoshi <$> o .: "amount")
<*> o
.: "gracePeriod"
<*> (fmap toThyme <$> o .: "requestExpiryPeriod")
<*> o
.:? "paymentRequestEmailTemplate"
<*> o
.:? "paymentRequestMemoTemplate"
import Control.Lens (view, _1, _2, _Right, _Left, preview, (&), (.~), (^.))
import Control.Monad.Trans.Maybe (mapMaybeT)
import Control.Exception (try)
import Control.Lens ( view
, _1
, _2
, _Right
, _Left
, preview
, (&)
, (.~)
, (^.)
)
import Control.Monad.Trans.Maybe ( mapMaybeT )
import Control.Exception ( try )
import Data.ProtocolBuffers (decodeMessage)
import Data.Serialize.Get (runGetLazy)
import Data.Thyme.Clock as C
import qualified Data.Text.Encoding as T
import qualified Network.Bippy.Proto as P
import Data.ProtocolBuffers ( decodeMessage )
import Data.Serialize.Get ( runGetLazy )
import Data.Thyme.Clock as C
import qualified Data.Text.Encoding as T
import qualified Network.Bippy.Proto as P
import Network.HTTP.Client (defaultManagerSettings, managerResponseTimeout, responseTimeoutMicro, HttpException)
import Network.Wreq (asValue, responseBody, defaults, manager, getWith)
import OpenSSL.Session (context)
import Network.HTTP.Client ( defaultManagerSettings
, managerResponseTimeout
, responseTimeoutMicro
, HttpException
)
import Network.Wreq ( asValue
, responseBody
, defaults
, manager
, getWith
)
import OpenSSL.Session ( context )
preq <- getPaymentRequestHandler'
pmnt <- either
(\msg -> snapError 400 $ "Could not decode payment response: " <> show msg)
pure
(runGetLazy decodeMessage requestBody)
now <- liftIO $ C.getCurrentTime
preq <- getPaymentRequestHandler'
pmnt <- either
(\msg -> snapError 400 $ "Could not decode payment response: " <> show msg)
pure
(runGetLazy decodeMessage requestBody)
now <- liftIO $ C.getCurrentTime
let opts = defaults & manager .~ Left (opensslManagerSettings context)
& manager .~ Left (defaultManagerSettings { managerResponseTimeout = responseTimeoutMicro 10000 } )
let
opts =
defaults
& manager
.~ Left (opensslManagerSettings context)
& manager
.~ Left
(defaultManagerSettings
{ managerResponseTimeout = responseTimeoutMicro 10000
}
)
pkey <- maybe
(snapError 400 $ "parameter paymentRequestKey is formatted incorrectly.") pure
(parsePaymentKey pkBytes)
pkey <- maybe
(snapError 400 $ "parameter paymentRequestKey is formatted incorrectly.")
pure
(parsePaymentKey pkBytes)
import Control.Monad.Trans.Maybe (mapMaybeT, runMaybeT)
import Data.Aeson as A
import Data.Attoparsec.ByteString (takeByteString)
import Data.Thyme.Clock as C
import Filesystem.Path.CurrentOS (encodeString)
import qualified Filesystem.Path.CurrentOS as F
import Control.Monad.Trans.Maybe ( mapMaybeT
, runMaybeT
)
import Data.Aeson as A
import Data.Attoparsec.ByteString ( takeByteString )
import Data.Thyme.Clock as C
import Filesystem.Path.CurrentOS ( encodeString )
import qualified Filesystem.Path.CurrentOS as F
cp <- either (snapError 400 . show) pure $ A.eitherDecode requestBody
t <- liftIO C.getCurrentTime
cp <- either (snapError 400 . show) pure $ A.eitherDecode requestBody
t <- liftIO C.getCurrentTime
fromMaybeT
(snapError 404 $ "Project not found for id " <> show pid)
(mapMaybeT snapEval $ findUserProject uid pid)
fromMaybeT (snapError 404 $ "Project not found for id " <> show pid)
(mapMaybeT snapEval $ findUserProject uid pid)
t <- liftIO C.getCurrentTime
(Just u, Just p, invCode) <- snapEval $
(,,) <$> (runMaybeT $ findUser uid)
<*> (runMaybeT $ findUserProject uid pid)
<*> createInvitation pid uid toEmail t
liftIO $ sendProjectInviteEmail cfg (p ^. projectName) (u ^. userEmail) toEmail invCode
t <- liftIO C.getCurrentTime
(Just u, Just p, invCode) <-
snapEval
$ (,,)
<$> (runMaybeT $ findUser uid)
<*> (runMaybeT $ findUserProject uid pid)
<*> createInvitation pid uid toEmail t
liftIO $ sendProjectInviteEmail cfg
(p ^. projectName)
(u ^. userEmail)
toEmail
invCode
sendProjectInviteEmail :: QConfig
-> ProjectName
-> Email -- Inviting user's email address
-> Email -- Invitee's email address
-> InvitationCode
-> IO ()
sendProjectInviteEmail
:: QConfig
-> ProjectName
-> Email -- Inviting user's email address
-> Email -- Invitee's email address
-> InvitationCode
-> IO ()
let SmtpConfig{..} = cfg ^. QC.smtpConfig
mailer = maybe (sendMailWithLogin _smtpHost) (sendMailWithLogin' _smtpHost) _smtpPort
in buildProjectInviteEmail (cfg ^. templatePath) pn fromEmail toEmail invCode >>=
(mailer _smtpUser _smtpPass)
let SmtpConfig {..} = cfg ^. QC.smtpConfig
mailer = maybe (sendMailWithLogin _smtpHost)
(sendMailWithLogin' _smtpHost)
_smtpPort
in buildProjectInviteEmail (cfg ^. templatePath) pn fromEmail toEmail invCode
>>= (mailer _smtpUser _smtpPass)
buildProjectInviteEmail :: F.FilePath
-> ProjectName
-> Email -- Inviting user's email address
-> Email -- Invitee's email address
-> InvitationCode
-> IO Mail
buildProjectInviteEmail
:: F.FilePath
-> ProjectName
-> Email -- Inviting user's email address
-> Email -- Invitee's email address
-> InvitationCode
-> IO Mail
let setAttrs = setAttribute "from_email" (fromEmail ^. _Email) .
setAttribute "project_name" pn .
setAttribute "to_email" (toEmail ^. _Email) .
setAttribute "inv_code" (renderInvCode invCode)
let setAttrs =
setAttribute "from_email" (fromEmail ^. _Email)
. setAttribute "project_name" pn
. setAttribute "to_email" (toEmail ^. _Email)
. setAttribute "inv_code" (renderInvCode invCode)
import Data.Aeson as A
import qualified Data.Map.Strict as M
import Data.Text as T
import Data.Thyme.Clock as C
import Data.Aeson as A
import qualified Data.Map.Strict as M
import Data.Text as T
import Data.Thyme.Clock as C
let parseUser = User <$> (UserName <$> v .: "username")
<*> (v .: "btcAddr")
<*> (Email <$> v .: "email")
let parseUser =
User
<$> (UserName <$> v .: "username")
<*> (v .: "btcAddr")
<*> (Email <$> v .: "email")
userData <- maybe (snapError 400 "Could not parse user data") pure $ A.decode requestBody
t <- liftIO C.getCurrentTime
userData <- maybe (snapError 400 "Could not parse user data") pure
$ A.decode requestBody
t <- liftIO C.getCurrentTime
let addr = stringToAddr (toNetwork nmode BTC) =<< (userData ^. cuser . userAddress)
let createSUser = AU.createUser (userData ^. (cuser.username._UserName)) (userData ^. password)
createQUser = snapEval $ do
userId <- createUser ((userData ^. cuser) & userAddress .~ ((BTC,) <$> addr))
void $ traverse (acceptInvitation userId t) (userData ^. invitationCodes)
return userId
let addr =
stringToAddr (toNetwork nmode BTC) =<< (userData ^. cuser . userAddress)
let
createSUser = AU.createUser (userData ^. (cuser . username . _UserName))
(userData ^. password)
createQUser = snapEval $ do
userId <- createUser
((userData ^. cuser) & userAddress .~ ((BTC, ) <$> addr))
void $ traverse (acceptInvitation userId t) (userData ^. invitationCodes)
return userId
uid <- requireUserId
t <- liftIO C.getCurrentTime
params <- getParams
invCodes <- maybe
(snapError 400 "invCode parameter is required")
(pure . traverse (parseInvCode . decodeUtf8))
(M.lookup "invCode" params)
uid <- requireUserId
t <- liftIO C.getCurrentTime
params <- getParams
invCodes <- maybe (snapError 400 "invCode parameter is required")
(pure . traverse (parseInvCode . decodeUtf8))
(M.lookup "invCode" params)
import qualified Data.Text as T
import Data.Thyme.Clock as C
import Data.UUID as U
import Network.Haskoin.Address (Address, stringToAddr)
import qualified Data.Text as T
import Data.Thyme.Clock as C
import Data.UUID as U
import Network.Haskoin.Address ( Address
, stringToAddr
)
timestamp <- liftIO C.getCurrentTime
case A.eitherDecode requestBody >>= parseEither (parseLogEntry nmode uid evCtr) of
Left err ->
snapError 400 $ "Unable to parse log entry " <> (show requestBody) <> ": " <> show err
Right entry ->
snapEval $ createEvent pid uid (entry timestamp)
timestamp <- liftIO C.getCurrentTime
case
A.eitherDecode requestBody >>= parseEither (parseLogEntry nmode uid evCtr)
of
Left err ->
snapError 400
$ "Unable to parse log entry "
<> (show requestBody)
<> ": "
<> show err
Right entry -> snapEval $ createEvent pid uid (entry timestamp)
snapError 400 $ "Unable to parse bitcoin address from " <> (show addrBytes)
Just addr ->
snapEval . createEvent pid uid $
LogEntry (CreditToCurrency (BTC, addr))
(evCtr timestamp)
(A.decode requestBody)
snapError 400
$ "Unable to parse bitcoin address from "
<> (show addrBytes)
Just addr -> snapEval . createEvent pid uid $ LogEntry
(CreditToCurrency (BTC, addr))
(evCtr timestamp)
(A.decode requestBody)
ival <- case endpoints of
(Just s, Just e) -> pure $ During s e
(Nothing, Just e) -> pure $ Before e
(Just s, Nothing) -> pure $ After s
(Nothing, Nothing) -> snapError 400 "You must at least one of the \"after\" or \"before\" query parameter"
ival <- case endpoints of
(Just s , Just e ) -> pure $ During s e
(Nothing, Just e ) -> pure $ Before e
(Just s , Nothing) -> pure $ After s
(Nothing, Nothing) -> snapError
400
"You must at least one of the \"after\" or \"before\" query parameter"
eventId <- maybe
(snapError 400 "eventId parameter is required")
(pure . EventId)
(eventIdBytes >>= U.fromASCIIBytes)
modTime <- ModTime <$> liftIO C.getCurrentTime
eventId <- maybe (snapError 400 "eventId parameter is required")
(pure . EventId)
(eventIdBytes >>= U.fromASCIIBytes)
modTime <- ModTime <$> liftIO C.getCurrentTime
either
(snapError 400 . T.pack)
(snapEval . amendEvent uid eventId)
(parseEither (parseEventAmendment nmode modTime) requestJSON)
either (snapError 400 . T.pack)
(snapEval . amendEvent uid eventId)
(parseEither (parseEventAmendment nmode modTime) requestJSON)
handleDBError (SubjectNotFound) =
snapError 404 "The subject of the requested operation could not be found."
handleDBError (SubjectNotFound) = snapError
404
"The subject of the requested operation could not be found."
parseParam :: MonadSnap m
=> Text -- ^ the name of the parameter to be parsed
-> Parser a -- ^ parser for the value of the parameter
-> m a -- ^ the parsed value
parseParam
:: MonadSnap m
=> Text -- ^ the name of the parameter to be parsed
-> Parser a -- ^ parser for the value of the parameter
-> m a -- ^ the parsed value
maybe (snapError 400 $ "Value of parameter \"" <> name <> "\" is not a valid UUID") pure maybeId
where
idParser = do
bs <- takeByteString
pure $ f <$> fromASCIIBytes bs
maybe
(snapError 400 $ "Value of parameter \"" <> name <> "\" is not a valid UUID"
)
pure
maybeId
where
idParser = do
bs <- takeByteString
pure $ f <$> fromASCIIBytes bs
import Control.Lens ((^.), to)
import Control.Exception (try)
import qualified Data.Aeson as A
import Data.ProtocolBuffers (encodeMessage)
import Data.Serialize.Put (runPutLazy)
import Filesystem.Path.CurrentOS (decodeString, encodeString)
import Control.Lens ( (^.)
, to
)
import Control.Exception ( try )
import qualified Data.Aeson as A
import Data.ProtocolBuffers ( encodeMessage )
import Data.Serialize.Put ( runPutLazy )
import Filesystem.Path.CurrentOS ( decodeString
, encodeString
)
cfg <- loadQConfig . decodeString $ fromRight "conf/aftok.cfg" cfgPath
sconf <- snapConfig cfg
cfg <- loadQConfig . decodeString $ fromRight "conf/aftok.cfg" cfgPath
sconf <- snapConfig cfg
sesss <- nestSnaplet "sessions" sess $
initCookieSessionManager (cfg ^. authSiteKey . to encodeString)
"quookie"
(Just "aftok.com")
(cfg ^. cookieTimeout)
sesss <- nestSnaplet "sessions" sess $ initCookieSessionManager
(cfg ^. authSiteKey . to encodeString)
"quookie"
(Just "aftok.com")
(cfg ^. cookieTimeout)
let nmode = cfg ^. billingConfig . C.networkMode
loginRoute = method GET requireLogin >> redirect "/home"
xhrLoginRoute = void $ method POST requireLogin
registerRoute = void $ method POST registerHandler
inviteRoute = void $ method POST (projectInviteHandler cfg)
acceptInviteRoute = void $ method POST acceptInvitationHandler
projectCreateRoute = serveJSON projectIdJSON $ method POST projectCreateHandler
projectListRoute = serveJSON (fmap qdbProjectJSON) $ method GET projectListHandler
projectRoute = serveJSON projectJSON $ method GET projectGetHandler
logEntriesRoute = serveJSON (fmap $ logEntryJSON nmode) $ method GET logEntriesHandler
logIntervalsRoute = serveJSON (workIndexJSON nmode) $ method GET loggedIntervalsHandler
payoutsRoute = serveJSON (payoutsJSON nmode) $ method GET payoutsHandler
let
nmode = cfg ^. billingConfig . C.networkMode
logWorkRoute f = serveJSON eventIdJSON $ method POST (logWorkHandler f)
logWorkBTCRoute f = serveJSON eventIdJSON $ method POST (logWorkBTCHandler f)
amendEventRoute = serveJSON amendmentIdJSON $ method PUT amendEventHandler
loginRoute = method GET requireLogin >> redirect "/home"
xhrLoginRoute = void $ method POST requireLogin
registerRoute = void $ method POST registerHandler
auctionCreateRoute = serveJSON auctionIdJSON $ method POST auctionCreateHandler
auctionRoute = serveJSON auctionJSON $ method GET auctionGetHandler
auctionBidRoute = serveJSON bidIdJSON $ method POST auctionBidHandler
inviteRoute = void $ method POST (projectInviteHandler cfg)
acceptInviteRoute = void $ method POST acceptInvitationHandler
billableCreateRoute = serveJSON billableIdJSON $ method POST billableCreateHandler
billableListRoute = serveJSON (fmap qdbBillableJSON) $ method GET billableListHandler
subscribeRoute = serveJSON subscriptionIdJSON $ method POST subscribeHandler
projectCreateRoute =
serveJSON projectIdJSON $ method POST projectCreateHandler
projectListRoute =
serveJSON (fmap qdbProjectJSON) $ method GET projectListHandler
payableRequestsRoute = serveJSON billDetailsJSON $ method GET listPayableRequestsHandler
getPaymentRequestRoute = writeLBS . runPutLazy . encodeMessage =<< method GET getPaymentRequestHandler
submitPaymentRoute = serveJSON paymentIdJSON $ method POST (paymentResponseHandler $ cfg ^. billingConfig)
projectRoute = serveJSON projectJSON $ method GET projectGetHandler
logEntriesRoute =
serveJSON (fmap $ logEntryJSON nmode) $ method GET logEntriesHandler
logIntervalsRoute =
serveJSON (workIndexJSON nmode) $ method GET loggedIntervalsHandler
, ("login", loginRoute)
, ("login", xhrLoginRoute)
, ("register", registerRoute)
, ("accept_invitation", acceptInviteRoute)
logWorkRoute f = serveJSON eventIdJSON $ method POST (logWorkHandler f)
logWorkBTCRoute f =
serveJSON eventIdJSON $ method POST (logWorkBTCHandler f)
amendEventRoute = serveJSON amendmentIdJSON $ method PUT amendEventHandler
, ("projects/:projectId/logStart/:btcAddr", logWorkBTCRoute StartWork)
, ("projects/:projectId/logEnd/:btcAddr", logWorkBTCRoute StopWork)
, ("projects/:projectId/logStart", logWorkRoute StartWork)
, ("projects/:projectId/logEnd", logWorkRoute StopWork)
, ("projects/:projectId/logEntries", logEntriesRoute)
, ("projects/:projectId/intervals", logIntervalsRoute)
, ("projects/:projectId/auctions", auctionCreateRoute) -- <|> auctionListRoute
, ("projects/:projectId/billables", billableCreateRoute <|> billableListRoute)
, ("projects/:projectId/payouts", payoutsRoute)
, ("projects/:projectId/invite", inviteRoute)
, ("projects/:projectId", projectRoute)
, ("projects", projectCreateRoute <|> projectListRoute)
auctionCreateRoute =
serveJSON auctionIdJSON $ method POST auctionCreateHandler
auctionRoute = serveJSON auctionJSON $ method GET auctionGetHandler
auctionBidRoute = serveJSON bidIdJSON $ method POST auctionBidHandler
, ("auctions/:auctionId", auctionRoute)
, ("auctions/:auctionId/bid", auctionBidRoute)
billableCreateRoute =
serveJSON billableIdJSON $ method POST billableCreateHandler
billableListRoute =
serveJSON (fmap qdbBillableJSON) $ method GET billableListHandler
subscribeRoute =
serveJSON subscriptionIdJSON $ method POST subscribeHandler
, ("subscribe/:billableId", subscribeRoute)
, ("subscriptions/:subscriptionId/payment_requests", payableRequestsRoute)
, ("pay/:paymentRequestKey", getPaymentRequestRoute <|> submitPaymentRoute)
payableRequestsRoute =
serveJSON billDetailsJSON $ method GET listPayableRequestsHandler
getPaymentRequestRoute =
writeLBS
. runPutLazy
. encodeMessage
=<< method GET getPaymentRequestHandler
submitPaymentRoute = serveJSON paymentIdJSON
$ method POST (paymentResponseHandler $ cfg ^. billingConfig)
, ("events/:eventId/amend", amendEventRoute)
]
addRoutes
[ ("static", serveDirectory . encodeString $ cfg ^. staticAssetPath)
, ("login" , loginRoute)
, ("login" , xhrLoginRoute)
, ("register" , registerRoute)
, ("accept_invitation" , acceptInviteRoute)
, ("projects/:projectId/logStart/:btcAddr", logWorkBTCRoute StartWork)
, ("projects/:projectId/logEnd/:btcAddr", logWorkBTCRoute StopWork)
, ("projects/:projectId/logStart" , logWorkRoute StartWork)
, ("projects/:projectId/logEnd" , logWorkRoute StopWork)
, ("projects/:projectId/logEntries" , logEntriesRoute)
, ("projects/:projectId/intervals" , logIntervalsRoute)
, ( "projects/:projectId/auctions"
, auctionCreateRoute
) -- <|> auctionListRoute
, ( "projects/:projectId/billables"
, billableCreateRoute <|> billableListRoute
)
, ("projects/:projectId/payouts", payoutsRoute)
, ("projects/:projectId/invite" , inviteRoute)
, ("projects/:projectId" , projectRoute)
, ("projects" , projectCreateRoute <|> projectListRoute)
, ("auctions/:auctionId" , auctionRoute)
, ("auctions/:auctionId/bid" , auctionBidRoute)
, ("subscribe/:billableId" , subscribeRoute)
, ("subscriptions/:subscriptionId/payment_requests", payableRequestsRoute)
, ("pay/:paymentRequestKey", getPaymentRequestRoute <|> submitPaymentRoute)
, ("events/:eventId/amend" , amendEventRoute)
]
genBid = Bid <$> (UserId <$> genUUID)
<*> (Seconds <$> arbitrary `suchThat` (>= 0))
<*> genSatoshi `suchThat` (> Satoshi 0)
<*> arbitrary
genBid =
Bid
<$> (UserId <$> genUUID)
<*> (Seconds <$> arbitrary `suchThat` (>= 0))
<*> genSatoshi
`suchThat` (> Satoshi 0)
<*> arbitrary
let testB0 = Bid (UserId nil) (Seconds 3) (Satoshi 100) (read "2016-03-05 15:59:26.033176 UTC")
testB1 = Bid (UserId nil) (Seconds 60) (Satoshi 1000)(read "2016-03-05 15:59:26.033177 UTC")
testB2 = Bid (UserId nil) (Seconds 60) (Satoshi 100) (read "2016-03-05 15:59:26.033178 UTC")
testB3 = Bid (UserId nil) (Seconds 90) (Satoshi 100) (read "2016-03-05 15:59:26.033179 UTC")
testB4 = Bid (UserId nil) (Seconds 60) (Satoshi 100) (read "2016-03-05 15:59:26.033180 UTC")
in do
describe "bid ordering" $ do
it "ensures that bids with lowest seconds/btc ratio are first" $ do
bidOrder testB0 testB1 `shouldBe` LT
bidOrder testB1 testB2 `shouldBe` LT
bidOrder testB2 testB3 `shouldBe` LT
let
testB0 = Bid (UserId nil)
(Seconds 3)
(Satoshi 100)
(read "2016-03-05 15:59:26.033176 UTC")
testB1 = Bid (UserId nil)
(Seconds 60)
(Satoshi 1000)
(read "2016-03-05 15:59:26.033177 UTC")
testB2 = Bid (UserId nil)
(Seconds 60)
(Satoshi 100)
(read "2016-03-05 15:59:26.033178 UTC")
testB3 = Bid (UserId nil)
(Seconds 90)
(Satoshi 100)
(read "2016-03-05 15:59:26.033179 UTC")
testB4 = Bid (UserId nil)
(Seconds 60)
(Satoshi 100)
(read "2016-03-05 15:59:26.033180 UTC")
in
do
describe "bid ordering" $ do
it "ensures that bids with lowest seconds/btc ratio are first" $ do
bidOrder testB0 testB1 `shouldBe` LT
bidOrder testB1 testB2 `shouldBe` LT
bidOrder testB2 testB3 `shouldBe` LT
describe "winning bids" $ do
it "determines a sufficient number of winners to fulfill the raise amount" $
let result = runAuction' (Satoshi 1250) [testB0, testB1, testB2, testB3, testB4]
split = Bid (UserId nil) (Seconds 30) (Satoshi 50) (testB4 ^. bidTime)
expected = sortBy bidOrder [testB0, testB1, testB2, split]
in case result of
WinningBids winners ->
sortBy bidOrder winners `shouldBe` expected
describe "winning bids" $ do
it
"determines a sufficient number of winners to fulfill the raise amount"
$ let
result = runAuction' (Satoshi 1250)
[testB0, testB1, testB2, testB3, testB4]
split =
Bid (UserId nil) (Seconds 30) (Satoshi 50) (testB4 ^. bidTime)
expected = sortBy bidOrder [testB0, testB1, testB2, split]
in
case result of
WinningBids winners ->
sortBy bidOrder winners `shouldBe` expected
InsufficientBids _ ->
assertFailure "Sufficinent bids were presented, but auction algorithm asserted otherwise."
InsufficientBids _ ->
assertFailure
"Sufficinent bids were presented, but auction algorithm asserted otherwise."
it "ensures that the raise amount is fully consumed by the winning bids" $
forAll ((,) <$> genSatoshi <*> listOf genBid) $
\(raiseAmount', bids) ->
case runAuction' raiseAmount' bids of
WinningBids xs -> bidsTotal xs == raiseAmount'
InsufficientBids t -> t == (raiseAmount' - bidsTotal bids)
it "ensures that the raise amount is fully consumed by the winning bids"
$ forAll ((,) <$> genSatoshi <*> listOf genBid)
$ \(raiseAmount', bids) -> case runAuction' raiseAmount' bids of
WinningBids xs -> bidsTotal xs == raiseAmount'
InsufficientBids t -> t == (raiseAmount' - bidsTotal bids)
startTime <- arbitrary
intervals <- suchThat (buildIntervals startTime <$> deltas) (not.null)
pure $ L.fromList intervals
startTime <- arbitrary
intervals <- suchThat (buildIntervals startTime <$> deltas) (not . null)
pure $ L.fromList intervals
it "reduces a log to a work index" $
let testAddrs = catMaybes
[ parseBtcAddr "123"
, parseBtcAddr "456"
, parseBtcAddr "789" ]
it "reduces a log to a work index"
$ let
testAddrs = catMaybes
[parseBtcAddr "123", parseBtcAddr "456", parseBtcAddr "789"]
in (workIndex testLogEntries) `shouldBe` expected
it "recovers a work index from events" $
forAll genWorkIndex $ \(WorkIndex widx) ->
let mergeAdjacent ((I.Interval s e) : (I.Interval s' e') : xs) | e == s' = mergeAdjacent $ I.Interval s e' : xs
it "recovers a work index from events"
$ forAll genWorkIndex
$ \(WorkIndex widx) ->
let
mergeAdjacent ((I.Interval s e) : (I.Interval s' e') : xs)
| e == s' = mergeAdjacent $ I.Interval s e' : xs
ivalEntries addr ival = LogEntry addr <$> [StartWork (ival ^. start), StopWork (ival ^. end)]
<*> [Nothing]
ivalEntries addr ival =
LogEntry addr
<$> [StartWork (ival ^. start), StopWork (ival ^. end)]
<*> [Nothing]