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] 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