-- ============================================================ -- db/main.lua – Database module (cache + core queries) -- ============================================================ _G.db = { cache = {} } -- ────────────────────────────────────────────── -- saveCache -- cacheName : string – cache key name -- cacheId : any – secondary identifier -- data : any – data to store -- ttl : number? – time-to-live in seconds (default 3 600 000) -- ────────────────────────────────────────────── function db.saveCache(self, cacheName, data, cacheId, ttl) if not data then Debug("No data to save to cache", cacheName, cacheId) return end local expireAt = os.time() + (ttl or 3600000) self.cache[#self.cache + 1] = { name = cacheName, id = cacheId, time = os.time(), expire = expireAt, data = data, } Debug("db:saveCache", cacheName, cacheId) end -- ────────────────────────────────────────────── -- clearCache -- cacheName : string – cache key to clear -- cacheId : any? – optional secondary identifier -- ────────────────────────────────────────────── function db.clearCache(self, cacheName, cacheId) self.cache = table.filter(self.cache, function(entry) return entry.name ~= cacheName end) Debug("db:clearCache", cacheName, cacheId) end -- ────────────────────────────────────────────── -- getCache -- Returns the cached data, or nil if not found. -- ────────────────────────────────────────────── function db.getCache(self, cacheName, cacheId) local found = table.find(self.cache, function(entry) return entry.name == cacheName end) if found then Debug("db:getCache", cacheName, cacheId) return found.data end return nil end -- ────────────────────────────────────────────── -- convertVectors -- Recursively converts plain {x,y,z[,w]} tables -- to FiveM vec3/vec4 native types. -- ────────────────────────────────────────────── function db.convertVectors(self, value) if type(value) ~= "table" then return value end -- vec4 detection if value.w and value.x and value.y and value.z then return vec4(value.x, value.y, value.z, value.w) end -- Recurse into nested tables for key, nested in pairs(value) do if type(nested) == "table" then value[key] = self:convertVectors(nested) end end return value end -- ────────────────────────────────────────────── -- getGarages -- Fetches all player_garages rows and decodes -- their JSON columns into Lua tables. -- ────────────────────────────────────────────── function db.getGarages() local rows = MySQL.query.await("SELECT * FROM player_garages") for _, garage in pairs(rows) do -- Normalize available flag garage.available = (garage.available == true or garage.available == 1) or false garage.zone = json.decode(garage.zone) garage.coords = json.decode(garage.coords) garage.shell = json.decode(garage.shell) garage.holders = (garage.holders and json.decode(garage.holders)) or {} garage.jobs = (garage.jobs and json.decode(garage.jobs)) or {} garage.gangs = (garage.gangs and json.decode(garage.gangs)) or {} garage.interior_type = garage.interior_type or "ipl" end return rows end -- ────────────────────────────────────────────── -- checkVehicleGarages -- For each vehicle whose garage no longer exists, -- moves it to the nearest impound. Used by /migratev5. -- ────────────────────────────────────────────── function db.checkVehicleGarages() local selectQuery = string.format("SELECT type, garage, plate FROM `%s`", garageTable) local vehicles = MySQL.query.await(selectQuery) local pendingUpdates = {} for _, row in pairs(vehicles) do if row.garage ~= "OUT" then local garageExists = Config.Garages[row.garage] if not garageExists then local impound = GetImpoundOfType(row.type) local updateQuery = string.format("UPDATE `%s` SET garage = ? WHERE plate = ?", garageTable) pendingUpdates[#pendingUpdates + 1] = { query = updateQuery, values = { impound, row.plate }, } end end end if #pendingUpdates > 0 then Debug("db.checkVehicleGarages", "We collected", #pendingUpdates, "queries to update vehicles because their garages were not found") MySQL.transaction(pendingUpdates) end end