├── .gitignore ├── .yarn.installed ├── README.mkd ├── build.cmd ├── config.js ├── debug.lua ├── debug ├── assets │ ├── bg.mp4 │ └── user.png ├── index.html ├── js │ ├── app.js │ └── tabs.js └── styles │ ├── app.css │ ├── backup.css │ ├── cache.css │ ├── database.css │ ├── input.css │ ├── intercept.css │ ├── logs.css │ ├── resources.css │ ├── root.css │ ├── tabs.css │ └── traffic-light.css ├── docs ├── assets │ ├── Async.drawio.png │ ├── ErrorSystem.drawio.png │ ├── MongoDB_Logo.png │ ├── ORM.drawio.png │ ├── Redis_Logo.png │ └── Sync.drawio.png └── features │ ├── AwaitAsync.mkd │ ├── BackupSystem.mkd │ ├── BasicCache.mkd │ ├── BasicErrorSystem.mkd │ ├── CacheSystem.mkd │ ├── ChangeOtherSystem.mkd │ ├── DiscordLogs.mkd │ ├── ErrorHandling.mkd │ ├── LogsSystem.mkd │ ├── MongoDB.mkd │ ├── MultipleDB.mkd │ ├── NamedParameters.mkd │ ├── ORM.mkd │ ├── Redis.mkd │ ├── ScriptUpdater.mkd │ └── SupportLuaJS.mkd ├── exports ├── ghmattimysql.json ├── mysql_async.json └── oxmysql.json ├── fxmanifest.lua ├── library ├── MySQL.js ├── MySQL.lua ├── csharp │ ├── MySQL.sln │ ├── README.md │ ├── Server │ │ ├── MySQL.cs │ │ └── MySQL.csproj │ └── build.cmd ├── library-changer.runtimeconfig.json ├── library-changer │ ├── Functions.cs │ ├── Program.cs │ ├── assets │ │ └── newIceCore.ico │ ├── build.cmd │ ├── library-changer.csproj │ └── library-changer.sln └── vrp_icmysql │ ├── fxmanifest.lua │ ├── init.lua │ └── init_vrp.lua ├── package.json ├── pnpm-lock.yaml ├── server └── main.lua └── src ├── build.js ├── db ├── Connections.js ├── Params.js ├── Query.js ├── Reader.js ├── Transactions.js ├── backup │ └── index.js ├── debug │ ├── Debug.js │ └── Interceptor.js ├── exports │ └── main.js ├── mongo │ ├── Connections.js │ ├── Query.js │ └── utils.js ├── orm │ ├── Connection.js │ ├── index.js │ └── models │ │ └── index.js └── redis │ └── index.js ├── errors ├── List.js └── Parser.js ├── index.js ├── json ├── Load.js ├── Save.js └── index.js ├── language ├── List.js └── localisation.js └── utils ├── Discord.js ├── Files.js ├── Logger.js ├── Parser.js ├── Time.js ├── TypeCast.js └── Updater.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | # Keep environment variables out of version control 3 | **/.env 4 | **/.cache 5 | **/.vscode 6 | **/.vs 7 | *.dll 8 | *.pdb 9 | *.props 10 | *.cache 11 | **/dist 12 | **/bin 13 | **/obj 14 | *.exe 15 | package-lock.json 16 | **/models/*/ 17 | .DS_Store 18 | -------------------------------------------------------------------------------- /.yarn.installed: -------------------------------------------------------------------------------- 1 | This document is essential to avoid the automatic construction of the troublesome yarn resource. -------------------------------------------------------------------------------- /README.mkd: -------------------------------------------------------------------------------- 1 | # ICMySQL 2 | An sophisticated encapsulation of the mysql2 and sequelize libraries, ingeniously designed to empower CFX scripts with seamless capabilities for accessing and manipulating data within relational databases. 3 | ### Why? 4 | We created this database connection system because, even though there are good solutions out there, we wanted to bring something new to the table for the FiveM community. We've added some cool features that are missing in other systems, and we hope you find it useful! 5 | ### Features 6 | - [Multiple DB Connections](docs/features/MultipleDB.mkd) 7 | - [Cache system for frequent queries](docs/features/CacheSystem.mkd) 8 | - [Basic connections cache](docs/features/BasicCache.mkd) 9 | - [MongoDB Support](docs/features/MongoDB.mkd) 10 | - [Await and Async support for all functions](docs/features/AwaitAsync.mkd) 11 | - [Backup system](docs/features/BackupSystem.mkd) 12 | - [Discord Logs for relevant information.](docs/features/DiscordLogs.mkd) 13 | - [ORM(Object Relation Mapping) support](docs/features/ORM.mkd) 14 | - [Script auto updater](docs/features/ScriptUpdater.mkd) 15 | - [Redis Support](docs/features/Redis.mkd) 16 | - [Logs system](docs/features/LogsSystem.mkd) 17 | - [Basic system to give a solution for each error](docs/features/BasicErrorSystem.mkd) 18 | - [Support Lua, JS, C# library](docs/features/SupportLuaJS.mkd) 19 | - [Library to change other mysql system functions to ice](docs/features/ChangeOtherSystem.mkd) 20 | - [Named and unnamed parameters support](docs/features/NamedParameters.mkd) 21 | - [Debug UI]() 22 | - [Error handling](docs/features/ErrorHandling.mkd) 23 | ### Installation 24 | 1. Download the latest release from [here](https://github.com/IceClusters/icmysql/releases/latest) 25 | 2. Extract the resource in your resources folder 26 | 3. Add `ensure icmysql` at the top of your server.cfg 27 | 4. In the server.cfg add your databases connections like this: 28 | ```cfg 29 | set mysqlCredentials_1 "host=127.0.0.1;user=root; password=1234; database=fxserver; port=3306" 30 | set mysqlCredentials_2 "host=127.0.0.1;user=root; password=1234; database=fxserver; port=3306" 31 | ... 32 | ``` 33 | 5. Enjoy! and thanks for trusting in ICMySQL 34 | ### Known Issues 35 | We're going to list some known issues here, so you don't have to worry about them. Or you don't have to report them, because we already know about them. If you find any other issues, please report them in our [Discord Server](https://discord.gg/3DhEgXAX2U). 36 | - First query after a restart is slow(because we wait to make the connection to the database) 37 | - Thread hitch warning when starting the resource(don't worry, it's normal) 38 | ### Support 39 | Right now we only give support in our [Discord Server](https://discord.gg/3DhEgXAX2U). Anyway you can open an issue in our [GitHub](https://github.com/IceClusters/icmysql/issues), if you ask in the forum we will try to answer you as soon as possible but to make sure to get an answer join our [Discord Server](https://discord.gg/3DhEgXAX2U). -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | 4 | choice /c yn /m "Do you want to build all projects? (y/n) " 5 | if errorlevel 2 ( 6 | echo Continuing without building other projects... 7 | echo ------------------------------------------------ 8 | ) else ( 9 | echo ------------------------------------------------ 10 | echo Building MySQL csharp project... 11 | echo ------------------------------------------------ 12 | cd library\csharp 13 | .\build.cmd | more 14 | 15 | echo ------------------------------------------------ 16 | cd ..\library-changer 17 | echo Building library changer project... 18 | echo ------------------------------------------------ 19 | .\build.cmd | more 20 | cd ..\ 21 | echo ------------------------------------------------ 22 | ) 23 | 24 | echo Installing dependencies 25 | pnpm install 26 | echo ------------------------------------------------ 27 | echo Building main nodejs project... 28 | echo ------------------------------------------------ 29 | pnpm run build | more 30 | 31 | pause 32 | 33 | endlocal 34 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | Config = {}; 2 | Config.Language = "en"; // en, es, fr, pt-BR 3 | 4 | // DataMemSave(TEMPORALY DISABLED UNTIL I FIND A WAY TO FIX PERFORMANCE ISSUES) 5 | Config.SaveDirPath = "IcMysql/Data/"; // Path respective to the FXServer.exe file 6 | Config.SaveInterval = 20000; // The interval in milliseconds that the resource will save the data in the JSON file don't put a value less than 5000 7 | 8 | // Errors 9 | Config.Debug = true; // Show debug messages in the console 10 | Config.ShowErrorDescription = true; // Show the error description if is registered in the list 11 | Config.ShowErrorSolution = true; // Show the error solution if is registered in the list 12 | 13 | // LOGS 14 | Config.LogFilesPath = "IcMysql/Logs/IcMysql"; // Path respective to the FXServer.exe file 15 | Config.MaxLogFiles = 10; // The count of log files that will be saved, the oldest will be deleted 16 | 17 | // Discord Logs 18 | Config.DiscordLogs = false; // Enable the discord logs 19 | Config.DiscordWebhook = "" // The discord webhook that will be used to send relevant data, if you don't want to send the error reports change the previous value to false 20 | Config.SendUnknownErrors = true; // Send the unknown errors to the discord webhook 21 | Config.SendCommonErrors = true; // Send the common errors to the discord webhook that include a posible solution 22 | Config.SendSaveData = true; // Send the file name that is being saved 23 | Config.SendBackupInfo = true; // Send the backup event when a backup is executed 24 | Config.SendDatabaseMapped = true; // Send the database mapped event when a database is mapped 25 | Config.SendDatabaseDisconnect = true; // Send the database disconnect event when a database is disconnected 26 | 27 | // BACKUP 28 | Config.BackupEnabled = true; // Enable the backup system 29 | Config.MysqlDumpPath = "C:/Program Files/MariaDB 11.3/bin/mysqldump.exe"; // The path of the mysqldump.exe file, in this case is the path of the xampp mysql dump 30 | // MOST USED PATHS 31 | // XAMPP: C:/xampp/mysql/bin/mysqldump.exe 32 | // WAMP: C:/wamp/bin/mysql/mysql5.7.26/bin/mysqldump.exe 33 | // MariaDB: C:/Program Files/MariaDB 11.1/bin/mysqldump.exe 34 | Config.BackupDirPath = "IcMysql/Backups"; // Path respective to the FXServer.exe file 35 | Config.MaxBackups = 4; // The max count of backups that will be saved, the oldest will be deleted 36 | Config.Days = [8, 14, 23, 29]; // The days that the backup will be executed, if the array is empty the backup will be executed every day 37 | Config.Hour = "00:56" // The hour in 24h format that the backup will be executed, in this example the backup will be executed at 4:30 AM 38 | 39 | // DATABASE 40 | Config.MySQL = true; // Enable the MySQL support 41 | Config.ConnectionTimeout = 40000; // The time in milliseconds that the MySQL will wait for a connection to be available in the pool, if the time is exceeded a error will be thrown 42 | Config.MaxDB = 10; // The max count of databases that can be readed in the server.cfg file 43 | Config.MaxConnectionLimit = 15; // The max count of connections that can be created per database(recomend to not alter this value too much) 44 | Config.QueueLimit = 100; // The max count of queries that can be queued per database(recomend to not alter this value too much) 45 | Config.DefaultDB = 1; // The default database that will be used if the database is not specified in the query 46 | Config.SlowQueryWarn = 300; // The time in milliseconds that the query will be considered slow and will be logged in the console 47 | Config.CacheMaxSize = 50; // The time of the cache in megabytes that will be used to store the queries, if the cache is full the oldest query will be deleted, please don't increase a lot this value because the cache is stored in the RAM 48 | Config.AllowDBDisconnection = true; // Allow the disconnection command to be executed, this will close the conection of a specific database 49 | 50 | // MongoDB 51 | Config.MongoDB = false; // Enable the MongoDB support 52 | Config.DefaultMongoDB = 1; 53 | Config.ConnectiTimout = 5000; // The time in milliseconds that the MongoDB will wait for a connection to be available in the pool, if the time is exceeded a error will be thrown 54 | 55 | // Debug UI 56 | Config.Enabled = false; // Enable the debug UI 57 | Config.DebugLicenses = ["license:357eb9ff6db060eb292e8e4f71b66a9efb2adcbe"] // The licenses of the players that will be able to use the debug UI. 58 | 59 | // ORM 60 | Config.ORM = false; // Enable the ORM queries, this depend of the MySQL support 61 | Config.DefaultORMDB = 1; // The default database that will be used if the database is not specified in the orm query 62 | Config.RawData = true; // If the ORM will return the raw data or the model instance that contains sequelize data 63 | Config.ConnectionsORM = [0, 10] // Minimum and maximum count of connections that will be created for the ORM, this is used to create a pool of connections that will be used by the ORM, this is used to avoid the creation of a connection for each query 64 | Config.ORMConnectionTimout = 15000 // The time in milliseconds that the ORM will wait for a connection to be available in the pool, if the time is exceeded a error will be thrown 65 | Config.LogORMConnections = true; // Show in the console when a connection is created or released by the ORM 66 | 67 | // Redis 68 | Config.Redis = false; // Enable the Redis support 69 | 70 | // Exports 71 | Config.ReplaceExports = true; // Replace the other mysql systems exports with the exports of this resource 72 | 73 | // Update 74 | Config.CheckForUpdates = true; // Check for updates when the server starts 75 | Config.AutoUpdate = false; // If there is a new version, the resource will ask you to type a command if you want to update it 76 | // After update you need to restart the server to apply the changes -------------------------------------------------------------------------------- /debug.lua: -------------------------------------------------------------------------------- 1 | RegisterNetEvent("icmysql:client:openDebugUI") 2 | AddEventHandler("icmysql:client:openDebugUI", function() 3 | SendNUIMessage({ 4 | action = "open" 5 | }) 6 | SetNuiFocus(true, true) 7 | end) 8 | 9 | RegisterNUICallback("closeDebugUI", function() 10 | SetNuiFocus(false, false) 11 | end) 12 | 13 | RegisterNUICallback("loadData", function(data) 14 | data = data.load 15 | if data == "cache" then 16 | TriggerServerEvent("icmysql:server:getquerycache") 17 | elseif data == "resources" then 18 | TriggerServerEvent("icmysql:server:getresources") 19 | elseif data == "logs" then 20 | TriggerServerEvent("icmysql:server:getlogs") 21 | elseif data == "backup" then 22 | TriggerServerEvent("icmysql:server:getbackup") 23 | elseif data == "dbs" then 24 | TriggerServerEvent("icmysql:server:getdbs") 25 | elseif data == "queries" then 26 | TriggerServerEvent("icmysql:server:getQueries") 27 | end 28 | end) 29 | 30 | RegisterNetEvent("icmysql:client:getdbs") 31 | AddEventHandler("icmysql:client:getdbs", function(data) 32 | SendNUIMessage({ 33 | action = "loadData", 34 | info = "dbs", 35 | data = data 36 | }) 37 | end) 38 | 39 | RegisterNetEvent("icmysql:client:getQueries") 40 | AddEventHandler("icmysql:client:getQueries", function(data) 41 | SendNUIMessage({ 42 | action = "loadData", 43 | info = "queries", 44 | data = data 45 | }) 46 | end) 47 | 48 | RegisterNetEvent("icmysql:client:getquerycache") 49 | AddEventHandler("icmysql:client:getquerycache", function(data) 50 | SendNUIMessage({ 51 | action = "loadData", 52 | info = "cache", 53 | data = data 54 | }) 55 | end) 56 | 57 | RegisterNetEvent("icmysql:client:getResources") 58 | AddEventHandler("icmysql:client:getResources", function(data) 59 | SendNUIMessage({ 60 | action = "loadData", 61 | info = "resources", 62 | data = data 63 | }) 64 | end) 65 | 66 | RegisterNetEvent("icmysql:client:getlogs") 67 | AddEventHandler("icmysql:client:getlogs", function(data) 68 | SendNUIMessage({ 69 | action = "loadData", 70 | info = "logs", 71 | data = data 72 | }) 73 | end) 74 | 75 | RegisterNetEvent("icmysql:client:getbackup") 76 | AddEventHandler("icmysql:client:getbackup", function(data) 77 | SendNUIMessage({ 78 | action = "loadData", 79 | info = "backup", 80 | data = data 81 | }) 82 | end) 83 | 84 | RegisterNetEvent("icmysql:client:setInterceptor") 85 | AddEventHandler("icmysql:client:setInterceptor", function(data) 86 | SendNUIMessage({ 87 | action = "setInterceptor", 88 | data = data 89 | }) 90 | end) 91 | 92 | RegisterNetEvent("icmysql:client:getQueryData") 93 | AddEventHandler("icmysql:client:getQueryData", function(queryID, data) 94 | SendNUIMessage({ 95 | action = "getQueryData", 96 | queryID = queryID, 97 | data = data 98 | }) 99 | end) 100 | 101 | RegisterNUICallback("suscribeInterceptor", function() 102 | TriggerServerEvent("icmysql:server:subscribeInterceptor") 103 | end) 104 | 105 | RegisterNUICallback("setInterceptor", function(data) 106 | TriggerServerEvent("icmysql:server:setInterceptor", data) 107 | end) 108 | 109 | RegisterNUICallback("forwardQuery", function(data) 110 | TriggerServerEvent("icmysql:server:forwardQuery", data) 111 | end) 112 | 113 | RegisterNUICallback("dropQuery", function(data) 114 | TriggerServerEvent("icmysql:server:dropQuery", data) 115 | end) -------------------------------------------------------------------------------- /debug/assets/bg.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IceClusters/icmysql/8f11782a87db43582597802aca901f85cceda3fb/debug/assets/bg.mp4 -------------------------------------------------------------------------------- /debug/assets/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IceClusters/icmysql/8f11782a87db43582597802aca901f85cceda3fb/debug/assets/user.png -------------------------------------------------------------------------------- /debug/js/app.js: -------------------------------------------------------------------------------- 1 | var isOpen = false; 2 | const Colors = { 3 | // "danger": "#ca0a0a1c", 4 | // "warn": "#ffb3001c", 5 | // "success": "#8bff001c", 6 | // "info": "#0034ff1c" 7 | } 8 | const mainSection = "section__buttons" 9 | const sections = [ 10 | "section__resources", 11 | "section__dbs", 12 | "section__logs", 13 | "section__cache", 14 | "section__backup", 15 | "section__interceptor" 16 | ] 17 | var queries = []; 18 | 19 | var resources = []; 20 | 21 | var intercepting = false; 22 | var interceptedQueryID = null; 23 | 24 | function ReduceText(text, max) { 25 | if (text.length <= max) return text; 26 | return text.substr(0, max) + "..."; 27 | } 28 | 29 | function LoadStats() { 30 | var data = { 31 | queries: 700, 32 | slowQueries: 40, 33 | timeQueries: 800, 34 | timeQuerying: 50, 35 | scripts: 20, 36 | databasesUsed: 2, 37 | failedQueries: 5 38 | } 39 | $("#statsQueries").html("Queries: " + data.queries); 40 | $("#statsQueries").css("border-color", data.queries >= 1000 ? Colors.danger : data.queries >= 500 ? Colors.warn : Colors.success) 41 | 42 | $("#statsSlowQueries").html("Slow queries: " + data.slowQueries); 43 | $("#statsSlowQueries").css("border-color", data.slowQueries >= 40 ? Colors.danger : data.slowQueries >= 15 ? Colors.warn : Colors.success) 44 | 45 | $("#statsAverageQueries").html("Average queries: " + data.timeQueries + "ms"); 46 | $("#statsAverageQueries").css("border-color", data.timeQueries >= 1000 ? Colors.danger : data.timeQueries >= 300 ? Colors.warn : Colors.success) 47 | 48 | $("#statsTimeQuerying").html("Time querying: " + data.timeQuerying + " s"); 49 | $("#statsTimeQuerying").css("border-color", data.timeQuerying >= 100 ? Colors.danger : data.timeQuerying >= 50 ? Colors.warn : Colors.success) 50 | 51 | $("#statsScripts").html("Scripts: " + data.scripts); 52 | $("#statsScripts").css("border-color", data.scripts >= 40 ? Colors.danger : data.scripts >= 30 ? Colors.warn : Colors.success) 53 | 54 | $("#statsDBCount").html("Databases used: " + data.databasesUsed); 55 | $("#statsDBCount").css("border-color", data.databasesUsed >= 4 ? Colors.danger : data.databasesUsed > 2 ? Colors.warn : Colors.success) 56 | 57 | $("#statsFailedQueries").html("Failed queries: " + data.failedQueries); 58 | $("#statsFailedQueries").css("border-color", data.failedQueries >= 10 ? Colors.danger : data.failedQueries >= 5 ? Colors.warn : Colors.success) 59 | } 60 | 61 | function CloseResourceModal() { 62 | $(".popup_container").removeClass("animate__fadeInUp"); 63 | $(".popup_container").addClass("animate__fadeOutDown"); 64 | $("#resourceModal").removeClass("--active"); 65 | $("#resourceModal").addClass("--hide"); 66 | setTimeout(() => { 67 | $("#resourceModal").removeClass("--hide"); 68 | $("#resourceModal").css("display", "none"); 69 | }, 500); 70 | } 71 | 72 | function OpenResource(resourceName) { 73 | let exist = false; 74 | for (let i = 0; i < resources.length; i++) { 75 | if (resources[i].name == resourceName) { 76 | exist = true; 77 | break; 78 | } 79 | } 80 | if (!exist) return; 81 | $(".popup_container").removeClass("animate__fadeOutDown"); 82 | $("#resourceModal").css("display", ""); 83 | $("#resourceModal").addClass("--active"); 84 | $("#resourceModalTitle").html(resourceName); 85 | setTimeout(() => { 86 | $(".popup_container").addClass("animate__fadeInUp"); 87 | }, 100); 88 | var resourceQueries = queries.filter(query => query.resourceName == resourceName); 89 | $("#resourceModalBody").html(""); 90 | resourceQueries.forEach(query => { 91 | $("#resourceModalBody").append(` 92 | 93 | ${query.cache ? ' ' : ''}${query.db} 94 | ${ReduceText(query.query, 47)} 95 | ${ReduceText(JSON.stringify(query.values), 47)} 96 | ${query.time} ms 97 | 98 | `) 99 | }); 100 | } 101 | 102 | function ListenSearchInput() { 103 | $("#inputBox").on("input", () => { 104 | const input = $("#inputBox").val(); 105 | if (input.length < 0) return; 106 | const filtered = resources.filter(resource => resource.name.toLowerCase().includes(input.toLowerCase())); 107 | $("#resources-content").html(""); 108 | filtered.forEach(resource => { 109 | $("#resources-content").append(` 110 |
111 |
112 |
113 |
114 |

${resource.name}

115 |

${resource.description}

116 |
117 |
118 |
119 | 121 | 122 |
123 |
124 |
`); 125 | 126 | $("#resource-list-" + resource.name).click(() => { 127 | OpenResource(resource.name); 128 | }); 129 | }); 130 | }); 131 | } 132 | 133 | function LoadResources(rscs) { 134 | resources = rscs; 135 | $("#resources-content").html(""); 136 | 137 | resources.forEach(resource => { 138 | $("#resources-content").append(` 139 |
140 |
141 |
142 |
143 |

${resource.name}

144 |

${resource.description}

145 |
146 |
147 |
148 | 150 | 151 |
152 |
153 |
`); 154 | 155 | $("#resource-list-" + resource.name).click(() => { 156 | OpenResource(resource.name); 157 | }); 158 | }); 159 | } 160 | 161 | function LoadDBs(dbs) { 162 | dbs = JSON.parse(dbs) 163 | $("#dbs-content").html(""); 164 | for (let i = 0; i < dbs.length; i++) { 165 | const db = dbs[i]; 166 | var lastUse = null; 167 | queries.forEach(element => { 168 | if (element.db == db.id) { 169 | lastUse = element.currentTimestamp; 170 | } 171 | }); 172 | var date = new Date(lastUse); 173 | date = date.getHours() + ":" + date.getMinutes() + ", " + date.toDateString(); 174 | 175 | $("#dbs-content").append(` 176 |
177 |
178 |
179 | 182 | 183 |

Database #${db.id}

184 |
185 |
186 |
187 |
188 |

${date}

189 |

Last Use

190 | 192 |

${db.time} ms

193 |

Connection Time

