diff --git a/UPDATES@DEV.md b/UPDATES@DEV.md index 8cc5226..27c55f5 100644 --- a/UPDATES@DEV.md +++ b/UPDATES@DEV.md @@ -1,8 +1,18 @@ # VERSION 2@DEV CHANGELOGS -## [DEV 5](https://github.com/Abadima/simply-xp/releases/tag/v2.0.0-dev.5) +## [DEV 5 FIX 0](https://github.com/Abadima/simply-xp/releases/tag/v2.0.0-dev.5-fix.0) + +### ⚠️ Breaking Changes +- `roleSetup()` now takes `role` instead of `roles` again, to make migration from V1 less painful +### Bug Fixes + +- Complete overhaul of `simply-xp-levelroles` in SQLite, making it actually functional +- Fix `simply-xps-levelroles` not having `timestamp` property in SQLite & MongoDB +- Fix `roleSetup.add()` not actually returning `true/false` + +## [DEV 5](https://github.com/Abadima/simply-xp/releases/tag/v2.0.0-dev.5) ### Additions diff --git a/package.json b/package.json index 939e6fd..41d06af 100644 --- a/package.json +++ b/package.json @@ -47,15 +47,15 @@ "@napi-rs/canvas": "^0.1.44" }, "devDependencies": { - "@types/better-sqlite3": "^7.6.5", - "@types/node": "^20.8.6", - "@typescript-eslint/eslint-plugin": "^6.7.5", - "@typescript-eslint/parser": "^6.7.5", + "@types/better-sqlite3": "^7.6.7", + "@types/node": "^20.9.0", + "@typescript-eslint/eslint-plugin": "^6.10.0", + "@typescript-eslint/parser": "^6.10.0", "better-sqlite3": "^9.0.0", "discord.js": "^14.13.0", - "eslint": "^8.51.0", + "eslint": "^8.53.0", "jsdoc-to-markdown": "^8.0.0", - "mongodb": "^6.1.0", + "mongodb": "^6.2.0", "typescript": "^5.2.2", "uglify-js": "^3.17.4" }, diff --git a/src/connect.ts b/src/connect.ts index 2f03d20..7c90203 100644 --- a/src/connect.ts +++ b/src/connect.ts @@ -68,8 +68,9 @@ export async function connect(uri: string, options: ConnectionOptions = {type: u ); xp.database.exec(`CREATE TABLE IF NOT EXISTS "simply-xp-levelroles" ( - gid TEXT UNIQUE, - lvlrole TEXT NOT NULL + gid TEXT NOT NULL, + lvlrole TEXT NOT NULL, + timestamp TEXT NOT NULL )` ); } catch (error: unknown) { diff --git a/src/functions/database.ts b/src/functions/database.ts index 72dab38..7f60155 100644 --- a/src/functions/database.ts +++ b/src/functions/database.ts @@ -47,25 +47,25 @@ export interface UserResult { * @property {string} collection - The collection to create the document in. * @property {object} data - The data to create the document with. * @property {string} data.guild - The guild ID. - * @property {number} [data.level] - The level to assign the role at. - * @property {string | Array} [data.roles] - The role(s) to assign. - * @property {string} data.timestamp - The timestamp of when the document was created. + * @property {object} data.lvlrole - The level role data. + * @property {number} data.lvlrole.lvl - The level. + * @property {string | Array} [data.lvlrole.role] - The role ID(s). */ export interface LevelRoleOptions { collection: "simply-xp-levelroles"; data: { guild: string; - level: number; - roles?: string | Array; - timestamp: string; + lvlrole: { + lvl: number, + role?: string | Array; + }; }; } export type LevelRoleResult = { _id?: string, guild: string; - level: number; - roles: Array; + lvlrole: string; timestamp: string; } @@ -99,20 +99,18 @@ export class db { */ static async createOne(query: UserOptions | LevelRoleOptions): Promise { if (!xp.database) throw new XpFatal({function: "createOne()", message: "No database connection"}); - let result: Document; switch (xp.dbType) { case "mongodb": (xp.database as MongoClient).db().collection(query.collection).insertOne(query.data).catch(error => handleError(error, "createOne()")); - result = db.findOne(query); break; case "sqlite": - if (query.collection === "simply-xps") result = (xp.database as Database).prepare("INSERT INTO \"simply-xps\" (user, guild, name, xp, level) VALUES (?, ?, ?, ?, ?)").run(query.data.user, query.data.guild, query.data?.name, query.data.xp, query.data.level); - else result = (xp.database as Database).prepare("INSERT INTO \"simply-xp-levelroles\" (guild, level, role) VALUES (?, ?, ?)").run(query.data.guild, query.data.level, query.data.roles); + if (query.collection === "simply-xps") (xp.database as Database).prepare("INSERT INTO \"simply-xps\" (user, guild, name, xp, level) VALUES (?, ?, ?, ?, ?)").run(query.data.user, query.data.guild, query.data?.name, query.data.xp, query.data.level); + else (xp.database as Database).prepare("INSERT INTO \"simply-xp-levelroles\" (gid, lvlrole, timestamp) VALUES (?, ?, ?)").run(query.data.guild, JSON.stringify(query.data.lvlrole), new Date().toISOString()); break; } - return result as UserResult | LevelRoleResult; + return await db.findOne(query) as UserResult | LevelRoleResult; } /** @@ -133,7 +131,7 @@ export class db { break; case "sqlite": if (query.collection === "simply-xps") result = (xp.database as Database).prepare("DELETE FROM \"simply-xps\" WHERE guild = ?").run(query.data.guild); - else result = (xp.database as Database).prepare("DELETE FROM \"simply-xp-levelroles\" WHERE guild = ?").run(query.data.guild); + else result = (xp.database as Database).prepare("DELETE FROM \"simply-xp-levelroles\" WHERE gid = ?").run(query.data.guild); } return !!result; } @@ -157,7 +155,11 @@ export class db { break; case "sqlite": if (query.collection === "simply-xps") result = (xp.database as Database).prepare("DELETE FROM \"simply-xps\" WHERE guild = ? AND user = ?").run(query.data.guild, query.data.user); - else result = (xp.database as Database).prepare("DELETE FROM \"simply-xp-levelroles\" WHERE guild = ? AND level = ?").run(query.data.guild, query.data.level); + else { + result = await this.find(query); + result = result.filter((row: LevelRoleResult) => JSON.parse(row.lvlrole).lvl === query.data.lvlrole.lvl)[0] as Document; + result = (xp.database as Database).prepare("DELETE FROM \"simply-xp-levelroles\" WHERE gid = ? AND lvlrole = ?").run(query.data.guild, result.lvlrole) as Document; + } } return !!result; } @@ -177,12 +179,16 @@ export class db { switch (xp.dbType) { case "mongodb": - result = (xp.database as MongoClient).db().collection(query.collection).findOne(query.data).catch(error => handleError(error, "findOne()")) as Document; + if (query.collection === "simply-xps") result = await (xp.database as MongoClient).db().collection(query.collection).findOne(query.data).catch(error => handleError(error, "findOne()")) as Document; + else result = await (xp.database as MongoClient).db().collection(query.collection).findOne({guild: query.data.guild, "lvlrole.lvl": query.data.lvlrole.lvl}).catch(error => handleError(error, "findOne()")) as Document; break; case "sqlite": if (query.collection === "simply-xps") result = (xp.database as Database).prepare("SELECT * FROM \"simply-xps\" WHERE guild = ? AND user = ?").get(query.data.guild, query.data.user) as Document; - else result = (xp.database as Database).prepare("SELECT * FROM \"simply-xp-levelroles\" WHERE guild = ? AND level = ?").get(query.data.guild, query.data.level) as Document; + else { + result = await this.find(query); + result = result.filter((row: LevelRoleResult) => JSON.parse(row.lvlrole).lvl === query.data.lvlrole.lvl)[0]; + } break; } return result as UserResult | LevelRoleResult; @@ -208,7 +214,7 @@ export class db { case "sqlite": if (query.collection === "simply-xps") result = (xp.database as Database).prepare("SELECT * FROM \"simply-xps\" WHERE guild = ?").all(query.data.guild) as Document; - else result = (xp.database as Database).prepare("SELECT * FROM \"simply-xp-levelroles\" WHERE guild = ?").all(query.data.guild) as Document; + else result = (xp.database as Database).prepare("SELECT * FROM \"simply-xp-levelroles\" WHERE gid = ?").all(query.data.guild) as Document; break; } return result as UserResult[] | LevelRoleResult[]; @@ -235,7 +241,7 @@ export class db { case "sqlite": if (filter.collection === "simply-xps" && update.collection === "simply-xps") (xp.database as Database).prepare("UPDATE \"simply-xps\" SET xp = ?, level = ?"+ (update.data?.name ? ", name = ?" : "") +" WHERE guild = ? AND user = ?").run(update.data?.name ? [update.data.xp, update.data.level, update.data.name, filter.data.guild, filter.data.user] : [update.data.xp, update.data.level, filter.data.guild, filter.data.user]); - else if (filter.collection === "simply-xp-levelroles" && update.collection === "simply-xp-levelroles") (xp.database as Database).prepare("UPDATE \"simply-xp-levelroles\" SET role = ? WHERE guild = ? AND level = ?").run(update.data.roles, filter.data.guild, filter.data.level); + else if (filter.collection === "simply-xp-levelroles" && update.collection === "simply-xp-levelroles") (xp.database as Database).prepare("UPDATE \"simply-xp-levelroles\" SET lvlrole = ? WHERE gid = ?").run(JSON.stringify(update.data.lvlrole), filter.data.guild); else throw new XpFatal({ function: "updateOne()", message: "Collection mismatch, expected same collection on both filter and update." }); diff --git a/src/roleSetup.ts b/src/roleSetup.ts index d8b1bd2..ada66ed 100644 --- a/src/roleSetup.ts +++ b/src/roleSetup.ts @@ -1,4 +1,5 @@ import {db} from "../xp"; +import { LevelRoleResult } from "./functions/database"; import {XpFatal} from "./functions/xplogs"; /** @@ -9,7 +10,7 @@ import {XpFatal} from "./functions/xplogs"; */ export interface RoleSetupObject { level: number; - roles: string[] | string; + role: string[] | string; } /** @@ -35,17 +36,16 @@ export class roleSetup { message: "Level must be a number" }); - if (!options?.roles) throw new XpFatal({ + if (!options?.role) throw new XpFatal({ function: "roleSetup.add()", message: "Role was not provided" }); - if (typeof options?.roles === "string") options.roles = [options.roles]; + if (typeof options?.role === "string") options.role = [options.role]; return await db.createOne({ - // make a new ISO date collection: "simply-xp-levelroles", - data: {guild: guildId, level: options.level, roles: options.roles, timestamp: new Date().toISOString()} - }) as unknown as boolean; + data: {guild: guildId, lvlrole: {lvl: options.level, role: options.role}} + }).then(() => true).catch(() => false); } /** @@ -54,19 +54,25 @@ export class roleSetup { * @param {string} guildId - The guild ID * @param {number} levelNumber - The level number * @link `Documentation:` https://simplyxp.js.org/docs/next/classes/roleSetup#roleSetupfind - * @returns {Promise} - The level role object + * @returns {Promise} - The level role object * @throws {XpFatal} If an invalid type is provided or value is not provided. */ - static async find(guildId: string, levelNumber: number): Promise { + static async find(guildId: string, levelNumber: number): Promise { if (!guildId) throw new XpFatal({function: "roleSetup.find()", message: "Guild ID was not provided"}); if (isNaN(levelNumber)) throw new XpFatal({ function: "roleSetup.find()", message: "Level Number was not provided" }); - return await db.findOne({ + const results = await db.findOne({ collection: "simply-xp-levelroles", - data: {guild: guildId, level: levelNumber, timestamp: new Date().toISOString()} - }) as RoleSetupObject; + data: {guild: guildId, lvlrole: {lvl: levelNumber}} + }) as LevelRoleResult; + + if (!results) return results; + else { + results.lvlrole = typeof(results.lvlrole) === "string" ? JSON.parse(results.lvlrole) : results.lvlrole; + return results; + } } /** @@ -86,7 +92,7 @@ export class roleSetup { return await db.deleteOne({ collection: "simply-xp-levelroles", - data: {guild: guildId, level: levelNumber, timestamp: new Date().toISOString()} + data: {guild: guildId, lvlrole: {lvl: levelNumber}} }); } } \ No newline at end of file diff --git a/src/set.ts b/src/set.ts index 4d9c30d..164650a 100644 --- a/src/set.ts +++ b/src/set.ts @@ -69,7 +69,7 @@ export async function setXP(userId: string, guildId: string, xpData: number, use if (!guildId) throw new XpFatal({function: "setXP()", message: "Guild ID was not provided"}); if (isNaN(xpData)) throw new XpFatal({function: "setXP()", message: "XP was not provided"}); - const user = await db.findOne({collection: "simply-xps", data: {user: userId, guild: guildId}}); + const user = await db.findOne({collection: "simply-xps", data: {user: userId, guild: guildId}}) as UserResult; let data; if (!user) {