ihp-ide-1.4.0: IHP/IDE/SchemaDesigner/Controller/Migrations.hs
module IHP.IDE.SchemaDesigner.Controller.Migrations where
import IHP.ControllerPrelude
import IHP.IDE.ToolServer.Types
import IHP.IDE.SchemaDesigner.View.Migrations.Index
import IHP.IDE.SchemaDesigner.View.Migrations.New
import IHP.IDE.SchemaDesigner.View.Migrations.Edit
import IHP.IDE.SchemaDesigner.View.Layout (schemaDesignerLayout)
import qualified Data.Text.IO as Text
import qualified IHP.SchemaMigration as SchemaMigration
import qualified IHP.IDE.CodeGen.MigrationGenerator as MigrationGenerator
import IHP.IDE.CodeGen.Controller
import IHP.IDE.ToolServer.Helper.Controller (openEditor, clearDatabaseNeedsMigration)
import IHP.Log.Types
import qualified Control.Exception.Safe as Exception
import qualified System.Directory as Directory
import qualified Database.PostgreSQL.Simple as PG
instance Controller MigrationsController where
beforeAction = setLayout schemaDesignerLayout
action MigrationsAction = do
migrations <- findRecentMigrations
migratedRevisions <- findMigratedRevisions
migrationsWithSql <- forM migrations $ \migration -> do
sql <- readSqlStatements migration
pure (migration, sql)
lastError <- getSessionAndClear "last_migraton_error"
render IndexView { .. }
action NewMigrationAction = do
let description = paramOrDefault "" "description"
(_, plan) <- MigrationGenerator.buildPlan description Nothing
let runMigration = paramOrDefault True "runMigration"
render NewView { .. }
action CreateMigrationAction = do
let description = paramOrDefault "" "description"
let sqlStatements = paramOrNothing "sqlStatements"
(revision, plan) <- MigrationGenerator.buildPlan description sqlStatements
let path = MigrationGenerator.migrationPathFromPlan plan
executePlan plan
let createOnly = paramOrDefault False "createOnly"
if createOnly
then do
setSuccessMessage ("Migration generated: " <> path)
openEditor path 0 0
else do
result <- Exception.try (migrateAppDB revision)
case result of
Left (exception :: SomeException) -> do
let errorMessage = case fromException exception of
Just (exception :: EnhancedSqlError) -> cs exception.sqlError.sqlErrorMsg
Nothing -> tshow exception
setErrorMessage errorMessage
redirectTo MigrationsAction
Right _ -> do
clearDatabaseNeedsMigration
redirectTo MigrationsAction
redirectTo MigrationsAction
action EditMigrationAction { migrationId } = do
migration <- findMigrationByRevision migrationId
sqlStatements <- readSqlStatements migration
render EditView { .. }
action UpdateMigrationAction { migrationId } = do
migration <- findMigrationByRevision migrationId
let sqlStatements = param "sqlStatements"
migrationFilePath <- SchemaMigration.migrationPath migration
Text.writeFile (cs migrationFilePath) sqlStatements
redirectTo MigrationsAction
action DeleteMigrationAction { migrationId } = do
migration <- findMigrationByRevision migrationId
path <- cs <$> SchemaMigration.migrationPath migration
Directory.removeFile path
redirectTo MigrationsAction
action RunMigrationAction { migrationId } = do
migration <- findMigrationByRevision migrationId
result <- Exception.try (migrateAppDB migrationId)
case result of
Left (exception :: SomeException) -> do
let errorMessage = case fromException exception of
Just (exception :: EnhancedSqlError) -> cs exception.sqlError.sqlErrorMsg
Nothing -> tshow exception
setErrorMessage errorMessage
redirectTo MigrationsAction
Right _ -> do
clearDatabaseNeedsMigration
redirectTo MigrationsAction
readSqlStatements :: SchemaMigration.Migration -> IO Text
readSqlStatements migration = do
migrationFilePath <- (SchemaMigration.migrationPath migration)
Text.readFile (cs migrationFilePath)
findRecentMigrations :: IO [SchemaMigration.Migration]
findRecentMigrations = take 20 . reverse <$> SchemaMigration.findAllMigrations
findMigrationByRevision :: Int -> IO SchemaMigration.Migration
findMigrationByRevision migrationRevision = do
migrations <- findRecentMigrations
let (Just migration) = migrations |> find (\SchemaMigration.Migration { revision } -> revision == migrationRevision)
pure migration
migrateAppDB :: Int -> IO ()
migrateAppDB revision = withAppModelContext do
let minimumRevision = Just (revision - 1)
SchemaMigration.migrate SchemaMigration.MigrateOptions { minimumRevision }
findMigratedRevisions :: IO [Int]
findMigratedRevisions = emptyListIfTablesDoesntExists (withAppModelContext SchemaMigration.findMigratedRevisions)
where
-- The schema_migrations table might not have been created yet
-- In that case there cannot be any migrations that have been run yet
emptyListIfTablesDoesntExists operation = do
result <- Exception.try operation
case result of
Left (EnhancedSqlError { sqlError }) | sqlError.sqlErrorMsg == "relation \"schema_migrations\" does not exist" -> pure []
Right result -> pure result
withAppModelContext :: ((?modelContext :: ModelContext) => IO result) -> IO result
withAppModelContext inner =
Exception.bracket initModelContext cleanupModelContext callback
where
callback (frameworkConfig, logger, modelContext) = let ?modelContext = modelContext in inner
initModelContext = do
frameworkConfig <- buildFrameworkConfig (pure ())
logger <- defaultLogger
modelContext <- createModelContext
(frameworkConfig.dbPoolIdleTime)
(frameworkConfig.dbPoolMaxConnections)
(frameworkConfig.databaseUrl)
logger
pure (frameworkConfig, logger, modelContext)
cleanupModelContext (frameworkConfig, logger, modelContext) = do
logger |> cleanup