From 0981bb0e485675346045005cc5ba31e97c32ea28 Mon Sep 17 00:00:00 2001
From: Ramakrishnan Muthukrishnan <ram@rkrishnan.org>
Date: Thu, 17 Oct 2019 15:06:01 +0530
Subject: [PATCH] persistence: WIP of sqlite based persistence

---
 PaymentServer.cabal              |  1 +
 src/PaymentServer/Persistence.hs | 37 ++++++++++++++++++++++++++++++++
 2 files changed, 38 insertions(+)

diff --git a/PaymentServer.cabal b/PaymentServer.cabal
index 165887e..2746523 100644
--- a/PaymentServer.cabal
+++ b/PaymentServer.cabal
@@ -35,6 +35,7 @@ library
                      , text
                      , containers
                      , cryptonite
+                     , sqlite-simple
   default-language:    Haskell2010
   ghc-options:       -Wmissing-import-lists -Wunused-imports
   pkgconfig-depends: ristretto
diff --git a/src/PaymentServer/Persistence.hs b/src/PaymentServer/Persistence.hs
index d08f12a..d13414d 100644
--- a/src/PaymentServer/Persistence.hs
+++ b/src/PaymentServer/Persistence.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeSynonymInstances #-}
 module PaymentServer.Persistence
   ( Voucher
   , Fingerprint
@@ -21,6 +23,10 @@ import Data.IORef
   , modifyIORef
   , readIORef
   )
+import qualified Database.SQLite.Simple as Sqlite
+import           Database.SQLite.Simple.FromRow
+  ( FromRow(fromRow)
+  )
 
 -- | A voucher is a unique identifier which can be associated with a payment.
 -- A paid voucher can be redeemed for ZKAPs which can themselves be exchanged
@@ -103,3 +109,34 @@ memory = do
   paid <- newIORef mempty
   redeemed <- newIORef mempty
   return $ Memory paid redeemed
+
+instance VoucherDatabase Sqlite.Connection where
+  -- payForVoucher :: Sqlite.Connection -> Voucher -> IO ()
+  payForVoucher dbConn voucher = do
+    undefined
+  -- redeemVoucher :: Sqlite.Connection -> Voucher -> Fingerprint -> IO (Either RedeemError ())
+  redeemVoucher dbConn voucher fingerprint = do
+    unpaid <- Set.notMember voucher <$> getPaidVouchers dbConn
+    existingFingerprint <- getVoucherFingerprint dbConn voucher
+    case (unpaid, existingFingerprint) of
+      (True, _) ->
+        return $ Left NotPaid
+      (False, []) ->
+        -- TODO: insert voucher and fingerprint into the redeemed table
+        return $ Right ()
+      (False, [fingerprint']) ->
+        if fingerprint == fingerprint' then
+          return $ Right ()
+        else
+          return $ Left AlreadyRedeemed
+
+instance FromRow Fingerprint where
+  fromRow = Sqlite.field
+
+getPaidVouchers :: Sqlite.Connection -> IO (Set.Set Voucher)
+getPaidVouchers dbConn = Set.fromList <$>
+  Sqlite.query_ dbConn "SELECT DISTINCT name FROM vouchers"
+
+getVoucherFingerprint :: Sqlite.Connection -> Voucher -> IO [Fingerprint]
+getVoucherFingerprint dbConn voucher = do
+  Sqlite.query dbConn "SELECT redeemed.fingerprint FROM vouchers INNER JOIN redeemed ON vouchers.id = redeemed.voucher_id AND vouchers.name = ?" (Sqlite.Only voucher)
-- 
GitLab