B6HWAPDPXIWH7CHK5VLMWLL6EQN6NOFZEFYO47BPUY2ZO4SL7VDAC UOG5H2TW5R3FSHQPJCEMNFDQZS5APZUP7OM54FIBQG7ZP4HASQ7QC IPG33FAWXGEQ2PO6OXRT2PWWXHRNMPVUKKADL6UKKN5GD2CNZ25AC 2MNO5FUYXF6GHHWTIDLW2JGMFC3UY54BHJKUYVF7SZCUJQWKZ4DQC SOIAMXLWIB5RIEMKXUFMBSE2SKQQTMHYSW3DKUX6GEV4VNOQVHAQC LEINLS3X55PB6TSCNC5RVMDMV56XHTV4MNDUC42H7DDFMPDYUNTAC 64C6AWH66FDKU6UE6Z6JPX2J2GBM2JOPTH2GL6LHKAIUBGNGDZ5AC 2XQD6KKKD6QVHFHAEMVE3XXY7X2T7BLTLL7XIILZAXNJJH2YONUQC DFOBMSAODB3NKW37B272ZXA2ML5HMIH3N3C4GT2DPEQS7ZFK4SNAC 4U7F3CPIDTK6JSEDMNMHVKSR7HOQDLZQD2PPVMDLHO5SFSIMUXZAC BROSTG5KP3NUNLSYPVQID254TE47E5RKQAKLPIY7PGWETE6JNMTAC UUR6SMCAJMA7O3ZFUCQMPZFDDIPUVQ5IHUAC5F252YVD6H3JIKPQC QMRKFEPGFBCEWAIXPEIG5ILKAJ2JH5L3TOITHR4HNJXK5FN3KXBQC HMDM3B557TO5RYP2IGFFC2C2VN6HYZTDQ47CJY2O37BW55DSMFZAC RPAJLHMTUJU4AYNBOHVGHGGB4NY2NLY3BVPYN5FMWB3ZIMAUQHCQC WZFQDWW4XK6M4A4PQ7WQJUTZUPRGQR7V7ZVZY5ZTL5AMGIFMHB2QC 5W5M56VJFJEBXMGBVKGCKPHOEMVTKUOQMLPJP7VNDQLTYNJXXLHQC NVOCQVASZWTKQJG7GPH7KHKZZR7NUG4WLV5YY4KAIRPCJRWCZPIAC ADMKQQGCGVSHHIMVQ4XFRDCG544SBJCYALSKZV45CQQBZ4ACUH2AC NEDDHXUK3GNFMOFO3KLU7NRIHCTYNWBT3D6HTKZAOXFDE6HMDZ6AC 75N3UJ4JK56KXF56GASGPAWLFYGJDETVJNYTF4KXFCQM767JUU5AC EMVTF2IWNQGRL44FC4JNG5FYYQTZSFPNM6SOM7IAEH6T7PPK2NVAC Z7KS5XHHC6PAMTVBHXY7KUSS3BWAOU6FSYIITUCFOOJZU4OUJHBAC Q5X5RYQLP5K7REYD6VLHOKC4W36ZELJYA45V6YFKTD5S6MPN3NDQC NAS4BFL43SIUAAC663R6VFBHQ2DKI45K6Y6ZKVQI7S5547HBAN7QC WO2MINIF4TXOHWSE7JWXRZYN64XRVLYIRFMF4SMPSOXKA2V77KMQC Y35QCWYW2OTZ27ZVTH2BA3XJTUCJ2WMLKU32ZCOCDY3AW7TIZXRAC EZQG2APB36DDMIAYDPPDGOIXOD7K2RZZSGC2NKGZIHB2HZBTW7EQC 7DBNV3GV773FH5ZLQWFX4RBOS4Q3CIK2RYZNNABY3ZOETYZCXRNQC POX3UAMTGCNS3SU5I6IKDNCCSHEAUSFBZ3WCMQ3EXKPNXETOI6BAC 7HPY3QPFPN35PSPUBVNW2GTFB3CBQZBST4J2BAVJ7QMXLIUN52JAC ZITLSTYXUOESFELOW3DLBKWKMSS5ZJYCTKMK4Z44WGIYAKYSMMVAC LAROLAYUGJ4Q5AEFV5EJMIA2ZKBNCBWHHHPCJ3CKCNIUIYUKRFVQC EKI57EJR65DA5FPILAHGHHAIU5ITVGHA6V3775OX7GV5XD67OWRQC IZEVQF627FA7VV25KJAWYWGGC35LZUUBBQRPN5ZAINTQLPEDRTEAC HBULCDN6E75FAPILFVLTQIKABDEWL3HZTBLICLCWOIKDRYM6UIBQC LHJ2HFXVUQ4VG25I7DADWU73G5K5WNZBDQ3SVNKFYLZ5BEYM4XCQC F2XLL7XWGUV4TJD4X2MJADYAQHCSB4HD2TPPEYVHEKHOQIOOFISAC GLFF5ZDKWI7WKPZSAEE3IUM27LL6DFOPIL4VPODXYXV3BCSCJ6GQC TLQ72DSJD7GGPWN6HGBHAVPBRQFKEQ6KSK43U7JWWID4ZWAF47JAC JFOEOFGA4CQR2LW43IVQGDZSPVJAD4KDN2DZMZXGM2QDIUD7AVCAC RN7EI6INGUUHGMNY5RU3NH56WPLRZY5ZMYDNFMNE3TGV4ESFLQIAC BSIUHCGFDFDFGWYMHZB7OVU3Z3IHPEUXRISIOPGZI2RUXZFDS2EQC A6HKMINBNGQLLX4QJMYWKQ4JAEHVJ4HIRVDKPPDI3FJUO2AAB7OQC 4FDQGIXN3Z4J55DILCSI5EOLIIA7R5CADTGFMW5X7N7MH6JIMBWAC 73NDXDEZRMK672GHSTC3CI6YHXFZ2GGJI5IKQGHKFDZKTNSQXLLQC Y3LIJ5USPMYBG7HKCIQBE7MWVGLQJPJSQD3KPZCTKXP22GOB535QC SEWTRB6S5PO5MQBLCPVBD7XT2BDYNZUE2RO6Z2XENZRIOCN6OZJAC GCVQD44VRPQVKPZEPIC4AOIXLJIG2ZMV3QI2Y7KALUT6NVUBSGSAC EKY7U7SKPF45OOUAHJBEQKXSUXWOHFBQFFVJWPBN5ARFJUFM2BPAC TNR3TEHKVADAEZSTOD2XLSUTSW5AWST2YUW4CWK5KE7DSC6XHZNAC O227CEAV7BTKSE3SSC7XHC5IWEBXZL2AOOKJMBMOOFNTLINBLQMAC 3QVT6MA6I2CILQH3LUZABS4YQ7MN6CNRYTDRVS376OOHTPLYTFJAC LD4GLVSF6YTA7OZWIGJ45H6TUXGM4WKUIYXKWQFNUP36WDMYSMXAC O5FVTOM6YFBLEPF3S576K6IMT6ZZ5VQCSB3YVXNS4CKBITKCRZ7AC EW2XN7KUMCAQNVFJJ5YTAVDZCPHNWDOEDMRFBUGLY6IE2HKNNX5AC AL37SVTCKRSG4HG2PCYK5Z7QSIZZH5JHH4Q2VLMXFAXSAQRFFG4QC QADKFHAR3KWQCNYU25Z7PJUGMD5WL26IU3DOAHBTRN2A7NKPUPKAC NLZ3JXLOOIL37O3RRQWXHNPNSNEOOLPD6MCB754BEBECQB3KGR2AC W35DDBFYF6Z4ZPCFEO5RPAONZLYCSNTXUSTS6FIUUVZHCI6Q7GHAC BWN72T44GRRZ6K2OPN56FTLNEB7J7AGC7T2U5HSMLEKUPGJP2NUAC Z3MK2PJ5U222DXRS22WCDHVPZ7HVAR3HOCUNXIGX6VMEPBQDF6PQC 7VGYLTMURLVSVUYFW7TCRZTDQ6RE2EPSPPA43XKHDOBFWYVVSJHQC XTBSG4C7SCZUFOU2BTNFR6B6TCGYI35BWUV4PVTS3N7KNH5VEARQC O722AOKEWXWJPRHGJREU6QPW7HEFPPRETZIAADZ2RMAXHARCNEKAC ASF3UPJLCX7KIUCNJD5KAXSPDXCUEJHLL4HBRTRUBPCW73IXOCWQC HALRDT2F22DAQ54M334YSKD2XJCAXDXEZPDKGMOWKH7XELBAS6MAC FD7SV5I6VCW27HZ3T3K4MMGB2OYGJTPKFFA263TNTAMRJGQJWVNAC KNSI575VAW6HRCZYXOEPQ4DTSML4EORML5MV4DJBRKE7TXCPS4EAC SCXG6TJWYIPRUMT27KGKIIF6FYKTUTY74UNZ2FQTT63XZ6HIF3AAC 5DRIWGLUKMQZU2ZPBXSTLAWJKAMOD5YXAHM5LEDQHDFGYYLHWCDQC 4QX5E5ACVN57KJLCWOM4JEI6JSV4XZNCWVYPOTKSOMUW3SOMCNJAC 7KZP4RHZ3QSYTPPQ257A65Z5UPX44TF2LAI2U5EMULQCLDCEUK2AC JUUMYIQEXSYRMPCQSHIRIG6TBHAR5LU46FE5WI3UHYX6KA4ESH7AC KEP5WUFJXTMKRRNZLYTGYYWA4VLFCMHTKTJYF5EA5IWBYFMU6WYQC MJ6R42RCK2ASXAJ6QXDPMAW56RBOJ4F4HI2LFIV3KXFIKWYMQK3QC V2VDN77HCSRYYWXDJJ2XOVHV4P6PVWNJZLXZ7JUYPQEZQIH5BZ3QC 6L5BK5EHPAOQX3JCKUJ273UDNAC23LPQL4HIJGM4AV3P3QK5OKIQC I2KHGVD44KT4MQJXGCTVSQKMBO6TVCY72F26TLXGWRL6PHGF6RNQC PBD7LZYQHXAA3KLH2ZUX5GW4UFML6BQ32KXZF4KZ6OYFASUYFJ5QC MB5SHULBN3WP7TGUWZDP6BRGP423FTYKF67T5IF5YHHLNXKQ5REAC UILI6PILCRDPZ3XYA54LGIGPSU7ERWNHCE7R3CE64ZEC7ONOEMOQC HO2PFRABW6BBTE4MUKUTEGXCMJS46WGVBCNWOHO4OL52DVAB4YDAC MGOF7IUFGXYQKZOKMM2GGULFFVAULEHLZDSHMUW6B5DBKVXXR74AC 2Y2QZFVFSKXEFEGYJB5A7GI735ONWPCF7DVTIY5T73AUEVTZTBBQC KQQAITFHRJFB274XKMKJ2HNGJVLHX7J4EXXC6GGNXMACAQIRGX6QC AXKKXBWN4EMUOLV43WN52JSKJPBV7TLSGLNJW5EZXHSJNKCYUWOQC {-# LANGUAGE DeriveDataTypeable #-}{-# LANGUAGE NoImplicitPrelude #-}{-# LANGUAGE TemplateHaskell #-}module Aftok whereimport ClassyPreludeimport Control.Lens (makeLenses, makePrisms)import Data.Aesonimport Data.Aeson.Typesimport Data.Dataimport Data.UUIDimport Network.Haskoin.Crypto (Address (..), base58ToAddr)newtype BtcAddr = BtcAddr Address deriving (Show, Eq, Ord)makePrisms ''BtcAddrparseBtcAddr :: Text -> Maybe BtcAddrparseBtcAddr addr = BtcAddr <$> (base58ToAddr . encodeUtf8) addrinstance FromJSON BtcAddr whereparseJSON v = dot <- parseJSON vmaybe (fail $ show t <> " is not a valid BTC address") pure $ parseBtcAddr tnewtype Months = Months Integerderiving (Eq, Show, Data, Typeable)data DepreciationFunction = LinearDepreciation Months Monthsderiving (Eq, Show, Data, Typeable)newtype UserId = UserId UUID deriving (Show, Eq, Ord)makePrisms ''UserIdnewtype UserName = UserName Text deriving (Show, Eq)makePrisms ''UserNamenewtype Email = Email Text deriving (Show, Eq)makePrisms ''Emaildata User = User{ _username :: !UserName, _userAddress :: !(Maybe BtcAddr), _userEmail :: !Email}makeLenses ''User-- | others tbdinstance ToJSON DepreciationFunction wheretoJSON (LinearDepreciation (Months up) (Months dp)) =object [ "type" .= ("LinearDepreciation" :: Text), "arguments" .= object [ "undep" .= up, "dep" .= dp]]instance FromJSON DepreciationFunction whereparseJSON (Object v) = dot <- v .: "type" :: Parser Textargs <- v .: "arguments"case unpack t of"LinearDepreciation" ->let undep = Months <$> (args .: "undep")dep = Months <$> (args .: "dep")in LinearDepreciation <$> undep <*> depx -> fail $ "No depreciation function recognized for type " <> xparseJSON _ = mzero
let remainder = raiseAmount' - totalwinFraction = toRational remainder / toRational (bid ^. bidAmount)remainderSeconds = Seconds . round $ winFraction * toRational (bid ^. bidSeconds)in [bid & bidSeconds .~ remainderSeconds & bidAmount .~ remainder]
let winFraction rem = rem % (bid ^. bidAmount . satoshi)remainderSeconds (Satoshi rem) = Seconds . round $ winFraction rem * fromIntegral (bid ^. bidSeconds)adjustBid rem = bid & bidSeconds .~ remainderSeconds rem & bidAmount .~ remin toList $ adjustBid <$> raiseAmount' `ssub` total
in if submittedTotal >= raiseAmount'then WinningBids $ takeWinningBids 0 $ sortBy bidOrder bidselse InsufficientBids (raiseAmount' - submittedTotal)
in maybe(WinningBids $ takeWinningBids (Satoshi 0) $ sortBy bidOrder bids)InsufficientBids(raiseAmount' `ssub` submittedTotal)
let remainder = raiseAmount' - xwinFraction = toRational remainder / toRational (bid ^. bidAmount)remainderSeconds = Seconds . round $ winFraction * toRational (bid ^. bidSeconds)in put (x + remainder) >>(pure . Just $ Commitment bid (remainderSeconds) remainder)
let winFraction rem = rem % (bid ^. bidAmount . satoshi)remainderSeconds (Satoshi rem) = Seconds . round $ winFraction rem * fromIntegral (bid ^. bidSeconds)in for (raiseAmount' `ssub` x) $ \remainder ->put (x <> remainder) *>(pure $ Commitment bid (remainderSeconds remainder) remainder)
Just (PrivKeyDSA _) -> fail "DSA keys not supported for payment request signing."Nothing -> fail $ "No keys found in private key file " <> encodeString (c ^. signingKeyFile)
Just _ -> fail $ "Only RSA keys are currently supported for payment request signing."Nothing -> fail $ "No keys found in private key file " <> encodeString (c ^. signingKeyFile)
{-# LANGUAGE GeneralizedNewtypeDeriving #-}{-# LANGUAGE LambdaCase #-}{-# LANGUAGE TemplateHaskell #-}module Aftok.Currency.Bitcoin whereimport ClassyPreludeimport qualified Data.Configurator.Types as Cimport Control.Lensimport Network.Bippy.Types (Satoshi (..))import Network.Haskoin.Constantssatoshi :: Lens' Satoshi Word64satoshi inj (Satoshi value) = Satoshi <$> inj valuessub :: Satoshi -> Satoshi -> Maybe Satoshissub (Satoshi a) (Satoshi b) | a > b = Just . Satoshi $ (a - b)ssub _ _ = Nothingdata NetworkId= BTC| BCHderiving (Eq, Show, Ord)renderNetworkId :: NetworkId -> TextrenderNetworkId = \caseBTC -> "btc"BCH -> "bch"parseNetworkId :: Text -> Maybe NetworkIdparseNetworkId = \case"btc" -> Just BTC"bch" -> Just BCH_ -> Nothingdata NetworkMode= LiveMode| TestModeparseNetworkMode :: Text -> Maybe NetworkModeparseNetworkMode = \case"test" -> Just TestMode"live" -> Just LiveMode_ -> Nothinginstance C.Configured NetworkMode whereconvert (C.String t) = parseNetworkMode tconvert _ = NothingtoNetwork :: NetworkMode -> NetworkId -> NetworktoNetwork LiveMode = \caseBTC -> btcBCH -> bchtoNetwork TestMode = \caseBTC -> btcTestBCH -> bchTesttoNetworkId :: Network -> Maybe NetworkIdtoNetworkId n = case getNetworkName n of"btc" -> Just BTC"btcTest" -> Just BTC"bch" -> Just BCH"bchTest" -> Just BCH_ -> Nothing
module Aftok.Currency whereimport Data.Aeson (Value)import Data.Aeson.Types (Parser)data Network a = Network{ addressFromJSON :: Parser a, addressToJSON :: a -> Value}
module Aftok.Database.PostgreSQL.Types whereimport ClassyPrelude hiding (null)import Data.Aeson (FromJSON(..), ToJSON(..))import Aftok.TimeLog.Serialization (depfFromJSON, depfToJSON)import Aftok.Types (DepreciationFunction)newtype SerDepFunction = SerDepFunction { unSerDepFunction :: DepreciationFunction }instance FromJSON SerDepFunction whereparseJSON v = SerDepFunction <$> depfFromJSON vinstance ToJSON SerDepFunction wheretoJSON (SerDepFunction depf) = depfToJSON depf
runQDBM :: Connection -> QDBM a -> ExceptT DBError IO arunQDBM conn (QDBM r) = runReaderT r conn
runQDBM :: NetworkMode -> Connection -> QDBM a -> ExceptT DBError IO arunQDBM mode conn (QDBM r) = runReaderT r (mode, conn)null :: RowParser Nullnull = field
btcAddrParser :: FieldParser BtcAddrbtcAddrParser f v = doaddrMay <- parseBtcAddr <$> fromField f v
networkIdParser :: FieldParser NetworkIdnetworkIdParser f b = donetworkName <- fromField f bcase networkName ofJust "btc" -> pure BTCJust "bch" -> pure BCHJust other -> returnError ConversionFailed f ("Network identifier " <> other <> " is not supported.")Nothing -> pure BTCaddressParser :: NetworkMode -> RowParser (NetworkId, Address)addressParser mode = donetworkId <- fieldWith (networkIdParser)address <- fieldWith $ addrFieldParser (toNetwork mode networkId)pure (networkId, address)addrFieldParser :: Network -> FieldParser AddressaddrFieldParser n f v = doaddrMay <- stringToAddr n <$> fromField f v
else maybe (returnError UnexpectedNull f "event type may not be null") (nameEvent . decodeUtf8) v
else maybe (returnError UnexpectedNull f "event type may not be null")(maybe (returnError Incompatible f "unrecognized event type value") pure . nameEvent . decodeUtf8)v
creditToParser' :: FieldParser (RowParser CreditTo)creditToParser' f v =let parser :: Text -> RowParser CreditToparser "credit_to_address" = CreditToAddress <$> (fieldWith btcAddrParser <* nullField <* nullField)parser "credit_to_user" = CreditToUser <$> (nullField *> idParser UserId <* nullField)parser "credit_to_project" = CreditToProject <$> (nullField *> nullField *> idParser P.ProjectId)
creditToParser' :: NetworkMode -> FieldParser (RowParser (CreditTo (NetworkId, Address)))creditToParser' mode f v =let parser :: Text -> RowParser (CreditTo (NetworkId, Address))parser "credit_to_address" =CreditToCurrency <$> (addressParser mode <* nullField <* nullField)parser "credit_to_user" =CreditToUser <$> (nullField *> nullField *> idParser UserId <* nullField)parser "credit_to_project" =CreditToProject <$> (nullField *> nullField *> nullField *> idParser ProjectId)
logEntryParser :: RowParser LogEntrylogEntryParser =LogEntry <$> creditToParser
logEntryParser :: NetworkMode -> RowParser (LogEntry (NetworkId, Address))logEntryParser mode =LogEntry <$> creditToParser mode
qdbLogEntryParser :: RowParser KeyedLogEntryqdbLogEntryParser =(,,) <$> idParser P.ProjectId
qdbLogEntryParser :: NetworkMode -> RowParser (KeyedLogEntry (NetworkId, Address))qdbLogEntryParser mode =(,,) <$> idParser ProjectId
conn <- asklift . ExceptT $ withTransaction conn (runExceptT $ runReaderT rt conn)
env <- asklift . ExceptT $ withTransaction (snd env) (runExceptT $ runReaderT rt env)
pinsert EventId[sql| INSERT INTO work_events(project_id, user_id, credit_to_type, credit_to_user_id, event_type, event_time, event_metadata)VALUES (?, ?, ?, ?, ?, ?, ?)
pinsert EventId[sql| INSERT INTO work_events(project_id, user_id, credit_to_type, credit_to_user_id, event_type, event_time, event_metadata)VALUES (?, ?, ?, ?, ?, ?, ?)
pgEval (FindEvent (EventId eid)) =headMay <$> pquery qdbLogEntryParser[sql| SELECT project_id, user_id,credit_to_type, credit_to_address, credit_to_user_id, credit_to_project_id,event_type, event_time, event_metadata FROM work_events
pgEval (FindEvent (EventId eid)) = domode <- askNetworkModeheadMay <$> pquery (qdbLogEntryParser mode)[sql| SELECT project_id, user_id,credit_to_type, credit_to_address, credit_to_user_id, credit_to_project_id,event_type, event_time, event_metadata FROM work_events
pgEval (FindEvents (P.ProjectId pid) (UserId uid) ival) =let q (Before e) = pquery logEntryParser[sql| SELECT credit_to_type, credit_to_address, credit_to_user_id, credit_to_project_id,event_type, event_time,event_metadataFROM work_events
pgEval (FindEvents (ProjectId pid) (UserId uid) ival) = domode <- askNetworkModelet q (Before e) = pquery (logEntryParser mode)[sql| SELECT credit_to_type, credit_to_address, credit_to_user_id, credit_to_project_id,event_type, event_time,event_metadataFROM work_events
q (During s e) = pquery logEntryParser[sql| SELECT credit_to_type, credit_to_address, credit_to_user_id, credit_to_project_id,event_type, event_time, event_metadataFROM work_eventsWHERE project_id = ? AND user_id = ?
q (During s e) = pquery (logEntryParser mode)[sql| SELECT credit_to_type, credit_to_address, credit_to_user_id, credit_to_project_id,event_type, event_time, event_metadataFROM work_eventsWHERE project_id = ? AND user_id = ?
q (After s) = pquery logEntryParser[sql| SELECT credit_to_type, credit_to_address, credit_to_user_id, credit_to_project_id,event_type, event_time, event_metadataFROM work_events
q (After s) = pquery (logEntryParser mode)[sql| SELECT credit_to_type, credit_to_address, credit_to_user_id, credit_to_project_id,event_type, event_time, event_metadataFROM work_events
CreditToAddress addr ->pinsert AmendmentId[sql| INSERT INTO event_credit_to_amendments(event_id, amended_at, credit_to_type, credit_to_btc_addr)
CreditToCurrency (nid, addr) -> dolet network = toNetwork mode nidpinsert AmendmentId[sql| INSERT INTO event_credit_to_amendments(event_id, amended_at, credit_to_type, credit_to_btc_addr)
pinsert AmendmentId[sql| INSERT INTO event_credit_to_amendments(event_id, amended_at, credit_to_type, credit_to_project_id)
pinsert AmendmentId[sql| INSERT INTO event_credit_to_amendments(event_id, amended_at, credit_to_type, credit_to_project_id)
pgEval (ReadWorkIndex (P.ProjectId pid)) = dologEntries <- pquery logEntryParser[sql| SELECT credit_to_type, credit_to_address, credit_to_user_id, credit_to_project_id,event_type, event_time, event_metadataFROM work_events
pgEval (ReadWorkIndex (ProjectId pid)) = domode <- askNetworkModelogEntries <- pquery (logEntryParser mode)[sql| SELECT credit_to_type, credit_to_address, credit_to_user_id, credit_to_project_id,event_type, event_time, event_metadataFROM work_events
headMay <$> pquery auctionParser[sql| SELECT project_id, initiator_id, created_at, raise_amount, start_time, end_timeFROM auctions
headMay <$> pquery auctionParser[sql| SELECT project_id, initiator_id, created_at, raise_amount, start_time, end_timeFROM auctions
pgEval (CreateUser user') =let addrMay :: Maybe ByteStringaddrMay = user' ^? (userAddress . traverse . _BtcAddr . to addrToBase58)in pinsert UserId[sql| INSERT INTO users (handle, btc_addr, email) VALUES (?, ?, ?) RETURNING id |]
pgEval (CreateUser user') = domode <- askNetworkModelet nidMay = fst <$> _userAddress user'addrMay :: Maybe TextaddrMay = donetwork <- toNetwork mode <$> nidMayaddress <- snd <$> _userAddress user'pure $ addrToString network addresspinsert UserId[sql| INSERT INTO users (handle, network, addr, email)VALUES (?, ?, ?, ?) RETURNING id |]
pgEval (FindUserByName (UserName h)) =headMay <$> pquery ((,) <$> idParser UserId <*> userParser)
pgEval (FindUserByName (UserName h)) = domode <- askNetworkModeheadMay <$> pquery ((,) <$> idParser UserId <*> userParser mode)
void $ pexec[sql| INSERT INTO project_companions (project_id, user_id, invited_by, joined_at)SELECT i.project_id, ?, i.invitor_id, ?FROM invitations i
void $ pexec[sql| INSERT INTO project_companions (project_id, user_id, invited_by, joined_at)SELECT i.project_id, ?, i.invitor_id, ?FROM invitations i
(p ^. P.projectName, p ^. (P.inceptionDate . to fromThyme), p ^. (P.initiator . _UserId), toJSON $ p ^. P.depf)
( p ^. P.projectName, p ^. (P.inceptionDate . to fromThyme), p ^. (P.initiator . _UserId), toJSON $ p ^. P.depf . to SerDepFunction)
pquery ((,) <$> idParser P.ProjectId <*> projectParser)[sql| SELECT p.id, p.project_name, p.inception_date, p.initiator_id, p.depreciation_fnFROM projects p LEFT OUTER JOIN project_companions pc ON pc.project_id = p.idWHERE pc.user_id = ?
pquery ((,) <$> idParser ProjectId <*> projectParser)[sql| SELECT p.id, p.project_name, p.inception_date, p.initiator_id, p.depreciation_fnFROM projects p LEFT OUTER JOIN project_companions pc ON pc.project_id = p.idWHERE pc.user_id = ?
pinsert B.BillableId[sql| INSERT INTO billables( project_id, event_id, name, description, recurrence_type, recurrence_count
pinsert B.BillableId[sql| INSERT INTO billables( project_id, event_id, name, description, recurrence_type, recurrence_count
headMay <$> pquery billableParser[sql| SELECT b.project_id, e.created_by, b.name, b.description,b.recurrence_type, b.recurrence_count,
headMay <$> pquery billableParser[sql| SELECT b.project_id, e.created_by, b.name, b.description,b.recurrence_type, b.recurrence_count,
pgEval (FindBillables pid) =pquery ((,) <$> idParser B.BillableId <*> billableParser)[sql| SELECT b.id, b.project_id, e.created_by, b.name, b.description,b.recurrence_type, b.recurrence_count,b.billing_amount, b.grace_period_days
pgEval (FindBillables pid) =pquery ((,) <$> idParser B.BillableId <*> billableParser)[sql| SELECT b.id, b.project_id, e.created_by, b.name, b.description,b.recurrence_type, b.recurrence_count,b.billing_amount, b.grace_period_days
pquery ((,) <$> idParser B.SubscriptionId <*> subscriptionParser)[sql| SELECT s.id, user_id, billable_id, start_date, end_dateFROM subscriptions sJOIN billables b ON b.id = s.billable_idWHERE s.user_id = ?
pquery ((,) <$> idParser B.SubscriptionId <*> subscriptionParser)[sql| SELECT s.id, user_id, billable_id, start_date, end_dateFROM subscriptions sJOIN billables b ON b.id = s.billable_idWHERE s.user_id = ?
pinsert PaymentRequestId[sql| INSERT INTO payment_requests(subscription_id, event_id, request_data, url_key, request_time, billing_date)
pinsert PaymentRequestId[sql| INSERT INTO payment_requests(subscription_id, event_id, request_data, url_key, request_time, billing_date)
headMay <$> pquery ((,) <$> idParser PaymentRequestId <*> paymentRequestParser)[sql| SELECT id, subscription_id, request_data, url_key, request_time, billing_dateFROM payment_requestsWHERE url_key = ?
headMay <$> pquery ((,) <$> idParser PaymentRequestId <*> paymentRequestParser)[sql| SELECT id, subscription_id, request_data, url_key, request_time, billing_dateFROM payment_requestsWHERE url_key = ?
pquery ((,) <$> idParser PaymentRequestId <*> paymentRequestParser)[sql| SELECT id, subscription_id, request_data, url_key, request_time, billing_dateFROM payment_requests
pquery ((,) <$> idParser PaymentRequestId <*> paymentRequestParser)[sql| SELECT id, subscription_id, request_data, url_key, request_time, billing_dateFROM payment_requests
in pquery rowp[sql| SELECT r.url_key,r.subscription_id, r.request_data, r.url_key, r.request_time, r.billing_date,s.user_id, s.billable_id, s.start_date, s.end_date,b.project_id, e.created_by, b.name, b.description, b.recurrence_type,
in pquery rowp[sql| SELECT r.url_key,r.subscription_id, r.request_data, r.url_key, r.request_time, r.billing_date,s.user_id, s.billable_id, s.start_date, s.end_date,b.project_id, e.created_by, b.name, b.description, b.recurrence_type,
pquery ((,) <$> idParser PaymentId <*> paymentParser)[sql| SELECT id, payment_request_id, payment_data, payment_dateFROM payments
pquery ((,) <$> idParser PaymentId <*> paymentParser)[sql| SELECT id, payment_request_id, payment_data, payment_dateFROM payments
type KeyedLogEntry = (ProjectId, UserId, LogEntry)type InvitingUID = UserIdtype InvitedUID = UserId
import Network.Haskoin.Address (Address)type KeyedLogEntry a = (ProjectId, UserId, LogEntry a)type InvitingUID = UserIdtype InvitedUID = UserIdtype BTCNet = (NetworkId, Address)type BTCUser = User BTCNet
CreateUser :: User -> DBOp UserIdFindUser :: UserId -> DBOp (Maybe User)FindUserByName :: UserName -> DBOp (Maybe (UserId, User))
CreateUser :: BTCUser -> DBOp UserIdFindUser :: UserId -> DBOp (Maybe BTCUser)FindUserByName :: UserName -> DBOp (Maybe (UserId, BTCUser))
CreateEvent :: ProjectId -> UserId -> LogEntry -> DBOp EventIdAmendEvent :: EventId -> EventAmendment -> DBOp AmendmentIdFindEvent :: EventId -> DBOp (Maybe KeyedLogEntry)FindEvents :: ProjectId -> UserId -> Interval' -> DBOp [LogEntry]ReadWorkIndex :: ProjectId -> DBOp WorkIndex
CreateEvent :: ProjectId -> UserId -> LogEntry BTCNet -> DBOp EventIdAmendEvent :: EventId -> EventAmendment BTCNet -> DBOp AmendmentIdFindEvent :: EventId -> DBOp (Maybe (KeyedLogEntry BTCNet))FindEvents :: ProjectId -> UserId -> Interval' -> DBOp [LogEntry BTCNet]ReadWorkIndex :: ProjectId -> DBOp (WorkIndex BTCNet)
instance Show Version whereshow Version{..} = intercalate "." $ fmap show [majorVersion, minorVersion]
failT :: Text -> Parser afailT = fail . T.unpackprintVersion :: Version -> TextprintVersion Version{..} = T.intercalate "." $ fmap (pack . show) [majorVersion, minorVersion]
version :: QuasiQuoterversion = 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."}
version :: MonadFail m => ByteString -> m Versionversion = fromEitherM fail . PC.parseOnly versionParserv :: QuasiQuoterv = 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."}
creditToJSON :: CreditTo -> ValuecreditToJSON (CreditToAddress addr) = v2 $ obj [ "creditToAddress" .= (addr ^. _BtcAddr) ]creditToJSON (CreditToUser uid) = v2 $ obj [ "creditToUser" .= idValue _UserId uid ]creditToJSON (CreditToProject pid) = v2 $ obj [ "creditToProject" .= projectIdJSON pid ]
creditToJSON :: NetworkMode -> CreditTo (NetworkId, Address) -> ValuecreditToJSON nmode (CreditToCurrency (netId, addr)) =v2 $ obj [ "creditToAddress" .= addrToJSON (toNetwork nmode netId) addr, "creditToNetwork" .= renderNetworkId netId]creditToJSON _ (CreditToUser uid) =v2 $ obj [ "creditToUser" .= idValue _UserId uid ]creditToJSON _ (CreditToProject pid) =v2 $ obj [ "creditToProject" .= projectIdJSON pid ]parseCreditTo :: NetworkMode -> Value -> Parser (CreditTo (NetworkId, Address))parseCreditTo nmode = unversion "CreditTo" $ \case(Version 1 0) -> parseCreditToV1 nmode(Version 2 0) -> parseCreditToV2 nmodever -> badVersion "EventAmendment" verparseBtcAddr:: NetworkMode-> NetworkId-> Text-> Parser (CreditTo (NetworkId, Address))parseBtcAddr nmode net addrText =maybe(fail . unpack $ "Address " <> addrText <> " cannot be parsed as a BTC network address.")(pure . CreditToCurrency . (net,))(stringToAddr (toNetwork nmode net) addrText)parseCreditToV1:: NetworkMode-> Object-> Parser (CreditTo (NetworkId, Address))parseCreditToV1 nmode x = doparseBtcAddr nmode BTC =<< x .: "btcAddr"parseCreditToV2 :: NetworkMode -> Object -> Parser (CreditTo (NetworkId, Address))parseCreditToV2 nmode o =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---- Payouts--
payoutsJSON :: Payouts -> ValuepayoutsJSON (Payouts m) = v2 $let payoutsRec :: (CreditTo, Rational) -> ValuepayoutsRec (c, r) = object [ "creditTo" .= creditToJSON c
payoutsJSON :: NetworkMode -> Payouts (NetworkId, Address)-> ValuepayoutsJSON nmode (Payouts m) = v2 $let payoutsRec :: (CreditTo (NetworkId, Address), Rational) -> ValuepayoutsRec (c, r) = object [ "creditTo" .= creditToJSON nmode c
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))p (Version 2 0) val =let parsePayoutRecord x = (,) <$> (parseCreditToV2 nmode =<< (x .: "creditTo"))<*> (x .: "payoutRatio")in Payouts . MS.fromList <$> (traverse parsePayoutRecord =<< parseJSON (Object val))p ver x =badVersion "Payouts" ver x
workIndexJSON :: WorkIndex -> ValueworkIndexJSON (WorkIndex widx) = v2 $let widxRec :: (CreditTo, NonEmpty Interval) -> ValuewidxRec (c, l) = object [ "creditTo" .= creditToJSON c
---- WorkIndex--workIndexJSON :: NetworkMode -> WorkIndex (NetworkId, Address) -> ValueworkIndexJSON nmode (WorkIndex widx) = v2 $let widxRec :: (CreditTo (NetworkId, Address), NonEmpty Interval) -> ValuewidxRec (c, l) = object [ "creditTo" .= creditToJSON nmode c
logEntryJSON :: LogEntry -> ValuelogEntryJSON (LogEntry c ev m) = v2 $obj [ "creditTo" .= creditToJSON c
logEntryJSON :: NetworkMode -> LogEntry (NetworkId, Address) -> ValuelogEntryJSON nmode (LogEntry c ev m) = v2 $obj [ "creditTo" .= creditToJSON nmode c
parseId p = fmap (review p) . parseUUIDparseCreditTo :: Value -> Parser CreditToparseCreditTo = unversion "CreditTo" $ p wherep (Version 1 0) = parseCreditToV1p (Version 2 0) = parseCreditToV2p ver = badVersion "EventAmendment" verparseCreditToV1 :: Object -> Parser CreditToparseCreditToV1 x = CreditToAddress <$> (parseJSON =<< (x .: "btcAddr"))parseCreditToV2 :: Object -> Parser CreditToparseCreditToV2 o =let parseCreditToAddr o' =fmap CreditToAddress . parseJSON <$> O.lookup "creditToAddress" o'parseCreditToUser o' =fmap CreditToUser . parseId _UserId <$> O.lookup "creditToUser" o'parseCreditToProject o' =fmap CreditToProject . parseId _ProjectId <$> O.lookup "creditToProject" o'notFound = fail $ "Value " <> show o <> " does not represent a CreditTo value."parseV v = (parseCreditToAddr v <|> parseCreditToUser v <|> parseCreditToProject v)in fromMaybe notFound $ parseV oparsePayoutsJSON :: Value -> Parser PayoutsparsePayoutsJSON = unversion "Payouts" $ p wherep :: Version -> Object -> Parser Payoutsp (Version 1 _) v =let parseKey :: String -> Parser CreditToparseKey k = maybe(fail $ "Key " <> k <> " cannot be parsed as a valid BTC address.")(pure . CreditToAddress)(parseBtcAddr $ T.pack k)in Payouts <$> join (traverseKeys parseKey <$> parseJSON (Object v))p (Version 2 0) v =let parsePayoutRecord x = (,) <$> (parseCreditToV2 =<< (x .: "creditTo")) <*> x .: "payoutRatio"in Payouts . MS.fromList <$> (traverse parsePayoutRecord =<< parseJSON (Object v))
parseId p = fmap (review p) . parseUUID
p ver x =badVersion "Payouts" ver xparseEventAmendment :: ModTime -> Value -> Parser EventAmendmentparseEventAmendment t = unversion "EventAmendment" $ p wherep (Version 1 _) = parseEventAmendmentV1 tp (Version 2 0) = parseEventAmendmentV2 t
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 t
parseEventAmendmentV1 :: ModTime -> Object -> Parser EventAmendmentparseEventAmendmentV1 t o =let parseA :: Text -> Parser EventAmendment
parseEventAmendmentV1:: NetworkMode-> ModTime-> Object-> Parser (EventAmendment (NetworkId, Address))parseEventAmendmentV1 nmode t o =let parseA :: Text -> Parser (EventAmendment (NetworkId, Address))
parseEventAmendmentV2 :: ModTime -> Object -> Parser EventAmendmentparseEventAmendmentV2 t o =let parseA :: Text -> Parser EventAmendment
parseEventAmendmentV2:: NetworkMode-> ModTime-> Object-> Parser (EventAmendment (NetworkId, Address))parseEventAmendmentV2 nmode t o =let parseA :: Text -> Parser (EventAmendment (NetworkId, Address))
parseLogEntry :: UserId -> (C.UTCTime -> LogEvent) -> Value -> Parser (C.UTCTime -> LogEntry)parseLogEntry uid f = unversion "LogEntry" p where
parseLogEntry:: NetworkMode-> UserId-> (UTCTime -> LogEvent)-> Value-> Parser (UTCTime -> (LogEntry (NetworkId, Address)))parseLogEntry nmode uid f = unversion "LogEntry" p where
createPaymentDetails :: (MonadRandom m, MonadReader r m, HasPaymentsConfig r, 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)=> 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
createOutputs :: (MonadDB m) => C.UTCTime -> TL.CreditTo -> BT.Satoshi -> m [BT.Output]createOutputs _ (TL.CreditToAddress (BtcAddr addr)) amt =
createOutputs:: (MonadDB m, MonadError e m, AsPaymentError e)=> C.UTCTime-> TL.CreditTo (NetworkId, Address)-> BT.Satoshi-> m [BT.Output]createOutputs _ (TL.CreditToCurrency (BTC, (PubKeyAddress addr))) amt =
addr <- MaybeT . pure $ user ^. userAddresspure $ BT.Output amt (PayPKHash (addr ^. _BtcAddr))
addr <- MaybeT . pure . fmap snd $ user ^. userAddresscase addr ofPubKeyAddress a -> pure $ BT.Output amt (PayPKHash a)other -> throwError $ review _IllegalAddress other
{-# LANGUAGE LambdaCase #-}{-# LANGUAGE NoImplicitPrelude #-}module Aftok.TimeLog.Serialization( depfFromJSON, depfToJSON) whereimport ClassyPreludeimport Control.Applicative ((<*>))import Data.Aeson (Value(..), (.=), (.:), object)import Data.Aeson.Types (Parser)import Data.Functor ((<$>))import Aftok.TypesdepfToJSON :: DepreciationFunction -> ValuedepfToJSON = \caseLinearDepreciation (Months up) (Months dp) ->object [ "type" .= ("LinearDepreciation" :: Text), "arguments" .= object [ "undep" .= up, "dep" .= dp]]depfFromJSON :: Value -> Parser DepreciationFunctiondepfFromJSON = \caseObject v -> dot <- v .: "type" :: Parser Textargs <- v .: "arguments"case unpack t of"LinearDepreciation" ->let undep = Months <$> (args .: "undep")dep = Months <$> (args .: "dep")in LinearDepreciation <$> undep <*> depx -> fail $ "No depreciation function recognized for type " <> x_ ->fail $ "Cannot interpret non-object value as a depreciation function."
nameEvent :: MonadPlus m => Text -> m (C.UTCTime -> LogEvent)nameEvent "start" = pure StartWorknameEvent "stop" = pure StopWorknameEvent _ = mzerodata CreditTo-- payouts are made directly to this address, or to an address replacing this one= CreditToAddress !BtcAddr-- payouts are distributed as requested by the specified contributor| CreditToUser !UserId-- payouts are distributed to this project's contributors| CreditToProject !ProjectIdderiving (Show, Eq, Ord)makePrisms ''CreditTo
creditToName :: CreditTo -> TextcreditToName (CreditToAddress _) = "credit_to_address"creditToName (CreditToUser _) = "credit_to_user"creditToName (CreditToProject _) = "credit_to_project"
nameEvent :: Text -> Maybe (C.UTCTime -> LogEvent)nameEvent "start" = Just StartWorknameEvent "stop" = Just StopWorknameEvent _ = Nothing
data EventAmendment = TimeChange !ModTime !C.UTCTime| CreditToChange !ModTime !CreditTo| MetadataChange !ModTime !A.Value
data EventAmendment a= TimeChange !ModTime !C.UTCTime| CreditToChange !ModTime !(CreditTo a)| MetadataChange !ModTime !A.Value
type NDT = C.NominalDiffTime{-|- 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.-}type DepF = C.UTCTime -> Interval -> NDT
import ClassyPreludeimport Control.Lensimport Network.Bippy.Types (Satoshi (..))
import Control.Lens (makeLenses, makePrisms)import Data.Maybe (Maybe)import Data.Eq (Eq)import Data.Functor (Functor)import Data.Ord (Ord)import Data.Text (Text)import Data.UUID (UUID)import Prelude (Integer)import Text.Show (Show)newtype UserId = UserId UUID deriving (Show, Eq, Ord)makePrisms ''UserId
satoshi :: Lens' Satoshi Word64satoshi inj (Satoshi value) = Satoshi <$> inj value
newtype UserName = UserName Text deriving (Show, Eq)makePrisms ''UserNamenewtype Email = Email Text deriving (Show, Eq)makePrisms ''Emaildata User a = User{ _username :: !UserName, _userAddress :: !(Maybe a), _userEmail :: !Email}makeLenses ''Usernewtype ProjectId = ProjectId UUID deriving (Show, Eq, Ord)makePrisms ''ProjectIddata CreditTo a-- 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 !ProjectIdderiving (Show, Eq, Ord, Functor)makePrisms ''CreditTo
creditToName :: CreditTo a -> TextcreditToName (CreditToCurrency _) = "credit_via_net"creditToName (CreditToUser _) = "credit_to_user"creditToName (CreditToProject _) = "credit_to_project"data DepreciationFunction = LinearDepreciation Months Monthsderiving (Eq, Show)newtype Months = Months Integerderiving (Eq, Show)
parseJSON (Object v) = CP <$> v .: "projectName" <*> v .: "depf"parseJSON _ = mzero
parseJSON (Object v) =CP <$> v .: "projectName"<*> (depfFromJSON =<< v .: "depf")parseJSON _ = mzero
case A.eitherDecode requestBody >>= parseEither (parseLogEntry uid evCtr) ofLeft err -> snapError 400 $ "Unable to parse log entry " <> (tshow requestBody) <> ": " <> tshow errRight entry -> snapEval $ createEvent pid uid (entry timestamp)
case A.eitherDecode requestBody >>= parseEither (parseLogEntry nmode uid evCtr) ofLeft err ->snapError 400 $ "Unable to parse log entry " <> (tshow requestBody) <> ": " <> tshow errRight entry ->snapEval $ createEvent pid uid (entry timestamp)
snapEval :: (MonadSnap m, HasPostgres m) => Program DBOp a -> m a
class HasNetworkMode m wheregetNetworkMode :: m NetworkModeinstance HasNetworkMode (S.Handler b App) wheregetNetworkMode = _networkMode <$> getsnapEval:: (MonadSnap m, HasPostgres m, HasNetworkMode m)=> Program DBOp a-> m a
logEntriesRoute = serveJSON (fmap logEntryJSON) $ method GET logEntriesHandlerlogIntervalsRoute = serveJSON workIndexJSON $ method GET loggedIntervalsHandler
logEntriesRoute = serveJSON (fmap $ logEntryJSON nmode) $ method GET logEntriesHandlerlogIntervalsRoute = serveJSON (workIndexJSON nmode) $ method GET loggedIntervalsHandler
packages:- '.'- location:git: https://github.com/aftok/bippy.gitcommit: 97fda0368ae660239d1b9398d44530cd5b05eec3extra-dep: true
- snap-1.0.0.2- snaplet-postgresql-simple-1.0.2.0- haskoin-core-0.4.2- heist-1.0.1.0- map-syntax-0.2.0.2- murmur3-1.0.3- pbkdf-1.1.1.1- secp256k1-0.4.8resolver: lts-8.5
# - snap-1.0.0.2# - snaplet-postgresql-simple-1.0.2.0# - haskoin-core-0.4.2# - heist-1.0.1.0# - map-syntax-0.2.0.2# - murmur3-1.0.3# - pbkdf-1.1.1.1# - secp256k1-0.4.8- snap-1.1.2.0@sha256:5640450870d06e659b0f31dd47a7b767a053a78b48048ff8c12c014e08d6651e- snaplet-postgresql-simple-1.1.0.0@sha256:93979aebd232cd92e2971faa118eb78cce399191278d4655354ed292fa980999- heist-1.1.0.1@sha256:7c0fe723e766e41a234def6ad3162958512ad78d3aaaa9b36676186a4427dd01- map-syntax-0.3@sha256:84dc86fa1c292af25963bf7212ae7d55ce87239a9f8d4cc85bd0acc35874d2e1- http-client-openssl-0.3.0.0@sha256:cd617e7bef6c3d8ac4587d7c623b80c35a15735d0142e56eca0ae1c8a67a1b5d- pwstore-fast-2.4.4@sha256:9b6a37510d8b9f37f409a8ab3babac9181afcaaa3fce8ba1c131a7ed3de30698- xmlhtml-0.2.5.2@sha256:0e9ada870a5c5c7d522ed8444bef0f9f0e1587e31a5881f15a5f9cdd983af8b4- git: https://github.com/aftok/bippy.gitcommit: 1c60b6fee50fff28f40c5d5412de422f4a501f66resolver: lts-13.9 #lts-8.5
pvp-bounds: both
# This file was autogenerated by Stack.# You should not edit this file by hand.# For more information, please see the documentation at:# https://docs.haskellstack.org/en/stable/lock_filespackages:- completed:hackage: snap-1.1.2.0@sha256:5640450870d06e659b0f31dd47a7b767a053a78b48048ff8c12c014e08d6651e,9175pantry-tree:size: 5743sha256: ef0dfdb19409ce2aae0d8d9c6312e51931b89f0285bf5798a2349d8130cab89aoriginal:hackage: snap-1.1.2.0@sha256:5640450870d06e659b0f31dd47a7b767a053a78b48048ff8c12c014e08d6651e- completed:hackage: snaplet-postgresql-simple-1.1.0.0@sha256:93979aebd232cd92e2971faa118eb78cce399191278d4655354ed292fa980999,2700pantry-tree:size: 655sha256: 6525a26918dec9179af73a433ac8de4d5a456f5f96a9fcd23f6365e3999b4f5aoriginal:hackage: snaplet-postgresql-simple-1.1.0.0@sha256:93979aebd232cd92e2971faa118eb78cce399191278d4655354ed292fa980999- completed:hackage: heist-1.1.0.1@sha256:7c0fe723e766e41a234def6ad3162958512ad78d3aaaa9b36676186a4427dd01,8973pantry-tree:size: 7354sha256: 1ed83746a3e9470618ef67da249b0b4d78c87cc5c50d9c892e27db057c0d4866original:hackage: heist-1.1.0.1@sha256:7c0fe723e766e41a234def6ad3162958512ad78d3aaaa9b36676186a4427dd01- completed:hackage: map-syntax-0.3@sha256:84dc86fa1c292af25963bf7212ae7d55ce87239a9f8d4cc85bd0acc35874d2e1,2420pantry-tree:size: 558sha256: bb33cb3230b362d94f2367b313f06f9d73d2b2afa4626bd2fab8dc4d45468164original:hackage: map-syntax-0.3@sha256:84dc86fa1c292af25963bf7212ae7d55ce87239a9f8d4cc85bd0acc35874d2e1- completed:hackage: http-client-openssl-0.3.0.0@sha256:cd617e7bef6c3d8ac4587d7c623b80c35a15735d0142e56eca0ae1c8a67a1b5d,1548pantry-tree:size: 387sha256: 5712016dbe69a539ca265b8e1b248d499445a2a414ac3a8bc9c8a62bef0ffc6doriginal:hackage: http-client-openssl-0.3.0.0@sha256:cd617e7bef6c3d8ac4587d7c623b80c35a15735d0142e56eca0ae1c8a67a1b5d- completed:hackage: pwstore-fast-2.4.4@sha256:9b6a37510d8b9f37f409a8ab3babac9181afcaaa3fce8ba1c131a7ed3de30698,1351pantry-tree:size: 270sha256: ff4a44ede62515efe5cd366a5803f7183c811c4a0cf56eea88da94181c4844c0original:hackage: pwstore-fast-2.4.4@sha256:9b6a37510d8b9f37f409a8ab3babac9181afcaaa3fce8ba1c131a7ed3de30698- completed:hackage: xmlhtml-0.2.5.2@sha256:0e9ada870a5c5c7d522ed8444bef0f9f0e1587e31a5881f15a5f9cdd983af8b4,46997pantry-tree:size: 61835sha256: 13fdaf307ac4a3f60999aca0c367792e97f92428f56ffe144092a6360bd1e33foriginal:hackage: xmlhtml-0.2.5.2@sha256:0e9ada870a5c5c7d522ed8444bef0f9f0e1587e31a5881f15a5f9cdd983af8b4- completed:cabal-file:size: 2747sha256: 6ec7c63e2fa691f9b07015e756018f1dbc13d280521801664cee1317be07cf71name: bippyversion: 0.1.0.0git: https://github.com/aftok/bippy.gitpantry-tree:size: 3547sha256: 4ee75c44d9cb4b8a39bbd297d63866a3a738108b438e33dfad068a78edcea5dccommit: 1c60b6fee50fff28f40c5d5412de422f4a501f66original:git: https://github.com/aftok/bippy.gitcommit: 1c60b6fee50fff28f40c5d5412de422f4a501f66snapshots:- completed:size: 496697url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/13/9.yamlsha256: 3846ba7d13dd1b2679426dc3f450332a3b8a181063b0f3fc2d0c7d55db2e9c24original: lts-13.9