@communecter/cocolight-api-client 1.0.154 → 1.0.156

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (331) hide show
  1. package/dist/1035.cocolight-api-client.browser.js +1 -1
  2. package/dist/1035.cocolight-api-client.cjs +1 -1
  3. package/dist/1483.cocolight-api-client.browser.js +1 -1
  4. package/dist/1483.cocolight-api-client.cjs +1 -1
  5. package/dist/1491.cocolight-api-client.browser.js +1 -1
  6. package/dist/1491.cocolight-api-client.cjs +1 -1
  7. package/dist/1494.cocolight-api-client.browser.js +1 -1
  8. package/dist/1494.cocolight-api-client.cjs +1 -1
  9. package/dist/1641.cocolight-api-client.browser.js +1 -1
  10. package/dist/1641.cocolight-api-client.cjs +1 -1
  11. package/dist/1678.cocolight-api-client.browser.js +1 -1
  12. package/dist/1678.cocolight-api-client.cjs +1 -1
  13. package/dist/2041.cocolight-api-client.browser.js +1 -1
  14. package/dist/2041.cocolight-api-client.cjs +1 -1
  15. package/dist/2302.cocolight-api-client.browser.js +1 -1
  16. package/dist/2302.cocolight-api-client.cjs +1 -1
  17. package/dist/2313.cocolight-api-client.browser.js +1 -1
  18. package/dist/2313.cocolight-api-client.cjs +1 -1
  19. package/dist/2379.cocolight-api-client.browser.js +1 -1
  20. package/dist/2379.cocolight-api-client.cjs +1 -1
  21. package/dist/2439.cocolight-api-client.browser.js +1 -1
  22. package/dist/2439.cocolight-api-client.cjs +1 -1
  23. package/dist/2594.cocolight-api-client.browser.js +1 -1
  24. package/dist/2594.cocolight-api-client.cjs +1 -1
  25. package/dist/2766.cocolight-api-client.browser.js +1 -1
  26. package/dist/2766.cocolight-api-client.cjs +1 -1
  27. package/dist/2812.cocolight-api-client.browser.js +1 -1
  28. package/dist/2812.cocolight-api-client.cjs +1 -1
  29. package/dist/2918.cocolight-api-client.browser.js +1 -1
  30. package/dist/2918.cocolight-api-client.cjs +1 -1
  31. package/dist/3028.cocolight-api-client.browser.js +1 -1
  32. package/dist/3028.cocolight-api-client.cjs +1 -1
  33. package/dist/3330.cocolight-api-client.browser.js +1 -1
  34. package/dist/3330.cocolight-api-client.cjs +1 -1
  35. package/dist/3371.cocolight-api-client.browser.js +1 -1
  36. package/dist/3371.cocolight-api-client.cjs +1 -1
  37. package/dist/3401.cocolight-api-client.browser.js +1 -1
  38. package/dist/3401.cocolight-api-client.cjs +1 -1
  39. package/dist/3516.cocolight-api-client.browser.js +1 -1
  40. package/dist/3516.cocolight-api-client.cjs +1 -1
  41. package/dist/3593.cocolight-api-client.browser.js +1 -1
  42. package/dist/3593.cocolight-api-client.cjs +1 -1
  43. package/dist/3764.cocolight-api-client.browser.js +1 -1
  44. package/dist/3764.cocolight-api-client.cjs +1 -1
  45. package/dist/3873.cocolight-api-client.browser.js +1 -1
  46. package/dist/3873.cocolight-api-client.cjs +1 -1
  47. package/dist/3911.cocolight-api-client.browser.js +1 -1
  48. package/dist/3911.cocolight-api-client.cjs +1 -1
  49. package/dist/4031.cocolight-api-client.browser.js +1 -1
  50. package/dist/4031.cocolight-api-client.cjs +1 -1
  51. package/dist/4152.cocolight-api-client.browser.js +1 -1
  52. package/dist/4152.cocolight-api-client.cjs +1 -1
  53. package/dist/4203.cocolight-api-client.browser.js +1 -1
  54. package/dist/4203.cocolight-api-client.cjs +1 -1
  55. package/dist/424.cocolight-api-client.browser.js +1 -1
  56. package/dist/424.cocolight-api-client.cjs +1 -1
  57. package/dist/435.cocolight-api-client.browser.js +1 -1
  58. package/dist/435.cocolight-api-client.cjs +1 -1
  59. package/dist/4402.cocolight-api-client.browser.js +1 -1
  60. package/dist/4402.cocolight-api-client.cjs +1 -1
  61. package/dist/4425.cocolight-api-client.browser.js +1 -1
  62. package/dist/4425.cocolight-api-client.cjs +1 -1
  63. package/dist/4472.cocolight-api-client.browser.js +1 -1
  64. package/dist/4472.cocolight-api-client.cjs +1 -1
  65. package/dist/4914.cocolight-api-client.browser.js +1 -1
  66. package/dist/4914.cocolight-api-client.cjs +1 -1
  67. package/dist/4959.cocolight-api-client.browser.js +1 -1
  68. package/dist/4959.cocolight-api-client.cjs +1 -1
  69. package/dist/5100.cocolight-api-client.browser.js +1 -1
  70. package/dist/5100.cocolight-api-client.cjs +1 -1
  71. package/dist/5111.cocolight-api-client.browser.js +1 -1
  72. package/dist/5111.cocolight-api-client.cjs +1 -1
  73. package/dist/5286.cocolight-api-client.browser.js +1 -1
  74. package/dist/5286.cocolight-api-client.cjs +1 -1
  75. package/dist/5380.cocolight-api-client.browser.js +1 -1
  76. package/dist/5380.cocolight-api-client.cjs +1 -1
  77. package/dist/5383.cocolight-api-client.browser.js +1 -1
  78. package/dist/5383.cocolight-api-client.cjs +1 -1
  79. package/dist/5480.cocolight-api-client.browser.js +1 -1
  80. package/dist/5480.cocolight-api-client.cjs +1 -1
  81. package/dist/5541.cocolight-api-client.browser.js +1 -1
  82. package/dist/5541.cocolight-api-client.cjs +1 -1
  83. package/dist/5839.cocolight-api-client.browser.js +1 -1
  84. package/dist/5839.cocolight-api-client.cjs +1 -1
  85. package/dist/5862.cocolight-api-client.browser.js +1 -1
  86. package/dist/5862.cocolight-api-client.cjs +1 -1
  87. package/dist/5869.cocolight-api-client.browser.js +1 -1
  88. package/dist/5869.cocolight-api-client.cjs +1 -1
  89. package/dist/588.cocolight-api-client.browser.js +1 -1
  90. package/dist/588.cocolight-api-client.cjs +1 -1
  91. package/dist/601.cocolight-api-client.browser.js +1 -1
  92. package/dist/601.cocolight-api-client.cjs +1 -1
  93. package/dist/6012.cocolight-api-client.browser.js +1 -1
  94. package/dist/6012.cocolight-api-client.cjs +1 -1
  95. package/dist/6200.cocolight-api-client.browser.js +1 -1
  96. package/dist/6200.cocolight-api-client.cjs +1 -1
  97. package/dist/6369.cocolight-api-client.browser.js +1 -1
  98. package/dist/6369.cocolight-api-client.cjs +1 -1
  99. package/dist/7009.cocolight-api-client.browser.js +1 -1
  100. package/dist/7009.cocolight-api-client.cjs +1 -1
  101. package/dist/725.cocolight-api-client.browser.js +1 -1
  102. package/dist/725.cocolight-api-client.cjs +1 -1
  103. package/dist/7320.cocolight-api-client.browser.js +1 -1
  104. package/dist/7339.cocolight-api-client.browser.js +1 -1
  105. package/dist/7339.cocolight-api-client.cjs +1 -1
  106. package/dist/7398.cocolight-api-client.browser.js +1 -1
  107. package/dist/7398.cocolight-api-client.cjs +1 -1
  108. package/dist/7888.cocolight-api-client.browser.js +1 -1
  109. package/dist/7888.cocolight-api-client.cjs +1 -1
  110. package/dist/7975.cocolight-api-client.browser.js +1 -1
  111. package/dist/7975.cocolight-api-client.cjs +1 -1
  112. package/dist/809.cocolight-api-client.browser.js +1 -1
  113. package/dist/809.cocolight-api-client.cjs +1 -1
  114. package/dist/8300.cocolight-api-client.browser.js +1 -1
  115. package/dist/8300.cocolight-api-client.cjs +1 -1
  116. package/dist/8317.cocolight-api-client.browser.js +1 -1
  117. package/dist/8317.cocolight-api-client.cjs +1 -1
  118. package/dist/8525.cocolight-api-client.browser.js +1 -1
  119. package/dist/8525.cocolight-api-client.cjs +1 -1
  120. package/dist/8894.cocolight-api-client.browser.js +1 -1
  121. package/dist/8894.cocolight-api-client.cjs +1 -1
  122. package/dist/9301.cocolight-api-client.browser.js +1 -1
  123. package/dist/9301.cocolight-api-client.cjs +1 -1
  124. package/dist/9438.cocolight-api-client.browser.js +1 -1
  125. package/dist/9438.cocolight-api-client.cjs +1 -1
  126. package/dist/9478.cocolight-api-client.browser.js +1 -1
  127. package/dist/9478.cocolight-api-client.cjs +1 -1
  128. package/dist/9568.cocolight-api-client.browser.js +1 -1
  129. package/dist/9568.cocolight-api-client.cjs +1 -1
  130. package/dist/cocolight-api-client.browser.js +6 -6
  131. package/dist/cocolight-api-client.cjs +1 -1
  132. package/dist/cocolight-api-client.vite.mjs.js +1 -1
  133. package/dist/cocolight-api-client.vite.mjs.js.map +1 -1
  134. package/package.json +7 -17
  135. package/types/Api.d.ts +1 -0
  136. package/types/ApiClient.d.ts +10 -1
  137. package/types/api/BaseEntity.d.ts +24 -0
  138. package/types/api/EndpointApi.d.ts +4 -4
  139. package/types/api/EndpointApi.types.d.ts +3 -3
  140. package/types/utils/OfflineClientManager.d.ts +8 -0
  141. package/dist/1035.cocolight-api-client.mjs.js +0 -1
  142. package/dist/1483.cocolight-api-client.mjs.js +0 -1
  143. package/dist/1491.cocolight-api-client.mjs.js +0 -1
  144. package/dist/1494.cocolight-api-client.mjs.js +0 -1
  145. package/dist/1641.cocolight-api-client.mjs.js +0 -1
  146. package/dist/1678.cocolight-api-client.mjs.js +0 -1
  147. package/dist/2041.cocolight-api-client.mjs.js +0 -1
  148. package/dist/2302.cocolight-api-client.mjs.js +0 -1
  149. package/dist/2313.cocolight-api-client.mjs.js +0 -1
  150. package/dist/2379.cocolight-api-client.mjs.js +0 -1
  151. package/dist/2439.cocolight-api-client.mjs.js +0 -1
  152. package/dist/2594.cocolight-api-client.mjs.js +0 -1
  153. package/dist/2766.cocolight-api-client.mjs.js +0 -1
  154. package/dist/2812.cocolight-api-client.mjs.js +0 -1
  155. package/dist/2918.cocolight-api-client.mjs.js +0 -1
  156. package/dist/3028.cocolight-api-client.mjs.js +0 -1
  157. package/dist/3330.cocolight-api-client.mjs.js +0 -1
  158. package/dist/3371.cocolight-api-client.mjs.js +0 -1
  159. package/dist/3401.cocolight-api-client.mjs.js +0 -1
  160. package/dist/3516.cocolight-api-client.mjs.js +0 -1
  161. package/dist/3593.cocolight-api-client.mjs.js +0 -1
  162. package/dist/3764.cocolight-api-client.mjs.js +0 -1
  163. package/dist/3873.cocolight-api-client.mjs.js +0 -1
  164. package/dist/3911.cocolight-api-client.mjs.js +0 -1
  165. package/dist/4031.cocolight-api-client.mjs.js +0 -1
  166. package/dist/4152.cocolight-api-client.mjs.js +0 -1
  167. package/dist/4203.cocolight-api-client.mjs.js +0 -1
  168. package/dist/424.cocolight-api-client.mjs.js +0 -1
  169. package/dist/435.cocolight-api-client.mjs.js +0 -1
  170. package/dist/4402.cocolight-api-client.mjs.js +0 -1
  171. package/dist/4425.cocolight-api-client.mjs.js +0 -1
  172. package/dist/4472.cocolight-api-client.mjs.js +0 -1
  173. package/dist/4914.cocolight-api-client.mjs.js +0 -1
  174. package/dist/4959.cocolight-api-client.mjs.js +0 -1
  175. package/dist/5100.cocolight-api-client.mjs.js +0 -1
  176. package/dist/5111.cocolight-api-client.mjs.js +0 -1
  177. package/dist/5286.cocolight-api-client.mjs.js +0 -1
  178. package/dist/5380.cocolight-api-client.mjs.js +0 -1
  179. package/dist/5383.cocolight-api-client.mjs.js +0 -1
  180. package/dist/5480.cocolight-api-client.mjs.js +0 -1
  181. package/dist/5541.cocolight-api-client.mjs.js +0 -1
  182. package/dist/5839.cocolight-api-client.mjs.js +0 -1
  183. package/dist/5862.cocolight-api-client.mjs.js +0 -1
  184. package/dist/5869.cocolight-api-client.mjs.js +0 -1
  185. package/dist/588.cocolight-api-client.mjs.js +0 -1
  186. package/dist/601.cocolight-api-client.mjs.js +0 -1
  187. package/dist/6012.cocolight-api-client.mjs.js +0 -1
  188. package/dist/6200.cocolight-api-client.mjs.js +0 -1
  189. package/dist/6369.cocolight-api-client.mjs.js +0 -1
  190. package/dist/7009.cocolight-api-client.mjs.js +0 -1
  191. package/dist/725.cocolight-api-client.mjs.js +0 -1
  192. package/dist/7339.cocolight-api-client.mjs.js +0 -1
  193. package/dist/7398.cocolight-api-client.mjs.js +0 -1
  194. package/dist/7888.cocolight-api-client.mjs.js +0 -1
  195. package/dist/7975.cocolight-api-client.mjs.js +0 -1
  196. package/dist/809.cocolight-api-client.mjs.js +0 -1
  197. package/dist/8300.cocolight-api-client.mjs.js +0 -1
  198. package/dist/8317.cocolight-api-client.mjs.js +0 -1
  199. package/dist/8525.cocolight-api-client.mjs.js +0 -1
  200. package/dist/8894.cocolight-api-client.mjs.js +0 -1
  201. package/dist/9301.cocolight-api-client.mjs.js +0 -1
  202. package/dist/9438.cocolight-api-client.mjs.js +0 -1
  203. package/dist/9478.cocolight-api-client.mjs.js +0 -1
  204. package/dist/9568.cocolight-api-client.mjs.js +0 -1
  205. package/dist/cocolight-api-client.mjs.js +0 -1
  206. package/src/Api.ts +0 -348
  207. package/src/ApiClient.ts +0 -1994
  208. package/src/EJSONType.ts +0 -103
  209. package/src/api/Action.ts +0 -665
  210. package/src/api/Answer.ts +0 -1392
  211. package/src/api/Badge.ts +0 -143
  212. package/src/api/BaseEntity.ts +0 -6338
  213. package/src/api/Classified.ts +0 -175
  214. package/src/api/Comment.ts +0 -280
  215. package/src/api/EndpointApi.ts +0 -2270
  216. package/src/api/EndpointApi.types.ts +0 -6566
  217. package/src/api/EntityRegistry.ts +0 -324
  218. package/src/api/Event.ts +0 -360
  219. package/src/api/Form.ts +0 -282
  220. package/src/api/News.ts +0 -547
  221. package/src/api/Notifications.ts +0 -196
  222. package/src/api/Organization.ts +0 -771
  223. package/src/api/Poi.ts +0 -242
  224. package/src/api/Project.ts +0 -545
  225. package/src/api/User.ts +0 -1889
  226. package/src/api/UserApi.ts +0 -192
  227. package/src/api/serverDataType/Action.ts +0 -177
  228. package/src/api/serverDataType/Answer.ts +0 -106
  229. package/src/api/serverDataType/Classified.ts +0 -152
  230. package/src/api/serverDataType/Comment.ts +0 -88
  231. package/src/api/serverDataType/Country.ts +0 -1
  232. package/src/api/serverDataType/Event.ts +0 -112
  233. package/src/api/serverDataType/Form.ts +0 -157
  234. package/src/api/serverDataType/News.ts +0 -138
  235. package/src/api/serverDataType/Notification.ts +0 -81
  236. package/src/api/serverDataType/Organization.ts +0 -111
  237. package/src/api/serverDataType/Poi.ts +0 -75
  238. package/src/api/serverDataType/Project.ts +0 -115
  239. package/src/api/serverDataType/User.ts +0 -112
  240. package/src/api/serverDataType/Zone.ts +0 -33
  241. package/src/api/serverDataType/common.ts +0 -84
  242. package/src/costum/normalize.ts +0 -7
  243. package/src/costum/registry.ts +0 -138
  244. package/src/costum/runtime-map.ts +0 -221
  245. package/src/costum/runtime.ts +0 -274
  246. package/src/costum/schemas/CercleTravailleursSociauxLiberauxLaPlumeSociale.ts +0 -19
  247. package/src/costum/schemas/FabEn.ts +0 -19
  248. package/src/costum/schemas/LivingLabInnovationSocialeBraspanon.ts +0 -19
  249. package/src/costum/schemas/alfph.ts +0 -27
  250. package/src/costum/schemas/campagne.ts +0 -19
  251. package/src/costum/schemas/chtitube.ts +0 -19
  252. package/src/costum/schemas/circuitCourt.ts +0 -19
  253. package/src/costum/schemas/cmcasHauteBretagne.ts +0 -19
  254. package/src/costum/schemas/coFinancementTierslieux.ts +0 -27
  255. package/src/costum/schemas/coeurNumerique.ts +0 -19
  256. package/src/costum/schemas/communLundi.ts +0 -19
  257. package/src/costum/schemas/cressReunion.ts +0 -35
  258. package/src/costum/schemas/cressReunionNetwork.ts +0 -35
  259. package/src/costum/schemas/ctenat.ts +0 -43
  260. package/src/costum/schemas/cyberReunion.ts +0 -19
  261. package/src/costum/schemas/ekisphere.ts +0 -35
  262. package/src/costum/schemas/equipementsSportifs974.ts +0 -19
  263. package/src/costum/schemas/federationDesCae.ts +0 -19
  264. package/src/costum/schemas/formationGenerique.ts +0 -35
  265. package/src/costum/schemas/franceTierslieux.ts +0 -19
  266. package/src/costum/schemas/hinaura.ts +0 -27
  267. package/src/costum/schemas/hubmayotte.ts +0 -19
  268. package/src/costum/schemas/hubnouvellecaledonie.ts +0 -19
  269. package/src/costum/schemas/hubpolynesiefrancaise.ts +0 -19
  270. package/src/costum/schemas/hubreunion.ts +0 -19
  271. package/src/costum/schemas/hubterritoires.ts +0 -19
  272. package/src/costum/schemas/hubwallisetfutuna.ts +0 -19
  273. package/src/costum/schemas/hva.ts +0 -27
  274. package/src/costum/schemas/ibv2.ts +0 -19
  275. package/src/costum/schemas/institutBleu.ts +0 -19
  276. package/src/costum/schemas/journalInsoumisChambery.ts +0 -27
  277. package/src/costum/schemas/kpacite.ts +0 -19
  278. package/src/costum/schemas/laCompagnieDesTierslieux.ts +0 -43
  279. package/src/costum/schemas/laMaisonDesFaiseurs.ts +0 -19
  280. package/src/costum/schemas/laReunionPourUnNumeriqueInclusif.ts +0 -19
  281. package/src/costum/schemas/laSemaineDesTierslieux.ts +0 -19
  282. package/src/costum/schemas/labEnCommuns.ts +0 -19
  283. package/src/costum/schemas/lakou.ts +0 -27
  284. package/src/costum/schemas/lesCommunsDesTierslieux.ts +0 -19
  285. package/src/costum/schemas/mayottePourUnNumeriqueInclusif.ts +0 -19
  286. package/src/costum/schemas/meir.ts +0 -27
  287. package/src/costum/schemas/navigatorDesTierslieux.ts +0 -19
  288. package/src/costum/schemas/notragora.ts +0 -27
  289. package/src/costum/schemas/nouvellecaledoniePourUnNumeriqueInclusif.ts +0 -19
  290. package/src/costum/schemas/openAtlas.ts +0 -19
  291. package/src/costum/schemas/polynesieFrancaisePourUnNumeriqueInclusif.ts +0 -19
  292. package/src/costum/schemas/prdr.ts +0 -19
  293. package/src/costum/schemas/reseauTierslieux.ts +0 -19
  294. package/src/costum/schemas/rfflabs.ts +0 -19
  295. package/src/costum/schemas/ries.ts +0 -19
  296. package/src/costum/schemas/siteDuPactePourLaTransition.ts +0 -27
  297. package/src/costum/schemas/sportSanteBienetre.ts +0 -43
  298. package/src/costum/schemas/suppa.ts +0 -19
  299. package/src/costum/schemas/terramies.ts +0 -19
  300. package/src/costum/schemas/testeTplFormation.ts +0 -35
  301. package/src/costum/schemas/tierslieuxbelgique.ts +0 -19
  302. package/src/costum/schemas/tierslieuxorg1.ts +0 -35
  303. package/src/costum/schemas/transiter.ts +0 -35
  304. package/src/costum/schemas/vhelioPeiTour2025.ts +0 -19
  305. package/src/costum/schemas/wallisetfutunaPourUnNumeriqueInclusif.ts +0 -19
  306. package/src/costum/symbol.ts +0 -26
  307. package/src/costum/types.ts +0 -895
  308. package/src/endpoints.module.ts +0 -4253
  309. package/src/error.ts +0 -86
  310. package/src/forms/entityForm.ts +0 -142
  311. package/src/index.ts +0 -161
  312. package/src/mixin/UserMixin.ts +0 -4
  313. package/src/types/api-responses.ts +0 -217
  314. package/src/types/entities.ts +0 -58
  315. package/src/types/error-guards.ts +0 -230
  316. package/src/types/index.ts +0 -41
  317. package/src/types/payloads.ts +0 -21
  318. package/src/types/transforms.ts +0 -119
  319. package/src/utils/FileOfflineStorageStrategy.node.ts +0 -50
  320. package/src/utils/FileStorageStrategy.node.ts +0 -63
  321. package/src/utils/MultiServerFileStorageStrategy.node.ts +0 -67
  322. package/src/utils/MultiServerTokenStorageStrategy.ts +0 -139
  323. package/src/utils/OfflineClientManager.ts +0 -216
  324. package/src/utils/OfflineQueueStorageStrategy.ts +0 -47
  325. package/src/utils/TokenStorage.ts +0 -77
  326. package/src/utils/compat.ts +0 -12
  327. package/src/utils/createDefaultMultiServerTokenStorageStrategy.ts +0 -35
  328. package/src/utils/createDefaultOfflineStrategy.ts +0 -34
  329. package/src/utils/createDefaultTokenStorageStrategy.ts +0 -33
  330. package/src/utils/reactive.ts +0 -243
  331. package/src/utils/stream-utils.node.ts +0 -12
