Model.ts 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. /* eslint max-classes-per-file: 0 */
  2. import { forEachIn } from "@common/utils/forEachIn";
  3. import { useModelStore } from "./stores/model";
  4. import { useWebsocketStore } from "./stores/websocket";
  5. import { ModelPermissionFetcher } from "./ModelPermissionFetcher";
  6. export default class Model {
  7. private _permissions?: object;
  8. private _subscriptions?: { updated: string; deleted: string };
  9. private _uses: number;
  10. private _loadedRelations: string[];
  11. constructor(data: object) {
  12. this._uses = 0;
  13. this._loadedRelations = [];
  14. Object.assign(this, data);
  15. }
  16. private async _getRelations(
  17. model?: object,
  18. path?: string
  19. ): Promise<string[]> {
  20. const relationPaths = await Object.entries(model ?? this)
  21. .filter(
  22. ([key, value]) =>
  23. !key.startsWith("_") &&
  24. (typeof value === "object" || Array.isArray(value))
  25. )
  26. .reduce(async (_modelIds, [key, value]) => {
  27. const paths = await _modelIds;
  28. path = path ? `${path}.${key}` : key;
  29. if (typeof value === "object" && value._id) paths.push(path);
  30. else if (Array.isArray(value))
  31. await forEachIn(value, async item => {
  32. if (typeof item !== "object") return;
  33. if (item._id) paths.push(path);
  34. else
  35. paths.push(
  36. ...(await this._getRelations(item, path))
  37. );
  38. });
  39. else paths.push(...(await this._getRelations(value, path)));
  40. return paths;
  41. }, Promise.resolve([]));
  42. return relationPaths.filter(
  43. (relationPath, index) =>
  44. relationPaths.indexOf(relationPath) === index
  45. );
  46. }
  47. private async _getRelation(key: string) {
  48. let relation = JSON.parse(JSON.stringify(this));
  49. key.split(".").forEach(property => {
  50. if (Number.isInteger(property))
  51. property = Number.parseInt(property);
  52. relation = relation[property];
  53. });
  54. return relation;
  55. }
  56. private async _loadRelation(
  57. model: object,
  58. path: string,
  59. force: boolean,
  60. pathParts?: string[]
  61. ): Promise<void> {
  62. const parts = path.split(".");
  63. let [head] = parts;
  64. const [, ...rest] = parts;
  65. let [next] = rest;
  66. if (Number.isInteger(head)) head = Number.parseInt(head);
  67. if (Number.isInteger(next)) next = Number.parseInt(next);
  68. pathParts ??= [];
  69. pathParts.push(head);
  70. if (Array.isArray(model[head])) {
  71. await forEachIn(
  72. model[head],
  73. async (item, index) => {
  74. let itemPath = `${index}`;
  75. if (rest.length > 0) itemPath += `.${rest.join(".")}`;
  76. await this._loadRelation(model[head], itemPath, force, [
  77. ...pathParts
  78. ]);
  79. },
  80. { concurrency: 1 }
  81. );
  82. return;
  83. }
  84. if (rest.length > 0 && model[next] === null) {
  85. await this._loadRelation(
  86. model[head],
  87. rest.join("."),
  88. force,
  89. pathParts
  90. );
  91. return;
  92. }
  93. const fullPath = pathParts.join(".");
  94. if (force || !this._loadedRelations.includes(fullPath)) {
  95. const { findById, registerModel } = useModelStore();
  96. const data = await findById(model[head]._name, model[head]._id);
  97. const registeredModel = await registerModel(data);
  98. model[head] = registeredModel;
  99. this._loadedRelations.push(fullPath);
  100. }
  101. if (rest.length === 0) return;
  102. await model[head].loadRelations(rest.join("."));
  103. }
  104. public async loadRelations(
  105. relations?: string | string[],
  106. force = false
  107. ): Promise<void> {
  108. if (relations)
  109. relations = Array.isArray(relations) ? relations : [relations];
  110. await forEachIn(relations ?? [], async path => {
  111. await this._loadRelation(this, path, force);
  112. });
  113. }
  114. public async unregisterRelations(): Promise<void> {
  115. const { unregisterModels } = useModelStore();
  116. const relations = {};
  117. await forEachIn(this._loadedRelations, async path => {
  118. const relation = await this._getRelation(path);
  119. const { _name: modelName, _id: modelId } = relation;
  120. relations[modelName] ??= [];
  121. relations[modelName].push(modelId);
  122. });
  123. const modelNames = Object.keys(relations);
  124. await forEachIn(modelNames, async modelName => {
  125. await unregisterModels(modelName, relations[modelName]);
  126. });
  127. }
  128. public async updateData(data: object) {
  129. await this.unregisterRelations();
  130. Object.assign(this, data);
  131. await this.loadRelations(this._loadedRelations, true);
  132. await this.refreshPermissions();
  133. }
  134. public getName(): string {
  135. return this._name;
  136. }
  137. public getId(): string {
  138. return this._id;
  139. }
  140. public async getPermissions(refresh = false): Promise<object> {
  141. if (refresh === false && this._permissions) return this._permissions;
  142. this._permissions = await ModelPermissionFetcher.fetchModelPermissions(
  143. this.getName(),
  144. this.getId()
  145. );
  146. return this._permissions;
  147. }
  148. public async refreshPermissions(): Promise<void> {
  149. if (this._permissions) this.getPermissions(true);
  150. }
  151. public async hasPermission(permission: string): Promise<boolean> {
  152. const permissions = await this.getPermissions();
  153. return !!permissions[permission];
  154. }
  155. public getSubscriptions() {
  156. return this._subscriptions;
  157. }
  158. public setSubscriptions(updated: string, deleted: string): void {
  159. this._subscriptions = { updated, deleted };
  160. }
  161. public getUses(): number {
  162. return this._uses;
  163. }
  164. public addUse(): void {
  165. this._uses += 1;
  166. }
  167. public removeUse(): void {
  168. this._uses -= 1;
  169. }
  170. public toJSON(): object {
  171. return Object.fromEntries(
  172. Object.entries(this).filter(
  173. ([key, value]) =>
  174. (!key.startsWith("_") || key === "_id") &&
  175. typeof value !== "function"
  176. )
  177. );
  178. }
  179. public async update(query: object) {
  180. const { runJob } = useWebsocketStore();
  181. return runJob(`data.${this.getName()}.updateById`, {
  182. _id: this.getId(),
  183. query
  184. });
  185. }
  186. public async delete() {
  187. const { runJob } = useWebsocketStore();
  188. return runJob(`data.${this.getName()}.deleteById`, { _id: this._id });
  189. }
  190. }