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 where
import ClassyPrelude
import Control.Lens (makeLenses, makePrisms)
import Data.Aeson
import Data.Aeson.Types
import Data.Data
import Data.UUID
import Network.Haskoin.Crypto (Address (..), base58ToAddr)
newtype BtcAddr = BtcAddr Address deriving (Show, Eq, Ord)
makePrisms ''BtcAddr
parseBtcAddr :: Text -> Maybe BtcAddr
parseBtcAddr addr = BtcAddr <$> (base58ToAddr . encodeUtf8) addr
instance FromJSON BtcAddr where
parseJSON v = do
t <- parseJSON v
maybe (fail $ show t <> " is not a valid BTC address") pure $ parseBtcAddr t
newtype Months = Months Integer
deriving (Eq, Show, Data, Typeable)
data DepreciationFunction = LinearDepreciation Months Months
deriving (Eq, Show, Data, Typeable)
newtype UserId = UserId UUID deriving (Show, Eq, Ord)
makePrisms ''UserId
newtype UserName = UserName Text deriving (Show, Eq)
makePrisms ''UserName
newtype Email = Email Text deriving (Show, Eq)
makePrisms ''Email
data User = User
{ _username :: !UserName
, _userAddress :: !(Maybe BtcAddr)
, _userEmail :: !Email
}
makeLenses ''User
-- | others tbd
instance ToJSON DepreciationFunction where
toJSON (LinearDepreciation (Months up) (Months dp)) =
object [ "type" .= ("LinearDepreciation" :: Text)
, "arguments" .= object [ "undep" .= up
, "dep" .= dp
]
]
instance FromJSON DepreciationFunction where
parseJSON (Object v) = do
t <- v .: "type" :: Parser Text
args <- v .: "arguments"
case unpack t of
"LinearDepreciation" ->
let undep = Months <$> (args .: "undep")
dep = Months <$> (args .: "dep")
in LinearDepreciation <$> undep <*> dep
x -> fail $ "No depreciation function recognized for type " <> x
parseJSON _ = mzero
let remainder = raiseAmount' - total
winFraction = 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 .~ rem
in toList $ adjustBid <$> raiseAmount' `ssub` total
in if submittedTotal >= raiseAmount'
then WinningBids $ takeWinningBids 0 $ sortBy bidOrder bids
else InsufficientBids (raiseAmount' - submittedTotal)
in maybe
(WinningBids $ takeWinningBids (Satoshi 0) $ sortBy bidOrder bids)
InsufficientBids
(raiseAmount' `ssub` submittedTotal)
let remainder = raiseAmount' - x
winFraction = 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 where
import ClassyPrelude
import qualified Data.Configurator.Types as C
import Control.Lens
import Network.Bippy.Types (Satoshi (..))
import Network.Haskoin.Constants
satoshi :: Lens' Satoshi Word64
satoshi inj (Satoshi value) = Satoshi <$> inj value
ssub :: Satoshi -> Satoshi -> Maybe Satoshi
ssub (Satoshi a) (Satoshi b) | a > b = Just . Satoshi $ (a - b)
ssub _ _ = Nothing
data NetworkId
= BTC
| BCH
deriving (Eq, Show, Ord)
renderNetworkId :: NetworkId -> Text
renderNetworkId = \case
BTC -> "btc"
BCH -> "bch"
parseNetworkId :: Text -> Maybe NetworkId
parseNetworkId = \case
"btc" -> Just BTC
"bch" -> Just BCH
_ -> Nothing
data NetworkMode
= LiveMode
| TestMode
parseNetworkMode :: Text -> Maybe NetworkMode
parseNetworkMode = \case
"test" -> Just TestMode
"live" -> Just LiveMode
_ -> Nothing
instance C.Configured NetworkMode where
convert (C.String t) = parseNetworkMode t
convert _ = Nothing
toNetwork :: NetworkMode -> NetworkId -> Network
toNetwork LiveMode = \case
BTC -> btc
BCH -> bch
toNetwork TestMode = \case
BTC -> btcTest
BCH -> bchTest
toNetworkId :: Network -> Maybe NetworkId
toNetworkId n = case getNetworkName n of
"btc" -> Just BTC
"btcTest" -> Just BTC
"bch" -> Just BCH
"bchTest" -> Just BCH
_ -> Nothing
module Aftok.Currency where
import Data.Aeson (Value)
import Data.Aeson.Types (Parser)
data Network a = Network
{ addressFromJSON :: Parser a
, addressToJSON :: a -> Value
}
module Aftok.Database.PostgreSQL.Types where
import 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 where
parseJSON v = SerDepFunction <$> depfFromJSON v
instance ToJSON SerDepFunction where
toJSON (SerDepFunction depf) = depfToJSON depf
runQDBM :: Connection -> QDBM a -> ExceptT DBError IO a
runQDBM conn (QDBM r) = runReaderT r conn
runQDBM :: NetworkMode -> Connection -> QDBM a -> ExceptT DBError IO a
runQDBM mode conn (QDBM r) = runReaderT r (mode, conn)
null :: RowParser Null
null = field
btcAddrParser :: FieldParser BtcAddr
btcAddrParser f v = do
addrMay <- parseBtcAddr <$> fromField f v
networkIdParser :: FieldParser NetworkId
networkIdParser f b = do
networkName <- fromField f b
case networkName of
Just "btc" -> pure BTC
Just "bch" -> pure BCH
Just other -> returnError ConversionFailed f ("Network identifier " <> other <> " is not supported.")
Nothing -> pure BTC
addressParser :: NetworkMode -> RowParser (NetworkId, Address)
addressParser mode = do
networkId <- fieldWith (networkIdParser)
address <- fieldWith $ addrFieldParser (toNetwork mode networkId)
pure (networkId, address)
addrFieldParser :: Network -> FieldParser Address
addrFieldParser n f v = do
addrMay <- 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 CreditTo
parser "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 LogEntry
logEntryParser =
LogEntry <$> creditToParser
logEntryParser :: NetworkMode -> RowParser (LogEntry (NetworkId, Address))
logEntryParser mode =
LogEntry <$> creditToParser mode
qdbLogEntryParser :: RowParser KeyedLogEntry
qdbLogEntryParser =
(,,) <$> idParser P.ProjectId
qdbLogEntryParser :: NetworkMode -> RowParser (KeyedLogEntry (NetworkId, Address))
qdbLogEntryParser mode =
(,,) <$> idParser ProjectId
conn <- ask
lift . ExceptT $ withTransaction conn (runExceptT $ runReaderT rt conn)
env <- ask
lift . 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)) = do
mode <- askNetworkMode
headMay <$> 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_metadata
FROM work_events
pgEval (FindEvents (ProjectId pid) (UserId uid) ival) = do
mode <- askNetworkMode
let 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_metadata
FROM 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_metadata
FROM work_events
WHERE 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_metadata
FROM work_events
WHERE 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_metadata
FROM 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_metadata
FROM 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) -> do
let network = toNetwork mode nid
pinsert 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)) = do
logEntries <- pquery logEntryParser
[sql| SELECT credit_to_type, credit_to_address, credit_to_user_id, credit_to_project_id,
event_type, event_time, event_metadata
FROM work_events
pgEval (ReadWorkIndex (ProjectId pid)) = do
mode <- askNetworkMode
logEntries <- pquery (logEntryParser mode)
[sql| SELECT credit_to_type, credit_to_address, credit_to_user_id, credit_to_project_id,
event_type, event_time, event_metadata
FROM work_events
headMay <$> pquery auctionParser
[sql| SELECT project_id, initiator_id, created_at, raise_amount, start_time, end_time
FROM auctions
headMay <$> pquery auctionParser
[sql| SELECT project_id, initiator_id, created_at, raise_amount, start_time, end_time
FROM auctions
pgEval (CreateUser user') =
let addrMay :: Maybe ByteString
addrMay = user' ^? (userAddress . traverse . _BtcAddr . to addrToBase58)
in pinsert UserId
[sql| INSERT INTO users (handle, btc_addr, email) VALUES (?, ?, ?) RETURNING id |]
pgEval (CreateUser user') = do
mode <- askNetworkMode
let nidMay = fst <$> _userAddress user'
addrMay :: Maybe Text
addrMay = do
network <- toNetwork mode <$> nidMay
address <- snd <$> _userAddress user'
pure $ addrToString network address
pinsert 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)) = do
mode <- askNetworkMode
headMay <$> 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_fn
FROM projects p LEFT OUTER JOIN project_companions pc ON pc.project_id = p.id
WHERE pc.user_id = ?
pquery ((,) <$> idParser ProjectId <*> projectParser)
[sql| SELECT p.id, p.project_name, p.inception_date, p.initiator_id, p.depreciation_fn
FROM projects p LEFT OUTER JOIN project_companions pc ON pc.project_id = p.id
WHERE 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_date
FROM subscriptions s
JOIN billables b ON b.id = s.billable_id
WHERE s.user_id = ?
pquery ((,) <$> idParser B.SubscriptionId <*> subscriptionParser)
[sql| SELECT s.id, user_id, billable_id, start_date, end_date
FROM subscriptions s
JOIN billables b ON b.id = s.billable_id
WHERE 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_date
FROM payment_requests
WHERE url_key = ?
headMay <$> pquery ((,) <$> idParser PaymentRequestId <*> paymentRequestParser)
[sql| SELECT id, subscription_id, request_data, url_key, request_time, billing_date
FROM payment_requests
WHERE url_key = ?
pquery ((,) <$> idParser PaymentRequestId <*> paymentRequestParser)
[sql| SELECT id, subscription_id, request_data, url_key, request_time, billing_date
FROM payment_requests
pquery ((,) <$> idParser PaymentRequestId <*> paymentRequestParser)
[sql| SELECT id, subscription_id, request_data, url_key, request_time, billing_date
FROM 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_date
FROM payments
pquery ((,) <$> idParser PaymentId <*> paymentParser)
[sql| SELECT id, payment_request_id, payment_data, payment_date
FROM payments
type KeyedLogEntry = (ProjectId, UserId, LogEntry)
type InvitingUID = UserId
type InvitedUID = UserId
import Network.Haskoin.Address (Address)
type KeyedLogEntry a = (ProjectId, UserId, LogEntry a)
type InvitingUID = UserId
type InvitedUID = UserId
type BTCNet = (NetworkId, Address)
type BTCUser = User BTCNet
CreateUser :: User -> DBOp UserId
FindUser :: UserId -> DBOp (Maybe User)
FindUserByName :: UserName -> DBOp (Maybe (UserId, User))
CreateUser :: BTCUser -> DBOp UserId
FindUser :: UserId -> DBOp (Maybe BTCUser)
FindUserByName :: UserName -> DBOp (Maybe (UserId, BTCUser))
CreateEvent :: ProjectId -> UserId -> LogEntry -> DBOp EventId
AmendEvent :: EventId -> EventAmendment -> DBOp AmendmentId
FindEvent :: EventId -> DBOp (Maybe KeyedLogEntry)
FindEvents :: ProjectId -> UserId -> Interval' -> DBOp [LogEntry]
ReadWorkIndex :: ProjectId -> DBOp WorkIndex
CreateEvent :: ProjectId -> UserId -> LogEntry BTCNet -> DBOp EventId
AmendEvent :: EventId -> EventAmendment BTCNet -> DBOp AmendmentId
FindEvent :: EventId -> DBOp (Maybe (KeyedLogEntry BTCNet))
FindEvents :: ProjectId -> UserId -> Interval' -> DBOp [LogEntry BTCNet]
ReadWorkIndex :: ProjectId -> DBOp (WorkIndex BTCNet)
instance Show Version where
show Version{..} = intercalate "." $ fmap show [majorVersion, minorVersion]
failT :: Text -> Parser a
failT = fail . T.unpack
printVersion :: Version -> Text
printVersion Version{..} = T.intercalate "." $ fmap (pack . show) [majorVersion, minorVersion]
version :: QuasiQuoter
version = 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 Version
version = fromEitherM fail . PC.parseOnly versionParser
v :: QuasiQuoter
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."
}
creditToJSON :: CreditTo -> Value
creditToJSON (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) -> Value
creditToJSON 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 nmode
ver -> badVersion "EventAmendment" ver
parseBtcAddr
:: 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 = do
parseBtcAddr nmode BTC =<< x .: "btcAddr"
parseCreditToV2 :: NetworkMode -> Object -> Parser (CreditTo (NetworkId, Address))
parseCreditToV2 nmode o =
let parseCreditToAddr = do
netName <- o .: "creditToNetwork"
net <- fromMaybeM
(fail . T.unpack $ "Currency network " <> netName <> " not recognized.")
(parseNetworkId netName)
addrValue <- o .: "creditToAddress"
CreditToCurrency . (net,) <$> addrFromJSON (toNetwork nmode net) addrValue
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."
in parseCreditToAddr <|> parseCreditToUser <|> parseCreditToProject <|> notFound
--
-- Payouts
--
payoutsJSON :: Payouts -> Value
payoutsJSON (Payouts m) = v2 $
let payoutsRec :: (CreditTo, Rational) -> Value
payoutsRec (c, r) = object [ "creditTo" .= creditToJSON c
payoutsJSON :: NetworkMode -> Payouts (NetworkId, Address)-> Value
payoutsJSON nmode (Payouts m) = v2 $
let payoutsRec :: (CreditTo (NetworkId, Address), Rational) -> Value
payoutsRec (c, r) = object [ "creditTo" .= creditToJSON nmode c
parsePayoutsJSON :: NetworkMode -> Value -> Parser (Payouts (NetworkId, Address))
parsePayoutsJSON nmode = unversion "Payouts" $ p where
p :: 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 -> Value
workIndexJSON (WorkIndex widx) = v2 $
let widxRec :: (CreditTo, NonEmpty Interval) -> Value
widxRec (c, l) = object [ "creditTo" .= creditToJSON c
--
-- WorkIndex
--
workIndexJSON :: NetworkMode -> WorkIndex (NetworkId, Address) -> Value
workIndexJSON nmode (WorkIndex widx) = v2 $
let widxRec :: (CreditTo (NetworkId, Address), NonEmpty Interval) -> Value
widxRec (c, l) = object [ "creditTo" .= creditToJSON nmode c
logEntryJSON :: LogEntry -> Value
logEntryJSON (LogEntry c ev m) = v2 $
obj [ "creditTo" .= creditToJSON c
logEntryJSON :: NetworkMode -> LogEntry (NetworkId, Address) -> Value
logEntryJSON nmode (LogEntry c ev m) = v2 $
obj [ "creditTo" .= creditToJSON nmode c
parseId p = fmap (review p) . parseUUID
parseCreditTo :: Value -> Parser CreditTo
parseCreditTo = unversion "CreditTo" $ p where
p (Version 1 0) = parseCreditToV1
p (Version 2 0) = parseCreditToV2
p ver = badVersion "EventAmendment" ver
parseCreditToV1 :: Object -> Parser CreditTo
parseCreditToV1 x = CreditToAddress <$> (parseJSON =<< (x .: "btcAddr"))
parseCreditToV2 :: Object -> Parser CreditTo
parseCreditToV2 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 o
parsePayoutsJSON :: Value -> Parser Payouts
parsePayoutsJSON = unversion "Payouts" $ p where
p :: Version -> Object -> Parser Payouts
p (Version 1 _) v =
let parseKey :: String -> Parser CreditTo
parseKey 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 x
parseEventAmendment :: ModTime -> Value -> Parser EventAmendment
parseEventAmendment t = unversion "EventAmendment" $ p where
p (Version 1 _) = parseEventAmendmentV1 t
p (Version 2 0) = parseEventAmendmentV2 t
parseEventAmendment
:: NetworkMode
-> ModTime
-> Value
-> Parser (EventAmendment (NetworkId, Address))
parseEventAmendment nmode t = unversion "EventAmendment" $ p where
p (Version 1 _) = parseEventAmendmentV1 nmode t
p (Version 2 0) = parseEventAmendmentV2 nmode t
parseEventAmendmentV1 :: ModTime -> Object -> Parser EventAmendment
parseEventAmendmentV1 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 EventAmendment
parseEventAmendmentV2 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 ^. userAddress
pure $ BT.Output amt (PayPKHash (addr ^. _BtcAddr))
addr <- MaybeT . pure . fmap snd $ user ^. userAddress
case addr of
PubKeyAddress a -> pure $ BT.Output amt (PayPKHash a)
other -> throwError $ review _IllegalAddress other
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Aftok.TimeLog.Serialization
( depfFromJSON
, depfToJSON
) where
import ClassyPrelude
import Control.Applicative ((<*>))
import Data.Aeson (Value(..), (.=), (.:), object)
import Data.Aeson.Types (Parser)
import Data.Functor ((<$>))
import Aftok.Types
depfToJSON :: DepreciationFunction -> Value
depfToJSON = \case
LinearDepreciation (Months up) (Months dp) ->
object [ "type" .= ("LinearDepreciation" :: Text)
, "arguments" .= object [ "undep" .= up
, "dep" .= dp
]
]
depfFromJSON :: Value -> Parser DepreciationFunction
depfFromJSON = \case
Object v -> do
t <- v .: "type" :: Parser Text
args <- v .: "arguments"
case unpack t of
"LinearDepreciation" ->
let undep = Months <$> (args .: "undep")
dep = Months <$> (args .: "dep")
in LinearDepreciation <$> undep <*> dep
x -> 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 StartWork
nameEvent "stop" = pure StopWork
nameEvent _ = mzero
data 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 !ProjectId
deriving (Show, Eq, Ord)
makePrisms ''CreditTo
creditToName :: CreditTo -> Text
creditToName (CreditToAddress _) = "credit_to_address"
creditToName (CreditToUser _) = "credit_to_user"
creditToName (CreditToProject _) = "credit_to_project"
nameEvent :: Text -> Maybe (C.UTCTime -> LogEvent)
nameEvent "start" = Just StartWork
nameEvent "stop" = Just StopWork
nameEvent _ = 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 ClassyPrelude
import Control.Lens
import 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 Word64
satoshi inj (Satoshi value) = Satoshi <$> inj value
newtype UserName = UserName Text deriving (Show, Eq)
makePrisms ''UserName
newtype Email = Email Text deriving (Show, Eq)
makePrisms ''Email
data User a = User
{ _username :: !UserName
, _userAddress :: !(Maybe a)
, _userEmail :: !Email
}
makeLenses ''User
newtype ProjectId = ProjectId UUID deriving (Show, Eq, Ord)
makePrisms ''ProjectId
data 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 !ProjectId
deriving (Show, Eq, Ord, Functor)
makePrisms ''CreditTo
creditToName :: CreditTo a -> Text
creditToName (CreditToCurrency _) = "credit_via_net"
creditToName (CreditToUser _) = "credit_to_user"
creditToName (CreditToProject _) = "credit_to_project"
data DepreciationFunction = LinearDepreciation Months Months
deriving (Eq, Show)
newtype Months = Months Integer
deriving (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) of
Left err -> snapError 400 $ "Unable to parse log entry " <> (tshow requestBody) <> ": " <> tshow err
Right entry -> snapEval $ createEvent pid uid (entry timestamp)
case A.eitherDecode requestBody >>= parseEither (parseLogEntry nmode uid evCtr) of
Left err ->
snapError 400 $ "Unable to parse log entry " <> (tshow requestBody) <> ": " <> tshow err
Right entry ->
snapEval $ createEvent pid uid (entry timestamp)
snapEval :: (MonadSnap m, HasPostgres m) => Program DBOp a -> m a
class HasNetworkMode m where
getNetworkMode :: m NetworkMode
instance HasNetworkMode (S.Handler b App) where
getNetworkMode = _networkMode <$> get
snapEval
:: (MonadSnap m, HasPostgres m, HasNetworkMode m)
=> Program DBOp a
-> m a
logEntriesRoute = serveJSON (fmap logEntryJSON) $ method GET logEntriesHandler
logIntervalsRoute = serveJSON workIndexJSON $ method GET loggedIntervalsHandler
logEntriesRoute = serveJSON (fmap $ logEntryJSON nmode) $ method GET logEntriesHandler
logIntervalsRoute = serveJSON (workIndexJSON nmode) $ method GET loggedIntervalsHandler
packages:
- '.'
- location:
git: https://github.com/aftok/bippy.git
commit: 97fda0368ae660239d1b9398d44530cd5b05eec3
extra-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.8
resolver: 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.git
commit: 1c60b6fee50fff28f40c5d5412de422f4a501f66
resolver: 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_files
packages:
- completed:
hackage: snap-1.1.2.0@sha256:5640450870d06e659b0f31dd47a7b767a053a78b48048ff8c12c014e08d6651e,9175
pantry-tree:
size: 5743
sha256: ef0dfdb19409ce2aae0d8d9c6312e51931b89f0285bf5798a2349d8130cab89a
original:
hackage: snap-1.1.2.0@sha256:5640450870d06e659b0f31dd47a7b767a053a78b48048ff8c12c014e08d6651e
- completed:
hackage: snaplet-postgresql-simple-1.1.0.0@sha256:93979aebd232cd92e2971faa118eb78cce399191278d4655354ed292fa980999,2700
pantry-tree:
size: 655
sha256: 6525a26918dec9179af73a433ac8de4d5a456f5f96a9fcd23f6365e3999b4f5a
original:
hackage: snaplet-postgresql-simple-1.1.0.0@sha256:93979aebd232cd92e2971faa118eb78cce399191278d4655354ed292fa980999
- completed:
hackage: heist-1.1.0.1@sha256:7c0fe723e766e41a234def6ad3162958512ad78d3aaaa9b36676186a4427dd01,8973
pantry-tree:
size: 7354
sha256: 1ed83746a3e9470618ef67da249b0b4d78c87cc5c50d9c892e27db057c0d4866
original:
hackage: heist-1.1.0.1@sha256:7c0fe723e766e41a234def6ad3162958512ad78d3aaaa9b36676186a4427dd01
- completed:
hackage: map-syntax-0.3@sha256:84dc86fa1c292af25963bf7212ae7d55ce87239a9f8d4cc85bd0acc35874d2e1,2420
pantry-tree:
size: 558
sha256: bb33cb3230b362d94f2367b313f06f9d73d2b2afa4626bd2fab8dc4d45468164
original:
hackage: map-syntax-0.3@sha256:84dc86fa1c292af25963bf7212ae7d55ce87239a9f8d4cc85bd0acc35874d2e1
- completed:
hackage: http-client-openssl-0.3.0.0@sha256:cd617e7bef6c3d8ac4587d7c623b80c35a15735d0142e56eca0ae1c8a67a1b5d,1548
pantry-tree:
size: 387
sha256: 5712016dbe69a539ca265b8e1b248d499445a2a414ac3a8bc9c8a62bef0ffc6d
original:
hackage: http-client-openssl-0.3.0.0@sha256:cd617e7bef6c3d8ac4587d7c623b80c35a15735d0142e56eca0ae1c8a67a1b5d
- completed:
hackage: pwstore-fast-2.4.4@sha256:9b6a37510d8b9f37f409a8ab3babac9181afcaaa3fce8ba1c131a7ed3de30698,1351
pantry-tree:
size: 270
sha256: ff4a44ede62515efe5cd366a5803f7183c811c4a0cf56eea88da94181c4844c0
original:
hackage: pwstore-fast-2.4.4@sha256:9b6a37510d8b9f37f409a8ab3babac9181afcaaa3fce8ba1c131a7ed3de30698
- completed:
hackage: xmlhtml-0.2.5.2@sha256:0e9ada870a5c5c7d522ed8444bef0f9f0e1587e31a5881f15a5f9cdd983af8b4,46997
pantry-tree:
size: 61835
sha256: 13fdaf307ac4a3f60999aca0c367792e97f92428f56ffe144092a6360bd1e33f
original:
hackage: xmlhtml-0.2.5.2@sha256:0e9ada870a5c5c7d522ed8444bef0f9f0e1587e31a5881f15a5f9cdd983af8b4
- completed:
cabal-file:
size: 2747
sha256: 6ec7c63e2fa691f9b07015e756018f1dbc13d280521801664cee1317be07cf71
name: bippy
version: 0.1.0.0
git: https://github.com/aftok/bippy.git
pantry-tree:
size: 3547
sha256: 4ee75c44d9cb4b8a39bbd297d63866a3a738108b438e33dfad068a78edcea5dc
commit: 1c60b6fee50fff28f40c5d5412de422f4a501f66
original:
git: https://github.com/aftok/bippy.git
commit: 1c60b6fee50fff28f40c5d5412de422f4a501f66
snapshots:
- completed:
size: 496697
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/13/9.yaml
sha256: 3846ba7d13dd1b2679426dc3f450332a3b8a181063b0f3fc2d0c7d55db2e9c24
original: lts-13.9