Last active
March 23, 2023 07:31
-
-
Save Local9/6610d67316591a0ff744d904c12b5495 to your computer and use it in GitHub Desktop.
Basic FiveM SQL Dapper which uses FxEvents Logger (Log class)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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": "" | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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