espial-0.0.40: src/Model/FileNote.hs
module Model.FileNote where
import ClassyPrelude.Yesod hiding (Value, exists, groupBy, on, (<=.), (==.), (>=.), (||.))
import Control.Monad.Fail (MonadFail)
import Data.Aeson qualified as A
import Data.Aeson.Types qualified as A (parseFail)
import Database.Persist.Sql (fromSqlKey)
import Model
import Model.Custom
import System.Directory (listDirectory)
import Types
import Util (format8601z, parseTimeText)
-- * FileNotes
data FileNote = FileNote
{ fileNoteId :: !Text,
fileNoteSlug :: !(Maybe Text),
fileNoteTitle :: !Text,
fileNoteText :: !Text,
fileNoteLength :: !Int,
fileNoteIsMarkdown :: !(Maybe Bool),
fileNoteShared :: !(Maybe Bool),
fileNoteCreatedAt :: !UTCTime,
fileNoteUpdatedAt :: !UTCTime
}
deriving (Show, Eq, Ord)
instance FromJSON FileNote where
parseJSON (Object o) =
FileNote
<$> o
.: "id"
<*> o
A..:? "slug"
<*> o
.: "title"
<*> o
.: "text"
<*> o
.: "length"
<*> o
A..:? "is_markdown"
<*> o
A..:? "shared"
<*> (readFileNoteTime =<< o .: "created_at")
<*> (readFileNoteTime =<< o .: "updated_at")
parseJSON _ = A.parseFail "bad parse"
instance ToJSON FileNote where
toJSON FileNote {..} =
object
[ "id" .= fileNoteId,
"slug" .= fileNoteSlug,
"title" .= fileNoteTitle,
"text" .= fileNoteText,
"length" .= fileNoteLength,
"is_markdown" .= fileNoteIsMarkdown,
"shared" .= fileNoteShared,
"created_at" .= showFileNoteTime fileNoteCreatedAt,
"updated_at" .= showFileNoteTime fileNoteUpdatedAt
]
toEncoding FileNote {..} =
A.pairs
( "id"
.= fileNoteId
<> "slug"
.= fileNoteSlug
<> "title"
.= fileNoteTitle
<> "text"
.= fileNoteText
<> "length"
.= fileNoteLength
<> "is_markdown"
.= fileNoteIsMarkdown
<> "shared"
.= fileNoteShared
<> "created_at"
.= showFileNoteTime fileNoteCreatedAt
<> "updated_at"
.= showFileNoteTime fileNoteUpdatedAt
)
readFileNoteTime ::
(MonadFail m, Alternative m) =>
String -> m UTCTime
readFileNoteTime = parseTimeText . pack
showFileNoteTime :: UTCTime -> String
showFileNoteTime = unpack . format8601z
fileNoteToNote :: UserId -> FileNote -> IO Note
fileNoteToNote user FileNote {..} = do
slug <- mkNtSlug
pure
$ Note
{ noteUserId = user,
noteSlug = slug,
noteLength = fileNoteLength,
noteTitle = fileNoteTitle,
noteText = fileNoteText,
noteIsMarkdown = fromMaybe False fileNoteIsMarkdown,
noteShared = fromMaybe False fileNoteShared,
noteCreated = fileNoteCreatedAt,
noteUpdated = fileNoteUpdatedAt
}
readDirFileNotes :: (MonadIO m) => FilePath -> m (Either String [FileNote])
readDirFileNotes fdir = liftIO $ do
files <- listDirectory fdir
noteBSS <- mapM (readFile . (fdir </>)) files
pure (mapM (A.eitherDecode' . fromStrict) noteBSS)
readFileNotes :: (MonadIO m) => FilePath -> m (Either String [FileNote])
readFileNotes fpath =
A.eitherDecode' . fromStrict <$> readFile fpath
insertFileNotes :: Key User -> [FileNote] -> DB Int
insertFileNotes userId fnotes = do
notes <- liftIO $ mapM (fileNoteToNote userId) fnotes
ins <- forM (zip fnotes notes) $ \(fnote, note) -> do
existing <- case fileNoteSlug fnote of
Just fslug -> checkUnique note {noteSlug = NtSlug fslug}
Nothing -> pure Nothing
case existing of
Just _ -> pure 0
Nothing -> insertUnique note >> pure 1
pure (sum ins)
insertDirFileNotes :: Key User -> FilePath -> DB (Either String Int)
insertDirFileNotes userId noteDirectory = do
mfnotes <- liftIO $ readDirFileNotes noteDirectory
case mfnotes of
Left e -> pure $ Left e
Right fnotes -> Right <$> insertFileNotes userId fnotes
noteToFileNote :: Entity Note -> FileNote
noteToFileNote (Entity k Note {..}) =
FileNote
{ fileNoteId = tshow (fromSqlKey k),
fileNoteSlug = Just (unNtSlug noteSlug),
fileNoteTitle = noteTitle,
fileNoteText = noteText,
fileNoteLength = noteLength,
fileNoteIsMarkdown = Just noteIsMarkdown,
fileNoteShared = Just noteShared,
fileNoteCreatedAt = noteCreated,
fileNoteUpdatedAt = noteUpdated
}
getFileNotes :: Key User -> DB [FileNote]
getFileNotes user = fmap noteToFileNote <$> allUserNotes user
exportFileNotes :: Key User -> FilePath -> DB ()
exportFileNotes user fpath =
liftIO . A.encodeFile fpath =<< getFileNotes user