Skip to content

Instantly share code, notes, and snippets.

@Local9
Last active March 23, 2023 07:31
Show Gist options
  • Save Local9/6610d67316591a0ff744d904c12b5495 to your computer and use it in GitHub Desktop.
Save Local9/6610d67316591a0ff744d904c12b5495 to your computer and use it in GitHub Desktop.
Basic FiveM SQL Dapper which uses FxEvents Logger (Log class)
{
"database": {
"server": "localhost",
"databaseName": "curiosity",
"username": "DATABASE_USERNAME",
"password": "DATABASE_PASSWORD",
"port": 3306
},
"discord": {
"botKey": "your_discord_bot_key",
"botname": "FiveM Server Discord BOT",
"guildId": 0,
"url": "",
"whitelist": [
0
],
"webhooks": {
"server-debug": "",
"server-error": "",
"server-player-log": ""
}
}
}
using Curiosity.Framework.Server.Models;
using Dapper;
using Logger; // FxEvents on NuGet
using MySqlConnector;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using Debug = CitizenFX.Core.Debug;
namespace Curiosity.Framework.Server.Database
{
internal class DapperDatabase<T>
{
private static string _connectionString;
private static string ConnectionString()
{
if (!string.IsNullOrEmpty(_connectionString))
return _connectionString;
DatabaseConfig databaseConfig = ServerConfiguration.GetDatabaseConfig;
MySqlConnectionStringBuilder mySqlConnectionStringBuilder = new MySqlConnectionStringBuilder();
mySqlConnectionStringBuilder.ApplicationName = databaseConfig.ApplicationName;
mySqlConnectionStringBuilder.Database = databaseConfig.DatabaseName;
mySqlConnectionStringBuilder.Server = databaseConfig.Server;
mySqlConnectionStringBuilder.Port = databaseConfig.Port;
mySqlConnectionStringBuilder.UserID = databaseConfig.Username;
mySqlConnectionStringBuilder.Password = databaseConfig.Password;
mySqlConnectionStringBuilder.MaximumPoolSize = databaseConfig.MaximumPoolSize;
mySqlConnectionStringBuilder.MinimumPoolSize = databaseConfig.MinimumPoolSize;
mySqlConnectionStringBuilder.ConnectionTimeout = databaseConfig.ConnectionTimeout;
return _connectionString = mySqlConnectionStringBuilder.ToString();
}
public static async Task<List<T>> GetListAsync(string query, DynamicParameters args = null)
{
var watch = Stopwatch.StartNew();
try
{
using (var conn = new MySqlConnection(ConnectionString()))
{
SetupTypeMap();
return (await conn.QueryAsync<T>(query, args)).AsList();
}
}
catch (Exception ex)
{
SqlExceptionHandler(query, ex.Message, watch.ElapsedMilliseconds);
}
finally
{
watch.Stop();
}
return null;
}
public static async Task<T> GetSingleAsync(string query, DynamicParameters args = null)
{
var watch = Stopwatch.StartNew();
try
{
using (var conn = new MySqlConnection(ConnectionString()))
{
SetupTypeMap();
return (await conn.QueryAsync<T>(query, args)).FirstOrDefault();
}
}
catch (Exception ex)
{
SqlExceptionHandler(query, ex.Message, watch.ElapsedMilliseconds);
}
finally
{
watch.Stop();
}
return default(T);
}
public static async Task<bool> ExecuteAsync(string query, DynamicParameters args = null)
{
var watch = Stopwatch.StartNew();
try
{
using (var conn = new MySqlConnection(ConnectionString()))
{
return (await conn.ExecuteAsync(query, args)) > 0;
}
}
catch (Exception ex)
{
SqlExceptionHandler(query, ex.Message, watch.ElapsedMilliseconds);
}
finally
{
watch.Stop();
}
return false;
}
private static void SqlExceptionHandler(string query, string exceptionMessage, long elapsedMilliseconds)
{
StringBuilder sb = new();
sb.Append("** SQL Exception **\n");
sb.Append($"Query: {query}\n");
sb.Append($"Exception Message: {exceptionMessage}\n");
sb.Append($"Time Elapsed: {elapsedMilliseconds}ms");
Debug.WriteLine($"{Log.DARK_RED}{sb}");
}
private static void SetupTypeMap()
{
var map = new CustomPropertyTypeMap(typeof(T), (type, columnName) =>
type.GetProperties().FirstOrDefault(prop => GetDescriptionFromAttribute(prop) == columnName.ToLower()));
SqlMapper.SetTypeMap(typeof(T), map);
}
public static string GetDescriptionFromAttribute(MemberInfo member)
{
if (member == null) return null;
var attrib = (DescriptionAttribute)Attribute.GetCustomAttribute(member, typeof(DescriptionAttribute), false);
return (attrib?.Description ?? member.Name).ToLower();
}
}
}
using Curiosity.Framework.Server.Database;
using Curiosity.Framework.Shared;
using Dapper;
using System.ComponentModel;
namespace Curiosity.Framework.Server.Models.Database
{
public class DataStoreUser
{
#region FIELDS
[Description("userId")]
public int UserID { get; private set; }
[Description("username")]
public string Username { get; private set; }
[Description("license")]
public string License { get; private set; }
[Description("discordId")]
public ulong DiscordId { get; private set; }
[Description("dateCreated")]
public DateTime DateCreated { get; private set; }
[Description("lastSeen")]
public DateTime LastSeen { get; private set; }
[Description("bannedPerm")]
public int BannedPermanently { get; private set; }
[Description("roleId")]
public int RoleId { get; private set; }
[Description("isPassive")]
public int IsPassive { get; private set; }
#endregion
public List<DataStoreCharacter> Characters { get; private set; } = new();
public DataStoreCharacter CurrentCharacter { get; private set; }
const string SQL_USER_GET = "call spUser(@pDiscordId);";
const string SQL_USER_INSERT = "call insUser(@pUsername, @pDiscordId, CURRENT_TIMESTAMP, @pDiscordId, 1, 1);";
const string SQL_CHARACTERS_GET = "call selCharacter(@pUserId, @pServerId);";
internal static async Task<DataStoreUser> GetUserAsync(string playerName, ulong discordId, bool withCharacters = false)
{
DynamicParameters dynamicParameters = new DynamicParameters();
dynamicParameters.Add("pDiscordId", discordId);
DataStoreUser user = await DapperDatabase<DataStoreUser>.GetSingleAsync(SQL_USER_GET, dynamicParameters);
await Common.MoveToMainThread();
if (user is null)
{
dynamicParameters.Add("pUsername", playerName);
bool success = await DapperDatabase<DataStoreUser>.ExecuteAsync(SQL_USER_INSERT, dynamicParameters);
await Common.MoveToMainThread();
if (success)
{
user = await DapperDatabase<DataStoreUser>.GetSingleAsync(SQL_USER_GET, dynamicParameters);
await Common.MoveToMainThread();
}
}
if (withCharacters)
await user.GetCharactersAsync();
user.Username = playerName;
return user;
}
internal async Task GetCharactersAsync()
{
DynamicParameters dynamicParameters = new DynamicParameters();
dynamicParameters.Add("pUserId", UserID);
dynamicParameters.Add("pServerId", PluginManager.ServerID);
var _characters = await DapperDatabase<DataStoreCharacter>.GetListAsync(SQL_CHARACTERS_GET, dynamicParameters);
await Common.MoveToMainThread();
Characters = _characters;
}
public override string ToString()
{
return JsonConvert.SerializeObject(this);
}
}
}
namespace Curiosity.Framework.Server.Models
{
[DataContract]
internal class ServerConfig
{
[DataMember(Name = "database")]
public DatabaseConfig Database;
[DataMember(Name = "discord")]
public Discord Discord;
}
[DataContract]
public class DatabaseConfig
{
[DataMember(Name = "application")]
public string ApplicationName;
[DataMember(Name = "server")]
public string Server;
[DataMember(Name = "databaseName")]
public string DatabaseName;
[DataMember(Name = "port")]
public uint Port;
[DataMember(Name = "username")]
public string Username;
[DataMember(Name = "password")]
public string Password;
[DataMember(Name = "minimumPoolSize")]
// Minimum number of connections to the database by this connection
public uint MinimumPoolSize { get; set; } = 10;
[DataMember(Name = "maximumPoolSize")]
// Maximum number of connections to the database by this connection
public uint MaximumPoolSize { get; set; } = 50;
[DataMember(Name = "connectionTimeout")]
public uint ConnectionTimeout { get; set; } = 5;
}
[DataContract]
public class Discord
{
[DataMember(Name = "botKey")]
public string BotKey;
[DataMember(Name = "botname")]
public string Botname;
[DataMember(Name = "webhooks")]
public DiscordWebhooks Webhooks;
[DataMember(Name = "whitelist")]
public List<string> Whitelist;
[DataMember(Name = "guildId")]
public ulong GuildId;
[DataMember(Name = "url")]
public string Url;
}
[DataContract]
public class DiscordWebhooks
{
[DataMember(Name = "server-debug")]
public string ServerDebug;
[DataMember(Name = "server-error")]
public string ServerError;
[DataMember(Name = "server-player-log")]
public string ServerPlayerLog;
}
}
using Curiosity.Framework.Server.Models;
using Logger; // FxEvents on NuGet
namespace Curiosity.Framework.Server
{
static class ServerConfiguration
{
const string SERVER_CONFIG_LOCATION = $"/data/server-config.json";
private static ServerConfig _serverConfig = null;
private static ServerConfig GetConfig()
{
try
{
if (_serverConfig is not null)
return _serverConfig;
string serverConfigFile = LoadResourceFile(GetCurrentResourceName(), SERVER_CONFIG_LOCATION);
_serverConfig = JsonConvert.DeserializeObject<ServerConfig>(serverConfigFile);
return _serverConfig;
}
catch (Exception ex)
{
Debug.WriteLine($"{Log.DARK_RED}Server Configuration was unable to be loaded.");
Debug.WriteLine($"{ex}");
return (ServerConfig)default!;
}
}
public static ServerConfig GetServerConfig => GetConfig();
public static DatabaseConfig GetDatabaseConfig => GetServerConfig.Database;
public static Discord GetDiscordConfig => GetServerConfig.Discord;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment