Model.ts 5.2 KB

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