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 LogEvent
workEventParser = fieldWith eventTypeParser <*> fieldWith utcParser
logEntryParser :: RowParser LogEntry
logEntryParser = LogEntry <$> fieldWith btcAddrParser <*> workEventParser <*> field
auctionRowParser :: RowParser Auction
auctionRowParser = Auction <$> fieldWith btcParser <*> field
bidRowParser :: RowParser Bid
bidRowParser = Bid <$> fieldWith uidParser
<*> fieldWith secondsParser
<*> fieldWith btcParser
<*> field
userRowParser :: RowParser User
userRowParser = User <$> fieldWith usernameParser
<*> fieldWith btcAddrParser
<*> field
qdbUserRowParser :: RowParser QDBUser
qdbUserRowParser = QDBUser <$> fieldWith uidParser
<*> userRowParser
projectRowParser :: RowParser Project
projectRowParser = Project <$> field
<*> field
<*> fieldWith uidParser
qdbProjectRowParser :: RowParser QDBProject
qdbProjectRowParser = 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 LogEntry
logEntryParser =
LogEntry <$> fieldWith btcAddrParser
<*> (fieldWith eventTypeParser <*> fieldWith utcParser)
<*> field
newtype PLogEntry = PLogEntry { pLogEntry :: LogEntry }
instance FromRow PLogEntry where
fromRow = PLogEntry <$> logEntryParser
qdbLogEntryParser :: RowParser QDBLogEntry
qdbLogEntryParser =
(,,,) <$> fieldWith eidParser
<*> fieldWith pidParser
<*> fieldWith uidParser
<*> logEntryParser
auctionParser :: RowParser Auction
auctionParser =
Auction <$> fieldWith btcParser
<*> field
newtype PQDBProject = PQDBProject { pQDBProject :: QDBProject }
instance FromRow PQDBProject where
fromRow = PQDBProject <$> qdbProjectRowParser
pexec :: (ToRow d) => Query -> d -> QDBM Int64
pexec q d = do
conn <- ask
lift $ execute conn q d
pure . EventId . fromOnly $ DL.head eventIds
findEvent' :: EventId -> QDBM (Maybe QDBLogEntry)
findEvent' (EventId eid) = do
logEntries <- 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 = do
aucIds <- 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 pids
void $ 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' value
unversion _ _ = mzero
verstr <- v .: "schemaVersion"
vers <- either fail pure $ P.parseOnly versionParser (encodeUtf8 verstr)
v .: "value" >>= f vers
unversion _ x =
fail $ show x <> " did not contain the expected version information."
--------------
-- Versions --
--------------
v1 :: Value -> Value
v1 = versioned $ Version 1 0
unv1 :: String -> (Value -> Parser a) -> Value -> Parser a
unv1 name f v =
let p (Version 1 0) = f
p ver = const . fail $ "Unrecognized " <> name <> " schema version: " <> show ver
in 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 Payouts
parsePayoutsJSON = unv1 "payouts" $ \v ->
Payouts . MS.mapKeys BtcAddr <$> parseJSON v
parseEventAmendment :: ModTime -> Value -> Parser EventAmendment
parseEventAmendment t =
let parseA x "timeChange" = TimeChange t <$> x .: "eventTime"
parseA x "addrChage" = do
addrText <- x .: "btcAddr"
maybe
(fail $ (show addrText) <> "is not a valid BTC address")
(pure . AddressChange t)
$ parseBtcAddr addrText
parseA x "metadataChange" =
MetadataChange t <$> x .: "eventMeta"
parseA _ other =
fail $ "Amendment value " <> other <> " not recognized."
p (Object x) = x .: "amendment" >>= parseA x
p 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.Value
newtype 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 loggedIntervalsHandler
amendEventRoute = serveJSON amendmentIdJSON $ method PUT amendEventHandler
-- amendEventHandler :: Handler App App AmendmentId
amendEventHandler :: Handler App App AmendmentId
amendEventHandler = do
QDB{..} <- view qdb <$> with qm get
(uid, _) <- requireProjectAccess
eventIdBytes <- 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 ev
modTime <- ModTime <$> liftIO C.getCurrentTime
requestJSON <- readRequestJSON 4096
if uid' == uid
then 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,