BaseModule.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. import { readdir } from "fs/promises";
  2. import path from "path";
  3. import { forEachIn } from "@common/utils/forEachIn";
  4. import LogBook, { Log } from "@/LogBook";
  5. import ModuleManager from "@/ModuleManager";
  6. import Job from "./Job";
  7. import { EventClass } from "./modules/EventsModule/Event";
  8. export enum ModuleStatus {
  9. LOADED = "LOADED",
  10. STARTING = "STARTING",
  11. STARTED = "STARTED",
  12. STOPPED = "STOPPED",
  13. STOPPING = "STOPPING",
  14. ERROR = "ERROR",
  15. DISABLED = "DISABLED"
  16. }
  17. export default abstract class BaseModule {
  18. protected _name: string;
  19. protected _status: ModuleStatus;
  20. protected _dependentModules: string[];
  21. protected _jobs: Record<string, typeof Job>;
  22. protected _events: Record<string, EventClass>;
  23. /**
  24. * Base Module
  25. *
  26. * @param name - Module name
  27. */
  28. public constructor(name: string) {
  29. this._name = name;
  30. this._status = ModuleStatus.LOADED;
  31. this._dependentModules = [];
  32. this._jobs = {};
  33. this._events = {};
  34. this.log(`Module (${this._name}) loaded`);
  35. }
  36. /**
  37. * getName - Get module name
  38. *
  39. * @returns name
  40. */
  41. public getName() {
  42. return this._name;
  43. }
  44. /**
  45. * getStatus - Get module status
  46. *
  47. * @returns status
  48. */
  49. public getStatus() {
  50. return this._status;
  51. }
  52. /**
  53. * setStatus - Set module status
  54. *
  55. * @param status - Module status
  56. */
  57. public setStatus(status: ModuleStatus) {
  58. this._status = status;
  59. }
  60. /**
  61. * getDependentModules - Get module dependencies
  62. */
  63. public getDependentModules() {
  64. return this._dependentModules;
  65. }
  66. /**
  67. * _loadJobs - Load jobs available via api module
  68. */
  69. private async _loadJobs() {
  70. let jobs;
  71. try {
  72. jobs = await readdir(
  73. path.resolve(
  74. __dirname,
  75. `./modules/${this.constructor.name}/jobs`
  76. )
  77. );
  78. } catch (error) {
  79. if (
  80. error instanceof Error &&
  81. "code" in error &&
  82. error.code === "ENOENT"
  83. )
  84. return;
  85. throw error;
  86. }
  87. await forEachIn(jobs, async jobFile => {
  88. const { default: Job } = await import(
  89. `./modules/${this.constructor.name}/jobs/${jobFile}`
  90. );
  91. const jobName = Job.getName();
  92. this._jobs[jobName] = Job;
  93. });
  94. }
  95. /**
  96. * getJob - Get module job
  97. */
  98. public getJob(name: string) {
  99. const [, Job] =
  100. Object.entries(this._jobs).find(([jobName]) => jobName === name) ??
  101. [];
  102. if (!Job) throw new Error(`Job "${name}" not found.`);
  103. return Job;
  104. }
  105. /**
  106. * getJobs - Get module jobs
  107. */
  108. public getJobs() {
  109. return this._jobs;
  110. }
  111. /**
  112. * canRunJobs - Determine if module can run jobs
  113. */
  114. public canRunJobs() {
  115. return this.getDependentModules().reduce(
  116. (canRunJobs: boolean, moduleName: string): boolean => {
  117. if (canRunJobs === false) return false;
  118. return !!ModuleManager.getModule(moduleName)?.canRunJobs();
  119. },
  120. this.getStatus() === ModuleStatus.STARTED
  121. );
  122. }
  123. /**
  124. * _loadEvents - Load events
  125. */
  126. private async _loadEvents() {
  127. let events;
  128. try {
  129. events = await readdir(
  130. path.resolve(
  131. __dirname,
  132. `./modules/${this.constructor.name}/events`
  133. )
  134. );
  135. } catch (error) {
  136. if (
  137. error instanceof Error &&
  138. "code" in error &&
  139. error.code === "ENOENT"
  140. )
  141. return;
  142. throw error;
  143. }
  144. await forEachIn(events, async eventFile => {
  145. const { default: EventClass } = await import(
  146. `./modules/${this.constructor.name}/events/${eventFile}`
  147. );
  148. const eventName = EventClass.getName();
  149. this._events[eventName] = EventClass;
  150. });
  151. }
  152. /**
  153. * getEvent - Get module event
  154. */
  155. public getEvent(name: string): EventClass {
  156. const [, Event] =
  157. Object.entries(this._events).find(
  158. ([eventName]) => eventName === name
  159. ) ?? [];
  160. if (!Event) throw new Error(`Event "${name}" not found.`);
  161. return Event;
  162. }
  163. /**
  164. * getEvents - Get module events
  165. */
  166. public getEvents() {
  167. return this._events;
  168. }
  169. /**
  170. * startup - Startup module
  171. */
  172. public async startup() {
  173. this.log(`Module (${this._name}) starting`);
  174. this.setStatus(ModuleStatus.STARTING);
  175. }
  176. /**
  177. * started - called with the module has started
  178. */
  179. protected async _started() {
  180. await this._loadJobs();
  181. await this._loadEvents();
  182. this.log(`Module (${this._name}) started`);
  183. this.setStatus(ModuleStatus.STARTED);
  184. }
  185. /**
  186. * shutdown - Shutdown module
  187. */
  188. public async shutdown() {
  189. this.log(`Module (${this._name}) stopping`);
  190. this.setStatus(ModuleStatus.STOPPING);
  191. }
  192. /**
  193. * stopped - called when the module has stopped
  194. */
  195. protected async _stopped() {
  196. this.log(`Module (${this._name}) stopped`);
  197. this.setStatus(ModuleStatus.STOPPED);
  198. }
  199. /**
  200. * log - Add log to logbook
  201. *
  202. * @param log - Log message or object
  203. */
  204. public log(log: string | Omit<Log, "timestamp" | "category">) {
  205. const {
  206. message,
  207. type = undefined,
  208. data = {}
  209. } = {
  210. ...(typeof log === "string" ? { message: log } : log)
  211. };
  212. LogBook.log({
  213. message,
  214. type,
  215. category: `modules.${this.getName()}`,
  216. data: {
  217. moduleName: this._name,
  218. ...data
  219. }
  220. });
  221. }
  222. }