From 9b0c8b06583b478bf4b7a19d9e2397ce6734a974 Mon Sep 17 00:00:00 2001 From: Kursat Aktas Date: Mon, 18 Nov 2024 19:53:02 +0300 Subject: [PATCH 1/5] Introducing IHP Guru on Gurubase.io Signed-off-by: Kursat Aktas --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 66a463077..bdb43dd0e 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,10 @@ + + + +

From f59b36f448cc0ee80684df56f4b7a90be56b0df2 Mon Sep 17 00:00:00 2001 From: Montmorency Date: Tue, 19 Nov 2024 20:52:11 +0000 Subject: [PATCH 2/5] Migration path Env Var. (#2015) * changed migrations path to detect an environemnt variable or use default. * updated docs. * updated options and migrate.nix. * fix lifting cs into IO. * updates to ihp.nix * recommenting testing flags in ihp.nix --- Guide/database-migrations.markdown | 14 ++++++++ IHP/SchemaMigration.hs | 11 ++++-- NixSupport/nixosModules/options.nix | 36 +++++++++++-------- NixSupport/nixosModules/services/migrate.nix | 3 +- .../SchemaDesigner/Controller/Migrations.hs | 14 ++++---- 5 files changed, 53 insertions(+), 25 deletions(-) diff --git a/Guide/database-migrations.markdown b/Guide/database-migrations.markdown index fa65e2b8a..692ef2f80 100644 --- a/Guide/database-migrations.markdown +++ b/Guide/database-migrations.markdown @@ -73,6 +73,20 @@ migrate A good value for `MINIMUM_REVISION` is typically the unix timestamp of the time when the database was initially created. + +### IHP MIGRATIONS DIR + +In production when running the migrations binary it is sometimes convenient to have all your Migrations in a non-standard place: +e.g. if you need to push migrations onto production server without rebuilding the application. There is an Environment variable +`IHP_MIGRATION_DIR` to accomplish this. + +``` +IHP_MIGRATION_DIR=path/to/my/migration/dir +``` + +This can be set in the environment attribute set of your IHP app flake. + + ## Common Issues ### ALTER TYPE ... ADD cannot run inside a transaction block diff --git a/IHP/SchemaMigration.hs b/IHP/SchemaMigration.hs index 942ed67ae..48f5ffab3 100644 --- a/IHP/SchemaMigration.hs +++ b/IHP/SchemaMigration.hs @@ -12,6 +12,7 @@ import qualified Data.Text.IO as Text import IHP.ModelSupport hiding (withTransaction) import qualified Data.Char as Char import IHP.Log.Types +import IHP.EnvVar data Migration = Migration { revision :: Int @@ -37,7 +38,9 @@ migrate options = do -- All queries are executed inside a database transaction to make sure that it can be restored when something goes wrong. runMigration :: (?modelContext :: ModelContext) => Migration -> IO () runMigration migration@Migration { revision, migrationFile } = do - migrationSql <- Text.readFile (cs $ migrationPath migration) + -- | User can specify migrations directory as environment variable (defaults to /Application/Migrations/...) + migrationFilePath <- migrationPath migration + migrationSql <- Text.readFile (cs migrationFilePath) let fullSql = [trimming| BEGIN; @@ -123,5 +126,7 @@ pathToMigration fileName = case revision of |> fmap textToInt |> join -migrationPath :: Migration -> Text -migrationPath Migration { migrationFile } = "Application/Migration/" <> migrationFile +migrationPath :: Migration -> IO Text +migrationPath Migration { migrationFile } = do + migrationDir <- envOrDefault "IHP_MIGRATION_DIR" "Application/Migration/" + pure (migrationDir <> migrationFile) diff --git a/NixSupport/nixosModules/options.nix b/NixSupport/nixosModules/options.nix index 952d571f6..310bc1d8e 100644 --- a/NixSupport/nixosModules/options.nix +++ b/NixSupport/nixosModules/options.nix @@ -13,63 +13,70 @@ with lib; type = types.str; default = "https://${config.services.ihp.domain}"; }; - + migrations = mkOption { type = types.path; }; - + schema = mkOption { type = types.path; }; - + fixtures = mkOption { type = types.path; }; - + httpsEnabled = mkOption { type = types.bool; default = true; }; - + databaseName = mkOption { type = types.str; default = "app"; }; - + databaseUser = mkOption { type = types.str; default = "ihp"; }; - + databaseUrl = mkOption { type = types.str; }; - + # https://ihp.digitallyinduced.com/Guide/database-migrations.html#skipping-old-migrations minimumRevision = mkOption { type = types.int; default = 0; }; - + + # https://ihp.digitallyinduced.com/Guide/database-migrations.html#ihp-migrations-dir + ihpMigrationDir = mkOption { + type = types.str; + default = "Application/Migration/"; + }; + + ihpEnv = mkOption { type = types.str; default = "Production"; }; - + appPort = mkOption { type = types.int; default = 8000; }; - + requestLoggerIPAddrSource = mkOption { type = types.str; default = "FromHeader"; }; - + sessionSecret = mkOption { type = types.str; }; - + additionalEnvVars = mkOption { type = types.attrs; default = {}; @@ -79,7 +86,7 @@ with lib; type = types.package; default = if config.services.ihp.optimized then self.packages."${pkgs.system}".optimized-prod-server else self.packages."${pkgs.system}".default; }; - + optimized = mkOption { type = types.bool; default = false; @@ -91,4 +98,3 @@ with lib; }; }; } - diff --git a/NixSupport/nixosModules/services/migrate.nix b/NixSupport/nixosModules/services/migrate.nix index a5f6d9ad5..ecd5d7cbe 100644 --- a/NixSupport/nixosModules/services/migrate.nix +++ b/NixSupport/nixosModules/services/migrate.nix @@ -22,6 +22,7 @@ in environment = { DATABASE_URL = cfg.databaseUrl; MINIMUM_REVISION = "${toString cfg.minimumRevision}"; + IHP_MIGRATION_DIR = cfg.ihpMigrationDir; }; }; -} \ No newline at end of file +} diff --git a/ihp-ide/IHP/IDE/SchemaDesigner/Controller/Migrations.hs b/ihp-ide/IHP/IDE/SchemaDesigner/Controller/Migrations.hs index 41f1d0d81..940c903ed 100644 --- a/ihp-ide/IHP/IDE/SchemaDesigner/Controller/Migrations.hs +++ b/ihp-ide/IHP/IDE/SchemaDesigner/Controller/Migrations.hs @@ -61,7 +61,7 @@ instance Controller MigrationsController where let errorMessage = case fromException exception of Just (exception :: EnhancedSqlError) -> cs exception.sqlError.sqlErrorMsg Nothing -> tshow exception - + setErrorMessage errorMessage redirectTo MigrationsAction Right _ -> do @@ -79,14 +79,14 @@ instance Controller MigrationsController where action UpdateMigrationAction { migrationId } = do migration <- findMigrationByRevision migrationId let sqlStatements = param "sqlStatements" - - Text.writeFile (cs $ SchemaMigration.migrationPath migration) sqlStatements + migrationFilePath <- SchemaMigration.migrationPath migration + Text.writeFile (cs migrationFilePath) sqlStatements redirectTo MigrationsAction action DeleteMigrationAction { migrationId } = do migration <- findMigrationByRevision migrationId - let path = cs $ SchemaMigration.migrationPath migration + path <- cs <$> SchemaMigration.migrationPath migration Directory.removeFile path @@ -101,7 +101,7 @@ instance Controller MigrationsController where let errorMessage = case fromException exception of Just (exception :: EnhancedSqlError) -> cs exception.sqlError.sqlErrorMsg Nothing -> tshow exception - + setErrorMessage errorMessage redirectTo MigrationsAction Right _ -> do @@ -109,7 +109,9 @@ instance Controller MigrationsController where redirectTo MigrationsAction readSqlStatements :: SchemaMigration.Migration -> IO Text -readSqlStatements migration = Text.readFile (cs $ SchemaMigration.migrationPath migration) +readSqlStatements migration = do + migrationFilePath <- (SchemaMigration.migrationPath migration) + pure Text.readFile (migrationFilePath) findRecentMigrations :: IO [SchemaMigration.Migration] findRecentMigrations = take 20 . reverse <$> SchemaMigration.findAllMigrations From 7a4c95f94a9138b99c9a8bdaac953cdca8f72025 Mon Sep 17 00:00:00 2001 From: Marc Scholten Date: Tue, 19 Nov 2024 12:55:18 -0800 Subject: [PATCH 3/5] fixed findAllMigrations still trying to access Application/Migration --- IHP/SchemaMigration.hs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/IHP/SchemaMigration.hs b/IHP/SchemaMigration.hs index 48f5ffab3..22ac3d645 100644 --- a/IHP/SchemaMigration.hs +++ b/IHP/SchemaMigration.hs @@ -99,7 +99,8 @@ findMigratedRevisions = map (\[revision] -> revision) <$> sqlQuery "SELECT revis -- The result is sorted so that the oldest revision is first. findAllMigrations :: IO [Migration] findAllMigrations = do - directoryFiles <- Directory.listDirectory "Application/Migration" + migrationDir <- detectMigrationDir + directoryFiles <- Directory.listDirectory (cs migrationDir) directoryFiles |> map cs |> filter (\path -> ".sql" `isSuffixOf` path) @@ -128,5 +129,10 @@ pathToMigration fileName = case revision of migrationPath :: Migration -> IO Text migrationPath Migration { migrationFile } = do - migrationDir <- envOrDefault "IHP_MIGRATION_DIR" "Application/Migration/" + migrationDir <- detectMigrationDir pure (migrationDir <> migrationFile) + +detectMigrationDir :: IO Text +detectMigrationDir = + envOrDefault "IHP_MIGRATION_DIR" "Application/Migration/" + From 5174b90852019ed1ff44601b82de86bcb4c588a9 Mon Sep 17 00:00:00 2001 From: Marc Scholten Date: Tue, 19 Nov 2024 13:02:48 -0800 Subject: [PATCH 4/5] fixed error in readSqlStatements --- ihp-ide/IHP/IDE/SchemaDesigner/Controller/Migrations.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ihp-ide/IHP/IDE/SchemaDesigner/Controller/Migrations.hs b/ihp-ide/IHP/IDE/SchemaDesigner/Controller/Migrations.hs index 940c903ed..99074aa65 100644 --- a/ihp-ide/IHP/IDE/SchemaDesigner/Controller/Migrations.hs +++ b/ihp-ide/IHP/IDE/SchemaDesigner/Controller/Migrations.hs @@ -111,7 +111,7 @@ instance Controller MigrationsController where readSqlStatements :: SchemaMigration.Migration -> IO Text readSqlStatements migration = do migrationFilePath <- (SchemaMigration.migrationPath migration) - pure Text.readFile (migrationFilePath) + Text.readFile (cs migrationFilePath) findRecentMigrations :: IO [SchemaMigration.Migration] findRecentMigrations = take 20 . reverse <$> SchemaMigration.findAllMigrations From 611b881d29cdb4be32887c356332510d06951273 Mon Sep 17 00:00:00 2001 From: Marc Scholten Date: Tue, 19 Nov 2024 16:47:00 -0800 Subject: [PATCH 5/5] DataSync: fixed connections closed when it's still needed --- lib/IHP/DataSync/ihp-datasync.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/IHP/DataSync/ihp-datasync.js b/lib/IHP/DataSync/ihp-datasync.js index 345834fb2..d568e6727 100644 --- a/lib/IHP/DataSync/ihp-datasync.js +++ b/lib/IHP/DataSync/ihp-datasync.js @@ -352,6 +352,11 @@ class DataSubscription { return; } + // Set isClosed early as we need to prevent a second close() from triggering another DeleteDataSubscription message + // also we don't want to receive any further messages, and onMessage will not process if isClosed == true + this.isClosed = true; + this.onClose(); + const dataSyncController = DataSyncController.getInstance(); const { subscriptionId } = await dataSyncController.sendMessage({ tag: 'DeleteDataSubscription', subscriptionId: this.subscriptionId }); @@ -360,10 +365,7 @@ class DataSubscription { dataSyncController.removeEventListener('reconnect', this.onDataSyncReconnect); dataSyncController.dataSubscriptions.splice(dataSyncController.dataSubscriptions.indexOf(this), 1); - this.isClosed = true; this.isConnected = false; - - this.onClose(); } onDataSyncClosed() { @@ -425,7 +427,9 @@ class DataSubscription { return () => { this.subscribers.splice(this.subscribers.indexOf(callback), 1); - this.closeIfNotUsed(); + // We delay the close as react could be re-rendering a component + // we garbage collect this connecetion once it's clearly not used anymore + setTimeout(this.closeIfNotUsed.bind(this), 1000); } }