IZEVQF627FA7VV25KJAWYWGGC35LZUUBBQRPN5ZAINTQLPEDRTEAC JKMHA2QGDSVHD4DKDYQUYNJJ3LUQCOPOWEC3543BDWDXLYIBBZXQC NJZ3DKZYZTAEHPAEXS3XFWIPCJFR3D4642UQMGQABVFGNKQUEQVAC MK7ODWHUEPOMAEVK5D3OWW25QRAXKDO4ZVJKNZGWKB5434MP2LNAC 75N3UJ4JK56KXF56GASGPAWLFYGJDETVJNYTF4KXFCQM767JUU5AC WO2MINIF4TXOHWSE7JWXRZYN64XRVLYIRFMF4SMPSOXKA2V77KMQC LAROLAYUGJ4Q5AEFV5EJMIA2ZKBNCBWHHHPCJ3CKCNIUIYUKRFVQC TLQ72DSJD7GGPWN6HGBHAVPBRQFKEQ6KSK43U7JWWID4ZWAF47JAC 5W5M56VJFJEBXMGBVKGCKPHOEMVTKUOQMLPJP7VNDQLTYNJXXLHQC EMVTF2IWNQGRL44FC4JNG5FYYQTZSFPNM6SOM7IAEH6T7PPK2NVAC 64C6AWH66FDKU6UE6Z6JPX2J2GBM2JOPTH2GL6LHKAIUBGNGDZ5AC LUM4VQJIHJKQWWD5NVWTVSNPKQTMGQQICTFOTM6W4BMME2G3G5RQC MWUPXTBF2LATVOJLJTXSDFB3OMFGMXDNETWJA3JHUOUBTUJ7WJAAC TZQJVHBAMDNWDBYCDE3SDVGBG2T5FOE3J5JAD6NENRW36XBHUUFQC WFZDMVUXZ2KPTMRAZGEYHKEJTKOKWVYCXKKAKQ7K6I5TMSLBUJ4QC GKGVYBZGPJXO7N7GLHLRNYQPXFHBQSNQN53OKRFCXLQEYDTC5I4QC NVOCQVASZWTKQJG7GPH7KHKZZR7NUG4WLV5YY4KAIRPCJRWCZPIAC 7DBNV3GV773FH5ZLQWFX4RBOS4Q3CIK2RYZNNABY3ZOETYZCXRNQC OBFPJS2GHO2PEHBHGEHKIUOUAFIQHPIZXEVD2YIE3ZIE2PVMH5VAC WZUHEZSBRKHQMNWDKVG4X6DDIQEAXTGI6IGAJ5ERPRQ3W2KUMX4QC TCOAKCGGHOIRJCTZYEZQ3K6KCGL2LGAYGYFRGSPCHBTJJY2V6AXAC ADMKQQGCGVSHHIMVQ4XFRDCG544SBJCYALSKZV45CQQBZ4ACUH2AC 7XN3I3QJHYMKU2DCUXX34WQMSJ4ZJOWW7FME34EANO3E5W4Q632AC 2Y2QZFVFSKXEFEGYJB5A7GI735ONWPCF7DVTIY5T73AUEVTZTBBQC 64VI73NPSFNWTL6UXM6YHRFLNJZ3NUJ2R3CL53MO2HSZWFGBIRTQC W35DDBFYF6Z4ZPCFEO5RPAONZLYCSNTXUSTS6FIUUVZHCI6Q7GHAC RSEB2NFGUBTFESE5BJKDUVQL5Y5ZVGY5O4CJX2LNP63MS3NRHHZQC Z3M53KTLZMPOISMHE25SZJSWX5TA37IV33IRE7KNRAD3PKEAEJXQC | total < auction ^. raiseAmount =let remainder = (auction ^. raiseAmount) ++ invert totalwinFraction :: RationalwinFraction = (toRational $ remainder ^. satoshis) / (toRational $ x ^. (bidAmount . satoshis))
| total < (auction ^. raiseAmount) =let remainder = (auction ^. raiseAmount) - totalwinFraction = toRational $ remainder / (x ^. bidAmount)
{-# LANGUAGE ScopedTypeVariables, OverloadedStrings, NoImplicitPrelude #-}{-# LANGUAGE TemplateHaskell #-}module Quixotic.Database.PostgreSQL (postgresQDB) whereimport Blaze.ByteString.Builder (fromByteString)import ClassyPreludeimport Control.Lensimport Data.Fixedimport Data.Hourglassimport qualified Data.List as DLimport Database.PostgreSQL.Simpleimport Database.PostgreSQL.Simple.ToFieldimport Database.PostgreSQL.Simple.FromFieldimport Database.PostgreSQL.Simple.ToRowimport Database.PostgreSQL.Simple.FromRowimport Network.Bitcoinimport Quixoticimport Quixotic.Auctionimport Quixotic.Databaseimport Quixotic.TimeLogimport Quixotic.UserseventTypeParser :: FieldParser EventTypeeventTypeParser f v = fromField f v >>= nameEventuidParser :: FieldParser UserIduidParser f v = UserId <$> fromField f vsecondsParser :: FieldParser SecondssecondsParser f v = Seconds <$> fromField f vusernameParser :: FieldParser UserNameusernameParser f v = UserName <$> fromField f vbtcAddrParser :: FieldParser BtcAddrbtcAddrParser f v = BtcAddr <$> fromField f vbtcParser :: FieldParser BTCbtcParser f v = fromField f vworkEventParser :: RowParser WorkEventworkEventParser = WorkEvent <$> fieldWith eventTypeParser <*> fieldlogEntryParser :: RowParser LogEntrylogEntryParser = LogEntry <$> fieldWith btcAddrParser <*> workEventParserauctionRowParser :: RowParser AuctionauctionRowParser = Auction <$> fieldWith btcParser <*> fieldbidRowParser :: RowParser BidbidRowParser = Bid <$> fieldWith uidParser<*> fieldWith secondsParser<*> fieldWith btcParser<*> fielduserRowParser :: RowParser UseruserRowParser = User <$> fieldWith usernameParser<*> fieldWith btcAddrParser<*> field-- Local newtypes to permit field serializationnewtype PBTC = PBTC { pBTC :: BTC }instance ToField PBTC wheretoField (PBTC btc) = Plain . fromByteString . fromString $ showFixed False btc-- Local newtypes to permit row deserialization via-- typeclass. Wish I could just pass the RowParser instancesnewtype PLogEntry = PLogEntry { pLogEntry :: LogEntry }instance FromRow PLogEntry wherefromRow = PLogEntry <$> logEntryParsernewtype PBid = PBid { pBid :: Bid }instance FromRow PBid wherefromRow = PBid <$> bidRowParsernewtype PUser = PUser { pUser :: User }instance FromRow PUser wherefromRow = PUser <$> userRowParsernewtype PAuction = PAuction { pAuction :: Auction }instance FromRow PAuction wherefromRow = PAuction <$> auctionRowParserrecordEvent' :: UserId -> LogEntry -> ReaderT Connection IO ()recordEvent' (UserId uid) (LogEntry a e) = doconn <- askvoid . lift $ execute conn"INSERT INTO work_events (user_id, btc_addr, event_type, event_time) VALUES (?, ?, ?, ?)"( uid, a ^. address, e ^. (eventType . to eventName), e ^. eventTime)readWorkIndex' :: ReaderT Connection IO WorkIndexreadWorkIndex' = doconn <- askrows <- lift $ query_ conn"SELECT btc_addr, event_type, event_time from work_events"pure . workIndex $ fmap pLogEntry rowsnewAuction' :: Auction -> ReaderT Connection IO AuctionIdnewAuction' auc = doconn <- askaucIds <- lift $ query conn"INSERT INTO auctions (raise_amount, end_time) VALUES (?, ?) RETURNING id"(auc ^. (raiseAmount.to PBTC), auc ^. auctionEnd)pure . AuctionId . fromOnly $ DL.head aucIdsreadAuction' :: AuctionId -> ReaderT Connection IO (Maybe Auction)readAuction' aucId = doconn <- askrows <- lift $ query conn"SELECT raise_amount, end_time FROM auctions WHERE ROWID = ?"(Only (aucId ^. _AuctionId))pure . fmap pAuction $ headMay rowsrecordBid' :: AuctionId -> Bid -> ReaderT Connection IO ()recordBid' (AuctionId aucId) bid = doconn <- askvoid . lift $ execute conn"INSERT INTO bids (auction_id, user_id, bid_seconds, bid_amount, bid_time) values (?, ?, ?, ?, ?)"( aucId, bid ^. (bidUser._UserId), case bid ^. bidSeconds of (Seconds i) -> i, bid ^. (bidAmount.to PBTC), bid ^. bidTime)readBids' :: AuctionId -> ReaderT Connection IO [Bid]readBids' aucId = doconn <- askrows <- lift $ query conn"SELECT user_id, bid_seconds, bid_amount, bid_time FROM bids WHERE auction_id = ?"(Only $ (aucId ^. _AuctionId))pure $ fmap pBid rowscreateUser' :: User -> ReaderT Connection IO UserIdcreateUser' user = doconn <- askuids <- lift $ query conn"INSERT INTO users (handle, btc_addr, email) VALUES (?, ?) RETURNING id"(user ^. (username._UserName), user ^. (userAddress.address), user ^. userEmail)pure . UserId . fromOnly $ DL.head uidsfindUser' :: UserId -> ReaderT Connection IO (Maybe User)findUser' (UserId uid) = doconn <- askusers <- lift $ query conn"SELECT handle, btc_addr, email FROM users WHERE id = ?"(Only uid)pure . fmap pUser $ headMay usersfindUserByHandle' :: Handle -> ReaderT Connection IO (Maybe QDBUser)findUserByHandle' = undefinedpostgresQDB :: QDB (ReaderT Connection IO)postgresQDB = QDB{ recordEvent = recordEvent', readWorkIndex = readWorkIndex', newAuction = newAuction', readAuction = readAuction', recordBid = recordBid', readBids = readBids', createUser = createUser', findUser = findUser', findUserByHandle = findUserByHandle'}
instance ToField PBTC wheretoField (PBTC (BTC i)) = toField irecordEvent' :: LogEntry -> ReaderT Connection IO ()recordEvent' logEntry = do
-- TODO: Record the user idrecordEvent' :: UserId -> LogEntry -> ReaderT Connection IO ()recordEvent' _ logEntry = do
data QDB m a = QDB{ recordEvent :: LogEntry -> ReaderT a m (), readWorkIndex :: ReaderT a m WorkIndex, newAuction :: Auction -> ReaderT a m AuctionId, readAuction :: AuctionId -> ReaderT a m (Maybe Auction), recordBid :: AuctionId -> Bid -> ReaderT a m (), readBids :: AuctionId -> ReaderT a m [Bid], createUser :: User -> ReaderT a m UserId
data QDBUser = QDBUser{ _userId :: UserId, _user :: User}makeLenses ''QDBUserdata QDB m = QDB{ recordEvent :: UserId -> LogEntry -> m (), readWorkIndex :: m WorkIndex, newAuction :: Auction -> m AuctionId, readAuction :: AuctionId -> m (Maybe Auction), recordBid :: AuctionId -> Bid -> m (), readBids :: AuctionId -> m [Bid], createUser :: User -> m UserId, findUser :: UserId -> m (Maybe User), findUserByHandle :: Handle -> m (Maybe QDBUser)
newtype BTC = BTC { _satoshis :: Int64 } deriving (Show, Eq, Ord)makeLenses ''BTCinstance Semigroup BTC where(<>) (BTC b1) (BTC b2) = BTC $ b1 + b2instance Monoid BTC wheremempty = BTC 0mappend = (<>)instance Group BTC whereinvert (BTC b) = BTC . negate $ binstance Abelian BTC where
main :: IO ()main = docfg <- loadQConfig "quixotic.cfg"db <- open $ dbName cfgsconf <- snapConfig cfgsimpleHttpServe sconf $ runReaderT (site sqliteQDB) dbsite :: QDB IO a -> ReaderT a Snap ()site qdb = route[ ("logStart/:btcAddr", logWorkHandler qdb StartWork), ("logEnd/:btcAddr", logWorkHandler qdb StopWork), ("loggedIntervals/:btcAddr", loggedIntervalsHandler qdb), ("payouts", payoutsHandler qdb)]
main :: IO ()main = docfg <- loadQConfig "quixotic.cfg"sconf <- snapConfig cfg--simpleHttpServe sconf $ runReaderT (site sqliteQDB) dbserveSnaplet sconf $ appInit cfgappInit :: QConfig -> SnapletInit App AppappInit QConfig{..} = makeSnaplet "quixotic" "Quixotic Time Tracker" Nothing $ doqdbs <- nestSnaplet "qdb" qdb qdbpgSnapletInitsesss <- nestSnaplet "sessions" sess $initCookieSessionManager (fpToString authSiteKey) "quookie" cookieTimeoutpgs <- nestSnaplet "db" db pgsInitauths <- nestSnaplet "auth" auth $ initPostgresAuth sess pgsaddRoutes [ ("logStart/:btcAddr", logWorkHandler StartWork), ("logEnd/:btcAddr", logWorkHandler StopWork)-- , ("loggedIntervals/:btcAddr", loggedIntervalsHandler qdb)-- , ("payouts", payoutsHandler qdb)]return $ App qdbs sesss pgs auths
logWorkHandler :: QDB IO a -> EventType -> ReaderT a Snap ()logWorkHandler qdb ev = dolet QDB{..} = qdbaddrBytes <- lift $ getParam "btcAddr"timestamp <- lift $ liftIO getCurrentTimemaybe(lift $ snapError 400 "")(\a -> mapReaderT liftIO $ recordEvent (LogEntry a (WorkEvent ev timestamp)))(fmap decodeUtf8 addrBytes >>= parseBtcAddr)
qdbpgSnapletInit :: SnapletInit a PQDBqdbpgSnapletInit = makeSnaplet "qdbpg" "QDB on Postgresql" Nothing $ doreturn postgresQDB
loggedIntervalsHandler :: QDB IO a -> ReaderT a Snap ()loggedIntervalsHandler qdb = dolet QDB{..} = qdbwidx <- mapReaderT liftIO $ readWorkIndexlift . modifyResponse $ addHeader "content-type" "application/json"lift . writeLBS . A.encode $ mapKeys (^. address) widx
logWorkHandler :: EventType -> Handler App App ()logWorkHandler evType = doQDB{..} <- with qdb memptypg <- with db getPostgresStateauthedUser <- with auth currentUserqUid <-addrBytes <- getParam "btcAddr"timestamp <- liftIO getCurrentTimelet workEvent = WorkEvent evType timestampbtcAddr = fmap decodeUtf8 addrBytes >>= parseBtcAddrstoreEv uid addr = runReaderT . recordEvent uid $ LogEntry addr workEventmaybe (snapError 400 "") (liftPG . storeEv) btcAddr
payoutsHandler :: QDB IO a -> ReaderT a Snap ()payoutsHandler qdb = dolet QDB{..} = qdbdep = linearDepreciation (Months 6) (Months 60)ptime <- lift . liftIO $ getCurrentTimewidx <- mapReaderT liftIO $ readWorkIndexlift . modifyResponse $ addHeader "content-type" "application/json"lift . writeLBS . A.encode . PayoutsResponse $ payouts dep ptime widx
--loggedIntervalsHandler :: QDB IO a -> ReaderT a Snap ()--loggedIntervalsHandler qdb = do-- let QDB{..} = qdb-- widx <- mapReaderT liftIO $ readWorkIndex-- lift . modifyResponse $ addHeader "content-type" "application/json"-- lift . writeLBS . A.encode $ mapKeys (^. address) widx----payoutsHandler :: QDB IO a -> ReaderT a Snap ()--payoutsHandler qdb = do-- let QDB{..} = qdb-- dep = linearDepreciation (Months 6) (Months 60)-- ptime <- lift . liftIO $ getCurrentTime-- widx <- mapReaderT liftIO $ readWorkIndex-- lift . modifyResponse $ addHeader "content-type" "application/json"-- lift . writeLBS . A.encode . PayoutsResponse $ payouts dep ptime widx