/////////////////////////////////////// //DATABASE MODULE /////////////////////////////////////// import { ODId, ODManager, ODManagerData, ODOptionalPromise, ODPromiseVoid, ODSystemError, ODValidId, ODValidJsonType } from "./base" import fs from "fs" import nodepath from "path" import { ODDebugger } from "./console" import * as fjs from "formatted-json-stringify" ///////////////////////////////////////////////////////// //TEMPORARY OPENTICKET => OPENDISCORD MIGRATION UTILITIES ///////////////////////////////////////////////////////// /** ## ❌ Temporary function. Will be removed on full OTv4 release! */ export function TEMP_migrateDatabaseIdPrefix(id:string): string { if (!id.startsWith("openticket:")) return id return id.replaceAll("openticket:","opendiscord:") } /** ## ❌ Temporary function. Will be removed on full OTv4 release! */ export function TEMP_migrateDatabaseValuePrefix(value:string): string { return value.replaceAll('"openticket:','"opendiscord:') } /** ## ❌ Temporary function. Will be removed on full OTv4 release! */ export function TEMP_migrateDatabaseStructurePrefix(structure:ODJsonDatabaseStructure): ODJsonDatabaseStructure { return structure.map((data) => { return { category:TEMP_migrateDatabaseIdPrefix(data.category), key:TEMP_migrateDatabaseIdPrefix(data.key), value:JSON.parse(TEMP_migrateDatabaseValuePrefix(JSON.stringify(data.value))) } }) } /**## ODDatabaseManager `class` * This is an Open Ticket database manager. * * It manages all databases in the bot and allows to permanently store data from the bot! * * You can use this class to get/add a database (`ODDatabase`) in your plugin! */ export class ODDatabaseManager extends ODManager { constructor(debug:ODDebugger){ super(debug,"database") } /**Init all database files. */ async init(){ for (const database of this.getAll()){ try{ await database.init() }catch(err){ process.emit("uncaughtException",new ODSystemError(err)) } } } } /**## ODDatabase `class` * This is an Open Ticket database template. * This class doesn't do anything at all, it just gives a template & basic methods for a database. Use `ODJsonDatabase` instead! * * You can use this class if you want to create your own database implementation (e.g. `mongodb`, `mysql`,...)! */ export class ODDatabase extends ODManagerData { /**The name of the file with extension. */ file: string = "" /**The path to the file relative to the main directory. */ path: string = "" /**Init the database. */ init(): ODPromiseVoid { //nothing } /**Add/Overwrite a specific category & key in the database. Returns `true` when overwritten. */ set(category:string, key:string, value:ODValidJsonType): ODOptionalPromise { return false } /**Get a specific category & key in the database */ get(category:string, key:string): ODOptionalPromise { return undefined } /**Delete a specific category & key in the database */ delete(category:string, key:string): ODOptionalPromise { return false } /**Check if a specific category & key exists in the database */ exists(category:string, key:string): ODOptionalPromise { return false } /**Get a specific category in the database */ getCategory(category:string): ODOptionalPromise<{key:string, value:ODValidJsonType}[]|undefined> { return undefined } /**Get all values in the database */ getAll(): ODOptionalPromise { return [] } } /**## ODJsonDatabaseStructure `type` * This is the structure of how a JSON database file! */ export type ODJsonDatabaseStructure = {category:string, key:string, value:ODValidJsonType}[] /**## ODJsonDatabase `class` * This is an Open Ticket JSON database. * It stores data in a `json` file as a large `Array` using the `category`, `key`, `value` strategy. * You can store the following types: `string`, `number`, `boolean`, `array`, `object` & `null`! * * You can use this class if you want to add your own database or to use an existing one! */ export class ODJsonDatabase extends ODDatabase { constructor(id:ODValidId, file:string, customPath?:string){ super(id) this.file = (file.endsWith(".json")) ? file : file+".json" this.path = customPath ? nodepath.join("./",customPath,this.file) : nodepath.join("./database/",this.file) } /**Init the database. */ init(): ODPromiseVoid { //this.#system.getData() //TEMPORARY!!! const newData = TEMP_migrateDatabaseStructurePrefix(this.#system.getData()) this.#system.setData(newData) } /**Set/overwrite the value of `category` & `key`. Returns `true` when overwritten! * @example * const didOverwrite = database.setData("category","key","value") //value can be any of the valid types * //You need an ODJsonDatabase class named "database" for this example to work! */ set(category:string, key:string, value:ODValidJsonType): ODOptionalPromise { const currentList = this.#system.getData() const currentData = currentList.find((d) => (d.category === category) && (d.key === key)) //overwrite when already present if (currentData){ currentList[currentList.indexOf(currentData)].value = value }else{ currentList.push({category,key,value}) } this.#system.setData(currentList) return currentData ? true : false } /**Get the value of `category` & `key`. Returns `undefined` when non-existent! * @example * const data = database.getData("category","key") //data will be the value * //You need an ODJsonDatabase class named "database" for this example to work! */ get(category:string, key:string): ODOptionalPromise { const currentList = this.#system.getData() const tempresult = currentList.find((d) => (d.category === category) && (d.key === key)) return tempresult ? tempresult.value : undefined } /**Remove the value of `category` & `key`. Returns `undefined` when non-existent! * @example * const didExist = database.deleteData("category","key") //delete this value * //You need an ODJsonDatabase class named "database" for this example to work! */ delete(category:string, key:string): ODOptionalPromise { const currentList = this.#system.getData() const currentData = currentList.find((d) => (d.category === category) && (d.key === key)) if (currentData) currentList.splice(currentList.indexOf(currentData),1) this.#system.setData(currentList) return currentData ? true : false } /**Check if a value of `category` & `key` exists. Returns `false` when non-existent! */ exists(category:string, key:string): ODOptionalPromise { const currentList = this.#system.getData() const tempresult = currentList.find((d) => (d.category === category) && (d.key === key)) return tempresult ? true : false } /**Get all values in `category`. Returns `undefined` when non-existent! */ getCategory(category:string): ODOptionalPromise<{key:string, value:ODValidJsonType}[]|undefined> { const currentList = this.#system.getData() const tempresult = currentList.filter((d) => (d.category === category)) return tempresult ? tempresult.map((data) => {return {key:data.key,value:data.value}}) : undefined } /**Get all values in `category`. */ getAll(): ODOptionalPromise { return this.#system.getData() } #system = { /**Read parsed data from the json file */ getData: (): ODJsonDatabaseStructure => { if (fs.existsSync(this.path)){ try{ return JSON.parse(fs.readFileSync(this.path).toString()) }catch(err){ process.emit("uncaughtException",err) throw new ODSystemError("Unable to read database "+this.path+"! getData() read error. (see error above)") } }else{ fs.writeFileSync(this.path,"[]") return [] } }, /**Write parsed data to the json file */ setData: (data:ODJsonDatabaseStructure) => { fs.writeFileSync(this.path,JSON.stringify(data,null,"\t")) } } } /**## ODFormattedJsonDatabase `class` * This is an Open Ticket Formatted JSON database. * It stores data in a `json` file as a large `Array` using the `category`, `key`, `value` strategy. * You can store the following types: `string`, `number`, `boolean`, `array`, `object` & `null`! * * This one is exactly the same as `ODJsonDatabase`, but it has a formatter from the `formatted-json-stringify` package. * This can help you organise it a little bit better! */ export class ODFormattedJsonDatabase extends ODDatabase { /**The formatter to use on the database array */ formatter: fjs.ArrayFormatter constructor(id:ODValidId, file:string, formatter:fjs.ArrayFormatter, customPath?:string){ super(id) this.file = (file.endsWith(".json")) ? file : file+".json" this.path = customPath ? nodepath.join("./",customPath,this.file) : nodepath.join("./database/",this.file) this.formatter = formatter } /**Init the database. */ init(): ODPromiseVoid { //this.#system.getData() //TEMPORARY!!! const newData = TEMP_migrateDatabaseStructurePrefix(this.#system.getData()) this.#system.setData(newData) } /**Set/overwrite the value of `category` & `key`. Returns `true` when overwritten! * @example * const didOverwrite = database.setData("category","key","value") //value can be any of the valid types * //You need an ODFormattedJsonDatabase class named "database" for this example to work! */ set(category:string, key:string, value:ODValidJsonType): ODOptionalPromise { const currentList = this.#system.getData() const currentData = currentList.find((d) => (d.category === category) && (d.key === key)) //overwrite when already present if (currentData){ currentList[currentList.indexOf(currentData)].value = value }else{ currentList.push({category,key,value}) } this.#system.setData(currentList) return currentData ? true : false } /**Get the value of `category` & `key`. Returns `undefined` when non-existent! * @example * const data = database.getData("category","key") //data will be the value * //You need an ODFormattedJsonDatabase class named "database" for this example to work! */ get(category:string, key:string): ODOptionalPromise { const currentList = this.#system.getData() const tempresult = currentList.find((d) => (d.category === category) && (d.key === key)) return tempresult ? tempresult.value : undefined } /**Remove the value of `category` & `key`. Returns `undefined` when non-existent! * @example * const didExist = database.deleteData("category","key") //delete this value * //You need an ODFormattedJsonDatabase class named "database" for this example to work! */ delete(category:string, key:string): ODOptionalPromise { const currentList = this.#system.getData() const currentData = currentList.find((d) => (d.category === category) && (d.key === key)) if (currentData) currentList.splice(currentList.indexOf(currentData),1) this.#system.setData(currentList) return currentData ? true : false } /**Check if a value of `category` & `key` exists. Returns `false` when non-existent! */ exists(category:string, key:string): ODOptionalPromise { const currentList = this.#system.getData() const tempresult = currentList.find((d) => (d.category === category) && (d.key === key)) return tempresult ? true : false } /**Get all values in `category`. Returns `undefined` when non-existent! */ getCategory(category:string): ODOptionalPromise<{key:string, value:ODValidJsonType}[]|undefined> { const currentList = this.#system.getData() const tempresult = currentList.filter((d) => (d.category === category)) return tempresult ? tempresult.map((data) => {return {key:data.key,value:data.value}}) : undefined } /**Get all values in `category`. */ getAll(): ODOptionalPromise { return this.#system.getData() } #system = { /**Read parsed data from the json file */ getData: (): ODJsonDatabaseStructure => { if (fs.existsSync(this.path)){ return JSON.parse(fs.readFileSync(this.path).toString()) }else{ fs.writeFileSync(this.path,"[]") return [] } }, /**Write parsed data to the json file */ setData: (data:ODJsonDatabaseStructure) => { fs.writeFileSync(this.path,this.formatter.stringify(data)) } } }