package/src/api/User.ts DELETED
@@ -1,1889 +0,0 @@
1
- import { ApiError } from "../error.js";
2
- import { BaseEntity } from "./BaseEntity.js";
3
- import { Notifications } from "./Notifications.js";
4
- import { resolveCostumScope, costumScopeFromEntity, type CostumScope } from "../costum/runtime.js";
5
- import { UserMixin } from "../mixin/UserMixin.js";
6
- import { createSocialTransform } from "../types/transforms.js";
7
-
8
-
9
- import type { Badge } from "./Badge.js";
10
- import type { PaginatorPage, PaginatorState } from "./BaseEntity.js";
11
- import type {
12
- ConnectData,
13
- DisconnectData,
14
- LinkValidateData,
15
- FollowData,
16
- ChangePasswordData,
17
- DeleteAccountData,
18
- GetSubscriptionsAdminData,
19
- GetSubscriptionsData,
20
- GetOrganizationsNoAdminData,
21
- GetOrganizationsAdminData,
22
- GetUserEligiblePlacesData,
23
- GetFriendsAdminData,
24
- DemoteAdminData,
25
- GetNotificationsData,
26
- GetNotificationsCountData,
27
- NotificationUpdateData,
28
- MarkNotificationAsReadData
29
- } from "./EndpointApi.types.js";
30
- import type { Organization } from "./Organization.js";
31
- import type { KnownCostumSlug } from "../costum/types.js";
32
- import type { NotificationItemData } from "./serverDataType/Notification.js";
33
- import type { EntityTypes } from "@/types/entities.js";
34
-
35
- type ApiClient = import("../ApiClient.js").default;
36
- type UserItemNormalized = import("./serverDataType/User.js").UserItemNormalized;
37
-
38
- /**
39
- * Interface représentant le lien entre un utilisateur et une entité parente.
40
- * Utilisée pour typer les données de relation (membre, admin, invitations, etc.)
41
- */
42
- interface ParentLink {
43
- isAdmin?: boolean;
44
- isAdminPending?: boolean;
45
- isInviting?: boolean;
46
- isAdminInviting?: boolean;
47
- toBeValidated?: boolean;
48
- }
49
-
50
- export class User extends BaseEntity<UserItemNormalized> {
51
-
52
- static override entityType = "citoyens" as const;
53
-
54
- static override entityTag = "User";
55
-
56
- static override SCHEMA_CONSTANTS: string[] = [
57
- "UPDATE_BLOCK_DESCRIPTION",
58
- "UPDATE_BLOCK_INFO",
59
- "UPDATE_BLOCK_SOCIAL",
60
- "UPDATE_BLOCK_LOCALITY",
61
- "UPDATE_BLOCK_SLUG",
62
- "PROFIL_IMAGE"
63
- ];
64
-
65
- static UPDATE_BLOCKS = new Map([
66
- ["UPDATE_BLOCK_DESCRIPTION", "updateDescription"],
67
- ["UPDATE_BLOCK_SOCIAL", "updateSocial"],
68
- ["UPDATE_BLOCK_LOCALITY", "updateLocality"],
69
- ["UPDATE_BLOCK_INFO", "updateInfo"],
70
- ["UPDATE_BLOCK_SLUG", "updateSlug"],
71
- ["PROFIL_IMAGE", "updateImageProfil"]
72
- ] as const);
73
-
74
- override defaultFields: Record<string, any> = {
75
- typeElement: this.getEntityType(),
76
- };
77
-
78
- override removeFields: string[] = [
79
- "typeElement",
80
- ];
81
-
82
- override transforms: Record<string, (val: any, full: any) => any> = {
83
- github: createSocialTransform("github"),
84
- gitlab: createSocialTransform("gitlab"),
85
- facebook: createSocialTransform("facebook"),
86
- twitter: createSocialTransform("twitter"),
87
- instagram: createSocialTransform("instagram"),
88
- diaspora: createSocialTransform("diaspora"),
89
- mastodon: createSocialTransform("mastodon"),
90
- telegram: createSocialTransform("telegram"),
91
- signal: createSocialTransform("signal")
92
- };
93
-
94
- /**
95
- * Crée une instance de `User`.
96
- *
97
- * @param parent
98
- * L'ApiClient, une entité parente, ou tout objet exposant `apiClient`.
99
- * @param {{ id?: string, slug?: string }} data Données initiales (il faut au moins `id` OU `slug`).
100
- * @param {{
101
- * EndpointApi: EndpointApiDep,
102
- * Organization: typeof import("./Organization.js").Organization,
103
- * Project: typeof import("./Project.js").Project,
104
- * Event: typeof import("./Event.js").Event,
105
- * Poi: typeof import("./Poi.js").Poi,
106
- * Badge: typeof import("./Badge.js").Badge,
107
- * News: typeof import("./News.js").News
108
- * }} deps
109
- * @throws {ApiError} Si `EndpointApi` n'est pas injecté, ou si `id`/`slug` manquent,
110
- * ou si une des classes dépendantes n'est pas injectée.
111
- */
112
- constructor(
113
- parent: ApiClient | any,
114
- data: { id?: string; slug?: string },
115
- deps: {
116
- EndpointApi: any;
117
- Organization: typeof import("./Organization.js").Organization;
118
- Project: typeof import("./Project.js").Project;
119
- Event: typeof import("./Event.js").Event;
120
- Poi: typeof import("./Poi.js").Poi;
121
- Badge: typeof import("./Badge.js").Badge;
122
- News: typeof import("./News.js").News;
123
- Comment: typeof import("./Comment.js").Comment;
124
- Answer: typeof import("./Answer.js").Answer;
125
- Form?: typeof import("./Form.js").Form;
126
- Classified?: typeof import("./Classified.js").Classified;
127
- Action?: typeof import("./Action.js").Action;
128
- }
129
- ) {
130
- if(!deps.EndpointApi){
131
- throw new ApiError("EndpointApi class must be injected to avoid circular dependency.", 500);
132
- }
133
- if (!data?.id && !data?.slug) {
134
- throw new ApiError("Vous devez fournir un id ou un slug pour créer un User.", 400);
135
- }
136
-
137
- if (!deps.Organization) throw new ApiError("Organization class must be injected.", 500);
138
- if (!deps.Project) throw new ApiError("Project class must be injected.", 500);
139
- if (!deps.Event) throw new ApiError("Event class must be injected.", 500);
140
- if (!deps.Poi) throw new ApiError("Poi class must be injected.", 500);
141
- if (!deps.Badge) throw new ApiError("Badge class must be injected.", 500);
142
- if (!deps.News) throw new ApiError("News class must be injected.", 500);
143
- if (!deps.Answer) throw new ApiError("Answer class must be injected.", 500);
144
-
145
- super(parent, data, deps);
146
- }
147
-
148
- override get slug() {
149
- return this._draftData.slug || null;
150
- }
151
-
152
- override get isMe() {
153
- return this.isConnected && this.userId === this.id;
154
- }
155
-
156
- get parentIsMe() {
157
- return super.isMe;
158
- }
159
-
160
- get isActingUser() {
161
- return this.parentIsMe && !this.isMe;
162
- }
163
-
164
- /** @returns {"citoyens"} */
165
- override getEntityType(): "citoyens" { return "citoyens"; }
166
-
167
- /**
168
- * Récupère le profil complet de l'utilisateur.
169
- * Si l'utilisateur est connecté, on appelle le endpoint ME_INFO_URL,
170
- * sinon, on peut imaginer appeler un endpoint public.
171
- *
172
- * @returns Le profil complet.
173
- */
174
- override async get(): Promise<Record<string, any>> {
175
- // Tombstone : `meInfoUrl()` est basé sur le token (ignore `this.id`), donc cette
176
- // lecture ne lèverait pas via le garde `!this.id` après un delete — on garde ici.
177
- this._checkNotDeleted();
178
- return this.apiClient.safeCall(async () => {
179
- if (this.isMe) {
180
- const data = await this.endpointApi.meInfoUrl();
181
- this._setData(data as UserItemNormalized, { forceInitialDraftReset: true });
182
- return data;
183
- } else {
184
- const data = await this._getPublicProfile();
185
- this._setData(data as UserItemNormalized, { forceInitialDraftReset: true });
186
- return data;
187
- }
188
- });
189
- }
190
-
191
- /**
192
- * Changer le mot de passe : Permet de changer le mot de passe d'un utilisateur.
193
- * Constant : CHANGE_PASSWORD
194
- * @param data - Données envoyées à l'API
195
- * @returns - Les données de réponse.
196
- * @throws {ApiResponseError} - En cas d'erreur détectée dans la réponse.
197
- * @throws {ApiAuthenticationError} - En cas d'erreur d'authentification.
198
- * @throws {Error} - En cas d'erreur inattendue.
199
- */
200
- async changePassword(data: ChangePasswordData): Promise<unknown> {
201
- return this.callIsMe(() => this.endpointApi.changePassword(data));
202
- }
203
-
204
- /**
205
- * Supprimer un compte : Permet de supprimer un compte utilisateur.
206
- * Constant : DELETE_ACCOUNT
207
- * @param data - Données envoyées à l'API
208
- * @returns - Les données de réponse.
209
- * @throws {ApiResponseError} - En cas d'erreur détectée dans la réponse.
210
- * @throws {ApiAuthenticationError} - En cas d'erreur d'authentification.
211
- * @throws {Error} - En cas d'erreur inattendue.
212
- */
213
- async delete(data: DeleteAccountData): Promise<unknown> {
214
- const result = await this.callIsMe(() => this.endpointApi.deleteAccount(data));
215
-
216
- // Vider les objets réactifs sans casser la réactivité (cf. Action/Answer/News/Comment.delete)
217
- Object.keys(this._draftData).forEach(key => delete this._draftData[key]);
218
- Object.keys(this._serverData).forEach(key => delete (this._serverData as any)[key]);
219
-
220
- // Marquer comme supprimé → toute opération ultérieure lève le tombstone.
221
- this._isDeleted = true;
222
- return result;
223
- }
224
-
225
- /**
226
- * Sauvegarde les modifications de l'utilisateur en appelant les endpoints correspondants.
227
- * Seul l'utilisateur connecté peut se modifier lui-même.
228
- *
229
- * @returns - Données serveur mises à jour si applicable.
230
- * @throws {ApiError} - Si l'utilisateur n'est pas autorisé.
231
- */
232
- override async save(): Promise<Record<string, any>> {
233
- if(!this.isMe){
234
- throw new ApiError("Vous devez être connecté et être l'utilisateur pour sauvegarder.", 401);
235
- }
236
- return await super.save();
237
- }
238
-
239
- override _add = async () => {
240
- throw new ApiError("Vous ne pouvez pas ajouter un utilisateur par ce moyen.", 403);
241
- };
242
-
243
- /**
244
- * Met à jour les blocs modifiés de l'utilisateur via les constantes de schéma définies.
245
- *
246
- * @param payload - Données courantes à comparer et envoyer.
247
- * @returns - Indique s'il y a eu une modification réelle.
248
- * @throws {ApiError} - Si l'utilisateur n'est pas autorisé à effectuer cette action.
249
- */
250
- override _update = async (payload: Record<string, any>): Promise<boolean> => {
251
- if (!this._calledFromSave) {
252
- throw new ApiError("utilisation invalide de _update, utilisez save", 400);
253
- }
254
-
255
- if (payload.id) {
256
- delete payload.id;
257
- }
258
-
259
- let hasChanged = false;
260
-
261
- // Sinon, on fait les updates en blocs
262
- for (const [constant, methodName] of Array.from(User.UPDATE_BLOCKS)) {
263
- const blockData = this._extractChangedFieldsFromSchema(
264
- this.apiClient,
265
- constant,
266
- { ...payload, ...this.defaultFields},
267
- () => this.initialDraftData,
268
- this.removeFields
269
- );
270
- if (blockData && Object.keys(blockData).length > 0) {
271
- await this._invokeBlockMethod(User.UPDATE_BLOCKS, methodName, blockData);
272
- hasChanged = true;
273
- }
274
- }
275
-
276
- return hasChanged;
277
- };
278
-
279
- static override fromServerData(data: any, parent: any, deps: any): User {
280
- const Ctor = this as any;
281
- const instance = new Ctor(parent, data, deps);
282
- if (data && typeof data === "object" && Object.keys(data).length > 0 && typeof instance._setData === "function") {
283
- // Voir BaseEntity.fromServerData : on réconcilie le baseline de diff avec l'état serveur
284
- // pour qu'une entité fraîchement revifiée ne se croie pas « entièrement modifiée ».
285
- instance._setData(data as UserItemNormalized, { forceInitialDraftReset: true });
286
- }
287
- return instance;
288
- }
289
-
290
- /**
291
- * {@inheritDoc BaseEntity#updateSettings}
292
- *
293
- * Mettre à jour les paramètres utilisateur : Mise à jour des paramètres spécifiques d'un utilisateur.
294
- */
295
- override async updateSettings(data: Parameters<BaseEntity<UserItemNormalized>["updateSettings"]>[0]) {
296
- if(!this.isMe){
297
- throw new ApiError("Vous devez être connecté et être l'utilisateur pour mettre à jour les paramètres.", 401);
298
- }
299
- const result = await super.updateSettings(data);
300
- await this.refresh();
301
- return result;
302
- }
303
-
304
- /**
305
- * {@inheritDoc BaseEntity#updateDescription}
306
- *
307
- * Mettre à jour la description d'un élément : Permet de mettre à jour la description courte et complète d'un élément.
308
- */
309
- override async updateDescription(data: Parameters<BaseEntity<UserItemNormalized>["updateDescription"]>[0]) {
310
- if(!this.isMe){
311
- throw new ApiError("Vous devez être connecté et être l'utilisateur pour mettre à jour la description.", 401);
312
- }
313
- return super.updateDescription(data);
314
- }
315
-
316
- /**
317
- * {@inheritDoc BaseEntity#updateInfo}
318
- *
319
- * Mettre à jour les informations d'un élément : Permet de mettre à jour les informations générales d'un élément (nom, contacts, etc.).
320
- */
321
- override async updateInfo(data: Parameters<BaseEntity<UserItemNormalized>["updateInfo"]>[0]) {
322
- if(!this.isMe){
323
- throw new ApiError("Vous devez être connecté et être l'utilisateur pour mettre à jour les informations.", 401);
324
- }
325
- return super.updateInfo(data);
326
- }
327
-
328
- /**
329
- * {@inheritDoc BaseEntity#updateSocial}
330
- *
331
- * Mettre à jour les réseaux sociaux d'un élément : Permet de mettre à jour les liens vers les réseaux sociaux d'un élément.
332
- */
333
- override async updateSocial(data: Parameters<BaseEntity<UserItemNormalized>["updateSocial"]>[0]) {
334
- if(!this.isMe){
335
- throw new ApiError("Vous devez être connecté et être l'utilisateur pour mettre à jour les réseaux sociaux.", 401);
336
- }
337
- return super.updateSocial(data);
338
- }
339
-
340
- /**
341
- * {@inheritDoc BaseEntity#updateLocality}
342
- *
343
- * Mettre à jour les localités d'un élément : Permet de mettre à jour l'adresse et les informations géographiques d'un élément.
344
- */
345
- override async updateLocality(data: Parameters<BaseEntity<UserItemNormalized>["updateLocality"]>[0]) {
346
- if(!this.isMe){
347
- throw new ApiError("Vous devez être connecté et être l'utilisateur pour mettre à jour la localité.", 401);
348
- }
349
- return super.updateLocality(data);
350
- }
351
-
352
- /**
353
- * {@inheritDoc BaseEntity#updateSlug}
354
- *
355
- * Mettre à jour le slug d'un élément : Permet de mettre à jour le slug pour une URL simplifiée.
356
- */
357
- override async updateSlug(data: Parameters<BaseEntity<UserItemNormalized>["updateSlug"]>[0]) {
358
- if(!this.isMe){
359
- throw new ApiError("Vous devez être connecté et être l'utilisateur pour mettre à jour le slug.", 401);
360
- }
361
- return super.updateSlug(data);
362
- }
363
-
364
- /**
365
- * {@inheritDoc BaseEntity#updateImageProfil}
366
- *
367
- * Mettre à jour l'image de profil : Permet de mettre à jour l'image de profil d'un utilisateur ou d'une entité.
368
- */
369
- override async updateImageProfil(data: Parameters<BaseEntity<UserItemNormalized>["updateImageProfil"]>[0]) {
370
- if(!this.isMe){
371
- throw new ApiError("Vous devez être connecté et être l'utilisateur pour mettre à jour l'image de profil.", 401);
372
- }
373
- return super.updateImageProfil(data);
374
- }
375
-
376
-
377
- /**
378
- * {@inheritDoc BaseEntity#updateImageBanner}
379
- *
380
- * Mettre à jour l'image de bannière : Permet de mettre à jour l'image de bannière d'un utilisateur ou d'une entité.
381
- */
382
- override async updateImageBanner(data: Parameters<BaseEntity<UserItemNormalized>["updateImageBanner"]>[0]) {
383
- if(!this.isMe){
384
- throw new ApiError("Vous devez être connecté et être l'utilisateur pour mettre à jour l'image de bannière.", 401);
385
- }
386
- return super.updateImageBanner(data);
387
- }
388
-
389
-
390
- /**
391
- * {@inheritDoc BaseEntity#getOrganizations}
392
- * @param [data]
393
- *
394
- * Récupérer les organisations d'un utilisateur : Récupère la liste des organisations auxquelles l'utilisateur appartient.
395
- * Constant : GET_ORGANIZATIONS_ADMIN | GET_ORGANIZATIONS_NO_ADMIN
396
- */
397
- override async getOrganizations(
398
- data: Partial<GetOrganizationsAdminData | GetOrganizationsNoAdminData> = {},
399
- options?: { restoredState?: PaginatorState }
400
- ) {
401
- data.searchType = this._getDefaultFromEndpoint("GET_ORGANIZATIONS_NO_ADMIN", "searchType") as GetOrganizationsNoAdminData["searchType"];
402
- // data.searchBy = "ALL";
403
-
404
- const paginator = this._createPaginatorEngine({
405
- initialData: data,
406
- methodName: "getOrganizations",
407
- restoredState: options?.restoredState,
408
- finalizer: async (finalData) => {
409
- delete finalData?.pathParams;
410
-
411
- const fetchFn = this.isMe
412
- ? () => this.callIsMe(() => this.endpointApi.getOrganizationsAdmin(finalData))
413
- : () => this.endpointApi.getOrganizationsNoAdmin(finalData);
414
-
415
- if (!this.isMe && !finalData.filters) {
416
- finalData.filters = {
417
- [`links.members.${this.id}`]: { "$exists": true },
418
- [`links.members.${this.id}.toBeValidated`]: { "$exists": false },
419
- [`links.members.${this.id}.isInviting`]: { "$exists": false }
420
- };
421
- }
422
-
423
- return fetchFn();
424
- }
425
- });
426
-
427
- return paginator.next() as Promise<PaginatorPage<Organization>>;
428
- }
429
-
430
- /**
431
- * Récupère les lieux (organizations) memberOf de l'utilisateur, filtrés
432
- * côté serveur par des filters arbitraires (tags, source.key, etc.) et
433
- * un flag `notSourceKey` configurables.
434
- *
435
- * Mirroir de `getOrganizations` mais permet de passer dynamiquement les
436
- * filtres du finder du formulaire (vue collaborative coform/place), pour
437
- * que la pagination soit correcte (filtrage côté serveur, pas côté client).
438
- *
439
- * Constant : GET_USER_ELIGIBLE_PLACES
440
- */
441
- async getEligiblePlaces(
442
- data: Partial<GetUserEligiblePlacesData> = {},
443
- options?: { restoredState?: PaginatorState }
444
- ) {
445
- // Force le default `searchType` ([NGO, Cooperative, ...]) — le PHP ne
446
- // retourne rien si le tableau est vide / absent. Pattern aligné avec
447
- // `getOrganizations`.
448
- data.searchType = this._getDefaultFromEndpoint(
449
- "GET_USER_ELIGIBLE_PLACES",
450
- "searchType"
451
- ) as GetUserEligiblePlacesData["searchType"];
452
-
453
- const paginator = this._createPaginatorEngine({
454
- initialData: data,
455
- methodName: "getEligiblePlaces",
456
- restoredState: options?.restoredState,
457
- finalizer: async (finalData) => {
458
- delete finalData?.pathParams;
459
-
460
- // Auto-merge du filtre memberOf par défaut si l'appelant n'en pousse
461
- // que des filters "métier" (tags, etc.). On garde la sémantique
462
- // "uniquement les orgs où l'user est membre validé".
463
- const userFilters = finalData.filters ?? {};
464
- const memberOfDefaults: Record<string, unknown> = {
465
- [`links.members.${this.id}`]: { "$exists": true },
466
- [`links.members.${this.id}.toBeValidated`]: { "$exists": false },
467
- [`links.members.${this.id}.isInviting`]: { "$exists": false },
468
- };
469
- finalData.filters = { ...memberOfDefaults, ...userFilters };
470
-
471
- // Cast nécessaire : callIsMe est typé Promise<unknown>, mais l'endpoint
472
- // retourne bien la shape `{ results, count }` attendue par le finalizer.
473
- // Pattern aligné avec getOrganizations (cf. la branche ternaire au-dessus).
474
- return this.callIsMe(() => this.endpointApi.getUserEligiblePlaces(finalData)) as ReturnType<
475
- typeof this.endpointApi.getUserEligiblePlaces
476
- >;
477
- },
478
- });
479
-
480
- return paginator.next() as Promise<PaginatorPage<Organization>>;
481
- }
482
-
483
- /**
484
- * {@inheritDoc BaseEntity#getProjects}
485
- *
486
- * Récupérer les projets d'un utilisateur : Récupère la liste des projets auxquels l'utilisateur contribue.
487
- */
488
- override async getProjects(data: Parameters<BaseEntity<UserItemNormalized>["getProjects"]>[0] = {}) {
489
- return super.getProjects(data);
490
- }
491
-
492
- /**
493
- * {@inheritDoc BaseEntity#getEvents}
494
- *
495
- * Récupère les événements d'un utilisateur.
496
- */
497
- override async getEvents(data: Parameters<BaseEntity<UserItemNormalized>["getEvents"]>[0] = {}) {
498
- return super.getEvents(data);
499
- }
500
-
501
- /**
502
- * {@inheritDoc BaseEntity#getPois}
503
- *
504
- * Récupérer les POIs
505
- */
506
- override async getPois(data: Parameters<BaseEntity<UserItemNormalized>["getPois"]>[0] = {}) {
507
- return super.getPois(data);
508
- }
509
-
510
- /**
511
- * {@inheritDoc BaseEntity#getNews}
512
- *
513
- * Récupérer les actualités : Récupère la liste des actualités liées à l'utilisateur.
514
- */
515
- override async getNews(data: Parameters<BaseEntity<UserItemNormalized>["getNews"]>[0] = {}) {
516
- return super.getNews(data);
517
- }
518
-
519
- /**
520
- * Récupérer les amis administrables : Récupère les amis administrée par l’utilisateur.
521
- * Constant : GET_FRIENDS_ADMIN
522
- * question : qui peut voir la liste d'amis, seulement l'utilisateur connecté ? ou tous les utilisateurs connectés ?
523
- * actuellement, c'est tous les utilisateurs connectés
524
- */
525
- async getFriends(
526
- data: Partial<GetFriendsAdminData> = {},
527
- options?: { restoredState?: PaginatorState }
528
- ): Promise<PaginatorPage<User>> {
529
- data.searchType = this._getDefaultFromEndpoint("GET_FRIENDS_ADMIN", "searchType") as GetFriendsAdminData["searchType"];
530
- // data.searchBy = "ALL";
531
-
532
- const paginator = this._createPaginatorEngine({
533
- initialData: data,
534
- methodName: "getFriends",
535
- restoredState: options?.restoredState,
536
- finalizer: async (finalData) => {
537
-
538
- delete finalData?.pathParams;
539
-
540
- if (!this.isMe && this.id){
541
- // is not me add id
542
- finalData.pathParams = { id: this.id };
543
- }
544
-
545
- return this.endpointApi.getFriendsAdmin(finalData);
546
- }
547
- });
548
-
549
- return paginator.next() as Promise<PaginatorPage<User>>;
550
- }
551
-
552
- /**
553
- * Récupérer les suivis
554
- * Constant : GET_SUBSCRIPTIONS / GET_SUBSCRIPTIONS_ADMIN
555
- */
556
- async getSubscriptions(
557
- data: Partial<GetSubscriptionsAdminData | GetSubscriptionsData> = {},
558
- options?: { restoredState?: PaginatorState }
559
- ): Promise<PaginatorPage<BaseEntity<any>>> {
560
- data.searchType = this._getDefaultFromEndpoint("GET_SUBSCRIPTIONS", "searchType") as GetSubscriptionsAdminData["searchType"];
561
- // data.searchBy = "ALL";
562
-
563
- const paginator = this._createPaginatorEngine({
564
- initialData: data,
565
- methodName: "getSubscriptions",
566
- restoredState: options?.restoredState,
567
- finalizer: async (finalData) => {
568
-
569
- delete finalData?.pathParams;
570
-
571
- const fetchFn = this.isMe
572
- ? () => this.callIsMe(() => this.endpointApi.getSubscriptionsAdmin(finalData))
573
- : () => this.endpointApi.getSubscriptions(finalData);
574
-
575
- if (!this.isMe && !finalData.filters) {
576
- finalData.filters = {
577
- [`links.followers.${this.id}`]: { "$exists": true },
578
- };
579
- }
580
-
581
- return fetchFn();
582
- }
583
- });
584
-
585
- return paginator.next() as Promise<PaginatorPage<BaseEntity<any>>>;
586
- }
587
-
588
- /**
589
- * {@inheritDoc BaseEntity#getSubscribers}
590
- *
591
- * Récupérer les abonnés
592
- */
593
- override async getSubscribers(data: Parameters<BaseEntity<UserItemNormalized>["getSubscribers"]>[0] = {}) {
594
- return super.getSubscribers(data);
595
- }
596
-
597
- /**
598
- * Obtenir la liste des événements
599
- * Constant : GET_EVENTS
600
- * TODO : il n'est pas fait encore pour GET_EVENTS_ADMIN / GET_EVENTS_NO_ADMIN comme les autres dans endpointApi
601
- */
602
-
603
- /**
604
- * {@inheritDoc BaseEntity#getBadgesIssuer}
605
- *
606
- * Liste des badges créés par l'utilisateur
607
- */
608
- override async getBadgesIssuer(data: Parameters<BaseEntity<UserItemNormalized>["getBadgesIssuer"]>[0] = {}) {
609
- return super.getBadgesIssuer(data);
610
- }
611
-
612
- /**
613
- * Liste des badges associés à l'utilisateur
614
- *
615
- * TODO : documenté le fonctionnement et sont utilisation avec un exemple
616
- */
617
- async getBadges(filter: Record<string, any> = {}): Promise<Badge[]> {
618
- /*
619
- "badges": {
620
- "627df5663a0fae60e57f4a31": {
621
- "attenteRecepteur": true,
622
- "expiredOn": false,
623
- "isParcours": "false",
624
- "issuedOn": "2023-10-02T14:00:00.000Z",
625
- "name": "CODEV",
626
- "show": "true",
627
- },
628
- },
629
- */
630
-
631
- const filteredBadges = await this.linkEntitiesFromServerData("badges", filter);
632
- // voir si je les met dans objet { count, results }
633
- return filteredBadges;
634
- }
635
-
636
- /**
637
- * {@inheritDoc BaseEntity#getGallery}
638
- *
639
- * Récupérer la galerie de l'utilisateur
640
- */
641
- override async getGallery(data: Parameters<BaseEntity<UserItemNormalized>["getGallery"]>[0] = {}) {
642
- return super.getGallery(data);
643
- }
644
-
645
- async user(userData: { id?: string, slug?: string }): Promise<User> {
646
- if(!this.isMe){
647
- throw new ApiError("Vous devez être connecté et être l'utilisateur", 401);
648
- }
649
- if (!userData.id && !userData.slug) {
650
- throw new ApiError("Vous devez fournir un id ou un slug pour créer un User.", 400);
651
- }
652
- const requiredDeps = this.deps as any;
653
- const user = new User(this, userData, requiredDeps as any);
654
- await user.get();
655
- return user;
656
- }
657
-
658
- /**
659
- * {@inheritDoc BaseEntity#organization}
660
- *
661
- * Crée une instance d'organisation et récupère son profil si nécessaire.
662
- */
663
- override async organization(organizationData: Parameters<BaseEntity<UserItemNormalized>["organization"]>[0] = {}) {
664
- if(!("id" in organizationData) && !("slug" in organizationData) && !this.isMe){
665
- throw new ApiError("Vous devez être connecté et être l'utilisateur pour créer une organisation.", 401);
666
- }
667
- return super.organization(organizationData);
668
- }
669
-
670
- /**
671
- * {@inheritDoc BaseEntity#project}
672
- *
673
- * Crée une instance de projet et récupère son profil si nécessaire.
674
- */
675
- override async project(projectData: Parameters<BaseEntity<UserItemNormalized>["project"]>[0] = {}) {
676
- if(!("id" in projectData) && !("slug" in projectData) && !this.isMe){
677
- throw new ApiError("Vous devez être connecté et être l'utilisateur pour créer un projet.", 401);
678
- }
679
- return super.project(projectData);
680
- }
681
-
682
- /**
683
- * Bloqué sur User : un Form nécessite un costumContext (slug d'Organization/Project)
684
- * que User n'a pas. Utilisez `user.organization({slug}).then(o => o.form({id}))`
685
- * ou `user.project({slug}).then(p => p.form({id}))` pour la chaîne propagée.
686
- */
687
- override async form(): Promise<never> {
688
- throw new ApiError(`form n'existe pas dans ${this.constructor.name}`, 501);
689
- }
690
-
691
- /**
692
- * {@inheritDoc BaseEntity#news}
693
- *
694
- * Crée une instance de news et la récupère si nécessaire.
695
- */
696
- override async news(newsData: Parameters<BaseEntity<UserItemNormalized>["news"]>[0] = {}) {
697
- if(!("id" in newsData) && !this.isMe){
698
- throw new ApiError("Vous devez être connecté et être l'utilisateur pour créer une actualité.", 401);
699
- }
700
- return super.news(newsData);
701
- }
702
-
703
- /**
704
- * {@inheritDoc BaseEntity#poi}
705
- *
706
- * Crée une instance de POI et la récupère si nécessaire.
707
- */
708
- override async poi(poiData: Parameters<BaseEntity<UserItemNormalized>["poi"]>[0] = {}) {
709
- if(!("id" in poiData) && !("slug" in poiData) && !this.isMe){
710
- throw new ApiError("Vous devez être connecté et être l'utilisateur pour créer un POI.", 401);
711
- }
712
- return super.poi(poiData);
713
- }
714
-
715
- /**
716
- * {@inheritDoc BaseEntity#event}
717
- *
718
- * Crée une instance d'événement et la récupère si nécessaire.
719
- */
720
- override async event(eventData: Parameters<BaseEntity<UserItemNormalized>["event"]>[0] = {}) {
721
- if(!("id" in eventData) && !("slug" in eventData) && !this.isMe){
722
- throw new ApiError("Vous devez être connecté et être l'utilisateur pour créer un événement.", 401);
723
- }
724
- return super.event(eventData);
725
- }
726
-
727
- /**
728
- * Ouvre un *scope costum* : les entités créées via le scope (`poi`/`organization`/`project`/`event`)
729
- * portent le contexte costum (costumSlug + champs costum validés par le binding du costum à `element/save`).
730
- *
731
- * @example
732
- * const tl = await me.costum("reseauTierslieux");
733
- * const org = await tl.organization({ name: "Mon TL", manageModel: "Association", buildingSurfaceArea: "120" });
734
- * await org.save();
735
- *
736
- * Deux formes :
737
- * - `me.costum(slug)` : slug d'un costum du registry (autocomplété) OU n'importe quel slug (`string`) —
738
- * le backend reste l'autorité de validité (`found`). Slug hors registry sans champ → inc1b (résolution live).
739
- * - `me.costum(entity)` : ouvre un scope FIELDLESS depuis une entité DÉJÀ CHARGÉE (org/projet/event qui porte
740
- * un costum), zéro fetch — `source.key` = slug de l'entité. Cas « créer sous ce site nu ».
741
- *
742
- * @param arg - slug (`KnownCostumSlug | string`) OU une entité chargée porteuse d'un costum.
743
- */
744
- async costum(slug: KnownCostumSlug | (string & {})): Promise<CostumScope>;
745
- async costum(entity: BaseEntity): Promise<CostumScope>;
746
- async costum(arg: KnownCostumSlug | (string & {}) | BaseEntity): Promise<CostumScope> {
747
- if (typeof arg === "string") return resolveCostumScope(this, arg);
748
- return costumScopeFromEntity(this, arg);
749
- }
750
-
751
- /**
752
- * {@inheritDoc BaseEntity#badge}
753
- *
754
- * Crée une instance de badge et la récupère si nécessaire.
755
- */
756
- override async badge(badgeData: Parameters<BaseEntity<UserItemNormalized>["badge"]>[0] = {}) {
757
- if(!("id" in badgeData) && !this.isMe){
758
- throw new ApiError("Vous devez être connecté et être l'utilisateur pour créer un badge.", 401);
759
- }
760
- return super.badge(badgeData);
761
- }
762
-
763
- /**
764
- * ───────────────────────────────
765
- * Lien utilisateur ↔ utilisateur (ami)
766
- * (demander, valider, se retirer)
767
- * ───────────────────────────────
768
- */
769
-
770
- /**
771
- * Envoie une demande d'amitié à cet utilisateur.
772
- * L'utilisateur ciblé devra valider la demande pour établir la relation.
773
- *
774
- * @returns - Résultat de la requête.
775
- * @throws {ApiError} - Si l'utilisateur n'est pas connecté ou si l'action est interdite.
776
- */
777
- async sendFriendRequest(): Promise<unknown> {
778
- if (!this.isActingUser || !this.userId) {
779
- throw new ApiError("Vous devez être connecté pour envoyer une demande d'amis.", 401);
780
- }
781
-
782
- this._checkLinkableEntity();
783
- if (!this.id) {
784
- throw new ApiError(`${this.constructor.name} non enregistrée.`, 404);
785
- }
786
-
787
- const { connectTypeConnect } = this._getLinkMeta();
788
- const userLink = this._getLinkFromConnectedUser();
789
-
790
- if (!userLink) {
791
- const data: ConnectData = {
792
- childId: this.userId,
793
- childType: "citoyens",
794
- parentType: this.getEntityType(),
795
- parentId: this.id,
796
- connectType: connectTypeConnect
797
- };
798
- const retour = await this.endpointApi.connect(data);
799
- // TODO : reflechier au moyen de remplir parent.serverData et this.serverData avec les data de retour pour eviter un refresh
800
- await this.userContext?.refresh();
801
- return retour;
802
- }
803
-
804
- if (userLink.isInviting && userLink.invitorId === this.id) {
805
- return this.acceptFriendRequest();
806
- }
807
-
808
- if (userLink.isInviting && userLink.invitorId === this.userId) {
809
- throw new ApiError("Vous avez déjà envoyé une demande d'amis à cet utilisateur.", 403);
810
- }
811
-
812
- throw new ApiError("Vous êtes déjà connecté à cette entité.", 409);
813
- }
814
-
815
- /**
816
- * Accepte une demande d'amitié envoyée par cet utilisateur.
817
- * Cette action établit un lien entre les deux utilisateurs.
818
- *
819
- * @returns - Résultat de la validation du lien.
820
- * @throws {ApiError} - Si aucune invitation n'est en attente ou si l'action est interdite.
821
- */
822
- async acceptFriendRequest(): Promise<unknown> {
823
- if (!this.isActingUser || !this.userId) {
824
- throw new ApiError("Vous devez être connecté pour accepter une demande d'amitié.", 401);
825
- }
826
-
827
- this._checkLinkableEntity();
828
- if (!this.id) {
829
- throw new ApiError(`${this.constructor.name} non enregistrée.`, 404);
830
- }
831
-
832
- const userLink = this._getLinkFromConnectedUser();
833
-
834
- if (userLink?.isInviting && userLink.invitorId === this.id) {
835
- const data: LinkValidateData = {
836
- childId: this.userId,
837
- childType: "citoyens",
838
- parentType: this.getEntityType(),
839
- parentId: this.id,
840
- linkOption: "isInviting"
841
- };
842
- const retour = await this.endpointApi.linkValidate(data);
843
- // TODO : reflechier au moyen de remplir parent.serverData et this.serverData avec les data de retour pour eviter un refresh
844
- await this.userContext?.refresh();
845
- return retour;
846
- }
847
-
848
- throw new ApiError("Vous n'avez pas d'invitation à valider.", 404);
849
- }
850
-
851
- /**
852
- * Supprime la relation d'amitié avec cet utilisateur.
853
- * Cette action annule tout lien existant entre les deux profils.
854
- *
855
- * @returns - Résultat de la suppression.
856
- * @throws {ApiError} - Si aucune relation n'existe.
857
- */
858
- async removeFriend(): Promise<unknown> {
859
- if (!this.isActingUser || !this.userId) {
860
- throw new ApiError("Vous devez être connecté pour supprimer un ami.", 401);
861
- }
862
-
863
- this._checkLinkableEntity();
864
- if (!this.id) {
865
- throw new ApiError(`${this.constructor.name} non enregistrée.`, 404);
866
- }
867
-
868
- const { connectTypeDisconnect } = this._getLinkMeta();
869
- const userLink = this._getLinkFromConnectedUser();
870
-
871
- if (!userLink) {
872
- throw new ApiError("Vous n'êtes pas connecté à cette entité.", 404);
873
- }
874
- const data: DisconnectData = {
875
- childId: this.userId,
876
- childType: "citoyens",
877
- parentType: this.getEntityType(),
878
- parentId: this.id,
879
- connectType: connectTypeDisconnect
880
- };
881
- const retour = await this.endpointApi.disconnect(data);
882
- // TODO : reflechier au moyen de remplir parent.serverData et this.serverData avec les data de retour pour eviter un refresh
883
- await this.userContext?.refresh();
884
- return retour;
885
- }
886
-
887
-
888
- override async requestToJoin(): Promise<never> {
889
- throw new ApiError("l'utilisation de requestToJoin n'est pas autorisée sur un utilisateur.", 403);
890
- }
891
-
892
- override async requestToJoinAdmin(): Promise<never> {
893
- throw new ApiError("l'utilisation de requestToJoinAdmin n'est pas autorisée sur un utilisateur.", 403);
894
- }
895
-
896
- override async acceptInvitation(): Promise<never> {
897
- throw new ApiError("l'utilisation de acceptInvitation n'est pas autorisée sur un utilisateur.", 403);
898
- }
899
-
900
- override async leave(): Promise<never> {
901
- throw new ApiError("l'utilisation de leave n'est pas autorisée sur un utilisateur.", 403);
902
- }
903
-
904
- /**
905
- * Suivre un utilisateur
906
- * Cette action permet à l'utilisateur connecté de suivre un autre utilisateur.
907
- * Elle nécessite que l'utilisateur soit connecté.
908
- *
909
- * @returns - Résultat de la requête.
910
- * @throws {ApiError} - Si l'utilisateur n'est pas connecté ou si l'action est interdite.
911
- */
912
- override async follow(): Promise<unknown> {
913
- if (!this.isActingUser || !this.userId) {
914
- throw new ApiError("Vous devez être connecté pour suivre un utilisateur.", 401);
915
- }
916
- if (!this.id) {
917
- throw new ApiError(`${this.constructor.name} non enregistrée.`, 404);
918
- }
919
-
920
- const userLink = this.userContext?.serverData?.links?.["follows"]?.[this.id] || null;
921
-
922
- if (!userLink) {
923
- const data: FollowData = {
924
- childId: this.userId,
925
- childType: "citoyens",
926
- parentType: this.getEntityType(),
927
- parentId: this.id
928
- };
929
- const retour = await this.endpointApi.follow(data);
930
- // TODO : reflechier au moyen de remplir parent.serverData et this.serverData avec les data de retour pour eviter un refresh
931
- await this.userContext?.refresh();
932
- return retour;
933
- }
934
-
935
- throw new ApiError("Vous êtes déjà abonné à cet utilisateur.", 409);
936
- }
937
-
938
- /**
939
- * Se désabonner d'un utilisateur
940
- * Cette action permet à l'utilisateur connecté de se désabonner d'un autre utilisateur.
941
- * Elle nécessite que l'utilisateur soit connecté.
942
- *
943
- * @returns - Résultat de la requête.
944
- * @throws {ApiError} - Si l'utilisateur n'est pas connecté ou si l'action est interdite.
945
- */
946
- override async unfollow(): Promise<unknown> {
947
- if (!this.isActingUser || !this.userId) {
948
- throw new ApiError("Vous devez être connecté pour vous désabonner d'un utilisateur.", 401);
949
- }
950
- if (!this.id) {
951
- throw new ApiError(`${this.constructor.name} non enregistrée.`, 404);
952
- }
953
-
954
- const userLink = this.userContext?.serverData?.links?.["follows"]?.[this.id] || null;
955
-
956
- if (userLink) {
957
- const data: DisconnectData = {
958
- childId: this.userId,
959
- childType: "citoyens",
960
- parentType: this.getEntityType(),
961
- parentId: this.id,
962
- connectType: "followers"
963
- };
964
- const retour = await this.endpointApi.disconnect(data);
965
- // TODO : reflechir au moyen de remplir parent.serverData et this.serverData avec les data de retour pour eviter un refresh
966
- await this.userContext?.refresh();
967
- return retour;
968
- }
969
-
970
- throw new ApiError("Vous n'êtes pas abonné à cet utilisateur.", 404);
971
- }
972
-
973
- /**
974
- * Vérifie si l'utilisateur connecté est ami avec cet utilisateur.
975
- * @param options - Options de vérification.
976
- * @param options.silent - Si `true`, retourne `false` au lieu de lever une exception. Par défaut `true`.
977
- * @returns - True si l'utilisateur connecté est ami, sinon false.
978
- * @throws {ApiError} - Si l'utilisateur n'est pas connecté.
979
- */
980
- isFriend(options?: { silent?: boolean }): boolean {
981
- const silent = options?.silent ?? true;
982
- try {
983
- if (!this.isActingUser) {
984
- throw new ApiError("Vous devez être connecté pour vérifier si vous êtes ami.", 401);
985
- }
986
- } catch (e) {
987
- if (silent) return false;
988
- throw e;
989
- }
990
- this._assertEntityType("citoyens");
991
- const userLink = this._getLinkFromConnectedUser();
992
- if(!userLink) return false;
993
- return this._validateUserLink(userLink);
994
- }
995
-
996
- /**
997
- * Vérifie si l'utilisateur suit l'entité.
998
- * @param options - Options de vérification.
999
- * @param options.silent - Si `true`, retourne `false` au lieu de lever une exception. Par défaut `true`.
1000
- * @returns - `true` si l'utilisateur suit l'entité, `false` sinon.
1001
- * @throws {ApiError}
1002
- */
1003
- override isFollower(options?: { silent?: boolean }): boolean {
1004
- const silent = options?.silent ?? true;
1005
- try {
1006
- if (!this.isActingUser) {
1007
- throw new ApiError("Vous devez être connecté pour vérifier si il vous suit.", 401);
1008
- }
1009
- if (!this.id) {
1010
- throw new ApiError(`${this.constructor.name} non enregistrée.`, 404);
1011
- }
1012
- } catch (e) {
1013
- if (silent) return false;
1014
- throw e;
1015
- }
1016
- this._assertEntityType("citoyens");
1017
- return this._isLinked("followers");
1018
- }
1019
-
1020
- /**
1021
- * Vérifie si l'utilisateur est abonné à l'entité.
1022
- * @param options - Options de vérification.
1023
- * @param options.silent - Si `true`, retourne `false` au lieu de lever une exception. Par défaut `true`.
1024
- * @returns - `true` si l'utilisateur est abonné, `false` sinon.
1025
- * @throws {ApiError}
1026
- */
1027
- override isFollowing(options?: { silent?: boolean }): boolean {
1028
- const silent = options?.silent ?? true;
1029
- try {
1030
- if (!this.isActingUser) {
1031
- throw new ApiError("Vous devez être connecté pour vérifier si vous le suivez.", 401);
1032
- }
1033
- if (!this.id) {
1034
- throw new ApiError(`${this.constructor.name} non enregistrée.`, 404);
1035
- }
1036
- } catch (e) {
1037
- if (silent) return false;
1038
- throw e;
1039
- }
1040
- this._assertEntityType("citoyens");
1041
- return this._isLinked("follows");
1042
- }
1043
-
1044
- /**
1045
- * Retourne une entité à partir d'un slug.
1046
- * @param slug - Le slug de l'entité à récupérer.
1047
- * @returns L'entité correspondante au slug.
1048
- */
1049
- override async entityBySlug(slug: string): Promise<EntityTypes> {
1050
- if(!this.isMe){
1051
- throw new ApiError("Vous devez être connecté et être l'utilisateur pour créer cet entité.", 401);
1052
- }
1053
- return super.entityBySlug(slug);
1054
- }
1055
-
1056
- // ────────────────────────────────
1057
- // Actions sur l'utilisateur par rapport à l'entité parente
1058
- // ────────────────────────────────
1059
-
1060
- /**
1061
- * Valide les préconditions de base (connexion, entité, parent, type).
1062
- * @private
1063
- * @param methodName - Nom de la méthode appelante (pour les messages d'erreur).
1064
- * @param expectedTypes - Types d'entité parent autorisés.
1065
- * @throws {ApiError} 401 - Si l'utilisateur n'est pas connecté.
1066
- * @throws {ApiError} 404 - Si l'utilisateur n'est pas enregistré.
1067
- * @throws {ApiError} 404 - Si l'entité parente n'est pas enregistrée.
1068
- * @throws {ApiError} 400 - Si le type d'entité parent n'est pas valide.
1069
- */
1070
- private _validateBasePreconditions(methodName: string, expectedTypes: string[]): void {
1071
- if (!this.userId) {
1072
- throw new ApiError(`Vous devez être connecté pour ${methodName}.`, 401);
1073
- }
1074
-
1075
- if (!this.id) {
1076
- throw new ApiError(`${this.constructor.name} non enregistrée.`, 404);
1077
- }
1078
-
1079
- if (!this.parent?.id) {
1080
- throw new ApiError("L'entité parente n'est pas enregistrée.", 404);
1081
- }
1082
-
1083
- if (!expectedTypes.includes(this.parent.getEntityType())) {
1084
- throw new ApiError(`L'entité doit être de type : ${expectedTypes.join(", ")}, reçu : ${this.parent.getEntityType()}`, 400);
1085
- }
1086
- }
1087
-
1088
- /**
1089
- * Valide les préconditions pour les méthodes nécessitant d'être membre.
1090
- * @private
1091
- * @param methodName - Nom de la méthode appelante (pour les messages d'erreur).
1092
- * @param expectedTypes - Types d'entité parent autorisés.
1093
- * @throws {ApiError} 401 - Si l'utilisateur n'est pas connecté.
1094
- * @throws {ApiError} 404 - Si l'utilisateur n'est pas enregistré.
1095
- * @throws {ApiError} 404 - Si l'entité parente n'est pas enregistrée.
1096
- * @throws {ApiError} 401 - Si l'utilisateur connecté n'est pas membre de l'entité parente.
1097
- * @throws {ApiError} 400 - Si le type d'entité parent n'est pas valide.
1098
- */
1099
- private _validateMemberPreconditions(methodName: string, expectedTypes: string[]): void {
1100
- this._validateBasePreconditions(methodName, expectedTypes);
1101
-
1102
- if (!this.parent?.id) {
1103
- throw new ApiError("L'entité parente n'est pas enregistrée.", 404);
1104
- }
1105
-
1106
- // Vérifier que l'utilisateur connecté a le bon rôle selon le type d'entité
1107
- // Pour les events, tout utilisateur connecté peut agir (pas besoin d'être participant)
1108
- const parentType = this.parent.getEntityType();
1109
-
1110
- if (parentType === "events") {
1111
- // Pour les événements, être connecté suffit (déjà vérifié ci-dessus)
1112
- return;
1113
- }
1114
-
1115
- let hasRole = false;
1116
-
1117
- switch (parentType) {
1118
- case "organizations":
1119
- hasRole = this.parent.isMember();
1120
- break;
1121
- case "projects":
1122
- hasRole = this.parent.isContributor();
1123
- break;
1124
- default:
1125
- hasRole = false;
1126
- }
1127
-
1128
- if (!hasRole) {
1129
- const roleNames: Record<string, string> = {
1130
- organizations: "membre",
1131
- projects: "contributeur"
1132
- };
1133
- const roleName = roleNames[parentType] || "membre";
1134
- throw new ApiError(`Vous devez être ${roleName} pour effectuer cette action.`, 401);
1135
- }
1136
- }
1137
-
1138
- /**
1139
- * Valide les préconditions pour les méthodes nécessitant d'être administrateur.
1140
- * @private
1141
- * @param methodName - Nom de la méthode appelante (pour les messages d'erreur).
1142
- * @param expectedTypes - Types d'entité parent autorisés.
1143
- * @throws {ApiError} 401 - Si l'utilisateur n'est pas connecté.
1144
- * @throws {ApiError} 404 - Si l'utilisateur n'est pas enregistré.
1145
- * @throws {ApiError} 404 - Si l'entité parente n'est pas enregistrée.
1146
- * @throws {ApiError} 401 - Si l'utilisateur connecté n'est pas membre de l'entité parente.
1147
- * @throws {ApiError} 401 - Si l'utilisateur connecté n'est pas administrateur de l'entité parente.
1148
- * @throws {ApiError} 400 - Si le type d'entité parent n'est pas valide.
1149
- */
1150
- private _validateAdminPreconditions(methodName: string, expectedTypes: string[]): void {
1151
- this._validateMemberPreconditions(methodName, expectedTypes);
1152
-
1153
- if (!this.parent?.isAdmin()) {
1154
- throw new ApiError("Vous devez être administrateur pour effectuer cette action.", 401);
1155
- }
1156
- }
1157
-
1158
-
1159
- /**
1160
- * Récupère le lien parent pour l'utilisateur actuel.
1161
- *
1162
- * @returns Le lien parent de l'utilisateur ou `null` s'il n'existe pas.
1163
- * @private
1164
- */
1165
- private _getParentLinkForUser(): ParentLink | null {
1166
- const { connectTypeDisconnect } = this.parent!._getLinkMeta();
1167
- const userId = this!.id;
1168
- if (!userId) return null;
1169
- return this.parent?.serverData?.links?.[connectTypeDisconnect]?.[userId] || null;
1170
- }
1171
-
1172
- /**
1173
- * Valide les préconditions et retourne le lien parent.
1174
- *
1175
- * @param methodName - Nom de la méthode appelante (pour les messages d'erreur).
1176
- * @param expectedTypes - Types d'entité parent autorisés.
1177
- * @param options - Options de validation.
1178
- * @param options.silent - Si `true`, retourne `null` au lieu de lever une exception. Par défaut `true`.
1179
- * @returns - Le lien parent de l'utilisateur, ou `null` si validation échoue.
1180
- * @throws {ApiError} - Si `silent` est `false` et que les préconditions ne sont pas remplies.
1181
- * @private
1182
- */
1183
- private _getValidatedParentLink(
1184
- methodName: string,
1185
- expectedTypes: string[],
1186
- options?: { silent?: boolean }
1187
- ): ParentLink | null {
1188
- const silent = options?.silent ?? true;
1189
- try {
1190
- this._validateBasePreconditions(methodName, expectedTypes);
1191
- } catch (e) {
1192
- if (silent) return null;
1193
- throw e;
1194
- }
1195
- return this._getParentLinkForUser();
1196
- }
1197
-
1198
- /**
1199
- * Rafraîchit l'utilisateur et l'entité parente après une action.
1200
- * @private
1201
- */
1202
- private async _refreshWithParent(): Promise<void> {
1203
- await this.refresh();
1204
- if (this.parent) {
1205
- await this.parent.refresh();
1206
- }
1207
- }
1208
-
1209
- /**
1210
- * Vérifie si l'utilisateur est administrateur de l'entité parente.
1211
- *
1212
- * Cette méthode permet de vérifier si un utilisateur possède les droits d'administration
1213
- * sur l'organisation ou le projet parent. Elle est particulièrement utile après avoir
1214
- * récupéré des membres via `getMembers()` pour déterminer leur niveau de permissions.
1215
- *
1216
- * @returns `true` si l'utilisateur est administrateur de l'entité parente, `false` sinon.
1217
- *
1218
- * @throws {ApiError} 401 - Si l'utilisateur n'est pas connecté.
1219
- * @throws {ApiError} 401 - Si l'utilisateur connecté n'est pas administrateur de l'entité parente.
1220
- * @throws {ApiError} 404 - Si l'utilisateur n'est pas enregistré (pas d'ID).
1221
- * @throws {ApiError} 404 - Si l'entité parente n'est pas enregistrée (pas d'ID).
1222
- * @throws {ApiError} 400 - Si l'entité parente n'est pas de type "organizations" ou "projects".
1223
- *
1224
- * @example
1225
- * // Vérifier les admins d'une organisation
1226
- * const org = await me.organization({ slug: "openAtlas" });
1227
- * const members = await org.getMembers();
1228
- *
1229
- * for (const member of members.results) {
1230
- * if (member.getEntityType() === "citoyens") {
1231
- * if (member.isAdmin()) {
1232
- * console.log(`${member.data.name} est administrateur`);
1233
- * }
1234
- * }
1235
- * }
1236
- *
1237
- * @example
1238
- * // Récupérer uniquement les membres admins
1239
- * const org = await me.organization({ slug: "myOrg" });
1240
- * const adminMembers = await org.getMembers({}, { isAdmin: true });
1241
- *
1242
- * // Vérifier chaque admin
1243
- * for (const admin of adminMembers.results) {
1244
- * if (admin.getEntityType() === "citoyens" && admin.isAdmin()) {
1245
- * console.log(`${admin.data.name} a les droits admin`);
1246
- * }
1247
- * }
1248
- */
1249
- override isAdmin(options?: { silent?: boolean }): boolean {
1250
- const parentLink = this._getValidatedParentLink(
1251
- "isAdmin",
1252
- ["organizations", "projects"],
1253
- { silent: options?.silent }
1254
- );
1255
- return this._validateUserLink(parentLink) && parentLink?.isAdmin === true;
1256
- }
1257
-
1258
- /**
1259
- * Vérifie si l'utilisateur est membre de l'organisation parente.
1260
- *
1261
- * Cette méthode permet de vérifier si un utilisateur est membre actif d'une organisation.
1262
- * Elle est utile pour déterminer si un utilisateur a accès aux ressources de l'organisation.
1263
- *
1264
- * @returns `true` si l'utilisateur est membre validé de l'organisation parente, `false` sinon.
1265
- *
1266
- * @throws {ApiError} 401 - Si l'utilisateur n'est pas connecté.
1267
- * @throws {ApiError} 401 - Si l'utilisateur connecté n'est pas administrateur de l'entité parente.
1268
- * @throws {ApiError} 404 - Si l'utilisateur n'est pas enregistré (pas d'ID).
1269
- * @throws {ApiError} 404 - Si l'entité parente n'est pas enregistrée (pas d'ID).
1270
- * @throws {ApiError} 400 - Si l'entité parente n'est pas de type "organizations".
1271
- *
1272
- * @example
1273
- * // Vérifier les membres d'une organisation
1274
- * const org = await me.organization({ slug: "myOrg" });
1275
- * const members = await org.getMembers();
1276
- *
1277
- * for (const member of members.results) {
1278
- * if (member.getEntityType() === "citoyens" && member.isMember()) {
1279
- * console.log(`${member.data.name} est membre de l'organisation`);
1280
- * }
1281
- * }
1282
- */
1283
- override isMember(options?: { silent?: boolean }): boolean {
1284
- const parentLink = this._getValidatedParentLink(
1285
- "isMember",
1286
- ["organizations"],
1287
- { silent: options?.silent }
1288
- );
1289
- return this._validateUserLink(parentLink);
1290
- }
1291
-
1292
- /**
1293
- * Vérifie si l'utilisateur est contributeur du projet parent.
1294
- *
1295
- * Cette méthode permet de vérifier si un utilisateur est contributeur actif d'un projet.
1296
- * Elle est utile pour déterminer si un utilisateur peut participer aux activités du projet.
1297
- *
1298
- * @returns `true` si l'utilisateur est contributeur validé du projet parent, `false` sinon.
1299
- *
1300
- * @throws {ApiError} 401 - Si l'utilisateur n'est pas connecté.
1301
- * @throws {ApiError} 401 - Si l'utilisateur connecté n'est pas administrateur de l'entité parente.
1302
- * @throws {ApiError} 404 - Si l'utilisateur n'est pas enregistré (pas d'ID).
1303
- * @throws {ApiError} 404 - Si l'entité parente n'est pas enregistrée (pas d'ID).
1304
- * @throws {ApiError} 400 - Si l'entité parente n'est pas de type "projects".
1305
- *
1306
- * @example
1307
- * // Vérifier les contributeurs d'un projet
1308
- * const project = await me.project({ slug: "myProject" });
1309
- * const contributors = await project.getContributors();
1310
- *
1311
- * for (const contributor of contributors.results) {
1312
- * if (contributor.getEntityType() === "citoyens" && contributor.isContributor()) {
1313
- * console.log(`${contributor.data.name} est contributeur du projet`);
1314
- * }
1315
- * }
1316
- */
1317
- override isContributor(options?: { silent?: boolean }): boolean {
1318
- const parentLink = this._getValidatedParentLink(
1319
- "isContributor",
1320
- ["projects"],
1321
- { silent: options?.silent }
1322
- );
1323
- return this._validateUserLink(parentLink);
1324
- }
1325
-
1326
- /**
1327
- * Vérifie si l'utilisateur est participant de l'événement parent.
1328
- *
1329
- * Cette méthode permet de vérifier si un utilisateur est inscrit comme participant à un événement.
1330
- * Elle est utile pour déterminer si un utilisateur a confirmé sa participation à l'événement.
1331
- *
1332
- * @returns `true` si l'utilisateur est participant validé de l'événement parent, `false` sinon.
1333
- *
1334
- * @throws {ApiError} 401 - Si l'utilisateur n'est pas connecté.
1335
- * @throws {ApiError} 401 - Si l'utilisateur connecté n'est pas administrateur de l'entité parente.
1336
- * @throws {ApiError} 404 - Si l'utilisateur n'est pas enregistré (pas d'ID).
1337
- * @throws {ApiError} 404 - Si l'entité parente n'est pas enregistrée (pas d'ID).
1338
- * @throws {ApiError} 400 - Si l'entité parente n'est pas de type "events".
1339
- *
1340
- * @example
1341
- * // Vérifier les participants d'un événement
1342
- * const event = await me.event({ slug: "myEvent" });
1343
- * const attendees = await event.getAttendees();
1344
- *
1345
- * for (const attendee of attendees.results) {
1346
- * if (attendee.getEntityType() === "citoyens" && attendee.isAttendee()) {
1347
- * console.log(`${attendee.data.name} participe à l'événement`);
1348
- * }
1349
- * }
1350
- */
1351
- override isAttendee(options?: { silent?: boolean }): boolean {
1352
- const parentLink = this._getValidatedParentLink(
1353
- "isAttendee",
1354
- ["events"],
1355
- { silent: options?.silent }
1356
- );
1357
- return this._validateUserLink(parentLink);
1358
- }
1359
-
1360
- /**
1361
- * Vérifie si l'utilisateur a une invitation en attente d'acceptation pour l'entité parente.
1362
- *
1363
- * Cette méthode vérifie si l'utilisateur a été invité à rejoindre l'organisation, le projet
1364
- * ou l'événement parent, mais n'a pas encore accepté l'invitation (`isInviting: true`).
1365
- *
1366
- * @returns {boolean} `true` si l'utilisateur a une invitation en attente, `false` sinon
1367
- * @throws {ApiError} Si l'utilisateur n'est pas connecté, pas admin du parent, ou si le parent n'est pas défini
1368
- *
1369
- * @example
1370
- * ```typescript
1371
- * const org = await me.organization({ slug: "myOrg" });
1372
- * const members = await org.getMembers();
1373
- * const invitedUser = members.results.find(m => m.isInviting());
1374
- * console.log("Invitation en attente:", invitedUser?.data.name);
1375
- * ```
1376
- */
1377
- override isInviting(options?: { silent?: boolean }): boolean {
1378
- const parentLink = this._getValidatedParentLink(
1379
- "isInviting",
1380
- ["citoyens", "organizations", "projects", "events"],
1381
- { silent: options?.silent }
1382
- );
1383
- if (!parentLink) return false;
1384
- return parentLink.isInviting === true;
1385
- }
1386
-
1387
- /**
1388
- * Vérifie si l'utilisateur connecté a envoyé une invitation d'ami à cet utilisateur.
1389
- *
1390
- * @param options - Options de vérification.
1391
- * @param options.silent - Si `true`, retourne `false` au lieu de lever une exception. Par défaut `true`.
1392
- * @returns - `true` si une invitation d'ami est en attente, `false` sinon.
1393
- */
1394
- isInvitingFriend(options?: { silent?: boolean }): boolean {
1395
- const userLink = this._getValidatedUserLink(
1396
- "isInvitingFriend",
1397
- ["citoyens", "organizations", "projects", "events"],
1398
- { silent: options?.silent }
1399
- );
1400
- if (!userLink) return false;
1401
- return userLink.isInviting === true;
1402
- }
1403
-
1404
- /**
1405
- * Vérifie si l'utilisateur a une invitation en attente avec des droits d'admin.
1406
- *
1407
- * Cette méthode vérifie si l'utilisateur a été invité à rejoindre l'organisation ou le projet
1408
- * parent en tant qu'administrateur, mais n'a pas encore accepté (`isInviting: true` et `isAdmin: true`).
1409
- *
1410
- * @returns {boolean} `true` si l'utilisateur a une invitation admin en attente, `false` sinon
1411
- * @throws {ApiError} Si l'utilisateur n'est pas connecté, pas admin du parent, ou si le parent n'est pas défini
1412
- *
1413
- * @example
1414
- * ```typescript
1415
- * const org = await me.organization({ slug: "myOrg" });
1416
- * const members = await org.getMembers();
1417
- * const invitedAdmin = members.results.find(m => m.isInvitingAdmin());
1418
- * console.log("Invitation admin en attente:", invitedAdmin?.data.name);
1419
- * ```
1420
- */
1421
- override isInvitingAdmin(options?: { silent?: boolean }): boolean {
1422
- const parentLink = this._getValidatedParentLink(
1423
- "isInvitingAdmin",
1424
- ["organizations", "projects"],
1425
- { silent: options?.silent }
1426
- );
1427
- if (!parentLink) return false;
1428
- return (parentLink.isAdminInviting === true || parentLink.isInviting === true) && parentLink.isAdmin === true;
1429
- }
1430
-
1431
- /**
1432
- * Vérifie si l'utilisateur a une demande de promotion admin en attente de validation.
1433
- *
1434
- * Cette méthode vérifie si l'utilisateur a demandé ou a été proposé pour devenir administrateur
1435
- * de l'organisation ou du projet parent, mais la demande n'a pas encore été validée (`isAdminPending: true`).
1436
- *
1437
- * @returns {boolean} `true` si l'utilisateur a une demande admin en attente, `false` sinon
1438
- * @throws {ApiError} Si l'utilisateur n'est pas connecté, pas admin du parent, ou si le parent n'est pas défini
1439
- *
1440
- * @example
1441
- * ```typescript
1442
- * const org = await me.organization({ slug: "myOrg" });
1443
- * const members = await org.getMembers();
1444
- * const pendingAdmin = members.results.find(m => m.isAdminPending());
1445
- * if (pendingAdmin) {
1446
- * await pendingAdmin.validateAdminRequest();
1447
- * }
1448
- * ```
1449
- */
1450
- override isAdminPending(options?: { silent?: boolean }): boolean {
1451
- const parentLink = this._getValidatedParentLink(
1452
- "isAdminPending",
1453
- ["organizations", "projects"],
1454
- { silent: options?.silent }
1455
- );
1456
- if (!parentLink) return false;
1457
- return parentLink.isAdmin === true && parentLink.isAdminPending === true;
1458
- }
1459
-
1460
- /**
1461
- * Vérifie si l'utilisateur a une demande d'adhésion en attente de validation.
1462
- *
1463
- * Cette méthode vérifie si l'utilisateur a demandé à rejoindre l'organisation, le projet
1464
- * ou l'événement parent, mais la demande n'a pas encore été validée par un administrateur (`toBeValidated: true`).
1465
- *
1466
- * @returns {boolean} `true` si l'utilisateur a une demande en attente, `false` sinon
1467
- * @throws {ApiError} Si l'utilisateur n'est pas connecté, pas admin du parent, ou si le parent n'est pas défini
1468
- *
1469
- * @example
1470
- * ```typescript
1471
- * const org = await me.organization({ slug: "myOrg" });
1472
- * const members = await org.getMembers();
1473
- * const pendingUser = members.results.find(m => m.isToBeValidated());
1474
- * if (pendingUser) {
1475
- * await pendingUser.validateMemberRequest();
1476
- * }
1477
- * ```
1478
- */
1479
- override isToBeValidated(options?: { silent?: boolean }): boolean {
1480
- const parentLink = this._getValidatedParentLink(
1481
- "isToBeValidated",
1482
- ["citoyens", "organizations", "projects", "events"],
1483
- { silent: options?.silent }
1484
- );
1485
- if (!parentLink) return false;
1486
- return parentLink.toBeValidated === true;
1487
- }
1488
-
1489
- /**
1490
- * Vérifie si cet utilisateur a envoyé une demande d'ami en attente de validation.
1491
- *
1492
- * @param options - Options de vérification.
1493
- * @param options.silent - Si `true`, retourne `false` au lieu de lever une exception. Par défaut `true`.
1494
- * @returns - `true` si une demande d'ami est en attente de validation, `false` sinon.
1495
- */
1496
- isToBeValidatedFriend(options?: { silent?: boolean }): boolean {
1497
- const userLink = this._getValidatedUserLink(
1498
- "isToBeValidatedFriend",
1499
- ["citoyens", "organizations", "projects", "events"],
1500
- { silent: options?.silent }
1501
- );
1502
- if (!userLink) return false;
1503
- return userLink.toBeValidated === true;
1504
- }
1505
-
1506
- /**
1507
- * Envoie une demande pour rejoindre l'entité parente.
1508
- *
1509
- * Cette méthode permet à un admin de créer une demande de connexion pour un utilisateur
1510
- * vers l'organisation ou le projet parent. Si l'utilisateur n'a pas encore de lien,
1511
- * une demande de connexion est créée.
1512
- *
1513
- * @param {Object} [options] - Options de la méthode
1514
- * @param {boolean} [options.admin=false] - Si true, utilise "admin" comme connectType au lieu du type par défaut (connectTypeConnect)
1515
- * @returns {Promise<unknown>} La réponse de l'API après création de la demande
1516
- * @throws {ApiError} Si l'utilisateur n'est pas connecté, pas admin, ou si le parent n'est pas défini
1517
- * @throws {ApiError} Si l'utilisateur est déjà en attente de validation
1518
- * @throws {ApiError} Si l'utilisateur est déjà connecté à l'entité
1519
- *
1520
- * @example
1521
- * ```typescript
1522
- * // Un admin récupère les membres et envoie une invitation (type par défaut)
1523
- * const org = await me.organization({ slug: "myOrg" });
1524
- * const users = await org.getMembers();
1525
- * const user = users.results[0];
1526
- * await user.sendRequestToJoinParent();
1527
- *
1528
- * // Avec le type admin explicite
1529
- * await user.sendRequestToJoinParent({ admin: true });
1530
- * ```
1531
- */
1532
- async sendRequestToJoinParent({ admin }: { admin: boolean } = { admin: false }): Promise<unknown> {
1533
- // Validation selon le type d'invitation : admin requis pour inviter en tant qu'admin
1534
- if (admin) {
1535
- // Invitation admin : uniquement orga/project, seul un admin peut le faire
1536
- this._validateAdminPreconditions("sendRequestToJoinParent", ["organizations", "projects"]);
1537
- } else {
1538
- // Invitation simple : orga/project/event, membre/contributeur/connecté peut le faire
1539
- this._validateMemberPreconditions("sendRequestToJoinParent", ["organizations", "projects", "events"]);
1540
- }
1541
-
1542
- const { connectTypeConnect } = this.parent!._getLinkMeta();
1543
-
1544
- const parentLink = this._getParentLinkForUser();
1545
-
1546
- // Cas : aucun lien → on demande à se connecter
1547
- if (!parentLink) {
1548
-
1549
- const t = this.parent!.getEntityType();
1550
-
1551
- // Narrow de type pour TypeScript
1552
- const parentType = t as ConnectData["parentType"];
1553
-
1554
- const data: ConnectData = {
1555
- childId: this.id!,
1556
- childType: "citoyens",
1557
- parentType,
1558
- parentId: this.parent!.id!,
1559
- connectType: admin ? "admin" : connectTypeConnect
1560
- };
1561
-
1562
- const retour = await this.callIsConnected(() => this.endpointApi.connect(data));
1563
- await this._refreshWithParent();
1564
- return retour;
1565
- }
1566
-
1567
- // Cas : déjà en attente d'acceptation
1568
- if (parentLink.isInviting || parentLink?.isAdminInviting) {
1569
- throw new ApiError("Une invitation est déjà en attente d'acceptation.", 400);
1570
- }
1571
-
1572
- // Cas par défaut : rien à faire
1573
- throw new ApiError("Vous êtes déjà connecté à cette entité.", 400);
1574
- }
1575
-
1576
- /**
1577
- * Valide une demande de membre en attente de validation.
1578
- *
1579
- * Cette méthode permet à un admin de valider une demande d'adhésion d'un utilisateur
1580
- * qui est en attente (`toBeValidated: true`). Après validation, l'utilisateur devient
1581
- * membre actif de l'entité parente.
1582
- *
1583
- * @returns {Promise<unknown>} La réponse de l'API après validation
1584
- * @throws {ApiError} Si l'utilisateur n'est pas connecté, pas admin, ou si le parent n'est pas défini
1585
- * @throws {ApiError} Si l'utilisateur n'a pas de demande en attente
1586
- *
1587
- * @example
1588
- * ```typescript
1589
- * // Un admin valide une demande en attente
1590
- * const org = await me.organization({ slug: "myOrg" });
1591
- * const members = await org.getMembers();
1592
- * const pendingUser = members.results.find(u => u.serverData.links?.memberOf?.[org.id]?.toBeValidated);
1593
- * await pendingUser.validateMemberRequest();
1594
- * ```
1595
- */
1596
- async validateMemberRequest(): Promise<unknown> {
1597
- this._validateAdminPreconditions("validateMemberRequest", ["organizations", "projects"]);
1598
-
1599
- const parentLink = this._getParentLinkForUser();
1600
-
1601
- if (!parentLink?.toBeValidated) {
1602
- throw new ApiError("Cet utilisateur n'a pas de demande en attente de validation.", 400);
1603
- }
1604
-
1605
- const t = this.parent!.getEntityType();
1606
-
1607
- const parentType = t as LinkValidateData["parentType"];
1608
-
1609
- const data: LinkValidateData = {
1610
- childId: this.id!,
1611
- childType: "citoyens",
1612
- parentType,
1613
- parentId: this.parent!.id!,
1614
- linkOption: "toBeValidated"
1615
- };
1616
-
1617
- const retour = await this.callIsConnected(() => this.endpointApi.linkValidate(data));
1618
- await this._refreshWithParent();
1619
- return retour;
1620
- }
1621
-
1622
- /**
1623
- * Valide une demande d'admin en attente.
1624
- *
1625
- * Cette méthode permet à un admin de valider une demande de promotion au statut d'admin
1626
- * pour un utilisateur qui est en attente (`isAdminPending: true`). Après validation,
1627
- * l'utilisateur devient admin de l'entité parente.
1628
- *
1629
- * @returns {Promise<unknown>} La réponse de l'API après validation
1630
- * @throws {ApiError} Si l'utilisateur n'est pas connecté, pas admin, ou si le parent n'est pas défini
1631
- * @throws {ApiError} Si l'utilisateur n'a pas de demande d'admin en attente
1632
- *
1633
- * @example
1634
- * ```typescript
1635
- * // Un admin valide une demande de promotion à admin
1636
- * const org = await me.organization({ slug: "myOrg" });
1637
- * const members = await org.getMembers();
1638
- * const pendingAdmin = members.results.find(u => u.serverData.links?.memberOf?.[org.id]?.isAdminPending);
1639
- * await pendingAdmin.validateAdminRequest();
1640
- * ```
1641
- */
1642
- async validateAdminRequest(): Promise<unknown> {
1643
- this._validateAdminPreconditions("validateAdminRequest", ["organizations", "projects"]);
1644
-
1645
- const parentLink = this._getParentLinkForUser();
1646
-
1647
- if (!parentLink?.isAdminPending) {
1648
- throw new ApiError("Cet utilisateur n'a pas de demande d'admin en attente.", 400);
1649
- }
1650
-
1651
- const t = this.parent!.getEntityType();
1652
-
1653
- const parentType = t as LinkValidateData["parentType"];
1654
-
1655
- const data: LinkValidateData = {
1656
- childId: this.id!,
1657
- childType: "citoyens",
1658
- parentType,
1659
- parentId: this.parent!.id!,
1660
- linkOption: "isAdminPending"
1661
- };
1662
-
1663
- const retour = await this.callIsConnected(() => this.endpointApi.linkValidate(data));
1664
- await this._refreshWithParent();
1665
- return retour;
1666
- }
1667
-
1668
- /**
1669
- * Retire un utilisateur de l'entité parente.
1670
- *
1671
- * Cette méthode permet à un admin de déconnecter un membre de l'organisation ou du projet parent.
1672
- * L'utilisateur perd tous ses liens avec l'entité (membre, admin, etc.).
1673
- *
1674
- * @returns {Promise<unknown>} La réponse de l'API après déconnexion
1675
- * @throws {ApiError} Si l'utilisateur n'est pas connecté, pas admin, ou si le parent n'est pas défini
1676
- * @throws {ApiError} Si l'utilisateur n'est pas membre de l'entité
1677
- *
1678
- * @example
1679
- * ```typescript
1680
- * // Un admin retire un membre
1681
- * const org = await me.organization({ slug: "myOrg" });
1682
- * const members = await org.getMembers();
1683
- * const userToRemove = members.results[0];
1684
- * await userToRemove.removeFromParent();
1685
- * ```
1686
- */
1687
- async removeFromParent(): Promise<unknown> {
1688
- this._validateAdminPreconditions("removeFromParent", ["organizations", "projects"]);
1689
-
1690
- const parentLink = this._getParentLinkForUser();
1691
-
1692
- if (!parentLink) {
1693
- throw new ApiError("Cet utilisateur n'est pas connecté à cette entité.", 400);
1694
- }
1695
-
1696
- const t = this.parent!.getEntityType();
1697
-
1698
- const parentType = t as DisconnectData["parentType"];
1699
-
1700
- const { connectTypeDisconnect } = this.parent!._getLinkMeta();
1701
-
1702
- const data: DisconnectData = {
1703
- childId: this.id!,
1704
- childType: "citoyens",
1705
- parentType,
1706
- parentId: this.parent!.id!,
1707
- connectType: connectTypeDisconnect
1708
- };
1709
-
1710
- const retour = await this.callIsConnected(() => this.endpointApi.disconnect(data));
1711
- await this._refreshWithParent();
1712
- return retour;
1713
- }
1714
-
1715
- /**
1716
- * Promeut un utilisateur au statut d'admin de l'entité parente.
1717
- *
1718
- * Cette méthode permet à un admin de promouvoir un membre ordinaire au statut d'admin
1719
- * de l'organisation ou du projet parent.
1720
- *
1721
- * @returns {Promise<unknown>} La réponse de l'API après promotion
1722
- * @throws {ApiError} Si l'utilisateur n'est pas connecté, pas admin, ou si le parent n'est pas défini
1723
- * @throws {ApiError} Si l'utilisateur n'est pas membre de l'entité
1724
- * @throws {ApiError} Si l'utilisateur est déjà admin
1725
- *
1726
- * @example
1727
- * ```typescript
1728
- * // Un admin promeut un membre au statut d'admin
1729
- * const org = await me.organization({ slug: "myOrg" });
1730
- * const members = await org.getMembers();
1731
- * const userToPromote = members.results.find(u => !u.isAdmin());
1732
- * await userToPromote.promoteToAdmin();
1733
- * ```
1734
- */
1735
- async promoteToAdmin(): Promise<unknown> {
1736
- this._validateAdminPreconditions("promoteToAdmin", ["organizations", "projects"]);
1737
-
1738
- const parentLink = this._getParentLinkForUser();
1739
-
1740
- if (!parentLink) {
1741
- throw new ApiError("Cet utilisateur n'est pas membre de cette entité.", 400);
1742
- }
1743
-
1744
- if (parentLink?.isAdmin && (parentLink?.isInviting || parentLink?.isAdminInviting)) {
1745
- throw new ApiError("Cet utilisateur est déjà en cours d'invitation en tant qu'admin.", 400);
1746
- }
1747
-
1748
- if (parentLink?.isAdmin && !parentLink?.isInviting && !parentLink?.isAdminInviting) {
1749
- throw new ApiError("Cet utilisateur est déjà admin.", 400);
1750
- }
1751
-
1752
- const t = this.parent!.getEntityType();
1753
-
1754
- const parentType = t as ConnectData["parentType"];
1755
-
1756
- const data: ConnectData = {
1757
- childId: this.id!,
1758
- childType: "citoyens",
1759
- parentType,
1760
- parentId: this.parent!.id!,
1761
- connectType: "admin"
1762
- };
1763
-
1764
- const retour = await this.callIsConnected(() => this.endpointApi.connect(data));
1765
- await this._refreshWithParent();
1766
- return retour;
1767
- }
1768
-
1769
- /**
1770
- * Rétrograde un admin au statut de membre ordinaire.
1771
- *
1772
- * Cette méthode permet à un admin de rétrograder un autre admin au statut de membre
1773
- * ordinaire de l'organisation ou du projet parent. L'utilisateur perd ses privilèges d'admin
1774
- * mais reste membre de l'entité.
1775
- *
1776
- * @returns {Promise<unknown>} La réponse de l'API après rétrogradation
1777
- * @throws {ApiError} Si l'utilisateur n'est pas connecté, pas admin, ou si le parent n'est pas défini
1778
- * @throws {ApiError} Si l'utilisateur n'est pas admin de l'entité
1779
- *
1780
- * @example
1781
- * ```typescript
1782
- * // Un admin rétrograde un autre admin
1783
- * const org = await me.organization({ slug: "myOrg" });
1784
- * const admins = await org.getMembers({}, { isAdmin: true });
1785
- * const adminToDemote = admins.results.find(u => u.isAdmin());
1786
- * await adminToDemote.demoteFromAdmin();
1787
- * ```
1788
- */
1789
- async demoteFromAdmin(): Promise<unknown> {
1790
- this._validateAdminPreconditions("demoteFromAdmin", ["organizations", "projects"]);
1791
-
1792
- const parentLink = this._getParentLinkForUser();
1793
-
1794
- if (!parentLink?.isAdmin || parentLink.isAdminPending) {
1795
- throw new ApiError("Cet utilisateur n'est pas admin de cette entité.", 400);
1796
- }
1797
-
1798
- const t = this.parent!.getEntityType();
1799
-
1800
- const { connectTypeDisconnect } = this.parent!._getLinkMeta();
1801
-
1802
- const parentType = t as DemoteAdminData["parentType"];
1803
-
1804
- // Pour rétrograder un admin, on déconnecte le lien "admin"
1805
- // L'utilisateur garde son lien "member"
1806
- const data: DemoteAdminData = {
1807
- childId: this.id!,
1808
- childType: "citoyens",
1809
- parentType,
1810
- parentId: this.parent!.id!,
1811
- connect: connectTypeDisconnect as DemoteAdminData["connect"],
1812
- isAdmin: false
1813
- };
1814
-
1815
- const retour = await this.callIsConnected(() => this.endpointApi.demoteAdmin(data));
1816
- await this._refreshWithParent();
1817
- return retour;
1818
- }
1819
-
1820
- // ===========================================================================
1821
- // Notifications (sous-ressource de l'utilisateur connecté)
1822
- // ===========================================================================
1823
-
1824
- /** Gestionnaire de notifications, créé paresseusement et mis en cache. */
1825
- private _notifications?: Notifications;
1826
-
1827
- /**
1828
- * Point d'entrée `me.notifications` (composition, PAS une entité BaseEntity).
1829
- * Sûr : endpointApi/apiClient sont initialisés dans le constructeur avant tout accès getter.
1830
- */
1831
- get notifications(): Notifications {
1832
- return (this._notifications ??= new Notifications(this));
1833
- }
1834
-
1835
- /**
1836
- * Récupère les notifications (mode liste). STATELESS + plat (`NotificationItemData[]`),
1837
- * idéal comme `queryFn` React Query / prefetch SSR — ne touche PAS le manager réactif
1838
- * (`me.notifications`). `ApiClient._transformData` garantit `notif` = tableau (map -> array
1839
- * + `id` injecté), donc on fait confiance à la normalisation amont.
1840
- * pathParams.id est OBLIGATOIRE : le défaut "@userId" ne matche pas le pattern de l'endpoint.
1841
- */
1842
- async fetchNotifications({ indexMin }: { indexMin?: number } = {}): Promise<NotificationItemData[]> {
1843
- const res = (await this.callIsMe(() => {
1844
- const data: GetNotificationsData = {
1845
- pathParams: { type: "citoyens", id: this.userId! },
1846
- ...(indexMin != null ? { indexMin } : {})
1847
- };
1848
- return this.endpointApi.getNotifications(data);
1849
- })) as { notif?: NotificationItemData[] };
1850
- return Array.isArray(res?.notif) ? res.notif : [];
1851
- }
1852
-
1853
- /**
1854
- * Compte les notifications NON VUES (badge). STATELESS. refreshTimestamp = maintenant ->
1855
- * le serveur renvoie `countNotif` (total non-vus) + une liste vide.
1856
- */
1857
- async fetchNotificationsCount(): Promise<number> {
1858
- const res = (await this.callIsMe(() => {
1859
- const data: GetNotificationsCountData = {
1860
- pathParams: { type: "citoyens", id: this.userId! },
1861
- refreshTimestamp: Math.floor(Date.now() / 1000)
1862
- };
1863
- return this.endpointApi.getNotificationsCount(data);
1864
- })) as { countNotif?: number };
1865
- return typeof res?.countNotif === "number" ? res.countNotif : 0;
1866
- }
1867
-
1868
- /** Marque une notification comme lue (MARK_NOTIFICATION_AS_READ). */
1869
- async markNotificationAsRead(id: string): Promise<unknown> {
1870
- const data: MarkNotificationAsReadData = { id };
1871
- return this.callIsMe(() => this.endpointApi.markNotificationAsRead(data));
1872
- }
1873
-
1874
- /** Marque toutes les notifications comme vues/lues (NOTIFICATION_UPDATE). */
1875
- async markAllNotifications(action: "seen" | "read"): Promise<unknown> {
1876
- const data: NotificationUpdateData = { action, all: true };
1877
- return this.callIsMe(() => this.endpointApi.notificationUpdate(data));
1878
- }
1879
-
1880
- /** Supprime toutes les notifications de l'utilisateur (REMOVE_ALL_NOTIFICATIONS). */
1881
- async removeAllNotifications(): Promise<unknown> {
1882
- return this.callIsMe(() => this.endpointApi.removeAllNotifications());
1883
- }
1884
-
1885
- }
1886
-
1887
- // Incorporation des mixins dans User
1888
- Object.assign(User.prototype, UserMixin);
1889
-