Attempting to improve JSON handling.

[?]
Apr 17, 2015, 9:44 PM
A6HKMINBNGQLLX4QJMYWKQ4JAEHVJ4HIRVDKPPDI3FJUO2AAB7OQC

Dependencies

  • [2] XTBSG4C7 Adding serveJSON combinator to eliminate some boilerplate from handlers.
  • [3] 5XFJNUAZ Start of addition of project infrastructure.
  • [4] NVOCQVAS Initial failing tests.
  • [5] TNR3TEHK Switch to Postgres + snaplet arch compiles.
  • [6] I2KHGVD4 Require project permissions for access to most data.
  • [7] TLQ72DSJ Lenses, sqlite-simple
  • [8] EQXRXRZD Changed to use tasty instead of test-framework
  • [9] N4NDAZYT Initial implementation of payouts.
  • [10] P6NR2CGX Beginning of implementation of depreciation.
  • [11] BROSTG5K Beginning of modularization of server.
  • [12] EZQG2APB Update task list.
  • [13] 2Y2QZFVF Switch to more modern cabal2nix-based workflow.
  • [14] 7XN3I3QJ Add 'loggedIntervals' endpoint.
  • [15] GKGVYBZG Added JSON serialization to TimeLog
  • [16] Y35QCWYW Minor improvement in WorkIndex type to eliminate duplicated information.
  • [17] 64C6AWH6 Rename Ananke -> Quixotic, project reboot.
  • [18] LD4GLVSF More database stuff.
  • [19] HE3JTXO3 Added client call to payouts.
  • [20] IZEVQF62 Work in progress replacing sqlite with postgres.
  • [21] EMVTF2IW WIP moving back to snap.
  • [22] VJPT6HDR Fix remaining type errors after addition of login handler.
  • [23] OBFPJS2G Project successfully builds and tests under nix.
  • [24] 7DBNV3GV Initial, stack-based impl of time log event reduction.
  • [25] W35DDBFY Factor common JSON conversions up into client lib module.
  • [26] SLL7262C Make depreciation functions more flexible.
  • [27] Z7KS5XHH Very WIP. Wow.
  • [28] 4IQVQL4T Added client for payouts endpoint.
  • [29] 4QX5E5AC Initial compilation of payouts function succeeds.
  • [30] 2G3GNDDU Event logging is now functioning in postgres.
  • [31] PBD7LZYQ Postgres & auth are beginning to function.
  • [*] ADMKQQGC Initial empty Snap project.

