{-# LANGUAGE CPP #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Simplex.Chat.Store.NoteFolders where

import Control.Monad.Except (ExceptT (..), throwError)
import Control.Monad.IO.Class (liftIO)
import Data.Time (getCurrentTime)
import Simplex.Chat.Store.Shared (StoreError (..))
import Simplex.Chat.Types (NoteFolder (..), NoteFolderId, User (..))
import Simplex.Messaging.Agent.Protocol (UserId)
import Simplex.Messaging.Agent.Store.AgentStore (firstRow)
import Simplex.Messaging.Agent.Store.DB (BoolInt (..))
import qualified Simplex.Messaging.Agent.Store.DB as DB
#if defined(dbPostgres)
import Database.PostgreSQL.Simple (Only (..))
import Database.PostgreSQL.Simple.SqlQQ (sql)
#else
import Database.SQLite.Simple (Only (..))
import Database.SQLite.Simple.QQ (sql)
#endif

createNoteFolder :: DB.Connection -> User -> ExceptT StoreError IO ()
createNoteFolder :: Connection -> User -> ExceptT StoreError IO ()
createNoteFolder Connection
db User {UserId
userId :: UserId
userId :: User -> UserId
userId} = do
  IO [Only UserId] -> ExceptT StoreError IO [Only UserId]
forall a. IO a -> ExceptT StoreError IO a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (Connection -> Query -> Only UserId -> IO [Only UserId]
forall q r.
(ToRow q, FromRow r) =>
Connection -> Query -> q -> IO [r]
DB.query Connection
db Query
"SELECT note_folder_id FROM note_folders WHERE user_id = ? LIMIT 1" (Only UserId -> IO [Only UserId])
-> Only UserId -> IO [Only UserId]
forall a b. (a -> b) -> a -> b
$ UserId -> Only UserId
forall a. a -> Only a
Only UserId
userId) ExceptT StoreError IO [Only UserId]
-> ([Only UserId] -> ExceptT StoreError IO ())
-> ExceptT StoreError IO ()
forall a b.
ExceptT StoreError IO a
-> (a -> ExceptT StoreError IO b) -> ExceptT StoreError IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
    [] -> IO () -> ExceptT StoreError IO ()
forall a. IO a -> ExceptT StoreError IO a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> ExceptT StoreError IO ())
-> IO () -> ExceptT StoreError IO ()
forall a b. (a -> b) -> a -> b
$ Connection -> Query -> Only UserId -> IO ()
forall q. ToRow q => Connection -> Query -> q -> IO ()
DB.execute Connection
db Query
"INSERT INTO note_folders (user_id) VALUES (?)" (UserId -> Only UserId
forall a. a -> Only a
Only UserId
userId)
    Only UserId
noteFolderId : [Only UserId]
_ -> StoreError -> ExceptT StoreError IO ()
forall a. StoreError -> ExceptT StoreError IO a
forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError (StoreError -> ExceptT StoreError IO ())
-> StoreError -> ExceptT StoreError IO ()
forall a b. (a -> b) -> a -> b
$ UserId -> StoreError
SENoteFolderAlreadyExists UserId
noteFolderId

getUserNoteFolderId :: DB.Connection -> User -> ExceptT StoreError IO NoteFolderId
getUserNoteFolderId :: Connection -> User -> ExceptT StoreError IO UserId
getUserNoteFolderId Connection
db User {UserId
userId :: User -> UserId
userId :: UserId
userId} =
  IO (Either StoreError UserId) -> ExceptT StoreError IO UserId
forall e (m :: * -> *) a. m (Either e a) -> ExceptT e m a
ExceptT (IO (Either StoreError UserId) -> ExceptT StoreError IO UserId)
-> (IO [Only UserId] -> IO (Either StoreError UserId))
-> IO [Only UserId]
-> ExceptT StoreError IO UserId
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Only UserId -> UserId)
-> StoreError -> IO [Only UserId] -> IO (Either StoreError UserId)
forall a b e. (a -> b) -> e -> IO [a] -> IO (Either e b)
firstRow Only UserId -> UserId
forall a. Only a -> a
fromOnly StoreError
SEUserNoteFolderNotFound (IO [Only UserId] -> ExceptT StoreError IO UserId)
-> IO [Only UserId] -> ExceptT StoreError IO UserId
forall a b. (a -> b) -> a -> b
$
    Connection -> Query -> Only UserId -> IO [Only UserId]
forall q r.
(ToRow q, FromRow r) =>
Connection -> Query -> q -> IO [r]
DB.query Connection
db Query
"SELECT note_folder_id FROM note_folders WHERE user_id = ?" (UserId -> Only UserId
forall a. a -> Only a
Only UserId
userId)

getNoteFolder :: DB.Connection -> User -> NoteFolderId -> ExceptT StoreError IO NoteFolder
getNoteFolder :: Connection -> User -> UserId -> ExceptT StoreError IO NoteFolder
getNoteFolder Connection
db User {UserId
userId :: User -> UserId
userId :: UserId
userId} UserId
noteFolderId =
  IO (Either StoreError NoteFolder)
-> ExceptT StoreError IO NoteFolder
forall e (m :: * -> *) a. m (Either e a) -> ExceptT e m a
ExceptT (IO (Either StoreError NoteFolder)
 -> ExceptT StoreError IO NoteFolder)
