{-# OPTIONS_GHC -fno-warn-name-shadowing #-} module Main where import Types import Model import ModelCustom import qualified Database.Persist as P import qualified Database.Persist.Sqlite as P import ClassyPrelude import Lens.Micro import Options.Generic import qualified Options.Applicative as OA import qualified Data.Text as T data Password = PasswordText Text | PasswordFile FilePath deriving (Show, Read) parsePassword :: OA.Parser Password parsePassword = passwordText <|> passwordFile where passwordText = PasswordText <$> OA.strOption ( OA.long "userPassword" <> OA.metavar "PASSWORD" <> OA.help "Password in plain-text" ) passwordFile = PasswordFile <$> OA.strOption ( OA.long "userPasswordFile" <> OA.metavar "FILE" <> OA.help "Password file" ) instance ParseFields Password instance ParseRecord Password where parseRecord = fmap getOnly parseRecord instance ParseField Password where parseField _ _ _ _ = parsePassword data MigrationOpts = CreateDB { conn :: Text } | CreateUser { conn :: Text , userName :: Text , userPassword :: Password , privateDefault :: Maybe Bool , archiveDefault :: Maybe Bool , privacyLock :: Maybe Bool } | CreateApiKey { conn :: Text , userName :: Text } | DeleteUser { conn :: Text , userName :: Text } | DeleteApiKey { conn :: Text , userName :: Text } | ImportBookmarks { conn :: Text , userName :: Text , bookmarkFile :: FilePath } | ImportFirefoxBookmarks { conn :: Text , userName :: Text , bookmarkFile :: FilePath } | ExportBookmarks { conn :: Text , userName :: Text , bookmarkFile :: FilePath } | ImportNotes { conn :: Text , userName :: Text , noteDirectory :: FilePath } | PrintMigrateDB { conn :: Text } deriving (Generic, Show) instance ParseRecord MigrationOpts main :: IO () main = do args <- getRecord "Migrations" case args of PrintMigrateDB {..} -> P.runSqlite conn dumpMigration CreateDB {..} -> do let connInfo = P.mkSqliteConnectionInfo conn & set P.fkEnabled False P.runSqliteInfo connInfo runMigrations CreateUser{..} -> P.runSqlite conn $ do passwordText <- liftIO . fmap T.strip $ case userPassword of PasswordText s -> pure s PasswordFile f -> readFileUtf8 f hash' <- liftIO (hashPassword passwordText) void $ P.upsertBy (UniqueUserName userName) (User userName hash' Nothing False False False) [ UserPasswordHash P.=. hash' , UserPrivateDefault P.=. fromMaybe False privateDefault , UserArchiveDefault P.=. fromMaybe False archiveDefault , UserPrivacyLock P.=. fromMaybe False privacyLock ] pure () :: DB () CreateApiKey {..} -> P.runSqlite conn $ do apiKey@(ApiKey plainKey) <- liftIO generateApiKey muser <- P.getBy (UniqueUserName userName) case muser of Nothing -> liftIO (print (userName ++ " not found")) Just (P.Entity uid _) -> do -- API key is only displayed once after creation, -- since it is stored in hashed form. let hashedKey = hashApiKey apiKey P.update uid [ UserApiToken P.=. Just hashedKey ] liftIO $ print plainKey DeleteApiKey {..} -> P.runSqlite conn $ do muser <- P.getBy (UniqueUserName userName) case muser of Nothing -> liftIO (print (userName ++ " not found")) Just (P.Entity uid _) -> do P.update uid [ UserApiToken P.=. Nothing ] DeleteUser {..} -> P.runSqlite conn $ do muser <- P.getBy (UniqueUserName userName) case muser of Nothing -> liftIO (print (userName ++ "not found")) Just (P.Entity uid _) -> do P.delete uid pure () :: DB () ExportBookmarks {..} -> P.runSqlite conn $ do muser <- P.getBy (UniqueUserName userName) case muser of Just (P.Entity uid _) -> exportFileBookmarks uid bookmarkFile Nothing -> liftIO (print (userName ++ "not found")) ImportBookmarks {..} -> P.runSqlite conn $ do muser <- P.getBy (UniqueUserName userName) case muser of Just (P.Entity uid _) -> do result <- insertFileBookmarks uid bookmarkFile case result of Left e -> liftIO (print e) Right n -> liftIO (print (show n ++ " bookmarks imported.")) Nothing -> liftIO (print (userName ++ "not found")) ImportFirefoxBookmarks {..} -> P.runSqlite conn $ do muser <- P.getBy (UniqueUserName userName) case muser of Just (P.Entity uid _) -> do result <- insertFFBookmarks uid bookmarkFile case result of Left e -> liftIO (print e) Right n -> liftIO (print (show n ++ " bookmarks imported.")) Nothing -> liftIO (print (userName ++ "not found")) ImportNotes {..} -> P.runSqlite conn $ do muser <- P.getBy (UniqueUserName userName) case muser of Just (P.Entity uid _) -> do result <- insertDirFileNotes uid noteDirectory case result of Left e -> liftIO (print e) Right n -> liftIO (print (show n ++ " notes imported.")) Nothing -> liftIO (print (userName ++ "not found"))