Change contents

  • replacement in TASKS.md at line 11
    [3.504][3.504:518]()
    Design Goals:
    [3.504]
    [3.518]
    Design Guidelines:
  • replacement in TASKS.md at line 20
    [3.874][3.874:883]()
    Library:
    [3.874]
    [3.883]
    Required for launch
    ===================
    Library
    -------
  • replacement in TASKS.md at line 31
    [3.1224][3.1224:1518]()
    the invitation
    * Timeline
    * Amend Event
    * Amend operations targeting events older than <commit_delay hours> fail.
    * MAYBE garnish/reimburse based approach?
    * Secure the transaction log via inclusion of periodic hashes of the log
    into the public blockchain?
    [3.1224]
    [3.1518]
    the invitation + script
    # * Timeline
    # * Amend Event
    # * Amend operations targeting events older than <commit_delay hours> fail.
  • edit in TASKS.md at line 37
    [3.1527][3.1527:1675]()
    * Add public keys that can be used to sign requests. How does this interact
    with certificate-based auth from browsers? Require openpgpjs?
  • edit in TASKS.md at line 43
    [3.2005][3.2005:2054]()
    * History of payouts (read from blockchain?)
  • replacement in TASKS.md at line 55
    [3.2512][3.2512:2726]()
    Webserver:
    * Login
    * Evaluate OpenID options
    * Companion Creation
    * Require user to provide the PGP public key that will be used to authenticate requests
    * Authentication
    * Require bodies of all
    [3.2512]
    [3.2726]
    Webserver
    ---------
  • replacement in TASKS.md at line 60
    [3.2758][3.2758:2775]()
    Payouts Service:
    [3.2758]
    [3.2775]
    Payouts Service
    ---------------
  • edit in TASKS.md at line 76
    [3.3394]
    Future Work
    ===========
    Library
    -------
    * Timeline
    * Amend Event
    * MAYBE garnish/reimburse based approach?
    * Secure the transaction log via inclusion of periodic hashes of the log
    into the public blockchain?
    * User
    * Add public keys that can be used to sign requests. How does this interact
    with certificate-based auth from browsers? Require openpgpjs?
    * Payouts
    * History of payouts (read from blockchain?)
    Webserver
    ---------
    * Login
    * Evaluate OpenID options
    * Companion Creation
    * Require user to provide the PGP public key that will be used to authenticate requests
    * Authentication
    * Require bodies of all requests to be PGP-signed; this will take the place of
    other authentication.
    Payouts Service
    ---------------
  • edit in lib/Quixotic/Client.hs at line 12
    [3.172][3.172:193]()
    import Quixotic.Json
  • replacement in lib/Quixotic/Client.hs at line 26
    [3.444][3.87:142]()
    pure $ payoutsResponse ^. (responseBody . _PayoutsJ)
    [3.444]
    [3.506]
    pure $ payoutsResponse ^. responseBody
  • edit in lib/Quixotic/Database/PostgreSQL.hs at line 21
    [3.1101]
    [3.1123]
    type QDBM = ReaderT Connection IO
  • replacement in lib/Quixotic/Database/PostgreSQL.hs at line 115
    [3.621][3.273:412](),[3.314][3.273:412]()
    recordEvent' :: ProjectId -> UserId -> LogEntry -> ReaderT Connection IO ()
    recordEvent' (ProjectId pid) (UserId uid) (LogEntry a e) = do
    [3.621]
    [3.3172]
    pquery :: (ToRow d, FromRow r) => Query -> d -> QDBM [r]
    pquery q d = do
  • replacement in lib/Quixotic/Database/PostgreSQL.hs at line 118
    [3.3186][3.3186:3216](),[3.3216][3.3512:3644]()
    void . lift $ execute conn
    "INSERT INTO work_events (project_id, user_id, btc_addr, event_type, event_time, event_meta) \
    \VALUES (?, ?, ?, ?, ?, ?)"
    [3.3186]
    [3.523]
    lift $ query conn q d
    pexec :: (ToRow d) => Query -> d -> QDBM Int64
    pexec q d = do
    conn <- ask
    lift $ execute conn q d
    recordEvent' :: ProjectId -> UserId -> LogEntry -> QDBM EventId
    recordEvent' (ProjectId pid) (UserId uid) (LogEntry a e) = do
    eventIds <- pquery
    "INSERT INTO work_events (project_id, user_id, btc_addr, event_type, event_time, event_metadata) \
    \VALUES (?, ?, ?, ?, ?, ?) \
    \RETURNING id"
  • edit in lib/Quixotic/Database/PostgreSQL.hs at line 137
    [3.3405]
    [3.3405]
    pure . EventId . fromOnly $ DL.head eventIds
  • replacement in lib/Quixotic/Database/PostgreSQL.hs at line 139
    [3.3406][3.539:602]()
    readWorkIndex' :: ProjectId -> ReaderT Connection IO WorkIndex
    [3.3406]
    [3.602]
    amendEvent' :: EventId -> LogModification -> QDBM ()
    amendEvent' (EventId eid) (TimeChange mt t) =
    void $ pexec
    "INSERT INTO event_time_amendments (event_id, mod_time, event_time) VALUES (?, ?, ?)"
    ( eid, mt ^. _ModTime, t )
    amendEvent' (EventId eid) (AddressChange mt addr) =
    void $ pexec
    "INSERT INTO event_addr_amendments (event_id, mod_time, btc_addr) VALUES (?, ?, ?)"
    ( eid, mt ^. _ModTime, addr ^. _BtcAddr )
    amendEvent' (EventId eid) (MetadataChange mt v) =
    void $ pexec
    "INSERT INTO event_metadata_amendments (event_id, mod_time, btc_addr) VALUES (?, ?, ?)"
    ( eid, mt ^. _ModTime, v )
    readWorkIndex' :: ProjectId -> QDBM WorkIndex
  • replacement in lib/Quixotic/Database/PostgreSQL.hs at line 157
    [3.626][3.3476:3490](),[3.3476][3.3476:3490](),[3.3490][3.627:655]()
    conn <- ask
    rows <- lift $ query conn
    [3.626]
    [3.655]
    rows <- pquery
  • replacement in lib/Quixotic/Database/PostgreSQL.hs at line 162
    [3.3625][3.763:834]()
    newAuction' :: ProjectId -> Auction -> ReaderT Connection IO AuctionId
    [3.3625]
    [3.834]
    newAuction' :: ProjectId -> Auction -> QDBM AuctionId
  • replacement in lib/Quixotic/Database/PostgreSQL.hs at line 164
    [3.859][3.3704:3748](),[3.3704][3.3704:3748]()
    conn <- ask
    aucIds <- lift $ query conn
    [3.859]
    [3.860]
    aucIds <- pquery
  • replacement in lib/Quixotic/Database/PostgreSQL.hs at line 169
    [3.3929][3.3929:3996]()
    readAuction' :: AuctionId -> ReaderT Connection IO (Maybe Auction)
    [3.3929]
    [3.3996]
    readAuction' :: AuctionId -> QDBM (Maybe Auction)
  • replacement in lib/Quixotic/Database/PostgreSQL.hs at line 171
    [3.4020][3.4020:4062]()
    conn <- ask
    rows <- lift $ query conn
    [3.4020]
    [3.4062]
    rows <- pquery
  • replacement in lib/Quixotic/Database/PostgreSQL.hs at line 176
    [3.4201][3.4201:4260]()
    recordBid' :: AuctionId -> Bid -> ReaderT Connection IO ()
    [3.4201]
    [3.4260]
    recordBid' :: AuctionId -> Bid -> QDBM ()
  • replacement in lib/Quixotic/Database/PostgreSQL.hs at line 178
    [3.4298][3.4298:4341]()
    conn <- ask
    void . lift $ execute conn
    [3.4298]
    [3.54]
    void $ pexec
  • replacement in lib/Quixotic/Database/PostgreSQL.hs at line 187
    [3.4598][3.4598:4652]()
    readBids' :: AuctionId -> ReaderT Connection IO [Bid]
    [3.4598]
    [3.4652]
    readBids' :: AuctionId -> QDBM [Bid]
  • replacement in lib/Quixotic/Database/PostgreSQL.hs at line 189
    [3.4673][3.4673:4715]()
    conn <- ask
    rows <- lift $ query conn
    [3.4673]
    [3.4715]
    rows <- pquery
  • replacement in lib/Quixotic/Database/PostgreSQL.hs at line 194
    [3.4862][3.4862:4914]()
    createUser' :: User -> ReaderT Connection IO UserId
    [3.4862]
    [3.315]
    createUser' :: User -> QDBM UserId
  • replacement in lib/Quixotic/Database/PostgreSQL.hs at line 196
    [3.338][3.4936:4978](),[3.4936][3.4936:4978]()
    conn <- ask
    uids <- lift $ query conn
    [3.338]
    [3.160]
    uids <- pquery
  • replacement in lib/Quixotic/Database/PostgreSQL.hs at line 201
    [3.5183][3.5183:5241]()
    findUser' :: UserId -> ReaderT Connection IO (Maybe User)
    [3.5183]
    [3.5241]
    findUser' :: UserId -> QDBM (Maybe User)
  • replacement in lib/Quixotic/Database/PostgreSQL.hs at line 203
    [3.5269][3.5269:5312]()
    conn <- ask
    users <- lift $ query conn
    [3.5269]
    [3.5312]
    users <- pquery
  • replacement in lib/Quixotic/Database/PostgreSQL.hs at line 208
    [3.5425][3.428:501]()
    findUserByUserName' :: UserName -> ReaderT Connection IO (Maybe QDBUser)
    [3.5425]
    [3.501]
    findUserByUserName' :: UserName -> QDBM (Maybe QDBUser)
  • replacement in lib/Quixotic/Database/PostgreSQL.hs at line 210
    [3.539][3.539:582]()
    conn <- ask
    users <- lift $ query conn
    [3.539]
    [3.582]
    users <- pquery
  • replacement in lib/Quixotic/Database/PostgreSQL.hs at line 215
    [3.5525][3.622:683]()
    createProject' :: Project -> ReaderT Connection IO ProjectId
    [3.5525]
    [3.683]
    createProject' :: Project -> QDBM ProjectId
  • replacement in lib/Quixotic/Database/PostgreSQL.hs at line 218
    [3.41][3.705:747](),[3.705][3.705:747]()
    conn <- ask
    pids <- lift $ query conn
    [3.41]
    [3.747]
    pids <- pquery
  • replacement in lib/Quixotic/Database/PostgreSQL.hs at line 222
    [3.126][3.126:155]()
    void . lift $ execute conn
    [3.126]
    [3.155]
    void $ pexec
  • replacement in lib/Quixotic/Database/PostgreSQL.hs at line 227
    [3.705][3.964:1030]()
    findUserProjects' :: UserId -> ReaderT Connection IO [QDBProject]
    [3.705]
    [3.1030]
    findUserProjects' :: UserId -> QDBM [QDBProject]
  • replacement in lib/Quixotic/Database/PostgreSQL.hs at line 229
    [3.1066][3.1066:1111]()
    conn <- ask
    results <- lift $ query conn
    [3.1066]
    [3.1111]
    results <- pquery
  • replacement in lib/Quixotic/Database/PostgreSQL.hs at line 236
    [3.705][3.5525:5568](),[3.1370][3.5525:5568](),[3.5525][3.5525:5568]()
    postgresQDB :: QDB (ReaderT Connection IO)
    [3.1369]
    [3.5568]
    postgresQDB :: QDB QDBM
  • edit in lib/Quixotic/Database/PostgreSQL.hs at line 239
    [3.5618]
    [3.5618]
    , amendEvent = amendEvent'
  • replacement in lib/Quixotic/Database.hs at line 25
    [3.6687][3.235:296]()
    { recordEvent :: ProjectId -> UserId -> LogEntry -> m ()
    [3.6687]
    [3.296]
    { recordEvent :: ProjectId -> UserId -> LogEntry -> m EventId
    , amendEvent :: EventId -> LogModification -> m ()
  • edit in lib/Quixotic/Interval.hs at line 5
    [3.44]
    [3.1483]
    , intervalJSON, parseIntervalJSON
  • replacement in lib/Quixotic/Interval.hs at line 10
    [3.1727][3.1727:1747]()
    import Control.Lens
    [3.1727]
    [3.1494]
    import Control.Lens(makeLenses, (^.))
    import Data.Aeson
    import Data.Aeson.Types
  • edit in lib/Quixotic/Interval.hs at line 27
    [3.121]
    intervalJSON :: Interval -> Value
    intervalJSON ival = object ["start" .= (ival ^. start), "end" .= (ival ^. end)]
    parseIntervalJSON :: Value -> Parser Interval
    parseIntervalJSON (Object v) = interval <$> v .: "start" <*> v .: "end"
    parseIntervalJSON _ = mzero
  • edit in lib/Quixotic/Json.hs at line 2
    [3.1964]
    [3.127]
    {-# LANGUAGE DeriveDataTypeable #-}
  • replacement in lib/Quixotic/Json.hs at line 9
    [3.2000][3.197:215](),[3.197][3.197:215](),[3.215][2.5:36]()
    import Data.Aeson
    import qualified Data.Map as M
    [3.2000]
    [3.231]
    import Data.Aeson
    import Data.Aeson.Types
    import Data.Data
    import qualified Data.Attoparsec.ByteString.Char8 as P
    import qualified Data.ByteString.Char8 as C
  • edit in lib/Quixotic/Json.hs at line 16
    [3.248][3.2001:2026](),[3.2026][3.248:272](),[3.248][3.248:272]()
    import Quixotic.Interval
    import Quixotic.TimeLog
  • replacement in lib/Quixotic/Json.hs at line 17
    [3.273][3.2027:2086]()
    newtype PayoutsJ = PayoutsJ Payouts
    makePrisms ''PayoutsJ
    [3.273]
    [3.323]
    import qualified Language.Haskell.TH as TH
    import Language.Haskell.TH.Quote
  • replacement in lib/Quixotic/Json.hs at line 20
    [3.324][3.2087:2143](),[3.2143][2.37:76]()
    instance ToJSON PayoutsJ where
    toJSON (PayoutsJ p) =
    toJSON $ M.mapKeys (^. _BtcAddr) p
    [3.324]
    [3.484]
    data Version = Version { majorVersion :: Word8
    , minorVersion :: Word8
    , trivialVersion :: Word8
    } deriving (Typeable, Data)
  • replacement in lib/Quixotic/Json.hs at line 25
    [3.485][3.2144:2177](),[3.2177][3.731:748](),[3.731][3.731:748](),[3.748][2.77:126]()
    instance FromJSON PayoutsJ where
    parseJSON v =
    PayoutsJ . M.mapKeys BtcAddr <$> parseJSON v
    [3.485]
    [3.2225]
    printVersion :: Version -> Text
    printVersion Version{..} = intercalate "." (fmap tshow [majorVersion, minorVersion, trivialVersion])
  • replacement in lib/Quixotic/Json.hs at line 28
    [3.2226][3.2226:2288]()
    newtype IntervalJ = IntervalJ Interval
    makePrisms ''IntervalJ
    [3.2226]
    [3.2288]
    versionParser :: P.Parser Version
    versionParser = Version <$> P.decimal <*> (P.char '.' >> P.decimal) <*> (P.char '.' >> P.decimal)
    versioned :: Version -> Value -> Value
    versioned ver v = object [ "schemaVersion" .= printVersion ver
    , "value" .= v ]
  • replacement in lib/Quixotic/Json.hs at line 35
    [3.2289][3.2289:2414]()
    instance ToJSON IntervalJ where
    toJSON (IntervalJ ival) =
    object ["start" .= (ival ^. start), "end" .= (ival ^. end)]
    [3.2289]
    [3.2414]
    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."
    }
  • edit in lib/Quixotic/Json.hs at line 42
    [3.2415][3.2415:2559]()
    instance FromJSON IntervalJ where
    parseJSON (Object v) =
    fmap IntervalJ $ interval <$> v .: "start" <*> v .: "end"
    parseJSON _ = mzero
  • replacement in lib/Quixotic/Json.hs at line 43
    [2.128][2.128:186]()
    newtype ProjectJ = ProjectJ Project
    makePrisms ''ProjectJ
    [2.128]
    [2.186]
    quoteVersionExp :: String -> TH.Q TH.Exp
    quoteVersionExp s = do
    v <- either (fail . show) pure $ P.parseOnly versionParser (C.pack s)
    dataToExpQ (const Nothing) v
  • replacement in lib/Quixotic/Json.hs at line 48
    [2.187][2.187:411]()
    instance ToJSON ProjectJ where
    toJSON (ProjectJ p) =
    object [ "projectName" .= (p ^. projectName)
    , "inceptionDate" .= (p ^. inceptionDate)
    , "initiator" .= (p ^. (initiator._UserId)) ]
    [2.187]
    [3.485]
    unversion :: (Version -> Value -> Parser a) -> Value -> Parser a
    unversion f (Object v) = do
    vers <- v .: "schemaVersion"
    vers' <- either (\_ -> mzero) pure $ P.parseOnly versionParser (encodeUtf8 vers)
    value <- v .: "value"
    f vers' value
    unversion _ _ = mzero
  • replacement in lib/Quixotic/Json.hs at line 56
    [3.486][2.412:463]()
    newtype WidxJ = WidxJ WorkIndex
    makePrisms ''WidxJ
    [3.486]
    [3.2560]
    projectJSON :: Project -> Value
    projectJSON p =
    object [ "projectName" .= (p ^. projectName)
    , "inceptionDate" .= (p ^. inceptionDate)
    , "initiator" .= (p ^. (initiator._UserId)) ]
  • edit in lib/Quixotic/Json.hs at line 62
    [3.2561][2.464:590]()
    instance ToJSON WidxJ where
    toJSON (WidxJ widx) =
    toJSON $ (fmap IntervalJ) <$> (M.mapKeysWith (++) (^._BtcAddr) widx)
  • replacement in lib/Quixotic/TimeLog.hs at line 12
    [3.107][3.3621:3635]()
    , workIndex
    [3.107]
    [3.3]
    , workIndex, workIndexJSON
  • edit in lib/Quixotic/TimeLog.hs at line 14
    [3.12]
    [3.5326]
    , EventId(EventId), _EventId, eventIdJSON
    , ModTime(ModTime), _ModTime
    , LogModification(..)
  • replacement in lib/Quixotic/TimeLog.hs at line 18
    [3.5345][3.5345:5357]()
    , Payouts
    [3.5345]
    [3.961]
    , Payouts(..), _Payouts
  • edit in lib/Quixotic/TimeLog.hs at line 27
    [3.3878]
    [3.65]
    import Data.Aeson.Types
  • edit in lib/Quixotic/TimeLog.hs at line 34
    [3.635]
    [3.635]
    import Quixotic.Json
  • edit in lib/Quixotic/TimeLog.hs at line 63
    [3.1725]
    [3.824]
    newtype EventId = EventId Int64 deriving (Show, Eq)
    makePrisms ''EventId
    newtype ModTime = ModTime UTCTime
    makePrisms ''ModTime
    data LogModification = TimeChange ModTime UTCTime
    | AddressChange ModTime BtcAddr
    | MetadataChange ModTime A.Value
  • replacement in lib/Quixotic/TimeLog.hs at line 78
    [3.2287][3.13:49]()
    type Payouts = Map BtcAddr Rational
    [3.2287]
    [3.337]
    newtype Payouts = Payouts (Map BtcAddr Rational)
    makePrisms ''Payouts
    payoutsJSON :: Payouts -> Value
    payoutsJSON (Payouts m) = toJSON $ MS.mapKeys (^. _BtcAddr) m
    parsePayoutsJSON :: Value -> Parser Payouts
    parsePayoutsJSON v =
    Payouts . MS.mapKeys BtcAddr <$> parseJSON v
    instance A.ToJSON Payouts where
    toJSON = versioned (Version 1 0 0) . payoutsJSON
    instance A.FromJSON Payouts where
    parseJSON v = let parsePayouts (Version 1 0 0) = parsePayoutsJSON
    parsePayouts v' = \_ -> fail . show $ printVersion v'
    in unversion parsePayouts $ v
  • edit in lib/Quixotic/TimeLog.hs at line 100
    [3.351]
    [3.89]
    workIndexJSON :: WorkIndex -> Value
    workIndexJSON widx = toJSON $ (fmap intervalJSON) <$> (MS.mapKeysWith (++) (^._BtcAddr) widx)
    eventIdJSON :: EventId -> Value
    eventIdJSON (EventId eid) = toJSON eid
  • replacement in lib/Quixotic/TimeLog.hs at line 123
    [3.516][3.516:573]()
    in fmap (\kt -> toRational $ kt / totalTime) keyTimes
    [3.516]
    [3.1011]
    in Payouts $ fmap (\kt -> toRational $ kt / totalTime) keyTimes
  • edit in payouts/Main.hs at line 14
    [3.834]
    [3.1745]
    import Quixotic.TimeLog
  • replacement in payouts/Main.hs at line 59
    [3.2798][3.914:957]()
    payouts <- currentPayouts (qcConfig cfg)
    [3.2798]
    [3.2798]
    (Payouts p) <- currentPayouts (qcConfig cfg)
  • replacement in payouts/Main.hs at line 61
    [3.2850][3.958:1012]()
    putStrLn (tshow unspent)
    putStrLn (tshow payouts)
    [3.2850]
    putStrLn . tshow $ unspent
    putStrLn . tshow $ p
  • edit in quixotic.cabal at line 36
    [3.1143]
    [3.3072]
    , template-haskell
  • replacement in server/Main.hs at line 53
    [3.8755][3.118:179]()
    addRoutes [ ("login", requireLogin >> (redirect "/home"))
    [3.8755]
    [3.851]
    addRoutes [ ("login", requireLogin >> (redirect "/home"))
  • replacement in server/Main.hs at line 55
    [3.914][3.914:1105](),[3.1105][2.649:847]()
    , ("projects/:projectId/logStart/:btcAddr", method POST $ logWorkHandler StartWork)
    , ("projects/:projectId/logEnd/:btcAddr", method POST $ logWorkHandler StopWork)
    , ("projects/:projectId/log/:btcAddr", serveJSON WidxJ $ method GET loggedIntervalsHandler)
    , ("projects/:projectId", serveJSON ProjectJ $ method GET projectGetHandler)
    [3.914]
    [2.847]
    , ("projects/:projectId/logStart/:btcAddr", serveJSON eventIdJSON . method POST $ logWorkHandler StartWork)
    , ("projects/:projectId/logEnd/:btcAddr", serveJSON eventIdJSON . method POST $ logWorkHandler StopWork)
    , ("projects/:projectId/log/:btcAddr", serveJSON workIndexJSON $ method GET loggedIntervalsHandler)
    , ("projects/:projectId", serveJSON projectJSON $ method GET projectGetHandler)
  • replacement in server/Main.hs at line 60
    [2.926][2.926:1120]()
    , ("projects", serveJSON (fmap (ProjectJ._project)) $ method GET projectListHandler)
    , ("payouts/:projectId", serveJSON PayoutsJ $ method GET payoutsHandler)
    [2.926]
    [3.8999]
    , ("payouts/:projectId", serveJSON id $ method GET payoutsHandler)
  • replacement in server/Quixotic/Snaplet/WorkLog.hs at line 20
    [3.5831][3.5831:5881]()
    logWorkHandler :: EventType -> Handler App App ()
    [3.5831]
    [3.3341]
    logWorkHandler :: EventType -> Handler App App EventId