-> (IO [(UTCTime, UTCTime, UTCTime, BoolInt, BoolInt)]
    -> IO (Either StoreError NoteFolder))
-> IO [(UTCTime, UTCTime, UTCTime, BoolInt, BoolInt)]
-> ExceptT StoreError IO NoteFolder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((UTCTime, UTCTime, UTCTime, BoolInt, BoolInt) -> NoteFolder)
-> StoreError
-> IO [(UTCTime, UTCTime, UTCTime, BoolInt, BoolInt)]
-> IO (Either StoreError NoteFolder)
forall a b e. (a -> b) -> e -> IO [a] -> IO (Either e b)
firstRow (UTCTime, UTCTime, UTCTime, BoolInt, BoolInt) -> NoteFolder
toNoteFolder (UserId -> StoreError
SENoteFolderNotFound UserId
noteFolderId) (IO [(UTCTime, UTCTime, UTCTime, BoolInt, BoolInt)]
 -> ExceptT StoreError IO NoteFolder)
-> IO [(UTCTime, UTCTime, UTCTime, BoolInt, BoolInt)]
-> ExceptT StoreError IO NoteFolder
forall a b. (a -> b) -> a -> b
$
    Connection
-> Query
-> (UserId, UserId)
-> IO [(UTCTime, UTCTime, UTCTime, BoolInt, BoolInt)]
forall q r.
(ToRow q, FromRow r) =>
Connection -> Query -> q -> IO [r]
DB.query
      Connection
db
      [sql|
        SELECT
          created_at, updated_at, chat_ts, favorite, unread_chat
        FROM note_folders
        WHERE user_id = ?
          AND note_folder_id = ?
      |]
      (UserId
userId, UserId
noteFolderId)
  where
    toNoteFolder :: (UTCTime, UTCTime, UTCTime, BoolInt, BoolInt) -> NoteFolder
toNoteFolder (UTCTime
createdAt, UTCTime
updatedAt, UTCTime
chatTs, BI Bool
favorite, BI Bool
unread) =
      NoteFolder {UserId
noteFolderId :: UserId
noteFolderId :: UserId
noteFolderId, UserId
userId :: UserId
userId :: UserId
userId, UTCTime
createdAt :: UTCTime
createdAt :: UTCTime
createdAt, UTCTime
updatedAt :: UTCTime
updatedAt :: UTCTime
updatedAt, UTCTime
chatTs :: UTCTime
chatTs :: UTCTime
chatTs, Bool
favorite :: Bool
favorite :: Bool
favorite, Bool
unread :: Bool
unread :: Bool
unread}

updateNoteFolderUnreadChat :: DB.Connection -> User -> NoteFolder -> Bool -> IO ()
updateNoteFolderUnreadChat :: Connection -> User -> NoteFolder -> Bool -> IO ()
updateNoteFolderUnreadChat Connection
db User {UserId
userId :: User -> UserId
userId :: UserId
userId} NoteFolder {UserId
noteFolderId :: NoteFolder -> UserId
noteFolderId :: UserId
noteFolderId} Bool
unreadChat = do
  UTCTime
updatedAt <- IO UTCTime
getCurrentTime
  Connection -> Query -> (BoolInt, UTCTime, UserId, UserId) -> IO ()
forall q. ToRow q => Connection -> Query -> q -> IO ()
DB.execute Connection
db Query
"UPDATE note_folders SET unread_chat = ?, updated_at = ? WHERE user_id = ? AND note_folder_id = ?" (Bool -> BoolInt
BI Bool
unreadChat, UTCTime
updatedAt, UserId
userId, UserId
noteFolderId)

deleteNoteFolderFiles :: DB.Connection -> UserId -> NoteFolder -> IO ()
deleteNoteFolderFiles :: Connection -> UserId -> NoteFolder -> IO ()
deleteNoteFolderFiles Connection
db UserId
userId NoteFolder {UserId
noteFolderId :: NoteFolder -> UserId
noteFolderId :: UserId
noteFolderId} = do
  Connection -> Query -> (UserId, UserId, UserId) -> IO ()
forall q. ToRow q => Connection -> Query -> q -> IO ()
DB.execute
    Connection
db
    [sql|
      DELETE FROM files
      WHERE user_id = ?
        AND chat_item_id IN (
          SELECT chat_item_id FROM chat_items WHERE user_id = ? AND note_folder_id = ?
        )
    |]
    (UserId
userId, UserId
userId, UserId
noteFolderId)

deleteNoteFolderCIs :: DB.Connection -> User -> NoteFolder -> IO ()
deleteNoteFolderCIs :: Connection -> User -> NoteFolder -> IO ()
deleteNoteFolderCIs Connection
db User {UserId
userId :: User -> UserId
userId :: UserId
userId} NoteFolder {UserId
noteFolderId :: NoteFolder -> UserId
noteFolderId :: UserId
noteFolderId} =
  Connection -> Query -> (UserId, UserId) -> IO ()
forall q. ToRow q => Connection -> Query -> q -> IO ()
DB.execute Connection
db Query
"DELETE FROM chat_items WHERE user_id = ? AND note_folder_id = ?" (UserId
userId, UserId
noteFolderId)