X3ES7NUA42D2BF7CQDDKXM5CLMVCYA3H5YU5KXLPTGDBFPE2LNVAC SQ7UMLN5WCPHIF66RO4UQVX6RSNRRZBOVZP7HEMSKP7VO6YNQPRAC F4ONFXF4MSA3QM64T7ATRVO3NQR2MC3RVZGVNGSQXCKXXQX2UG7QC AXKKXBWN4EMUOLV43WN52JSKJPBV7TLSGLNJW5EZXHSJNKCYUWOQC IPG33FAWXGEQ2PO6OXRT2PWWXHRNMPVUKKADL6UKKN5GD2CNZ25AC B6HWAPDPXIWH7CHK5VLMWLL6EQN6NOFZEFYO47BPUY2ZO4SL7VDAC 4R7XIYK3BP664CO3YJ2VM64ES2JYN27UTQG5KS34OTEPAIODSZLQC EFSXYZPOGA5M4DN65IEIDO7JK6U34DMQELAPHOIL2UAT6HRC66BAC UWMGUJOW5X5HQTS76T2FD7MNAJF7SESPQVU5FDIZO52V75TT2X6AC LTSVBVA235BQAIU3SQURKSRHIAL33K47G4J6TSEP2K353OCHNJEAC ENNZIQJG4XJ62QCNRMLNAXN7ICTPCHQFZTURX6QSUYYWNADFJHXQC NEDDHXUK3GNFMOFO3KLU7NRIHCTYNWBT3D6HTKZAOXFDE6HMDZ6AC LAROLAYUGJ4Q5AEFV5EJMIA2ZKBNCBWHHHPCJ3CKCNIUIYUKRFVQC HBULCDN6E75FAPILFVLTQIKABDEWL3HZTBLICLCWOIKDRYM6UIBQC NAS4BFL43SIUAAC663R6VFBHQ2DKI45K6Y6ZKVQI7S5547HBAN7QC IZEVQF627FA7VV25KJAWYWGGC35LZUUBBQRPN5ZAINTQLPEDRTEAC RPAJLHMTUJU4AYNBOHVGHGGB4NY2NLY3BVPYN5FMWB3ZIMAUQHCQC 5ZSKPQ3KY6T6O5S6T6HW4OHJMQXA72WKJSJJMGKGX2WMFTNZ7EGAC TLQ72DSJD7GGPWN6HGBHAVPBRQFKEQ6KSK43U7JWWID4ZWAF47JAC 75N3UJ4JK56KXF56GASGPAWLFYGJDETVJNYTF4KXFCQM767JUU5AC EKI57EJR65DA5FPILAHGHHAIU5ITVGHA6V3775OX7GV5XD67OWRQC 2XQD6KKKD6QVHFHAEMVE3XXY7X2T7BLTLL7XIILZAXNJJH2YONUQC GCVQD44VRPQVKPZEPIC4AOIXLJIG2ZMV3QI2Y7KALUT6NVUBSGSAC ZP62WC472OTQETO2HTHIQIPO57XZIWVKPA4KL62GYU4OZDMB6NSAC KEP5WUFJXTMKRRNZLYTGYYWA4VLFCMHTKTJYF5EA5IWBYFMU6WYQC WO2MINIF4TXOHWSE7JWXRZYN64XRVLYIRFMF4SMPSOXKA2V77KMQC LHJ2HFXVUQ4VG25I7DADWU73G5K5WNZBDQ3SVNKFYLZ5BEYM4XCQC 7HPY3QPFPN35PSPUBVNW2GTFB3CBQZBST4J2BAVJ7QMXLIUN52JAC DFOBMSAODB3NKW37B272ZXA2ML5HMIH3N3C4GT2DPEQS7ZFK4SNAC JFOEOFGA4CQR2LW43IVQGDZSPVJAD4KDN2DZMZXGM2QDIUD7AVCAC HMDM3B557TO5RYP2IGFFC2C2VN6HYZTDQ47CJY2O37BW55DSMFZAC AL37SVTCKRSG4HG2PCYK5Z7QSIZZH5JHH4Q2VLMXFAXSAQRFFG4QC SEWTRB6S5PO5MQBLCPVBD7XT2BDYNZUE2RO6Z2XENZRIOCN6OZJAC Q5X5RYQLP5K7REYD6VLHOKC4W36ZELJYA45V6YFKTD5S6MPN3NDQC 73NDXDEZRMK672GHSTC3CI6YHXFZ2GGJI5IKQGHKFDZKTNSQXLLQC Y3LIJ5USPMYBG7HKCIQBE7MWVGLQJPJSQD3KPZCTKXP22GOB535QC 5IDB3IWSB6LFW4U772Y7BH5Y3FQOQ7IFWLVXDZE5XS6SKJITFV4QC AWWC6P5ZVFDQHX3EAYDG4DKTUZ6A5LHQAV3NIUO3VP6FM7JKPK5AC HALRDT2F22DAQ54M334YSKD2XJCAXDXEZPDKGMOWKH7XELBAS6MAC U256ZALIPTVWLNACYPIMWLNEYDQWP7CHF4Y4CGMILQTONJHMGQVQC A6HKMINBNGQLLX4QJMYWKQ4JAEHVJ4HIRVDKPPDI3FJUO2AAB7OQC 4U7F3CPIDTK6JSEDMNMHVKSR7HOQDLZQD2PPVMDLHO5SFSIMUXZAC J6S23MDGHVSCVVIRB6XRNSY3EGTDNWFJHV7RYLIEHBUK5KU63CFQC RN7EI6INGUUHGMNY5RU3NH56WPLRZY5ZMYDNFMNE3TGV4ESFLQIAC BSIUHCGFDFDFGWYMHZB7OVU3Z3IHPEUXRISIOPGZI2RUXZFDS2EQC O227CEAV7BTKSE3SSC7XHC5IWEBXZL2AOOKJMBMOOFNTLINBLQMAC EW2XN7KUMCAQNVFJJ5YTAVDZCPHNWDOEDMRFBUGLY6IE2HKNNX5AC WAIX6AGNDVJOKTWZ7OP7QOYSJHAJSX5EOWXZHOAO2IG6ALWUCJ6QC ZTPDQKLAB6JJGUFYNBE2OYDW7LV64FNI6BXBO3TBWOM4YF5UWI5QC 3QVT6MA6I2CILQH3LUZABS4YQ7MN6CNRYTDRVS376OOHTPLYTFJAC QMRKFEPGFBCEWAIXPEIG5ILKAJ2JH5L3TOITHR4HNJXK5FN3KXBQC 2J37EVJMX255K3XEJHTZGRPEIRMAQ62JQWOA7JU3YTZUB6PUPWVQC RSF6UAJKG7CEKILSVXI6C4YZXY7PIYZM2EMA2IXKQ7SADKNVSH7QC 7KZP4RHZ3QSYTPPQ257A65Z5UPX44TF2LAI2U5EMULQCLDCEUK2AC EKY7U7SKPF45OOUAHJBEQKXSUXWOHFBQFFVJWPBN5ARFJUFM2BPAC TNR3TEHKVADAEZSTOD2XLSUTSW5AWST2YUW4CWK5KE7DSC6XHZNAC O5FVTOM6YFBLEPF3S576K6IMT6ZZ5VQCSB3YVXNS4CKBITKCRZ7AC 2G3GNDDUOVPF45PELJ65ZB2IXEHJJXJILFRVHZXGPXUL4BVNZJFQC LD4GLVSF6YTA7OZWIGJ45H6TUXGM4WKUIYXKWQFNUP36WDMYSMXAC ZIG57EE6RB22JGB3CT33EN2HVYCHCXBT5GROBTBMBLEMDTGQOOBQC 4FDQGIXN3Z4J55DILCSI5EOLIIA7R5CADTGFMW5X7N7MH6JIMBWAC QADKFHAR3KWQCNYU25Z7PJUGMD5WL26IU3DOAHBTRN2A7NKPUPKAC WZFQDWW4XK6M4A4PQ7WQJUTZUPRGQR7V7ZVZY5ZTL5AMGIFMHB2QC NLZ3JXLOOIL37O3RRQWXHNPNSNEOOLPD6MCB754BEBECQB3KGR2AC 64C6AWH66FDKU6UE6Z6JPX2J2GBM2JOPTH2GL6LHKAIUBGNGDZ5AC EMVTF2IWNQGRL44FC4JNG5FYYQTZSFPNM6SOM7IAEH6T7PPK2NVAC FXJQACESPGTLPG5ELXBU3M3OQXUZQQIR7HPIEHQ3FNUTMWVH4WBAC 5OI44E4EEVYOMHDWNK2WA7K4L4JWRWCUJUNN2UAUGE5VY4W7GTNAC ASF3UPJLCX7KIUCNJD5KAXSPDXCUEJHLL4HBRTRUBPCW73IXOCWQC EQXRXRZDYCM7BDAVBOXQYPG6C7IJT3OFGNIXCDAHJJBRKAXNGL7AC SCXG6TJWYIPRUMT27KGKIIF6FYKTUTY74UNZ2FQTT63XZ6HIF3AAC Z7KS5XHHC6PAMTVBHXY7KUSS3BWAOU6FSYIITUCFOOJZU4OUJHBAC W35DDBFYF6Z4ZPCFEO5RPAONZLYCSNTXUSTS6FIUUVZHCI6Q7GHAC XTBSG4C7SCZUFOU2BTNFR6B6TCGYI35BWUV4PVTS3N7KNH5VEARQC POX3UAMTGCNS3SU5I6IKDNCCSHEAUSFBZ3WCMQ3EXKPNXETOI6BAC Z3MK2PJ5U222DXRS22WCDHVPZ7HVAR3HOCUNXIGX6VMEPBQDF6PQC 7VGYLTMURLVSVUYFW7TCRZTDQ6RE2EPSPPA43XKHDOBFWYVVSJHQC UOG5H2TW5R3FSHQPJCEMNFDQZS5APZUP7OM54FIBQG7ZP4HASQ7QC GKLIPHR5YOBKEMC4744J3WYYFLPFXMZEOLC6Z26QXAG4IM2HQVEQC UILI6PILCRDPZ3XYA54LGIGPSU7ERWNHCE7R3CE64ZEC7ONOEMOQC BWN72T44GRRZ6K2OPN56FTLNEB7J7AGC7T2U5HSMLEKUPGJP2NUAC O722AOKEWXWJPRHGJREU6QPW7HEFPPRETZIAADZ2RMAXHARCNEKAC NVOCQVASZWTKQJG7GPH7KHKZZR7NUG4WLV5YY4KAIRPCJRWCZPIAC FD7SV5I6VCW27HZ3T3K4MMGB2OYGJTPKFFA263TNTAMRJGQJWVNAC P6NR2CGXCWAW6GXXSIXCGOBIRAS2BM4LEM6D5ADPN4IL7TMW6UVAC Y35QCWYW2OTZ27ZVTH2BA3XJTUCJ2WMLKU32ZCOCDY3AW7TIZXRAC 2OIPAQCBDIUJBXB4K2QVP3IEBIUOCQHSWSWFVMVSVZC7GHX2VK7AC KNSI575VAW6HRCZYXOEPQ4DTSML4EORML5MV4DJBRKE7TXCPS4EAC 5DRIWGLUKMQZU2ZPBXSTLAWJKAMOD5YXAHM5LEDQHDFGYYLHWCDQC GKGVYBZGPJXO7N7GLHLRNYQPXFHBQSNQN53OKRFCXLQEYDTC5I4QC 4QX5E5ACVN57KJLCWOM4JEI6JSV4XZNCWVYPOTKSOMUW3SOMCNJAC 7XN3I3QJHYMKU2DCUXX34WQMSJ4ZJOWW7FME34EANO3E5W4Q632AC 7DBNV3GV773FH5ZLQWFX4RBOS4Q3CIK2RYZNNABY3ZOETYZCXRNQC A2J7B4SCCJYKQV3G2LDHEFNE2GUICO3N3Y5FKF4EUZW5AG7PTDWAC N4NDAZYTLSI2W22KT3SYXL257DBMSH3UT2BXOYM7LH7FSZAY4RLAC SLL7262CJUE7TZDDZZXFROHCVVDA527WA4PHXCKEGZUJF2EN5MQAC PBD7LZYQHXAA3KLH2ZUX5GW4UFML6BQ32KXZF4KZ6OYFASUYFJ5QC V2VDN77HCSRYYWXDJJ2XOVHV4P6PVWNJZLXZ7JUYPQEZQIH5BZ3QC GMYPBCWEB6NKURRILAHR3TJUKDOGR2ZMK5I6MS6P5G2LAGH36P3QC MJ6R42RCK2ASXAJ6QXDPMAW56RBOJ4F4HI2LFIV3KXFIKWYMQK3QC 6L5BK5EHPAOQX3JCKUJ273UDNAC23LPQL4HIJGM4AV3P3QK5OKIQC O2BZOX7MS4JCDS3C6EJQXAWUEQV6HVDCIF2FIN2BCJNRLIU6ZVKAC I2KHGVD44KT4MQJXGCTVSQKMBO6TVCY72F26TLXGWRL6PHGF6RNQC NJNMO72S7VIUV22JXB4IFPZMHWTJAOTP6EC6Z4QSKYIROSXT52MQC PT4276XCOP5NJ3GRFJLIBZKVNVAOATAY5PLWV7FWK6RZW5FTEP5AC 2MNO5FUYXF6GHHWTIDLW2JGMFC3UY54BHJKUYVF7SZCUJQWKZ4DQC BROSTG5KP3NUNLSYPVQID254TE47E5RKQAKLPIY7PGWETE6JNMTAC MB5SHULBN3WP7TGUWZDP6BRGP423FTYKF67T5IF5YHHLNXKQ5REAC BXGLKYRXO2O4NRM3BLNWQ7AWVPQXAMFS57MFYHJNOZZEZZW5BH6AC Z24SZOGZJLDTDTGWH7M25RYQ7MYSU52ZLFWJ2PSQFTMK4J35PIWAC EZQG2APB36DDMIAYDPPDGOIXOD7K2RZZSGC2NKGZIHB2HZBTW7EQC QMEYU4MWLTSWPWEEOFRLK2IKE64BY3V5X73323WPLCGPP3TPDYGAC ADMKQQGCGVSHHIMVQ4XFRDCG544SBJCYALSKZV45CQQBZ4ACUH2AC 4ZLEDBK7VGLKFUPENAFLUJYNFLKFYJ3TREPQ7P6PKMYGJUXB55HQC TCOAKCGGHOIRJCTZYEZQ3K6KCGL2LGAYGYFRGSPCHBTJJY2V6AXAC HO2PFRABW6BBTE4MUKUTEGXCMJS46WGVBCNWOHO4OL52DVAB4YDAC Z3M53KTLZMPOISMHE25SZJSWX5TA37IV33IRE7KNRAD3PKEAEJXQC MGOF7IUFGXYQKZOKMM2GGULFFVAULEHLZDSHMUW6B5DBKVXXR74AC DXIGERDTERUIG7QHHRPKTSJHSQEPJPDJVLUW7YVC7URXBQ4ZJVOAC DJATFGIC75CQDWMFHIWOKFXF26GKPINREMP6FNNTLF75JZZ3EQEQC RFYEVKZQLOOQP536GRZOROSQW2O7TEHJ2HZDRVVUSBKLY5FBEO3QC UUR6SMCAJMA7O3ZFUCQMPZFDDIPUVQ5IHUAC5F252YVD6H3JIKPQC YWNTVA7PN7MC3HNTER3OCFHQAVKNJUK7KRQDZYFK24S5JLWHNU4AC 3GBSDS5PDSTTJTJOLEKZRRTAONS3T3JFZ3FQGFGS3AOXDBZ6SPLAC GLFF5ZDKWI7WKPZSAEE3IUM27LL6DFOPIL4VPODXYXV3BCSCJ6GQC NMWWP4ZNOKHZKSJ6F5KYEREWXXR5F4UD35WOKI3EH42AZWVCTCJAC OV5AKJHA773ETIJPTMQ7K64U7BRQE34OXJ6FJNH6TZG22WS5QTIAC JV3UEPNCNIPNEL3EM4MOJPWTD3ZNL5FUEBEMGNUDKFUXSAWTNKNQC 2KZPOGRBY6KBMO76F55ZKIVOLSG3O63VP3RHRZVANXYT3OLZ3OWQC RSEB2NFGUBTFESE5BJKDUVQL5Y5ZVGY5O4CJX2LNP63MS3NRHHZQC OBFPJS2GHO2PEHBHGEHKIUOUAFIQHPIZXEVD2YIE3ZIE2PVMH5VAC 4SCFOJGNDAN4XZEAPWQQCBJ3CGZCJP3HUADRQLYZ2ITAKA7EJJTQC WJO37T74RYR5DRMSVNCXAQBOV42FQB63EG43XDZUU5TA354AIJRAC nnoremap <leader>h :!~/.nix-profile/bin/hasktags -o tags -c $(find lib server daemon test -name \*.hs) && ~/.nix-profile/bin/ctags --options-maybe=.ctags --options=$HOME/.ctags --append=yes .<CR><CR>nnoremap <leader>o :!~/.nix-profile/bin/ormolu --mode inplace $(find lib server daemon test -name '*.hs')<CR><CR>
import Control.Error.Util ( maybeT )import Control.Lens ( (^.), makeLenses, makeClassyPrisms, traverseOf, to)import Control.Monad.Except ( MonadError, throwError)import Control.Monad.Trans.Except ( withExceptT )import Control.Monad.Trans.Reader ( mapReaderT )import Crypto.Random.Types ( MonadRandom(..) )import Database.PostgreSQL.Simple ( Connection, connect)import Data.Thyme.Clock as Cimport Data.Thyme.Time as Cimport qualified Data.Text as Timport qualified Network.Mail.Mime as Mimeimport qualified Network.Mail.SMTP as SMTPimport Network.URI ( URI, parseURI)import Text.StringTemplate ( directoryGroup, newSTMP, getStringTemplate, setManyAttrib, render)import Filesystem.Path.CurrentOS ( encodeString )import Bippy.Types ( Satoshi )
import Aftok.Billing( Billable,Billable',ContactChannel (..),Subscription',billable,contactChannel,customer,name,paymentRequestEmailTemplate,paymentRequestMemoTemplate,project,)import qualified Aftok.Config as ACimport Aftok.Currency.Bitcoin (satoshi)import qualified Aftok.Database as DBimport Aftok.Database.PostgreSQL (QDBM (..))import qualified Aftok.Payments as Pimport Aftok.Payments.Types( PaymentKey (..),paymentKey,paymentRequestTotal,subscription,)import Aftok.Project( Project,projectName,)import Aftok.Types( ProjectId (..),User,UserId,_Email,)import qualified AftokD as Dimport Bippy.Types (Satoshi)import Control.Error.Util (maybeT)import Control.Lens( (^.),makeClassyPrisms,makeLenses,to,traverseOf,)import Control.Monad.Except( MonadError,throwError,)import Control.Monad.Trans.Except (withExceptT)import Control.Monad.Trans.Reader (mapReaderT)import Crypto.Random.Types (MonadRandom (..))import qualified Data.Text as Timport Data.Thyme.Clock as Cimport Data.Thyme.Time as Cimport Database.PostgreSQL.Simple( Connection,connect,)import Filesystem.Path.CurrentOS (encodeString)import qualified Network.Mail.Mime as Mimeimport qualified Network.Mail.SMTP as SMTPimport Network.URI( URI,parseURI,)import Text.StringTemplate( directoryGroup,getStringTemplate,newSTMP,render,setManyAttrib,)
import Aftok.Types ( User, UserId, ProjectId(..), _Email)import Aftok.Currency.Bitcoin ( satoshi )import qualified Aftok.Config as ACimport Aftok.Billing ( Billable, Billable', Subscription', ContactChannel(..), contactChannel, customer, name, billable, project, paymentRequestEmailTemplate, paymentRequestMemoTemplate)import qualified Aftok.Database as DBimport Aftok.Database.PostgreSQL ( QDBM(..) )import qualified Aftok.Payments as Pimport Aftok.Payments.Types ( PaymentKey(..), subscription, paymentRequestTotal, paymentKey)import Aftok.Project ( Project, projectName)import qualified AftokD as D
preqCfg = cfg ^. (dcfg . D.paymentRequestConfig)reqMay = dopreq <- DB.findPaymentRequestId reqIdpreq' <- traverseOf P.subscription DB.findSubscriptionBillable preq
preqCfg = cfg ^. (dcfg . D.paymentRequestConfig)reqMay = dopreq <- DB.findPaymentRequestId reqIdpreq' <- traverseOf P.subscription DB.findSubscriptionBillable preq
mail <- buildPaymentRequestEmail preqCfg req bip70URLlet mailer = maybe (SMTP.sendMailWithLogin _smtpHost)(SMTP.sendMailWithLogin' _smtpHost)_smtpPort
mail <- buildPaymentRequestEmail preqCfg req bip70URLlet mailer =maybe(SMTP.sendMailWithLogin _smtpHost)(SMTP.sendMailWithLogin' _smtpHost)_smtpPort
buildPaymentRequestEmail:: (MonadIO m, MonadError AftokDErr m)=> D.PaymentRequestConfig-> P.PaymentRequest' (Subscription' User (Billable' Project UserId Satoshi))-> URI-> m Mime.Mail
buildPaymentRequestEmail ::(MonadIO m, MonadError AftokDErr m) =>D.PaymentRequestConfig ->P.PaymentRequest' (Subscription' User (Billable' Project UserId Satoshi)) ->URI ->m Mime.Mail
pname = req ^. (subscription . billable . project . projectName)total = req ^. (P.paymentRequest . to paymentRequestTotal)setAttrs = setManyAttrib[ ("from_email" , fromEmail ^. _Email), ("project_name", pname), ("to_email" , toEmail ^. _Email), ("amount_due" , show $ total ^. satoshi), ("payment_url" , show paymentUrl)]
pname = req ^. (subscription . billable . project . projectName)total = req ^. (P.paymentRequest . to paymentRequestTotal)setAttrs =setManyAttrib[ ("from_email", fromEmail ^. _Email),("project_name", pname),("to_email", toEmail ^. _Email),("amount_due", show $ total ^. satoshi),("payment_url", show paymentUrl)]
toAddr = Mime.Address Nothing (toEmail ^. _Email)subject = "Payment is due for your " <> pname <> " subscription!"body = Mime.plainPart . render $ setAttrs template
toAddr = Mime.Address Nothing (toEmail ^. _Email)subject = "Payment is due for your " <> pname <> " subscription!"body = Mime.plainPart . render $ setAttrs template
setAttrs = setManyAttrib[ ("project_name", req ^. (billable . project . projectName)), ("subscription", req ^. (billable . name)), ("billing_date", show billingDate), ("issue_time" , show requestTime)]
setAttrs =setManyAttrib[ ("project_name", req ^. (billable . project . projectName)),("subscription", req ^. (billable . name)),("billing_date", show billingDate),("issue_time", show requestTime)]
import Control.Lens
import qualified Aftok.Config as ACimport Aftok.Types (Email (..))import Control.Lensimport qualified Data.Configurator as Cimport qualified Data.Configurator.Types as CTimport Database.PostgreSQL.Simple (ConnectInfo)import Filesystem.Path.CurrentOS( encodeString,fromText,)import qualified Filesystem.Path.CurrentOS as P
import qualified Data.Configurator as Cimport qualified Data.Configurator.Types as CTimport Database.PostgreSQL.Simple ( ConnectInfo )import Filesystem.Path.CurrentOS ( fromText, encodeString)import qualified Filesystem.Path.CurrentOS as P
data PaymentRequestConfig= PaymentRequestConfig{ _aftokHost :: Text,_templatePath :: P.FilePath,_billingFromEmail :: Email}
data PaymentRequestConfig = PaymentRequestConfig{ _aftokHost :: Text, _templatePath :: P.FilePath, _billingFromEmail :: Email}makeLenses ''PaymentRequestConfig
data Config= Config{ _smtpConfig :: AC.SmtpConfig,_billingConfig :: AC.BillingConfig,_dbConfig :: ConnectInfo,_paymentRequestConfig :: PaymentRequestConfig}
import Control.Exception ( try )import System.Environment ( getEnv )import System.IO.Error ( IOError )import Filesystem.Path.CurrentOS ( decodeString )import qualified AftokD as Dimport AftokD.AftokM ( createAllPaymentRequests )
import Control.Lensimport Data.Hourglass ( Seconds(..) )import Data.Ratio ( (%) )import Data.Traversable ( for )import Data.Thyme.Clock as Cimport Data.Thyme.Format ( )import Data.UUID
import Aftok.Currency.Bitcoin( satoshi,ssub,)import Aftok.Types( ProjectId,UserId,)import Bippy.Types (Satoshi (..))import Control.Lensimport Data.Hourglass (Seconds (..))import Data.Ratio ((%))import Data.Thyme.Clock as Cimport Data.Thyme.Format ()import Data.Traversable (for)import Data.UUID
data Auction = Auction{ _projectId :: ProjectId, _initiator :: UserId, _createdAt :: C.UTCTime, _raiseAmount :: Satoshi, _auctionStart :: C.UTCTime, _auctionEnd :: C.UTCTime}
data Auction= Auction{ _projectId :: ProjectId,_initiator :: UserId,_createdAt :: C.UTCTime,_raiseAmount :: Satoshi,_auctionStart :: C.UTCTime,_auctionEnd :: C.UTCTime}
data Bid = Bid{ _bidUser :: UserId, _bidSeconds :: Seconds, _bidAmount :: Satoshi, _bidTime :: C.UTCTime} deriving (Eq, Show)
data Bid= Bid{ _bidUser :: UserId,_bidSeconds :: Seconds,_bidAmount :: Satoshi,_bidTime :: C.UTCTime}deriving (Eq, Show)
data Commitment = Commitment{ _baseBid :: Bid, _commitmentSeconds :: Seconds, _commitmentAmount :: Satoshi}
data Commitment= Commitment{ _baseBid :: Bid,_commitmentSeconds :: Seconds,_commitmentAmount :: Satoshi}
wheresecs bid = toRational $ bid ^. bidSecondsbtc bid = toRational $ bid ^. bidAmount . satoshicostRatio bid = secs bid / btc bid
wheresecs bid = toRational $ bid ^. bidSecondsbtc bid = toRational $ bid ^. bidAmount . satoshicostRatio bid = secs bid / btc bid
lettakeWinningBids :: Satoshi -> [Bid] -> [Bid]takeWinningBids total (bid : xs)|-- if the total is fully within the raise amounttotal <> (bid ^. bidAmount) < raiseAmount'= bid : takeWinningBids (total <> (bid ^. bidAmount)) xs|-- if the last bid will exceed the raise amount, reduce it to fittotal < raiseAmount'= letwinFraction r = r % (bid ^. bidAmount . satoshi)remainderSeconds (Satoshi r) =Seconds . round $ winFraction r * fromIntegral (bid ^. bidSeconds)adjustBid r = bid & bidSeconds .~ remainderSeconds r & bidAmount .~ rintoList $ adjustBid <$> raiseAmount' `ssub` total| otherwise= []takeWinningBids _ [] = []submittedTotal = bidsTotal bidsinmaybe (WinningBids $ takeWinningBids (Satoshi 0) $ sortBy bidOrder bids)InsufficientBids(raiseAmount' `ssub` submittedTotal)
let takeWinningBids :: Satoshi -> [Bid] -> [Bid]takeWinningBids total (bid : xs)| -- if the total is fully within the raise amounttotal <> (bid ^. bidAmount) < raiseAmount' =bid : takeWinningBids (total <> (bid ^. bidAmount)) xs| -- if the last bid will exceed the raise amount, reduce it to fittotal < 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 .~ rin toList $ adjustBid <$> raiseAmount' `ssub` total| otherwise =[]takeWinningBids _ [] = []submittedTotal = bidsTotal bidsin maybe(WinningBids $ takeWinningBids (Satoshi 0) $ sortBy bidOrder bids)InsufficientBids(raiseAmount' `ssub` submittedTotal)
x | x <> (bid ^. bidAmount) < raiseAmount' ->put (x <> bid ^. bidAmount)>> (pure . Just $ Commitment bid (bid ^. bidSeconds) (bid ^. bidAmount))
x| x <> (bid ^. bidAmount) < raiseAmount' ->put (x <> bid ^. bidAmount)>> (pure . Just $ Commitment bid (bid ^. bidSeconds) (bid ^. bidAmount))
x | x < raiseAmount' ->let winFraction r = r % (bid ^. bidAmount . satoshi)remainderSeconds (Satoshi r) =
x| x < raiseAmount' ->let winFraction r = r % (bid ^. bidAmount . satoshi)remainderSeconds (Satoshi r) =
in for (raiseAmount' `ssub` x) $ \remainder ->put (x <> remainder)*> (pure $ Commitment bid (remainderSeconds remainder) remainder)
in for (raiseAmount' `ssub` x) $ \remainder ->put (x <> remainder)*> (pure $ Commitment bid (remainderSeconds remainder) remainder)
import Control.Lens (makeLenses, makePrisms, preview, view, _Just)import Data.Thyme.Clock as Cimport Data.Thyme.Time as Timport Data.UUID
import Aftok.Types (UserId, ProjectId, Email)import Bippy.Types (Satoshi)
import Aftok.Types (Email, ProjectId, UserId)import Bippy.Types (Satoshi)import Control.Lens (_Just, makeLenses, makePrisms, preview, view)import Data.Thyme.Clock as Cimport Data.Thyme.Time as Timport Data.UUID
data Billable' p u c = Billable{ _project :: p, _creator :: u, _name :: Text, _description :: Text, _recurrence :: Recurrence, _amount :: c, _gracePeriod :: Days, _requestExpiryPeriod :: Maybe C.NominalDiffTime, _paymentRequestEmailTemplate :: Maybe Text, _paymentRequestMemoTemplate :: Maybe Text}
data Billable' p u c= Billable{ _project :: p,_creator :: u,_name :: Text,_description :: Text,_recurrence :: Recurrence,_amount :: c,_gracePeriod :: Days,_requestExpiryPeriod :: Maybe C.NominalDiffTime,_paymentRequestEmailTemplate :: Maybe Text,_paymentRequestMemoTemplate :: Maybe Text}
data Subscription' u b = Subscription{ _customer :: u, _billable :: b, _contactChannel :: ContactChannel, _startTime :: C.UTCTime, _endTime :: Maybe C.UTCTime}
data Subscription' u b= Subscription{ _customer :: u,_billable :: b,_contactChannel :: ContactChannel,_startTime :: C.UTCTime,_endTime :: Maybe C.UTCTime}
import Control.Lens ( makeClassy, (^.))import qualified Data.Configurator as Cimport qualified Data.Configurator.Types as Cimport Data.X509import Data.X509.File ( readKeyFile, readSignedObject)import Database.PostgreSQL.Simple ( ConnectInfo(..) )import Filesystem.Path.CurrentOS ( fromText, encodeString)import qualified Filesystem.Path.CurrentOS as Pimport Safe ( headMay )
import Aftok.Currency.Bitcoin (NetworkMode)import Aftok.Payments (PaymentsConfig (..))import qualified Bippy.Types as BTimport Control.Lens( (^.),makeClassy,)import qualified Data.Configurator as Cimport qualified Data.Configurator.Types as Cimport Data.X509import Data.X509.File( readKeyFile,readSignedObject,)import Database.PostgreSQL.Simple (ConnectInfo (..))import Filesystem.Path.CurrentOS( encodeString,fromText,)import qualified Filesystem.Path.CurrentOS as Pimport qualified Network.Mail.SMTP as SMTPimport qualified Network.Socket as NSimport Safe (headMay)
import qualified Bippy.Types as BTimport qualified Network.Mail.SMTP as SMTPimport qualified Network.Socket as NSimport Aftok.Currency.Bitcoin ( NetworkMode )import Aftok.Payments ( PaymentsConfig(..) )
data SmtpConfig= SmtpConfig{ _smtpHost :: NS.HostName,_smtpPort :: Maybe NS.PortNumber,_smtpUser :: SMTP.UserName,_smtpPass :: SMTP.Password}
data BillingConfig = BillingConfig{ _networkMode :: NetworkMode, _signingKeyFile :: P.FilePath, _certsFile :: P.FilePath, _exchangeRateServiceURI :: String}
data BillingConfig= BillingConfig{ _networkMode :: NetworkMode,_signingKeyFile :: P.FilePath,_certsFile :: P.FilePath,_exchangeRateServiceURI :: String}
fail$ "Only RSA keys are currently supported for payment request signing."Nothing -> fail $ "No keys found in private key file " <> encodeString(c ^. signingKeyFile)
fail $"Only RSA keys are currently supported for payment request signing."Nothing ->fail $"No keys found in private key file "<> encodeString(c ^. signingKeyFile)
import qualified Data.Configurator.Types as Cimport Control.Lensimport Bippy.Types ( Satoshi(..) )import Haskoin.Constants
import Bippy.Types (Satoshi (..))import Control.Lensimport qualified Data.Configurator.Types as Cimport Haskoin.Constants
( ZAddr(..), _ZAddr, RPCError(..), ZValidateAddressErr(..), ZcashdConfig(..), Zatoshi, ToZatoshi(..), rpcAddViewingKey, rpcValidateZAddr) where
( ZAddr (..),_ZAddr,RPCError (..),ZValidateAddressErr (..),ZcashdConfig (..),Zatoshi,ToZatoshi (..),rpcAddViewingKey,rpcValidateZAddr,)where
import Control.Exception ( catch )import Control.Lens ( makePrisms )import Control.Monad.Trans.Except ( except )import qualified Data.Aeson as Aimport Data.Aeson ( Value, (.=), (.:), (.:?), object, encode )import Data.Aeson.Types ( Parser )import qualified Data.Text.Encoding as T
import Control.Exception (catch)import Control.Lens (makePrisms)import Control.Monad.Trans.Except (except)import Data.Aeson ((.:), (.:?), (.=), Value, encode, object)import qualified Data.Aeson as Aimport Data.Aeson.Types (Parser)import qualified Data.Text.Encoding as Timport Network.HTTP.Client( HttpException,Manager,RequestBody (..),applyBasicAuth,defaultRequest,host,httpLbs,method,port,requestBody,responseBody,responseStatus,)import Network.HTTP.Types (Status, statusCode)
import Network.HTTP.Client ( Manager, RequestBody(..), HttpException, defaultRequest, responseBody, responseStatus, httpLbs, host, port, method, requestBody, applyBasicAuth)import Network.HTTP.Types ( Status, statusCode )
data ZcashdConfig = ZcashdConfig{ zcashdHost :: Text, zcashdPort :: Int, rpcUser :: Text, rpcPassword :: Text}
data ZcashdConfig= ZcashdConfig{ zcashdHost :: Text,zcashdPort :: Int,rpcUser :: Text,rpcPassword :: Text}
let req = applyBasicAuth (T.encodeUtf8 $ rpcUser cfg) (T.encodeUtf8 $ rpcPassword cfg) $defaultRequest { host = T.encodeUtf8 $ zcashdHost cfg, port = zcashdPort cfg, method = "POST", requestBody = RequestBodyLBS . encode $ toRequestBody call}response <- ExceptT $ catch(Right <$> httpLbs req mgr)(pure . Left . HttpError)
let req =applyBasicAuth (T.encodeUtf8 $ rpcUser cfg) (T.encodeUtf8 $ rpcPassword cfg) $defaultRequest{ host = T.encodeUtf8 $ zcashdHost cfg,port = zcashdPort cfg,method = "POST",requestBody = RequestBodyLBS . encode $ toRequestBody call}response <-ExceptT $catch(Right <$> httpLbs req mgr)(pure . Left . HttpError)
data ZValidateAddressResp = ZValidateAddressResp{ vzrIsValid :: Bool--, vzrAddress :: Maybe Text, vzrAddrType :: Maybe ZAddrType}
data ZValidateAddressResp= ZValidateAddressResp{ vzrIsValid :: Bool,--, vzrAddress :: Maybe TextvzrAddrType :: Maybe ZAddrType}
validateZAddrRequest addr = object[ "jsonrpc" .= ("1.0" :: Text), "id" .= ("aftok-z_validateaddress" :: Text), "method" .= ("z_validateaddress" :: Text), "params" .= [addr]]
validateZAddrRequest addr =object[ "jsonrpc" .= ("1.0" :: Text),"id" .= ("aftok-z_validateaddress" :: Text),"method" .= ("z_validateaddress" :: Text),"params" .= [addr]]
except $ if vzrIsValid respthencase vzrAddrType resp ofNothing -> Left (RPCError DataMissing)Just Sprout -> Left (RPCError SproutAddress)
except $if vzrIsValid respthen case vzrAddrType resp ofNothing -> Left (RPCError DataMissing)Just Sprout -> Left (RPCError SproutAddress)
importViewingKeyRequest vk = object[ "jsonrpc" .= ("1.0" :: Text), "id" .= ("aftok-z_importviewingkey" :: Text), "method" .= ("z_importviewingkey" :: Text), "params" .= [vk, "no"] -- no need to rescan, for our purposes]
importViewingKeyRequest vk =object[ "jsonrpc" .= ("1.0" :: Text),"id" .= ("aftok-z_importviewingkey" :: Text),"method" .= ("z_importviewingkey" :: Text),"params" .= [vk, "no"] -- no need to rescan, for our purposes]
import Data.Aeson ( FromJSON(..), ToJSON(..))import Aftok.TimeLog.Serialization ( depfFromJSON, depfToJSON)import Aftok.Types ( DepreciationFunction )
import Aftok.TimeLog.Serialization( depfFromJSON,depfToJSON,)import Aftok.Types (DepreciationFunction)import Data.Aeson( FromJSON (..),ToJSON (..),)
import Prelude hiding ( null )import Control.Lensimport Control.Monad.Trans.Except ( throwE )import Crypto.Random.Types ( MonadRandom, getRandomBytes)import Data.Aeson ( Value, toJSON)import Data.Hourglassimport qualified Data.List as Limport Data.ProtocolBuffers ( decodeMessage, encodeMessage)import Data.Serialize.Get ( runGet )import Data.Serialize.Put ( runPut )import qualified Data.Text as Timport Data.Thyme.Clock as Cimport Data.Thyme.Timeimport Data.UUID ( UUID )import Database.PostgreSQL.Simpleimport Database.PostgreSQL.Simple.FromFieldimport Database.PostgreSQL.Simple.FromRowimport Database.PostgreSQL.Simple.SqlQQ( sql )import Database.PostgreSQL.Simple.Types( Null )import Safe ( headMay )
import qualified Aftok.Auction as Aimport qualified Aftok.Billing as Bimport Aftok.Currency.Bitcoinimport Aftok.Currency.Zcash (ZAddr (..), _ZAddr)import Aftok.Databaseimport Aftok.Database.PostgreSQL.Types( SerDepFunction (..),)import Aftok.Intervalimport Aftok.Json( billableJSON,createSubscriptionJSON,paymentJSON,paymentRequestJSON,)import Aftok.Payments.Typesimport qualified Aftok.Project as Pimport Aftok.TimeLogimport Aftok.Typesimport Bippy.Types (Satoshi (..))import Control.Lensimport Control.Monad.Trans.Except (throwE)import Crypto.Random.Types( MonadRandom,getRandomBytes,)import Data.Aeson( Value,toJSON,)import Data.Hourglassimport qualified Data.List as Limport Data.ProtocolBuffers( decodeMessage,encodeMessage,)import Data.Serialize.Get (runGet)import Data.Serialize.Put (runPut)import qualified Data.Text as Timport Data.Thyme.Clock as Cimport Data.Thyme.Timeimport Data.UUID (UUID)import Database.PostgreSQL.Simpleimport Database.PostgreSQL.Simple.FromFieldimport Database.PostgreSQL.Simple.FromRowimport Database.PostgreSQL.Simple.SqlQQ( sql,)import Database.PostgreSQL.Simple.Types( Null,)import Haskoin.Address( Address,addrToText,textToAddr,)import Haskoin.Constants (Network)import Safe (headMay)import Prelude hiding (null)
import qualified Aftok.Auction as Aimport qualified Aftok.Billing as Bimport Aftok.Currency.Bitcoinimport Aftok.Currency.Zcash (ZAddr(..), _ZAddr)import Aftok.Databaseimport Aftok.Database.PostgreSQL.Types( SerDepFunction(..) )import Aftok.Intervalimport Aftok.Json ( billableJSON, createSubscriptionJSON, paymentJSON, paymentRequestJSON)import Aftok.Payments.Typesimport qualified Aftok.Project as Pimport Aftok.TimeLogimport Aftok.Typesimport Bippy.Types ( Satoshi(..) )import Haskoin.Address ( Address, textToAddr, addrToText)import Haskoin.Constants ( Network )
let err = returnErrorConversionFailedf( "could not deserialize value "<> T.unpack fieldValue<> " to a valid BTC address for network "<> show n)
let err =returnErrorConversionFailedf( "could not deserialize value "<> T.unpack fieldValue<> " to a valid BTC address for network "<> show n)
else maybe(returnError UnexpectedNull f "event type may not be null")( maybe (returnError Incompatible f "unrecognized event type value") pure. nameEvent. decodeUtf8)v
elsemaybe(returnError UnexpectedNull f "event type may not be null")( maybe (returnError Incompatible f "unrecognized event type value") pure. nameEvent. decodeUtf8)v
whereparser :: Text -> RowParser (CreditTo (NetworkId, Address))parser = \case"credit_to_address" ->CreditToCurrency <$> (btcAddressParser mode <* nullField <* nullField)"credit_to_user" ->CreditToUser <$> (nullField *> nullField *> idParser UserId <* nullField)"credit_to_project" ->CreditToProject<$> (nullField *> nullField *> nullField *> idParser ProjectId)_ -> empty
whereparser :: Text -> RowParser (CreditTo (NetworkId, Address))parser = \case"credit_to_address" ->CreditToCurrency <$> (btcAddressParser mode <* nullField <* nullField)"credit_to_user" ->CreditToUser <$> (nullField *> nullField *> idParser UserId <* nullField)"credit_to_project" ->CreditToProject<$> (nullField *> nullField *> nullField *> idParser ProjectId)_ -> empty
<*> ((maybe empty pure =<< fmap (RecoverByEmail . Email) <$> field)<|>(maybe empty pure =<< fmap (RecoverByZAddr . ZAddr) <$> field))
<*> ( (maybe empty pure =<< fmap (RecoverByEmail . Email) <$> field)<|> (maybe empty pure =<< fmap (RecoverByZAddr . ZAddr) <$> field))
"weekly" -> B.Weekly <$> field"onetime" -> nullField *> pure B.OneTime_ -> emptyin field >>= prec
"weekly" -> B.Weekly <$> field"onetime" -> nullField *> pure B.OneTime_ -> emptyin field >>= prec
storeEvent (CreateSubscription uid bid t) = Just $ storeEventJSON(Just uid)"create_subscription"(createSubscriptionJSON uid bid t)storeEvent (CreatePaymentRequest req) = Just$ storeEventJSON Nothing "create_payment_request" (paymentRequestJSON req)
storeEvent (CreateSubscription uid bid t) =Just $storeEventJSON(Just uid)"create_subscription"(createSubscriptionJSON uid bid t)storeEvent (CreatePaymentRequest req) =Just $storeEventJSON Nothing "create_payment_request" (paymentRequestJSON req)
( pid, uid, creditToName c, renderNetworkId nid, addrToText network addr, eventName e, fromThyme $ e ^. eventTime, m
( pid,uid,creditToName c,renderNetworkId nid,addrToText network addr,eventName e,fromThyme $ e ^. eventTime,m
CreditToProject pid' -> pinsertEventId[sql| INSERT INTO work_events
CreditToProject pid' ->pinsertEventId[sql| INSERT INTO work_events
( pid, uid, creditToName c, pid' ^. _ProjectId, eventName e, fromThyme $ e ^. eventTime, m)CreditToUser uid' -> pinsertEventId[sql| INSERT INTO work_events
( pid,uid,creditToName c,pid' ^. _ProjectId,eventName e,fromThyme $ e ^. eventTime,m)CreditToUser uid' ->pinsertEventId[sql| INSERT INTO work_events
( pid, uid, creditToName c, uid' ^. _UserId, eventName e, fromThyme $ e ^. eventTime, m)
( pid,uid,creditToName c,uid' ^. _UserId,eventName e,fromThyme $ e ^. eventTime,m)
(pid, uid, fromThyme e, limit)(During s e) -> pquery(logEntryParser mode)[sql| SELECT credit_to_type,
(pid, uid, fromThyme e, limit)(During s e) ->pquery(logEntryParser mode)[sql| SELECT credit_to_type,
(pid, uid, fromThyme s, fromThyme e, limit)(After s) -> pquery(logEntryParser mode)[sql| SELECT credit_to_type,
(pid, uid, fromThyme s, fromThyme e, limit)(After s) ->pquery(logEntryParser mode)[sql| SELECT credit_to_type,
(pid, uid, limit)pgEval (AmendEvent (EventId eid) (TimeChange mt t)) = pinsertAmendmentId[sql| INSERT INTO event_time_amendments
(pid, uid, limit)pgEval (AmendEvent (EventId eid) (TimeChange mt t)) =pinsertAmendmentId[sql| INSERT INTO event_time_amendments
(eid, fromThyme $ mt ^. _ModTime, fromThyme t)
(eid, fromThyme $ mt ^. _ModTime, fromThyme t)
( eid, fromThyme $ mt ^. _ModTime, creditToName c, renderNetworkId nid, addrToText network addr
( eid,fromThyme $ mt ^. _ModTime,creditToName c,renderNetworkId nid,addrToText network addr
CreditToProject pid -> pinsertAmendmentId[sql| INSERT INTO event_credit_to_amendments
CreditToProject pid ->pinsertAmendmentId[sql| INSERT INTO event_credit_to_amendments
(eid, fromThyme $ mt ^. _ModTime, creditToName c, pid ^. _ProjectId)CreditToUser uid -> pinsertAmendmentId[sql| INSERT INTO event_credit_to_amendments
(eid, fromThyme $ mt ^. _ModTime, creditToName c, pid ^. _ProjectId)CreditToUser uid ->pinsertAmendmentId[sql| INSERT INTO event_credit_to_amendments
(eid, fromThyme $ mt ^. _ModTime, creditToName c, uid ^. _UserId)pgEval (AmendEvent (EventId eid) (MetadataChange mt v)) = pinsertAmendmentId[sql| INSERT INTO event_metadata_amendments
(eid, fromThyme $ mt ^. _ModTime, creditToName c, uid ^. _UserId)pgEval (AmendEvent (EventId eid) (MetadataChange mt v)) =pinsertAmendmentId[sql| INSERT INTO event_metadata_amendments
pgEval (CreateAuction auc) = pinsertA.AuctionId[sql| INSERT INTO auctions (project_id, initiator_id, raise_amount, end_time)
pgEval (CreateAuction auc) =pinsertA.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))pgEval (FindAuction aucId) = headMay <$> pqueryauctionParser[sql| SELECT project_id, initiator_id, created_at, raise_amount, start_time, end_time
( auc ^. (A.projectId . _ProjectId),auc ^. (A.initiator . _UserId),auc ^. (A.raiseAmount . satoshi),auc ^. (A.auctionEnd . to fromThyme))pgEval (FindAuction aucId) =headMay<$> pqueryauctionParser[sql| SELECT project_id, initiator_id, created_at, raise_amount, start_time, end_time
(Only (aucId ^. A._AuctionId))pgEval (CreateBid (A.AuctionId aucId) bid) = pinsertA.BidId[sql| INSERT INTO bids (auction_id, bidder_id, bid_seconds, bid_amount, bid_time)
(Only (aucId ^. A._AuctionId))pgEval (CreateBid (A.AuctionId aucId) bid) =pinsertA.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))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))
( 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))
( user' ^. (username . _UserName), user' ^? userAccountRecovery . _RecoverByEmail . _Email, user' ^? userAccountRecovery . _RecoverByZAddr . _ZAddr
( user' ^. (username . _UserName),user' ^? userAccountRecovery . _RecoverByEmail . _Email,user' ^? userAccountRecovery . _RecoverByZAddr . _ZAddr
headMay <$> pqueryuserParser[sql| SELECT handle, recovery_email, recovery_zaddr FROM users WHERE id = ? |](Only uid)
headMay<$> pqueryuserParser[sql| SELECT handle, recovery_email, recovery_zaddr FROM users WHERE id = ? |](Only uid)
headMay <$> pquery((,) <$> idParser UserId <*> userParser)[sql| SELECT id, handle, recovery_email, recovery_zaddr FROM users WHERE handle = ? |](Only h)
headMay<$> pquery((,) <$> idParser UserId <*> userParser)[sql| SELECT id, handle, recovery_email, recovery_zaddr FROM users WHERE handle = ? |](Only h)
headMay <$> pquery(btcAddressParser mode)[sql| SELECT default_payment_network, default_payment_addr FROM users WHERE id = ? |](Only uid)
headMay<$> pquery(btcAddressParser mode)[sql| SELECT default_payment_network, default_payment_addr FROM users WHERE id = ? |](Only uid)
pgEval (FindInvitation ic) = headMay <$> pqueryinvitationParser[sql| SELECT project_id, invitor_id, invitee_email, invitation_time, acceptance_time
pgEval (FindInvitation ic) =headMay<$> pqueryinvitationParser[sql| SELECT project_id, invitor_id, invitee_email, invitation_time, acceptance_time
void $ pexec[sql| UPDATE invitations SET acceptance_time = ? WHERE invitation_key = ? |](fromThyme t, P.renderInvCode ic)void $ pexec[sql| INSERT INTO project_companions (project_id, user_id, invited_by, joined_at)
void $pexec[sql| UPDATE invitations SET acceptance_time = ? WHERE invitation_key = ? |](fromThyme t, P.renderInvCode ic)void $pexec[sql| INSERT INTO project_companions (project_id, user_id, invited_by, joined_at)
(uid, fromThyme t, P.renderInvCode ic)pgEval (CreateProject p) = pinsertProjectId[sql| INSERT INTO projects (project_name, inception_date, initiator_id, depreciation_fn)
(uid, fromThyme t, P.renderInvCode ic)pgEval (CreateProject p) =pinsertProjectId[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)
(Only (pid ^. _ProjectId))pgEval (FindProject (ProjectId pid)) = headMay <$> pqueryprojectParser[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 DISTINCT ON (p.inception_date, p.id) p.id, p.project_name, p.inception_date, p.initiator_id, p.depreciation_fn
(Only (pid ^. _ProjectId))pgEval (FindProject (ProjectId pid)) =headMay<$> pqueryprojectParser[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 DISTINCT ON (p.inception_date, p.id) p.id, p.project_name, p.inception_date, p.initiator_id, p.depreciation_fn
(uid, uid)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)
(uid, uid)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)
( b ^. (B.project . _ProjectId), eventId ^. _EventId, b ^. B.name, b ^. B.description, b ^. (B.recurrence . to B.recurrenceName), b ^. (B.recurrence . to B.recurrenceCount), b ^. (B.amount . satoshi), b ^. (B.gracePeriod), b ^. (B.paymentRequestEmailTemplate), b ^. (B.paymentRequestMemoTemplate)
( b ^. (B.project . _ProjectId),eventId ^. _EventId,b ^. B.name,b ^. B.description,b ^. (B.recurrence . to B.recurrenceName),b ^. (B.recurrence . to B.recurrenceCount),b ^. (B.amount . satoshi),b ^. (B.gracePeriod),b ^. (B.paymentRequestEmailTemplate),b ^. (B.paymentRequestMemoTemplate)
pgEval (FindBillable bid) = headMay <$> pquerybillableParser[sql| SELECT b.project_id, e.created_by, b.name, b.description,
pgEval (FindBillable bid) =headMay<$> pquerybillableParser[sql| SELECT b.project_id, e.created_by, b.name, b.description,
(Only (bid ^. B._BillableId))pgEval (FindBillables pid) = pquery((,) <$> idParser B.BillableId <*> billableParser)[sql| SELECT b.id, b.project_id, e.created_by, b.name, b.description,
(Only (bid ^. B._BillableId))pgEval (FindBillables pid) =pquery((,) <$> idParser B.BillableId <*> billableParser)[sql| SELECT b.id, b.project_id, e.created_by, b.name, b.description,
( view _UserId uid, view B._BillableId bid, view _EventId eventId, fromThyme start_date
( view _UserId uid,view B._BillableId bid,view _EventId eventId,fromThyme start_date
pgEval (FindSubscription sid) = headMay <$> pquerysubscriptionParser[sql| SELECT id, billable_id, contact_email, start_date, end_date
pgEval (FindSubscription sid) =headMay<$> pquerysubscriptionParser[sql| SELECT id, billable_id, contact_email, start_date, end_date
(Only (sid ^. B._SubscriptionId))pgEval (FindSubscriptions uid pid) = pquery((,) <$> idParser B.SubscriptionId <*> subscriptionParser)[sql| SELECT s.id, user_id, billable_id, contact_email, start_date, end_date
(Only (sid ^. B._SubscriptionId))pgEval (FindSubscriptions uid pid) =pquery((,) <$> idParser B.SubscriptionId <*> subscriptionParser)[sql| SELECT s.id, user_id, billable_id, contact_email, start_date, end_date
( req ^. (subscription . B._SubscriptionId), eventId ^. _EventId, req ^. (paymentRequest . to (runPut . encodeMessage)), req ^. (paymentKey . _PaymentKey), req ^. (paymentRequestTime . to fromThyme), req ^. (billingDate . to fromThyme)
( req ^. (subscription . B._SubscriptionId),eventId ^. _EventId,req ^. (paymentRequest . to (runPut . encodeMessage)),req ^. (paymentKey . _PaymentKey),req ^. (paymentRequestTime . to fromThyme),req ^. (billingDate . to fromThyme)
pgEval (FindPaymentRequest (PaymentKey k)) = headMay <$> pquery((,) <$> idParser PaymentRequestId <*> paymentRequestParser)[sql| SELECT id, subscription_id, request_data, url_key, request_time, billing_date
pgEval (FindPaymentRequest (PaymentKey k)) =headMay<$> pquery((,) <$> idParser PaymentRequestId <*> paymentRequestParser)[sql| SELECT id, subscription_id, request_data, url_key, request_time, billing_date
(Only k)pgEval (FindPaymentRequestId (PaymentRequestId prid)) = headMay <$> pquerypaymentRequestParser[sql| SELECT subscription_id, request_data, url_key, request_time, billing_date
(Only k)pgEval (FindPaymentRequestId (PaymentRequestId prid)) =headMay<$> pquerypaymentRequestParser[sql| SELECT subscription_id, request_data, url_key, request_time, billing_date
(Only prid)pgEval (FindPaymentRequests sid) = pquery((,) <$> idParser PaymentRequestId <*> paymentRequestParser)[sql| SELECT id, subscription_id, request_data, url_key, request_time, billing_date
(Only prid)pgEval (FindPaymentRequests sid) =pquery((,) <$> idParser PaymentRequestId <*> paymentRequestParser)[sql| SELECT id, subscription_id, request_data, url_key, request_time, billing_date
( p ^. (request . _PaymentRequestId), eventId ^. _EventId, p ^. (payment . to (runPut . encodeMessage)), p ^. (paymentDate . to fromThyme), p ^. exchangeRates
( p ^. (request . _PaymentRequestId),eventId ^. _EventId,p ^. (payment . to (runPut . encodeMessage)),p ^. (paymentDate . to fromThyme),p ^. exchangeRates
pgEval (FindPayments rid) = pquery((,) <$> idParser PaymentId <*> paymentParser)[sql| SELECT id, payment_request_id, payment_data, payment_date
pgEval (FindPayments rid) =pquery((,) <$> idParser PaymentId <*> paymentParser)[sql| SELECT id, payment_request_id, payment_data, payment_date
(Only (rid ^. _PaymentRequestId))
(Only (rid ^. _PaymentRequestId))
{-# LANGUAGE ExplicitForAll #-}{-# LANGUAGE FlexibleInstances #-}{-# LANGUAGE GADTs #-}{-# LANGUAGE TupleSections #-}{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE ExplicitForAll #-}{-# LANGUAGE FlexibleInstances #-}{-# LANGUAGE GADTs #-}{-# LANGUAGE TemplateHaskell #-}{-# LANGUAGE TupleSections #-}
import Control.Lens ( view, (^.), makeClassyPrisms, traverseOf)import Data.AffineSpaceimport Data.Thyme.Clock as Cimport Data.Thyme.Time as T( Day )import Safe ( headMay )import Aftok.Typesimport Aftok.Auction as Aimport Aftok.Billing as Bimport Aftok.Currency.Bitcoin ( NetworkId )import Aftok.Intervalimport Aftok.Payments.Typesimport Aftok.Project as Pimport Aftok.TimeLogimport Aftok.Util
import Aftok.Auction as Aimport Aftok.Billing as Bimport Aftok.Currency.Bitcoin (NetworkId)import Aftok.Intervalimport Aftok.Payments.Typesimport Aftok.Project as Pimport Aftok.TimeLogimport Aftok.Typesimport Aftok.Utilimport Control.Lens( (^.),makeClassyPrisms,traverseOf,view,)import Data.AffineSpaceimport Data.Thyme.Clock as Cimport Data.Thyme.Time as T( Day,)import Haskoin.Address (Address)import Safe (headMay)
CreateUser :: User -> DBOp UserIdFindUser :: UserId -> DBOp (Maybe User)FindUserByName :: UserName -> DBOp (Maybe (UserId, User))
CreateUser :: User -> DBOp UserIdFindUser :: UserId -> DBOp (Maybe User)FindUserByName :: UserName -> DBOp (Maybe (UserId, User))
CreateProject :: Project -> DBOp ProjectIdFindProject :: ProjectId -> DBOp (Maybe Project)ListProjects :: DBOp [ProjectId]FindSubscribers :: ProjectId -> DBOp [UserId]
CreateProject :: Project -> DBOp ProjectIdFindProject :: ProjectId -> DBOp (Maybe Project)ListProjects :: DBOp [ProjectId]FindSubscribers :: ProjectId -> DBOp [UserId]
CreateEvent :: ProjectId -> UserId -> LogEntry BTCNet -> DBOp EventIdAmendEvent :: EventId -> EventAmendment BTCNet -> DBOp AmendmentIdFindEvent :: EventId -> DBOp (Maybe (KeyedLogEntry BTCNet))FindEvents :: ProjectId -> UserId -> RangeQuery -> Word32 -> DBOp [LogEntry BTCNet]ReadWorkIndex :: ProjectId -> DBOp (WorkIndex BTCNet)CreateAuction :: Auction -> DBOp AuctionIdFindAuction :: AuctionId -> DBOp (Maybe Auction)CreateBid :: AuctionId -> Bid -> DBOp BidIdFindBids :: AuctionId -> DBOp [(BidId, Bid)]CreateBillable :: UserId -> Billable -> DBOp BillableIdFindBillable :: BillableId -> DBOp (Maybe Billable)FindBillables :: ProjectId -> DBOp [(BillableId, Billable)]
CreateEvent :: ProjectId -> UserId -> LogEntry BTCNet -> DBOp EventIdAmendEvent :: EventId -> EventAmendment BTCNet -> DBOp AmendmentIdFindEvent :: EventId -> DBOp (Maybe (KeyedLogEntry BTCNet))FindEvents :: ProjectId -> UserId -> RangeQuery -> Word32 -> DBOp [LogEntry BTCNet]ReadWorkIndex :: ProjectId -> DBOp (WorkIndex BTCNet)CreateAuction :: Auction -> DBOp AuctionIdFindAuction :: AuctionId -> DBOp (Maybe Auction)CreateBid :: AuctionId -> Bid -> DBOp BidIdFindBids :: AuctionId -> DBOp [(BidId, Bid)]CreateBillable :: UserId -> Billable -> DBOp BillableIdFindBillable :: BillableId -> DBOp (Maybe Billable)FindBillables :: ProjectId -> DBOp [(BillableId, Billable)]
FindSubscription :: SubscriptionId -> DBOp (Maybe Subscription)FindSubscriptions :: UserId -> ProjectId -> DBOp [(SubscriptionId, Subscription)]CreatePaymentRequest :: PaymentRequest -> DBOp PaymentRequestIdFindPaymentRequests :: SubscriptionId -> DBOp [(PaymentRequestId, PaymentRequest)]FindUnpaidRequests :: SubscriptionId -> DBOp [BillDetail]FindPaymentRequest :: PaymentKey -> DBOp (Maybe (PaymentRequestId, PaymentRequest))FindPaymentRequestId :: PaymentRequestId -> DBOp (Maybe PaymentRequest)CreatePayment :: Payment -> DBOp PaymentIdFindPayments :: PaymentRequestId -> DBOp [(PaymentId, Payment)]
FindSubscription :: SubscriptionId -> DBOp (Maybe Subscription)FindSubscriptions :: UserId -> ProjectId -> DBOp [(SubscriptionId, Subscription)]CreatePaymentRequest :: PaymentRequest -> DBOp PaymentRequestIdFindPaymentRequests :: SubscriptionId -> DBOp [(PaymentRequestId, PaymentRequest)]FindUnpaidRequests :: SubscriptionId -> DBOp [BillDetail]FindPaymentRequest :: PaymentKey -> DBOp (Maybe (PaymentRequestId, PaymentRequest))FindPaymentRequestId :: PaymentRequestId -> DBOp (Maybe PaymentRequest)CreatePayment :: Payment -> DBOp PaymentIdFindPayments :: PaymentRequestId -> DBOp [(PaymentId, Payment)]RaiseDBError :: forall x y. DBError -> DBOp x -> DBOp y
data OpForbiddenReason = UserNotProjectMember| UserNotEventLogger| UserNotSubscriber SubscriptionId| InvitationExpired| InvitationAlreadyAccepted| AuctionEndedderiving (Eq, Show, Typeable)
data DBError= OpForbidden UserId OpForbiddenReason| SubjectNotFound| EventStorageFailedderiving (Eq, Show, Typeable)
Just i | t .-. (i ^. invitationTime) > fromSeconds (60 * 60 * 72 :: Int) ->raiseOpForbidden uid InvitationExpired actJust i | isJust (i ^. acceptanceTime) ->raiseOpForbidden uid InvitationAlreadyAccepted act
Just i| t .-. (i ^. invitationTime) > fromSeconds (60 * 60 * 72 :: Int) ->raiseOpForbidden uid InvitationExpired actJust i| isJust (i ^. acceptanceTime) ->raiseOpForbidden uid InvitationAlreadyAccepted act
missing = raiseSubjectNotFound actmaybe missing(\(_, uid', _) -> if uid' == uid then liftdb act else forbidden)ev
missing = raiseSubjectNotFound actmaybemissing(\(_, uid', _) -> if uid' == uid then liftdb act else forbidden)ev
( Interval(..), interval, start, end, ilen, RangeQuery(..), rangeQuery, start', end', intervalJSON, parseIntervalJSON, containsInclusive
( Interval (..),interval,start,end,ilen,RangeQuery (..),rangeQuery,start',end',intervalJSON,parseIntervalJSON,containsInclusive,
import Control.Lens ( makeLenses, (^.))import Data.Aesonimport Data.Aeson.Typesimport Data.AffineSpaceimport Data.Thyme.Clock as Cimport Data.Thyme.Format.Aeson ( )import Data.Thyme.LocalTime ( )
data Interval= Interval{ _start :: C.UTCTime,_end :: C.UTCTime}deriving (Show, Eq, Ord)
{-# LANGUAGE ExplicitForAll #-}{-# LANGUAGE LambdaCase #-}{-# LANGUAGE RankNTypes #-}{-# LANGUAGE RecordWildCards #-}{-# LANGUAGE TupleSections #-}{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ExplicitForAll #-}{-# LANGUAGE LambdaCase #-}{-# LANGUAGE RankNTypes #-}{-# LANGUAGE RecordWildCards #-}{-# LANGUAGE TupleSections #-}{-# LANGUAGE TypeApplications #-}
import Control.FromSum ( fromMaybeM, fromEitherM)import Control.Lens hiding ( (.=) )import qualified Control.Lens as Limport Data.Aesonimport Data.Aeson.Typesimport qualified Data.Attoparsec.ByteString.Char8as PCimport qualified Data.ByteString.Base64 as B64import qualified Data.ByteString.Char8 as Cimport Data.Dataimport Data.HashMap.Strict as Oimport Data.List.NonEmpty as Limport Data.Map.Strict as MSimport Data.ProtocolBuffers ( encodeMessage )import Data.Serialize.Put ( runPut )import qualified Data.Text as Timport qualified Data.Text.Encoding as Timport Data.Thyme.Calendar ( showGregorian )import Data.Thyme.Clock as Clockimport Data.Thyme.Time ( Day )import Data.UUID as Uimport Haskoin.Address ( Address, addrToJSON, addrFromJSON, textToAddr)import Aftok.Currency.Bitcoinimport Aftok.Auction as Aimport qualified Aftok.Billing as Bimport Aftok.Intervalimport Aftok.Paymentsimport Aftok.Project as Pimport Aftok.TimeLogimport Aftok.Typesimport Aftok.Util ( traverseKeys )
import qualified Language.Haskell.TH as THimport Language.Haskell.TH.Quote
import Aftok.Auction as Aimport qualified Aftok.Billing as Bimport Aftok.Currency.Bitcoinimport Aftok.Intervalimport Aftok.Paymentsimport Aftok.Project as Pimport Aftok.TimeLogimport Aftok.Typesimport Aftok.Util (traverseKeys)import Control.FromSum( fromEitherM,fromMaybeM,)import Control.Lens hiding ((.=))import qualified Control.Lens as Limport Data.Aesonimport Data.Aeson.Typesimport qualified Data.Attoparsec.ByteString.Char8 as PCimport qualified Data.ByteString.Base64 as B64import qualified Data.ByteString.Char8 as Cimport Data.Dataimport Data.HashMap.Strict as Oimport Data.List.NonEmpty as Limport Data.Map.Strict as MSimport Data.ProtocolBuffers (encodeMessage)import Data.Serialize.Put (runPut)import qualified Data.Text as Timport qualified Data.Text.Encoding as Timport Data.Thyme.Calendar (showGregorian)import Data.Thyme.Clock as Clockimport Data.Thyme.Time (Day)import Data.UUID as Uimport Haskoin.Address( Address,addrFromJSON,addrToJSON,textToAddr,)import qualified Language.Haskell.TH as THimport Language.Haskell.TH.Quote
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."}
{-|- Convenience function to allow dispatch of different serialized- versions to different parsers.-}
-- |-- - Convenience function to allow dispatch of different serialized-- - versions to different parsers.
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]
parseBtcAddr:: 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, ))(textToAddr (toNetwork nmode net) addrText)
parseBtcAddr ::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,))(textToAddr (toNetwork nmode net) addrText)
letparseCreditToAddr = donetName <- 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 = donetName <- o .: "creditToNetwork"net <-fromMaybeM(fail . T.unpack $ "Currency network " <> netName <> " not recognized.")(parseNetworkId netName)addrValue <- o .: "creditToAddress"CreditToCurrency. (net,)<$> addrFromJSON (toNetwork nmode net) addrValueparseCreditToUser =fmap CreditToUser . parseId _UserId =<< o .: "creditToUser"parseCreditToProject =fmap CreditToProject . parseId _ProjectId =<< o .: "creditToProject"notFound =fail $ "Value " <> show o <> " does not represent a CreditTo value."in parseCreditToAddr<|> parseCreditToUser<|> parseCreditToProject<|> notFound
parseCreditToUser =fmap CreditToUser . parseId _UserId =<< o .: "creditToUser"parseCreditToProject =fmap CreditToProject . parseId _ProjectId =<< o .: "creditToProject"notFound =fail $ "Value " <> show o <> " does not represent a CreditTo value."inparseCreditToAddr<|> parseCreditToUser<|> parseCreditToProject<|> notFound
v2$ let payoutsRec :: (CreditTo (NetworkId, Address), Rational) -> ValuepayoutsRec (c, r) =object ["creditTo" .= creditToJSON nmode c, "payoutRatio" .= r]in obj $ ["payouts" .= fmap payoutsRec (MS.assocs m)]parsePayoutsJSON:: NetworkMode -> Value -> Parser (Payouts (NetworkId, Address))parsePayoutsJSON nmode = unversion "Payouts" $ p wherep :: Version -> Object -> Parser (Payouts (NetworkId, Address))p (Version 1 _) val = Payouts <$> join(traverseKeys (parseBtcAddr nmode BTC) <$> parseJSON (Object val))
v2 $let payoutsRec :: (CreditTo (NetworkId, Address), Rational) -> ValuepayoutsRec (c, r) =object ["creditTo" .= creditToJSON nmode c, "payoutRatio" .= r]in obj $ ["payouts" .= fmap payoutsRec (MS.assocs m)]
p (Version 2 0) val =let parsePayoutRecord x =
parsePayoutsJSON ::NetworkMode -> Value -> Parser (Payouts (NetworkId, Address))parsePayoutsJSON nmode = unversion "Payouts" $ pwherep :: Version -> Object -> Parser (Payouts (NetworkId, Address))p (Version 1 _) val =Payouts<$> join(traverseKeys (parseBtcAddr nmode BTC) <$> parseJSON (Object val))p (Version 2 0) val =let parsePayoutRecord x =
workIndexJSON nmode (WorkIndex widx) = v2$ obj ["workIndex" .= fmap widxRec (MS.assocs widx)]wherewidxRec :: (CreditTo (NetworkId, Address), NonEmpty Interval) -> ValuewidxRec (c, l) = object[ "creditTo" .= creditToJSON nmode c, "intervals" .= (intervalJSON <$> L.toList l)]
workIndexJSON nmode (WorkIndex widx) =v2 $obj ["workIndex" .= fmap widxRec (MS.assocs widx)]wherewidxRec :: (CreditTo (NetworkId, Address), NonEmpty Interval) -> ValuewidxRec (c, l) =object[ "creditTo" .= creditToJSON nmode c,"intervals" .= (intervalJSON <$> L.toList l)]
[ "projectId" .= idValue (B.project . _ProjectId) b, "name" .= (b ^. B.name), "description" .= (b ^. B.description), "recurrence" .= recurrenceJSON' (b ^. B.recurrence), "amount" .= (b ^. (B.amount . satoshi)), "gracePeriod" .= (b ^. B.gracePeriod), "requestExpiryPeriod" .= (Clock.toSeconds' <$> (b ^. B.requestExpiryPeriod))
[ "projectId" .= idValue (B.project . _ProjectId) b,"name" .= (b ^. B.name),"description" .= (b ^. B.description),"recurrence" .= recurrenceJSON' (b ^. B.recurrence),"amount" .= (b ^. (B.amount . satoshi)),"gracePeriod" .= (b ^. B.gracePeriod),"requestExpiryPeriod" .= (Clock.toSeconds' <$> (b ^. B.requestExpiryPeriod))
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]
[ "user_id" .= idValue (B.customer . _UserId) sub, "billable_id" .= idValue (B.billable . B._BillableId) sub, "start_time" .= view B.startTime sub, "end_time" .= view B.endTime sub
[ "user_id" .= idValue (B.customer . _UserId) sub,"billable_id" .= idValue (B.billable . B._BillableId) sub,"start_time" .= view B.startTime sub,"end_time" .= view B.endTime sub
[ "subscription_id" .= idValue (subscription . B._SubscriptionId) r, "payment_request_protobuf_64" .= view prBytes r, "url_key" .= view (paymentKey . _PaymentKey) r, "payment_request_time" .= view paymentRequestTime r, "billing_date" .= view (billingDate . to showGregorian) r
[ "subscription_id" .= idValue (subscription . B._SubscriptionId) r,"payment_request_protobuf_64" .= view prBytes r,"url_key" .= view (paymentKey . _PaymentKey) r,"payment_request_time" .= view paymentRequestTime r,"billing_date" .= view (billingDate . to showGregorian) r
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)]wherepaymentBytes =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)]wherepaymentBytes =payment . to (T.decodeUtf8 . B64.encode . runPut . encodeMessage)
parseEventAmendment:: NetworkMode-> ModTime-> Value-> Parser (EventAmendment (NetworkId, Address))parseEventAmendment nmode t = unversion "EventAmendment" $ p wherep (Version 1 _) = parseEventAmendmentV1 nmode tp (Version 2 0) = parseEventAmendmentV2 nmode tp ver = badVersion "EventAmendment" ver
parseEventAmendment ::NetworkMode ->ModTime ->Value ->Parser (EventAmendment (NetworkId, Address))parseEventAmendment nmode t = unversion "EventAmendment" $ pwherep (Version 1 _) = parseEventAmendmentV1 nmode tp (Version 2 0) = parseEventAmendmentV2 nmode tp ver = badVersion "EventAmendment" ver
letparseA :: Text -> Parser (EventAmendment (NetworkId, Address))parseA "timeChange" = TimeChange t <$> o .: "eventTime"parseA "addrChange" = CreditToChange t <$> parseCreditToV1 nmode oparseA "metadataChange" = MetadataChange t <$> o .: "eventMeta"parseA tid =fail . T.unpack $ "Amendment type " <> tid <> " not recognized."ino .: "amendment" >>= parseA
let parseA :: Text -> Parser (EventAmendment (NetworkId, Address))parseA "timeChange" = TimeChange t <$> o .: "eventTime"parseA "addrChange" = CreditToChange t <$> parseCreditToV1 nmode oparseA "metadataChange" = MetadataChange t <$> o .: "eventMeta"parseA tid =fail . T.unpack $ "Amendment type " <> tid <> " not recognized."in o .: "amendment" >>= parseA
letparseA :: Text -> Parser (EventAmendment (NetworkId, Address))parseA "timeChange" = TimeChange t <$> o .: "eventTime"parseA "creditToChange" = CreditToChange t <$> parseCreditToV2 nmode oparseA "metadataChange" = MetadataChange t <$> o .: "eventMeta"parseA tid =fail . T.unpack $ "Amendment type " <> tid <> " not recognized."ino .: "amendment" >>= parseA
let parseA :: Text -> Parser (EventAmendment (NetworkId, Address))parseA "timeChange" = TimeChange t <$> o .: "eventTime"parseA "creditToChange" = CreditToChange t <$> parseCreditToV2 nmode oparseA "metadataChange" = MetadataChange t <$> o .: "eventMeta"parseA tid =fail . T.unpack $ "Amendment type " <> tid <> " not recognized."in o .: "amendment" >>= parseA
parseLogEntry:: NetworkMode-> UserId-> (UTCTime -> LogEvent)-> Value-> Parser (UTCTime -> (LogEntry (NetworkId, Address)))parseLogEntry nmode uid f = unversion "LogEntry" p wherep (Version 2 0) o = docreditTo' <- o .:? "creditTo" >>= maybe (pure $ CreditToUser uid)(parseCreditToV2 nmode)eventMeta' <- o .:? "eventMeta"pure $ \t -> LogEntry creditTo' (f t) eventMeta'
parseLogEntry ::NetworkMode ->UserId ->(UTCTime -> LogEvent) ->Value ->Parser (UTCTime -> (LogEntry (NetworkId, Address)))parseLogEntry nmode uid f = unversion "LogEntry" pwherep (Version 2 0) o = docreditTo' <-o .:? "creditTo">>= maybe(pure $ CreditToUser uid)(parseCreditToV2 nmode)eventMeta' <- o .:? "eventMeta"pure $ \t -> LogEntry creditTo' (f t) eventMeta'p ver o = badVersion "LogEntry" ver o
letparseAnnually 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 valinfromMaybe notFound $ parseV 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 valin fromMaybe notFound $ parseV o
import Aftok.Billing( Billable,Subscription,SubscriptionId,)import qualified Bippy.Proto as Pimport Bippy.Types( Satoshi (..),expiryTime,getExpires,getPaymentDetails,)import Control.Lens( makeLenses,makePrisms,view,)import Data.Aeson (Value)import qualified Data.Text as Timport Data.Thyme.Clock as Cimport Data.Thyme.Time as Cimport Data.UUIDimport Haskoin.Address.Base58 (decodeBase58Check)
import Control.Lens ( makeLenses, makePrisms, view)import Data.Aeson ( Value )import Data.Thyme.Clock as Cimport Data.Thyme.Time as Cimport qualified Data.Text as Timport Data.UUIDimport qualified Bippy.Proto as Pimport Bippy.Types ( expiryTime, getExpires, getPaymentDetails, Satoshi(..))import Haskoin.Address.Base58 ( decodeBase58Check )import Aftok.Billing ( Billable, Subscription, SubscriptionId)newtype PaymentRequestId = PaymentRequestId UUID deriving (Show, Eq)
data PaymentRequest' s = PaymentRequest{ _subscription :: s, _paymentRequest :: P.PaymentRequest, _paymentKey :: PaymentKey, _paymentRequestTime :: C.UTCTime, _billingDate :: C.Day} deriving (Functor, Foldable, Traversable)
data PaymentRequest' s= PaymentRequest{ _subscription :: s,_paymentRequest :: P.PaymentRequest,_paymentKey :: PaymentKey,_paymentRequestTime :: C.UTCTime,_billingDate :: C.Day}deriving (Functor, Foldable, Traversable)
data Payment' r = Payment{ _request :: r, _payment :: P.Payment, _paymentDate :: C.UTCTime, _exchangeRates :: Maybe Value} deriving (Functor, Foldable, Traversable)
data Payment' r= Payment{ _request :: r,_payment :: P.Payment,_paymentDate :: C.UTCTime,_exchangeRates :: Maybe Value}deriving (Functor, Foldable, Traversable)
-- using error here is reasonable since it would indicate-- a serialization problemin either (error . T.pack) (check . getExpires)$ getPaymentDetails (view paymentRequest req)
in -- using error here is reasonable since it would indicate-- a serialization problemeither (error . T.pack) (check . getExpires) $getPaymentDetails (view paymentRequest req)
import Aftok.Billingimport Aftok.Currency.Bitcoin( NetworkId (..),NetworkMode,satoshi,toNetwork,)import Aftok.Databaseimport Aftok.Payments.Typesimport Aftok.Project (depf)import qualified Aftok.TimeLog as TLimport Aftok.Types( ProjectId,UserId,)import qualified Bippy as Bimport qualified Bippy.Proto as Pimport qualified Bippy.Types as BTimport Control.Error.Util (maybeT)import Control.Lens( (%~),(^.),makeClassy,makeClassyPrisms,review,traverseOf,view,)import Control.Lens.Tupleimport Control.Monad.Except( MonadError,throwError,)import qualified Crypto.PubKey.RSA.Types as RSA( Error (..),PrivateKey,)import Crypto.Random.Types( MonadRandom,getRandomBytes,)import Data.AffineSpace ((.+^))import Data.Map.Strict (assocs)import Data.Thyme.Clock as Cimport Data.Thyme.Time as Timport Haskoin.Address (Address (..))import Haskoin.Address.Base58 (encodeBase58Check)import Haskoin.Script (ScriptOutput (..))import Network.URI
import Control.Error.Util ( maybeT )import Control.Lens ( makeClassy, makeClassyPrisms, review, view, (%~), (^.), traverseOf)import Control.Lens.Tupleimport Control.Monad.Except ( MonadError, throwError)import qualified Crypto.PubKey.RSA.Types as RSA( Error(..), PrivateKey)import Crypto.Random.Types ( MonadRandom, getRandomBytes)import Data.AffineSpace ( (.+^) )import Data.Map.Strict ( assocs )import Data.Thyme.Clock as Cimport Data.Thyme.Time as Timport qualified Bippy as Bimport qualified Bippy.Proto as Pimport qualified Bippy.Types as BTimport Haskoin.Address ( Address(..) )import Haskoin.Address.Base58 ( encodeBase58Check )import Haskoin.Script ( ScriptOutput(..) )import Network.URIimport Aftok.Types ( UserId, ProjectId)import Aftok.Billingimport Aftok.Currency.Bitcoin ( NetworkId(..), NetworkMode, satoshi, toNetwork)import Aftok.Databaseimport Aftok.Payments.Typesimport Aftok.Project ( depf )import qualified Aftok.TimeLog as TL
data PaymentsConfig= PaymentsConfig{ _networkMode :: !NetworkMode,_signingKey :: !RSA.PrivateKey,_pkiData :: !BT.PKIData}
data PaymentsConfig = PaymentsConfig{ _networkMode :: !NetworkMode, _signingKey :: !RSA.PrivateKey, _pkiData :: !BT.PKIData}
data BillingOps (m :: * -> *) = BillingOps{ -- | generator for user memomemoGen :: Subscription' UserId Billable -- ^ subscription being billed-> T.Day -- ^ billing date-> C.UTCTime -- ^ payment request generation time-> m (Maybe Text)-- | generator for payment response URL, uriGen :: PaymentKey -- ^ payment key to be included in the URL-> m (Maybe URI)-- | generator for merchant payload, payloadGen :: Subscription' UserId Billable -- ^ subscription being billed-> T.Day -- ^ billing date-> C.UTCTime -- ^ payment request generation time-> m (Maybe ByteString)}
data BillingOps (m :: * -> *)= BillingOps{ -- | generator for user memomemoGen ::Subscription' UserId Billable -> -- subscription being billedT.Day -> -- billing dateC.UTCTime -> -- payment request generation timem (Maybe Text),-- | generator for payment response URLuriGen ::PaymentKey -> -- payment key to be included in the URLm (Maybe URI),-- | generator for merchant payloadpayloadGen ::Subscription' UserId Billable -> -- subscription being billedT.Day -> -- billing dateC.UTCTime -> -- payment request generation timem (Maybe ByteString)}
= Paid !Payment -- ^ the request was paid with the specified payment| Unpaid !PaymentRequest -- ^ the request has not been paid, but has not yet expired| Expired !PaymentRequest -- ^ the request was not paid prior to the expiration date
= -- | the request was paid with the specified paymentPaid !Payment| -- | the request has not been paid, but has not yet expiredUnpaid !PaymentRequest| -- | the request was not paid prior to the expiration dateExpired !PaymentRequest
createPaymentRequests:: ( MonadRandom m, MonadReader r m, HasPaymentsConfig r, MonadError e m, AsPaymentError e, MonadDB m)=> BillingOps m -- ^ generators for payment request components-> C.UTCTime -- ^ timestamp for payment request creation-> UserId -- ^ customer responsible for payment-> ProjectId -- ^ project whose worklog is to be paid-> m [PaymentRequestId]
createPaymentRequests ::( MonadRandom m,MonadReader r m,HasPaymentsConfig r,MonadError e m,AsPaymentError e,MonadDB m) =>-- | generators for payment request componentsBillingOps m ->-- | timestamp for payment request creationC.UTCTime ->-- | customer responsible for paymentUserId ->-- | project whose worklog is to be paidProjectId ->m [PaymentRequestId]
createSubscriptionPaymentRequests:: ( MonadRandom m, MonadReader r m, HasPaymentsConfig r, MonadError e m, AsPaymentError e, MonadDB m)=> BillingOps m-> C.UTCTime-> (SubscriptionId, Subscription)-> m [PaymentRequestId]
createSubscriptionPaymentRequests ::( MonadRandom m,MonadReader r m,HasPaymentsConfig r,MonadError e m,AsPaymentError e,MonadDB m) =>BillingOps m ->C.UTCTime ->(SubscriptionId, Subscription) ->m [PaymentRequestId]
createPaymentRequest:: ( MonadRandom m, MonadReader r m, HasPaymentsConfig r, MonadError e m, AsPaymentError e, MonadDB m)=> BillingOps m-> C.UTCTime-> SubscriptionId-> Subscription' UserId Billable-> T.Day-> m PaymentRequestId
createPaymentRequest ::( MonadRandom m,MonadReader r m,HasPaymentsConfig r,MonadError e m,AsPaymentError e,MonadDB m) =>BillingOps m ->C.UTCTime ->SubscriptionId ->Subscription' UserId Billable ->T.Day ->m PaymentRequestId
pkey <- PaymentKey . encodeBase58Check <$> getRandomBytes 32memo <- memoGen ops sub bday nowuri <- uriGen ops pkey
pkey <- PaymentKey . encodeBase58Check <$> getRandomBytes 32memo <- memoGen ops sub bday nowuri <- uriGen ops pkey
reqErr <- B.createPaymentRequest (cfg ^. signingKey) (cfg ^. pkiData) detailsreq <- either (throwError . review _SigningError) pure reqErr
reqErr <- B.createPaymentRequest (cfg ^. signingKey) (cfg ^. pkiData) detailsreq <- either (throwError . review _SigningError) pure reqErr
findUnbilledDates:: (MonadDB m, MonadError e m, AsPaymentError e)=> C.UTCTime -- ^ the date against which payment request expiration should be checked-> Billable-> [(PaymentRequestId, PaymentRequest)] -- ^ the list of existing payment requests-> [T.Day] -- ^ the list of expected billing days-> m [T.Day] -- ^ the list of billing days for which no payment request exists
findUnbilledDates ::(MonadDB m, MonadError e m, AsPaymentError e) =>-- | the date against which payment request expiration should be checkedC.UTCTime ->Billable ->-- | the list of existing payment requests[(PaymentRequestId, PaymentRequest)] ->-- | the list of expected billing days[T.Day] ->-- | the list of billing days for which no payment request existsm [T.Day]
EQ -> getRequestStatus now p >>= \s -> case s ofExpired r ->if view _utctDay now > addDays (view gracePeriod b) (view billingDate r)then throwError (review _Overdue (r ^. subscription))else fmap (d :) $ findUnbilledDates now b px dx -- d will be rebilled_ -> findUnbilledDates now b ps ds -- if paid or unpaid, nothing to do
EQ ->getRequestStatus now p >>= \s -> case s ofExpired r ->if view _utctDay now > addDays (view gracePeriod b) (view billingDate r)then throwError (review _Overdue (r ^. subscription))else fmap (d :) $ findUnbilledDates now b px dx -- d will be rebilled_ -> findUnbilledDates now b ps ds -- if paid or unpaid, nothing to do
getRequestStatus:: (MonadDB m)=> C.UTCTime -- ^ the date against which request expiration should be checked-> (PaymentRequestId, PaymentRequest) -- ^ the request for which to find a payment-> m PaymentRequestStatus
getRequestStatus ::(MonadDB m) =>-- | the date against which request expiration should be checkedC.UTCTime ->-- | the request for which to find a payment(PaymentRequestId, PaymentRequest) ->m PaymentRequestStatus
createPaymentDetails:: ( MonadRandom m, MonadReader r m, HasPaymentsConfig r, MonadError e m, AsPaymentError e, MonadDB m)=> T.Day -- ^ payout date (billing date)-> C.UTCTime -- ^ timestamp of payment request creation-> Maybe Text -- ^ user memo-> Maybe URI -- ^ payment response URL-> Maybe ByteString -- ^ merchant payload-> Billable -- ^ billing information-> m P.PaymentDetails
createPaymentDetails ::( MonadRandom m,MonadReader r m,HasPaymentsConfig r,MonadError e m,AsPaymentError e,MonadDB m) =>-- | payout date (billing date)T.Day ->-- | timestamp of payment request creationC.UTCTime ->-- | user memoMaybe Text ->-- | payment response URLMaybe URI ->-- | merchant payloadMaybe ByteString ->-- | billing informationBillable ->m P.PaymentDetails
pure $ B.createPaymentDetails (toNetwork (cfg ^. networkMode) BTC)outputs(T.fromThyme billingTime)expirymemouripayloadwhere payoutTime = T.mkUTCTime payoutDate (fromInteger 0)
pure $B.createPaymentDetails(toNetwork (cfg ^. networkMode) BTC)outputs(T.fromThyme billingTime)expirymemouripayloadwherepayoutTime = T.mkUTCTime payoutDate (fromInteger 0)
getProjectPayouts:: (MonadDB m, MonadError e m, AsPaymentError e)=> C.UTCTime-> ProjectId-> m (TL.Payouts (NetworkId, Address))
getProjectPayouts ::(MonadDB m, MonadError e m, AsPaymentError e) =>C.UTCTime ->ProjectId ->m (TL.Payouts (NetworkId, Address))
createPayoutsOutputs:: (MonadDB m, MonadError e m, AsPaymentError e)=> C.UTCTime-> BT.Satoshi-> TL.Payouts (NetworkId, Address)-> m [BT.Output]
createPayoutsOutputs ::(MonadDB m, MonadError e m, AsPaymentError e) =>C.UTCTime ->BT.Satoshi ->TL.Payouts (NetworkId, Address) ->m [BT.Output]
createOutputs:: (MonadDB m, MonadError e m, AsPaymentError e)=> C.UTCTime-> TL.CreditTo (NetworkId, Address)-> BT.Satoshi-> m [BT.Output]
createOutputs ::(MonadDB m, MonadError e m, AsPaymentError e) =>C.UTCTime ->TL.CreditTo (NetworkId, Address) ->BT.Satoshi ->m [BT.Output]
other -> throwError $ review _IllegalAddress other
other -> throwError $ review _IllegalAddress other
wherefindOp = FindUnpaidRequests sidcheckAccess d = if view (_3 . customer) d == uidthen pure [d]else raiseOpForbidden uid (UserNotSubscriber sid) findOp
wherefindOp = FindUnpaidRequests sidcheckAccess d =if view (_3 . customer) d == uidthen pure [d]else raiseOpForbidden uid (UserNotSubscriber sid) findOp
import Control.Lens ( makeLenses, makePrisms)import Crypto.Random.Types ( MonadRandom, getRandomBytes)import qualified Data.ByteString as BSimport Data.ByteString.Base64.URL as B64import Data.Thyme.Clock as Cimport Aftok.Types
data Project = Project{ _projectName :: ProjectName, _inceptionDate :: C.UTCTime, _initiator :: UserId, _depf :: DepreciationFunction}
data Project= Project{ _projectName :: ProjectName,_inceptionDate :: C.UTCTime,_initiator :: UserId,_depf :: DepreciationFunction}
data Invitation = Invitation{ _projectId :: ProjectId, _invitingUser :: UserId, _invitedEmail :: Email, _invitationTime :: C.UTCTime, _acceptanceTime :: Maybe C.UTCTime}
data Invitation= Invitation{ _projectId :: ProjectId,_invitingUser :: UserId,_invitedEmail :: Email,_invitationTime :: C.UTCTime,_acceptanceTime :: Maybe C.UTCTime}
import Data.Aeson ( Value(..), (.=), (.:), object)import Data.Aeson.Types ( Parser )import Data.Text ( unpack )import Aftok.Types
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]]
( LogEntry(..), creditTo, event, eventMeta, CreditTo(..), _CreditToCurrency, _CreditToUser, _CreditToProject, creditToName, LogEvent(..), eventName, nameEvent, eventTime, WorkIndex(WorkIndex), _WorkIndex, workIndex, DepF, toDepF, EventId(EventId), _EventId, ModTime(ModTime), _ModTime, EventAmendment(..), AmendmentId(AmendmentId), _AmendmentId, Payouts(..), _Payouts, payouts, linearDepreciation
( LogEntry (..),creditTo,event,eventMeta,CreditTo (..),_CreditToCurrency,_CreditToUser,_CreditToProject,creditToName,LogEvent (..),eventName,nameEvent,eventTime,WorkIndex (WorkIndex),_WorkIndex,workIndex,DepF,toDepF,EventId (EventId),_EventId,ModTime (ModTime),_ModTime,EventAmendment (..),AmendmentId (AmendmentId),_AmendmentId,Payouts (..),_Payouts,payouts,linearDepreciation,
import Control.Arrow ( (&&&) )import Control.Lensimport Data.AdditiveGroupimport Data.Aeson as Aimport Data.AffineSpaceimport Data.Eq ( Eq, (==))import Data.Either ( Either(..), rights)import Data.Foldable as Fimport Data.Function ( ($), (.), id)import Data.Functor ( fmap )import Data.Heap as Himport Data.List.NonEmpty as Limport Data.Maybe ( Maybe(..) )import Data.Map.Strict as MSimport Data.Ord ( Ord(..), Ordering(..))import Data.Ratio ( Rational )import Data.Text ( Text )import Data.Thyme.Clock as Cimport Data.UUIDimport Data.VectorSpaceimport Prelude ( (/), (*))import Text.Show ( Show )import Aftok.Intervalimport Aftok.Types
import Aftok.Intervalimport Aftok.Typesimport Control.Arrow ((&&&))import Control.Lensimport Data.AdditiveGroupimport Data.Aeson as Aimport Data.AffineSpaceimport Data.Either( Either (..),rights,)import Data.Eq( (==),Eq,)import Data.Foldable as Fimport Data.Function( ($),(.),id,)import Data.Functor (fmap)import Data.Heap as Himport Data.List.NonEmpty as Limport Data.Map.Strict as MSimport Data.Maybe (Maybe (..))import Data.Ord( Ord (..),Ordering (..),)import Data.Ratio (Rational)import Data.Text (Text)import Data.Thyme.Clock as Cimport Data.UUIDimport Data.VectorSpaceimport Text.Show (Show)import Prelude( (*),(/),)
{-|- The depreciation function should return a value between 0 and 1;- this result is multiplied by the length of an interval of work to determine- the depreciated value of the work.-}
-- |-- - The depreciation function should return a value between 0 and 1;-- - this result is multiplied by the length of an interval of work to determine-- - the depreciated value of the work.
data LogEvent = StartWork { _eventTime :: !C.UTCTime }| StopWork { _eventTime :: !C.UTCTime }deriving (Show, Eq)
data LogEvent= StartWork {_eventTime :: !C.UTCTime}| StopWork {_eventTime :: !C.UTCTime}deriving (Show, Eq)
compare (StartWork t0) (StopWork t1) = if t0 == t1 then GT else compare t0 t1compare (StopWork t0) (StartWork t1) = if t0 == t1 then LT else compare t0 t1
compare (StartWork t0) (StopWork t1) = if t0 == t1 then GT else compare t0 t1compare (StopWork t0) (StartWork t1) = if t0 == t1 then LT else compare t0 t1
data LogEntry a = LogEntry{ _creditTo :: !(CreditTo a), _event :: !LogEvent, _eventMeta :: !(Maybe A.Value)} deriving (Show, Eq)
data LogEntry a= LogEntry{ _creditTo :: !(CreditTo a),_event :: !LogEvent,_eventMeta :: !(Maybe A.Value)}deriving (Show, Eq)
{-|- Given a depreciation function, the "current" time, and a foldable functor of log intervals,- produce the total, depreciated length of work to be credited to an address.-}
-- |-- - Given a depreciation function, the "current" time, and a foldable functor of log intervals,-- - produce the total, depreciated length of work to be credited to an address.
{-|- Payouts are determined by computing a depreciated duration value for- each work interval. This function computes the percentage of the total- work allocated to each address.-}
-- |-- - Payouts are determined by computing a depreciated duration value for-- - each work interval. This function computes the percentage of the total-- - work allocated to each address.
(^+^ total) &&& id $ workCredit dep ptime ivals
(^+^ total) &&& id $ workCredit dep ptime ivals
rawIndex = F.foldl' appendLogEntry MS.empty sortedEntriesaccum:: (CreditTo a)-> [Either LogEvent Interval]-> Map (CreditTo a) (NonEmpty Interval)-> Map (CreditTo a) (NonEmpty Interval)
rawIndex = F.foldl' appendLogEntry MS.empty sortedEntriesaccum ::(CreditTo a) ->[Either LogEvent Interval] ->Map (CreditTo a) (NonEmpty Interval) ->Map (CreditTo a) (NonEmpty Interval)
{-|- The values of the raw index map are either complete intervals (which may be- extended if a new start is encountered at the same instant as the end of the- interval) or start events awaiting completion.-}
-- |-- - The values of the raw index map are either complete intervals (which may be-- - extended if a new start is encountered at the same instant as the end of the-- - interval) or start events awaiting completion.
combine (e1@(StopWork _)) (e2@(StopWork _)) = Left $ min e1 e2 -- ignore redundant endscombine _ e2 = Left e2
combine (e1@(StopWork _)) (e2@(StopWork _)) = Left $ min e1 e2 -- ignore redundant endscombine _ e2 = Left e2
{-|- A very simple linear function for calculating depreciation.-}linearDepreciation:: Months -- ^ The number of initial months during which no depreciation occurs-> Months -- ^ The number of months over which each logged interval will be depreciated-> DepF -- ^ The resulting configured depreciation function.
-- |-- - A very simple linear function for calculating depreciation.linearDepreciation ::-- | The number of initial months during which no depreciation occursMonths ->-- | The number of months over which each logged interval will be depreciatedMonths ->-- | The resulting configured depreciation function.DepF
depPct dt = if dt < monthsLength undepLengththen 1else toSeconds (max zeroV (maxDepreciable ^-^ dt))/ toSeconds maxDepreciablein \ptime ival ->
depPct dt =if dt < monthsLength undepLengththen 1elsetoSeconds (max zeroV (maxDepreciable ^-^ dt))/ toSeconds maxDepreciablein \ptime ival ->
import Control.Lens ( makeLenses, makePrisms)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 Aftok.Currency.Zcash ( ZAddr )
-- payouts are made directly via a cryptocurrency network= CreditToCurrency !a-- payouts are distributed as requested by the specified contributor| CreditToUser !UserId-- payouts are distributed to this project's contributors| CreditToProject !ProjectId
= -- payouts are made directly via a cryptocurrency networkCreditToCurrency !a| -- payouts are distributed as requested by the specified contributorCreditToUser !UserId| -- payouts are distributed to this project's contributorsCreditToProject !ProjectId
import Control.Error.Util ( maybeT )import Control.Monad.Free.Churchimport Data.Functor.Coyonedaimport Data.Map.Strict as M
import Control.Error.Util (maybeT)import Control.Monad.Free.Churchimport Data.Functor.Coyonedaimport Data.Map.Strict as M
import Control.Lens ( makeLenses, (^.))import qualified Data.ByteString.Char8 as C8import qualified Data.Configurator as Cimport qualified Data.Configurator.Types as CTimport qualified Data.List as Limport System.Environment ( getEnvironment )import Filesystem.Path.CurrentOS ( fromText, encodeString)import qualified Filesystem.Path.CurrentOS as Pimport Snap.Coreimport qualified Snap.Http.Server.Config as SCimport Snap.Snaplet.PostgresqlSimple
import Aftok.Configimport Aftok.Currency.Zcash (ZcashdConfig (..))import Aftok.Snaplet.Users (CaptchaConfig (..))import Control.Lens( (^.),makeLenses,)import qualified Data.ByteString.Char8 as C8import qualified Data.Configurator as Cimport qualified Data.Configurator.Types as CTimport qualified Data.List as Limport Filesystem.Path.CurrentOS( encodeString,fromText,)import qualified Filesystem.Path.CurrentOS as Pimport Snap.Coreimport qualified Snap.Http.Server.Config as SCimport Snap.Snaplet.PostgresqlSimpleimport System.Environment (getEnvironment)
import Aftok.Currency.Zcash (ZcashdConfig(..))import Aftok.Configimport Aftok.Snaplet.Users (CaptchaConfig(..))
data QConfig= QConfig{ _hostname :: C8.ByteString,_port :: Int,_authSiteKey :: P.FilePath,_cookieTimeout :: Maybe Int,_pgsConfig :: PGSConfig,_smtpConfig :: SmtpConfig,_billingConfig :: BillingConfig,_templatePath :: P.FilePath,_staticAssetPath :: P.FilePath,_recaptchaSecret :: CaptchaConfig,_zcashdConfig :: ZcashdConfig}
data QConfig = QConfig{ _hostname :: C8.ByteString, _port :: Int, _authSiteKey :: P.FilePath, _cookieTimeout :: Maybe Int, _pgsConfig :: PGSConfig, _smtpConfig :: SmtpConfig, _billingConfig :: BillingConfig, _templatePath :: P.FilePath, _staticAssetPath :: P.FilePath, _recaptchaSecret :: CaptchaConfig, _zcashdConfig :: ZcashdConfig}
import Control.Monad.Trans.Maybe ( mapMaybeT )import Data.Aesonimport Data.Aeson.Typesimport Data.Hourglass.Types ( Seconds(..) )import Data.Thyme.Clock as Cimport Snap.Snaplet as Simport Aftok.Types ( UserId )import Aftok.Auction ( Auction(..), AuctionId, Bid(..), BidId)import Aftok.Database ( createAuction, createBid, findAuction)import Aftok.Jsonimport Aftok.Util ( fromMaybeT )
import Aftok.Snapletimport Aftok.Snaplet.Authimport Bippy.Types ( Satoshi(..) )
import Aftok.Auction( Auction (..),AuctionId,Bid (..),BidId,)import Aftok.Database( createAuction,createBid,findAuction,)import Aftok.Jsonimport Aftok.Snapletimport Aftok.Snaplet.Authimport Aftok.Types (UserId)import Aftok.Util (fromMaybeT)import Bippy.Types (Satoshi (..))import Control.Monad.Trans.Maybe (mapMaybeT)import Data.Aesonimport Data.Aeson.Typesimport Data.Hourglass.Types (Seconds (..))import Data.Thyme.Clock as Cimport Snap.Snaplet as S
auctionCreateParser = unv1 "auctions" p wherep o = CA <$> o .: "raiseAmount" <*> o .: "auctionStart" <*> o .: "auctionEnd"
auctionCreateParser = unv1 "auctions" pwherep o = CA <$> o .: "raiseAmount" <*> o .: "auctionStart" <*> o .: "auctionEnd"
bidCreateParser uid t = unv1 "bids" p wherep o =Bid uid<$> (Seconds <$> o .: "bidSeconds")<*> (Satoshi <$> o .: "bidAmount")<*> pure t
bidCreateParser uid t = unv1 "bids" pwherep o =Bid uid<$> (Seconds <$> o .: "bidSeconds")<*> (Satoshi <$> o .: "bidAmount")<*> pure t
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.Lensimport Control.Error.Util ( maybeT )import Control.Monad.Trans.Maybe ( mapMaybeT )import Data.Aeson ( (.:) )import qualified Data.Aeson as Aimport qualified Data.Aeson.Types as Aimport Data.Attoparsec.ByteString ( parseOnly )import Aftok.Typesimport Aftok.Databaseimport Aftok.Snapletimport Aftok.Util.Http ( authHeaderParser )
import Aftok.Databaseimport Aftok.Snapletimport Aftok.Typesimport Aftok.Util.Http (authHeaderParser)import Control.Error.Util (maybeT)import Control.Lensimport Control.Monad.Trans.Maybe (mapMaybeT)import Data.Aeson ((.:))import qualified Data.Aeson as Aimport qualified Data.Aeson.Types as Aimport Data.Attoparsec.ByteString (parseOnly)import Snap.Coreimport Snap.Snaplet as Simport qualified Snap.Snaplet.Auth as AU
(uname, pwd) <- either (throwDenied . AU.AuthError) pure$ parseOnly authHeaderParser rawHeader
(uname, pwd) <-either (throwDenied . AU.AuthError) pure $parseOnly authHeaderParser rawHeader
authResult <- with auth $ AU.loginByUsername(loginUser credentials)(AU.ClearText (encodeUtf8 $ loginPass credentials))False
authResult <-with auth $AU.loginByUsername(loginUser credentials)(AU.ClearText (encodeUtf8 $ loginPass credentials))False
import Aftok.Billingimport Aftok.Database( DBOp (..),createBillable,liftdb,withProjectAuth,)import Aftok.Jsonimport Aftok.Snapletimport Aftok.Snaplet.Authimport Aftok.Typesimport Bippy.Types (Satoshi (..))import Control.Lens ((^.))import Data.Aesonimport Data.Aeson.Typesimport Data.Thyme.Clock as Cimport Data.Thyme.Time.Core (toThyme)import Snap.Snaplet as S
import Control.Lens ( (^.) )import Data.Aesonimport Data.Aeson.Typesimport Data.Thyme.Clock as Cimport Data.Thyme.Time.Core ( toThyme )import Snap.Snaplet as Simport Aftok.Billingimport Bippy.Types ( Satoshi(..) )import Aftok.Jsonimport Aftok.Typesimport Aftok.Database ( createBillable, withProjectAuth, liftdb, DBOp(..))import Aftok.Snapletimport Aftok.Snaplet.Auth
parseCreateBillable uid pid = unversion "Billable" p wherep (Version 1 0) o =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"p ver o = badVersion "Billable" ver o
parseCreateBillable uid pid = unversion "Billable" pwherep (Version 1 0) o =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"p ver o = badVersion "Billable" ver o
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 Cimport qualified Data.Text.Encoding as Timport qualified Bippy.Proto as Pimport Network.HTTP.Client.OpenSSLimport Network.HTTP.Client ( defaultManagerSettings, managerResponseTimeout, responseTimeoutMicro, HttpException)import Network.Wreq ( asValue, responseBody, defaults, manager, getWith)import OpenSSL.Session ( context )import Snap.Core ( readRequestBody, logError)import Snap.Snaplet as S
import Aftok.Config as ACimport Aftok.Billingimport Aftok.Databaseimport Aftok.Paymentsimport Aftok.Util ( fromMaybeT )import Aftok.Snapletimport Aftok.Snaplet.Auth
import Aftok.Billingimport Aftok.Config as ACimport Aftok.Databaseimport Aftok.Paymentsimport Aftok.Snapletimport Aftok.Snaplet.Authimport Aftok.Util (fromMaybeT)import qualified Bippy.Proto as Pimport Control.Exception (try)import Control.Lens( (.~),(^.),_1,_2,_Left,_Right,preview,view,)import Control.Monad.Trans.Maybe (mapMaybeT)import Data.ProtocolBuffers (decodeMessage)import Data.Serialize.Get (runGetLazy)import qualified Data.Text.Encoding as Timport Data.Thyme.Clock as Cimport Network.HTTP.Client( HttpException,defaultManagerSettings,managerResponseTimeout,responseTimeoutMicro,)import Network.HTTP.Client.OpenSSLimport Network.Wreq( asValue,defaults,getWith,manager,responseBody,)import OpenSSL.Session (context)import Snap.Core( logError,readRequestBody,)import Snap.Snaplet as S
preq <- getPaymentRequestHandler'pmnt <- either(\msg -> snapError 400 $ "Could not decode payment response: " <> show msg)pure(runGetLazy decodeMessage requestBody)
preq <- getPaymentRequestHandler'pmnt <-either(\msg -> snapError 400 $ "Could not decode payment response: " <> show msg)pure(runGetLazy decodeMessage requestBody)
letopts =defaults& manager.~ Left (opensslManagerSettings context)& manager.~ Left(defaultManagerSettings{ managerResponseTimeout = responseTimeoutMicro 10000})
let opts =defaults& manager.~ Left (opensslManagerSettings context)& manager.~ Left( defaultManagerSettings{ managerResponseTimeout = responseTimeoutMicro 10000})
import Aftok.Configimport Aftok.Databaseimport Aftok.Projectimport Aftok.QConfig as QCimport Aftok.Snapletimport Aftok.Snaplet.Authimport Aftok.TimeLog.Serialization (depfFromJSON)import Aftok.Typesimport Aftok.Util (fromMaybeT)import Control.Lensimport Control.Monad.Trans.Maybe (mapMaybeT)import Data.Aeson as Aimport Data.Attoparsec.ByteString (takeByteString)import Data.Thyme.Clock as Cimport Filesystem.Path.CurrentOS (encodeString)import qualified Filesystem.Path.CurrentOS as Fimport Network.Mail.Mimeimport Network.Mail.SMTP as SMTPimport Snap.Coreimport Snap.Snaplet as Simport Text.StringTemplate
import Control.Lensimport Control.Monad.Trans.Maybe ( mapMaybeT )import Data.Aeson as Aimport Data.Attoparsec.ByteString ( takeByteString )import Data.Thyme.Clock as Cimport Filesystem.Path.CurrentOS ( encodeString )import qualified Filesystem.Path.CurrentOS as Fimport Network.Mail.Mimeimport Network.Mail.SMTP as SMTPimport Text.StringTemplate
data ProjectCreateRequest = CP {cpn :: Text, cpdepf :: DepreciationFunction}
import Aftok.Typesimport Aftok.Configimport Aftok.Databaseimport Aftok.Projectimport Aftok.QConfig as QCimport Aftok.Snapletimport Aftok.Snaplet.Authimport Aftok.TimeLog.Serialization ( depfFromJSON )import Aftok.Util ( fromMaybeT )import Snap.Coreimport Snap.Snaplet as Sdata ProjectCreateRequest = CP { cpn :: Text, cpdepf :: DepreciationFunction }
snapEval$ (,)<$> (runMaybeT $ findUserProject uid pid)<*> createInvitation pid uid toEmail tliftIO $ sendProjectInviteEmail cfg(p ^. projectName)(Email "noreply@aftok.com")toEmailinvCode
snapEval $(,)<$> (runMaybeT $ findUserProject uid pid)<*> createInvitation pid uid toEmail tliftIO $sendProjectInviteEmailcfg(p ^. projectName)(Email "noreply@aftok.com")toEmailinvCode
sendProjectInviteEmail:: QConfig-> ProjectName-> Email -- Inviting user's email address-> Email -- Invitee's email address-> InvitationCode-> IO ()
sendProjectInviteEmail ::QConfig ->ProjectName ->Email -> -- Inviting user's email addressEmail -> -- Invitee's email addressInvitationCode ->IO ()
mailer = maybe (sendMailWithLogin _smtpHost)(sendMailWithLogin' _smtpHost)_smtpPortin buildProjectInviteEmail (cfg ^. templatePath) pn fromEmail toEmail invCode
mailer =maybe(sendMailWithLogin _smtpHost)(sendMailWithLogin' _smtpHost)_smtpPortin buildProjectInviteEmail (cfg ^. templatePath) pn fromEmail toEmail invCode
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 addressEmail -> -- Invitee's email addressInvitationCode ->IO Mail
setAttribute "from_email" (fromEmail ^. _Email). setAttribute "project_name" pn. setAttribute "to_email" (toEmail ^. _Email). setAttribute "inv_code" (renderInvCode invCode)
setAttribute "from_email" (fromEmail ^. _Email). setAttribute "project_name" pn. setAttribute "to_email" (toEmail ^. _Email). setAttribute "inv_code" (renderInvCode invCode)
toAddr = Address Nothing (toEmail ^. _Email)subject = "Welcome to the " <> pn <> " Aftok!"body = plainPart . render $ setAttrs templatein pure $ SMTP.simpleMail fromAddr [toAddr] [] [] subject [body]
toAddr = Address Nothing (toEmail ^. _Email)subject = "Welcome to the " <> pn <> " Aftok!"body = plainPart . render $ setAttrs templatein pure $ SMTP.simpleMail fromAddr [toAddr] [] [] subject [body]
( acceptInvitationHandler, checkZAddrHandler, registerHandler, CaptchaConfig(..), CaptchaError(..), checkCaptcha, RegisterOps(..)
( acceptInvitationHandler,checkZAddrHandler,registerHandler,CaptchaConfig (..),CaptchaError (..),checkCaptcha,RegisterOps (..),
import Control.Lens ( makeLenses, (^.) )import Control.FromSum ( fromMaybeM )import qualified Data.Aeson as Aimport Data.Aeson ( (.:), (.:?), (.=))import qualified Data.Map.Strict as Mimport Data.Text as Timport Data.Text.Encoding as Timport Data.Thyme.Clock as Cimport Network.HTTP.Client ( parseRequest, responseBody, responseStatus, httpLbs)import Network.HTTP.Client.TLS ( newTlsManager )import Network.HTTP.Client.MultipartFormData( formDataBody, partBS)import Network.HTTP.Types.Status ( statusCode )import Aftok.Currency.Zcash ( ZAddr, RPCError, ZValidateAddressErr )import Aftok.Database ( createUser, acceptInvitation )import Aftok.Project ( InvitationCode, parseInvCode )import Aftok.Types ( UserId, User(..), AccountRecovery(..), Email(..), UserName(..), _UserName)import Aftok.Snapletimport Aftok.Snaplet.Auth
import Aftok.Currency.Zcash (RPCError, ZAddr, ZValidateAddressErr)import Aftok.Database (acceptInvitation, createUser)import Aftok.Project (InvitationCode, parseInvCode)import Aftok.Snapletimport Aftok.Snaplet.Authimport Aftok.Types( AccountRecovery (..),Email (..),User (..),UserId,UserName (..),_UserName,)import Control.FromSum (fromMaybeM)import Control.Lens ((^.), makeLenses)import Data.Aeson( (.:),(.:?),(.=),)import qualified Data.Aeson as Aimport qualified Data.Map.Strict as Mimport Data.Text as Timport Data.Text.Encoding as Timport Data.Thyme.Clock as Cimport Network.HTTP.Client( httpLbs,parseRequest,responseBody,responseStatus,)import Network.HTTP.Client.MultipartFormData( formDataBody,partBS,)import Network.HTTP.Client.TLS (newTlsManager)import Network.HTTP.Types.Status (statusCode)import qualified Snap.Core as Simport qualified Snap.Snaplet as Simport qualified Snap.Snaplet.Auth as AU
import qualified Snap.Core as Simport qualified Snap.Snaplet as Simport qualified Snap.Snaplet.Auth as AU
data RegisterOps m= RegisterOps{ validateZAddr :: Text -> m (Either (RPCError ZValidateAddressErr) ZAddr),sendConfirmationEmail :: Email -> m ()}
data RegisterRequest = RegisterRequest{ _regUser :: RegUser, _password :: ByteString, _captchaToken :: Text, _invitationCodes :: [InvitationCode]}
data RegisterRequest= RegisterRequest{ _regUser :: RegUser,_password :: ByteString,_captchaToken :: Text,_invitationCodes :: [InvitationCode]}
user <- RegUser <$> (UserName <$> v .: "username")<*> pure recovery
user <-RegUser <$> (UserName <$> v .: "username")<*> pure recovery
parseInvitationCodes c = either(\e -> fail $ "Invitation code was rejected as invalid: " <> e)pure(traverse parseInvCode c)
parseInvitationCodes c =either(\e -> fail $ "Invitation code was rejected as invalid: " <> e)pure(traverse parseInvCode c)
RegParseError msg -> A.object[ "parseError" .= msg ]RegCaptchaError e -> A.object[ "captchaError" .= (show e :: Text) ]RegZAddrError zerr -> A.object[ "zaddrError" .= (show zerr :: Text) ]
RegParseError msg ->A.object["parseError" .= msg]RegCaptchaError e ->A.object["captchaError" .= (show e :: Text)]RegZAddrError zerr ->A.object["zaddrError" .= (show zerr :: Text)]
letuname = userData ^. (regUser . username)createSUser = AU.createUser (uname ^. _UserName) (userData ^. password)createQUser = snapEval $ douserId <- createUser $ User uname acctRecoveryvoid $ traverse (acceptInvitation userId now) (userData ^. invitationCodes)pure userId
let uname = userData ^. (regUser . username)createSUser = AU.createUser (uname ^. _UserName) (userData ^. password)createQUser = snapEval $ douserId <- createUser $ User uname acctRecoveryvoid $ traverse (acceptInvitation userId now) (userData ^. invitationCodes)pure userId
uid <- requireUserIdnow <- liftIO C.getCurrentTimeparams <- S.getParamsinvCodes <- maybe (snapError 400 "invCode parameter is required")(pure . traverse (parseInvCode . T.decodeUtf8))(M.lookup "invCode" params)
uid <- requireUserIdnow <- liftIO C.getCurrentTimeparams <- S.getParamsinvCodes <-maybe(snapError 400 "invCode parameter is required")(pure . traverse (parseInvCode . T.decodeUtf8))(M.lookup "invCode" params)
wheretoError = \case"missing-input-secret" -> MissingInputSecret"invalid-input-secret" -> InvalidInputSecret"missing-input-response" -> MissingInputResponse"invalid-input-response" -> InvalidInputResponse"bad-request" -> BadRequest"timeout-or-duplicate" -> TimeoutOrDuplicateother -> CaptchaError $ "Unexpected error code: " <> other
wheretoError = \case"missing-input-secret" -> MissingInputSecret"invalid-input-secret" -> InvalidInputSecret"missing-input-response" -> MissingInputResponse"invalid-input-response" -> InvalidInputResponse"bad-request" -> BadRequest"timeout-or-duplicate" -> TimeoutOrDuplicateother -> CaptchaError $ "Unexpected error code: " <> other
request <- parseRequest "https://www.google.com/recaptcha/api/siteverify"reqWithBody <- formDataBody[ partBS "secret" (T.encodeUtf8 $ secretKey cfg), partBS "response" (T.encodeUtf8 token)]requestmanager <- newTlsManager
request <- parseRequest "https://www.google.com/recaptcha/api/siteverify"reqWithBody <-formDataBody[ partBS "secret" (T.encodeUtf8 $ secretKey cfg),partBS "response" (T.encodeUtf8 token)]requestmanager <- newTlsManager
import Data.Attoparsec.ByteString ( parseOnly )import Data.Attoparsec.ByteString.Char8( decimal )import Data.ByteString.Char8 as Bimport Data.Thyme.Clock as Cimport Data.Thyme.Timeimport Data.Time.ISO8601
import Data.Attoparsec.ByteString (parseOnly)import Data.Attoparsec.ByteString.Char8( decimal,)import Data.ByteString.Char8 as Bimport Data.Thyme.Clock as Cimport Data.Thyme.Timeimport Data.Time.ISO8601import Snap.Core
{-# LANGUAGE TupleSections #-}module Aftok.Snaplet.WorkLog whereimport Control.Lens ( (^.) )import Control.Monad.Trans.Maybe ( mapMaybeT )import qualified Data.Aeson as Aimport Data.Aeson ( (.=) )import qualified Data.Aeson.Types as Aimport qualified Data.Text as Timport Data.Thyme.Clock as Cimport Data.UUID as Uimport Haskoin.Address ( Address, textToAddr)import Aftok.Currency.Bitcoin ( NetworkId(..), NetworkMode, toNetwork)import Aftok.Databaseimport Aftok.Intervalimport Aftok.Jsonimport Aftok.Projectimport Aftok.TimeLogimport Aftok.Types ( _ProjectId, _UserId)import Aftok.Util ( fromMaybeT )
{-# LANGUAGE TupleSections #-}
import Snap.Coreimport Snap.Snaplet as S
import Aftok.Currency.Bitcoin( NetworkId (..),NetworkMode,toNetwork,)import Aftok.Databaseimport Aftok.Intervalimport Aftok.Jsonimport Aftok.Projectimport Aftok.Snapletimport Aftok.Snaplet.Authimport Aftok.Snaplet.Utilimport Aftok.TimeLogimport Aftok.Types( _ProjectId,_UserId,)import Aftok.Util (fromMaybeT)import Control.Lens ((^.))import Control.Monad.Trans.Maybe (mapMaybeT)import Data.Aeson ((.=))import qualified Data.Aeson as Aimport qualified Data.Aeson.Types as Aimport qualified Data.Text as Timport Data.Thyme.Clock as Cimport Data.UUID as Uimport Haskoin.Address( Address,textToAddr,)import Snap.Coreimport Snap.Snaplet as S
timestamp <- liftIO C.getCurrentTimecaseA.eitherDecode requestBody>>= A.parseEither (parseLogEntry nmode uid evCtr)ofLeft err ->snapError 400$ "Unable to parse log entry "
timestamp <- liftIO C.getCurrentTimecase A.eitherDecode requestBody>>= A.parseEither (parseLogEntry nmode uid evCtr) ofLeft err ->snapError 400 $"Unable to parse log entry "
Right entry -> doeid <- snapEval $ createEvent pid uid (entry timestamp)ev <- snapEval $ findEvent eidmaybe( snapError 500$ "An error occured retrieving the newly created event record.")(pure . (eid, ))ev
Right entry -> doeid <- snapEval $ createEvent pid uid (entry timestamp)ev <- snapEval $ findEvent eidmaybe( snapError 500 $"An error occured retrieving the newly created event record.")(pure . (eid,))ev
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)
uid <- requireUserIdpid <- requireProjectIdproject <- fromMaybeT(snapError 400 $ "Project not found for id " <> show pid)(mapMaybeT snapEval $ findUserProject uid pid)widx <- snapEval $ readWorkIndex pid uid
uid <- requireUserIdpid <- requireProjectIdproject <-fromMaybeT(snapError 400 $ "Project not found for id " <> show pid)(mapMaybeT snapEval $ findUserProject uid pid)widx <- snapEval $ readWorkIndex pid uid
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)(A.parseEither (parseEventAmendment nmode modTime) requestJSON)
either(snapError 400 . T.pack)(snapEval . amendEvent uid eventId)(A.parseEither (parseEventAmendment nmode modTime) requestJSON)
. obj$ [ "eventId" .= idValue _EventId eid, "projectId" .= idValue _ProjectId pid, "loggedBy" .= idValue _UserId uid]<> logEntryFields nmode ev
. obj$ [ "eventId" .= idValue _EventId eid,"projectId" .= idValue _ProjectId pid,"loggedBy" .= idValue _UserId uid]<> logEntryFields nmode ev
import Control.Lensimport qualified Data.Aeson as Aimport Data.Attoparsec.ByteString ( Parser, parseOnly, takeByteString)import Data.UUID ( UUID, fromASCIIBytes )import Aftok.Auction ( AuctionId(..) )import Aftok.Currency.Bitcoin ( NetworkMode(..) )import Aftok.Database ( DBError(..), DBOp, liftdb)import Aftok.Database.PostgreSQL ( runQDBM )import Aftok.Types ( UserId(..), ProjectId(..))import Aftok.Utilimport Snap.Core ( MonadSnap, getParam, readRequestBody, setResponseCode, modifyResponse, finishWith, getResponse, writeText, writeLBS, setResponseStatus, logError)import Snap.Snaplet as Simport qualified Snap.Snaplet.Auth as AUimport Snap.Snaplet.PostgresqlSimple ( Postgres, HasPostgres(..), setLocalPostgresState, liftPG)import Snap.Snaplet.Session ( SessionManager )
import Aftok.Auction (AuctionId (..))import Aftok.Currency.Bitcoin (NetworkMode (..))import Aftok.Database( DBError (..),DBOp,liftdb,)import Aftok.Database.PostgreSQL (runQDBM)import Aftok.Types( ProjectId (..),UserId (..),)import Aftok.Utilimport Control.Lensimport qualified Data.Aeson as Aimport Data.Attoparsec.ByteString( Parser,parseOnly,takeByteString,)import Data.UUID (UUID, fromASCIIBytes)import Snap.Core( MonadSnap,finishWith,getParam,getResponse,logError,modifyResponse,readRequestBody,setResponseCode,setResponseStatus,writeLBS,writeText,)import Snap.Snaplet as Simport qualified Snap.Snaplet.Auth as AUimport Snap.Snaplet.PostgresqlSimple( HasPostgres (..),Postgres,liftPG,setLocalPostgresState,)import Snap.Snaplet.Session (SessionManager)
data App = App{ _networkMode :: NetworkMode, _sess :: Snaplet SessionManager, _db :: Snaplet Postgres, _auth :: Snaplet (AU.AuthManager App)}
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 =>-- | the name of the parameter to be parsedText ->-- | parser for the value of the parameterParser a ->-- | the parsed valuem a
import Control.Lens ( (^.), to)import Control.Exception ( try )import qualified Data.Aeson as Aimport Data.ProtocolBuffers ( encodeMessage )import Data.Serialize.Put ( runPutLazy )import Filesystem.Path.CurrentOS ( decodeString, encodeString)import Network.HTTP.Client ( Manager, newManager, defaultManagerSettings )import System.Environmentimport System.IO.Error ( IOError )import Aftok.Currency.Zcash ( rpcValidateZAddr )import Aftok.Jsonimport Aftok.TimeLogimport qualified Aftok.Config as Cimport Aftok.QConfig as Qimport Aftok.Snapletimport Aftok.Snaplet.Auctionsimport Aftok.Snaplet.Billingimport Aftok.Snaplet.Authimport Aftok.Snaplet.Paymentsimport Aftok.Snaplet.Projectsimport Aftok.Snaplet.Usersimport Aftok.Snaplet.WorkLog
import qualified Aftok.Config as Cimport Aftok.Currency.Zcash (rpcValidateZAddr)import Aftok.Jsonimport Aftok.QConfig as Qimport Aftok.Snapletimport Aftok.Snaplet.Auctionsimport Aftok.Snaplet.Authimport Aftok.Snaplet.Billingimport Aftok.Snaplet.Paymentsimport Aftok.Snaplet.Projectsimport Aftok.Snaplet.Usersimport Aftok.Snaplet.WorkLogimport Aftok.TimeLogimport Control.Exception (try)import Control.Lens( (^.),to,)import qualified Data.Aeson as Aimport Data.ProtocolBuffers (encodeMessage)import Data.Serialize.Put (runPutLazy)import Filesystem.Path.CurrentOS( decodeString,encodeString,)import Network.HTTP.Client (Manager, defaultManagerSettings, newManager)import Snap.Coreimport Snap.Snapletimport qualified Snap.Snaplet.Auth as AUimport Snap.Snaplet.Auth.Backends.PostgresqlSimpleimport Snap.Snaplet.PostgresqlSimpleimport Snap.Snaplet.Session.Backends.CookieSessionimport Snap.Util.FileServe (serveDirectory)import System.Environmentimport System.IO.Error (IOError)
import Snap.Coreimport Snap.Snapletimport qualified Snap.Snaplet.Auth as AUimport Snap.Snaplet.PostgresqlSimpleimport Snap.Snaplet.Auth.Backends.PostgresqlSimpleimport Snap.Snaplet.Session.Backends.CookieSessionimport Snap.Util.FileServe ( serveDirectory )
registerOps mgr cfg = RegisterOps{ validateZAddr = rpcValidateZAddr mgr (_zcashdConfig cfg), sendConfirmationEmail = const $ pure ()}
registerOps mgr cfg =RegisterOps{ validateZAddr = rpcValidateZAddr mgr (_zcashdConfig cfg),sendConfirmationEmail = const $ pure ()}
letnmode = cfg ^. billingConfig . C.networkModeloginRoute = method GET requireLogin >> redirect "/app"xhrLoginRoute = void $ method POST requireLoginXHRcheckLoginRoute = void $ method GET requireUserlogoutRoute = method GET (with auth AU.logout)checkZAddrRoute = void $ method GET (checkZAddrHandler rops)registerRoute = void $ method POST (registerHandler rops (cfg ^. recaptchaSecret))inviteRoute = void $ method POST (projectInviteHandler cfg)acceptInviteRoute = void $ method POST acceptInvitationHandlerprojectCreateRoute =serveJSON projectIdJSON $ method POST projectCreateHandlerprojectListRoute =serveJSON (fmap qdbProjectJSON) $ method GET projectListHandlerprojectRoute = serveJSON projectJSON $ method GET projectGetHandlerprojectWorkIndexRoute =serveJSON (workIndexJSON nmode) (method GET projectWorkIndex)projectPayoutsRoute =serveJSON (payoutsJSON nmode) $ method GET payoutsHandlerlogWorkRoute f =serveJSON (keyedLogEntryJSON nmode) $ method POST (logWorkHandler f)-- logWorkBTCRoute f =-- serveJSON eventIdJSON $ method POST (logWorkBTCHandler f)amendEventRoute = serveJSON amendmentIdJSON $ method PUT amendEventHandleruserEventsRoute =serveJSON (fmap $ logEntryJSON nmode) $ method GET userEventsuserWorkIndexRoute =serveJSON (workIndexJSON nmode) $ method GET userWorkIndexauctionCreateRoute =serveJSON auctionIdJSON $ method POST auctionCreateHandlerauctionRoute = serveJSON auctionJSON $ method GET auctionGetHandlerauctionBidRoute = serveJSON bidIdJSON $ method POST auctionBidHandlerbillableCreateRoute =serveJSON billableIdJSON $ method POST billableCreateHandlerbillableListRoute =serveJSON (fmap qdbBillableJSON) $ method GET billableListHandlersubscribeRoute =serveJSON subscriptionIdJSON $ method POST subscribeHandlerpayableRequestsRoute =serveJSON billDetailsJSON $ method GET listPayableRequestsHandlergetPaymentRequestRoute =writeLBS. runPutLazy. encodeMessage=<< method GET getPaymentRequestHandlersubmitPaymentRoute = serveJSON paymentIdJSON$ method POST (paymentResponseHandler $ cfg ^. billingConfig)
let nmode = cfg ^. billingConfig . C.networkModeloginRoute = method GET requireLogin >> redirect "/app"xhrLoginRoute = void $ method POST requireLoginXHRcheckLoginRoute = void $ method GET requireUserlogoutRoute = method GET (with auth AU.logout)checkZAddrRoute = void $ method GET (checkZAddrHandler rops)registerRoute = void $ method POST (registerHandler rops (cfg ^. recaptchaSecret))inviteRoute = void $ method POST (projectInviteHandler cfg)acceptInviteRoute = void $ method POST acceptInvitationHandlerprojectCreateRoute =serveJSON projectIdJSON $ method POST projectCreateHandlerprojectListRoute =serveJSON (fmap qdbProjectJSON) $ method GET projectListHandlerprojectRoute = serveJSON projectJSON $ method GET projectGetHandlerprojectWorkIndexRoute =serveJSON (workIndexJSON nmode) (method GET projectWorkIndex)projectPayoutsRoute =serveJSON (payoutsJSON nmode) $ method GET payoutsHandlerlogWorkRoute f =serveJSON (keyedLogEntryJSON nmode) $ method POST (logWorkHandler f)-- logWorkBTCRoute f =-- serveJSON eventIdJSON $ method POST (logWorkBTCHandler f)amendEventRoute = serveJSON amendmentIdJSON $ method PUT amendEventHandleruserEventsRoute =serveJSON (fmap $ logEntryJSON nmode) $ method GET userEventsuserWorkIndexRoute =serveJSON (workIndexJSON nmode) $ method GET userWorkIndexauctionCreateRoute =serveJSON auctionIdJSON $ method POST auctionCreateHandlerauctionRoute = serveJSON auctionJSON $ method GET auctionGetHandlerauctionBidRoute = serveJSON bidIdJSON $ method POST auctionBidHandlerbillableCreateRoute =serveJSON billableIdJSON $ method POST billableCreateHandlerbillableListRoute =serveJSON (fmap qdbBillableJSON) $ method GET billableListHandlersubscribeRoute =serveJSON subscriptionIdJSON $ method POST subscribeHandlerpayableRequestsRoute =serveJSON billDetailsJSON $ method GET listPayableRequestsHandlergetPaymentRequestRoute =writeLBS. runPutLazy. encodeMessage=<< method GET getPaymentRequestHandlersubmitPaymentRoute =serveJSON paymentIdJSON $method POST (paymentResponseHandler $ cfg ^. billingConfig)
[ ("static", serveDirectory . encodeString $ cfg ^. staticAssetPath), ("login" , loginRoute), ("login" , xhrLoginRoute), ("logout" , logoutRoute), ("login/check", checkLoginRoute), ("register" , registerRoute), ("validate_zaddr", checkZAddrRoute), ( "accept_invitation", acceptInviteRoute)-- , ("projects/:projectId/logStart/:btcAddr" , logWorkBTCRoute StartWork)-- , ("projects/:projectId/logEnd/:btcAddr" , logWorkBTCRoute StopWork), ("user/projects/:projectId/logStart" , logWorkRoute StartWork), ("user/projects/:projectId/logEnd" , logWorkRoute StopWork), ("user/projects/:projectId/events" , userEventsRoute), ("user/projects/:projectId/workIndex", userWorkIndexRoute), ("projects/:projectId/workIndex" , projectWorkIndexRoute), ( "projects/:projectId/auctions", auctionCreateRoute) -- <|> auctionListRoute), ( "projects/:projectId/billables", billableCreateRoute <|> billableListRoute), ("projects/:projectId/payouts", projectPayoutsRoute), ("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)
[ ("static", serveDirectory . encodeString $ cfg ^. staticAssetPath),("login", loginRoute),("login", xhrLoginRoute),("logout", logoutRoute),("login/check", checkLoginRoute),("register", registerRoute),("validate_zaddr", checkZAddrRoute),( "accept_invitation",acceptInviteRoute),-- , ("projects/:projectId/logStart/:btcAddr" , logWorkBTCRoute StartWork)-- , ("projects/:projectId/logEnd/:btcAddr" , logWorkBTCRoute StopWork)("user/projects/:projectId/logStart", logWorkRoute StartWork),("user/projects/:projectId/logEnd", logWorkRoute StopWork),("user/projects/:projectId/events", userEventsRoute),("user/projects/:projectId/workIndex", userWorkIndexRoute),("projects/:projectId/workIndex", projectWorkIndexRoute),( "projects/:projectId/auctions",auctionCreateRoute), -- <|> auctionListRoute)( "projects/:projectId/billables",billableCreateRoute <|> billableListRoute),("projects/:projectId/payouts", projectPayoutsRoute),("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)
import Control.Lensimport Data.Hourglassimport Data.List ( (!!) )import Data.Thyme.Clock ( )import qualified Data.UUID.V4 as Uimport Text.Read ( read )
import Aftok.Auctionimport Aftok.Generatorsimport Aftok.Typesimport Bippy.Test.Types (arbitrarySatoshi)import Bippy.Types (Satoshi (..))import Control.Lensimport Data.Hourglassimport Data.List ((!!))import Data.Thyme.Clock ()import qualified Data.UUID.V4 as Uimport Haskoin.Constants (btc)import Test.HUnit.Base (assertFailure)import Test.Hspecimport Test.QuickCheckimport Text.Read (read)
import Haskoin.Constants ( btc )import Bippy.Types ( Satoshi(..) )import Bippy.Test.Types ( arbitrarySatoshi )import Aftok.Auctionimport Aftok.Generatorsimport Aftok.Typesimport Test.Hspecimport Test.HUnit.Base ( assertFailure )import Test.QuickCheck
let testB0 = Bid (users !! 0)(Seconds 3)(Satoshi 100)(read "2016-03-05 15:59:20.000000 UTC")testB1 = Bid (users !! 1)(Seconds 60)(Satoshi 1000)(read "2016-03-05 15:59:21.000000 UTC")testB2 = Bid (users !! 2)(Seconds 60)(Satoshi 100)(read "2016-03-05 15:59:22.000000 UTC")testB3 = Bid (users !! 3)(Seconds 90)(Satoshi 100)(read "2016-03-05 15:59:23.000000 UTC")testB4 = Bid (users !! 4)(Seconds 60)(Satoshi 100)(read "2016-03-05 15:59:24.000000 UTC")
let testB0 =Bid(users !! 0)(Seconds 3)(Satoshi 100)(read "2016-03-05 15:59:20.000000 UTC")testB1 =Bid(users !! 1)(Seconds 60)(Satoshi 1000)(read "2016-03-05 15:59:21.000000 UTC")testB2 =Bid(users !! 2)(Seconds 60)(Satoshi 100)(read "2016-03-05 15:59:22.000000 UTC")testB3 =Bid(users !! 3)(Seconds 90)(Satoshi 100)(read "2016-03-05 15:59:23.000000 UTC")testB4 =Bid(users !! 4)(Seconds 60)(Satoshi 100)(read "2016-03-05 15:59:24.000000 UTC")
it "determines a sufficient number of winners to fulfill the raise amount"$ letresult =
it "determines a sufficient number of winners to fulfill the raise amount" $let result =
--it "returns the billing date in the presence of an expired payment request" $-- forAll ((,) <$> genSatoshi <*> listOf genBid) $-- \(raiseAmount', bids) ->-- case runAuction' raiseAmount' bids of-- WinningBids xs -> bidsTotal xs == raiseAmount'-- InsufficientBids t -> t == (raiseAmount' - bidsTotal bids)
--it "returns the billing date in the presence of an expired payment request" $-- forAll ((,) <$> genSatoshi <*> listOf genBid) $-- \(raiseAmount', bids) ->-- case runAuction' raiseAmount' bids of-- WinningBids xs -> bidsTotal xs == raiseAmount'-- InsufficientBids t -> t == (raiseAmount' - bidsTotal bids)
import qualified Aftok.Interval as Iimport Aftok.TimeLogimport Control.Lens ((^.))import Data.AffineSpaceimport qualified Data.List.NonEmpty as Limport qualified Data.Map.Strict as Mimport Data.Thyme.Time as Timport Data.Time.ISO8601import Haskoin.Address (Address)import Haskoin.Util.Arbitrary.Address (arbitraryAddress)import Test.Hspecimport Test.QuickCheck
import Control.Lens ( (^.) )import Data.AffineSpaceimport qualified Data.List.NonEmpty as Limport qualified Data.Map.Strict as Mimport Data.Thyme.Time as Timport Data.Time.ISO8601import Haskoin.Address ( Address )import qualified Aftok.Interval as Iimport Aftok.TimeLogimport Test.Hspecimport Test.QuickCheckimport Haskoin.Util.Arbitrary.Address ( arbitraryAddress )
let starts = toThyme <$> catMaybes[ parseISO8601 "2014-01-01T00:08:00Z", parseISO8601 "2014-01-01T00:12:00Z"]ends = toThyme <$> catMaybes[ parseISO8601 "2014-01-01T00:11:59Z", parseISO8601 "2014-01-01T00:18:00Z"]
let starts =toThyme<$> catMaybes[ parseISO8601 "2014-01-01T00:08:00Z",parseISO8601 "2014-01-01T00:12:00Z"]ends =toThyme<$> catMaybes[ parseISO8601 "2014-01-01T00:11:59Z",parseISO8601 "2014-01-01T00:18:00Z"]