From dd60b20c998e35917f2810149ae17f95f3282e4a Mon Sep 17 00:00:00 2001 From: Kenshin13 <63159154+Kenshiin13@users.noreply.github.com> Date: Sun, 24 Aug 2025 17:20:20 +0200 Subject: [PATCH 1/7] feat: add auto-migrations on startup --- [core]/es_extended/fxmanifest.lua | 4 +- [core]/es_extended/server/functions.lua | 7 ++- [core]/es_extended/server/migration/main.lua | 30 +++++++++ .../server/migration/v1.13.3/ssn/main.lua | 61 +++++++++++++++++++ 4 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 [core]/es_extended/server/migration/main.lua create mode 100644 [core]/es_extended/server/migration/v1.13.3/ssn/main.lua diff --git a/[core]/es_extended/fxmanifest.lua b/[core]/es_extended/fxmanifest.lua index 4171d44a..3a04fea2 100644 --- a/[core]/es_extended/fxmanifest.lua +++ b/[core]/es_extended/fxmanifest.lua @@ -35,7 +35,9 @@ server_scripts { 'server/bridge/**/*.lua', 'server/modules/npwd.lua', - 'server/modules/createJob.lua' + 'server/modules/createJob.lua', + 'server/migration/**/main.lua', + 'server/migration/main.lua', } client_scripts { diff --git a/[core]/es_extended/server/functions.lua b/[core]/es_extended/server/functions.lua index dc4a8fe6..1577708c 100644 --- a/[core]/es_extended/server/functions.lua +++ b/[core]/es_extended/server/functions.lua @@ -818,8 +818,9 @@ function Core.IsPlayerAdmin(playerSrc) end -- Generates a unique 9-digit SSN in dashed format (XXX-XX-XXXX). +---@param skipUniqueCheck boolean? ---@return string -function Core.generateSSN() +function Core.generateSSN(skipUniqueCheck) local reservedSSNs = { ["078-05-1120"] = true, ["219-09-9999"] = true, @@ -852,6 +853,10 @@ function Core.generateSSN() goto continue end + if skipUniqueCheck then + return candidate + end + local exists = MySQL.scalar.await("SELECT 1 FROM `users` WHERE `ssn` = ? LIMIT 1", { candidate }) if not exists then diff --git a/[core]/es_extended/server/migration/main.lua b/[core]/es_extended/server/migration/main.lua new file mode 100644 index 00000000..b682d867 --- /dev/null +++ b/[core]/es_extended/server/migration/main.lua @@ -0,0 +1,30 @@ +RegisterCommand("resetmigrations", function(src) + if src > 0 then + print("^1[ERROR]^7 This command can only be run from the server console.") + return + end + + for version, _ in pairs(Migrations or {}) do + DeleteResourceKvp(("esx_migration:%s"):format(version)) + end + print("^2[SUCCESS]^7 Reset all migrations. This will re-run all migrations on the next server start.") +end) + +for esxVersion, migrations in pairs(Migrations or {}) do + ---@cast esxVersion string + ---@cast migrations table + + if GetResourceKvpInt(("esx_migration:%s"):format(esxVersion)) ~= 1 then + print(("^4[INFO]^7 Running migrations for ESX version %s"):format(esxVersion)) + + for migrationName, migration in pairs(migrations) do + local success, err = pcall(migration) + if not success then + error(("^1[ERROR]^7 Failed migration ^4['%s.%s']^7: %s"):format(esxVersion, migrationName, err)) + end + end + + SetResourceKvpInt(("esx_migration:%s"):format(esxVersion), 1) + print(("^2[SUCCESS]^7 Successfully completed migrations for ESX version %s"):format(esxVersion)) + end +end diff --git a/[core]/es_extended/server/migration/v1.13.3/ssn/main.lua b/[core]/es_extended/server/migration/v1.13.3/ssn/main.lua new file mode 100644 index 00000000..0f8734d5 --- /dev/null +++ b/[core]/es_extended/server/migration/v1.13.3/ssn/main.lua @@ -0,0 +1,61 @@ +Migrations = Migrations or {} +Migrations["v1.13.3"] = Migrations["v1.13.3"] or {} + +Migrations["v1.13.3"].ssn = function() + print("^4[esx_migration:v.1.13.3:ssn]^7 Adding SSN column to users table.") + local col = MySQL.scalar.await([[ + SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'users' + AND COLUMN_NAME = 'ssn' +]]) + + local idx = MySQL.scalar.await([[ + SELECT COUNT(*) + FROM INFORMATION_SCHEMA.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'users' + AND INDEX_NAME = 'unique_ssn' +]]) + + if col == 0 and idx == 0 then + MySQL.update.await([[ + ALTER TABLE `users` + ADD COLUMN `ssn` VARCHAR(11) NULL DEFAULT NULL AFTER `identifier`, + ADD UNIQUE KEY `unique_ssn` (`ssn`) + ]]) + elseif col == 0 then + MySQL.update.await("ALTER TABLE `users` ADD COLUMN `ssn` VARCHAR(11) NULL DEFAULT NULL AFTER `identifier`") + elseif idx == 0 then + MySQL.update.await("ALTER TABLE `users` ADD UNIQUE KEY `unique_ssn` (`ssn`)") + end + + + local Result = MySQL.query.await("SELECT `identifier` FROM `users` WHERE `ssn` IS NULL") + if #Result == 0 then + print("^4[esx_migration:v.1.13.3:ssn]^7 No users found without SSN, migration not needed.") + return + end + + print("^4[esx_migration:v.1.13.3:ssn]^7 Generating SSN for existing users.") + local GeneratedSSNs = {} + local Parameters = {} + for i = 1, #Result do + local ssn + repeat + ssn = Core.generateSSN(true) + until not GeneratedSSNs[ssn] + + GeneratedSSNs[ssn] = true + Parameters[i] = { ssn, Result[i].identifier } + end + + print("^4[esx_migration:v.1.13.3:ssn]^7 Updating users with generated SSN. This may take a minute...") + MySQL.prepare.await("UPDATE `users` SET `ssn` = ? WHERE `identifier` = ?", Parameters) + + print("^4[esx_migration:v.1.13.3:ssn]^7 Removing SSN default value.") + MySQL.update.await("ALTER TABLE `users` MODIFY `ssn` VARCHAR(11) NOT NULL") + + print(("^4[esx_migration:v.1.13.3:ssn]^7 Successfully migrated %d users."):format(#Parameters)) +end From 5cb6f35b117df3b0f8fba8e7578097b40b094afc Mon Sep 17 00:00:00 2001 From: Kenshin13 <63159154+Kenshiin13@users.noreply.github.com> Date: Sun, 24 Aug 2025 17:31:05 +0200 Subject: [PATCH 2/7] feat: add "restart required" print --- [core]/es_extended/server/migration/main.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/[core]/es_extended/server/migration/main.lua b/[core]/es_extended/server/migration/main.lua index b682d867..8fccf155 100644 --- a/[core]/es_extended/server/migration/main.lua +++ b/[core]/es_extended/server/migration/main.lua @@ -10,6 +10,8 @@ RegisterCommand("resetmigrations", function(src) print("^2[SUCCESS]^7 Reset all migrations. This will re-run all migrations on the next server start.") end) +local migrationsRan = 0 + for esxVersion, migrations in pairs(Migrations or {}) do ---@cast esxVersion string ---@cast migrations table @@ -26,5 +28,13 @@ for esxVersion, migrations in pairs(Migrations or {}) do SetResourceKvpInt(("esx_migration:%s"):format(esxVersion), 1) print(("^2[SUCCESS]^7 Successfully completed migrations for ESX version %s"):format(esxVersion)) + migrationsRan += 1 + end +end + +if migrationsRan > 0 then + while true do + print(("^4[INFO]^7 Ran migrations for %d ESX version(s). ^1Server restart required!^7"):format(migrationsRan)) + Wait(500) end end From 22ab3dc8df95e2f04243d0abc0a81bbcd61a75a4 Mon Sep 17 00:00:00 2001 From: Kenshin13 <63159154+Kenshiin13@users.noreply.github.com> Date: Sun, 24 Aug 2025 17:33:10 +0200 Subject: [PATCH 3/7] refactor: store migrations in core table --- [core]/es_extended/server/migration/main.lua | 4 ++-- [core]/es_extended/server/migration/v1.13.3/ssn/main.lua | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/[core]/es_extended/server/migration/main.lua b/[core]/es_extended/server/migration/main.lua index 8fccf155..304590eb 100644 --- a/[core]/es_extended/server/migration/main.lua +++ b/[core]/es_extended/server/migration/main.lua @@ -4,7 +4,7 @@ RegisterCommand("resetmigrations", function(src) return end - for version, _ in pairs(Migrations or {}) do + for version, _ in pairs(Core.Migrations or {}) do DeleteResourceKvp(("esx_migration:%s"):format(version)) end print("^2[SUCCESS]^7 Reset all migrations. This will re-run all migrations on the next server start.") @@ -12,7 +12,7 @@ end) local migrationsRan = 0 -for esxVersion, migrations in pairs(Migrations or {}) do +for esxVersion, migrations in pairs(Core.Migrations or {}) do ---@cast esxVersion string ---@cast migrations table diff --git a/[core]/es_extended/server/migration/v1.13.3/ssn/main.lua b/[core]/es_extended/server/migration/v1.13.3/ssn/main.lua index 0f8734d5..8b6d6ead 100644 --- a/[core]/es_extended/server/migration/v1.13.3/ssn/main.lua +++ b/[core]/es_extended/server/migration/v1.13.3/ssn/main.lua @@ -1,7 +1,7 @@ -Migrations = Migrations or {} -Migrations["v1.13.3"] = Migrations["v1.13.3"] or {} +Core.Migrations = Core.Migrations or {} +Core.Migrations["v1.13.3"] = Core.Migrations["v1.13.3"] or {} -Migrations["v1.13.3"].ssn = function() +Core.Migrations["v1.13.3"].ssn = function() print("^4[esx_migration:v.1.13.3:ssn]^7 Adding SSN column to users table.") local col = MySQL.scalar.await([[ SELECT COUNT(*) From 45308f5d490a37e262c88529e78d900d6842dae1 Mon Sep 17 00:00:00 2001 From: Kenshin13 <63159154+Kenshiin13@users.noreply.github.com> Date: Sun, 24 Aug 2025 17:35:56 +0200 Subject: [PATCH 4/7] refactor: only store migrations if not ran --- [core]/es_extended/server/migration/main.lua | 20 +++++++++---------- .../server/migration/v1.13.3/ssn/main.lua | 12 ++++++++--- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/[core]/es_extended/server/migration/main.lua b/[core]/es_extended/server/migration/main.lua index 304590eb..2f19ce8c 100644 --- a/[core]/es_extended/server/migration/main.lua +++ b/[core]/es_extended/server/migration/main.lua @@ -16,20 +16,18 @@ for esxVersion, migrations in pairs(Core.Migrations or {}) do ---@cast esxVersion string ---@cast migrations table - if GetResourceKvpInt(("esx_migration:%s"):format(esxVersion)) ~= 1 then - print(("^4[INFO]^7 Running migrations for ESX version %s"):format(esxVersion)) + print(("^4[INFO]^7 Running migrations for ESX version %s"):format(esxVersion)) - for migrationName, migration in pairs(migrations) do - local success, err = pcall(migration) - if not success then - error(("^1[ERROR]^7 Failed migration ^4['%s.%s']^7: %s"):format(esxVersion, migrationName, err)) - end + for migrationName, migration in pairs(migrations) do + local success, err = pcall(migration) + if not success then + error(("^1[ERROR]^7 Failed migration ^4['%s.%s']^7: %s"):format(esxVersion, migrationName, err)) end - - SetResourceKvpInt(("esx_migration:%s"):format(esxVersion), 1) - print(("^2[SUCCESS]^7 Successfully completed migrations for ESX version %s"):format(esxVersion)) - migrationsRan += 1 end + + SetResourceKvpInt(("esx_migration:%s"):format(esxVersion), 1) + print(("^2[SUCCESS]^7 Successfully completed migrations for ESX version %s"):format(esxVersion)) + migrationsRan += 1 end if migrationsRan > 0 then diff --git a/[core]/es_extended/server/migration/v1.13.3/ssn/main.lua b/[core]/es_extended/server/migration/v1.13.3/ssn/main.lua index 8b6d6ead..24462ce1 100644 --- a/[core]/es_extended/server/migration/v1.13.3/ssn/main.lua +++ b/[core]/es_extended/server/migration/v1.13.3/ssn/main.lua @@ -1,7 +1,13 @@ -Core.Migrations = Core.Migrations or {} -Core.Migrations["v1.13.3"] = Core.Migrations["v1.13.3"] or {} +local esxVersion = "v1.13.3" -Core.Migrations["v1.13.3"].ssn = function() +if GetResourceKvpInt(("esx_migration:%s"):format(esxVersion)) == 1 then + return +end + +Core.Migrations = Core.Migrations or {} +Core.Migrations[esxVersion] = Core.Migrations[esxVersion] or {} + +Core.Migrations[esxVersion].ssn = function() print("^4[esx_migration:v.1.13.3:ssn]^7 Adding SSN column to users table.") local col = MySQL.scalar.await([[ SELECT COUNT(*) From ff8798a4418aa4b0ebd2cf40effe156e60dc8f06 Mon Sep 17 00:00:00 2001 From: Kenshin13 <63159154+Kenshiin13@users.noreply.github.com> Date: Sun, 24 Aug 2025 17:39:16 +0200 Subject: [PATCH 5/7] Revert "refactor: only store migrations if not ran" This reverts commit 45308f5d490a37e262c88529e78d900d6842dae1. --- [core]/es_extended/server/migration/main.lua | 20 ++++++++++--------- .../server/migration/v1.13.3/ssn/main.lua | 10 ++-------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/[core]/es_extended/server/migration/main.lua b/[core]/es_extended/server/migration/main.lua index 2f19ce8c..304590eb 100644 --- a/[core]/es_extended/server/migration/main.lua +++ b/[core]/es_extended/server/migration/main.lua @@ -16,18 +16,20 @@ for esxVersion, migrations in pairs(Core.Migrations or {}) do ---@cast esxVersion string ---@cast migrations table - print(("^4[INFO]^7 Running migrations for ESX version %s"):format(esxVersion)) + if GetResourceKvpInt(("esx_migration:%s"):format(esxVersion)) ~= 1 then + print(("^4[INFO]^7 Running migrations for ESX version %s"):format(esxVersion)) - for migrationName, migration in pairs(migrations) do - local success, err = pcall(migration) - if not success then - error(("^1[ERROR]^7 Failed migration ^4['%s.%s']^7: %s"):format(esxVersion, migrationName, err)) + for migrationName, migration in pairs(migrations) do + local success, err = pcall(migration) + if not success then + error(("^1[ERROR]^7 Failed migration ^4['%s.%s']^7: %s"):format(esxVersion, migrationName, err)) + end end - end - SetResourceKvpInt(("esx_migration:%s"):format(esxVersion), 1) - print(("^2[SUCCESS]^7 Successfully completed migrations for ESX version %s"):format(esxVersion)) - migrationsRan += 1 + SetResourceKvpInt(("esx_migration:%s"):format(esxVersion), 1) + print(("^2[SUCCESS]^7 Successfully completed migrations for ESX version %s"):format(esxVersion)) + migrationsRan += 1 + end end if migrationsRan > 0 then diff --git a/[core]/es_extended/server/migration/v1.13.3/ssn/main.lua b/[core]/es_extended/server/migration/v1.13.3/ssn/main.lua index 24462ce1..8b6d6ead 100644 --- a/[core]/es_extended/server/migration/v1.13.3/ssn/main.lua +++ b/[core]/es_extended/server/migration/v1.13.3/ssn/main.lua @@ -1,13 +1,7 @@ -local esxVersion = "v1.13.3" - -if GetResourceKvpInt(("esx_migration:%s"):format(esxVersion)) == 1 then - return -end - Core.Migrations = Core.Migrations or {} -Core.Migrations[esxVersion] = Core.Migrations[esxVersion] or {} +Core.Migrations["v1.13.3"] = Core.Migrations["v1.13.3"] or {} -Core.Migrations[esxVersion].ssn = function() +Core.Migrations["v1.13.3"].ssn = function() print("^4[esx_migration:v.1.13.3:ssn]^7 Adding SSN column to users table.") local col = MySQL.scalar.await([[ SELECT COUNT(*) From 8fad1f94edca9eb65c7212ea13120f63a93052a6 Mon Sep 17 00:00:00 2001 From: Kenshin13 <63159154+Kenshiin13@users.noreply.github.com> Date: Mon, 25 Aug 2025 01:15:30 +0200 Subject: [PATCH 6/7] refactor: only store migrations if not already ran --- [core]/es_extended/server/migration/main.lua | 2 +- .../server/migration/v1.13.3/ssn/main.lua | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/[core]/es_extended/server/migration/main.lua b/[core]/es_extended/server/migration/main.lua index 304590eb..c5f226c4 100644 --- a/[core]/es_extended/server/migration/main.lua +++ b/[core]/es_extended/server/migration/main.lua @@ -16,7 +16,7 @@ for esxVersion, migrations in pairs(Core.Migrations or {}) do ---@cast esxVersion string ---@cast migrations table - if GetResourceKvpInt(("esx_migration:%s"):format(esxVersion)) ~= 1 then + if ESX.Table.SizeOf(migrations) > 0 then print(("^4[INFO]^7 Running migrations for ESX version %s"):format(esxVersion)) for migrationName, migration in pairs(migrations) do diff --git a/[core]/es_extended/server/migration/v1.13.3/ssn/main.lua b/[core]/es_extended/server/migration/v1.13.3/ssn/main.lua index 8b6d6ead..0beee7aa 100644 --- a/[core]/es_extended/server/migration/v1.13.3/ssn/main.lua +++ b/[core]/es_extended/server/migration/v1.13.3/ssn/main.lua @@ -1,7 +1,13 @@ -Core.Migrations = Core.Migrations or {} -Core.Migrations["v1.13.3"] = Core.Migrations["v1.13.3"] or {} +local esxVersion = "v1.13.3" -Core.Migrations["v1.13.3"].ssn = function() +Core.Migrations = Core.Migrations or {} +Core.Migrations[esxVersion] = Core.Migrations[esxVersion] or {} + +if GetResourceKvpInt(("esx_migration:%s"):format(esxVersion)) == 1 then + return +end + +Core.Migrations[esxVersion].ssn = function() print("^4[esx_migration:v.1.13.3:ssn]^7 Adding SSN column to users table.") local col = MySQL.scalar.await([[ SELECT COUNT(*) From f78beaf0847d5bf6fbea02c1d3780abdf43c7df4 Mon Sep 17 00:00:00 2001 From: Kenshin13 <63159154+Kenshiin13@users.noreply.github.com> Date: Sun, 31 Aug 2025 15:17:51 +0200 Subject: [PATCH 7/7] fix(es_extended/server/migration): only require restart if actually neccessary --- [core]/es_extended/server/migration/main.lua | 18 ++++++++++++------ .../server/migration/v1.13.3/ssn/main.lua | 5 ++++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/[core]/es_extended/server/migration/main.lua b/[core]/es_extended/server/migration/main.lua index c5f226c4..378894fd 100644 --- a/[core]/es_extended/server/migration/main.lua +++ b/[core]/es_extended/server/migration/main.lua @@ -11,6 +11,7 @@ RegisterCommand("resetmigrations", function(src) end) local migrationsRan = 0 +local restartRequired = false for esxVersion, migrations in pairs(Core.Migrations or {}) do ---@cast esxVersion string @@ -20,10 +21,17 @@ for esxVersion, migrations in pairs(Core.Migrations or {}) do print(("^4[INFO]^7 Running migrations for ESX version %s"):format(esxVersion)) for migrationName, migration in pairs(migrations) do - local success, err = pcall(migration) + local success, result = pcall(migration) if not success then + local err = result --[[@as string]] error(("^1[ERROR]^7 Failed migration ^4['%s.%s']^7: %s"):format(esxVersion, migrationName, err)) end + + local migrationRequiresRestart = result --[[@as boolean]] + + if not restartRequired and migrationRequiresRestart then + restartRequired = true + end end SetResourceKvpInt(("esx_migration:%s"):format(esxVersion), 1) @@ -32,9 +40,7 @@ for esxVersion, migrations in pairs(Core.Migrations or {}) do end end -if migrationsRan > 0 then - while true do - print(("^4[INFO]^7 Ran migrations for %d ESX version(s). ^1Server restart required!^7"):format(migrationsRan)) - Wait(500) - end +while restartRequired do + print(("^4[INFO]^7 Ran migrations for %d ESX version(s). ^1Server restart required!^7"):format(migrationsRan)) + Wait(500) end diff --git a/[core]/es_extended/server/migration/v1.13.3/ssn/main.lua b/[core]/es_extended/server/migration/v1.13.3/ssn/main.lua index 0beee7aa..83b36e9b 100644 --- a/[core]/es_extended/server/migration/v1.13.3/ssn/main.lua +++ b/[core]/es_extended/server/migration/v1.13.3/ssn/main.lua @@ -7,6 +7,7 @@ if GetResourceKvpInt(("esx_migration:%s"):format(esxVersion)) == 1 then return end +---@return boolean restartRequired Core.Migrations[esxVersion].ssn = function() print("^4[esx_migration:v.1.13.3:ssn]^7 Adding SSN column to users table.") local col = MySQL.scalar.await([[ @@ -41,7 +42,7 @@ Core.Migrations[esxVersion].ssn = function() local Result = MySQL.query.await("SELECT `identifier` FROM `users` WHERE `ssn` IS NULL") if #Result == 0 then print("^4[esx_migration:v.1.13.3:ssn]^7 No users found without SSN, migration not needed.") - return + return false end print("^4[esx_migration:v.1.13.3:ssn]^7 Generating SSN for existing users.") @@ -64,4 +65,6 @@ Core.Migrations[esxVersion].ssn = function() MySQL.update.await("ALTER TABLE `users` MODIFY `ssn` VARCHAR(11) NOT NULL") print(("^4[esx_migration:v.1.13.3:ssn]^7 Successfully migrated %d users."):format(#Parameters)) + + return true end