entityManager.ts 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. import ApiRequestService from "./apiRequestService";
  2. import {Repository, useRepo} from "pinia-orm";
  3. import UrlBuilder from "~/services/utils/urlBuilder";
  4. import ModelNormalizer from "./serializer/normalizer/modelNormalizer";
  5. import HydraDenormalizer from "./serializer/denormalizer/hydraDenormalizer";
  6. import ApiModel from "~/models/ApiModel";
  7. import {useProfileAccessStore} from "~/store/profile/access";
  8. import ApiResource from "~/models/ApiResource";
  9. import {MyProfile} from "~/models/Access/MyProfile";
  10. import { v4 as uuid4 } from 'uuid';
  11. import {AssociativeArray} from "~/types/data.d";
  12. /**
  13. * Entity manager: make operations on the models defined with the Pinia-Orm library
  14. *
  15. * @see https://pinia-orm.codedredd.de/
  16. */
  17. class EntityManager {
  18. private CLONE_PREFIX = '_clone_'
  19. private apiRequestService: ApiRequestService;
  20. public constructor(apiRequestService: ApiRequestService) {
  21. this.apiRequestService = apiRequestService
  22. }
  23. /**
  24. * Return the repository for the model
  25. *
  26. * @param model
  27. */
  28. public getRepository(model: typeof ApiResource): Repository<ApiResource> {
  29. return useRepo(model)
  30. }
  31. /**
  32. * Create a new instance of the given model
  33. *
  34. * @param model
  35. * @param properties
  36. */
  37. public new(model: typeof ApiResource, properties: object = {}) {
  38. const repository = this.getRepository(model)
  39. const entity = repository.make(properties)
  40. // @ts-ignore
  41. if (!properties.hasOwnProperty('id') || !properties.id) {
  42. // Object has no id yet, we give him a temporary one
  43. entity.id = 'tmp' + uuid4()
  44. }
  45. repository.save(entity)
  46. this.saveInitialState(model, entity)
  47. return entity
  48. }
  49. private reactiveUpdate(entity: ApiResource, newEntity: ApiResource) {
  50. // On met à jour l'entité par référence, pour maintenir la réactivité lorsque l'entité est réactive
  51. // @see http://underscorejs.org/#extend
  52. useExtend(entity, newEntity)
  53. }
  54. /**
  55. * Fetch one Entity / ApiResource by its id, save it to the store and returns it
  56. *
  57. * @param model Model of the object to fetch
  58. * @param id Id of the object to fetch
  59. * @param forceRefresh Force a new get request to the api ;
  60. * current object in store will be overwritten if it exists
  61. */
  62. public async fetch(model: typeof ApiResource, id: number, forceRefresh: boolean = false): Promise<ApiResource> {
  63. const repository = this.getRepository(model)
  64. // If the entity is already in the store and forceRefresh is false, return the object in store
  65. if (!forceRefresh) {
  66. const item = repository.find(id)
  67. if (item && typeof item !== 'undefined') {
  68. return item
  69. }
  70. }
  71. // Else, get the object from the API
  72. const url = UrlBuilder.join('api', model.entity, String(id))
  73. const response = await this.apiRequestService.get(url)
  74. // deserialize the response
  75. const attributes = HydraDenormalizer.denormalize(response).data as object
  76. return this.new(model, attributes)
  77. }
  78. public findBy(model: typeof ApiResource, query: AssociativeArray) {
  79. // TODO: implement
  80. }
  81. public fetchAll(model: typeof ApiResource) {
  82. // TODO: implement
  83. }
  84. /**
  85. * Persist the entity as it is in the store into the data source via the API
  86. *
  87. * @param model
  88. * @param entity
  89. */
  90. public async persist(model: typeof ApiModel, entity: ApiModel) {
  91. const repository = this.getRepository(model)
  92. let url = UrlBuilder.join('api', model.entity)
  93. let response
  94. const data = ModelNormalizer.normalize(entity)
  95. if (!entity.isNew()) {
  96. url = UrlBuilder.join(url, String(entity.id))
  97. response = await this.apiRequestService.put(url, data)
  98. } else {
  99. delete data.id
  100. response = await this.apiRequestService.post(url, data)
  101. }
  102. const hydraResponse = await HydraDenormalizer.denormalize(response)
  103. const returnedEntity = this.new(model, hydraResponse.data)
  104. this.saveInitialState(model, returnedEntity)
  105. // Save data into the store
  106. repository.save(returnedEntity)
  107. if (['accesses', 'organizations', 'parameters', 'subdomains'].includes(model.entity)) {
  108. await this.refreshProfile()
  109. }
  110. this.reactiveUpdate(entity, returnedEntity)
  111. }
  112. /**
  113. * Delete the entity from the datasource via the API
  114. *
  115. * @param model
  116. * @param entity
  117. */
  118. public async delete(model: typeof ApiModel, entity: ApiResource) {
  119. const repository = this.getRepository(model)
  120. // If object has been persisted to the datasource, send a delete request
  121. if (!entity.isNew()) {
  122. const url = UrlBuilder.join('api', model.entity, String(entity.id))
  123. await this.apiRequestService.delete(url)
  124. }
  125. // reactiveUpdate the store
  126. repository.destroy(entity.id)
  127. }
  128. /**
  129. * Reset the entity to its initial state (i.e. the state it had when it was fetched from the API)
  130. *
  131. * @param model
  132. * @param entity
  133. */
  134. public reset(model: typeof ApiResource, entity: ApiResource) {
  135. const initialEntity = this.getInitialStateOf(model, entity.id)
  136. if (initialEntity === null) {
  137. throw new Error('no initial state recorded for this object - abort [' + model.entity + '/' + entity.id + ']')
  138. }
  139. const repository = this.getRepository(model)
  140. repository.save(initialEntity)
  141. this.reactiveUpdate(entity, initialEntity)
  142. }
  143. /**
  144. * Re-fetch the user profile and reactiveUpdate the store
  145. */
  146. public async refreshProfile() {
  147. const response = await this.apiRequestService.get('api/my_profile')
  148. // deserialize the response
  149. const hydraResponse = await HydraDenormalizer.denormalize(response)
  150. const profile = this.new(MyProfile, hydraResponse.data)
  151. const profileAccessStore = useProfileAccessStore()
  152. profileAccessStore.setProfile(profile)
  153. }
  154. /**
  155. * Delete all records in the repository of the model
  156. *
  157. * @param model
  158. */
  159. public async flush(model: typeof ApiModel) {
  160. const repository = this.getRepository(model)
  161. repository.flush()
  162. }
  163. /**
  164. * Is the entity a new one, or does it already exist in the data source (=API)
  165. *
  166. * @param model
  167. * @param id
  168. */
  169. public isNewEntity(model: typeof ApiModel, id: number | string): boolean {
  170. const repository = this.getRepository(model)
  171. const item = repository.find(id)
  172. if (!item || typeof item === 'undefined') {
  173. console.error(model.entity + '/' + id, ' does not exist!')
  174. return false
  175. }
  176. return item.isNew()
  177. }
  178. /**
  179. * Save the state of the entity in the store, so this state could be be restored later
  180. *
  181. * @param model
  182. * @param entity
  183. * @private
  184. */
  185. private saveInitialState(model: typeof ApiResource, entity: ApiResource) {
  186. const repository = this.getRepository(model)
  187. // Clone and prefix id
  188. const clone = useCloneDeep(entity)
  189. clone.id = this.CLONE_PREFIX + clone.id
  190. repository.save(clone)
  191. }
  192. /**
  193. * Return the saved state of the entity from the store
  194. *
  195. * @param model
  196. * @param id
  197. * @private
  198. */
  199. private getInitialStateOf(model: typeof ApiResource, id: string | number): ApiResource | null {
  200. const repository = this.getRepository(model)
  201. // Find the clone by id
  202. const entity = repository.find(this.CLONE_PREFIX + id)
  203. if (entity === null) {
  204. return null
  205. }
  206. // Restore the initial id
  207. entity.id = id
  208. return entity
  209. }
  210. }
  211. export default EntityManager