194 |
195 | 196 |
197 |
198 |
199 |
200 | `) 201 | } 202 | } 203 | 204 | function LoadLogs(logs) { 205 | $("#logsModalBody").html(""); 206 | logs.forEach(log => { 207 | $("#logsModalBody").append(` 208 | 209 | ${log.type} 210 | ${log.message.replace("^0", "").replace("^1", "").replace("^2", "").replace("^3", "")} 211 | ${log.solution ? log.solution : "Unavailable"} 212 | 213 | `); 214 | }); 215 | } 216 | 217 | function LoadCache(cache) { 218 | $("#cacheModalBody").html(""); 219 | for (let i = 0; i < cache.length; i++) { 220 | $("#cacheModalBody").append(` 221 | 222 | ${cache[i].table} 223 | ${cache[i].hash} 224 | ${ReduceText(JSON.stringify(cache[i].values), 60)} 225 | 226 | `); 227 | } 228 | } 229 | 230 | function LoadBackups(backups) { 231 | $("#backupModalBody").html(""); 232 | for (let i = 0; i < backups.length; i++) { 233 | $("#backupModalBody").append(` 234 | 235 | ${backups[i].name} 236 | ${backups[i].date} 237 | ${backups[i].size} mb 238 | 239 | `); 240 | } 241 | } 242 | 243 | function GoBack() { 244 | sections.forEach(element => { 245 | $(`#${element}`).css("display", "none"); 246 | }); 247 | $(`#${mainSection}`).css("display", "flex"); 248 | } 249 | 250 | function OpenSection(sec) { 251 | if(sec == "section__interceptor") { 252 | $.post("https://icmysql/suscribeInterceptor", {}); 253 | }; 254 | $(`#${mainSection}`).css("display", "none"); 255 | $(`#${sec}`).css("display", "flex"); 256 | } 257 | 258 | let isOpened = false; 259 | 260 | function OpenUI(state) { 261 | if (state){ 262 | $("#main-container").css("display", "flex"); 263 | $(".menu").removeClass("animate__fadeOutDown"); 264 | $(".menu").css("display", "flex"); 265 | $(".menu").addClass("animate__fadeInUp"); 266 | }else { 267 | $(".menu").removeClass("animate__fadeInUp"); 268 | $(".menu").addClass("animate__fadeOutDown"); 269 | setTimeout(() => { 270 | $("#main-container").css("display", "none"); 271 | $.post("https://icmysql/closeDebugUI", {}); 272 | }, 1000); 273 | } 274 | 275 | 276 | isOpen = state; 277 | } 278 | 279 | // LoadResources([ 280 | // { 281 | // "name": "ice_gym", 282 | // "description": "Script that manage all connections and querys to the database." 283 | // }, 284 | // { 285 | // "name": "ice_hud", 286 | // "description": "Script that manage all connections and querys to the database." 287 | // } 288 | // ]); 289 | 290 | function HeldNewInterceptedQuery(id, data) { 291 | $("#traffic-light").addClass("hidden"); 292 | $("#requestDataContainer").removeClass("hidden"); 293 | 294 | $("#btnforward").addClass("active"); 295 | $("#btndrop").addClass("active"); 296 | 297 | new JsonViewer({ 298 | value: data, 299 | theme: 'dark', 300 | expand: true, 301 | }).render('#json-viewer') 302 | 303 | const parser = new NodeSQLParser.Parser() 304 | const ast = parser.astify(data.query) 305 | new JsonViewer({ 306 | value: ast, 307 | theme: 'dark', 308 | expand: true, 309 | }).render('#json-viewer-query') 310 | 311 | $("#dbIntercQuery").val(data.dbId); 312 | $("#queryIntercQuery").val(data.query); 313 | $("#valuesIntercQuery").val(JSON.stringify(data.values)); 314 | $("#scriptIntercQuery").val(data.resourceName); 315 | $("#typeIntercQuery").val(data.type); 316 | $("#hashIntercQuery").val("DISABLED"); 317 | 318 | interceptedQueryID = id; 319 | } 320 | 321 | $(document).ready(function () { 322 | // OpenSection("section__interceptor") 323 | ListenSearchInput(); 324 | // LoadStats(); 325 | // LoadDBs(); 326 | 327 | // // Only for testing 328 | // OpenSection("section__backup") 329 | 330 | window.addEventListener("message", function (event) { 331 | if (event.data.action == undefined) return; 332 | switch (event.data.action) { 333 | case "open": 334 | $.post("https://icmysql/loadData", JSON.stringify({ "load": "queries" })); 335 | OpenUI(true); 336 | break; 337 | case "loadData": 338 | if (event.data.info == "dbs") { 339 | LoadDBs(event.data.data); 340 | } else if (event.data.info == "queries") { 341 | queries = event.data.data; 342 | $.post("https://icmysql/loadData", JSON.stringify({ "load": "dbs" })); 343 | $.post("https://icmysql/loadData", JSON.stringify({ "load": "resources" })); 344 | $.post("https://icmysql/loadData", JSON.stringify({ "load": "logs" })); 345 | $.post("https://icmysql/loadData", JSON.stringify({ "load": "cache" })); 346 | $.post("https://icmysql/loadData", JSON.stringify({ "load": "backup" })); 347 | } else if (event.data.info == "resources") { 348 | LoadResources(event.data.data); 349 | } else if (event.data.info == "logs") { 350 | LoadLogs(event.data.data) 351 | } else if (event.data.info == "cache") { 352 | LoadCache(JSON.parse(event.data.data)) 353 | } else if (event.data.info == "backup") { 354 | LoadBackups(event.data.data) 355 | } 356 | break; 357 | case "setInterceptor": 358 | intercepting = event.data.data; 359 | if(intercepting) { 360 | $("#btnIntercept").addClass("enabled"); 361 | $("#btnIntercept").text("Intercept is on"); 362 | $("#redLight").prop('checked', true); 363 | $("#greenLight").prop('checked', false); 364 | $("#intercept-traffic-title").text("Intercept is on") 365 | $("#intercept-traffic-desc").text("When enabled, queries sent by the server are held here so that you can analyze and modify them before forwading them to the target database.") 366 | } else { 367 | $("#traffic-light").removeClass("hidden"); 368 | $("#requestDataContainer").addClass("hidden"); 369 | 370 | $("#btnforward").removeClass("active"); 371 | $("#btndrop").removeClass("active"); 372 | 373 | $("#btnIntercept").removeClass("enabled"); 374 | $("#btnIntercept").text("Intercept is off"); 375 | $("#redLight").prop('checked', false); 376 | $("#greenLight").prop('checked', true); 377 | $("#intercept-traffic-title").text("Intercept is off") 378 | $("#intercept-traffic-desc").text("Queries sent by the server will be hel here so that you can analyze and modify them before forwading them to the target database.") 379 | } 380 | break; 381 | case "getQueryData": 382 | HeldNewInterceptedQuery(event.data.queryID, event.data.data); 383 | break; 384 | } 385 | }) 386 | window.addEventListener("keydown", function (event) { 387 | if (event.key == "Escape" && isOpen) { 388 | OpenUI(false); 389 | } 390 | }) 391 | 392 | $("#btnIntercept").click(function () { 393 | $.post("https://icmysql/setInterceptor", !intercepting); 394 | }); 395 | 396 | $("#btnforward").click(function () { 397 | const forwardData = { 398 | dbId: $("#dbIntercQuery").val(), 399 | query: $("#queryIntercQuery").val(), 400 | values: JSON.parse($("#valuesIntercQuery").val()), 401 | resourceName: $("#scriptIntercQuery").val(), 402 | type: $("#typeIntercQuery").val(), 403 | hash: $("#hashIntercQuery").val(), 404 | cache: false 405 | } 406 | $.post("https://icmysql/forwardQuery", {queryID: interceptedQueryID, data: forwardData}); 407 | 408 | $("#traffic-light").removeClass("hidden"); 409 | $("#requestDataContainer").addClass("hidden"); 410 | 411 | $("#btnforward").removeClass("active"); 412 | $("#btndrop").removeClass("active"); 413 | }); 414 | 415 | $("#btndrop").click(function () { 416 | $.post("https://icmysql/dropQuery", interceptedQueryID); 417 | 418 | $("#traffic-light").removeClass("hidden"); 419 | $("#requestDataContainer").addClass("hidden"); 420 | 421 | $("#btnforward").removeClass("active"); 422 | $("#btndrop").removeClass("active"); 423 | }); 424 | 425 | if(window.location == window.parent.location) { 426 | OpenUI(true); 427 | } else { 428 | OpenUI(false); 429 | } 430 | }) -------------------------------------------------------------------------------- /debug/js/tabs.js: -------------------------------------------------------------------------------- 1 | import { Tablist } from "https://cdn.jsdelivr.net/npm/jolty/dist/jolty.esm.min.js"; 2 | 3 | 4 | Tablist.data("navigation", (tablist) => { 5 | const marker = tablist.firstElementChild; 6 | 7 | const setMarker = (tab) => { 8 | marker.style.width = tab.offsetWidth + "px"; 9 | marker.style.translate = tab.offsetLeft + "px"; 10 | }; 11 | 12 | return { 13 | data: "ui-tabs", 14 | on: { 15 | show(instance, { tab }) { 16 | setMarker(tab); 17 | } 18 | } 19 | }; 20 | }); 21 | 22 | Tablist.initAll(); -------------------------------------------------------------------------------- /debug/styles/app.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Raleway:wght@600;800&display=swap'); 2 | 3 | @import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700&display=swap'); 4 | 5 | body { 6 | height: 100vh; 7 | padding: 0; 8 | margin: 0; 9 | /* background-color: #00000074; */ 10 | overflow: hidden; 11 | } 12 | 13 | * { 14 | box-sizing: border-box; 15 | font-family: 'Montserrat', sans-serif; 16 | } 17 | 18 | .container { 19 | display: flex; 20 | justify-content: center; 21 | align-items: center; 22 | width: 100%; 23 | height: 100%; 24 | } 25 | 26 | .container .menu { 27 | position: relative; 28 | display: flex; 29 | flex-direction: column; 30 | justify-content: flex-start; 31 | align-items: center; 32 | width: 90vh; 33 | height: 55vh; 34 | background-color: var(--bg__color); 35 | border-radius: 7px; 36 | overflow: hidden; 37 | border: 6px solid var(--border-top); 38 | } 39 | 40 | .menu video { 41 | position: absolute; 42 | z-index: 1; 43 | width: 100%; 44 | height: 100%; 45 | top: 0; 46 | opacity: 0.05; 47 | left: 0; 48 | object-fit: cover; 49 | } 50 | 51 | 52 | .main { 53 | position: relative; 54 | display: flex; 55 | align-items: center; 56 | justify-content: center; 57 | z-index: 2; 58 | width: 100%; 59 | height: 55vh; 60 | object-fit: cover; 61 | } 62 | 63 | 64 | .section__buttons { 65 | width: calc(100% - 20px); 66 | height: calc(100% - 20px); 67 | display: flex; 68 | justify-content: center; 69 | align-items: center; 70 | } 71 | 72 | .grid { 73 | width: 100%; 74 | height: 100%; 75 | padding: 10px 20px; 76 | display: grid; 77 | grid-template-columns: 1fr 1fr; 78 | grid-template-rows: 0.3fr 1fr 1fr 1fr; 79 | gap: 5px 5px; 80 | grid-template-areas: 81 | "profile__section profile__section" 82 | "resources__section dbs__section" 83 | "logs__section cache__section" 84 | "backups__section interceptor__section"; 85 | } 86 | 87 | .profile__section { 88 | display: flex; 89 | justify-content: flex-start; 90 | grid-area: profile__section; 91 | background-color: var(--bg__color); 92 | width: 100%; 93 | flex-direction: row; 94 | align-items: flex-start; 95 | } 96 | 97 | .container--profile{ 98 | display: flex; 99 | justify-content: flex-end; 100 | align-items: flex-start; 101 | flex-direction: row; 102 | flex: 1; 103 | } 104 | 105 | .profile--content{ 106 | display: flex; 107 | justify-content: flex-end; 108 | align-items: center; 109 | flex-direction: row; 110 | } 111 | 112 | .profile__image { 113 | width: 30px; 114 | height: 30px; 115 | border-radius: 50%; 116 | background-color: var(--bg__btn--color); 117 | margin: 0 1vh; 118 | overflow: hidden; 119 | } 120 | 121 | .profile__image img { 122 | position: relative; 123 | width: 100%; 124 | height: 100%; 125 | object-fit: cover; 126 | } 127 | 128 | .profile__name{ 129 | color: var(--color-text--secondary); 130 | font-size: 1rem; 131 | margin: 0; 132 | 133 | } 134 | 135 | .title { 136 | color: white; 137 | font-size: 1.5rem; 138 | width: auto; 139 | margin: 0; 140 | height: auto; 141 | text-transform: uppercase; 142 | 143 | } 144 | 145 | .resources__section { 146 | grid-area: resources__section; 147 | background-color: var(--bg__color) 148 | } 149 | 150 | .dbs__section { 151 | grid-area: dbs__section; 152 | } 153 | 154 | .logs__section { 155 | grid-area: logs__section; 156 | } 157 | 158 | .cache__section { 159 | grid-area: cache__section; 160 | } 161 | 162 | .interceptor__section { 163 | grid-area: interceptor__section; 164 | } 165 | 166 | .backups__section { 167 | grid-area: backups__section; 168 | } 169 | 170 | 171 | 172 | .button__style { 173 | cursor: pointer; 174 | border-radius: 7px; 175 | display: flex; 176 | align-items: center; 177 | justify-content: center; 178 | flex-direction: column; 179 | border-top: 2px solid var(--hover-button); 180 | background-color: var(--hover-button); 181 | transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275), background-color 0.3s ease-in-out, border-radius 0.3s ease-in-out; 182 | } 183 | 184 | .button__style:hover { 185 | border: 0px solid transparent; 186 | transform: scale(0.99); 187 | background-color: var(--hover-button--color); 188 | } 189 | 190 | .resources__section.button__style:hover { 191 | border-radius: 20px 0 0px 0; 192 | } 193 | 194 | .dbs__section.button__style:hover { 195 | border-radius: 0px 20px 0px 0; 196 | } 197 | 198 | .cache__section.button__style:hover { 199 | border-radius: 0px 0px 0px 0; 200 | } 201 | 202 | .logs__section.button__style:hover { 203 | border-radius: 0px 0px 0px 0; 204 | } 205 | 206 | .backups__section.button__style:hover { 207 | border-radius: 0 0 20px 20px; 208 | } 209 | 210 | 211 | .button__style i { 212 | font-size: 2.5rem; 213 | color: var(--color--primary); 214 | } 215 | 216 | .button__style p { 217 | bottom: 0; 218 | margin: 1.1vh; 219 | font-size: 1.1rem; 220 | color: var(--color-text--secondary); 221 | } 222 | 223 | .section__resources { 224 | /* display: flex; */ 225 | justify-content: center; 226 | align-items: center; 227 | flex-direction: column; 228 | width: calc(100% - 50px); 229 | height: calc(100% - 50px); 230 | display: none; 231 | } 232 | 233 | .section__dbs { 234 | justify-content: center; 235 | align-items: center; 236 | flex-direction: column; 237 | width: calc(100% - 50px); 238 | height: calc(100% - 50px); 239 | display: none; 240 | } 241 | 242 | .section__logs { 243 | justify-content: center; 244 | align-items: center; 245 | flex-direction: column; 246 | width: calc(100% - 50px); 247 | height: calc(100% - 50px); 248 | display: none; 249 | } 250 | 251 | .section__cache { 252 | justify-content: center; 253 | align-items: center; 254 | flex-direction: column; 255 | width: calc(100% - 50px); 256 | height: calc(100% - 50px); 257 | display: none; 258 | } 259 | 260 | .section__backup { 261 | justify-content: center; 262 | align-items: center; 263 | flex-direction: column; 264 | width: calc(100% - 50px); 265 | height: calc(100% - 50px); 266 | display: none; 267 | } 268 | 269 | .section__interceptor { 270 | justify-content: center; 271 | align-items: center; 272 | flex-direction: column; 273 | width: calc(100% - 50px); 274 | height: calc(100% - 50px); 275 | display: none; 276 | } 277 | 278 | .row { 279 | display: flex; 280 | justify-content: center; 281 | align-items: center; 282 | width: 100%; 283 | height: 100%; 284 | } 285 | 286 | .return { 287 | display: flex; 288 | justify-content: flex-end; 289 | width: 100%; 290 | order: 1; 291 | } 292 | 293 | .btn__style { 294 | cursor: pointer; 295 | border-radius: 3px; 296 | /* font-size: 1.5rem; */ 297 | padding: 0.5vh 1.5vh; 298 | margin-right: 0.3vh; 299 | border-top: 2px solid var(--border-top); 300 | color: var(--color--placeholder); 301 | background-color: var(--bg__btn--color); 302 | transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275), background-color 0.3s ease-in-out; 303 | } 304 | 305 | .btn__style:hover { 306 | transform: scale(1.1); 307 | background-color: var(--bg__btn--hover); 308 | } 309 | 310 | *::-webkit-scrollbar-track { 311 | background-color: #00000038; 312 | } 313 | 314 | *::-webkit-scrollbar { 315 | width: 3px; 316 | background-color: rgba(0, 0, 0, 0.185); 317 | border-radius: 3px; 318 | } 319 | 320 | *::-webkit-scrollbar-thumb { 321 | background-color: var(--color-primary); 322 | border-radius: 3px; 323 | } 324 | 325 | @keyframes bg-hide { 326 | 0% { 327 | backdrop-filter: blur(10px); 328 | background-color: rgba(5, 5, 5, 0.5); 329 | } 330 | 331 | 100% { 332 | backdrop-filter: blur(0px); 333 | background-color: #00000000; 334 | } 335 | } -------------------------------------------------------------------------------- /debug/styles/backup.css: -------------------------------------------------------------------------------- 1 | .container--backup{ 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: center; 6 | width: 100%; 7 | height: 100%; 8 | } 9 | 10 | .adg--backup{ 11 | width: 100%; 12 | height: 95%; 13 | } 14 | 15 | .backup--content { 16 | margin-top: 2vh; 17 | width: 100%; 18 | max-height: 100%; 19 | } 20 | 21 | .--backup { 22 | height: 88%; 23 | width: 100%; 24 | } 25 | -------------------------------------------------------------------------------- /debug/styles/cache.css: -------------------------------------------------------------------------------- 1 | .container--cache{ 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: center; 6 | width: 100%; 7 | height: 100%; 8 | } 9 | 10 | .adg--cache{ 11 | width: 100%; 12 | height: 95%; 13 | } 14 | 15 | .cache--content { 16 | margin-top: 2vh; 17 | width: 100%; 18 | max-height: 100%; 19 | } 20 | 21 | .--modal { 22 | height: 88%; 23 | width: 100%; 24 | } 25 | -------------------------------------------------------------------------------- /debug/styles/database.css: -------------------------------------------------------------------------------- 1 | .container--dbs{ 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: center; 6 | width: 100%; 7 | height: 100%; 8 | } 9 | 10 | .boxdb { 11 | position: relative; 12 | display: flex; 13 | justify-content: flex-start; 14 | align-items: flex-start; 15 | flex-direction: column; 16 | /* padding: 0.0vh 2vh; */ 17 | width: 100%; 18 | height: 20vh; 19 | border-top: 2px solid var(--border--top); 20 | background-color: var(--color-box); 21 | border-radius: 5px; 22 | transition: background-color 0.3s cubic-bezier(0.215, 0.610, 0.355, 1); 23 | overflow: hidden; 24 | user-select: none; 25 | } 26 | 27 | .boxdb:hover { 28 | background-color: var(--color-box-hover); 29 | } 30 | 31 | .boxdb .icon--box { 32 | width: 2.5vh; 33 | color: var(--color--placeholder); 34 | } 35 | 36 | .boxdb .desc.sub { 37 | font-size: 0.6rem; 38 | color: var(--color-text--secondary) !important; 39 | /* height: 3vh; */ 40 | width: fit-content; 41 | max-width: 100%; 42 | overflow: hidden; 43 | } 44 | 45 | .boxdb .desc--db{ 46 | font-size: 0.7rem; 47 | margin: 0; 48 | color: silver; 49 | margin-left: 2vh; 50 | width: fit-content; 51 | max-width: 100%; 52 | overflow: hidden; 53 | } 54 | 55 | .column{ 56 | display: flex; 57 | flex-direction: column; 58 | justify-content: center; 59 | align-items: center; 60 | width: 100%; 61 | height: 100%; 62 | } 63 | 64 | .justify-content{ 65 | justify-content: center; 66 | } 67 | 68 | .align-item { 69 | align-items: flex-start; 70 | } 71 | 72 | .boxdb .icon--box { 73 | width: 7vh; 74 | height: 7vh; 75 | } 76 | 77 | .icon__db{ 78 | display: flex; 79 | align-items: center; 80 | justify-content: center; 81 | flex-direction: column; 82 | width: 100%; 83 | } 84 | 85 | .adjust-content{ 86 | padding: 0.0vh 2vh;; 87 | width: 100%; 88 | height: 100%; 89 | } 90 | -------------------------------------------------------------------------------- /debug/styles/input.css: -------------------------------------------------------------------------------- 1 | .inputBox_container { 2 | display: flex; 3 | align-items: center; 4 | flex-direction: row; 5 | /* max-width: 18em; */ 6 | width: 14em; 7 | height: fit-content; 8 | margin-right: 1vh; 9 | padding: 0.5vh; 10 | background-color: var(--bg__color--input); 11 | border-top: 2px solid var(--border-top); 12 | border-radius: 5px; 13 | transition: background-color 0.3s cubic-bezier(0.215, 0.610, 0.355, 1), width 0.4s ease-in-out; 14 | overflow: hidden; 15 | } 16 | 17 | .inputBox_container:hover { 18 | background-color: var(--bg--hover-input); 19 | } 20 | 21 | .inputBox_container:focus-within { 22 | background-color: var(--bg--hover-input); 23 | width: 18em; 24 | } 25 | 26 | .search_icon { 27 | height: 1.6em; 28 | padding: 0 0.5em 0 0.0em; 29 | } 30 | 31 | .inputBox { 32 | background-color: transparent; 33 | color: var(--color--secondary); 34 | outline: none; 35 | width: 100%; 36 | border: 0; 37 | padding: 0.5em 0.5em 0.5em 0.5em; 38 | } 39 | 40 | ::placeholder { 41 | color: var(--color--placeholder); 42 | } 43 | -------------------------------------------------------------------------------- /debug/styles/intercept.css: -------------------------------------------------------------------------------- 1 | .intercept { 2 | display: flex; 3 | margin: auto; 4 | height: 2.5em; 5 | width: 10em; 6 | background-color: #ffffff24; 7 | color: #a3a3a3; 8 | transition: 0.3s; 9 | user-select: none; 10 | } 11 | 12 | .intercept:hover { 13 | background-color: #2c2c2c44; 14 | color: #ffffff; 15 | border-color: #2d2d2d; 16 | } 17 | 18 | .intercept--wrapper{ 19 | display: grid; 20 | grid-template-columns: repeat(2, 1fr); 21 | grid-template-rows: repeat(7, 0.2fr); 22 | gap: 0.5vh; 23 | width: 100%; 24 | height: 100%; 25 | overflow: hidden; 26 | } 27 | 28 | .button_intercept--style { 29 | display: flex; 30 | justify-content: center; 31 | align-items: center; 32 | min-height: 2vh; 33 | text-align: center; 34 | background-color: var(--border-top); 35 | border: 2px dashed var(--border-top); 36 | border-radius: 5px; 37 | font-size: 0.7rem; 38 | color: var(--bg__btn--color); 39 | cursor: pointer; 40 | transition: 0.3s cubic-bezier(0.215, 0.610, 0.355, 1) all; 41 | } 42 | 43 | .button_intercept--style.active { 44 | background-color: #2c2c2c44; 45 | color: #ffffff; 46 | border-color: #2d2d2d; 47 | } 48 | 49 | .button_intercept--style:hover { 50 | transform: scale(0.95); 51 | } 52 | 53 | .button_intercept--style.enabled { 54 | background-color: rgba(145, 255, 108, 0.609); 55 | border-color: rgba(145, 255, 108, 0.609); 56 | } 57 | 58 | 59 | .container__intercepting { 60 | display: flex; 61 | flex-direction: column; 62 | justify-content: center; 63 | align-items: center; 64 | padding-top: 5vh; 65 | width: 100%; 66 | height: 100%; 67 | } 68 | 69 | .container__intercepting .titleInt { 70 | margin-top: 3vh; 71 | color: silver; 72 | } 73 | 74 | .container__intercepting .wrap-text { 75 | display: flex; 76 | flex-direction: column; 77 | justify-content: center; 78 | align-items: center; 79 | text-align: center; 80 | width: 100%; 81 | max-width: 70%; 82 | color: gray; 83 | } 84 | 85 | 86 | .container__tabs { 87 | display: flex; 88 | flex-direction: column; 89 | justify-content: flex-start; 90 | align-items: flex-start; 91 | width: 100%; 92 | height: 100%; 93 | padding-top: 2vh; 94 | padding-left: 1vh; 95 | padding-right: 1vh; 96 | /* background-color: red; */ 97 | } 98 | 99 | 100 | .hidden { 101 | display: none; 102 | } 103 | 104 | 105 | .options--style { 106 | display: flex; 107 | justify-content: center; 108 | align-items: center; 109 | min-height: 5vh; 110 | text-align: center; 111 | background-color: var(--border-top); 112 | border: 2px dashed var(--border-top); 113 | border-radius: 5px; 114 | font-size: 0.7rem; 115 | color: var(--bg__btn--color); 116 | cursor: pointer; 117 | transition: 0.3s cubic-bezier(0.215, 0.610, 0.355, 1) all; 118 | } -------------------------------------------------------------------------------- /debug/styles/logs.css: -------------------------------------------------------------------------------- 1 | .container--logs{ 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: center; 6 | width: 100%; 7 | height: 100%; 8 | } 9 | 10 | .adg--logs{ 11 | width: 100%; 12 | height: 95%; 13 | } 14 | 15 | .logs--content { 16 | margin-top: 2vh; 17 | width: 100%; 18 | max-height: 100%; 19 | } 20 | 21 | .--modal { 22 | height: 88%; 23 | width: 100%; 24 | } 25 | -------------------------------------------------------------------------------- /debug/styles/resources.css: -------------------------------------------------------------------------------- 1 | 2 | .rs--grid { 3 | display: flex; 4 | justify-content: center; 5 | align-items: center; 6 | flex-direction: column; 7 | width: 100%; 8 | height: 100%; 9 | overflow: hidden; 10 | } 11 | 12 | .rs--grid.adj { 13 | margin-right: 2vh; 14 | } 15 | 16 | 17 | .rs--grid.stats { 18 | display: flex; 19 | justify-content: center; 20 | align-items: center; 21 | flex-direction: column; 22 | width: 50%; 23 | height: 100%; 24 | overflow: hidden; 25 | } 26 | 27 | .container__title { 28 | display: flex; 29 | justify-content: flex-start; 30 | align-items: center; 31 | color: var(--color--title); 32 | width: 100%; 33 | height: 10%; 34 | overflow: hidden; 35 | } 36 | 37 | .container__title h1 { 38 | margin-left: 1vh; 39 | font-size: 1.5rem; 40 | } 41 | 42 | .container__title .inputBox_container { 43 | margin-left: auto; 44 | } 45 | 46 | .container__content { 47 | display: flex; 48 | justify-content: center; 49 | align-items: flex-start; 50 | color: var(--color--title); 51 | width: 100%; 52 | height: 90%; 53 | overflow: hidden; 54 | } 55 | 56 | .scroll { 57 | margin-top: 2vh; 58 | position: relative; 59 | overflow-y: auto; 60 | scroll-snap-type: y mandatory; 61 | padding: 1vh; 62 | width: 100%; 63 | height: fit-content; 64 | max-height: 100%; 65 | display: grid; 66 | grid-template-columns: 1fr 1fr; 67 | gap: 1vh; 68 | } 69 | 70 | .scroll__dbs { 71 | margin-top: 2vh; 72 | position: relative; 73 | overflow-y: auto; 74 | scroll-snap-type: y mandatory; 75 | padding: 1vh; 76 | width: 100%; 77 | height: fit-content; 78 | max-height: 100%; 79 | display: grid; 80 | grid-template-columns: 1fr 1fr 1fr; 81 | gap: 1vh; 82 | } 83 | 84 | .box { 85 | position: relative; 86 | display: flex; 87 | justify-content: flex-start; 88 | /* padding: 0.0vh 2vh; */ 89 | align-items: center; 90 | width: 100%; 91 | height: 10vh; 92 | border-top: 2px solid var(--border--top); 93 | background-color: var(--color-box); 94 | border-radius: 5px; 95 | transition: background-color 0.3s cubic-bezier(0.215, 0.610, 0.355, 1); 96 | overflow: hidden; 97 | user-select: none; 98 | } 99 | 100 | .box:hover { 101 | background-color: var(--color-box-hover); 102 | } 103 | 104 | 105 | .grid--box { 106 | display: flex; 107 | justify-content: flex-start; 108 | align-items: center; 109 | } 110 | 111 | .flex { 112 | display: flex; 113 | justify-content: center; 114 | align-items: flex-start; 115 | flex-direction: column; 116 | width: 100%; 117 | height: 10vh; 118 | } 119 | 120 | .adjust { 121 | padding: 0.0vh 2vh; 122 | height: 100%; 123 | } 124 | 125 | .box .icon--box { 126 | width: 2.5vh; 127 | color: var(--color--placeholder); 128 | } 129 | 130 | .box .desc { 131 | font-size: 0.7rem; 132 | color: var(--bg__btn--color); 133 | height: 3vh; 134 | width: fit-content; 135 | max-width: 100%; 136 | overflow: hidden; 137 | } 138 | 139 | .right--icon { 140 | display: flex; 141 | justify-content: center; 142 | align-items: flex-start; 143 | margin-bottom: auto; 144 | width: 30%; 145 | height: 100%; 146 | } 147 | 148 | 149 | .--stats { 150 | display: flex; 151 | align-items: center; 152 | justify-content: center; 153 | flex-direction: column; 154 | margin-top: 3.5vh; 155 | } 156 | 157 | .tags--wrapper { 158 | display: grid; 159 | grid-template-columns: repeat(2, 1fr); 160 | grid-template-rows: repeat(4, 0.2fr); 161 | gap: 0.5vh; 162 | width: 100%; 163 | height: 55%; 164 | overflow: hidden; 165 | } 166 | 167 | .tags { 168 | display: flex; 169 | justify-content: center; 170 | align-items: center; 171 | min-height: 2vh; 172 | /* padding: 1vh 1vh; */ 173 | text-align: center; 174 | background-color: var(--border-top); 175 | border: 2px dashed var(--border-top); 176 | border-radius: 5px; 177 | font-size: 0.7rem; 178 | color: var(--bg__btn--color); 179 | } 180 | 181 | .--graphic { 182 | height: 45%; 183 | } 184 | 185 | .modal_container { 186 | position: absolute; 187 | display: none; 188 | justify-content: center; 189 | align-items: center; 190 | width: 100%; 191 | height: 100%; 192 | background-color: rgba(0, 0, 0, 0.0); 193 | backdrop-filter: blur(5px); 194 | z-index: 3; 195 | -webkit-backdrop-filter: blur(5px); 196 | } 197 | 198 | .--active { 199 | display: flex; 200 | animation: bg 1s ease-in-out forwards; 201 | } 202 | 203 | .--hide { 204 | display: flex; 205 | animation: bg-hide .4s ease-in-out forwards; 206 | } 207 | 208 | 209 | .modal--style { 210 | position: relative; 211 | /* display: none; */ 212 | opacity: 0; 213 | width: 90%; 214 | height: 80%; 215 | border-radius: 8px; 216 | background-color: var(--modal--color); 217 | z-index: 4; 218 | } 219 | 220 | .modal--title { 221 | display: flex; 222 | justify-content: flex-start; 223 | align-items: center; 224 | width: 100%; 225 | height: 12%; 226 | overflow: hidden; 227 | } 228 | 229 | .txt--title { 230 | margin-left: 2.5vh; 231 | font-size: 1.1em; 232 | color: var(--color--title); 233 | } 234 | 235 | .icon--modal { 236 | margin-left: auto; 237 | margin-right: 2vh; 238 | color: var(--color--placeholder); 239 | padding: 1vh; 240 | padding-right: 0; 241 | transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275), color 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); 242 | ; 243 | } 244 | 245 | .icon--modal:hover { 246 | transform: scale(1.1); 247 | color: var(--color--secondary); 248 | } 249 | 250 | .shadow-sm { 251 | transition: all ease-in-out 0.3s; 252 | } 253 | 254 | .table__container { 255 | height: auto; 256 | background-color: transparent !important; 257 | overflow: auto; 258 | scrollbar-width: none; 259 | border-radius: 5px; 260 | transition: ease-in-out 0.3s all; 261 | width: 95%; 262 | height: 95%; 263 | } 264 | 265 | .tablaContent { 266 | width: 100%; 267 | border: 0; 268 | border-radius: 5px !important; 269 | box-sizing: border-box; 270 | border-collapse: collapse; 271 | border-spacing: 0; 272 | } 273 | 274 | .tablaContent td { 275 | border: 0; 276 | height: 50px; 277 | color: var(--color--secondary); 278 | padding: .1vh .5vw; 279 | 280 | } 281 | 282 | #resourceModalBody{ 283 | background-color: var(--modal--body); 284 | } 285 | 286 | .tablaContent tbody tr td:first-child { 287 | width: 8vw; 288 | } 289 | 290 | .tablaContent tbody tr td:last-child { 291 | width: 8vw; 292 | } 293 | 294 | .tablaContent tbody tr td { 295 | padding: 1vh .5vw; 296 | color: var(--modal_td--color) !important; 297 | background-color: transparent !important; 298 | transition: .3s ease all; 299 | 300 | } 301 | 302 | .tablaContent tbody tr { 303 | background-color: transparent !important; 304 | /* border-bottom: 1px solid var(--border-top); */ 305 | transition: .3s ease all; 306 | 307 | } 308 | 309 | 310 | 311 | 312 | .tablaContent thead th { 313 | position: sticky; 314 | top: 0; 315 | padding: 1vh .5vw; 316 | background-color: var(--bg__color--input); 317 | } 318 | 319 | 320 | 321 | 322 | .tablaContent thead tr { 323 | font-family: 'Montserrat' !important; 324 | } 325 | 326 | .tablaContent tbody td { 327 | /* background-color: red; */ 328 | text-align: center; 329 | } 330 | 331 | .table__container::-webkit-scrollbar { 332 | opacity: 0; 333 | width: 0; 334 | scrollbar-width: none; 335 | display: none; 336 | 337 | } 338 | 339 | .table__container::-webkit-scrollbar-track { 340 | opacity: 0; 341 | width: 0; 342 | scrollbar-width: none; 343 | display: none; 344 | } 345 | 346 | .table__container::-webkit-scrollbar-thumb { 347 | opacity: 0; 348 | width: 0; 349 | scrollbar-width: none; 350 | display: none; 351 | } 352 | 353 | #query{ 354 | text-align: left; 355 | font-family: "Montserrat" !important; 356 | font-size: 0.8rem; 357 | color: var(--color--secondary); 358 | font-weight: 500; 359 | } 360 | 361 | #values{ 362 | font-family: "Montserrat" !important; 363 | font-size: 0.9rem; 364 | color: var(--color--title); 365 | font-weight: 600; 366 | } 367 | 368 | #time{ 369 | font-family: "Montserrat" !important; 370 | font-size: 0.9rem; 371 | font-weight: 600; 372 | } 373 | 374 | 375 | @keyframes bg { 376 | 0% { 377 | backdrop-filter: blur(0px); 378 | background-color: #00000000; 379 | } 380 | 381 | 100% { 382 | backdrop-filter: blur(10px); 383 | background-color: rgba(5, 5, 5, 0.5); 384 | ; 385 | } 386 | } -------------------------------------------------------------------------------- /debug/styles/root.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --bg__color: rgb(17, 17, 17); 3 | --hover-button: rgba(255, 255, 255, 0.01); 4 | --hover-button--color: rgba(255, 255, 255, 0.048); 5 | --color--primary: #ffffff; 6 | --color-text--secondary: #353535; 7 | --color--secondary: #adadad; 8 | --color--placeholder: #808080; 9 | --bg__btn--color: #353535; 10 | --bg__btn--hover: #74747485; 11 | --bg__color--input: rgb(24, 24, 24); 12 | --bg--hover-input: rgb(28, 28, 28); 13 | --border-top: #ffffff05; 14 | --color--title: whitesmoke; 15 | --color-box: rgba(24, 24, 24, 0.7); 16 | --color-box-hover: rgba(28, 28, 28, 0.7); 17 | --modal--color: rgb(18, 18, 18); 18 | --modal--body: rgb(20, 20, 20); 19 | --modal_td--color: rgb(110, 110, 110); 20 | } 21 | -------------------------------------------------------------------------------- /debug/styles/tabs.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"); 2 | 3 | [hidden] { 4 | display: none !important; 5 | } 6 | 7 | .tabs { 8 | display: flex; 9 | align-items: center; 10 | justify-content: flex-start; 11 | border-radius: 0.5rem; 12 | padding: 0.15rem 0.5rem; 13 | /* background: rgba(255, 255, 255, 0.3); */ 14 | position: relative; 15 | outline: none; 16 | z-index: 1; 17 | /* &:has(:focus-visible) { 18 | outline: 3px solid rgb(17 21 36 / 50%); 19 | } */ 20 | } 21 | 22 | .tabs-marker { 23 | position: absolute; 24 | z-index: -1; 25 | /* background: rgba(106, 247, 231, 0.7); */ 26 | background: linear-gradient( 27 | 90deg, 28 | rgba(124, 124, 124, 0.7) 0%, 29 | rgba(148, 148, 148, 0.7) 100% 30 | ); 31 | top: 0.4rem; 32 | bottom: 0.4rem; 33 | left: 0; 34 | border-radius: 0.2rem; 35 | transition: 0.55s; 36 | } 37 | 38 | .tabs-tab { 39 | display: inline-flex; 40 | justify-content: flex-start; 41 | align-items: center; 42 | appearance: none; 43 | transition: all 150ms; 44 | outline-width: 2px; 45 | outline-offset: 2px; 46 | padding: 0px; 47 | border-width: 0; 48 | color: inherit; 49 | font-weight: 500; 50 | padding: 1rem; 51 | color: white; 52 | background: transparent; 53 | cursor: pointer; 54 | outline: none; 55 | font-family: inherit; 56 | color: rgba(255, 255, 255, 0.9); 57 | font-size: 0.95rem; 58 | } 59 | 60 | .tabs-tab:hover { 61 | color: rgba(255, 255, 255, 1); 62 | } 63 | 64 | .tabs-tab.ui-active { 65 | pointer-events: none; 66 | color: rgba(255, 255, 255, 1); 67 | } 68 | 69 | .tabpanels { 70 | display: flex; 71 | align-items: flex-start; 72 | justify-content: flex-start; 73 | background-color: rgb(24, 24, 24); 74 | border-radius: 0.5rem; 75 | width: 100%; 76 | height: 100%; 77 | border: 2px dashed var(--border-top); 78 | max-height: 327px; 79 | overflow-y: auto; 80 | } 81 | 82 | .tabpanel { 83 | padding: 1rem 1.25rem; 84 | /* text-align: center; */ 85 | min-height: 5rem; 86 | display: flex; 87 | align-items: flex-start; 88 | justify-content: flex-start; 89 | place-content: center; 90 | border-radius: 0.5rem; 91 | width: 100%; 92 | height: 100%; 93 | } 94 | 95 | .--options{ 96 | display: grid; 97 | grid-template-columns: repeat(2, 1fr); 98 | grid-template-rows: repeat(7, 0.2fr); 99 | gap: 0.5vh; 100 | } 101 | 102 | .tabpanel:focus-visible { 103 | outline: 3px solid rgb(17 21 36 / 50%); 104 | } 105 | 106 | .tabpanel.ui-enter-active { 107 | transition: all 200ms; 108 | transform-origin: center top; 109 | } 110 | 111 | .tabpanel.ui-enter-from { 112 | opacity: 0; 113 | transform: scale(0.98); 114 | } -------------------------------------------------------------------------------- /debug/styles/traffic-light.css: -------------------------------------------------------------------------------- 1 | 2 | .wrap { 3 | --black: #000000; 4 | --ch-black: #141414; 5 | --eer-black: #1b1b1b; 6 | --night-rider: #2e2e2e; 7 | --tz: 0px; 8 | --white: #ffffff; 9 | --af-white: #f3f3f3; 10 | --ch-white: #e1e1e1; 11 | --af-green: #91ff6c; 12 | --af-red: #ff6c6c; 13 | --ch-green: #6cff6c; 14 | --ch-red: #ff6c6c; 15 | font-family: Helvetica, sans-serif; 16 | display: flex; 17 | align-items: center; 18 | justify-content: center; 19 | 20 | } 21 | 22 | .back { 23 | position: relative; 24 | width: auto-fit; 25 | height: max-content; 26 | padding: 10px; 27 | border-radius: 8px; 28 | box-shadow: rgba(0, 0, 0, 0.4) 0px 2px 2px, rgba(0, 0, 0, 0.3) 0px 7px 13px -3px, rgba(0, 0, 0, 0.2) 0px -1px 0px inset; 29 | background-color: #171717; 30 | /* transform: rotateY(20deg) rotateX(-8deg); 31 | transform-style: preserve-3d; */ 32 | 33 | .layers { 34 | position: absolute; 35 | left: 0; top: 0; 36 | width: 100%; height: 100%; 37 | transform-style: preserve-3d; 38 | z-index: -1; 39 | } 40 | 41 | .layer { 42 | position: absolute; 43 | left: 0; top: 0; 44 | width: 100%; height: 100%; 45 | border-radius: 1em; 46 | transform: translateZ(var(--tz)); 47 | box-shadow: 0 0 0.9em #000d inset; 48 | 49 | &:nth-child(1) { 50 | --tz: 0px; 51 | } 52 | 53 | &:nth-child(2) { 54 | --tz: -4px; 55 | 56 | } 57 | 58 | &:nth-child(3) { 59 | --tz: -8px; 60 | } 61 | 62 | &:nth-child(4) { 63 | --tz: -12px; 64 | } 65 | 66 | &:nth-child(5) { 67 | --tz: -16px; 68 | } 69 | 70 | &:nth-child(6) { 71 | --tz: -20px; 72 | } 73 | 74 | &:nth-child(7) { 75 | --tz: -24px; 76 | } 77 | 78 | &:nth-child(8) { 79 | --tz: -28px; 80 | } 81 | 82 | &:nth-child(9) { 83 | --tz: -32px; 84 | } 85 | 86 | &:nth-child(10) { 87 | --tz: -36px; 88 | } 89 | } 90 | 91 | 92 | 93 | &:last-child { 94 | box-shadow: 0 0 0.5em #000d inset, 0 0 5px #000; 95 | } 96 | } 97 | 98 | .traffic-light { 99 | display: flex; 100 | flex-direction: row; 101 | justify-content: center; 102 | align-items: center; 103 | } 104 | 105 | 106 | .switch { 107 | display: block; 108 | position: relative; 109 | background-color: black; 110 | width: 50px; 111 | height: 50px; 112 | box-shadow: 0 0 10px 2px rgba(0, 0, 0, 0.2), 0 0 1px 2px var(--black), inset 0 2px 2px -2px var(--white), inset 0 0 2px 5px var(--night-rider), inset 0 0 2px 22px var(--black); 113 | border-radius: 50%; 114 | padding: 20px; 115 | margin: 5px; 116 | } 117 | 118 | .switch input { 119 | display: none; 120 | } 121 | 122 | .switch input:checked + .button .light { 123 | animation: flicker 0.2s infinite 0.3s; 124 | } 125 | 126 | .switch input:checked + .button .shine { 127 | opacity: 1; 128 | } 129 | 130 | .switch input:checked + .button .shadow { 131 | opacity: 0; 132 | } 133 | 134 | .switch .button { 135 | transition: all 0.3s cubic-bezier(1, 0, 1, 1); 136 | background-color: var(--night-rider); 137 | width: 35px; 138 | height: 35px; 139 | border-radius: 50%; 140 | position: relative; 141 | left: -0.78em; 142 | top: -0.79em; 143 | cursor: pointer; 144 | } 145 | 146 | .switch .light { 147 | opacity: 0; 148 | animation: light-off 1s; 149 | position: absolute; 150 | width: 100%; 151 | height: 100%; 152 | background-image: radial-gradient(var(--ch-red), var(--af-red) 70%, transparent 72%); 153 | } 154 | 155 | .switch .dots { 156 | position: absolute; 157 | width: 100%; 158 | height: 100%; 159 | background-image: radial-gradient(transparent 30%, var(--black) 70%); 160 | background-size: 10px 10px; 161 | border-radius: 50%; 162 | } 163 | 164 | @keyframes flicker { 165 | 0% { 166 | opacity: 1; 167 | } 168 | 169 | 80% { 170 | opacity: 0.8; 171 | } 172 | 173 | 100% { 174 | opacity: 1; 175 | } 176 | } 177 | 178 | @keyframes light-off { 179 | 0% { 180 | opacity: 1; 181 | } 182 | 183 | 80% { 184 | opacity: 0; 185 | } 186 | } 187 | 188 | .switch1 { 189 | display: block; 190 | position: relative; 191 | background-color: var(--black); 192 | width: 50px; 193 | height: 50px; 194 | box-shadow: 0 0 10px 2px rgba(0, 0, 0, 0.2), 0 0 1px 2px var(--black), inset 0 2px 2px -2px var(--white), inset 0 0 2px 5px var(--night-rider), inset 0 0 2px 22px var(--black); 195 | border-radius: 50%; 196 | padding: 20px; 197 | margin: 5px; 198 | } 199 | 200 | .switch1 input { 201 | display: none; 202 | } 203 | 204 | .switch1 input:checked + .button .light { 205 | animation: flicker 0.2s infinite 0.3s; 206 | } 207 | 208 | .switch1 input:checked + .button .shine { 209 | opacity: 1; 210 | } 211 | 212 | .switch1 input:checked + .button .shadow { 213 | opacity: 0; 214 | } 215 | 216 | .switch1 .button { 217 | transition: all 0.3s cubic-bezier(1, 0, 1, 1); 218 | background-color: var(--night-rider); 219 | width: 35px; 220 | height: 35px; 221 | border-radius: 50%; 222 | position: relative; 223 | left: -0.75em; 224 | top: -0.79em; 225 | cursor: pointer; 226 | } 227 | 228 | .switch1 .light { 229 | opacity: 0; 230 | animation: light-off 1s; 231 | position: absolute; 232 | width: 100%; 233 | height: 100%; 234 | background-image: radial-gradient(var(--ch-green), var(--af-green) 70%, transparent 72%); 235 | } 236 | 237 | .switch1 .dots { 238 | position: absolute; 239 | width: 100%; 240 | height: 100%; 241 | background-image: radial-gradient(transparent 30%, var(--black) 70%); 242 | background-size: 10px 10px; 243 | border-radius: 50%; 244 | } 245 | 246 | .switch2 { 247 | display: block; 248 | position: relative; 249 | background-color: var(--black); 250 | width: 70px; 251 | height: 70px; 252 | box-shadow: 0 0 10px 2px rgba(0, 0, 0, 0.2), 0 0 1px 2px var(--black), inset 0 2px 2px -2px var(--white), inset 0 0 2px 5px var(--night-rider), inset 0 0 2px 22px var(--black); 253 | border-radius: 50%; 254 | padding: 20px; 255 | margin: 5px; 256 | } 257 | 258 | .switch2 input { 259 | display: none; 260 | } 261 | 262 | .switch2 input:checked + .button .light { 263 | animation: flicker 0.2s infinite 0.3s; 264 | } 265 | 266 | .switch2 input:checked + .button .shine { 267 | opacity: 1; 268 | } 269 | 270 | .switch2 input:checked + .button .shadow { 271 | opacity: 0; 272 | } 273 | 274 | .switch2 .button { 275 | transition: all 0.3s cubic-bezier(1, 0, 1, 1); 276 | background-color: var(--night-rider); 277 | width: 55px; 278 | height: 55px; 279 | border-radius: 50%; 280 | position: relative; 281 | left: -0.75em; 282 | top: -0.79em; 283 | cursor: pointer; 284 | } 285 | 286 | .switch2 .light { 287 | opacity: 0; 288 | animation: light-off 1s; 289 | position: absolute; 290 | width: 100%; 291 | height: 100%; 292 | background-image: radial-gradient(var(--ch-white), var(--white) 70%, transparent 72%); 293 | } 294 | 295 | .switch2 .dots { 296 | position: absolute; 297 | width: 100%; 298 | height: 100%; 299 | background-image: radial-gradient(transparent 30%, var(--black) 70%); 300 | background-size: 10px 10px; 301 | border-radius: 50%; 302 | } -------------------------------------------------------------------------------- /docs/assets/Async.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IceClusters/icmysql/8f11782a87db43582597802aca901f85cceda3fb/docs/assets/Async.drawio.png -------------------------------------------------------------------------------- /docs/assets/ErrorSystem.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IceClusters/icmysql/8f11782a87db43582597802aca901f85cceda3fb/docs/assets/ErrorSystem.drawio.png -------------------------------------------------------------------------------- /docs/assets/MongoDB_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IceClusters/icmysql/8f11782a87db43582597802aca901f85cceda3fb/docs/assets/MongoDB_Logo.png -------------------------------------------------------------------------------- /docs/assets/ORM.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IceClusters/icmysql/8f11782a87db43582597802aca901f85cceda3fb/docs/assets/ORM.drawio.png -------------------------------------------------------------------------------- /docs/assets/Redis_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IceClusters/icmysql/8f11782a87db43582597802aca901f85cceda3fb/docs/assets/Redis_Logo.png -------------------------------------------------------------------------------- /docs/assets/Sync.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IceClusters/icmysql/8f11782a87db43582597802aca901f85cceda3fb/docs/assets/Sync.drawio.png -------------------------------------------------------------------------------- /docs/features/AwaitAsync.mkd: -------------------------------------------------------------------------------- 1 | # Await and Async Support 2 | ### Description 3 | In the functions that make queries to the database, you can now use the ```await``` keyword to wait for the query to finish and get the result, this is useful when you want to make a query and get the result in the same function, without having to use callbacks or promises. 4 | 5 | ### Asynchronous Scheme 6 | This scheme is going to explain how asynchronous queries work in the script. This explication is based on JS so probably it's not going to be 100% accurate but it's going to be enough to understand how it works. 7 | ![](../assets/Async.drawio.png) 8 | ##### Steps 9 | 1. First we declare some vars to use later in the code. 10 | 2. We execute the console.log function. 11 | 3. We execute the query function, and we send a param as the callback function to be executed later. 12 | 4. We execute another console.log function. 13 | ##### Call Stack 14 | The call stack is like a list of tasks that a computer keeps track of while running a program. When a function is called, it gets added to the top of the stack. As the function completes, it's removed from the top. 15 | In this case as you can see the first step is an anonymous function because JS don't have a main function so all the code is executed inside an anonymous function. Then we have the console.log function, then the query function that register a callback in C++ APIs and when the query is finished the callback is added to the event loop that wait for the call stack to be empty to add the callback to the main thread and execute it. Then we have another console.log function and finally the anonymous function is removed from the call stack. 16 | ##### Conclusion 17 | So as we see the async functions execute in the main thread, but the callback is executed in another thread, so the main thread is not blocked while the query is being executed. That explains why the console.log function is execute before the query callback that is previous at the function. 18 | 19 | ### Synchronous Scheme 20 | This scheme is going to explain how synchronous queries work in the script. This explication is based on JS so probably it's not going to be 100% accurate but it's going to be enough to understand how it works. 21 | ![](../assets/Sync.drawio.png) 22 | ##### Call Stack 23 | As you can see in this scheme the call stack is the same as the async scheme, but in this case the main thread is blocked while the query is being executed, so the console.log function is executed after the query callback. 24 | 25 | ### Use 26 | You don't have to make anything special to use this feature as this is already implmented in the script, you just have to make the queries as you would do normally, the script will take care of the rest. 27 | 28 | ### Files 29 | This is the list of the involved files in this feature to help any developer to understand how it works: 30 | - ```src/db/Query.js``` 31 | - ```src/db/[Select, Insert, Delete...].lua``` 32 | - ```src/db/Params.lua``` 33 | 34 | ### Warn 35 | I am **not a professional developer** so some explanations may not be 100% correct but I have made an effort to document myself and make these schematics so that you can understand how synchronous and asynchronous functions work. If you have any suggestion to improve the explanation, or anything, it will be well received. -------------------------------------------------------------------------------- /docs/features/BackupSystem.mkd: -------------------------------------------------------------------------------- 1 | # Backup System 2 | ### Description 3 | System that allows you to backup your database in a simple way, it will create a folder in the specified path in the config.js with the value of ```Config.BackupDirPath```, then it will create a file with the current date, time and database name, the file will contain the database backup in sql format. 4 | 5 | ### Use 6 | To use this system you have to enable this options ```Config.BackupEnabled``` in the config.js, specify the mysqldump.exe path in ```Config.MysqlDumpPath``` and the backup directory path in ```Config.BackupDirPath``` respective to the FXServer.exe file. Then you have to set the days in an array format, for example: if you want to set to make the backup the 2, 5, 10 days of each month you have to set: ```Config.Days = [2, 5, 10];```. 7 | To set the hour you have to make sure to type in 24h format, for example: if you want to set the backup to 2:00 AM you have to set ```Config.Hour = "02:00";```. 8 | 9 | ### Files 10 | This is the list of the involved files in this feature to help any developer to understand how it works: 11 | - ```src/db/backup/index.js``` -------------------------------------------------------------------------------- /docs/features/BasicCache.mkd: -------------------------------------------------------------------------------- 1 | # Basic Connection Cache System 2 | ### Description 3 | This is a very basic system to keep alive multiple connections of one database and make queries to them. For example for each database the script will create the neccessary connections and will keep them alive to make queries to them. This is very useful to reduce time of creating a new connection from the pool, an example of that is for example when you make a query, the script will create a new connection from the pool if there isn't available connection, once the query has ended the connection keeps stored in an array an is waiting for other queries, you can change the max connection limit in the config.js, by default the max connection limit is 15. 4 | 5 | ### Video 6 | https://github.com/IceClusters/icmysql/assets/96537843/9306427a-f58a-4335-8134-3684a436cfa6 7 | ### Use 8 | You don't have to make anything special to use this feature as this is already implmented in the script, you just have to make the queries as you would do normally, the script will take care of the rest. 9 | 10 | ### Files 11 | This is the list of the involved files in this feature to help any developer to understand how it works: 12 | - ```src/db/Connections.js``` 13 | - ```src/db/Query.lua``` 14 | -------------------------------------------------------------------------------- /docs/features/BasicErrorSystem.mkd: -------------------------------------------------------------------------------- 1 | # Error System 2 | ### Description 3 | We made this system thinking about the person that have an error that don't understand why it happens, so we made this system to help them to understand what is happening and giving a possible solution before they ask for help. 4 | 5 | ### Use 6 | To enable this system you have to set the ```Config.ShowErrorSolution``` variable to ```true``` in the ```src/config.js``` file. If you want the description of the error to be shown in the console, you have to set the ```Config.ShowErrorDescription``` variable to ```true``` in the ```src/config.js``` file. 7 | 8 | ### How it works 9 | When a error happens in any part of the code, the ```ErrorParser()``` function will search in the ```ErrorList``` a solution for that error, before that it will send a warn to discord webhook then a log error will be saved in the log file. 10 | Here there's an example of bad user specified, and in the right you can see what the user will see in the console or in the discord webhook: 11 | ![](../assets/ErrorSystem.drawio.png) 12 | 13 | ### Files 14 | This is the list of the involved files in this feature to help any developer to understand how it works: 15 | - ```src/errors/Parser.js``` 16 | - ```src/errors/List.js``` -------------------------------------------------------------------------------- /docs/features/CacheSystem.mkd: -------------------------------------------------------------------------------- 1 | # Cache System 2 | ### Description 3 | The cache system if a efficient mode to make only necessary queries to databases, that save the new data in a cache and if the data is already in the cache it will not make a new query to the database. This feature is very useful to make a lot of queries to the database without having to worry about the performance of the server. 4 | Using this feature you will save more than the 50% of the time that you use to make queries to the database. If you have made any change to the database for example making a Update query the cache data will be removed and the next time that you make a query to the database it will be saved in the cache again. 5 | ### Video 6 | https://github.com/IceClusters/icmysql/assets/96537843/f306b9eb-1ace-4ea7-a8fc-83bafa864e6e 7 | ### Use 8 | To use this feature you don't have to do anything, by default is applied in the default query functions, if there's a reason to disable it you can do it by passing a false value after the function param. 9 | Example to disable cache: 10 | ```lua 11 | MySQL.Select(1, "SELECT name FROM players WHERE id = ?". {3}, function(result) 12 | print(result) -- Output: Daniel 13 | end, false) 14 | ``` 15 | ### Files 16 | This is the list of the involved files in this feature to help any developer to understand how it works: 17 | - ```src/db/Query.js``` 18 | -------------------------------------------------------------------------------- /docs/features/ChangeOtherSystem.mkd: -------------------------------------------------------------------------------- 1 | # Change to our functions 2 | ### Description 3 | We have made a small program to replace other systems functions to our functions, this is to make easier to a developer to change from other system to our system. 4 | 5 | ### Warn 6 | Make sure to **make a backup** of the resources folder before use this program, we are not responsible for any damage that this program can do to your files. Obviously the program isn't going to delete your server but probably some functions don't work because the program probably don't replace functions correctly. 7 | 8 | ### Use 9 | To use you have to open a command prompt in this path: ```icmysql/library``` when you're in that dir, you can type ```library-changer.exe ``` and it will change all the functions to our functions. 10 | Example:```library-changer.exe "C:\Users\DanielGP\Desktop\FivemServer\resources"```. 11 | 12 | ### How it works 13 | The program iterates all the files in the path that you have given, and it will be finding all funtions from other system and replace with our functions. 14 | 15 | ### Files 16 | This is the list of the involved files in this feature to help any developer to understand how it works: 17 | - ```library/library-changer/*``` 18 | -------------------------------------------------------------------------------- /docs/features/DiscordLogs.mkd: -------------------------------------------------------------------------------- 1 | # Discord Logs 2 | ### Description 3 | The discord logs system is a way to log all the mysql involved information in a discord channel. 4 | 5 | ### Use 6 | To use this system you have to enable this options ```Config.DiscordLogs``` in the config.js and specify the webhook url in ```Config.DiscordWebhook```, then you have some options to enable if you want to send some information. 7 | 8 | ### Files 9 | This is the list of the involved files in this feature to help any developer to understand how it works: 10 | - ```src/utils/Discord.js``` -------------------------------------------------------------------------------- /docs/features/ErrorHandling.mkd: -------------------------------------------------------------------------------- 1 | # Error Handling 2 | ### Description 3 | We've put a lot of effort into making sure that the API is as robust as possible. We've tried to make sure that the API is as informative as possible when it comes to errors. If you get an error, you should be able to figure out what went wrong and how to fix it. If you get an error that you don't understand, please let us know and we'll try to fix it. 4 | ### Files 5 | This is the list of the involved files in this feature to help any developer to understand how it works: 6 | - ```src/*``` -------------------------------------------------------------------------------- /docs/features/LogsSystem.mkd: -------------------------------------------------------------------------------- 1 | # Log System 2 | ### Description 3 | This is a simple log system that will log all the relevant information and save in a file in the ```IcMysql/Logs``` folder by default, the file will be named with the current date and time, so you can have a log file for each server restart. 4 | In the log file at the first you could see some relevent component information about the hardware so if you have any problem related to the mysql script you can provide this information to the developer to help him to understand what is going on. 5 | 6 | ### Use 7 | This is always in use, you don't have to make anything. 8 | 9 | ### Files 10 | This is the list of the involved files in this feature to help any developer to understand how it works: 11 | - ```src/utils/Logger.js``` -------------------------------------------------------------------------------- /docs/features/MongoDB.mkd: -------------------------------------------------------------------------------- 1 | # Mongo DB Support 2 | 3 | 4 | ### Description 5 | We've added the support to Mongo DB, so you can use it as a database, you can mix it with other SQL & non-SQL databases. The multi-database support is a feature that you can use in SQL & non-SQL databases. 6 | 7 | ### Use 8 | To use is the same as the other databases, you just need to enable this option in the config.js ```Config.MongoDB``` and provide the credentials in the server.cfg like this: 9 | ```cfg 10 | set mongoCredentials_1 "mongodb://localhost:27017/test" 11 | ``` 12 | As you can see is the same structure like mysql you have to specify the id of the database because we've provided multiple database support here, then the database will be connected automatically on the server start. 13 | **Function list:** 14 | ```lua 15 | Mongo.MongoInsert(dbID, options, callback); 16 | Mongo.MongoFind(dbID, options, callback); 17 | Mongo.MongoUpdate(dbID, options, callback); 18 | Mongo.MongoCount(dbID, options, callback); 19 | Mongo.MongoDelete(dbID, options, callback); 20 | ``` 21 | 22 | #### Params 23 | - ```dbID(optional)``` is the database ID that you want to use, by default the database ID is 1, you can change it in the config.js ```Config.DefaultMongoDB```, this parameter is optional so if you don't specify that it will work. 24 | - ```options(*)``` is the options that you want to make in the query, above where're going to explain you how it works and some examples, but you can find the options in the [MongoDB Documentation](https://docs.mongodb.com/manual/reference/method/js-collection/). 25 | - ```callback(optional)``` is the function that will be called when the query is finished, if the callback is not specified the query will be executed and it will be as an await function. 26 | 27 | #### Examples 28 | ##### Insert 29 | ```lua 30 | local result = exports["icmysql"]:MongoInsert(1, { 31 | collection = "users", 32 | documents = { 33 | ["name"] = "Daniel" 34 | } 35 | }); 36 | ``` 37 | ##### Find 38 | ```lua 39 | local result = exports["icmysql"]:MongoFind(1, { 40 | collection = "users", 41 | query = { 42 | ["name"] = "Daniel" 43 | } 44 | }); 45 | ``` 46 | ##### Update 47 | ```lua 48 | local result = exports["icmysql"]:MongoUpdate(1, { 49 | collection = "users", 50 | query = { 51 | ["name"] = "Daniel" 52 | }, 53 | update = { 54 | ["$set"] = { 55 | ["name"] = "Daniel2" 56 | } 57 | } 58 | }); 59 | ``` 60 | ##### Count 61 | ```lua 62 | local result = exports["icmysql"]:MongoCount(1, { 63 | collection = "users", 64 | }); 65 | ``` 66 | ##### Delete 67 | ```lua 68 | local result = exports["icmysql"]:MongoDelete(1, { 69 | collection = "users", 70 | query = { 71 | ["name"] = "Daniel2" 72 | } 73 | }); 74 | ``` 75 | 76 | ### Files 77 | This is the list of the involved files in this feature to help any developer to understand how it works: 78 | - ```src/db/mongo/*.js``` 79 | -------------------------------------------------------------------------------- /docs/features/MultipleDB.mkd: -------------------------------------------------------------------------------- 1 | # Multiple DB 2 | ### Description 3 | The multiple DB feature make it possible to use multiple databases in a single server. This feature is useful when you want to separate the data of each user or when you want to separate the data of each service. For example if you ban a user in a database you can share that database with multiple servers and the user will be banned in all of them. Other example you have a data and you want to share it with multiple servers, in this case it can be bought coins, items, or other data. 4 | ### Use 5 | To use this feature you have to specify the databases credentials in the server.cfg, for example: 6 | ```cfg 7 | set mysqlCredentials_1 "host=127.0.0.1;user=root; password=1234; database=fxserver; port=3306" 8 | set mysqlCredentials_2 "host=127.0.0.1;user=root; password=1234; database=fxserver; port=3306" 9 | ``` 10 | Once you have specified the databases when the server start in the console you will see a message that tell you if the script has connected to each DB correctly. 11 | To use you have to specify the database number in the function that you want to use as the first parameter, for example to make a select in the database 2: 12 | ```lua 13 | MySQL.Select(2, "SELECT name FROM players WHERE id = ?". {3}, function(result) 14 | print(result) -- Output: Daniel 15 | end) 16 | ``` 17 | ### Files 18 | This is the list of the involved files in this feature to help any developer to understand how it works: 19 | - ```src/db/Connections.js``` 20 | - ```src/db/Query.lua``` 21 | - ```src/db/Reader.js``` -------------------------------------------------------------------------------- /docs/features/NamedParameters.mkd: -------------------------------------------------------------------------------- 1 | # Named and placeholder parameters 2 | ### Description 3 | This make possible to use named and placeholder parameters in the queries. 4 | This approach offers several benefits, such as preventing SQL injection attacks and improving code readability. 5 | 6 | ### Use 7 | 8 | ##### Examples 9 | **Example 1**: Using "@" as a prefix for named placeholders: 10 | In this example, "@" is used as a prefix for named placeholders. The query retrieves the name of a player with ID 3. 11 | ```lua 12 | MySQL.Select("SELECT name FROM players WHERE id = @id", { 13 | ["@id"] = 3 14 | }) 15 | ``` 16 | **Example 2**: Using ":" as a prefix for named placeholders: 17 | 18 | Here, ":" is used as a prefix for named placeholders. The query also retrieves the name of a player with ID 3. 19 | ```lua 20 | MySQL.Select("SELECT name FROM players WHERE id = :id", { 21 | ["id"] = 3 22 | }) 23 | ``` 24 | **Example 3**: Using "?" as anonymous placeholders: 25 | 26 | In this example, anonymous placeholders "?" are used instead of named placeholders. The values are then provided in the same order as they appear in the query. The query retrieves the name of a player with ID 3 and the name "Daniel". 27 | ```lua 28 | MySQL.Select("SELECT name FROM players WHERE id = ? AND name = ?", { 29 | 3, 30 | "Daniel" 31 | }) 32 | ``` 33 | 34 | ### Files 35 | This is the list of the involved files in this feature to help any developer to understand how it works: 36 | - ```src/db/Queries.js``` 37 | - ```src/db/Params.js``` -------------------------------------------------------------------------------- /docs/features/ORM.mkd: -------------------------------------------------------------------------------- 1 | # ORM(Object Relational Mapping) 2 | ### Description 3 | ORM, or Object-Relational Mapping, is a technique that helps software developers work smoothly with databases. It acts as a translator between the object-oriented code we write and the tables in a relational database. Instead of dealing with complex database queries, we define our data as programming objects, and ORM takes care of converting them into database operations. This simplifies coding, improves readability, and makes database interaction more intuitive. It's like having a helper that understands both programming and databases, making our work easier and more efficient. 4 | 5 | ### How it works 6 | The ORM system that we have is bassed in [sequelize](https://www.npmjs.com/package/sequelize) package from nodejs, this package is a promise-based Node.js ORM for Postgres, MySQL, MariaDB, SQLite and Microsoft SQL Server. It features solid transaction support, relations, eager and lazy loading, read replication and more. 7 | When the script first load find all connections in the server.cfg when the script test that the connection is establish it will dump all the database structure in ```src/db/orm/models``` folder, for example if you have a database with id 3, the folder will be created in ```src/db/orm/models/3```, in that folder you don't have to touch anything, but make sure that when the database structure change the models need to be updated, you can do it by removing the folder and restarting the script, the script will create the folder again with the new structure. 8 | Once the script have writed correctly the sctructure in that folder you can use all the functions that we provide to make the queries, the script will take care of the rest. 9 | Here we're going to give to you an example of how it works. 10 | ![](../assets/ORM.drawio.png) 11 | 12 | 13 | ### Use 14 | To use this system make sure that you have enabled in config.js at ```Config.ORM``` once you have enabled this the script at the first start it will print some messages noticing you how many databases have been found and how many models have been created, once the script have created the models you can use the functions that we provide to make the queries, here we're going to give to you all the list of functions. 15 | - ```MySQL.ORM.FindAll(databaseID, table, options, callback)``` 16 | - ```MySQL.ORM.FindOne(databaseID, table, options, callback)``` 17 | - ```MySQL.ORM.FindById(databaseID, table, id, callback)``` 18 | - ```MySQL.ORM.FindAndCountAll(databaseID, table, options, callback)``` 19 | - ```MySQL.ORM.Create(databaseID, table, options, callback)``` 20 | - ```MySQL.ORM.Modify(databaseID, table, values, options, callback)``` 21 | - ```MySQL.ORM.Destroy(databaseID, table, options, callback)``` 22 | - ```MySQL.ORM.Count(databaseID, table, options, callback)``` 23 | - ```MySQL.ORM.Max(databaseID, table, field, options, callback)``` 24 | - ```MySQL.ORM.Min(databaseID, table, field, options, callback)``` 25 | - ```MySQL.ORM.Sum(databaseID, table, field, options, callback)``` 26 | - ```MySQL.ORM.Increment(databaseID, field, table, options, callback)``` 27 | - ```MySQL.ORM.Decrement(databaseID, field, table, options, callback)``` 28 | - ```MySQL.ORM.BulkCreate(databaseID, table, values, options, callback)``` 29 | In all the functions we provide callbacks if you want to use the function as synchronous you just need to remove the callback and the function will return the result, for example: 30 | ```js 31 | const result = MySQL.ORM.FindAll(1, "users", { 32 | where: { 33 | name: "Daniel" 34 | } 35 | }); 36 | ``` 37 | 38 | ### Files 39 | This is the list of the involved files in this feature to help any developer to understand how it works: 40 | - ```src/db/Query.js``` 41 | - ```src/db/orm/index.js``` 42 | - ```src/db/orm/models/index.js``` 43 | 44 | ### Warn 45 | I am **not a professional developer** so some explanations may not be 100% correct but I have made an effort to document myself and make these schematics so that you can understand how ORM system of sequelize work. If you have any suggestion to improve the explanation, or anything, it will be well received. -------------------------------------------------------------------------------- /docs/features/Redis.mkd: -------------------------------------------------------------------------------- 1 | # Redis Support 2 | 3 | 4 | ### Description 5 | We've added the support for Redis, this is a database that works with key-value, it's very fast and it's used for cache, we've added the support for this database because it's very useful for some things like save in memory data that need to be accessed very fast. 6 | 7 | ### Use 8 | To use this feature you need to enable the ```Config.Redis``` option in the ```config.js``` file, then you have to provide the redis credentials in the server.cfg file like this: 9 | ```cfg 10 | set redisCredentials "redis://user:password@127.0.0.1:6379/0" 11 | ``` 12 | When the server start if the credentials are correct the resource will connect to the database and it will be ready to use. 13 | **Functions List:** 14 | 15 | ```lua 16 | Redis.Get(key, callback); 17 | Redis.Set(key, values, callback); 18 | Redis.Del(key, callback); 19 | Redis.Exist(key, callback); 20 | Redis.Expire(key, seconds, callback); 21 | Redis.Update(key, value, callback); 22 | Redis.Flush(callback); 23 | Redis.Keys(pattern, callback); 24 | Redis.MGet(keys, callback); 25 | Redis.Close(); 26 | Redis.Open(); 27 | Redis.Reload(); 28 | ``` 29 | 30 | #### Params 31 | - ```key/s(*)``` is the key or keys that you want to use in the query, if you want to use more than one key you have to use an array of keys. 32 | - ```values(depending)``` is the value or values that you want to set in an entry, if you want to use more than one value you have to use an array of values. 33 | - ```callback(optional)``` is the function that will be called when the data or function is finished, if the callback is not specified the query will be executed and it will be as an await function. 34 | 35 | #### Examples 36 | ##### Set 37 | ```lua 38 | local result = exports["icmysql"]:RedisSet("name", "Daniel"); 39 | -- OK 40 | ``` 41 | ##### Update 42 | ```lua 43 | local result = exports["icmysql"]:RedisUpdate("name", "Daniel3"); 44 | -- OK 45 | ``` 46 | ##### Get 47 | ```lua 48 | local result = exports["icmysql"]:RedisGet("name"); 49 | -- Daniel3 50 | ``` 51 | ##### Delete 52 | ```lua 53 | local result = exports["icmysql"]:RedisDel("name"); 54 | -- OK 55 | ``` 56 | ##### Flush 57 | ```lua 58 | local result = exports["icmysql"]:RedisFlush(); 59 | -- OK 60 | ``` 61 | 62 | ### Files 63 | This is the list of the involved files in this feature to help any developer to understand how it works: 64 | - ```src/db/redis/index.js``` 65 | -------------------------------------------------------------------------------- /docs/features/ScriptUpdater.mkd: -------------------------------------------------------------------------------- 1 | # Auto Update 2 | ### Description 3 | The ICMySQL have a system to auto update if there is a new available version it will download the script and replace the necessary files, the config.js will be kipped and not will be replaced. 4 | 5 | ### Use 6 | If you want to use the system you only have to enable these options in the config.js file: ```Config.CheckForUpdates``` and ```Config.AutoUpdate```. 7 | 8 | ### Warn 9 | Make sure that the FXServer process has permission to create directories and rename directories by default you will not have any problem with this. 10 | 11 | ### Files 12 | This is the list of the involved files in this feature to help any developer to understand how it works: 13 | - ```src/utils/Updater.js``` -------------------------------------------------------------------------------- /docs/features/SupportLuaJS.mkd: -------------------------------------------------------------------------------- 1 | # Lua, JS, C# library support 2 | ### Description 3 | We made compatible the ICMySQL script compatible with Lua, JS and C# libraries, so you can use the same script in any of these languages. 4 | Obviously, you can call the script exports from any language, but this feature is to make it easier to use and read. 5 | 6 | ### Use 7 | #### Lua 8 | fxmanifest.lua 9 | ```lua 10 | server_script '@icmysql/library/MySQL.lua' 11 | ``` 12 | server.lua 13 | ```lua 14 | MySQL.Select()... 15 | ``` 16 | #### JS 17 | fxmanifest.lua 18 | ```lua 19 | server_script '@icmysql/library/MySQL.js' 20 | ``` 21 | server.js 22 | ```js 23 | MySQL.Select()... 24 | ``` 25 | #### C# 26 | In the server project you have to add a dependency to the MySQL.net.dll file, path: ```icmysql/library/MySQL.net.dll``` 27 | ```cs 28 | using System; 29 | using System.Collections.Generic; 30 | using System.Threading.Tasks; 31 | using CitizenFX.Core; 32 | using MySQL.Server; 33 | using Newtonsoft.Json; 34 | 35 | namespace test_csharp.Server 36 | { 37 | public class ServerMain : BaseScript 38 | { 39 | public MySQL.Server.MySQL mySqlInstance; 40 | public ServerMain() 41 | { 42 | mySqlInstance = new MySQL.Server.MySQL(); 43 | } 44 | 45 | public async Task PruebaSelect() 46 | { 47 | var result = await mySqlInstance.AwaitSelect(1, "SELECT name FROM players WHERE id=@id", new Dictionary { { "@id", 3 } }, true); 48 | string json = JsonConvert.SerializeObject(result); 49 | 50 | Debug.WriteLine(json); 51 | } 52 | } 53 | } 54 | ``` 55 | 56 | ### How it works 57 | We've just maded a dependency for each language, in the case of C# we've made a small lib that make the same as the other language, call the exports of the script and return the result. 58 | 59 | ### Files 60 | This is the list of the involved files in this feature to help any developer to understand how it works: 61 | - ```src/errors/Parser.js``` 62 | - ```src/errors/List.js``` -------------------------------------------------------------------------------- /exports/ghmattimysql.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ghmattimysql", 3 | "functions": { 4 | "AwaitRaw": "execute", 5 | "Raw": "executeSync", 6 | "Scalar": "scalar", 7 | "AwaitScalar": "scalarSync" 8 | } 9 | } -------------------------------------------------------------------------------- /exports/mysql_async.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mysql-async", 3 | "functions": { 4 | "Insert": "mysql_insert", 5 | "Prepare": "prepare", 6 | "Query": "mysql_fetch_all", 7 | "Raw": "mysql_execute", 8 | "Scalar": "mysql_fetch_scalar", 9 | "Single": "single", 10 | "Transaction": "mysql_transaction", 11 | "Update": "update" 12 | } 13 | } -------------------------------------------------------------------------------- /exports/oxmysql.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oxmysql", 3 | "functions": { 4 | "AwaitInsert": "insert_async", 5 | "Insert": "insert", 6 | "AwaitPrepare": "prepare_async", 7 | "Prepare": "prepare", 8 | "AwaitQuery": ["query_async", "executeSync"], 9 | "Query": "query", 10 | "AwaitRaw": "rawExecute_async", 11 | "Raw": "rawExecute", 12 | "AwaitScalar": "scalar_async", 13 | "Scalar": "scalar", 14 | "AwaitSingle": "single_async", 15 | "Single": "single", 16 | "AwaitTransaction": "transaction_async", 17 | "Transaction": "transaction", 18 | "AwaitUpdate": "update_async", 19 | "Update": "update" 20 | } 21 | } -------------------------------------------------------------------------------- /fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version 'cerulean' 2 | game 'common' 3 | 4 | author 'Ice Cluster' 5 | description 'Advanced database system for FiveM with MySQL, ORM, Mongo and Redis support.' 6 | version '3.0.0' 7 | 8 | client_scripts { 9 | 'debug.lua' 10 | } 11 | 12 | server_scripts { 13 | 'config.js', 14 | 'server/main.lua', 15 | 'dist/build.js' 16 | } 17 | 18 | ui_page 'debug/index.html' 19 | files { 20 | 'debug/*.html', 21 | 'debug/js/*.js', 22 | 'debug/styles/*.css', 23 | 'debug/assets/*.*' 24 | } 25 | 26 | convar_category 'IcMySQL' { 27 | 'IcMySQL Configuration', 28 | { 29 | { 'Connection Data', 'mysqlCredentials_1', 'CV_STRING', 'host=127.0.0.1;user=root; password=1234; database=fxserver; port=3306' }, 30 | } 31 | } 32 | 33 | provide 'ghmattimysql' 34 | provide 'mysql-async' 35 | provide 'oxmysql' -------------------------------------------------------------------------------- /library/MySQL.js: -------------------------------------------------------------------------------- 1 | const icmysql = exports.icmysql; 2 | 3 | const ORMFunctions = ["FindAll", "FindOne", "FindById", "Modify", "FindAndCountAll", "Create", "Destroy", "Count", "Max", "Min", "Sum", "Increment", "Decrement", "BulkCreate"] 4 | const QueryFunctions = ["Query", "AwaitQuery", "Select", "AwaitSelect", "Insert", "AwaitInsert", "Update", "AwaitUpdate", "Delete", "AwaitDelete", "Transaction", "AwaitTransaction", "Unique", "AwaitUnique", "Single", "AwaitSingle"] 5 | const RedisFunctions = ["RedisGet", "RedisSet", "RedisDel", "RedisExists", "RedisExpire", "RedisUpdate", "RedisFlush", "RedisKeys", "RedisMGet", "CloseRedis", "OpenRedis", "ReloadRedis"] 6 | const MongoFunctions = ["MongoInsertOne", "MongoInsertMany", "MongoFindOne", "MongoFindMany", "MongoUpdateOne", "MongoUpdateMany", "MongoCount", "MongoDeleteOne", "MongoDeleteMany", "MongoCreateIndex", "MongoStartTransactionSession", "MongoWithTransaction", "MongoBulkWrite", "MongoReplaceOne", "MongoReplaceMany", "MongoIsConnected"] 7 | 8 | MySQL = { 9 | ORM: {}, 10 | Mongo: {}, 11 | Redis: {} 12 | } 13 | 14 | for (let i = 0; i < ORMFunctions.length; i++) { 15 | MySQL.ORM[ORMFunctions[i]] = function () { 16 | return icmysql[ORMFunctions[i]](...arguments) 17 | } 18 | } 19 | for (let i = 0; i < QueryFunctions.length; i++) { 20 | MySQL[QueryFunctions[i]] = function () { 21 | return icmysql[QueryFunctions[i]](...arguments) 22 | } 23 | } 24 | for (let i = 0; i < MongoFunctions.length; i++) { 25 | MySQL.Mongo[MongoFunctions[i]] = function () { 26 | return icmysql[MongoFunctions[i]](...arguments) 27 | } 28 | } 29 | 30 | for (let i = 0; i < RedisFunctions.length; i++) { 31 | MySQL.Redis[RedisFunctions[i]] = function () { 32 | return icmysql[RedisFunctions[i]](...arguments) 33 | } 34 | } -------------------------------------------------------------------------------- /library/MySQL.lua: -------------------------------------------------------------------------------- 1 | local icmysql = exports.icmysql 2 | local MySQLData = {} 3 | local dbReady = false 4 | 5 | MySQLData.__call = function(self, ...) 6 | local newObj = {} 7 | setmetatable(newObj, self) 8 | self.__index = self 9 | return newObj 10 | end 11 | 12 | local ORMFunctions = {"FindAll", "FindOne", "FindById", "Modify", "FindAndCountAll", "Create", "Destroy", "Count", "Max", "Min", "Sum", "Increment", "Decrement", "BulkCreate"} 13 | local QueryFunctions = {"Query", "AwaitQuery", "Prepare", "AwaitPrepare", "Insert", "AwaitInsert", "Update", "AwaitUpdate", "Delete", "AwaitDelete", "Transaction", "AwaitTransaction", "Single", "AwaitSingle"} 14 | local MongoFunctions = {"MongoInsertOne", "MongoInsertMany", "MongoFindOne", "MongoFindMany", "MongoUpdateOne", "MongoUpdateMany", "MongoCount", "MongoDeleteOne", "MongoDeleteMany", "MongoCreateIndex", "MongoStartTransactionSession", "MongoWithTransaction", "MongoBulkWrite", "MongoReplaceOne", "MongoReplaceMany", "MongoIsConnected"} 15 | local RedisFunctions = {"RedisGet", "RedisSet", "RedisDel", "RedisExists", "RedisExpire", "RedisUpdate", "RedisFlush", "RedisKeys", "RedisMGet", "CloseRedis", "OpenRedis", "ReloadRedis" } 16 | 17 | MySQL = { 18 | ORM = {}, 19 | 20 | -- Replace OxMySQL functions 21 | Sync = { 22 | insert = function(...) 23 | return icmysql.AwaitInsert(nil, ...) 24 | end, 25 | fetchAll = function(...) 26 | return icmysql.AwaitQuery(nil, ...) 27 | end, 28 | fetchSingle = function(...) 29 | return icmysql.AwaitSingle(nil, ...) 30 | end, 31 | fetchScalar = function(...) 32 | return icmysql.AwaitScalar(nil, ...) 33 | end, 34 | transaction = function(...) 35 | return icmysql.AwaitTransaction(nil, ...) 36 | end, 37 | execute = function(...) 38 | return icmysql.AwaitUpdate(nil, ...) 39 | end, 40 | }, 41 | Async = { 42 | insert = function(...) 43 | return icmysql.Insert(nil, ...) 44 | end, 45 | fetchAll = function(...) 46 | return icmysql.Query(nil, ...) 47 | end, 48 | fetchSingle = function(...) 49 | return icmysql.Select(nil, ...) 50 | end, 51 | fetchScalar = function(...) 52 | return icmysql.Scalar(nil, ...) 53 | end, 54 | transaction = function(...) 55 | return icmysql.Transaction(nil, ...) 56 | end, 57 | execute = function(...) 58 | return icmysql.Update(nil, ...) 59 | end, 60 | }, 61 | insert = setmetatable({ 62 | await = function(...) 63 | return icmysql.AwaitInsert(nil, ...) 64 | end, 65 | }, { 66 | __call = function(self, ...) 67 | return icmysql.Insert(nil, ...) 68 | end 69 | }), 70 | prepare = setmetatable({ 71 | await = function(...) 72 | return icmysql.AwaitPrepare(nil, ...) 73 | end, 74 | }, { 75 | __call = function(self, ...) 76 | return icmysql.Prepare(nil, ...) 77 | end 78 | }), 79 | query = setmetatable({ 80 | await = function(...) 81 | return icmysql.AwaitQuery(nil, ...) 82 | end, 83 | }, { 84 | __call = function(self, ...) 85 | return icmysql.Query(nil, ...) 86 | end 87 | }), 88 | rawExecute = setmetatable({ 89 | await = function(...) 90 | return icmysql.AwaitRaw(nil, ...) 91 | end, 92 | }, { 93 | __call = function(self, ...) 94 | return icmysql.Raw(nil, ...) 95 | end 96 | }), 97 | scalar = setmetatable({ 98 | await = function(...) 99 | return icmysql.AwaitScalar(nil, ...) 100 | end, 101 | }, { 102 | __call = function(self, ...) 103 | return icmysql.Scalar(nil, ...) 104 | end 105 | }), 106 | single = setmetatable({ 107 | await = function(...) 108 | return icmysql.AwaitSingle(nil, ...) 109 | end, 110 | }, { 111 | __call = function(self, ...) 112 | return icmysql.Single(nil, ...) 113 | end 114 | }), 115 | transaction = setmetatable({ 116 | await = function(...) 117 | return icmysql.AwaitTransaction(nil, ...) 118 | end, 119 | }, { 120 | __call = function(self, ...) 121 | return icmysql.Transaction(nil, ...) 122 | end 123 | }), 124 | update = setmetatable({ 125 | await = function(...) 126 | return icmysql.AwaitUpdate(nil, ...) 127 | end, 128 | }, { 129 | __call = function(self, ...) 130 | return icmysql.Update(nil, ...) 131 | end 132 | }), 133 | } 134 | 135 | for _, func in ipairs(ORMFunctions) do 136 | MySQL.ORM[func] = function(...) 137 | return icmysql[func](...) 138 | end 139 | end 140 | 141 | for _, func in pairs(QueryFunctions) do 142 | MySQL[func] = function(...) 143 | return icmysql[func](nil, ...) 144 | end 145 | end 146 | MySQL.Mongo = {} 147 | for _, func in pairs(MongoFunctions) do 148 | MySQL.Mongo[func:gsub("Mongo", "")] = function(...) 149 | return icmysql[func](nil, ...) 150 | end 151 | end 152 | MySQL.Redis = {} 153 | for _, func in pairs(RedisFunctions) do 154 | MySQL.Redis[func:gsub("Redis", "")] = function(...) 155 | return icmysql[func](nil, ...) 156 | end 157 | end 158 | 159 | MySQL.ready = function(cb) 160 | CreateThread(function() 161 | while(not dbReady) do 162 | dbReady = icmysql.IsReady() 163 | Wait(10) 164 | end 165 | cb() 166 | end) 167 | end 168 | 169 | setmetatable(MySQL, MySQLData) -------------------------------------------------------------------------------- /library/csharp/MySQL.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio 15 3 | VisualStudioVersion = 15.0.26124.0 4 | MinimumVisualStudioVersion = 15.0.26124.0 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MySQL", "Server\MySQL.csproj", "{DB6F724E-7713-44D4-A229-2B5B5DB01394}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|Any CPU = Debug|Any CPU 10 | Release|Any CPU = Release|Any CPU 11 | EndGlobalSection 12 | GlobalSection(SolutionProperties) = preSolution 13 | HideSolutionNode = FALSE 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {D8C24683-9886-4B7B-A81C-BF8D8C5F78F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {D8C24683-9886-4B7B-A81C-BF8D8C5F78F3}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {D8C24683-9886-4B7B-A81C-BF8D8C5F78F3}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {D8C24683-9886-4B7B-A81C-BF8D8C5F78F3}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {DB6F724E-7713-44D4-A229-2B5B5DB01394}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {DB6F724E-7713-44D4-A229-2B5B5DB01394}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {DB6F724E-7713-44D4-A229-2B5B5DB01394}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {DB6F724E-7713-44D4-A229-2B5B5DB01394}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /library/csharp/README.md: -------------------------------------------------------------------------------- 1 | # csharp for CitizenFX 2 | 3 | This csharp project has been automatically generated by the CitizenFX resource template. 4 | 5 | To edit it, open `MySQL.sln` in Visual Studio. 6 | 7 | To build it, run `build.cmd`. To run it, run the following commands to make a symbolic link in your server data directory: 8 | 9 | ```dos 10 | cd /d [PATH TO THIS RESOURCE] 11 | mklink /d X:\cfx-server-data\resources\[local]\csharp dist 12 | ``` 13 | 14 | Afterwards, you can use `ensure MySQL` in your server.cfg or server console to start the resource. -------------------------------------------------------------------------------- /library/csharp/Server/MySQL.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using CitizenFX.Core; 4 | 5 | namespace MySQL 6 | { 7 | public class MySQL : BaseScript 8 | { 9 | public async Task FindAll(params object[] args) 10 | { 11 | return await Exports["icmysql"].FindAll(args[0], args[1], args[2], args[3], args[4], args[5]); 12 | } 13 | public async Task FindOne(params object[] args) 14 | { 15 | return await Exports["icmysql"].FindOne(args[0], args[1], args[2], args[3], args[4], args[5]); 16 | } 17 | 18 | public async Task FindById(params object[] args) 19 | { 20 | return await Exports["icmysql"].FindById(args[0], args[1], args[2], args[3], args[4], args[5]); 21 | } 22 | 23 | public async Task Modify(params object[] args) 24 | { 25 | return await Exports["icmysql"].Modify(args[0], args[1], args[2], args[3], args[4], args[5]); 26 | } 27 | 28 | public async Task FindAndCountAll(params object[] args) 29 | { 30 | return await Exports["icmysql"].FindAndCountAll(args[0], args[1], args[2], args[3], args[4], args[5]); 31 | } 32 | 33 | public async Task Create(params object[] args) 34 | { 35 | return await Exports["icmysql"].Create(args[0], args[1], args[2], args[3], args[4], args[5]); 36 | } 37 | 38 | public async Task Destroy(params object[] args) 39 | { 40 | return await Exports["icmysql"].Destroy(args[0], args[1], args[2], args[3], args[4], args[5]); 41 | } 42 | 43 | public async Task Count(params object[] args) 44 | { 45 | return await Exports["icmysql"].Count(args[0], args[1], args[2], args[3], args[4], args[5]); 46 | } 47 | 48 | public async Task Max(params object[] args) 49 | { 50 | return await Exports["icmysql"].Max(args[0], args[1], args[2], args[3], args[4], args[5]); 51 | } 52 | 53 | public async Task Min(params object[] args) 54 | { 55 | return await Exports["icmysql"].Min(args[0], args[1], args[2], args[3], args[4], args[5]); 56 | } 57 | 58 | public async Task Sum(params object[] args) 59 | { 60 | return await Exports["icmysql"].Sum(args[0], args[1], args[2], args[3], args[4], args[5]); 61 | } 62 | 63 | public async Task Increment(params object[] args) 64 | { 65 | return await Exports["icmysql"].Increment(args[0], args[1], args[2], args[3], args[4], args[5]); 66 | } 67 | 68 | public async Task Decrement(params object[] args) 69 | { 70 | return await Exports["icmysql"].Decrement(args[0], args[1], args[2], args[3], args[4], args[5]); 71 | } 72 | 73 | public async Task BulkCreate(params object[] args) 74 | { 75 | return await Exports["icmysql"].BulkCreate(args[0], args[1], args[2], args[3], args[4], args[5]); 76 | } 77 | 78 | public async Task Query(params object[] args) 79 | { 80 | return await Exports["icmysql"].Query(args[0], args[1], args[2], args[3]); 81 | } 82 | 83 | public async Task AwaitQuery(params object[] args) 84 | { 85 | return await Exports["icmysql"].AwaitQuery(args[0], args[1], args[2], args[3]); 86 | } 87 | 88 | public async Task Select(params object[] args) 89 | { 90 | return await Exports["icmysql"].Select(args[0], args[1], args[2], args[3]); 91 | } 92 | 93 | public async Task AwaitSelect(params object[] args) 94 | { 95 | return await Exports["icmysql"].AwaitSelect(args[0], args[1], args[2], args[3]); 96 | } 97 | 98 | public async Task Insert(params object[] args) 99 | { 100 | return await Exports["icmysql"].Insert(args[0], args[1], args[2], args[3]); 101 | } 102 | 103 | public async Task AwaitInsert(params object[] args) 104 | { 105 | return await Exports["icmysql"].AwaitInsert(args[0], args[1], args[2], args[3]); 106 | } 107 | 108 | public async Task Update(params object[] args) 109 | { 110 | return await Exports["icmysql"].Update(args[0], args[1], args[2], args[3]); 111 | } 112 | 113 | public async Task AwaitUpdate(params object[] args) 114 | { 115 | return await Exports["icmysql"].AwaitUpdate(args[0], args[1], args[2], args[3]); 116 | } 117 | 118 | public async Task Delete(params object[] args) 119 | { 120 | return await Exports["icmysql"].Delete(args[0], args[1], args[2], args[3]); 121 | } 122 | 123 | public async Task AwaitDelete(params object[] args) 124 | { 125 | return await Exports["icmysql"].AwaitDelete(args[0], args[1], args[2], args[3]); 126 | } 127 | 128 | public async Task Transaction(params object[] args) 129 | { 130 | return await Exports["icmysql"].Transaction(args[0], args[1], args[2], args[3]); 131 | } 132 | 133 | public async Task AwaitTransaction(params object[] args) 134 | { 135 | return await Exports["icmysql"].AwaitTransaction(args[0], args[1], args[2], args[3]); 136 | } 137 | 138 | public async Task Unique(params object[] args) 139 | { 140 | return await Exports["icmysql"].Unique(args[0], args[1], args[2], args[3]); 141 | } 142 | 143 | public async Task AwaitUnique(params object[] args) 144 | { 145 | return await Exports["icmysql"].AwaitUnique(args[0], args[1], args[2], args[3]); 146 | } 147 | 148 | public async Task Single(params object[] args) 149 | { 150 | return await Exports["icmysql"].Single(args[0], args[1], args[2], args[3]); 151 | } 152 | 153 | public async Task AwaitSingle(params object[] args) 154 | { 155 | return await Exports["icmysql"].AwaitSingle(args[0], args[1], args[2], args[3]); 156 | } 157 | 158 | public async Task MongoInsert(params object[] args) 159 | { 160 | return await Exports["icmysql"].MongoInsert(args[0], args[1], args[2], args[3]); 161 | } 162 | 163 | public async Task MongoFind(params object[] args) 164 | { 165 | return await Exports["icmysql"].MongoFind(args[0], args[1], args[2], args[3]); 166 | } 167 | 168 | public async Task MongoUpdate(params object[] args) 169 | { 170 | return await Exports["icmysql"].MongoUpdate(args[0], args[1], args[2], args[3]); 171 | } 172 | 173 | public async Task MongoCount(params object[] args) 174 | { 175 | return await Exports["icmysql"].MongoCount(args[0], args[1], args[2], args[3]); 176 | } 177 | 178 | public async Task MongoDelete(params object[] args) 179 | { 180 | return await Exports["icmysql"].MongoDelete(args[0], args[1], args[2], args[3]); 181 | } 182 | } 183 | } -------------------------------------------------------------------------------- /library/csharp/Server/MySQL.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | portable 5 | $(AssemblyName).net 6 | MySQL 7 | 8 | 9 | none 10 | 11 | 12 | none 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /library/csharp/build.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | pushd Server 3 | dotnet publish -c Release 4 | popd 5 | 6 | rmdir /s /q dist 7 | mkdir dist 8 | 9 | xcopy /y /e Server\bin\Release\netstandard2.0\publish dist\ 10 | xcopy dist\MySQL.net.dll ..\ -------------------------------------------------------------------------------- /library/library-changer.runtimeconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "runtimeOptions": { 3 | "tfm": "net6.0", 4 | "framework": { 5 | "name": "Microsoft.NETCore.App", 6 | "version": "6.0.0" 7 | }, 8 | "configProperties": { 9 | "System.Reflection.Metadata.MetadataUpdater.IsSupported": false 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /library/library-changer/Functions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace library_changer 8 | { 9 | public static class Functions 10 | { 11 | 12 | public static class Queries 13 | { 14 | public static string Query = "MySQL.Query"; 15 | public static string AwaitQuery = "MySQL.AwaitQuery"; 16 | public static string Select = "MySQL.Select"; 17 | public static string AwaitSelect = "MySQL.AwaitSelect"; 18 | public static string Insert = "MySQL.Insert"; 19 | public static string AwaitInsert = "MySQL.AwaitInsert"; 20 | public static string Update = "MySQL.Update"; 21 | public static string AwaitUpdate = "MySQL.AwaitUpdate"; 22 | public static string Delete = "MySQL.Delete"; 23 | public static string AwaitDelete = "MySQL.AwaitDelete"; 24 | public static string Transaction = "MySQL.Transaction"; 25 | public static string AwaitTransaction = "MySQL.AwaitTransaction"; 26 | public static string Unique = "MySQL.Unique"; 27 | public static string AwaitUnique = "MySQL.AwaitUnique"; 28 | public static string Single = "MySQL.Single"; 29 | public static string AwaitSingle = "MySQL.AwaitSingle"; 30 | public static string MongoInsert = "Mongo.MongoInsert"; 31 | public static string MongoFind = "Mongo.MongoFind"; 32 | public static string MongoUpdate = "Mongo.MongoUpdate"; 33 | public static string MongoCount = "Mongo.MongoCount"; 34 | public static string MongoDelete = "Mongo.MongoDelete"; 35 | public static class Exports 36 | { 37 | public static string Query = "exports['icmysql']:Query"; 38 | public static string AwaitQuery = "exports['icmysql']:AwaitQuery"; 39 | public static string Select = "exports['icmysql']:Select"; 40 | public static string AwaitSelect = "exports['icmysql']:AwaitSelect"; 41 | public static string Insert = "exports['icmysql']:Insert"; 42 | public static string AwaitInsert = "exports['icmysql']:AwaitInsert"; 43 | public static string Update = "exports['icmysql']:Update"; 44 | public static string AwaitUpdate = "exports['icmysql']:AwaitUpdate"; 45 | public static string Delete = "exports['icmysql']:Delete"; 46 | public static string AwaitDelete = "exports['icmysql']:AwaitDelete"; 47 | public static string Transaction = "exports['icmysql']:Transaction"; 48 | public static string AwaitTransaction = "exports['icmysql']:AwaitTransaction"; 49 | public static string Unique = "exports['icmysql']:Unique"; 50 | public static string AwaitUnique = "exports['icmysql']:AwaitUnique"; 51 | public static string Single = "exports['icmysql']:Single"; 52 | public static string AwaitSingle = "exports['icmysql']:AwaitSingle"; 53 | public static string MongoInsert = "exports['icmysql']:MongoInsert"; 54 | public static string MongoFind = "exports['icmysql']:MongoFind"; 55 | public static string MongoUpdate = "exports['icmysql']:MongoUpdate"; 56 | public static string MongoCount = "exports['icmysql']:MongoCount"; 57 | public static string MongoDelete = "exports['icmysql']:MongoDelete"; 58 | } 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /library/library-changer/Program.cs: -------------------------------------------------------------------------------- 1 | namespace LibChanger 2 | { 3 | class Program 4 | { 5 | public static String[] ignoreFiles = new string[] 6 | { 7 | "node_modules", 8 | ".dll", 9 | ".exe" 10 | }; 11 | 12 | public static string[] handledExtensions = new string[] 13 | { 14 | ".lua", ".js", ".cs" 15 | }; 16 | 17 | public static string[] ignoreExtensions = new string[] 18 | { 19 | "csproj" 20 | }; 21 | 22 | public static string[] ignoreResources = new string[] 23 | { 24 | "icmysql" 25 | }; 26 | 27 | static void Main(string[] args) 28 | { 29 | if (args.Length > 0) 30 | { 31 | ParseResourcesFolder(args[0]); 32 | } 33 | else 34 | { 35 | string currentPath = Environment.CurrentDirectory; 36 | bool resourceFolderFound = false; 37 | try 38 | { 39 | while (!resourceFolderFound) 40 | { 41 | string[] directories = Directory.GetDirectories(currentPath); 42 | foreach (string directory in directories) 43 | { 44 | string[] parts = directory.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); 45 | string folderName = parts[parts.Length - 1]; 46 | if (folderName == "resources") 47 | { 48 | resourceFolderFound = true; 49 | Console.WriteLine("Resource folder found at " + directory); 50 | ParseResourcesFolder(directory); 51 | break; 52 | } 53 | } 54 | if (!resourceFolderFound) 55 | { 56 | currentPath = Path.GetDirectoryName(currentPath); 57 | } 58 | } 59 | } catch(Exception err) 60 | { 61 | Console.WriteLine("Couldn't find resources folder. Please insert the path manually by passing the path as a param to the exe, example: library-changer.exe C:\\FiveM\\Servers\\MyServer\\resources"); 62 | } 63 | } 64 | } 65 | 66 | static void ParseResourcesFolder(string path) 67 | { 68 | bool isRooted = Path.IsPathRooted(path); 69 | if (!isRooted) 70 | { 71 | Console.WriteLine("You have inserted a bad path"); 72 | return; 73 | } 74 | Console.WriteLine("¿Have you maded a resources backup? (Y/N): "); 75 | char respuesta = Console.ReadKey().KeyChar; 76 | bool canContinue = false; 77 | if (respuesta == 'Y' || respuesta == 'y') 78 | { 79 | canContinue = true; 80 | Console.WriteLine("\nConverting functions..."); 81 | } 82 | else if (respuesta == 'N' || respuesta == 'n') 83 | { 84 | Console.WriteLine("\nPlease first make a backup of your resources folder and then try this tool."); 85 | } 86 | if (!canContinue) return; 87 | IEnumerable Files = Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories); 88 | foreach (string file in Files) 89 | { 90 | bool ignore = false; 91 | foreach (string keyword in ignoreFiles) 92 | { 93 | if (file.Contains(keyword)) 94 | { 95 | ignore = true; 96 | break; 97 | } 98 | } 99 | bool handled = false; 100 | foreach (string extension in handledExtensions) 101 | { 102 | if (file.Contains(extension)) { handled = true; break; } 103 | } 104 | foreach (string extension in ignoreExtensions) 105 | { 106 | if (file.Contains(extension)) 107 | { 108 | ignore = true; 109 | break; 110 | } 111 | } 112 | foreach (string resource in ignoreResources) 113 | { 114 | if (file.Contains(resource)) 115 | { 116 | ignore = true; 117 | break; 118 | } 119 | } 120 | if (handled && !ignore) 121 | { 122 | if (file.Contains("fxmanifest.lua")) 123 | HandleManifetst(file); 124 | } 125 | } 126 | } 127 | 128 | static void HandleManifetst(string path) 129 | { 130 | string content = File.ReadAllText(path); 131 | content = content.Replace("@oxmysql/lib/MySQL", "@icmysql/library/MySQL"); 132 | content = content.Replace("@mysql-async/lib/MySQL", "@icmysql/library/MySQL"); 133 | content = content.Replace("@ghmattimysql/lib/MySQL", "@icmysql/library/MySQL"); 134 | content = content.Replace("oxmysql", "icmysql"); 135 | content = content.Replace("mysql-async", "icmysql"); 136 | content = content.Replace("ghmattimysql", "icmysql"); 137 | if (content.Contains("icmysql")) 138 | { 139 | string directoryName = Path.GetDirectoryName(path); 140 | string[] parts = directoryName.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); 141 | string resourceName = parts[parts.Length - 1]; 142 | Console.WriteLine("Resource manifest of " + resourceName + " has been updated to use icmysql."); 143 | File.WriteAllText(path, content); 144 | } 145 | } 146 | } 147 | } -------------------------------------------------------------------------------- /library/library-changer/assets/newIceCore.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IceClusters/icmysql/8f11782a87db43582597802aca901f85cceda3fb/library/library-changer/assets/newIceCore.ico -------------------------------------------------------------------------------- /library/library-changer/build.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | pushd library-changer 3 | dotnet publish -c Release 4 | popd 5 | 6 | rmdir /s /q dist 7 | mkdir dist 8 | 9 | xcopy /y /e bin\Release\net6.0\publish dist\ 10 | xcopy dist\library-changer.exe ..\ 11 | xcopy dist\library-changer.dll ..\ 12 | xcopy dist\library-changer.runtimeconfig.json ..\ -------------------------------------------------------------------------------- /library/library-changer/library-changer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | library_changer 7 | enable 8 | enable 9 | x86 10 | bin\ 11 | False 12 | assets\newIceCore.ico 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /library/library-changer/library-changer.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.6.33815.320 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "library-changer", "library-changer.csproj", "{0C9AE56C-1A81-4DB5-957F-733F074DEAD2}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {0C9AE56C-1A81-4DB5-957F-733F074DEAD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {0C9AE56C-1A81-4DB5-957F-733F074DEAD2}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {0C9AE56C-1A81-4DB5-957F-733F074DEAD2}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {0C9AE56C-1A81-4DB5-957F-733F074DEAD2}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {DC1E2FAD-6B13-47CD-981F-AD734CDBACF2} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /library/vrp_icmysql/fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version "adamant" 2 | games {"gta5"} 3 | 4 | description "vRP icmysql db driver bridge" 5 | 6 | dependencies{ 7 | "vrp", 8 | "icmysql" 9 | } 10 | 11 | -- server scripts 12 | server_scripts{ 13 | "@vrp/lib/utils.lua", 14 | "init.lua" 15 | } 16 | -------------------------------------------------------------------------------- /library/vrp_icmysql/init.lua: -------------------------------------------------------------------------------- 1 | local Proxy = module("vrp", "lib/Proxy") 2 | local vRP = Proxy.getInterface("vRP") 3 | 4 | async(function() 5 | vRP.loadScript("vrp_icmysql", "init_vrp") 6 | end) 7 | -------------------------------------------------------------------------------- /library/vrp_icmysql/init_vrp.lua: -------------------------------------------------------------------------------- 1 | local DBDriver = class("icmysql", vRP.DBDriver) 2 | 3 | -- STATIC 4 | 5 | local function blob2string(blob) 6 | local data = {} 7 | for index,byte in ipairs(blob) do 8 | table.insert(data, string.char(byte)) 9 | end 10 | 11 | return table.concat(data) 12 | end 13 | 14 | -- METHODS 15 | 16 | function DBDriver:onInit(cfg) 17 | self.queries = {} 18 | self.API = exports["icmysql"] 19 | return self.API ~= nil 20 | end 21 | 22 | function DBDriver:onPrepare(name, query) 23 | self.queries[name] = query 24 | end 25 | 26 | function DBDriver:onQuery(name, params, mode) 27 | local query = self.queries[name] 28 | 29 | local r = async() 30 | 31 | if mode == "execute" then 32 | self.API:Raw(query, params, function (affectedRows) 33 | r(affectedRows or 0) 34 | end) 35 | elseif mode == "scalar" then 36 | self.API:Scalar(query, params, function (result) 37 | r(result) 38 | end) 39 | else 40 | self.API:Query(query, params, function (result) 41 | -- last insert id backwards compatibility 42 | if query:find(";.-SELECT.+LAST_INSERT_ID%(%)") then 43 | r({ { id = result[1].insertId } }, result[1].affectedRows) 44 | end 45 | 46 | for _,row in pairs(result) do 47 | for k,v in pairs(row) do 48 | if type(v) == "table" then 49 | row[k] = blob2string(v) 50 | end 51 | end 52 | end 53 | 54 | r(result, #result) 55 | end) 56 | end 57 | 58 | return r:wait() 59 | end 60 | 61 | async(function() 62 | vRP:registerDBDriver(DBDriver) 63 | end) 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ice_mysql", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "src/index.js", 6 | "private": true, 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "build": "node src/build.js" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@types/node": "^20.4.5", 15 | "axios": "^1.4.0", 16 | "child_process": "^1.0.2", 17 | "extract-zip": "^2.0.1", 18 | "fs": "^0.0.1-security", 19 | "mongodb": "^5.7.0", 20 | "mysql2": "^3.9.7", 21 | "path-browserify": "^1.0.1", 22 | "perf_hooks": "^0.0.1", 23 | "pg": "^8.11.3", 24 | "pg-hstore": "^2.3.4", 25 | "readline": "^1.3.0", 26 | "redis": "^4.6.7", 27 | "sequelize": "^6.32.1", 28 | "sequelize-auto": "^0.8.8" 29 | }, 30 | "devDependencies": { 31 | "esbuild": "^0.18.19", 32 | "sequelize-cli": "^6.6.1" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /server/main.lua: -------------------------------------------------------------------------------- 1 | Citizen.CreateThread(function() 2 | -- To check if the script is compiled or not to avoid people asking for support with a non-compiled version 3 | local exist = LoadResourceFile(GetCurrentResourceName(), "dist/metabuild.js") ~= nil 4 | if not exist then 5 | print("^0[^3WARN^0] ^1You are using a non-compiled version of ICMySQL. The script will not work, please download the latest release: ^5https://github.com/IceClusters/icmysql/releases/latest^0") 6 | return 7 | end 8 | end) -------------------------------------------------------------------------------- /src/build.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { build } = require('esbuild'); 3 | 4 | // if dist directory doesn't exist, create it 5 | if (!fs.existsSync('dist')) { 6 | fs.mkdirSync('dist'); 7 | } 8 | 9 | // The metabuild.js is for know if the file exist and know if the server is using a compiled version, we create a new file and don't find the build.js to avoid to load a full build in the server memory 10 | fs.writeFileSync('dist/metabuild.js', '//DONT REMOVE THIS FILE', 'utf-8'); 11 | build({ 12 | entryPoints: ['./src/index.js'], 13 | outfile: 'dist/build.js', 14 | bundle: true, 15 | platform: 'node', 16 | logLevel: 'info', 17 | }); -------------------------------------------------------------------------------- /src/db/Connections.js: -------------------------------------------------------------------------------- 1 | const { ParseError } = require('../errors/Parser.js'); 2 | const { performance } = require('perf_hooks'); 3 | const { createPool } = require('mysql2/promise.js') 4 | const { DirExist } = require('../utils/Files.js') 5 | const { RegisterORMConnection, GenerateModels } = require('./orm/index.js') 6 | const { Log, LogTypes } = require('../utils/Logger.js') 7 | const { PrepareBackup } = require('../db/backup/index.js') 8 | const { AddDB, SetDBORM } = require('./debug/Debug.js') 9 | const { GetKey } = require('../language/localisation.js') 10 | const { SendDiscordLog } = require('../utils/Discord.js') 11 | 12 | var connecting = true; 13 | global.pools = {}; 14 | global.poolsORM = Config.ORM ? {} : null; 15 | global.mongoConnetions = {}; 16 | 17 | async function CheckConnection(credentials) { 18 | try { 19 | credentials.multipleStatements = true; 20 | credentials.namedPlaceholders = true; 21 | const pool = createPool(credentials); 22 | const connection = await pool.getConnection(); 23 | connection.release(); 24 | return true; 25 | } catch (e) { 26 | return e.message; 27 | } 28 | } 29 | 30 | async function RegisterConnection(index, credentials) { 31 | ScheduleResourceTick(global.resourceName) 32 | const start = performance.now(); 33 | const checkConnection = await CheckConnection(credentials); 34 | if (checkConnection !== true) { 35 | ParseError(`^3Can't connect to database ${index}, ${checkConnection}.^0`); 36 | return; 37 | } 38 | credentials.multipleStatements = true; 39 | credentials.namedPlaceholders = true; 40 | credentials.connectTimeout = Config.ConnectionTimeout; 41 | credentials.trace = false; 42 | credentials.supportBigNumbers = true; 43 | credentials.typeCast = require('../utils/TypeCast.js'); 44 | credentials.flags = ["CONNECT_WITH_DB"]; 45 | 46 | const pool = createPool(credentials); 47 | pool.on('connection', function (connection) { 48 | connection.query("SET TRANSACTION ISOLATION LEVEL READ COMMITTED"); 49 | }); 50 | const end = performance.now(); 51 | global.pools[index] = pool; 52 | AddDB(index, "mysql", (end - start).toFixed(4)); 53 | Log(LogTypes.Info, `^2Database ${index} connected successfully in ${(end - start).toFixed(4)}ms`); 54 | PrepareBackup(index, credentials); 55 | connecting = false; 56 | } 57 | 58 | function ReleasePool(index, resource) { 59 | if (!global.pools[index]) return; 60 | global.pools[index].end(); 61 | delete global.pools[index]; 62 | Log(LogTypes.Warn, `^5Database ${index} was realised by ${resource}.`); 63 | } 64 | 65 | function GetPools() { 66 | return global.pools; 67 | } 68 | 69 | function GetORMPools() { 70 | return JSON.stringify(global.poolsORM); 71 | } 72 | 73 | function IsConnecting() { 74 | return connecting; 75 | } 76 | 77 | async function GetConnection(index) { 78 | const connection = await global.pools[index].getConnection(); 79 | return connection; 80 | } 81 | 82 | function ReleaseConnection(index, connection) { 83 | connection.release(); 84 | } 85 | 86 | function DisconnectDB(index) { 87 | if (!global.pools[index]) return; 88 | 89 | global.pools[index].end(); 90 | delete global.pools[index]; 91 | Log(LogTypes.Warning, `Database ${index} was disconnected.^0`); 92 | if(Config.SendDatabaseDisconnect) 93 | SendDiscordLog({ 94 | "content": null, 95 | "embeds": [ 96 | { 97 | "title": GetKey("DBDisconnectTitle"), 98 | "description": GetKey("DBDisconnectDescription"), 99 | "color": 16755968 100 | } 101 | ], 102 | "attachments": [] 103 | }); 104 | } 105 | 106 | global.exports("IsReady", function(){ 107 | return !connecting; 108 | }) 109 | 110 | RegisterCommand("disconnectdb", async function(source, args, rawCommand){ 111 | if(Number(source) != 0) return; 112 | 113 | if (!Config.MySQL) return Log(LogTypes.Warning, "^3" + GetKey("TryMySQLWithoutEnabled")+"^0"); 114 | if (!Config.AllowDBDisconnection) return Log(LogTypes.Warning, "^3" + GetKey("TryDisconnectWithoutEnabled")+"^0"); 115 | const dbId = args[0].length > 0 ? args[0] == "*" ? "*" : Number(args[0]) : Config.DefaultDB; 116 | if(!pools[dbId] && dbId != "*") return ParseError(`^1Can't find DB with ID: ${dbId} ^0`, null); 117 | 118 | if(dbId == "*") { 119 | const keys = Object.keys(pools[i])[0] 120 | for(let i = 0; i < keys; i++) { 121 | DisconnectDB(keys[i]); 122 | } 123 | return; 124 | } 125 | DisconnectDB(dbId); 126 | 127 | }, false) 128 | 129 | module.exports = { GetORMPools, RegisterConnection, IsConnecting, GetConnection, ReleaseConnection } 130 | -------------------------------------------------------------------------------- /src/db/Params.js: -------------------------------------------------------------------------------- 1 | const { escape } = require('mysql2') 2 | 3 | function ReplaceNamedParams(query, params) { 4 | for (var [key, value] of Object.entries(params)) { 5 | key = key.replace("@", ""); 6 | const regex = new RegExp(`@${key}\\b`, 'g'); 7 | const escapedValue = value === null ? 'NULL' : escape(value); 8 | query = query.replace(regex, escapedValue); 9 | } 10 | return query; 11 | } 12 | 13 | function ReplaceDotParams(query, params) { 14 | for (var [key, value] of Object.entries(params)) { 15 | key = key.replace(":", ""); 16 | const regex = new RegExp(`\\:${key}\\b`, 'g'); 17 | const escapedValue = value === null ? 'NULL' : escape(value); 18 | query = query.replace(regex, escapedValue); 19 | } 20 | return query; 21 | } 22 | 23 | function ConvertNilParams(params) { 24 | function GetMaxIndex(params) { 25 | let maxIndex = 0; 26 | for (const key in params) { 27 | if (maxIndex < key) { 28 | maxIndex = key; 29 | } 30 | } 31 | return maxIndex; 32 | } 33 | var newParams = []; 34 | for (let i = 0; i < GetMaxIndex(params); i++) { 35 | newParams[i] = params[i + 1] === null ? escape("NULL") : escape(params[i + 1]); 36 | } 37 | return newParams; 38 | } 39 | 40 | module.exports = { ReplaceNamedParams, ReplaceDotParams, ConvertNilParams } -------------------------------------------------------------------------------- /src/db/Query.js: -------------------------------------------------------------------------------- 1 | const { ParseError } = require('../errors/Parser.js'); 2 | const { ReplaceNamedParams } = require('./Params.js'); 3 | const { GetConnection, ReleaseConnection } = require('./Connections.js'); 4 | const { performance } = require('perf_hooks'); 5 | const { Log, LogTypes } = require('../utils/Logger.js'); 6 | const { ParseArgs, ParseResponse, ParseNilArgs } = require('../utils/Parser.js'); 7 | const QueryInterceptor = require('./debug/Interceptor.js').Middleware; 8 | const AddExport = require('./exports/main.js'); 9 | 10 | global.queryTypes = Object.freeze({ 11 | "Query": "Query", 12 | "Prepare": "Prepare", 13 | "Insert": "Insert", 14 | "Update": "Update", 15 | "Delete": "Delete", 16 | "Scalar": "Scalar", 17 | "Single": "Single", 18 | "Raw": "Raw" 19 | }); 20 | 21 | async function ExecuteQuery(type, dbId, query, values, callback, cache) { 22 | const start = performance.now(); 23 | let connection = null; 24 | try { 25 | if (global.interceptor) { 26 | let resourceName = GetInvokingResource(); 27 | const resultMiddleware = await QueryInterceptor({ resourceName, type, dbId, query, values, callback, cache }); 28 | if (resultMiddleware === null) return callback ? callback(null) : null; 29 | ({ resourceName, type, dbId, query, values, callback, cache } = resultMiddleware); 30 | } 31 | 32 | connection = await GetConnection(dbId); 33 | 34 | if (values) { 35 | if(typeof(values) === "string") 36 | values = [values]; 37 | } else { 38 | values = null; 39 | } 40 | values = ParseNilArgs(query, values) 41 | 42 | if (query.includes("@")) { 43 | query = ReplaceNamedParams(query, values) 44 | values = null; 45 | } 46 | const [rows] = (type == "Raw" || type == "Scalar" || type == "Single" || type == "Update" || type == "Insert" || type == "Query") 47 | ? await connection.query(query, values) : await connection.execute(query, values); 48 | const end = performance.now(); 49 | const time = (end - start).toFixed(3); 50 | 51 | if (time >= Config.SlowQueryWarn) { 52 | Log(LogTypes.Warning, `Slow query detected: ${query} - ${time}ms`) 53 | } 54 | 55 | let result = ParseResponse(type, rows); 56 | try { 57 | return callback ? callback(result) : result; 58 | } catch(err) { 59 | return result; 60 | } 61 | } catch (err) { 62 | ParseError(`Error while executing query: ${err} , query: ${query}, values: ${values}`, null, true); 63 | } finally { 64 | ReleaseConnection(dbId, connection) 65 | } 66 | } 67 | 68 | async function ExecuteTransaction(dbId, queries, params, callback) { 69 | const connection = await GetConnection(dbId); 70 | await connection.beginTransaction(); 71 | try { 72 | const queryPromises = queries.map((queryObject) => { 73 | if (queryObject["query"].includes("@")) { 74 | queryObject["query"] = ReplaceNamedParams(queryObject["query"], queryObject["values"] == null ? (params != null ? params : []) : queryObject["values"]) 75 | queryObject["values"] = null; 76 | } 77 | return connection.query(queryObject["query"], queryObject["values"] == null ? (params != null ? params : []) : queryObject["values"]); 78 | }); 79 | const results = await Promise.allSettled(queryPromises); 80 | const hasErrors = results.some((result) => { 81 | if (result.reason && result.reason.message) { 82 | ParseError(`Error while executing query: ${result.reason.message} `); 83 | } 84 | return result.status === "rejected" 85 | }); 86 | if (hasErrors) { 87 | await connection.rollback(); 88 | ParseError("Some queries failed while executing transaction."); 89 | if (typeof callback === "function") { 90 | callback(false); 91 | } 92 | return false; 93 | } 94 | await connection.commit(); 95 | connection.release(); 96 | if (typeof callback === "function") { 97 | callback(true); 98 | } 99 | return true; 100 | } catch (err) { 101 | await connection.rollback(); 102 | connection.release(); 103 | ParseError(`Error while executing transaction: ${err.message} `); 104 | if (typeof callback === "function") { 105 | callback(false); 106 | } 107 | return false; 108 | } 109 | } 110 | 111 | function AddMethod(type) { 112 | const method = async function(dbId, query, values, callback, cache) { 113 | ScheduleResourceTick(global.resourceName) 114 | const data = await ParseArgs(dbId, query, values, callback, cache); 115 | if (data == null) return null; 116 | ExecuteQuery(type, data.dbId, data.query, data.values, data.callback, data.cache); 117 | } 118 | const awaitMethod = async function(dbId, query, values, cache) { 119 | ScheduleResourceTick(global.resourceName) 120 | const data = await ParseArgs(dbId, query, values, null, cache); 121 | if (data == null) return null; 122 | // const invokingResource = GetInvokingResource(); 123 | return await ExecuteQuery(type, data.dbId, data.query, data.values, data.cache); 124 | } 125 | AddExport(type, method); 126 | AddExport(`Await${type}`, awaitMethod); 127 | } 128 | 129 | for(type in queryTypes) { 130 | AddMethod(queryTypes[type]); 131 | } 132 | 133 | module.exports = { ExecuteQuery, ExecuteTransaction } 134 | -------------------------------------------------------------------------------- /src/db/Reader.js: -------------------------------------------------------------------------------- 1 | const { ParseError } = require('../errors/Parser.js'); 2 | const { RegisterConnection } = require('../db/Connections.js') 3 | 4 | function ParseCredentials(credentials) { 5 | if (!credentials || credentials === "null") return ParseError(`^3Can't find credentials in server.cfg.^0`); 6 | 7 | const values = {}; 8 | if (!credentials.includes("mysql://")) 9 | credentials.split(';').forEach((item) => { 10 | const [key, value] = item.split('='); 11 | if (key && value) 12 | values[key.trim()] = value.trim(); 13 | }); 14 | else { 15 | const urlParts = new URL(credentials); 16 | values.user = urlParts.username; 17 | values.password = urlParts.password; 18 | values.host = urlParts.hostname; 19 | values.port = urlParts.port; 20 | values.database = urlParts.pathname.slice(1); 21 | } 22 | 23 | if (!values.host || !values.user || !values.database) { 24 | ParseError(`^1Invalid credentials provided in server.cfg.^0`); 25 | return; 26 | } 27 | 28 | const host = values.host; 29 | const user = values.user; 30 | const password = values.password || ''; 31 | const database = values.database; 32 | const port = values.port || '3306'; 33 | 34 | return { 35 | host: host, 36 | user: user, 37 | password: password, 38 | database: database, 39 | port: port 40 | }; 41 | } 42 | 43 | async function ReadCFG() { 44 | var i = 0; 45 | for (; ;) { 46 | i++; 47 | const credentials = GetConvar(`mysqlCredentials_${i}`, "null"); 48 | if (i > Config.MaxDB || credentials === "null") break; 49 | const data = ParseCredentials(credentials) 50 | if (data != null) 51 | RegisterConnection(i, ParseCredentials(credentials)) 52 | } 53 | if (i == 1) { 54 | ParseError(`^3Can't find credentials in server.cfg.^0`); 55 | } 56 | } 57 | 58 | module.exports = { ReadCFG }; -------------------------------------------------------------------------------- /src/db/Transactions.js: -------------------------------------------------------------------------------- 1 | const { ExecuteTransaction } = require('./Query.js'); 2 | 3 | function ParseValues(values) { 4 | for(let i = 0; i < values.length; i++) { 5 | if(typeof values[i] === "object") { 6 | values[i] = JSON.stringify(values[i]); 7 | } 8 | } 9 | return values; 10 | } 11 | 12 | async function AwaitTransaction(dbId, queries, params) { 13 | ScheduleResourceTick(global.resourceName) 14 | if(typeof dbId !== "number") { 15 | params = queries; 16 | queries = dbId; 17 | dbId = Config.DefaultDB; 18 | } 19 | const queriesList = []; 20 | if(Object.keys(queries[0])[0] == "query") { 21 | for(let i = 0; i < queries.length; i++) { 22 | queriesList.push({query: queries[i]["query"], values: queries[i]["values"]}) 23 | } 24 | } else if(Object.keys(queries[0])[0] == "0" && params == null) { 25 | for(let i = 0; i < queries.length; i++) { 26 | queriesList.push({query: queries[i][0], values: queries[i][1]}) 27 | } 28 | } else { 29 | for(let i = 0; i < queries.length; i++) { 30 | queriesList.push({query : queries[i]}) 31 | } 32 | } 33 | return await ExecuteTransaction(dbId, queriesList, params, null /* CALLBACK */); 34 | } 35 | 36 | 37 | function Transaction(dbId, queries, params, callback) { 38 | ScheduleResourceTick(global.resourceName) 39 | if(typeof dbId !== "number") { 40 | callback = params; 41 | params = queries; 42 | queries = dbId; 43 | dbId = Config.DefaultDB; 44 | } 45 | if(typeof params === "function") { callback = params; params = null;} 46 | const queriesList = []; 47 | if(Object.keys(queries[0])[0] == "query") { 48 | for(let i = 0; i < queries.length; i++) { 49 | queriesList.push({query: queries[i]["query"], values: queries[i]["values"]}) 50 | } 51 | } else if(Object.keys(queries[0])[0] == "0" && params == null) { 52 | for(let i = 0; i < queries.length; i++) { 53 | queriesList.push({query: queries[i][0], values: queries[i][1]}) 54 | } 55 | } else { 56 | for(let i = 0; i < queries.length; i++) { 57 | queriesList.push({query : queries[i]}) 58 | } 59 | } 60 | ExecuteTransaction(dbId, queriesList, params, callback); 61 | } 62 | 63 | global.exports("AwaitTransaction", AwaitTransaction); 64 | global.exports("Transaction", Transaction); 65 | -------------------------------------------------------------------------------- /src/db/backup/index.js: -------------------------------------------------------------------------------- 1 | const { exec } = require('child_process'); 2 | const { SendDiscordLog } = require('../../utils/Discord.js'); 3 | const { ParseError } = require('../../errors/Parser.js'); 4 | const { performance } = require('perf_hooks'); 5 | const { CreateDirRecursive, GetOldestFiles, DeleteFile } = require('../../utils/Files.js'); 6 | const { Log, LogTypes } = require('../../utils/Logger.js'); 7 | const { GetDate } = require('../../utils/Time.js') 8 | const { GetKey } = require('../../language/localisation.js') 9 | const { ReadDir, GetFileSize } = require('../../utils/Files.js') 10 | 11 | var pendingBackup = []; 12 | var backupMaded = false; 13 | var lastBackupTime = 0; 14 | 15 | function execAsync(command) { 16 | return new Promise((resolve, reject) => { 17 | exec(command, (error, stdout, stderr) => { 18 | if (error) { 19 | reject(error); 20 | } else { 21 | resolve(stdout); 22 | } 23 | }); 24 | }); 25 | } 26 | 27 | async function PrepareBackup(id, credentials) { 28 | var makeBackup = false; 29 | var day = new Date().getDate(); 30 | pendingBackup.push(credentials); 31 | for (let i = 0; i < Config.Days.length; i++) { 32 | if (Config.Days[i] == day) { 33 | makeBackup = true; 34 | break; 35 | } 36 | } 37 | if (!makeBackup) return; 38 | const BackupInterval = setInterval(async () => { 39 | var hour = new Date().getHours(); 40 | var minutes = new Date().getMinutes(); 41 | var exceptHour = Config.Hour.split(":")[0]; 42 | var exceptedMinute = Config.Hour.split(":")[1]; 43 | if (hour == parseInt(exceptHour) && minutes == parseInt(exceptedMinute)) { 44 | clearInterval(BackupInterval); 45 | if (id == 1) { 46 | setTimeout(async () => { 47 | await MakeBackup(); 48 | }, 5000); 49 | } 50 | } 51 | }, 40000); 52 | } 53 | 54 | async function MakeBackup() { 55 | if (backupMaded) return; 56 | await CreateDirRecursive(Config.BackupDirPath) 57 | backupMaded = true; 58 | GetOldestFiles(Config.BackupDirPath, Config.MaxBackups).then(filesToDelete => { 59 | if (filesToDelete.length == 0) return; 60 | for (const file of filesToDelete) { 61 | const filePath = path.join(Config.BackupDirPath, file.name); 62 | DeleteFile(filePath); 63 | } 64 | }).catch(err => { 65 | ParseError(`Error deleting files, `, [err.message]); 66 | }); 67 | var times = []; 68 | for (let i = 0; i < pendingBackup.length; i++) { 69 | const start = performance.now(); 70 | const backupName = `${pendingBackup[i].database}_${GetDate().replace("/", "-").replace("/", "-")}_${new Date().getHours()}-${new Date().getMinutes()}-${new Date().getSeconds()}`; 71 | var passwordString = ""; 72 | if (pendingBackup[i].password) { 73 | passwordString = `-p ${pendingBackup[i].password}`; 74 | } 75 | const command = `"${Config.MysqlDumpPath}" --skip-ssl -h ${pendingBackup[i].host} ${passwordString} -u ${pendingBackup[i].user} ${pendingBackup[i].database} > ${Config.BackupDirPath}/${backupName}.sql`; 76 | process.env.MYSQL_PWD = pendingBackup[i].password; 77 | Log(LogTypes.Info, "^3" + GetKey("BackupStart")) 78 | try { 79 | await execAsync(command); 80 | process.env.MYSQL_PWD = null; 81 | Log(LogTypes.Info, "^2" + GetKey("BackupSuccess")); 82 | const end = performance.now(); 83 | times.push({ path: `${Config.BackupDirPath}/${backupName}.sql`, duration: `${(end - start).toFixed(4)}ms` }); 84 | } catch (error) { 85 | process.env.MYSQL_PWD = null; 86 | ParseError(`Backup error`, [error.message]); 87 | } 88 | } 89 | var pathList = ""; 90 | for (let i = 0; i < times.length; i++) { 91 | pathList += `\n\`${times[i].path}\` in ${times[i].duration}`; 92 | } 93 | lastBackupTime = new Date().getTime(); 94 | if (Config.SendBackupInfo) 95 | SendDiscordLog({ 96 | "content": null, 97 | "embeds": [ 98 | { 99 | "title": "Backup Success", 100 | "description": "Hey there! The database backup process has been successfully completed. 🎉\n\nYou can find the backup file at the following path:\n" + pathList, 101 | "color": 8912728, 102 | "footer": { 103 | "text": "IcMySQL" 104 | }, 105 | "timestamp": new Date().toISOString() 106 | } 107 | ], 108 | "attachments": [] 109 | }); 110 | process.env.MYSQL_PWD = null; 111 | // pendingBackup = []; 112 | } 113 | 114 | async function GetBackupHistory() { 115 | const data = await ReadDir(Config.BackupDirPath); 116 | const result = []; 117 | for (let i = 0; i < data.length; i++) { 118 | const name = data[i].split("_")[0]; 119 | const date = data[i].split("_")[1].replace(".sql", ""); 120 | const size = await GetFileSize(`${Config.BackupDirPath}/${data[i]}`) / 1000; 121 | result.push({ name: name, date: date, size: size }); 122 | } 123 | return result; 124 | } 125 | 126 | RegisterCommand("backupdb", async function(source, args, rawCommand) { 127 | if(Number(source) != 0) return; 128 | 129 | if (!Config.MySQL) return Log(LogTypes.Warning, "^3" + GetKey("TryMySQLWithoutEnabled")+"^0"); 130 | if (!Config.BackupEnabled) return Log(LogTypes.Warning, "^3" + GetKey("TryBackupWithoutEnabled")+"^0"); 131 | if(lastBackupTime + 60000 > new Date().getTime()) return Log(LogTypes.Warning, "^3" + GetKey("BackupTimeRate")+"^0"); 132 | backupMaded = false; 133 | MakeBackup() 134 | 135 | }, false) 136 | 137 | module.exports = { PrepareBackup, GetBackupHistory } -------------------------------------------------------------------------------- /src/db/debug/Debug.js: -------------------------------------------------------------------------------- 1 | const { DirExist } = require('../../utils/Files.js') 2 | const { GetLogs } = require('../../utils/Logger.js'); 3 | const { GetBackupHistory } = require('../backup/index.js'); 4 | var queries = []; 5 | var cache = []; 6 | var dbs = []; 7 | 8 | function AddDB(db, type, time) { 9 | dbs.push({ 10 | id: db, 11 | orm: false, 12 | type: type, 13 | time: time 14 | }); 15 | } 16 | 17 | function SetDBORM(db, orm) { 18 | dbs.forEach((item, index) => { 19 | if (item.id == db) { 20 | return dbs[index].orm = orm; 21 | } 22 | }); 23 | } 24 | 25 | function AddQuery(resourceName, type, db, query, values, result, time) { 26 | queries.push({ 27 | resourceName: resourceName, 28 | type: type, 29 | db: db, 30 | query: query, 31 | values: values, 32 | result: result, 33 | time: time, 34 | currentTimestamp: Date.now() 35 | }); 36 | } 37 | 38 | function AddDebugCache(table, hash, values) { 39 | cache.push({ 40 | table: table, 41 | hash: hash, 42 | values: values 43 | }) 44 | } 45 | 46 | function DeleteCache(table, hash) { 47 | cache.forEach((item, index) => { 48 | if (item.table == table && item.hash == hash) { 49 | return cache.splice(index, 1); 50 | } 51 | }); 52 | } 53 | 54 | function GetResources() { 55 | var resources = []; 56 | var resourceNames = []; 57 | queries.forEach(query => { 58 | if (!resourceNames.includes(query.resourceName)) { 59 | resourceNames.push(query.resourceName); 60 | resources.push({ name: query.resourceName, description: GetResourceMetadata(query.resourceName, "description", 0) }); 61 | } 62 | }); 63 | return resources; 64 | } 65 | 66 | function GetQueries() { 67 | return queries; 68 | } 69 | 70 | function GetQueryCache() { 71 | return JSON.stringify(cache, null, 2) 72 | } 73 | 74 | function CheckPermission(src) { 75 | if (!Config.Enabled) return false; 76 | if (Config.DebugLicenses.length === 0) return false; 77 | const license = GetPlayerIdentifier(src, 0); 78 | for (const element of Config.DebugLicenses) { 79 | if (element == license) { 80 | return true; 81 | } 82 | } 83 | return false; 84 | } 85 | 86 | RegisterNetEvent("icmysql:server:getdbs"); 87 | AddEventHandler("icmysql:server:getdbs", function () { 88 | if (!CheckPermission(source)) return; 89 | TriggerClientEvent("icmysql:client:getdbs", source, JSON.stringify(dbs)) 90 | }); 91 | 92 | RegisterNetEvent("icmysql:server:getresources"); 93 | AddEventHandler("icmysql:server:getresources", function () { 94 | if (!CheckPermission(source)) return; 95 | TriggerClientEvent("icmysql:client:getResources", source, GetResources()) 96 | }); 97 | 98 | RegisterNetEvent("icmysql:server:getquerycache"); 99 | AddEventHandler("icmysql:server:getquerycache", function () { 100 | if (!CheckPermission(source)) return; 101 | TriggerClientEvent("icmysql:client:getquerycache", source, GetQueryCache()) 102 | }); 103 | 104 | RegisterNetEvent("icmysql:server:getlogs"); 105 | AddEventHandler("icmysql:server:getlogs", async function () { 106 | if (!CheckPermission(source)) return; 107 | TriggerClientEvent("icmysql:client:getlogs", source, await GetLogs()) 108 | }); 109 | 110 | RegisterNetEvent("icmysql:server:getQueries"); 111 | AddEventHandler("icmysql:server:getQueries", function () { 112 | if (!CheckPermission(source)) return; 113 | TriggerClientEvent("icmysql:client:getQueries", source, GetQueries()) 114 | }); 115 | 116 | RegisterNetEvent("icmysql:server:getbackup"); 117 | AddEventHandler("icmysql:server:getbackup", async function () { 118 | const src = source; 119 | if (!CheckPermission(source)) return; 120 | if (await DirExist(Config.BackupDirPath)) 121 | TriggerClientEvent("icmysql:client:getbackup", src, await GetBackupHistory()) 122 | }); 123 | 124 | if (Config.Enabled) { 125 | RegisterCommand("debug_ui", (source) => { 126 | if (!CheckPermission(source)) return; 127 | TriggerClientEvent("icmysql:client:openDebugUI", source); 128 | }); 129 | } 130 | 131 | 132 | module.exports = { CheckPermission, AddQuery, AddDebugCache, DeleteCache, AddDB, SetDBORM } -------------------------------------------------------------------------------- /src/db/debug/Interceptor.js: -------------------------------------------------------------------------------- 1 | const { CheckPermission } = require("./Debug.js") 2 | const { performance } = require("perf_hooks"); 3 | global.interceptor = false; 4 | let queue = []; 5 | let interceptorSuscribers = []; 6 | 7 | function SetInterceptor(value) { 8 | interceptor = typeof value === "boolean" ? value : false; 9 | } 10 | 11 | function ForwardQuery(queryId, data) { 12 | if(!interceptor || !queue[queryId]) return; 13 | data.canfollow = true; 14 | queue[queryId] = data; 15 | } 16 | 17 | function DropQuery(queryId) { 18 | if(!interceptor || !queue[queryId]) return; 19 | queue[queryId].canfollow = -1; 20 | queue[queryId].result = null; 21 | } 22 | 23 | async function Middleware(data) { 24 | const randomId = Math.floor(Math.random() * 100000); 25 | const queryId = `${performance.now()}-${randomId}`; 26 | queue[queryId] = { resourceName: data.resourceName, type: data.type, dbId: data.dbId, query: data.query, values: data.values, callback: data.callback, cache: data.cache, canfollow: false }; 27 | for(let i = 0; i < interceptorSuscribers.length; i++) { 28 | TriggerClientEvent("icmysql:client:getQueryData", interceptorSuscribers[i], queryId, data); 29 | } 30 | 31 | while(queue[queryId].canfollow === false && global.interceptor) { 32 | await new Promise((resolve) => setTimeout(resolve, 1000)); 33 | } 34 | if(queue[queryId].canfollow === -1) return null; 35 | const result = queue[queryId]; 36 | delete queue[queryId]; 37 | return result; 38 | } 39 | 40 | function SendEventToSuscribers(eventName, ...args) { 41 | for(let i = 0; i < interceptorSuscribers.length; i++) { 42 | TriggerClientEvent(eventName, interceptorSuscribers[i], ...args); 43 | } 44 | } 45 | 46 | RegisterNetEvent("icmysql:server:forwardQuery"); 47 | AddEventHandler("icmysql:server:forwardQuery", async function (data) { 48 | const src = source; 49 | if (!CheckPermission(src)) return; 50 | if(!data.queryID) return; 51 | ForwardQuery(data.queryID, data.data); 52 | }); 53 | 54 | RegisterNetEvent("icmysql:server:dropQuery"); 55 | AddEventHandler("icmysql:server:dropQuery", async function (queryID) { 56 | const src = source; 57 | if (!CheckPermission(src)) return; 58 | if(!queryID) return; 59 | DropQuery(queryID); 60 | }); 61 | 62 | RegisterNetEvent("icmysql:server:setInterceptor"); 63 | AddEventHandler("icmysql:server:setInterceptor", async function (value) { 64 | const src = source; 65 | if (!CheckPermission(src)) return; 66 | SetInterceptor(value); 67 | SendEventToSuscribers("icmysql:client:setInterceptor", value); 68 | }); 69 | 70 | RegisterNetEvent("icmysql:server:subscribeInterceptor"); 71 | AddEventHandler("icmysql:server:subscribeInterceptor", async function () { 72 | const src = source; 73 | if (!CheckPermission(src)) return; 74 | interceptorSuscribers.push(src); 75 | }); 76 | 77 | RegisterNetEvent("icmysql:server:unsubscribeInterceptor"); 78 | AddEventHandler("icmysql:server:unsubscribeInterceptor", async function () { 79 | const src = source; 80 | if (!CheckPermission(src)) return; 81 | const index = interceptorSuscribers.indexOf(src); 82 | if(index > -1) { 83 | interceptorSuscribers.splice(index, 1); 84 | } 85 | }); 86 | 87 | module.exports = { Middleware } -------------------------------------------------------------------------------- /src/db/exports/main.js: -------------------------------------------------------------------------------- 1 | const { ReadDir } = require('../../utils/Files.js'); 2 | 3 | module.exports = async function(name, func){ 4 | global.exports(name, func); 5 | if(!Config.ReplaceExports) return; 6 | const exportsPath = GetResourcePath(GetCurrentResourceName()) + "/exports"; 7 | const exportsFiles = await ReadDir(exportsPath); 8 | exportsFiles.forEach(script => { 9 | const scriptReplacement = require(exportsPath + "/" + script); 10 | for(let i = 0; i < Object.keys(scriptReplacement.functions).length; i++){ 11 | const scFunc = Object.values(scriptReplacement.functions)[i]; 12 | if(typeof scFunc == "object") { 13 | for(let k = 0; k < Object.keys(scFunc).length; k++){ 14 | const icFunc = Object.keys(scriptReplacement.functions)[i]; 15 | if(icFunc == name) { 16 | AddEventHandler(`__cfx_export_${scriptReplacement.name}_${scFunc[i]}`, async function(cb){ 17 | return cb(await func); 18 | }); 19 | break; 20 | } 21 | } 22 | }else{ 23 | const icFunc = Object.keys(scriptReplacement.functions)[i]; 24 | if(icFunc == name) { 25 | AddEventHandler(`__cfx_export_${scriptReplacement.name}_${scFunc}`, async function(cb){ 26 | return cb(await func); 27 | }); 28 | break; 29 | } 30 | } 31 | } 32 | }); 33 | } -------------------------------------------------------------------------------- /src/db/mongo/Connections.js: -------------------------------------------------------------------------------- 1 | const { MongoClient } = require('mongodb'); 2 | const { Log, LogTypes } = require('../../utils/Logger.js'); 3 | const { performance } = require('perf_hooks'); 4 | require('./Query.js') 5 | 6 | global.MongoConnections = {}; 7 | global.connectingMongo = true; 8 | 9 | function ParseMongoCrendentials(credentials, index) { 10 | if (credentials == "null") return null; 11 | if (!credentials.includes("mongodb://")) { 12 | ParseError(`^3MongoDB credentials must start with mongodb://^0`); 13 | return null; 14 | } 15 | const split = credentials.split("/"); 16 | const databaseName = split[split.length - 1]; 17 | if (databaseName == "") return ParseError(`^3MongoDB credentials must contain database name^0`); 18 | credentials = credentials.replace(`/${databaseName}`, ""); 19 | const start = performance.now(); 20 | try { 21 | const client = new MongoClient(credentials, { 22 | useUnifiedTopology: true, 23 | connectTimeoutMS: Config.ConnectiTimout, 24 | serverSelectionTimeoutMS: Config.ConnectiTimout 25 | }); 26 | 27 | client.connect() 28 | .then(() => { 29 | const end = performance.now(); 30 | const db = client.db(databaseName) 31 | global.MongoConnections[index] = db; 32 | Log(LogTypes.Info, `^2Mongo DB#${index} connected successfully in ${(end - start).toFixed(4)}ms`); 33 | global.connectingMongo = false; 34 | }) 35 | .catch(err => { 36 | ParseError(`^3Can't connect to mongodb#${index}, ${err.message}^0`); 37 | }); 38 | } catch (e) { 39 | ParseError(`^3Can't connect to mongodb#${index}, ${e.message}^0`); 40 | } 41 | } 42 | 43 | function ReadMongoCredentials() { 44 | var i = 0; 45 | for (; ;) { 46 | i++; 47 | const credentials = GetConvar(`mongoCredentials_${i}`, "null"); 48 | if (i > Config.MaxDB || credentials === "null") break; 49 | const data = ParseMongoCrendentials(credentials, i) 50 | if (data != null) 51 | RegisterConnection(i, data) 52 | } 53 | if (i == 1) { 54 | ParseError(`^3Can't find mongo credentials in server.cfg.^0`); 55 | } 56 | } 57 | 58 | setTimeout(() => { 59 | ReadMongoCredentials() 60 | }); -------------------------------------------------------------------------------- /src/db/mongo/Query.js: -------------------------------------------------------------------------------- 1 | const { ParseError } = require('../../errors/Parser.js'); 2 | const { PrepareObject } = require('./utils.js'); 3 | 4 | async function MongoMiddleware(dbId, params) { 5 | ScheduleResourceTick(global.resourceName); 6 | if (dbId == null) return ParseError('^1Invalid params to make mongoquery^0'); 7 | if (typeof dbId === "object") { 8 | params = dbId; 9 | dbId = Config.DefaultMongoDB; 10 | } 11 | while (global.connectingMongo) { 12 | await new Promise(resolve => setTimeout(resolve, 1)); 13 | } 14 | if (typeof dbId !== "number" || typeof params !== 'object') return ParseError('^1Invalid params to make mongoquery^0'); 15 | if (global.MongoConnections[dbId] == null) return ParseError('^1MongoDB with id ' + dbId + ' not found^0'); 16 | const db = global.MongoConnections[dbId]; 17 | const collection = db.collection(params.collection); 18 | if (collection == null) return ParseError('^1Collection ' + params.collection + ' not found^0'); 19 | return { collection: collection, params: params }; 20 | } 21 | 22 | async function MongoInsertOne(dbId, params, callback) { 23 | try { 24 | if (typeof callback !== "function") callback = null; 25 | const data = await MongoMiddleware(dbId, params); 26 | if (typeof data !== "object") return null; 27 | 28 | const document = PrepareObject(data.params.document); 29 | const options = PrepareObject(data.params.options); 30 | const collection = global.MongoConnections[dbId].collection(params.collection) 31 | const result = await collection.insertOne(document, options); 32 | return callback ? callback(result) : result; 33 | } catch (error) { 34 | ParseError("MongoDB find error catched:", error.message); 35 | return callback ? callback(null) : null; 36 | } 37 | } 38 | 39 | async function MongoInsertMany(dbId, params, callback) { 40 | try { 41 | if (typeof callback !== "function") callback = null; 42 | const data = await MongoMiddleware(dbId, params); 43 | if (typeof data !== "object") return null; 44 | 45 | const documents = PrepareObject(data.params.documents); 46 | const options = PrepareObject(data.params.options); 47 | const collection = global.MongoConnections[dbId].collection(params.collection) 48 | const result = await collection.insertMany(documents, options); 49 | return callback ? callback(result) : result; 50 | } catch (error) { 51 | ParseError("MongoDB find error catched:", error.message); 52 | return callback ? callback(null) : null; 53 | } 54 | } 55 | 56 | async function MongoFindOne(dbId, params, callback) { 57 | try { 58 | if (typeof callback !== "function") callback = null; 59 | const data = await MongoMiddleware(dbId, params); 60 | if (typeof data !== "object") return null; 61 | 62 | const query = PrepareObject(data.params.query); 63 | const options = PrepareObject(data.params.options); 64 | 65 | const collection = global.MongoConnections[dbId].collection(params.collection) 66 | const result = await collection.findOne(query, options); 67 | return callback ? callback(result) : result; 68 | } catch (error) { 69 | ParseError("MongoDB find error catched:", error.message); 70 | return callback ? callback(null) : null; 71 | } 72 | } 73 | 74 | async function MongoFindMany(dbId, params, callback) { 75 | try { 76 | if (typeof callback !== "function") callback = null; 77 | const data = await MongoMiddleware(dbId, params); 78 | if (typeof data !== "object") return null; 79 | 80 | const query = PrepareObject(data.params.query); 81 | const options = PrepareObject(data.params.options); 82 | 83 | const collection = global.MongoConnections[dbId].collection(params.collection) 84 | const result = await collection.find(query, options).toArray(); 85 | return callback ? callback(result) : result; 86 | } catch (error) { 87 | ParseError("MongoDB find error catched:", error.message); 88 | return callback ? callback(null) : null; 89 | } 90 | } 91 | 92 | async function MongoUpdateOne(dbId, params, callback) { 93 | try { 94 | if (typeof callback !== "function") callback = null; 95 | const data = await MongoMiddleware(dbId, params); 96 | if (typeof data !== "object") return null; 97 | 98 | const query = PrepareObject(data.params.query); 99 | const update = PrepareObject(data.params.update); 100 | const options = PrepareObject(data.params.options); 101 | 102 | const collection = global.MongoConnections[dbId].collection(params.collection) 103 | const result = await collection.updateOne(query, update, options); 104 | return callback ? callback(result) : result; 105 | } catch (error) { 106 | ParseError("MongoDB find error catched:", error.message); 107 | return callback ? callback(null) : null; 108 | } 109 | } 110 | 111 | async function MongoUpdateMany(dbId, params, callback) { 112 | try { 113 | if (typeof callback !== "function") callback = null; 114 | const data = await MongoMiddleware(dbId, params); 115 | if (typeof data !== "object") return null; 116 | 117 | const query = PrepareObject(data.params.query); 118 | const update = PrepareObject(data.params.update); 119 | const options = PrepareObject(data.params.options); 120 | 121 | const collection = global.MongoConnections[dbId].collection(params.collection) 122 | const result = await collection.updateMany(query, update, options); 123 | return callback ? callback(result) : result; 124 | } catch (error) { 125 | ParseError("MongoDB find error catched:", error.message); 126 | return callback ? callback(null) : null; 127 | } 128 | } 129 | 130 | async function MongoCount(dbId, params, callback) { 131 | try { 132 | if (typeof callback !== "function") callback = null; 133 | const data = await MongoMiddleware(dbId, params); 134 | if (typeof data !== "object") return null; 135 | 136 | const query = PrepareObject(data.params.query); 137 | const options = PrepareObject(data.params.options); 138 | 139 | const collection = global.MongoConnections[dbId].collection(params.collection) 140 | const result = await collection.countDocuments(query, options); 141 | return callback ? callback(result) : result; 142 | } catch (error) { 143 | ParseError("MongoDB find error catched:", error.message); 144 | return callback ? callback(null) : null; 145 | } 146 | } 147 | 148 | async function MongoDeleteOne(dbId, params, callback) { 149 | try { 150 | if (typeof callback !== "function") callback = null; 151 | const data = await MongoMiddleware(dbId, params); 152 | if (typeof data !== "object") return null; 153 | 154 | const query = PrepareObject(data.params.query); 155 | const options = PrepareObject(data.params.options); 156 | 157 | const collection = global.MongoConnections[dbId].collection(params.collection) 158 | const result = await collection.deleteOne(query, options); 159 | return callback ? callback(result) : result; 160 | } catch (error) { 161 | ParseError("MongoDB find error catched:", error.message); 162 | return callback ? callback(null) : null; 163 | } 164 | } 165 | 166 | async function MongoDeleteMany(dbId, params, callback) { 167 | try { 168 | if (typeof callback !== "function") callback = null; 169 | const data = await MongoMiddleware(dbId, params); 170 | if (typeof data !== "object") return null; 171 | 172 | const query = PrepareObject(data.params.query); 173 | const options = PrepareObject(data.params.options); 174 | 175 | const collection = global.MongoConnections[dbId].collection(params.collection) 176 | const result = await collection.deleteMany(query, options); 177 | return callback ? callback(result) : result; 178 | } catch (error) { 179 | ParseError("MongoDB find error catched:", error.message); 180 | return callback ? callback(null) : null; 181 | } 182 | } 183 | 184 | async function MongoCreateIndex(dbId, params, callback) { 185 | try { 186 | if (typeof callback !== "function") callback = null; 187 | if (typeof params !== "object") return null; 188 | if (typeof params.collection !== "string") return null; 189 | if (typeof params.index !== "object") return null; 190 | const collection = global.MongoConnections[dbId].collection(params.collection) 191 | const result = await collection.createIndex(params.index); 192 | return callback ? callback(result) : result; 193 | } catch (error) { 194 | ParseError("MongoDB create index error catched:", error.message); 195 | return callback ? callback(null) : null; 196 | } 197 | } 198 | 199 | async function MongoStartTransactionSession(dbId, callback) { 200 | try { 201 | if (typeof callback !== "function") callback = null; 202 | const session = global.MongoConnections[dbId].startSession(); 203 | const result = await session.withTransaction(callback); 204 | return result; 205 | } catch (error) { 206 | ParseError("MongoDB transaction error catched:", error.message); 207 | return null; 208 | } 209 | } 210 | 211 | async function MongoWithTransaction(dbId, callback) { 212 | try { 213 | if (typeof callback !== "function") callback = null; 214 | const session = global.MongoConnections[dbId].startSession(); 215 | const result = await session.withTransaction(callback); 216 | return result; 217 | } catch (error) { 218 | ParseError("MongoDB transaction error catched:", error.message); 219 | return null; 220 | } 221 | } 222 | 223 | async function MongoBulkWrite(dbId, params, callback) { 224 | try { 225 | if (typeof callback !== "function") callback = null; 226 | if (typeof params !== "object") return null; 227 | if (typeof params.collection !== "string") return null; 228 | if (typeof params.operations !== "object") return null; 229 | const collection = global.MongoConnections[dbId].collection(params.collection) 230 | const result = await collection.bulkWrite(params.operations); 231 | return callback ? callback(result) : result; 232 | } catch (error) { 233 | ParseError("MongoDB bulk write error catched:", error.message); 234 | return callback ? callback(null) : null; 235 | } 236 | } 237 | 238 | async function MongoReplaceOne(dbId, params, callback) { 239 | try { 240 | if (typeof callback !== "function") callback = null; 241 | if (typeof params !== "object") return null; 242 | if (typeof params.collection !== "string") return null; 243 | if (typeof params.filter !== "object") return null; 244 | if (typeof params.replacement !== "object") return null; 245 | const collection = global.MongoConnections[dbId].collection(params.collection) 246 | const result = await collection.replaceOne(params.filter, params.replacement); 247 | return callback ? callback(result) : result; 248 | } catch (error) { 249 | ParseError("MongoDB replace one error catched:", error.message); 250 | return callback ? callback(null) : null; 251 | } 252 | } 253 | 254 | async function MongoReplaceMany(dbId, params, callback) { 255 | try { 256 | if (typeof callback !== "function") callback = null; 257 | if (typeof params !== "object") return null; 258 | if (typeof params.collection !== "string") return null; 259 | if (typeof params.filter !== "object") return null; 260 | if (typeof params.replacement !== "object") return null; 261 | const collection = global.MongoConnections[dbId].collection(params.collection) 262 | const result = await collection.replaceMany(params.filter, params.replacement); 263 | return callback ? callback(result) : result; 264 | } catch (error) { 265 | ParseError("MongoDB replace many error catched:", error.message); 266 | return callback ? callback(null) : null; 267 | } 268 | } 269 | 270 | async function MongoIsConnected(dbId) { 271 | try { 272 | const db = global.MongoConnections[dbId]; 273 | const result = await db.command({ ping: 1 }); 274 | return result.ok === 1; 275 | } catch (error) { 276 | return false; 277 | } 278 | } 279 | 280 | global.exports('MongoInsertOne', MongoInsertOne); 281 | global.exports('MongoInsertMany', MongoInsertMany); 282 | global.exports('MongoFindOne', MongoFindOne); 283 | global.exports('MongoFindMany', MongoFindMany); 284 | global.exports('MongoUpdateOne', MongoUpdateOne); 285 | global.exports('MongoUpdateMany', MongoUpdateMany); 286 | global.exports('MongoCount', MongoCount); 287 | global.exports('MongoDeleteOne', MongoDeleteOne); 288 | global.exports('MongoDeleteMany', MongoDeleteMany); 289 | global.exports('MongoCreateIndex', MongoCreateIndex); 290 | global.exports('MongoStartTransactionSession', MongoStartTransactionSession); 291 | global.exports('MongoWithTransaction', MongoWithTransaction); 292 | global.exports('MongoBulkWrite', MongoBulkWrite); 293 | global.exports('MongoReplaceOne', MongoReplaceOne); 294 | global.exports('MongoReplaceMany', MongoReplaceMany); 295 | global.exports('MongoIsConnected', MongoIsConnected); -------------------------------------------------------------------------------- /src/db/mongo/utils.js: -------------------------------------------------------------------------------- 1 | function PrepareObject(input) { 2 | if (!input) return {}; 3 | 4 | let output = {}; 5 | 6 | if (Array.isArray(input)) { 7 | input.forEach((item, index) => { 8 | output[index] = item; 9 | }); 10 | } else if (typeof input === "object") { 11 | if (input._id) { 12 | output = { 13 | ...input, 14 | _id: mongodb.ObjectID(input._id) 15 | }; 16 | } else { 17 | output = { ...input }; 18 | } 19 | } 20 | 21 | return output; 22 | } 23 | 24 | module.exports = { PrepareObject } -------------------------------------------------------------------------------- /src/db/orm/Connection.js: -------------------------------------------------------------------------------- 1 | const { ParseError } = require('../../errors/Parser.js'); 2 | const { RegisterORMConnection } = require('./index.js'); 3 | 4 | function ParseCredentials(credentials) { 5 | const data = credentials.split(";").map(elemento => elemento.replace(/ /g, "")); 6 | const retval = {}; 7 | data.forEach(element => { 8 | retval[element.split("=")[0]] = element.split("=")[1] 9 | }); 10 | return retval 11 | } 12 | 13 | function ReadORMCredentials() { 14 | var i = 0; 15 | for (; ;) { 16 | i++; 17 | const credentials = GetConvar(`ormCredentials_${i}`, "null"); 18 | if (i > Config.MaxDB || credentials === "null") break; 19 | RegisterORMConnection(i, ParseCredentials(credentials)) 20 | } 21 | if (i == 1) { 22 | ParseError(`^3Can't find mongo credentials in server.cfg.^0`); 23 | } 24 | } 25 | 26 | setTimeout(() => { 27 | ReadORMCredentials() 28 | }); -------------------------------------------------------------------------------- /src/db/orm/models/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const Sequelize = require('sequelize') 3 | 4 | function DefineModels(sequelize, index) { 5 | const modelsPath = GetResourcePath(global.resourceName) + "/Models/" + index; 6 | fs.readdirSync(modelsPath) 7 | .filter((file) => file.endsWith('.js')) 8 | .forEach((file) => { 9 | const model = require(path.join(modelsPath, file))(sequelize, Sequelize.DataTypes); 10 | sequelize.models[model.name] = model; 11 | } 12 | ); 13 | } 14 | 15 | module.exports = { DefineModels } -------------------------------------------------------------------------------- /src/db/redis/index.js: -------------------------------------------------------------------------------- 1 | const { createClient } = require('redis'); 2 | const { Log, LogTypes } = require('../../utils/Logger.js'); 3 | const { performance } = require('perf_hooks'); 4 | const { ParseError } = require('../../errors/Parser.js'); 5 | 6 | const credentials = GetConvar("redisCredentials", "null"); 7 | var client = null; 8 | var start = 0; 9 | 10 | async function ConnectToRedis() { 11 | if (credentials === "null") return ParseError("Unknown Redis credentials. Please set the redisCredentials convar in your server.cfg file."); 12 | client = createClient({ 13 | url: credentials 14 | }); 15 | start = performance.now(); 16 | client.once('ready', () => { 17 | Log(LogTypes.Info, `^2Redis connected in ${(performance.now() - start).toFixed(4)}ms^0`); 18 | }); 19 | client.once('error', err => { 20 | Log(LogTypes.Error, `^3Can't connect to redis, ${err.message}^0`); 21 | }); 22 | await client.connect(); 23 | } 24 | 25 | setTimeout(async () => { 26 | try { 27 | await ConnectToRedis(); 28 | } catch (err) { } 29 | }); 30 | 31 | 32 | global.exports('RedisGet', async (key, callback) => { 33 | ScheduleResourceTick(global.resourceName) 34 | try { 35 | const data = await client.get(key); 36 | return callback ? callback(data) : data; 37 | } catch (err) { 38 | Log(LogTypes.Error, "Redis error while getting data: " + err.message); 39 | } 40 | }); 41 | 42 | global.exports('RedisSet', async (key, value, callback) => { 43 | ScheduleResourceTick(global.resourceName) 44 | try { 45 | const result = await client.set(key, value); 46 | return callback ? callback(result) : result; 47 | } catch (err) { 48 | Log(LogTypes.Error, "Redis error while setting data: " + err.message); 49 | } 50 | }); 51 | 52 | global.exports('RedisDel', async (key, callback) => { 53 | ScheduleResourceTick(global.resourceName) 54 | try { 55 | const result = await client.del(key); 56 | return callback ? callback(result) : result; 57 | } catch (err) { 58 | Log(LogTypes.Error, "Redis error while deleting data: " + err.message); 59 | } 60 | }); 61 | 62 | global.exports('RedisExists', async (key, callback) => { 63 | ScheduleResourceTick(global.resourceName) 64 | try { 65 | const result = await client.exists(key); 66 | return callback ? callback(result) : result; 67 | } catch (err) { 68 | Log(LogTypes.Error, "Redis error while checking if data exists: " + err.message); 69 | } 70 | }); 71 | 72 | global.exports('RedisExpire', async (key, seconds, callback) => { 73 | ScheduleResourceTick(global.resourceName) 74 | try { 75 | const result = await client.expire(key, seconds); 76 | return callback ? callback(result) : result; 77 | } catch (err) { 78 | Log(LogTypes.Error, "Redis error while setting expire time: " + err.message); 79 | } 80 | }); 81 | 82 | global.exports('RedisUpdate', async (key, value, callback) => { 83 | ScheduleResourceTick(global.resourceName) 84 | try { 85 | const result = await client.update(key, value, "XX"); 86 | return callback ? callback(result) : result; 87 | } catch (err) { 88 | Log(LogTypes.Error, "Redis error while updating data: " + err.message); 89 | } 90 | }); 91 | 92 | global.exports('RedisFlush', async (callback) => { 93 | ScheduleResourceTick(global.resourceName) 94 | try { 95 | const result = await client.flush(); 96 | return callback ? callback(result) : result; 97 | } catch (err) { 98 | Log(LogTypes.Error, "Redis error while flushing data: " + err.message); 99 | } 100 | }); 101 | 102 | global.exports('RedisKeys', async (pattern, callback) => { 103 | ScheduleResourceTick(global.resourceName) 104 | try { 105 | const data = await client.keys(pattern); 106 | return callback ? callback(data) : data; 107 | } catch (err) { 108 | Log(LogTypes.Error, "Redis error while getting keys: " + err.message); 109 | } 110 | }); 111 | 112 | global.exports('RedisMGet', async (keys, callback) => { 113 | ScheduleResourceTick(global.resourceName) 114 | try { 115 | const data = await client.mget(keys); 116 | return callback ? callback(data) : data; 117 | } catch (err) { 118 | Log(LogTypes.Error, "Redis error while getting multiple keys: " + err.message); 119 | } 120 | }); 121 | 122 | global.exports("CloseRedis", () => { 123 | client.quit(); 124 | }); 125 | 126 | global.exports("OpenRedis", () => { 127 | client.connect(); 128 | }); 129 | 130 | global.exports("ReloadRedis", () => { 131 | client.quit(); 132 | client.connect(); 133 | }); -------------------------------------------------------------------------------- /src/errors/Parser.js: -------------------------------------------------------------------------------- 1 | const { SendDiscordLog } = require('../utils/Discord.js'); 2 | const ErrorList = require('./List.js'); 3 | const { GetKey } = require('../language/localisation.js'); 4 | const { Log, LogTypes } = require('../utils/Logger.js') 5 | const { GetLanguage } = require('../language/localisation.js') 6 | 7 | var errors = []; 8 | var quickFix = []; 9 | var unknowErrors = []; 10 | 11 | function GetErrors() { 12 | return { "quickFix": quickFix, "unknowErrors": unknowErrors }; 13 | } 14 | 15 | function ParseError(error, replaceParams = [], canTrowException = false) { 16 | errors.push(error); 17 | var errorData = null; 18 | 19 | for (let i = 0; i < ErrorList.length; i++) { 20 | ErrorList[i].tags.forEach(tag => { 21 | if (error.includes(tag)) { 22 | return errorData = ErrorList[i]; 23 | } 24 | }); 25 | } 26 | 27 | if (errorData == null) { 28 | if (Config.SendUnknownErrors) 29 | SendDiscordLog({ 30 | "content": null, 31 | "embeds": [ 32 | { 33 | "title": GetKey("UnkownError"), 34 | "description": GetKey("UnkownErrorDescription") + "\n `" + error + "`", 35 | "color": 16711680 36 | } 37 | ], 38 | "attachments": [] 39 | }) 40 | unknowErrors.push(error); 41 | Log(LogTypes.Error, error + "^0"); 42 | } 43 | else { 44 | quickFix.push(errorData); 45 | var errorSolution = errorData.solution[GetLanguage()]; 46 | var errorDescription = errorData.description[GetLanguage()]; 47 | const keyword = "{icmysql_error_replace}"; 48 | replaceParams.forEach(param => { 49 | errorSolution = errorSolution.replace(keyword, param); 50 | errorDescription = errorDescription.replace(keyword, param); 51 | }); 52 | Log(LogTypes.Error, error + "^0", errorDescription, errorSolution); 53 | if (Config.SendCommonErrors) 54 | SendDiscordLog({ 55 | "content": null, 56 | "embeds": [ 57 | { 58 | "title": GetKey("ErrorDetected"), 59 | "description": GetKey("ErrorDetectedDescription") + "\n `" + error + "`\n\n" + GetKey("Description") + ": " + errorDescription + "\n\n" + GetKey("Possible_Solutions") + ": " + errorSolution + "\n\n", 60 | "color": 16755968 61 | } 62 | ], 63 | "attachments": [] 64 | }) 65 | 66 | if (Config.ShowErrorDescription) 67 | Log(LogTypes.Info, `^5${GetKey("Description")}: ${errorDescription}^0`); 68 | if (Config.ShowErrorSolution) 69 | Log(LogTypes.Solution, `^3${GetKey("Possible_Solutions")}: ${errorSolution}^0`); 70 | } 71 | if(canTrowException) 72 | throw new Error(error); 73 | } 74 | 75 | module.exports = { ParseError, GetErrors } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const { ParseError } = require('./errors/Parser.js'); 2 | const { CheckVersion } = require('./utils/Updater.js'); 3 | const { ParseLocalisations } = require('./language/localisation.js'); 4 | const { ReadCFG } = require('./db/Reader.js'); 5 | const { InitLogs } = require('./utils/Logger.js'); 6 | require('pg') 7 | 8 | require('./db/Transactions.js'); 9 | if (Config.ORM) require('./db/orm/Connection.js'); 10 | if (Config.MongoDB) require('./db/mongo/Connections.js'); 11 | if (Config.Redis) require('./db/redis/index.js'); 12 | global.resourceName = GetCurrentResourceName(); 13 | 14 | setTimeout(async () => { 15 | try { 16 | if (Config.CheckForUpdates) { 17 | await CheckVersion(); 18 | } 19 | // InitData() // This is the JSON system, disabled until fix the high cpu usage 20 | ParseLocalisations(); 21 | if(Config.MySQL) 22 | ReadCFG(); 23 | if(!Config.MySQL && !Config.MongoDB && !Config.Redis && !Config.ORM) { 24 | ParseError(`^3You need to enable at least one database system in config.js.^0`) 25 | } 26 | InitLogs(); 27 | } catch (error) { 28 | ParseError(error.message) 29 | } 30 | }); -------------------------------------------------------------------------------- /src/json/Load.js: -------------------------------------------------------------------------------- 1 | const { DirExist } = require('../utils/Files.js') 2 | 3 | var loadedData = {}; 4 | var changedFiles = []; 5 | var loadingData = true; 6 | 7 | async function InitData() { 8 | if (!await DirExist(Config.SaveDirPath)) { 9 | await CreateIfNotExist(Config.SaveDirPath + `/${global.resourceName}.json`, "{}"); 10 | } 11 | 12 | ReadDir(Config.SaveDirPath).then(async function (files) { 13 | let promises = []; 14 | for (let i = 0; i < files.length; i++) { 15 | if (!files[i].endsWith(".json")) continue; 16 | let fileName = files[i].replace(".json", ""); 17 | promises.push(ReadFile(Config.SaveDirPath + "/" + files[i]).then(function (data) { 18 | loadedData[fileName] = JSON.parse(data); 19 | })); 20 | } 21 | await Promise.all(promises); 22 | loadingData = false; 23 | Log(LogTypes.Debug, `Loaded data from: ${files.map(file => file.replace(".json", "")).join(", ")}`); 24 | }); 25 | } 26 | 27 | // module.exports = { InitData } -------------------------------------------------------------------------------- /src/json/Save.js: -------------------------------------------------------------------------------- 1 | const { SendDiscordLog } = require('../utils/Discord.js'); 2 | 3 | async function SaveData() { 4 | if (changedFiles.length == 0) return; 5 | for (let i = 0; i < changedFiles.length; i++) { 6 | if (await FileExist(Config.SaveDirPath + "/" + changedFiles[i] + ".json")) { 7 | await ModifyData(Config.SaveDirPath + "/" + changedFiles[i] + ".json", JSON.stringify(loadedData[changedFiles[i]])); 8 | } 9 | else { 10 | await CreateIfNotExist(Config.SaveDirPath + "/" + changedFiles[i] + ".json", JSON.stringify(loadedData[changedFiles[i]])); 11 | } 12 | } 13 | Log(LogTypes.Debug, `Saved data of ${changedFiles.map(file => file).join(", ")}`); 14 | if (Config.SendSaveData) 15 | SendDiscordLog({ 16 | "content": null, 17 | "embeds": [ 18 | { 19 | "title": "JSON Saved", 20 | "description": "The JSON files have been saved successfully.\n `" + changedFiles.map(file => file).join("\n") + "`", 21 | "color": 16773720 22 | } 23 | ], 24 | "attachments": [] 25 | }) 26 | changedFiles = []; 27 | 28 | } 29 | 30 | global.exports('SaveData', SaveData); -------------------------------------------------------------------------------- /src/json/index.js: -------------------------------------------------------------------------------- 1 | const { ParseError } = require('../errors/Parser.js'); 2 | 3 | async function GetDataInteral(resourceName, type, key, defaultValue) { 4 | while (loadingData) { 5 | await new Promise(resolve => setTimeout(resolve, 1)) 6 | }; 7 | if (resourceName == null || key == null || defaultValue == null || type == null) return ParseError("Invalid parameters for LoadData function"); 8 | if (type !== typeof defaultValue) return ParseError("Invalid variable type for LoadData function", [typeof defaultValue, type]); 9 | if (loadedData[resourceName] == null || loadedData[resourceName][type] == null || loadedData[resourceName][type][key] == null) return defaultValue; 10 | return loadedData[resourceName][type][key]; 11 | } 12 | 13 | function SetDataInteral(resourceName, type, key, value) { 14 | 15 | if (resourceName == null || key == null || type == null || value == null) return ParseError("Invalid parameters for SetData function"); 16 | if (!loadedData[resourceName]) { 17 | loadedData[resourceName] = {}; 18 | } 19 | if (!loadedData[resourceName][type]) { 20 | loadedData[resourceName][type] = {}; 21 | } 22 | loadedData[resourceName][type][key] = value; 23 | if (!changedFiles.includes(resourceName)) 24 | changedFiles.push(resourceName); 25 | } 26 | 27 | function RemoveDataInteral(resourceName, type, key) { 28 | if (resourceName == null || key == null || type == null) return ParseError("Invalid parameters for RemoveData function"); 29 | if (loadedData[resourceName] == null || loadedData[resourceName][type] == null || loadedData[resourceName][type][key] == null) return; 30 | delete loadedData[resourceName][type][key]; 31 | if (!changedFiles.includes(resourceName)) 32 | changedFiles.push(resourceName); 33 | } 34 | 35 | async function GetData(resourceName, type, key, defaultValue) { 36 | if (resourceName == null || key == null || type == null) return ParseError("Invalid parameters for LoadData function"); 37 | if (defaultValue == undefined) { 38 | defaultValue = key; 39 | key = type; 40 | type = resourceName; 41 | resourceName = GetInvokingResource(); 42 | } 43 | if (typeof resourceName != "string" || typeof key != "string" || typeof type != "string") return ParseError("Invalid parameters for LoadData function"); 44 | return await GetDataInteral(resourceName, type, key, defaultValue); 45 | } 46 | 47 | function SetData(resourceName, type, key, value) { 48 | if (resourceName == null || key == null || type == null) return ParseError("Invalid parameters for LoadData function"); 49 | if (typeof resourceName != "string" || typeof key != "string" || typeof type != "string") return ParseError("Invalid parameters for LoadData function"); 50 | if (value == null) { 51 | value = key; 52 | key = type; 53 | type = resourceName; 54 | resourceName = GetInvokingResource(); 55 | } 56 | return SetDataInteral(resourceName, type, key, value); 57 | } 58 | 59 | function RemoveData(resourceName, type, key) { 60 | if (resourceName == null || key == null || type == null) return ParseError("Invalid parameters for LoadData function"); 61 | if (typeof resourceName != "string" || typeof key != "string" || typeof type != "string") return ParseError("Invalid parameters for LoadData function"); 62 | if (key == null) { 63 | key = type; 64 | type = resourceName; 65 | resourceName = GetInvokingResource(); 66 | } 67 | return RemoveDataInteral(resourceName, type, key); 68 | } 69 | 70 | if (typeof Config.SaveInterval !== "number" || Config.SaveInterval < 5000) { 71 | Config.SaveInterval = 30000; 72 | ParseError("Invalid SaveInterval value") 73 | } 74 | 75 | // setInterval(async () => { 76 | // await SaveData(); 77 | // }, Config.SaveInterval); 78 | 79 | // global.exports('GetData', GetData); 80 | // global.exports('SetData', SetData); 81 | // global.exports('RemoveData', RemoveData); -------------------------------------------------------------------------------- /src/language/List.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "Possible_Solutions": { 3 | 'es': "Posible solucion", 4 | 'en': "Possible solution", 5 | 'fr': "Solution possible", 6 | 'pt-BR': "Solucao possivel" 7 | }, 8 | "Description": { 9 | 'es': "Descripcion", 10 | 'en': "Description", 11 | 'fr': "Description", 12 | 'pt-BR': "Descricao" 13 | }, 14 | "MappingDB": { 15 | 'es': "Mapeando base de datos...", 16 | 'en': "Mapping database...", 17 | 'fr': "Cartographie de la base de donnees...", 18 | 'pt-BR': "Mapeando banco de dados..." 19 | }, 20 | "MappedSuccess": { 21 | 'es': "Mapeado con exito en", 22 | 'en': "Mapped successfully in", 23 | 'fr': "Mappe avec succes en", 24 | 'pt-BR': "Mapeado com sucesso em" 25 | }, 26 | "MappedError": { 27 | 'es': "Error al mapear", 28 | 'en': "Error mapping", 29 | 'fr': "Erreur de cartographie", 30 | 'pt-BR': "Erro ao mapear" 31 | }, 32 | "BackupSuccess": { 33 | 'es': "Backup completado exitosamente!", 34 | 'en': "Backup completed successfully!", 35 | 'fr': "Sauvegarde terminee avec succes!", 36 | 'pt-BR': "Backup concluido com sucesso!" 37 | }, 38 | "BackupStart": { 39 | 'es': "Iniciando backup...", 40 | 'en': "Starting backup...", 41 | 'fr': "Demarrage de la sauvegarde...", 42 | 'pt-BR': "Iniciando backup..." 43 | }, 44 | "ErrorDetected": { 45 | 'es': "Error detectado", 46 | 'en': "Error detected", 47 | 'fr': "Erreur detectee", 48 | 'pt-BR': "Erro detectado" 49 | }, 50 | "ErrorDetectedDescription": { 51 | 'es': "Se ha detectado un error.", 52 | 'en': "An error has been detected.", 53 | 'fr': "Une erreur a ete detectee.", 54 | 'pt-BR': "Um erro foi detectado." 55 | }, 56 | "UnkownError": { 57 | 'es': "Error desconocido", 58 | 'en': "Unknown error", 59 | 'fr': "Erreur inconnue", 60 | 'pt-BR': "Erro desconhecido" 61 | }, 62 | "UnkownErrorDescription": { 63 | 'es': "Se ha detectado un error desconocido.", 64 | 'en': "An unknown error has been detected.", 65 | 'fr': "Une erreur inconnue a ete detectee.", 66 | 'pt-BR': "Um erro desconhecido foi detectado." 67 | }, 68 | "TryOrmWithoutEnabled": { 69 | 'es': "Estas intentando usar el ORM sin tenerlo habilitado.", 70 | 'en': "You are trying to use the ORM without having it enabled.", 71 | 'fr': "Vous essayez d'utiliser l'ORM sans l'avoir active.", 72 | 'pt-BR': "Voce esta tentando usar o ORM sem te-lo habilitado." 73 | }, 74 | "MappingProgrammed": { 75 | 'es': "Se ha programado el mapeo de la base de datos en el proximo reinicio de servidor.", 76 | 'en': "The mapping of the database has been programmed in the next server restart.", 77 | 'fr': "La cartographie de la base de donnees a ete programmee lors du prochain redemarrage du serveur.", 78 | 'pt-BR': "O mapeamento do banco de dados foi programado para o proximo reinicio do servidor." 79 | }, 80 | "AlreadyMapedProgrammed": { 81 | 'es': "Ya se ha programado el mapeo de la base de datos en el proximo reinicio de servidor.", 82 | 'en': "The mapping of the database has already been programmed in the next server restart.", 83 | 'fr': "La cartographie de la base de donnees a deja ete programme lors du prochain redemarrage du serveur.", 84 | 'pt-BR': "O mapeamento do banco de dados ja foi programado para o proximo reinicio do servidor." 85 | }, 86 | "TryMySQLWithoutEnabled": { 87 | 'es': "Estas intentando usar MySQL sin tenerlo habilitado.", 88 | 'en': "You are trying to use MySQL without having it enabled.", 89 | 'fr': "Vous essayez d'utiliser MySQL sans l'avoir active.", 90 | 'pt-BR': "Voce esta tentando usar o MySQL sem te-lo habilitado." 91 | }, 92 | "TryBackupWithoutEnabled": { 93 | 'es': "Estas intentando usar el backup sin tenerlo habilitado.", 94 | 'en': "You are trying to use the backup without having it enabled.", 95 | 'fr': "Vous essayez d'utiliser la sauvegarde sans l'avoir active.", 96 | 'pt-BR': "Voce esta tentando usar o backup sem te-lo habilitado." 97 | }, 98 | "BackupTimeRate": { 99 | 'es': "El tiempo de backup no puede ser menor a 60 segundos.", 100 | 'en': "The backup time cannot be less than 60 seconds.", 101 | 'fr': "Le temps de sauvegarde ne peut pas etre inferieur a 60 secondes.", 102 | 'pt-BR': "O tempo de backup nao pode ser inferior a 60 segundos." 103 | }, 104 | "TryDisconnectWithoutEnabled": { 105 | 'es': "Estas intentando usar la desconexión sin tenerla habilitada.", 106 | 'en': "You are trying to use the disconnection without having it enabled.", 107 | 'fr': "Vous essayez d'utiliser la deconnexion sans l'avoir active.", 108 | 'pt-BR': "Voce esta tentando usar a desconexao sem te-la habilitada." 109 | }, 110 | "DBDisconnectTitle": { 111 | 'es': "Desconexión de base de datos", 112 | 'en': "Database disconnection", 113 | 'fr': "Deconnexion de la base de donnees", 114 | 'pt-BR': "Desconexao de banco de dados" 115 | }, 116 | "DBDisconnectDescription": { 117 | 'es': "Se han desconectado una o más bases de datos.", 118 | 'en': "One or more databases have been disconnected.", 119 | 'fr': "Une ou plusieurs bases de donnees ont ete deconnectees.", 120 | 'pt-BR': "Uma ou mais bases de dados foram desconectadas." 121 | }, 122 | } -------------------------------------------------------------------------------- /src/language/localisation.js: -------------------------------------------------------------------------------- 1 | const { ParseError } = require('../errors/Parser.js'); 2 | const Translations = require('./List.js'); 3 | 4 | const supportedLanguages = ["en", "es", "fr", "pt-BR"]; 5 | const defaultLanguage = "en"; 6 | 7 | function ParseLocalisations() { 8 | if (!LanguageSpecified()) { 9 | return ParseError("language_undefined"); 10 | } 11 | 12 | if (!LanguageSupported()) { 13 | return ParseError("language_unsupported", [supportedLanguages.join(", ")]); 14 | } 15 | } 16 | 17 | function LanguageSpecified() { 18 | return Config.Language && Config.Language != ""; 19 | } 20 | 21 | function LanguageSupported() { 22 | return typeof Config.Language == "string" && supportedLanguages.includes(Config.Language); 23 | } 24 | 25 | function GetLanguage(replaceUnknown = true) { 26 | return (!LanguageSpecified() || !LanguageSupported()) && replaceUnknown ? defaultLanguage : Config.Language; 27 | } 28 | 29 | function GetKey(index) { 30 | return Translations[index][GetLanguage()] || "Can't find localisation: " + index + ", with language id: " + GetLanguage(false); 31 | } 32 | 33 | module.exports = { ParseLocalisations, GetKey, GetLanguage } -------------------------------------------------------------------------------- /src/utils/Discord.js: -------------------------------------------------------------------------------- 1 | const { post } = require('axios'); 2 | const { ParseError } = require('../errors/Parser.js') 3 | let warned = false; 4 | module.exports = { 5 | SendDiscordLog: async function (content) { 6 | if (!Config.DiscordLogs) return; 7 | if(warned) return; 8 | if (Config.DiscordWebhook === "") { 9 | warned = true; 10 | return ParseError(`You have enabled the discord logs but you have not set the webhook, please set the webhook in the config.js file`); 11 | } 12 | try { 13 | await post(Config.DiscordWebhook, content); 14 | } catch (error) { 15 | ParseError(`Error sending message to discord webhook`, [err.message]); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/utils/Files.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const { ParseError } = require('../errors/Parser.js'); 4 | 5 | async function FileExist(path) { 6 | return await fs.existsSync(path); 7 | } 8 | 9 | async function CreateFile(path, data) { 10 | await fs.writeFileSync(path, data); 11 | } 12 | 13 | async function ReadFile(path) { 14 | return (await fs.promises.readFile(path)).toString(); 15 | } 16 | 17 | async function ReadDir(path) { 18 | return (await fs.readdirSync(path)); 19 | } 20 | 21 | async function DeleteFile(path) { 22 | await fs.unlinkSync(path); 23 | } 24 | 25 | async function CreateDir(path) { 26 | await fs.mkdirSync(path); 27 | } 28 | 29 | async function DirExist(path) { 30 | return await fs.existsSync(path); 31 | } 32 | 33 | async function AppendData(path, data) { 34 | if (!(await FileExist(path))) return; 35 | await fs.appendFileSync(path, data); 36 | } 37 | 38 | async function ModifyData(path, data) { 39 | if (!(await FileExist(path))) return; 40 | await fs.promises.writeFile(path, data); 41 | } 42 | 43 | async function CreateDirRecursive(path) { 44 | await fs.mkdirSync(path, { recursive: true }); 45 | } 46 | 47 | async function CreateIfNotExist(pathFile, data) { 48 | if (!(await FileExist(path))) { 49 | try { 50 | const directoryPath = path.dirname(pathFile); 51 | await fs.mkdirSync(directoryPath, { recursive: true }); 52 | await fs.writeFileSync(pathFile, data); 53 | 54 | } catch (err) { 55 | ParseError(`Can't create file at "${pathFile}": ${err.message}`); 56 | } 57 | } 58 | } 59 | 60 | async function GetOldestFiles(dirPath, numFilesToKeep) { 61 | try { 62 | if (!(await DirExist(dirPath))) return []; 63 | const files = await ReadDir(dirPath); 64 | const fileDetails = []; 65 | 66 | for (const file of files) { 67 | const filePath = path.join(dirPath, file); 68 | const stats = await fs.promises.stat(filePath); 69 | fileDetails.push({ name: file, date: stats.birthtime }); 70 | } 71 | 72 | const sortedFiles = fileDetails.sort((a, b) => a.date - b.date); 73 | const filesToDelete = sortedFiles.slice(0, Math.max(0, sortedFiles.length - numFilesToKeep)); 74 | return filesToDelete; 75 | } catch (err) { 76 | throw err; 77 | } 78 | } 79 | 80 | function GetFileCreationDate(filePath) { 81 | try { 82 | const stats = fs.statSync(filePath); 83 | return stats.birthtime; 84 | } catch (err) { 85 | throw err; 86 | } 87 | } 88 | 89 | function GetFileSize(filePath) { 90 | try { 91 | const stats = fs.statSync(filePath); 92 | return stats.size; 93 | } catch (err) { 94 | throw err; 95 | } 96 | } 97 | 98 | function DeleteDir(dirPath) { 99 | try { 100 | if (!(fs.existsSync(dirPath))) return; 101 | fs.readdirSync(dirPath).forEach((file, index) => { 102 | const curPath = path.join(dirPath, file); 103 | if (fs.lstatSync(curPath).isDirectory()) { 104 | DeleteDir(curPath); 105 | } else { 106 | fs.unlinkSync(curPath); 107 | } 108 | }); 109 | fs.rmdirSync(dirPath); 110 | } catch (err) { 111 | throw err; 112 | } 113 | } 114 | 115 | module.exports = { FileExist, CreateFile, ReadFile, ReadDir, DeleteFile, CreateDir, DirExist, AppendData, ModifyData, CreateDirRecursive, CreateIfNotExist, GetOldestFiles, GetFileCreationDate, GetFileSize, DeleteDir } -------------------------------------------------------------------------------- /src/utils/Logger.js: -------------------------------------------------------------------------------- 1 | const os = require('os'); 2 | const { ParseError } = require('../errors/Parser.js') 3 | const { GetTimestamp, GetTime } = require('../utils/Time.js'); 4 | const { GetOldestFiles, CreateIfNotExist, AppendData, DeleteFile } = require('../utils/Files.js') 5 | const path = require('path'); 6 | 7 | const LogTypes = Object.freeze({ "Info": "INFO", "Warning": "WARNING", "Error": "ERROR", "Debug": "DEBUG", "Solution": "SOLUTION" }) 8 | var logReady = false; 9 | var logPath = `${Config.LogFilesPath}_${GetTimestamp()}.log`; 10 | var logs = []; 11 | 12 | function GetComponents() { 13 | const architecture = os.arch(); 14 | const totalMemory = os.totalmem(); 15 | const freeMemory = os.freemem(); 16 | const cpus = os.cpus(); 17 | 18 | return `Hardware Information:\n---------------------\nArchitecture: ${architecture}\nTotal Memory: ${totalMemory} bytes (${(totalMemory / (1024 ** 3)).toFixed(2)} GB)\nFree Memory: ${freeMemory} bytes (${(freeMemory / (1024 ** 3)).toFixed(2)} GB)\n\nCPU Information:\n---------------\nNumber of Cores: ${cpus.length}\nModel: ${cpus[0].model}\nSpeed: ${cpus[0].speed} MHz\n\n\n`; 19 | } 20 | 21 | async function InitLogs() { 22 | GetOldestFiles(path.dirname(logPath), Config.MaxLogFiles).then(filesToDelete => { 23 | if (filesToDelete.length == 0) return; 24 | for (const file of filesToDelete) { 25 | const filePath = path.join(path.dirname(logPath), file.name); 26 | DeleteFile(filePath); 27 | } 28 | }).catch(err => { 29 | ParseError(`Can't delete old logs: ${err.message}`); 30 | }); 31 | await CreateIfNotExist(logPath, "ICMySQL\n" + GetComponents()); 32 | logReady = true; 33 | } 34 | 35 | async function Log(logType, message, description = null, solution = null) { 36 | while (!logReady) { 37 | await new Promise(resolve => setTimeout(resolve, 1)); 38 | } 39 | if (!logType || !message) return; 40 | var logMessage = `[${GetTime()}] [${logType}] ${message}`; 41 | switch (logType) { 42 | case LogTypes.Info: 43 | console.log(`${message}^0`); 44 | break; 45 | case LogTypes.Warning: 46 | console.warn(`${message}^0`); 47 | break; 48 | case LogTypes.Error: 49 | console.error(`${message}^0`); 50 | break; 51 | case LogTypes.Debug: 52 | Config.Debug ? console.log(`^5${message}^0`) : null; 53 | break; 54 | case LogTypes.Solution: 55 | console.log(`^3${message}^0`); 56 | break; 57 | } 58 | logs.push({ 59 | type: logType, 60 | message: logMessage, 61 | description: description, 62 | solution: solution 63 | }); 64 | AppendData(logPath, logMessage.replace(/\^(\d+)/g, '') + "\n"); 65 | } 66 | 67 | async function GetLogs() { 68 | return logs; 69 | } 70 | 71 | module.exports = { Log, LogTypes, InitLogs, GetLogs } -------------------------------------------------------------------------------- /src/utils/Parser.js: -------------------------------------------------------------------------------- 1 | const { IsConnecting } = require('../db/Connections.js'); 2 | const { ParseError } = require('../errors/Parser.js'); 3 | 4 | async function ParseArgs(dbId, query, values, callback, cache) { 5 | while (IsConnecting()) { 6 | await new Promise(resolve => requestAnimationFrame(resolve)); 7 | } 8 | if (dbId == null) { 9 | return ParseError("Invalid params for query function."); 10 | } 11 | 12 | if (typeof dbId === "string") { 13 | cache = callback != null ? callback : false; 14 | callback = values; 15 | values = query != null ? query : []; 16 | query = dbId; 17 | dbId = Config.DefaultDB; 18 | } 19 | 20 | if (typeof values === "function") { 21 | callback = values; 22 | values = []; 23 | if (typeof callback === "boolean") 24 | cache = callback; 25 | else 26 | cache = false; 27 | } else if (typeof values === "boolean") { 28 | cache = values; 29 | values = []; 30 | } else if (typeof values === "object") { 31 | if (typeof callback === "boolean") 32 | cache = callback; 33 | else 34 | cache = false; 35 | } else { 36 | values = []; 37 | } 38 | if(values.length > 0) { 39 | if(values[0] instanceof Array) { 40 | values = values[0]; 41 | } 42 | } 43 | if (cache === undefined) cache = false; 44 | if (typeof dbId !== "number" || typeof query !== "string" || typeof values !== "object" || typeof cache !== "boolean") return ParseError("Invalid params for query function."); 45 | if (pools[dbId] == null) return ParseError(`The database ${dbId} is not registered.`); 46 | return { dbId: dbId, query: query, values: values, callback: callback, cache: cache }; 47 | } 48 | 49 | function ParseNilArgs(query, values) { 50 | const match = query.match(/\?(?!\?)/g); 51 | if(query.includes("@") || match == null) return values; 52 | for(let i = 1; i < match.length; i++) { 53 | if(values[i] == undefined) { 54 | values[i] = null; 55 | } 56 | } 57 | return Object.values(values); 58 | } 59 | 60 | function ParseResponse(type, rows) { 61 | switch (type) { 62 | case 'Prepare': 63 | return (rows.affectedRows != null) ? rows.affectedRows : (rows.length == 1) ? (Object.keys(rows[0]).length == 1 ? rows[0][Object.keys(rows[0])[0]] : rows[0]) : (rows.length == 0 ? null : rows); 64 | case 'Insert': 65 | return rows.affectedRows != null ? rows.insertId : null; 66 | 67 | case 'Update': 68 | return rows.affectedRows != null ? rows.affectedRows : null; 69 | 70 | case 'Single': 71 | return rows.length != null ? rows[0] : null; 72 | 73 | case 'Scalar': 74 | return (rows.length != null && rows[0] != null && Object.values(rows[0])[0] != null) ? Object.values(rows[0])[0] : null; 75 | 76 | default: 77 | return rows || null; 78 | } 79 | } 80 | 81 | module.exports = { ParseArgs, ParseResponse, ParseNilArgs } -------------------------------------------------------------------------------- /src/utils/Time.js: -------------------------------------------------------------------------------- 1 | function GetDate() { 2 | const currentDate = new Date(); 3 | return `${String(currentDate.getDate()).padStart(2, '0')}/${String(currentDate.getMonth() + 1).padStart(2, '0')}/${currentDate.getFullYear()}`; 4 | } 5 | 6 | function GetTime() { 7 | const currentDate = new Date(); 8 | return `${String(currentDate.getHours()).padStart(2, '0')}:${String(currentDate.getMinutes()).padStart(2, '0')}:${String(currentDate.getSeconds()).padStart(2, '0')}`;; 9 | } 10 | 11 | function GetTimeDate() { 12 | return `${GetDate()}_${GetTime()}`; 13 | } 14 | 15 | function GetTimestamp() { 16 | return new Date().getTime(); 17 | } 18 | 19 | module.exports = { GetDate, GetTime, GetTimeDate, GetTimestamp }; -------------------------------------------------------------------------------- /src/utils/TypeCast.js: -------------------------------------------------------------------------------- 1 | module.exports = function(field, next) { 2 | const { type, string, buffer, length, charset } = field; 3 | const typeHandlers = { 4 | 'DATETIME': field => new Date(field.string()).getTime(), 5 | 'DATETIME2': field => new Date(field.string()).getTime(), 6 | 'TIMESTAMP': field => new Date(field.string()).getTime(), 7 | 'TIMESTAMP2': field => new Date(field.string()).getTime(), 8 | 'NEWDATE': field => new Date(field.string()).getTime(), 9 | 'DATE': field => new Date(field.string() + ' 00:00:00').getTime(), 10 | 'TINY': field => field.length === 1 ? field.string() === '1' : next(), 11 | 'BIT': field => field.length === 1 ? field.buffer()[0] === 1 : field.buffer()[0], 12 | 'TINY_BLOB': field => field.charset === 63 ? (field.buffer() === null ? [null] : [...field.buffer()]) : field.string(), 13 | 'MEDIUM_BLOB': field => field.charset === 63 ? (field.buffer() === null ? [null] : [...field.buffer()]) : field.string(), 14 | 'LONG_BLOB': field => field.charset === 63 ? (field.buffer() === null ? [null] : [...field.buffer()]) : field.string(), 15 | 'BLOB': field => field.charset === 63 ? (field.buffer() === null ? [null] : [...field.buffer()]) : field.string() 16 | }; 17 | 18 | const handler = typeHandlers[type]; 19 | return handler ? handler(field) : next(); 20 | } -------------------------------------------------------------------------------- /src/utils/Updater.js: -------------------------------------------------------------------------------- 1 | const extract = require('extract-zip') 2 | const { ParseError } = require('../errors/Parser.js') 3 | const axios = require('axios') 4 | const { Log, LogTypes } = require('../utils/Logger.js') 5 | 6 | var updateAvailable = false; 7 | 8 | async function GetVersion() { 9 | const urlVersion = `https://raw.githubusercontent.com/IceClusters/IceVersions/develop/${global.resourceName}`; 10 | try { 11 | return await axios.get(urlVersion).then((response) => { 12 | if (response.status != 200) { 13 | return ParseError("Error checking version", [response.status]); 14 | } 15 | 16 | const versionNumbers = response.data.match(/\d+\.\d+\.\d+/g).map(numberWithDot => 17 | numberWithDot.split('.').map(Number) 18 | )[0]; 19 | if (versionNumbers == undefined) { 20 | return ParseError("Undefined version", [response.data]); 21 | } 22 | if (versionNumbers.length != 3) { 23 | return ParseError("Unkown version structure", [versionNumbers]); 24 | } 25 | return typeof versionNumbers == "object" ? versionNumbers : null; 26 | }); 27 | } catch (e) { 28 | ParseError("Can't get version, error: " + e.message) 29 | return null 30 | } 31 | } 32 | 33 | async function UpdateScript() { 34 | const ignoreFiles = ["config.js"]; 35 | const downloadUrl = "https://github.com/IceClusters/icmysql/archive/refs/heads/develop.zip"; 36 | const extractPath = path.join(GetResourcePath(global.resourceName)); 37 | const downloadPath = path.join(extractPath, "icmysql.zip"); 38 | const download = await axios({ 39 | method: 'get', 40 | url: downloadUrl, 41 | responseType: 'stream' 42 | }); 43 | const writer = fs.createWriteStream(downloadPath); 44 | download.data.pipe(writer); 45 | await new Promise((resolve, reject) => { 46 | writer.on('finish', resolve); 47 | writer.on('error', reject); 48 | }); 49 | if (!fs.existsSync(extractPath)) { 50 | await CreateDirRecursive(extractPath) 51 | } 52 | try { 53 | await extract(downloadPath, { dir: path.resolve(extractPath, '..') }); 54 | const newFolderPath = extractPath.replace("icmysql", "icmysql-old"); 55 | const oldFolderPath = extractPath.replace("icmysql", "icmysql-develop"); 56 | await fs.promises.copyFile(path.join(extractPath, "config.js"), path.join(oldFolderPath, "config.js")); 57 | await fs.promises.rename(extractPath, newFolderPath); 58 | await fs.promises.rename(path.join(path.resolve(extractPath, '..'), "icmysql-develop"), extractPath); 59 | } catch (err) { 60 | ParseError("Error updating resource", [err]); 61 | } 62 | } 63 | 64 | async function CheckVersion() { 65 | GetVersion().then(async function (version) { 66 | if(version == null) return; 67 | const currentVersion = GetResourceMetadata(global.resourceName, 'version', 0).split('.').map(Number); 68 | if (currentVersion.length != 3) { 69 | return ParseError("Unkown current version structure", [currentVersion]); 70 | } 71 | var missingMajorUpdate = 0; 72 | var missingMinorUpdate = 0; 73 | var missingPatchUpdate = 0; 74 | for (let i = 0; i < version.length; i++) { 75 | if (version[i] > currentVersion[i]) { 76 | switch (i) { 77 | case 0: 78 | missingMajorUpdate = version[i] - currentVersion[i]; 79 | break; 80 | case 1: 81 | missingMinorUpdate = version[i] - currentVersion[i]; 82 | break; 83 | case 2: 84 | missingPatchUpdate = version[i] - currentVersion[i]; 85 | break; 86 | } 87 | } 88 | } 89 | if (missingMajorUpdate > 0 || missingMinorUpdate > 0 || missingPatchUpdate > 0) { 90 | Log(LogTypes.Info, "^6New version available"); 91 | Log(LogTypes.Info, "^6Current version: ^5" + currentVersion.join('.')); 92 | Log(LogTypes.Info, "^6New version: ^5" + version.join('.')); 93 | if (Config.AutoUpdate) { 94 | updateAvailable = true; 95 | Log(LogTypes.Info, "^3There is a new version available, type ^2updateic^3 command to update the resource, if you don't want to recieve this message again, disable the auto update in the config.js file."); 96 | RegisterCommand("updateic", async function (source, args, rawCommand) { 97 | if(Number(source) != 0) return; 98 | if (updateAvailable) { 99 | Log(LogTypes.Info, "^3Updating resource..."); 100 | await UpdateScript(); 101 | } 102 | }); 103 | } 104 | } 105 | }); 106 | } 107 | 108 | module.exports = { CheckVersion } --------------------------------------------------------------------------------