module Persistence.Payment ( count , find , firstPunctualDay , listActive , listActivePage , listPunctual , listActiveMonthlyOrderedByName , create , createMany , edit , delete ) where import Data.Maybe (listToMaybe) import Data.Text (Text) import qualified Data.Text as T import Data.Time.Calendar (Day) import Data.Time.Clock (getCurrentTime) import Database.SQLite.Simple (FromRow (fromRow), Only (Only), ToRow) import qualified Database.SQLite.Simple as SQLite import Database.SQLite.Simple.ToField (ToField (toField)) import Prelude hiding (id) import Common.Model (Frequency (..), Payment (..), PaymentId, UserId) import Model.Query (Query (Query)) import Persistence.Frequency (FrequencyField (..)) newtype Row = Row Payment instance FromRow Row where fromRow = Row <$> (Payment <$> SQLite.field <*> SQLite.field <*> SQLite.field <*> SQLite.field <*> SQLite.field <*> (fmap (\(FrequencyField f) -> f) $ SQLite.field) <*> SQLite.field <*> SQLite.field <*> SQLite.field) newtype InsertRow = InsertRow Payment instance ToRow InsertRow where toRow (InsertRow p) = [ toField (_payment_user p) , toField (_payment_name p) , toField (_payment_cost p) , toField (_payment_date p) , toField (FrequencyField (_payment_frequency p)) , toField (_payment_createdAt p) ] data Count = Count Int instance FromRow Count where fromRow = Count <$> SQLite.field count :: Query Int count = Query (\conn -> (\[Count n] -> n) <$> SQLite.query_ conn "SELECT COUNT(*) FROM payment WHERE deleted_at IS NULL" ) find :: PaymentId -> Query (Maybe Payment) find paymentId = Query (\conn -> do fmap (\(Row p) -> p) . listToMaybe <$> SQLite.query conn "SELECT * FROM payment WHERE id = ?" (Only paymentId) ) data DayRow = DayRow Day instance FromRow DayRow where fromRow = DayRow <$> SQLite.field firstPunctualDay :: Query (Maybe Day) firstPunctualDay = Query (\conn -> do fmap (\(DayRow d) -> d) . listToMaybe <$> SQLite.query conn "SELECT date FROM payment WHERE frequency = ? AND deleted_at IS NULL ORDER BY date LIMIT 1" (Only (FrequencyField Punctual)) ) listActive :: Frequency -> Query [Payment] listActive frequency = Query (\conn -> do map (\(Row p) -> p) <$> SQLite.query conn "SELECT * FROM payment WHERE deleted_at IS NULL AND frequency = ?" (Only (FrequencyField frequency)) ) listActivePage :: Int -> Int -> Query [Payment] listActivePage page perPage = Query (\conn -> map (\(Row p) -> p) <$> SQLite.query conn (SQLite.Query $ T.intercalate " " [ "SELECT *" , "FROM payment" , "WHERE deleted_at IS NULL AND frequency = ?" , "ORDER BY date DESC" , "LIMIT ?" , "OFFSET ?" ] ) (FrequencyField Punctual, perPage, (page - 1) * perPage) ) listPunctual :: Query [Payment] listPunctual = Query (\conn -> do map (\(Row p) -> p) <$> SQLite.query conn (SQLite.Query "SELECT * FROM payment WHERE frequency = ?") (Only (FrequencyField Punctual)) ) listActiveMonthlyOrderedByName :: Query [Payment] listActiveMonthlyOrderedByName = Query (\conn -> do map (\(Row p) -> p) <$> SQLite.query conn (SQLite.Query $ T.intercalate " " [ "SELECT *" , "FROM payment" , "WHERE deleted_at IS NULL AND frequency = ?" , "ORDER BY name DESC" ]) (Only (FrequencyField Monthly)) ) create :: UserId -> Text -> Int -> Day -> Frequency -> Query Payment create userId name cost date frequency = Query (\conn -> do time <- getCurrentTime SQLite.execute conn (SQLite.Query $ T.intercalate " " [ "INSERT INTO payment (user_id, name, cost, date, frequency, created_at)" , "VALUES (?, ?, ?, ?, ?, ?)" ]) (userId, name, cost, date, FrequencyField frequency, time) paymentId <- SQLite.lastInsertRowId conn return $ Payment { _payment_id = paymentId , _payment_user = userId , _payment_name = name , _payment_cost = cost , _payment_date = date , _payment_frequency = frequency , _payment_createdAt = time , _payment_editedAt = Nothing , _payment_deletedAt = Nothing } ) createMany :: [Payment] -> Query () createMany payments = Query (\conn -> SQLite.executeMany conn (SQLite.Query $ T.intercalate "" [ "INSERT INTO payment (user_id, name, cost, date, frequency, created_at)" , "VALUES (?, ?, ?, ?, ?, ?)" ]) (map InsertRow payments) ) edit :: UserId -> PaymentId -> Text -> Int -> Day -> Frequency -> Query (Maybe (Payment, Payment)) edit userId paymentId name cost date frequency = Query (\conn -> do mbPayment <- fmap (\(Row p) -> p) . listToMaybe <$> SQLite.query conn "SELECT * FROM payment WHERE id = ? and user_id = ?" (paymentId, userId) case mbPayment of Just payment -> do now <- getCurrentTime SQLite.execute conn (SQLite.Query $ T.intercalate " " [ "UPDATE" , " payment" , "SET" , " edited_at = ?," , " name = ?," , " cost = ?," , " date = ?," , " frequency = ?" , "WHERE" , " id = ?" , " AND user_id = ?" ]) ( now , name , cost , date , FrequencyField frequency , paymentId , userId ) return . Just . (,) payment $ Payment { _payment_id = paymentId , _payment_user = userId , _payment_name = name , _payment_cost = cost , _payment_date = date , _payment_frequency = frequency , _payment_createdAt = _payment_createdAt payment , _payment_editedAt = Just now , _payment_deletedAt = Nothing } Nothing -> return Nothing ) delete :: UserId -> PaymentId -> Query () delete userId paymentId = Query (\conn -> SQLite.execute conn "UPDATE payment SET deleted_at = datetime('now') WHERE id = ? AND user_id = ?" (paymentId, userId) )