Refactor QDB to use a free monad algebra instead.
[?]
Jun 13, 2015, 5:15 AM
QMRKFEPGFBCEWAIXPEIG5ILKAJ2JH5L3TOITHR4HNJXK5FN3KXBQCDependencies
- [2]
4U7F3CPITHE GREAT RENAMING OF THINGS! - [3]
2OIPAQCBMerge branch 'master' of github.com:nuttycom/ananke - [4]
Z7KS5XHHVery WIP. Wow. - [5]
IZEVQF62Work in progress replacing sqlite with postgres. - [6]
O5FVTOM6Undo JSON silliness, enable a couple more routes. - [7]
P6NR2CGXBeginning of implementation of depreciation. - [8]
W35DDBFYFactor common JSON conversions up into client lib module. - [9]
64C6AWH6Rename Ananke -> Quixotic, project reboot. - [10]
EZQG2APBUpdate task list. - [11]
GCVQD44VCreate amends endpoint, switch to UUID primary keys - [12]
V2VDN77HEnable postgres configuration via environment variable for Heroku. - [13]
64VI73NPServer now compiles using abstracted SQLite - [14]
Y35QCWYWMinor improvement in WorkIndex type to eliminate duplicated information. - [15]
4QX5E5ACInitial compilation of payouts function succeeds. - [16]
NVOCQVASInitial failing tests. - [17]
XTBSG4C7Adding serveJSON combinator to eliminate some boilerplate from handlers. - [18]
A2J7B4SCInitial impl of depreciation function. - [19]
75N3UJ4JMore progression toward lenses. - [20]
5W5M56VJMove library code to 'lib' - [21]
LD4GLVSFMore database stuff. - [22]
SLL7262CMake depreciation functions more flexible. - [23]
SCXG6TJWMake log reduction safer in presence of overlapping events. - [24]
BROSTG5KBeginning of modularization of server. - [25]
TZQJVHBAAdd auction functions to ADB. - [26]
WFZDMVUXRename ADB -> QDB - [27]
NTPC7KJETrivial changes, feature scratchpad. - [28]
LAROLAYUWIP - [29]
VJPT6HDRFix remaining type errors after addition of login handler. - [30]
GKGVYBZGAdded JSON serialization to TimeLog - [31]
TLQ72DSJLenses, sqlite-simple - [32]
JKMHA2QGSQLite support is now relatively sane. - [33]
2Y2QZFVFSwitch to more modern cabal2nix-based workflow. - [34]
TNR3TEHKSwitch to Postgres + snaplet arch compiles. - [35]
BXGLKYRXAdded primitive user registration handler. - [36]
5XFJNUAZStart of addition of project infrastructure. - [37]
I2KHGVD4Require project permissions for access to most data. - [38]
4IQVQL4TAdded client for payouts endpoint. - [39]
PBD7LZYQPostgres & auth are beginning to function. - [40]
2G3GNDDUEvent logging is now functioning in postgres. - [41]
A6HKMINBAttempting to improve JSON handling. - [42]
FD7SV5I6Fix handling of event_t columns. - [43]
N4NDAZYTInitial implementation of payouts. - [44]
7KZP4RHZSwitch from Data.Time to Data.Thyme - [45]
5DRIWGLUImproving TimeLog specs - [46]
EMVTF2IWWIP moving back to snap. - [47]
KNSI575VCleanup of EventLog types. - [48]
RPAJLHMTChange to use UUIDs instead of ints for primary keys. - [*]
ADMKQQGCInitial empty Snap project.
Change contents
- edit in aftok.cabal at line 30
Aftok.Util - edit in aftok.cabal at line 45
, free - edit in aftok.cabal at line 49
, kan-extensions - replacement in lib/Aftok/Database/PostgreSQL.hs at line 1
{-# LANGUAGE TemplateHaskell #-}{-# LANGUAGE GADTs, GeneralizedNewtypeDeriving #-} - replacement in lib/Aftok/Database/PostgreSQL.hs at line 3
module Aftok.Database.PostgreSQL (postgresQDB) wheremodule Aftok.Database.PostgreSQL (QDBM(..)) where - edit in lib/Aftok/Database/PostgreSQL.hs at line 8
import Data.Aeson(toJSON) - replacement in lib/Aftok/Database/PostgreSQL.hs at line 28
type QDBM = ReaderT Connection IOnewtype QDBM a = QDBM { runQDBM :: ReaderT Connection IO a }deriving (Functor, Applicative, Monad) - edit in lib/Aftok/Database/PostgreSQL.hs at line 49
eidParser :: FieldParser EventIdeidParser f v = EventId <$> fromField f v - replacement in lib/Aftok/Database/PostgreSQL.hs at line 89
qdbLogEntryParser :: RowParser QDBLogEntryqdbLogEntryParser :: RowParser KeyedLogEntry - replacement in lib/Aftok/Database/PostgreSQL.hs at line 91
(,,,) <$> fieldWith eidParser<*> fieldWith pidParser(,,) <$> fieldWith pidParser - replacement in lib/Aftok/Database/PostgreSQL.hs at line 113
qdbUserParser :: RowParser QDBUserqdbUserParser :: RowParser KeyedUser - edit in lib/Aftok/Database/PostgreSQL.hs at line 123
<*> fieldWith fromJSONField - replacement in lib/Aftok/Database/PostgreSQL.hs at line 125
qdbProjectParser :: RowParser QDBProjectqdbProjectParser :: RowParser KeyedProject - replacement in lib/Aftok/Database/PostgreSQL.hs at line 131
pexec q d = dopexec q d = QDBM $ do - replacement in lib/Aftok/Database/PostgreSQL.hs at line 136
pinsert f q d = dopinsert f q d = QDBM $ do - replacement in lib/Aftok/Database/PostgreSQL.hs at line 142
pquery p q d = dopquery p q d = QDBM $ do - replacement in lib/Aftok/Database/PostgreSQL.hs at line 146[3.1362]→[3.295:359](∅→∅),[3.359]→[3.1726:1806](∅→∅),[3.1806]→[3.1510:1666](∅→∅),[3.1510]→[3.1510:1666](∅→∅),[3.1666]→[3.523:538](∅→∅),[3.3644]→[3.523:538](∅→∅),[3.523]→[3.523:538](∅→∅),[3.538]→[3.3645:3665](∅→∅),[3.3665]→[3.1025:1043](∅→∅),[3.1043]→[3.225:258](∅→∅),[3.258]→[3.1073:1081](∅→∅),[3.1073]→[3.1073:1081](∅→∅),[3.1081]→[3.3399:3405](∅→∅),[3.3687]→[3.3399:3405](∅→∅),[3.3399]→[3.3399:3405](∅→∅)
createEvent' :: ProjectId -> UserId -> LogEntry -> QDBM EventIdcreateEvent' (ProjectId pid) (UserId uid) (LogEntry a e m) =pinsert EventId"INSERT INTO work_events (project_id, user_id, btc_addr, event_type, event_time, event_metadata) \\VALUES (?, ?, ?, ?, ?, ?) \\RETURNING id"( pid, uid, a ^. _BtcAddr, eventName e, fromThyme $ e ^. eventTime, m)instance DBEval QDBM wheredbEval (CreateEvent (ProjectId pid) (UserId uid) (LogEntry a e m)) =pinsert EventId"INSERT INTO work_events (project_id, user_id, btc_addr, event_type, event_time, event_metadata) \\VALUES (?, ?, ?, ?, ?, ?) \\RETURNING id"( pid, uid, a ^. _BtcAddr, eventName e, fromThyme $ e ^. eventTime, m) - replacement in lib/Aftok/Database/PostgreSQL.hs at line 159
findEvent' :: EventId -> QDBM (Maybe QDBLogEntry)findEvent' (EventId eid) = dologEntries <- pquery qdbLogEntryParser"SELECT id, project_id, user_id, btc_addr, event_type, event_time, event_metadata FROM work_events \\WHERE id = ?"(Only eid)pure $ headMay logEntriesdbEval (FindEvent (EventId eid)) = dologEntries <- pquery qdbLogEntryParser"SELECT project_id, user_id, btc_addr, event_type, event_time, event_metadata FROM work_events \\WHERE id = ?"(Only eid)pure $ headMay logEntries - replacement in lib/Aftok/Database/PostgreSQL.hs at line 166[3.3406]→[3.259:374](∅→∅),[3.374]→[3.2098:2130](∅→∅),[3.2130]→[3.402:585](∅→∅),[3.402]→[3.402:585](∅→∅),[3.585]→[3.2131:2165](∅→∅),[3.2165]→[3.615:841](∅→∅),[3.615]→[3.615:841](∅→∅),[3.841]→[3.2166:2197](∅→∅),[3.2197]→[3.868:1051](∅→∅),[3.868]→[3.868:1051](∅→∅),[3.1051]→[3.2198:2226](∅→∅)
findEvents' :: ProjectId -> UserId -> Interval' -> QDBM [LogEntry]findEvents' (ProjectId pid) (UserId uid) ival =let q p (Before e) = pquery p"SELECT btc_addr, event_type, event_time, event_metadata FROM work_events \\WHERE project_id = ? AND user_id = ? AND event_time <= ?"(pid, uid, PUTCTime e)q p (During s e) = pquery p"SELECT btc_addr, event_type, event_time, event_metadata FROM work_events \\WHERE project_id = ? AND user_id = ? \\AND event_time >= ? AND event_time <= ?"(pid, uid, PUTCTime s, PUTCTime e)q p (After s) = pquery p"SELECT btc_addr, event_type, event_time, event_metadata FROM work_events \\WHERE project_id = ? AND user_id = ? AND event_time >= ?"(pid, uid, PUTCTime s)in q logEntryParser ivaldbEval (FindEvents (ProjectId pid) (UserId uid) ival) =let q p (Before e) = pquery p"SELECT btc_addr, event_type, event_time, event_metadata FROM work_events \\WHERE project_id = ? AND user_id = ? AND event_time <= ?"(pid, uid, PUTCTime e)q p (During s e) = pquery p"SELECT btc_addr, event_type, event_time, event_metadata FROM work_events \\WHERE project_id = ? AND user_id = ? \\AND event_time >= ? AND event_time <= ?"(pid, uid, PUTCTime s, PUTCTime e)q p (After s) = pquery p"SELECT btc_addr, event_type, event_time, event_metadata FROM work_events \\WHERE project_id = ? AND user_id = ? AND event_time >= ?"(pid, uid, PUTCTime s)in q logEntryParser ival - replacement in lib/Aftok/Database/PostgreSQL.hs at line 182[3.1084]→[3.2227:2288](∅→∅),[3.2288]→[3.1768:1815](∅→∅),[3.1768]→[3.1768:1815](∅→∅),[3.1815]→[3.2289:2414](∅→∅),[3.2414]→[3.264:317](∅→∅),[3.1920]→[3.264:317](∅→∅)
amendEvent' :: EventId -> EventAmendment -> QDBM AmendmentIdamendEvent' (EventId eid) (TimeChange mt t) =pinsert AmendmentId"INSERT INTO event_time_amendments (event_id, mod_time, event_time) VALUES (?, ?, ?) RETURNING id"( eid, fromThyme $ mt ^. _ModTime, fromThyme t )dbEval (AmendEvent (EventId eid) (TimeChange mt t)) =pinsert AmendmentId"INSERT INTO event_time_amendments (event_id, mod_time, event_time) VALUES (?, ?, ?) RETURNING id"( eid, fromThyme $ mt ^. _ModTime, fromThyme t ) - replacement in lib/Aftok/Database/PostgreSQL.hs at line 187[3.1952]→[3.1952:2005](∅→∅),[3.2005]→[3.2415:2538](∅→∅),[3.2538]→[3.318:376](∅→∅),[3.2108]→[3.318:376](∅→∅)
amendEvent' (EventId eid) (AddressChange mt addr) =pinsert AmendmentId"INSERT INTO event_addr_amendments (event_id, mod_time, btc_addr) VALUES (?, ?, ?) RETURNING id"( eid, fromThyme $ mt ^. _ModTime, addr ^. _BtcAddr )dbEval (AmendEvent (EventId eid) (AddressChange mt addr)) =pinsert AmendmentId"INSERT INTO event_addr_amendments (event_id, mod_time, btc_addr) VALUES (?, ?, ?) RETURNING id"( eid, fromThyme $ mt ^. _ModTime, addr ^. _BtcAddr ) - replacement in lib/Aftok/Database/PostgreSQL.hs at line 192[3.2157]→[3.2157:2208](∅→∅),[3.2208]→[3.2539:2708](∅→∅),[3.420]→[3.2346:2393](∅→∅),[3.2708]→[3.2346:2393](∅→∅),[3.2346]→[3.2346:2393](∅→∅),[3.2393]→[3.602:626](∅→∅),[3.602]→[3.602:626](∅→∅),[3.626]→[3.2709:2747](∅→∅),[3.2747]→[3.1085:1186](∅→∅),[3.2411]→[3.1085:1186](∅→∅),[3.405]→[3.740:762](∅→∅),[3.1186]→[3.740:762](∅→∅),[3.740]→[3.740:762](∅→∅),[3.762]→[3.2748:2778](∅→∅),[3.2778]→[3.3624:3625](∅→∅),[3.3624]→[3.3624:3625](∅→∅),[3.3625]→[3.425:482](∅→∅),[3.482]→[3.2779:2926](∅→∅),[3.2926]→[3.954:1029](∅→∅),[3.954]→[3.954:1029](∅→∅),[3.3928]→[3.3928:3929](∅→∅),[3.3929]→[3.511:585](∅→∅),[3.585]→[3.2927:3026](∅→∅),[3.3026]→[3.4129:4162](∅→∅),[3.4129]→[3.4129:4162](∅→∅),[3.4162]→[3.3027:3053](∅→∅),[3.3053]→[3.4200:4201](∅→∅),[3.4200]→[3.4200:4201](∅→∅),[3.4201]→[3.3054:3099](∅→∅),[3.3099]→[3.628:666](∅→∅),[3.628]→[3.628:666](∅→∅),[3.666]→[3.3100:3241](∅→∅),[3.159]→[3.4444:4597](∅→∅),[3.3241]→[3.4444:4597](∅→∅),[3.4444]→[3.4444:4597](∅→∅)
amendEvent' (EventId eid) (MetadataChange mt v) =pinsert AmendmentId"INSERT INTO event_metadata_amendments (event_id, mod_time, btc_addr) VALUES (?, ?, ?) RETURNING id"( eid, fromThyme $ mt ^. _ModTime, v)readWorkIndex' :: ProjectId -> QDBM WorkIndexreadWorkIndex' pid = dologEntries <- pquery logEntryParser"SELECT btc_addr, event_type, event_time, event_metadata FROM work_events WHERE project_id = ?"(Only $ PPid pid)pure $ workIndex logEntriescreateAuction' :: ProjectId -> Auction -> QDBM AuctionIdcreateAuction' pid auc =pinsert AuctionId"INSERT INTO auctions (project_id, raise_amount, end_time) \\VALUES (?, ?, ?) RETURNING id"(pid ^. (_ProjectId), auc ^. (raiseAmount.to PBTC), auc ^. auctionEnd)findAuction' :: AuctionId -> QDBM (Maybe Auction)findAuction' aucId = doauctions <- pquery auctionParser"SELECT raise_amount, end_time FROM auctions WHERE id = ?"(Only (aucId ^. _AuctionId))pure $ headMay auctionscreateBid' :: AuctionId -> Bid -> QDBM BidIdcreateBid' (AuctionId aucId) bid = dopinsert BidId"INSERT INTO bids (auction_id, bidder_id, bid_seconds, bid_amount, bid_time) \\VALUES (?, ?, ?, ?, ?) RETURNING id"( aucId, bid ^. (bidUser._UserId), case bid ^. bidSeconds of (Seconds i) -> i, bid ^. (bidAmount.to PBTC), bid ^. bidTime)dbEval (AmendEvent (EventId eid) (MetadataChange mt v)) =pinsert AmendmentId"INSERT INTO event_metadata_amendments (event_id, mod_time, btc_addr) VALUES (?, ?, ?) RETURNING id"( eid, fromThyme $ mt ^. _ModTime, v) - replacement in lib/Aftok/Database/PostgreSQL.hs at line 197[3.4598]→[3.2615:2652](∅→∅),[3.2652]→[3.3242:3280](∅→∅),[3.2670]→[3.4715:4837](∅→∅),[3.3280]→[3.4715:4837](∅→∅),[3.4715]→[3.4715:4837](∅→∅)
readBids' :: AuctionId -> QDBM [Bid]readBids' aucId =pquery bidParser"SELECT user_id, bid_seconds, bid_amount, bid_time FROM bids WHERE auction_id = ?"(Only $ (aucId ^. _AuctionId))dbEval (ReadWorkIndex pid) = dologEntries <- pquery logEntryParser"SELECT btc_addr, event_type, event_time, event_metadata FROM work_events WHERE project_id = ?"(Only $ PPid pid)pure $ workIndex logEntries - replacement in lib/Aftok/Database/PostgreSQL.hs at line 203[3.4862]→[3.2671:2706](∅→∅),[3.2706]→[3.3281:3319](∅→∅),[3.2724]→[3.160:240](∅→∅),[3.3319]→[3.160:240](∅→∅),[3.4978]→[3.160:240](∅→∅),[3.240]→[3.3688:3777](∅→∅)
createUser' :: User -> QDBM UserIdcreateUser' user' =pinsert UserId"INSERT INTO users (handle, btc_addr, email) VALUES (?, ?, ?) RETURNING id"(user' ^. (username._UserName), user' ^. (userAddress._BtcAddr), user' ^. userEmail)dbEval (CreateAuction pid auc) =pinsert AuctionId"INSERT INTO auctions (project_id, raise_amount, end_time) \\VALUES (?, ?, ?) RETURNING id"(pid ^. (_ProjectId), auc ^. (raiseAmount.to PBTC), auc ^. auctionEnd) - replacement in lib/Aftok/Database/PostgreSQL.hs at line 209[3.5183]→[3.2725:2766](∅→∅),[3.2766]→[3.5241:5269](∅→∅),[3.5241]→[3.5241:5269](∅→∅),[3.5269]→[3.3320:3349](∅→∅),[3.2785]→[3.5312:5388](∅→∅),[3.3349]→[3.5312:5388](∅→∅),[3.5312]→[3.5312:5388](∅→∅),[3.5388]→[3.3350:3373](∅→∅)
findUser' :: UserId -> QDBM (Maybe User)findUser' (UserId uid) = dousers <- pquery userParser"SELECT handle, btc_addr, email FROM users WHERE id = ?"(Only uid)pure $ headMay usersdbEval (FindAuction aucId) = doauctions <- pquery auctionParser"SELECT raise_amount, end_time FROM auctions WHERE id = ?"(Only (aucId ^. _AuctionId))pure $ headMay auctions - replacement in lib/Aftok/Database/PostgreSQL.hs at line 215[3.5425]→[3.2786:2842](∅→∅),[3.2842]→[3.501:539](∅→∅),[3.501]→[3.501:539](∅→∅),[3.539]→[3.3374:3406](∅→∅),[3.2861]→[3.582:664](∅→∅),[3.3406]→[3.582:664](∅→∅),[3.582]→[3.582:664](∅→∅),[3.664]→[3.3407:3430](∅→∅)
findUserByUserName' :: UserName -> QDBM (Maybe QDBUser)findUserByUserName' (UserName h) = dousers <- pquery qdbUserParser"SELECT id, handle, btc_addr, email FROM users WHERE handle = ?"(Only h)pure $ headMay usersdbEval (CreateBid (AuctionId aucId) bid) =pinsert BidId"INSERT INTO bids (auction_id, bidder_id, bid_seconds, bid_amount, bid_time) \\VALUES (?, ?, ?, ?, ?) RETURNING id"( aucId, bid ^. (bidUser._UserId), case bid ^. bidSeconds of (Seconds i) -> i, bid ^. (bidAmount.to PBTC), bid ^. bidTime) - replacement in lib/Aftok/Database/PostgreSQL.hs at line 226[3.5525]→[3.2862:2906](∅→∅),[3.2906]→[3.683:705](∅→∅),[3.683]→[3.683:705](∅→∅),[3.705]→[3.4:41](∅→∅),[3.41]→[3.3431:3458](∅→∅),[3.2924]→[3.747:849](∅→∅),[3.3458]→[3.747:849](∅→∅),[3.747]→[3.747:849](∅→∅),[3.849]→[3.42:90](∅→∅),[3.90]→[3.3459:3588](∅→∅)
createProject' :: Project -> QDBM ProjectIdcreateProject' p = dolet uid = p ^. (initiator._UserId)pid <- pinsert ProjectId"INSERT INTO projects (project_name, inception_date, initiator_id) VALUES (?, ?, ?) RETURNING id"(p ^. projectName, p ^. inceptionDate, uid)void $ pexec"INSERT INTO project_companions (project_id, user_id) VALUES (?, ?)"(pid ^. _ProjectId, uid)pure piddbEval (ReadBids aucId) =pquery bidParser"SELECT user_id, bid_seconds, bid_amount, bid_time FROM bids WHERE auction_id = ?"(Only $ (aucId ^. _AuctionId)) - replacement in lib/Aftok/Database/PostgreSQL.hs at line 231[3.668]→[3.668:752](∅→∅),[3.752]→[3.3589:3624](∅→∅),[3.3624]→[3.773:871](∅→∅),[3.773]→[3.773:871](∅→∅),[3.871]→[3.3625:3651](∅→∅)
findProject' :: ProjectId -> QDBM (Maybe Project)findProject' (ProjectId pid) = doprojects <- pquery projectParser"SELECT project_name, inception_date, initiator_id FROM projects WHERE id = ?"(Only pid)pure $ headMay projectsdbEval (CreateUser user') =pinsert UserId"INSERT INTO users (handle, btc_addr, email) VALUES (?, ?, ?) RETURNING id"(user' ^. (username._UserName), user' ^. (userAddress._BtcAddr), user' ^. userEmail) - replacement in lib/Aftok/Database/PostgreSQL.hs at line 236[3.705]→[3.2941:2990](∅→∅),[3.2990]→[3.3652:3712](∅→∅),[3.3011]→[3.1111:1180](∅→∅),[3.3712]→[3.1111:1180](∅→∅),[3.1111]→[3.1111:1180](∅→∅),[3.1180]→[3.274:348](∅→∅),[3.348]→[3.3713:3740](∅→∅),[3.3740]→[3.380:395](∅→∅),[3.380]→[3.380:395](∅→∅)
findUserProjects' :: UserId -> QDBM [QDBProject]findUserProjects' (UserId uid) =pquery qdbProjectParser"SELECT p.id, p.project_name, p.inception_date, p.initiator_id \\FROM projects p JOIN project_companions pc ON pc.project_id = p.id \\WHERE pc.user_id = ?"(Only uid)dbEval (FindUser (UserId uid)) = dousers <- pquery userParser"SELECT handle, btc_addr, email FROM users WHERE id = ?"(Only uid)pure $ headMay users - edit in lib/Aftok/Database/PostgreSQL.hs at line 242
dbEval (FindUserByName (UserName h)) = dousers <- pquery qdbUserParser"SELECT id, handle, btc_addr, email FROM users WHERE handle = ?"(Only h)pure $ headMay users - replacement in lib/Aftok/Database/PostgreSQL.hs at line 248[3.3742]→[3.3012:3036](∅→∅),[3.1369]→[3.3012:3036](∅→∅),[3.3036]→[3.5568:5587](∅→∅),[3.5568]→[3.5568:5587](∅→∅),[3.5587]→[3.914:945](∅→∅),[3.945]→[3.3037:3066](∅→∅),[3.5618]→[3.3037:3066](∅→∅),[3.3066]→[3.3743:3770](∅→∅),[3.3770]→[3.1187:1216](∅→∅),[3.3066]→[3.1187:1216](∅→∅),[3.1216]→[3.5618:5654](∅→∅),[3.3066]→[3.5618:5654](∅→∅),[3.5618]→[3.5618:5654](∅→∅)
postgresQDB :: QDB QDBMpostgresQDB = QDB{ createEvent = createEvent', amendEvent = amendEvent', findEvent = findEvent', findEvents = findEvents', readWorkIndex = readWorkIndex'dbEval (CreateProject p) =pinsert ProjectId"INSERT INTO projects (project_name, inception_date, initiator_id, depreciation_fn) \\VALUES (?, ?, ?, ?) RETURNING id"(p ^. projectName, p ^. inceptionDate, p ^. (initiator._UserId), toJSON $ p ^. depf) - replacement in lib/Aftok/Database/PostgreSQL.hs at line 254
, createAuction = createAuction', findAuction = findAuction'dbEval (FindProject (ProjectId pid)) = doprojects <- pquery projectParser"SELECT project_name, inception_date, initiator_id FROM projects WHERE id = ?"(Only pid)pure $ headMay projects - replacement in lib/Aftok/Database/PostgreSQL.hs at line 260
, createBid = createBid', readBids = readBids'dbEval (FindUserProjects (UserId uid)) =pquery qdbProjectParser"SELECT p.id, p.project_name, p.inception_date, p.initiator_id \\FROM projects p JOIN project_companions pc ON pc.project_id = p.id \\WHERE pc.user_id = ? \\UNION \\SELECT p.id, p.project_name, p.inception_date, p.initiator_id \\FROM projects p \\WHERE p.initiator_id = ?"(uid, uid) - replacement in lib/Aftok/Database/PostgreSQL.hs at line 271
, createUser = createUser', findUser = findUser', findUserByUserName = findUserByUserName'dbEval (AddUserToProject pid current new) = dovoid $ pexec"INSERT INTO project_companions (project_id, user_id, invited_by) VALUES (?, ?, ?)"(pid ^. _ProjectId, new ^. _UserId, current ^. _UserId) - replacement in lib/Aftok/Database/PostgreSQL.hs at line 276[3.1045]→[3.1371:1406](∅→∅),[3.751]→[3.1371:1406](∅→∅),[3.1406]→[3.1046:1077](∅→∅),[3.1077]→[3.1406:1447](∅→∅),[3.1406]→[3.1406:1447](∅→∅),[3.751]→[3.5861:5865](∅→∅),[3.1447]→[3.5861:5865](∅→∅),[3.5861]→[3.5861:5865](∅→∅)
, createProject = createProject', findProject = findProject', findUserProjects = findUserProjects'}[3.1045]-- FIXME, these are just placeholdersdbEval (OpForbidden _ reason _) = fail $ show reasondbEval (SubjectNotFound _) = fail "Subject of operation was not found." - replacement in lib/Aftok/Database.hs at line 1
{-# LANGUAGE TemplateHaskell #-}{-# LANGUAGE GADTs #-} - edit in lib/Aftok/Database.hs at line 6
import Control.Lens - edit in lib/Aftok/Database.hs at line 12
import Aftok.Utiltype KeyedUser = (UserId, User)type KeyedLogEntry = (ProjectId, UserId, LogEntry)type KeyedProject = (ProjectId, Project)type InvitingUID = UserIdtype InvitedUID = UserIdtype DBProg a = Program DBOp adata DBOp a whereCreateUser :: User -> DBOp UserIdFindUser :: UserId -> DBOp (Maybe User)FindUserByName :: UserName -> DBOp (Maybe KeyedUser)CreateProject :: Project -> DBOp ProjectIdFindProject :: ProjectId -> DBOp (Maybe Project)FindUserProjects :: UserId -> DBOp [KeyedProject]AddUserToProject :: ProjectId -> InvitingUID -> InvitedUID -> DBOp ()CreateEvent :: ProjectId -> UserId -> LogEntry -> DBOp EventIdAmendEvent :: EventId -> EventAmendment -> DBOp AmendmentIdFindEvent :: EventId -> DBOp (Maybe KeyedLogEntry)FindEvents :: ProjectId -> UserId -> Interval' -> DBOp [LogEntry]ReadWorkIndex :: ProjectId -> DBOp WorkIndexCreateAuction :: ProjectId -> Auction -> DBOp AuctionIdFindAuction :: AuctionId -> DBOp (Maybe Auction)CreateBid :: AuctionId -> Bid -> DBOp BidIdReadBids :: AuctionId -> DBOp [Bid]OpForbidden :: forall x. UserId -> OpForbiddenReason -> DBOp x -> DBOp xSubjectNotFound :: forall x. DBOp x -> DBOp xdata OpForbiddenReason = UserNotProjectMember| UserNotEventLoggerderiving (Eq, Show)class DBEval m wheredbEval :: DBOp a -> m a-- User opscreateUser :: User -> DBProg UserIdcreateUser = fc . CreateUserfindUser :: UserId -> DBProg (Maybe User)findUser = fc . FindUser - replacement in lib/Aftok/Database.hs at line 61
type QDBUser = (UserId, User)type QDBLogEntry = (EventId, ProjectId, UserId, LogEntry)type QDBProject = (ProjectId, Project)findUserByName :: UserName -> DBProg (Maybe KeyedUser)findUserByName = fc . FindUserByName-- Project opscreateProject :: Project -> DBProg ProjectIdcreateProject p = dopid <- fc $ CreateProject paddUserToProject pid (p ^. initiator) (p ^. initiator)return pidfindProject :: ProjectId -> UserId -> DBProg (Maybe Project)findProject pid uid = dokps <- findUserProjects uidpure $ fmap snd (find (\(pid', _) -> pid' == pid) kps)findUserProjects :: UserId -> DBProg [KeyedProject]findUserProjects = fc . FindUserProjectsaddUserToProject :: ProjectId -> InvitingUID -> InvitedUID -> DBProg ()addUserToProject pid current new =withProjectAuth pid current $ AddUserToProject pid current newwithProjectAuth :: ProjectId -> UserId -> DBOp a -> DBProg awithProjectAuth pid uid act = dopx <- findUserProjects uidfc $ if any (\(pid', _) -> pid' == pid) pxthen actelse OpForbidden uid UserNotProjectMember act - replacement in lib/Aftok/Database.hs at line 91[3.6669]→[3.6669:6687](∅→∅),[3.6687]→[3.1079:1145](∅→∅),[3.1145]→[3.3905:4023](∅→∅),[3.4023]→[3.1244:1314](∅→∅),[3.3190]→[3.1244:1314](∅→∅),[3.1314]→[3.296:342](∅→∅),[3.3190]→[3.296:342](∅→∅),[3.296]→[3.296:342](∅→∅)
data QDB m = QDB{ createEvent :: ProjectId -> UserId -> LogEntry -> m EventId, amendEvent :: EventId -> EventAmendment -> m AmendmentId, findEvent :: EventId -> m (Maybe QDBLogEntry), findEvents :: ProjectId -> UserId -> Interval' -> m [LogEntry], readWorkIndex :: ProjectId -> m WorkIndex-- Log ops-- TODO: ignore "duplicate" events within some small time limit?createEvent :: ProjectId -> UserId -> LogEntry -> DBProg EventIdcreateEvent p u l = withProjectAuth p u $ CreateEvent p u lamendEvent :: UserId -> EventId -> EventAmendment -> DBProg AmendmentIdamendEvent uid eid a = doev <- findEvent eidlet act = AmendEvent eid aforbidden = OpForbidden uid UserNotEventLogger actmissing = SubjectNotFound actfc $ maybe missing (\(_, uid', _) -> if uid' == uid then act else forbidden) ev - replacement in lib/Aftok/Database.hs at line 105
, createAuction :: ProjectId -> Auction -> m AuctionId, findAuction :: AuctionId -> m (Maybe Auction)findEvent :: EventId -> DBProg (Maybe KeyedLogEntry)findEvent = fc . FindEvent - replacement in lib/Aftok/Database.hs at line 108[3.4025]→[3.4025:4074](∅→∅),[3.1302]→[3.1598:1640](∅→∅),[3.4074]→[3.1598:1640](∅→∅),[3.1598]→[3.1598:1640](∅→∅)
, createBid :: AuctionId -> Bid -> m BidId, readBids :: AuctionId -> m [Bid]findEvents :: ProjectId -> UserId -> Interval' -> DBProg [LogEntry]findEvents p u i = fc $ FindEvents p u i - replacement in lib/Aftok/Database.hs at line 111[3.1304]→[3.578:662](∅→∅),[3.1640]→[3.578:662](∅→∅),[3.578]→[3.578:662](∅→∅),[3.662]→[3.799:855](∅→∅),[3.7012]→[3.799:855](∅→∅)
, createUser :: User -> m UserId, findUser :: UserId -> m (Maybe User), findUserByUserName :: UserName -> m (Maybe QDBUser)readWorkIndex :: ProjectId -> UserId -> DBProg WorkIndexreadWorkIndex pid uid = withProjectAuth pid uid $ ReadWorkIndex pid - edit in lib/Aftok/Database.hs at line 114[3.1306]→[3.1449:1493](∅→∅),[3.855]→[3.1449:1493](∅→∅),[3.1493]→[3.1307:1359](∅→∅),[3.1359]→[3.1641:1690](∅→∅),[3.1493]→[3.1641:1690](∅→∅),[3.855]→[3.1641:1690](∅→∅),[3.855]→[3.5159:5163](∅→∅),[3.1688]→[3.5159:5163](∅→∅),[3.1690]→[3.5159:5163](∅→∅),[3.1725]→[3.5159:5163](∅→∅),[3.5930]→[3.5159:5163](∅→∅),[3.7064]→[3.5159:5163](∅→∅),[3.5159]→[3.5159:5163](∅→∅)
, createProject :: Project -> m ProjectId, findProject :: ProjectId -> m (Maybe Project), findUserProjects :: UserId -> m [QDBProject]} - replacement in lib/Aftok/Json.hs at line 82
qdbProjectJSON :: QDBProject -> ValueqdbProjectJSON :: KeyedProject -> Value - replacement in lib/Aftok/TimeLog.hs at line 10
, DepF, DepF, toDepF - edit in lib/Aftok/TimeLog.hs at line 15[3.12]→[3.5326:5345](∅→∅),[3.5608]→[3.5326:5345](∅→∅),[3.6623]→[3.5326:5345](∅→∅),[3.24]→[3.5326:5345](∅→∅)
, Months(Months) - edit in lib/Aftok/TimeLog.hs at line 98
toDepF :: DepreciationFunction -> DepFtoDepF (LinearDepreciation undepLength depLength) = linearDepreciation undepLength depLength - replacement in lib/Aftok/TimeLog.hs at line 106
workCredit depf ptime ivals = getSum $ F.foldMap (Sum . depf ptime) ivalsworkCredit df ptime ivals = getSum $ F.foldMap (Sum . df ptime) ivals - edit in lib/Aftok/TimeLog.hs at line 164
newtype Months = Months Integer - replacement in lib/Aftok/TimeLog.hs at line 167
linearDepreciation :: Months -> -- ^ The number of initial months during which no depreciation occursMonths -> -- ^ The number of months over which each logged interval will be depreciatedDepFlinearDepreciation undepPeriod depPeriod =linearDepreciation :: Months -- ^ The number of initial months during which no depreciation occurs-> Months -- ^ The number of months over which each logged interval will be depreciated-> DepF -- ^ The resulting configured depreciation function.linearDepreciation undepLength depLength = - replacement in lib/Aftok/TimeLog.hs at line 175
maxDepreciable = monthsLength undepPeriod ^+^ monthsLength depPeriodmaxDepreciable = monthsLength undepLength ^+^ monthsLength depLength - replacement in lib/Aftok/TimeLog.hs at line 179
if dt < monthsLength undepPeriod then 1if dt < monthsLength undepLength then 1 - file addition: Util.hs[2.679]
{-# LANGUAGE RankNTypes #-}module Aftok.Util whereimport ClassyPreludeimport Control.Monad.Free.Churchimport Data.Functor.Coyonedatype Program f a = F (Coyoneda f) a-- Shouldn't this exist already in a library somewhere?interpret :: Monad m => (forall x. f x -> m x) -> Program f a -> m ainterpret nt p =let eval (Coyoneda cf cm) = nt cm >>= cfin iterM eval pfc :: f a -> Program f afc = liftF . liftCoyoneda - replacement in lib/Aftok.hs at line 1
{-# LANGUAGE NoImplicitPrelude, TemplateHaskell #-}{-# LANGUAGE NoImplicitPrelude, TemplateHaskell, DeriveDataTypeable #-} - replacement in lib/Aftok.hs at line 7
import Control.Lensimport Control.Lens(makePrisms, makeLenses)import Data.Aesonimport Data.Aeson.Typesimport Data.Data - edit in lib/Aftok.hs at line 19
newtype Months = Months Integerderiving (Eq, Show, Data, Typeable) - edit in lib/Aftok.hs at line 23
data DepreciationFunction = LinearDepreciation Months Monthsderiving (Eq, Show, Data, Typeable) - edit in lib/Aftok.hs at line 46
, _depf :: DepreciationFunction - edit in lib/Aftok.hs at line 68[3.3595]
-- | 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 .: "text" :: 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 - replacement in server/Aftok/Snaplet/Auth.hs at line 6
import Control.Monad.State-- import Control.Monad.State - replacement in server/Aftok/Snaplet/Auth.hs at line 16
import Snap.Snaplet.PostgresqlSimple-- import Snap.Snaplet.PostgresqlSimple - edit in server/Aftok/Snaplet/Auth.hs at line 34
QDB{..} <- view qdb <$> with qm get - replacement in server/Aftok/Snaplet/Auth.hs at line 35
qdbUser <- liftPG . runReaderT $ findUserByUserName currentUserqdbUser <- snapEval $ findUserByName currentUser - replacement in server/Aftok/Snaplet/Auth.hs at line 40
requireProjectAccess :: Handler App App (UserId, ProjectId)requireProjectAccess = doQDB{..} <- view qdb <$> with qm getrequireProjectId :: Handler App App ProjectIdrequireProjectId = do - replacement in server/Aftok/Snaplet/Auth.hs at line 44[3.2346]→[3.2346:2445](∅→∅),[3.2445]→[3.2101:2120](∅→∅),[3.2120]→[3.4107:4134](∅→∅),[3.4134]→[3.2120:2181](∅→∅),[3.2120]→[3.2120:2181](∅→∅),[3.2181]→[3.8101:8146](∅→∅),[3.8146]→[3.4135:4164](∅→∅),[3.2233]→[3.4135:4164](∅→∅),[3.4164]→[3.2255:2361](∅→∅),[3.2255]→[3.2255:2361](∅→∅)
Nothing -> snapError 403 "Value of parameter projectId could not be parsed to a valid value."Just pid -> douid <- requireUserIdprojects <- liftPG . runReaderT $ findUserProjects uidif any (\p -> p ^. _1 == pid) projectsthen pure (uid, pid)else snapError 403 $ "User " ++ (tshow uid) ++ " does not have access to project " ++ (tshow pid)Nothing -> snapError 400 "Value of parameter projectId could not be parsed to a valid value."Just pid -> pure pid - edit in server/Aftok/Snaplet/Projects.hs at line 7
import Control.Lensimport Control.Monad.State - edit in server/Aftok/Snaplet/Projects.hs at line 15
import Snap.Snaplet.PostgresqlSimple - replacement in server/Aftok/Snaplet/Projects.hs at line 16
data CreateProject = CreateProject { createProjectName :: Text }data CProject = CP { cpn :: Text, cpdepf :: DepreciationFunction } - replacement in server/Aftok/Snaplet/Projects.hs at line 18
instance FromJSON CreateProject whereparseJSON (Object v) = CreateProject <$> v .: "projectName"instance FromJSON CProject whereparseJSON (Object v) = CP <$> v .: "projectName" <*> v .: "depf" - edit in server/Aftok/Snaplet/Projects.hs at line 24
QDB{..} <- view qdb <$> with qm get - replacement in server/Aftok/Snaplet/Projects.hs at line 28
liftPG . runReaderT . createProject $ Project (createProjectName cp) timestamp uidsnapEval . createProject $ Project (cpn cp) timestamp uid (cpdepf cp) - replacement in server/Aftok/Snaplet/Projects.hs at line 30
projectListHandler :: Handler App App [QDBProject]projectListHandler :: Handler App App [KeyedProject] - edit in server/Aftok/Snaplet/Projects.hs at line 32
QDB{..} <- view qdb <$> with qm get - replacement in server/Aftok/Snaplet/Projects.hs at line 33
liftPG . runReaderT $ findUserProjects uidsnapEval $ findUserProjects uid - replacement in server/Aftok/Snaplet/Projects.hs at line 37[3.3469]→[3.3469:3507](∅→∅),[3.3507]→[3.4166:4205](∅→∅),[3.4205]→[3.3564:3610](∅→∅),[3.3564]→[3.3564:3610](∅→∅)
QDB{..} <- view qdb <$> with qm getpid <- fmap snd requireProjectAccessmp <- liftPG . runReaderT $ findProject piduid <- requireUserIdpid <- requireProjectIdmp <- snapEval $ findProject pid uid - edit in server/Aftok/Snaplet/Users.hs at line 10
import Control.Monad.State - edit in server/Aftok/Snaplet/Users.hs at line 19
import Snap.Snaplet.PostgresqlSimple - replacement in server/Aftok/Snaplet/Users.hs at line 20
data CreateUser = CreateUserdata CUser = CU - replacement in server/Aftok/Snaplet/Users.hs at line 24
makeLenses ''CreateUsermakeLenses ''CUser - replacement in server/Aftok/Snaplet/Users.hs at line 26
instance FromJSON CreateUser whereinstance FromJSON CUser where - replacement in server/Aftok/Snaplet/Users.hs at line 31
in CreateUser <$> u <*> (fromString <$> v .: "password")in CU <$> u <*> (fromString <$> v .: "password") - edit in server/Aftok/Snaplet/Users.hs at line 36
QDB{..} <- view qdb <$> with qm get - replacement in server/Aftok/Snaplet/Users.hs at line 39
createQUser = liftPG $ runReaderT (createUser $ userData ^. cuser)createQUser = snapEval (createUser $ userData ^. cuser) - edit in server/Aftok/Snaplet/WorkLog.hs at line 6
import Control.Monad.State - edit in server/Aftok/Snaplet/WorkLog.hs at line 23
import Snap.Snaplet.PostgresqlSimple - edit in server/Aftok/Snaplet/WorkLog.hs at line 24
-- TODO: ignore "duplicate" events within some small time limit? - replacement in server/Aftok/Snaplet/WorkLog.hs at line 26[3.3246]→[3.5933:5971](∅→∅),[3.3369]→[3.5933:5971](∅→∅),[3.5933]→[3.5933:5971](∅→∅),[3.5971]→[3.4709:4746](∅→∅)
QDB{..} <- view qdb <$> with qm get(uid, pid) <- requireProjectAccessuid <- requireUserIdpid <- requireProjectId - replacement in server/Aftok/Snaplet/WorkLog.hs at line 32
Nothing -> snapError 400 $ "Unable to parse bitcoin address from " <> (tshow addrBytes)Nothing ->snapError 400 $ "Unable to parse bitcoin address from " <> (tshow addrBytes) - replacement in server/Aftok/Snaplet/WorkLog.hs at line 35
let logEntry a = LogEntry a (evCtr timestamp) (A.decode requestBody)storeEv a = runReaderT . createEvent pid uid $ logEntry ain liftPG $ storeEv addrsnapEval $ createEvent pid uid (LogEntry addr (evCtr timestamp) (A.decode requestBody)) - replacement in server/Aftok/Snaplet/WorkLog.hs at line 39[3.3456]→[3.6436:6474](∅→∅),[3.6436]→[3.6436:6474](∅→∅),[3.6474]→[3.4819:4858](∅→∅),[3.4858]→[3.1619:1661](∅→∅),[3.3515]→[3.1619:1661](∅→∅)
QDB{..} <- view qdb <$> with qm getpid <- fmap snd requireProjectAccessliftPG . runReaderT $ readWorkIndex piduid <- requireUserIdpid <- requireProjectIdsnapEval $ readWorkIndex pid uid - replacement in server/Aftok/Snaplet/WorkLog.hs at line 45
QDB{..} <- view qdb <$> with qm get(uid, pid) <- requireProjectAccessuid <- requireUserIdpid <- requireProjectId - replacement in server/Aftok/Snaplet/WorkLog.hs at line 53
liftPG . runReaderT $ findEvents pid uid ivalsnapEval $ findEvents pid uid ival - replacement in server/Aftok/Snaplet/WorkLog.hs at line 57
(QModules QDB{..} df) <- with qm getpid <- fmap snd requireProjectAccesswidx <- liftPG . runReaderT $ readWorkIndex piduid <- requireUserIdpid <- requireProjectIdprojectMay <- snapEval $ findProject pid uidproject <- maybe (snapError 400 $ "Project not found for id " <> tshow pid) pure projectMaywidx <- snapEval $ readWorkIndex pid uid - replacement in server/Aftok/Snaplet/WorkLog.hs at line 63
pure $ payouts df ptime widxpure $ payouts (toDepF $ project ^. depf) ptime widx - replacement in server/Aftok/Snaplet/WorkLog.hs at line 67
QDB{..} <- view qdb <$> with qm get(uid, _) <- requireProjectAccessuid <- requireUserId - edit in server/Aftok/Snaplet/WorkLog.hs at line 73
ev <- liftPG . runReaderT $ findEvent eventId(_, _, uid', _) <- maybe (snapError 404 ("Event not found for id " <> tshow eventId)) pure ev - replacement in server/Aftok/Snaplet/WorkLog.hs at line 75
if uid' == uidthen either(snapError 400 . pack)(liftPG . runReaderT . amendEvent eventId)(parseEither (parseEventAmendment modTime) requestJSON)else(snapError 403 "You do not have permission to view this event.")[3.9233]either(snapError 400 . pack)(snapEval . amendEvent uid eventId)(parseEither (parseEventAmendment modTime) requestJSON) - edit in server/Aftok/Snaplet.hs at line 11
import Database.PostgreSQL.Simple - replacement in server/Aftok/Snaplet.hs at line 14
import Aftok.TimeLogimport Aftok.Util - edit in server/Aftok/Snaplet.hs at line 21
data QModules = QModules{ _qdb :: QDB (ReaderT Connection IO), _depf :: DepF}makeLenses ''QModules - replacement in server/Aftok/Snaplet.hs at line 23
{ _qm :: Snaplet QModules, _sess :: Snaplet SessionManager{ _sess :: Snaplet SessionManager - replacement in server/Aftok/Snaplet.hs at line 33
-- | FIXME, make configurableqdbpgSnapletInit :: SnapletInit a QModulesqdbpgSnapletInit = makeSnaplet "qdbpg" "QDB on Postgresql" Nothing $ dopure $ QModules postgresQDB $ linearDepreciation (Months 6) (Months 60)snapEval :: DBProg a -> Handler App App asnapEval p = liftPG . runReaderT . runQDBM $ interpret dbEval p - edit in server/Main.hs at line 33
qms <- nestSnaplet "qmodules" qm qdbpgSnapletInit - replacement in server/Main.hs at line 64
return $ App qms sesss pgs authsreturn $ App sesss pgs auths