From 7e12f3a4ec4a242f66b0f6411d941eed24bbe8f0 Mon Sep 17 00:00:00 2001 From: Manason Date: Mon, 10 Jun 2024 02:10:45 -0700 Subject: [PATCH 1/3] feat!: API re-write --- .vscode/settings.json | 3 +- fxmanifest.lua | 1 + server/main.lua | 197 +++++++++++++++++++++++------------------- 3 files changed, 109 insertions(+), 92 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 1f091d0..4e9be72 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,6 +6,7 @@ "cache", "QBX", "locale", - "qbx" + "qbx", + "MySQL" ] } diff --git a/fxmanifest.lua b/fxmanifest.lua index 866befb..7c23226 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -7,6 +7,7 @@ version '0.0.1' server_scripts { '@oxmysql/lib/MySQL.lua', + 'qbx_core/modules/lib.lua', 'server/main.lua' } diff --git a/server/main.lua b/server/main.lua index 7f5b922..5dc07eb 100644 --- a/server/main.lua +++ b/server/main.lua @@ -5,6 +5,108 @@ local State = { IMPOUNDED = 2 } +---Returns true if the given plate exists +---@param plate string +---@return boolean +local function doesEntityPlateExist(plate) + local result = MySQL.scalar.await('SELECT 1 FROM player_vehicles WHERE plate = ?', {plate}) + return not not result +end + +exports('DoesEntityPlateExist', doesEntityPlateExist) +exports('DoesPlayerVehiclePlateExist', doesEntityPlateExist) + +---@class PlayerVehicle +---@field id number +---@field citizenid? string +---@field modelName string +---@field props table ox_lib properties table + +---@class GetPlayerVehiclesRequest +---@field vehicleId? number +---@field citizenid? string +---@field license? string +---@field plate? string + +---@param idType 'citizenid'|'license'|'plate'|'vehicleId' +---@param idValue string +---@return PlayerVehicle[] +local function getPlayerVehicles(idType, idValue) + local column = idType == 'vehicleId' and 'id' or idType + local results = MySQL.query.await('SELECT id, citizenid, vehicle, mods FROM player_vehicles WHERE ? = ?', { + column, + idValue, + }) + local ownedVehicles = {} + for _, data in pairs(results) do + ownedVehicles[#ownedVehicles+1] = { + id = data.id, + citizenid = data.citizenid, + modelName = data.vehicle, + props = json.decode(data.mods) + } + end + return ownedVehicles +end + +exports('GetPlayerVehicles', getPlayerVehicles) + +---@class CreatePlayerVehicleRequest +---@field model string model name +---@field citizenid? string owner of the vehicle +---@field props? table ox_lib properties to set. See https://github.com/overextended/ox_lib/blob/master/resource/vehicleProperties/client.lua#L3 + +---@param request CreatePlayerVehicleRequest +---@return integer vehicleId +local function createPlayerVehicle(request) + local props = request.props or {} + if not props.plate then + repeat + props.plate = qbx.generateRandomPlate() + until doesEntityPlateExist(props.plate) == false + end + props.engineHealth = props.engineHealth or 1000 + props.bodyHealth = props.bodyHealth or 1000 + props.fuelLevel = props.fuelLevel or 100 + props.model = joaat(request.model) + + return MySQL.insert.await('INSERT INTO player_vehicles (license, citizenid, vehicle, hash, mods, plate, state) VALUES ((SELECT license FROM players WHERE citizenid = ?),?,?,?,?,?,?)', { + request.citizenid, + request.citizenid, + request.model, + props.model, + json.encode(props), + props.plate, + State.OUT + }) +end + +exports('CreatePlayerVehicle', createPlayerVehicle) + +---@param vehicleId integer +---@param citizenId string +local function setPlayerVehicleOwner(vehicleId, citizenId) + MySQL.update.await('UPDATE player_vehicles SET citizenid = ?, license = (SELECT license FROM players WHERE citizenid = ?) WHERE id = ?', { + citizenId, + citizenId, + vehicleId + }) +end + +exports('SetPlayerVehicleOwner', setPlayerVehicleOwner) + +---@param idType 'citizenid'|'license'|'plate'|'vehicleId' +---@param idValue string +local function deletePlayerVehicles(idType, idValue) + local column = idType == 'vehicleId' and 'id' or idType + MySQL.query.await('DELETE FROM player_vehicles WHERE ? = ?', { + column, + idValue + }) +end + +exports('DeletePlayerVehicles', deletePlayerVehicles) + ---@class VehicleEntity ---@field id number ---@field license string @@ -31,6 +133,7 @@ local State = { ---@field state? State The state of the vehicle --- Creates a Vehicle DB Entity +---@deprecated ---@param query CreateEntityQuery ---@return integer vehicleId local function createEntity(query) @@ -47,83 +150,12 @@ end exports('CreateVehicleEntity', createEntity) ----@class FetchVehicleEntityQuery ----@field valueType 'citizenid'|'license'|'plate' ----@field value string - ---- Fetches DB Vehicle Entity ----@param query FetchVehicleEntityQuery ----@return VehicleEntity[] -local function fetchEntities(query) - local vehicleData = {} - if query.valueType ~= 'citizenid' and query.valueType ~= 'license' and query.valueType ~= 'plate' then return {} end - local results = MySQL.query.await('SELECT * FROM player_vehicles WHERE ? = ?', { - query.valueType, - query.value - }) - for _, data in pairs(results) do - vehicleData[#vehicleData + 1] = { - id = data.id, - citizenid = data.citizenid, - model = data.vehicle, - hash = data.hash, - mods = data.mods, - plate = data.plate, - fakeplate = data.fakeplate, - garage = data.garage, - fuel = data.fuel, - engine = data.engine, - body = data.body, - state = data.state, - depotprice = data.depotprice, - drivingdistance = data.drivingdistance, - status = data.status - } - end - return vehicleData -end - ----Fetches DB Vehicle Entity by CiizenId ----@param citizenId string ----@return VehicleEntity[] -local function fetchEntitiesByCitizenId(citizenId) - return fetchEntities({ - valueType = 'citizenid', - value = citizenId - }) -end - -exports('FetchEntitiesByCitizenId', fetchEntitiesByCitizenId) - ----Fetches DB Vehicle Entity by License ----@param license string ----@return VehicleEntity[] -local function fetchEntitiesByLicense(license) - return fetchEntities({ - valueType = 'license', - value = license - }) -end - -exports('FetchEntitiesByLicense', fetchEntitiesByLicense) - ----Fetches DB Vehicle Entity by Plate ----@param plate string ----@return VehicleEntity[] -local function fetchEntitiesByPlate(plate) - return fetchEntities({ - valueType = 'plate', - value = plate - }) -end - -exports('FetchEntitiesByPlate', fetchEntitiesByPlate) - ---@class SetEntityOwnerQuery ---@field citizenId string ---@field plate string --- Update Vehicle Entity Owner +---@deprecated ---@param query SetEntityOwnerQuery local function setEntityOwner(query) MySQL.update('UPDATE player_vehicles SET citizenid = ?, license = (SELECT license FROM players WHERE citizenid = ?) WHERE plate = ?', { @@ -135,28 +167,11 @@ end exports("SetVehicleEntityOwner", setEntityOwner) ---- Deletes a DB Vehicle Entity through searching for the number plate ----@param plate string -local function deleteEntityByPlate(plate) - MySQL.query('DELETE FROM player_vehicles WHERE plate = ?', {plate}) -end - -exports('DeleteEntityByPlate', deleteEntityByPlate) - --- Deletes a DB Vehicle Entity through searching for the vehicle id +---@deprecated ---@param id integer local function deleteEntityById(id) MySQL.query('DELETE FROM player_vehicles WHERE id = ?', {id}) end -exports('DeleteEntityById', deleteEntityById) - ---- Returns if the given plate exists ----@param plate string ----@return boolean -local function doesEntityPlateExist(plate) - local count = MySQL.scalar.await('SELECT COUNT(*) FROM player_vehicles WHERE plate = ?', {plate}) - return count > 0 -end - -exports('DoesEntityPlateExist', doesEntityPlateExist) \ No newline at end of file +exports('DeleteEntityById', deleteEntityById) \ No newline at end of file From d8fcbcbe3d1f7f0cf3659dd95ce36a98662ee7d2 Mon Sep 17 00:00:00 2001 From: Manason Date: Mon, 10 Jun 2024 09:37:12 -0700 Subject: [PATCH 2/3] feat: error handling --- server/main.lua | 46 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/server/main.lua b/server/main.lua index 5dc07eb..8b79f81 100644 --- a/server/main.lua +++ b/server/main.lua @@ -1,3 +1,7 @@ +---@class ErrorResult +---@field code string +---@field message string + ---@enum State local State = { OUT = 0, @@ -5,12 +9,25 @@ local State = { IMPOUNDED = 2 } +---@alias IdType 'citizenid'|'license'|'plate'|'vehicleId' + +---@param idType IdType +---@return ErrorResult? +local function validateIdType(idType) + if idType ~= 'citizenid' and idType ~= 'license' and idType ~= 'plate' and idType ~= 'vehicleId' then + return { + code = 'bad_request', + message = 'idType:' .. json.encode(idType) .. ' is not a valid idType' + } + end +end + ---Returns true if the given plate exists ---@param plate string ---@return boolean local function doesEntityPlateExist(plate) local result = MySQL.scalar.await('SELECT 1 FROM player_vehicles WHERE plate = ?', {plate}) - return not not result + return result ~= nil end exports('DoesEntityPlateExist', doesEntityPlateExist) @@ -28,10 +45,13 @@ exports('DoesPlayerVehiclePlateExist', doesEntityPlateExist) ---@field license? string ---@field plate? string ----@param idType 'citizenid'|'license'|'plate'|'vehicleId' +---@param idType IdType ---@param idValue string ----@return PlayerVehicle[] +---@return PlayerVehicle[]?, ErrorResult? errorResult local function getPlayerVehicles(idType, idValue) + local err = validateIdType(idType) + if err then return nil, err end + local column = idType == 'vehicleId' and 'id' or idType local results = MySQL.query.await('SELECT id, citizenid, vehicle, mods FROM player_vehicles WHERE ? = ?', { column, @@ -57,8 +77,15 @@ exports('GetPlayerVehicles', getPlayerVehicles) ---@field props? table ox_lib properties to set. See https://github.com/overextended/ox_lib/blob/master/resource/vehicleProperties/client.lua#L3 ---@param request CreatePlayerVehicleRequest ----@return integer vehicleId +---@return integer? vehicleId, ErrorResult? errorResult local function createPlayerVehicle(request) + if not request.model then + return nil, { + code = 'bad_request', + message = 'missing required field model' + } + end + local props = request.props or {} if not props.plate then repeat @@ -84,25 +111,32 @@ end exports('CreatePlayerVehicle', createPlayerVehicle) ---@param vehicleId integer ----@param citizenId string +---@param citizenId? string +---@return boolean success, ErrorResult? errorResult local function setPlayerVehicleOwner(vehicleId, citizenId) MySQL.update.await('UPDATE player_vehicles SET citizenid = ?, license = (SELECT license FROM players WHERE citizenid = ?) WHERE id = ?', { citizenId, citizenId, vehicleId }) + return true end exports('SetPlayerVehicleOwner', setPlayerVehicleOwner) ----@param idType 'citizenid'|'license'|'plate'|'vehicleId' +---@param idType IdType ---@param idValue string +---@return boolean success, ErrorResult? errorResult local function deletePlayerVehicles(idType, idValue) + local err = validateIdType(idType) + if err then return false, err end + local column = idType == 'vehicleId' and 'id' or idType MySQL.query.await('DELETE FROM player_vehicles WHERE ? = ?', { column, idValue }) + return true end exports('DeletePlayerVehicles', deletePlayerVehicles) From 4b7ef51dcca3174c52d602b40ec256312085b45d Mon Sep 17 00:00:00 2001 From: Manason Date: Mon, 10 Jun 2024 09:38:01 -0700 Subject: [PATCH 3/3] limit plate existence query to 1 value --- server/main.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/main.lua b/server/main.lua index 8b79f81..8c63b86 100644 --- a/server/main.lua +++ b/server/main.lua @@ -26,7 +26,7 @@ end ---@param plate string ---@return boolean local function doesEntityPlateExist(plate) - local result = MySQL.scalar.await('SELECT 1 FROM player_vehicles WHERE plate = ?', {plate}) + local result = MySQL.scalar.await('SELECT 1 FROM player_vehicles WHERE plate = ? LIMIT 1', {plate}) return result ~= nil end