GCVQD44VRPQVKPZEPIC4AOIXLJIG2ZMV3QI2Y7KALUT6NVUBSGSAC RPAJLHMTUJU4AYNBOHVGHGGB4NY2NLY3BVPYN5FMWB3ZIMAUQHCQC 75N3UJ4JK56KXF56GASGPAWLFYGJDETVJNYTF4KXFCQM767JUU5AC LAROLAYUGJ4Q5AEFV5EJMIA2ZKBNCBWHHHPCJ3CKCNIUIYUKRFVQC IZEVQF627FA7VV25KJAWYWGGC35LZUUBBQRPN5ZAINTQLPEDRTEAC KNSI575VAW6HRCZYXOEPQ4DTSML4EORML5MV4DJBRKE7TXCPS4EAC 7KZP4RHZ3QSYTPPQ257A65Z5UPX44TF2LAI2U5EMULQCLDCEUK2AC FD7SV5I6VCW27HZ3T3K4MMGB2OYGJTPKFFA263TNTAMRJGQJWVNAC EZQG2APB36DDMIAYDPPDGOIXOD7K2RZZSGC2NKGZIHB2HZBTW7EQC TNR3TEHKVADAEZSTOD2XLSUTSW5AWST2YUW4CWK5KE7DSC6XHZNAC LD4GLVSF6YTA7OZWIGJ45H6TUXGM4WKUIYXKWQFNUP36WDMYSMXAC O5FVTOM6YFBLEPF3S576K6IMT6ZZ5VQCSB3YVXNS4CKBITKCRZ7AC A6HKMINBNGQLLX4QJMYWKQ4JAEHVJ4HIRVDKPPDI3FJUO2AAB7OQC Z7KS5XHHC6PAMTVBHXY7KUSS3BWAOU6FSYIITUCFOOJZU4OUJHBAC PBD7LZYQHXAA3KLH2ZUX5GW4UFML6BQ32KXZF4KZ6OYFASUYFJ5QC 2G3GNDDUOVPF45PELJ65ZB2IXEHJJXJILFRVHZXGPXUL4BVNZJFQC TZQJVHBAMDNWDBYCDE3SDVGBG2T5FOE3J5JAD6NENRW36XBHUUFQC 64C6AWH66FDKU6UE6Z6JPX2J2GBM2JOPTH2GL6LHKAIUBGNGDZ5AC W35DDBFYF6Z4ZPCFEO5RPAONZLYCSNTXUSTS6FIUUVZHCI6Q7GHAC XTBSG4C7SCZUFOU2BTNFR6B6TCGYI35BWUV4PVTS3N7KNH5VEARQC NVOCQVASZWTKQJG7GPH7KHKZZR7NUG4WLV5YY4KAIRPCJRWCZPIAC 5DRIWGLUKMQZU2ZPBXSTLAWJKAMOD5YXAHM5LEDQHDFGYYLHWCDQC 7DBNV3GV773FH5ZLQWFX4RBOS4Q3CIK2RYZNNABY3ZOETYZCXRNQC 2KZPOGRBY6KBMO76F55ZKIVOLSG3O63VP3RHRZVANXYT3OLZ3OWQC GKGVYBZGPJXO7N7GLHLRNYQPXFHBQSNQN53OKRFCXLQEYDTC5I4QC EQXRXRZDYCM7BDAVBOXQYPG6C7IJT3OFGNIXCDAHJJBRKAXNGL7AC OBFPJS2GHO2PEHBHGEHKIUOUAFIQHPIZXEVD2YIE3ZIE2PVMH5VAC 4IQVQL4TS35GL2GYZJG254TKJLL5EHMRSFT77Z4VTRZIG2TMBM3QC HE3JTXO37O4MOMWPZ4BRBHP53KBPZDG3PCSUCVNOKIS7IY26OCIAC EMVTF2IWNQGRL44FC4JNG5FYYQTZSFPNM6SOM7IAEH6T7PPK2NVAC BROSTG5KP3NUNLSYPVQID254TE47E5RKQAKLPIY7PGWETE6JNMTAC WZUHEZSBRKHQMNWDKVG4X6DDIQEAXTGI6IGAJ5ERPRQ3W2KUMX4QC TCOAKCGGHOIRJCTZYEZQ3K6KCGL2LGAYGYFRGSPCHBTJJY2V6AXAC ADMKQQGCGVSHHIMVQ4XFRDCG544SBJCYALSKZV45CQQBZ4ACUH2AC 2Y2QZFVFSKXEFEGYJB5A7GI735ONWPCF7DVTIY5T73AUEVTZTBBQC Z3M53KTLZMPOISMHE25SZJSWX5TA37IV33IRE7KNRAD3PKEAEJXQC I2KHGVD44KT4MQJXGCTVSQKMBO6TVCY72F26TLXGWRL6PHGF6RNQC workEventParser :: RowParser LogEventworkEventParser = fieldWith eventTypeParser <*> fieldWith utcParserlogEntryParser :: RowParser LogEntrylogEntryParser = LogEntry <$> fieldWith btcAddrParser <*> workEventParser <*> fieldauctionRowParser :: RowParser AuctionauctionRowParser = Auction <$> fieldWith btcParser <*> fieldbidRowParser :: RowParser BidbidRowParser = Bid <$> fieldWith uidParser<*> fieldWith secondsParser<*> fieldWith btcParser<*> fielduserRowParser :: RowParser UseruserRowParser = User <$> fieldWith usernameParser<*> fieldWith btcAddrParser<*> fieldqdbUserRowParser :: RowParser QDBUserqdbUserRowParser = QDBUser <$> fieldWith uidParser<*> userRowParserprojectRowParser :: RowParser ProjectprojectRowParser = Project <$> field<*> field<*> fieldWith uidParserqdbProjectRowParser :: RowParser QDBProjectqdbProjectRowParser = QDBProject <$> fieldWith pidParser <*> projectRowParser-- Local newtypes to permit field serialization
-- Local newtypes to permit row deserialization via-- typeclass. Wish I could just pass the RowParser instances
logEntryParser :: RowParser LogEntrylogEntryParser =LogEntry <$> fieldWith btcAddrParser<*> (fieldWith eventTypeParser <*> fieldWith utcParser)<*> field
newtype PLogEntry = PLogEntry { pLogEntry :: LogEntry }instance FromRow PLogEntry wherefromRow = PLogEntry <$> logEntryParser
qdbLogEntryParser :: RowParser QDBLogEntryqdbLogEntryParser =(,,,) <$> fieldWith eidParser<*> fieldWith pidParser<*> fieldWith uidParser<*> logEntryParserauctionParser :: RowParser AuctionauctionParser =Auction <$> fieldWith btcParser<*> field
newtype PQDBProject = PQDBProject { pQDBProject :: QDBProject }instance FromRow PQDBProject wherefromRow = PQDBProject <$> qdbProjectRowParser
pexec :: (ToRow d) => Query -> d -> QDBM Int64pexec q d = doconn <- asklift $ execute conn q d
pure . EventId . fromOnly $ DL.head eventIds
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 logEntries
void $ pexec"INSERT INTO event_metadata_amendments (event_id, mod_time, btc_addr) VALUES (?, ?, ?)"( eid, fromThyme $ mt ^. _ModTime, v )
pinsert AmendmentId"INSERT INTO event_metadata_amendments (event_id, mod_time, btc_addr) VALUES (?, ?, ?) RETURNING id"( eid, fromThyme $ mt ^. _ModTime, v)
createAuction' pid auc = doaucIds <- pquery"INSERT INTO auctions (project_id, raise_amount, end_time) VALUES (?, ?, ?) RETURNING id"
createAuction' pid auc =pinsert AuctionId"INSERT INTO auctions (project_id, raise_amount, end_time) \\VALUES (?, ?, ?) RETURNING id"
void $ pexec"INSERT INTO bids (auction_id, bidder_id, bid_seconds, bid_amount, bid_time) values (?, ?, ?, ?, ?)"
pinsert BidId"INSERT INTO bids (auction_id, bidder_id, bid_seconds, bid_amount, bid_time) \\VALUES (?, ?, ?, ?, ?) RETURNING id"
let pid = fromOnly $ DL.head pidsvoid $ pexec"INSERT INTO project_companions (project_id, companion_id) VALUES (?, ?)"(pid, uid)pure . ProjectId $ pid
void $ pexec"INSERT INTO project_companions (project_id, user_id) VALUES (?, ?)"(pid ^. _ProjectId, uid)pure pid
vers <- v .: "schemaVersion"vers' <- either (\_ -> mzero) pure $ P.parseOnly versionParser (encodeUtf8 vers)value <- v .: "value"f vers' valueunversion _ _ = mzero
verstr <- v .: "schemaVersion"vers <- either fail pure $ P.parseOnly versionParser (encodeUtf8 verstr)v .: "value" >>= f versunversion _ x =fail $ show x <> " did not contain the expected version information."---------------- Versions ----------------v1 :: Value -> Valuev1 = versioned $ Version 1 0unv1 :: String -> (Value -> Parser a) -> Value -> Parser aunv1 name f v =let p (Version 1 0) = fp ver = const . fail $ "Unrecognized " <> name <> " schema version: " <> show verin unversion p v------------------- Serializers -------------------
qdbProjectJSON qp =object [ "projectId" .= (tshow $ qp ^. (projectId._ProjectId)), "project" .= projectJSON (qp ^. project)
qdbProjectJSON (projectId, project) = v1 $object [ "projectId" .= (tshow $ projectId ^. _ProjectId), "project" .= projectJSON project
parsePayoutsJSON :: Value -> Parser PayoutsparsePayoutsJSON = unv1 "payouts" $ \v ->Payouts . MS.mapKeys BtcAddr <$> parseJSON vparseEventAmendment :: ModTime -> Value -> Parser EventAmendmentparseEventAmendment t =let parseA x "timeChange" = TimeChange t <$> x .: "eventTime"parseA x "addrChage" = doaddrText <- x .: "btcAddr"maybe(fail $ (show addrText) <> "is not a valid BTC address")(pure . AddressChange t)$ parseBtcAddr addrTextparseA x "metadataChange" =MetadataChange t <$> x .: "eventMeta"parseA _ other =fail $ "Amendment value " <> other <> " not recognized."p (Object x) = x .: "amendment" >>= parseA xp x = fail $ "Value " <> show x <> " missing 'amendment' field."in unv1 "amendment" p
data LogModification = TimeChange ModTime C.UTCTime| AddressChange ModTime BtcAddr| MetadataChange ModTime A.Value
data EventAmendment = TimeChange ModTime C.UTCTime| AddressChange ModTime BtcAddr| MetadataChange ModTime A.Valuenewtype AmendmentId = AmendmentId UUID deriving (Show, Eq)makePrisms ''AmendmentId
curl -v -u "nuttycom:kjntest" -X POST -d '' http://localhost:8000/projects/1/logEnd/1KamUn1BaRMd2HwikyQWGTdUvfPScg9QA5
curl -v -u "nuttycom:kjntest" -X POST -d '' http://localhost:8000/projects/6f4cba6f-02ec-4cc3-9241-00609d6a6f6a/logEnd/1KamUn1BaRMd2HwikyQWGTdUvfPScg9QA5
curl -v -u "nuttycom:kjntest" -X POST -d '' http://localhost:8000/projects/1/logStart/1KamUn1BaRMd2HwikyQWGTdUvfPScg9QA5
curl -v -u "nuttycom:kjntest" -X POST -d '' http://localhost:8000/projects/6f4cba6f-02ec-4cc3-9241-00609d6a6f6a/logStart/1KamUn1BaRMd2HwikyQWGTdUvfPScg9QA5
logIntervalsRoute = serveJSON workIndexJSON $ method GET loggedIntervalsHandler--amendEventRoute = void $ method PUT amendEventHandler
logIntervalsRoute = serveJSON workIndexJSON $ method GET loggedIntervalsHandleramendEventRoute = serveJSON amendmentIdJSON $ method PUT amendEventHandler
-- amendEventHandler :: Handler App App AmendmentId
amendEventHandler :: Handler App App AmendmentIdamendEventHandler = doQDB{..} <- view qdb <$> with qm get(uid, _) <- requireProjectAccesseventIdBytes <- getParam "eventId"eventId <- maybe(snapError 400 "eventId parameter is required")(pure . EventId)(eventIdBytes >>= U.fromASCIIBytes)
ev <- liftPG . runReaderT $ findEvent eventId(_, _, uid', _) <- maybe (snapError 404 ("Event not found for id " <> tshow eventId)) pure evmodTime <- ModTime <$> liftIO C.getCurrentTimerequestJSON <- readRequestJSON 4096if 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.")
id serial primary key,project_id integer references projects(id) not null,user_id integer references users(id) not null,
id uuid primary key default uuid_generate_v4(),project_id uuid references projects(id) not null,user_id uuid references users(id) not null,
id serial primary key,project_id integer references projects(id) not null,initiator_id integer references users (id) not null,
id uuid primary key default uuid_generate_v4(),project_id uuid references projects(id) not null,initiator_id uuid references users (id) not null,
id serial primary key,auction_id integer references projects (id) not null,bidder_id integer references users (id) not null,
id uuid primary key default uuid_generate_v4(),auction_id uuid references projects (id) not null,bidder_id uuid references users (id) not null,