strapi-plugin-keycloak-realm-users 1.0.0

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 (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +485 -0
  3. package/__tests__/constants.test.mjs +207 -0
  4. package/__tests__/mocks/strapi.mjs +182 -0
  5. package/__tests__/services/audit-log.test.mjs +283 -0
  6. package/__tests__/services/keycloak-client.test.mjs +651 -0
  7. package/__tests__/services/permission.test.mjs +374 -0
  8. package/__tests__/services/realm.test.mjs +415 -0
  9. package/__tests__/services/user.test.mjs +487 -0
  10. package/__tests__/utils/errors.test.mjs +109 -0
  11. package/admin/src/components/Initializer.jsx +14 -0
  12. package/admin/src/components/RealmBadge.jsx +17 -0
  13. package/admin/src/constants.js +14 -0
  14. package/admin/src/hooks/useAuditLogs.js +142 -0
  15. package/admin/src/hooks/useKeycloakRoles.js +182 -0
  16. package/admin/src/hooks/useKeycloakUsers.js +477 -0
  17. package/admin/src/hooks/useRealmAdmins.js +249 -0
  18. package/admin/src/hooks/useRealms.js +269 -0
  19. package/admin/src/index.js +46 -0
  20. package/admin/src/pages/App.jsx +21 -0
  21. package/admin/src/pages/AuditPage/index.jsx +213 -0
  22. package/admin/src/pages/RealmsPage/RealmEditPage.jsx +791 -0
  23. package/admin/src/pages/RealmsPage/RealmListPage.jsx +231 -0
  24. package/admin/src/pages/RealmsPage/index.jsx +7 -0
  25. package/admin/src/pages/UsersPage/UserEditPage.jsx +313 -0
  26. package/admin/src/pages/UsersPage/UserListPage.jsx +437 -0
  27. package/admin/src/pages/UsersPage/index.jsx +7 -0
  28. package/admin/src/pluginId.js +2 -0
  29. package/admin/src/translations/en.json +77 -0
  30. package/admin/src/translations/fr.json +77 -0
  31. package/babel.config.cjs +17 -0
  32. package/coverage/clover.xml +422 -0
  33. package/coverage/coverage-final.json +8 -0
  34. package/coverage/lcov-report/base.css +224 -0
  35. package/coverage/lcov-report/block-navigation.js +87 -0
  36. package/coverage/lcov-report/favicon.png +0 -0
  37. package/coverage/lcov-report/index.html +146 -0
  38. package/coverage/lcov-report/prettify.css +1 -0
  39. package/coverage/lcov-report/prettify.js +2 -0
  40. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  41. package/coverage/lcov-report/sorter.js +210 -0
  42. package/coverage/lcov-report/src/bootstrap.js.html +346 -0
  43. package/coverage/lcov-report/src/config/index.html +116 -0
  44. package/coverage/lcov-report/src/config/index.js.html +106 -0
  45. package/coverage/lcov-report/src/constants.js.html +850 -0
  46. package/coverage/lcov-report/src/content-types/audit-log/index.html +116 -0
  47. package/coverage/lcov-report/src/content-types/audit-log/index.js.html +94 -0
  48. package/coverage/lcov-report/src/content-types/index.html +116 -0
  49. package/coverage/lcov-report/src/content-types/index.js.html +112 -0
  50. package/coverage/lcov-report/src/content-types/realm-admin/index.html +116 -0
  51. package/coverage/lcov-report/src/content-types/realm-admin/index.js.html +94 -0
  52. package/coverage/lcov-report/src/content-types/realm-config/index.html +116 -0
  53. package/coverage/lcov-report/src/content-types/realm-config/index.js.html +94 -0
  54. package/coverage/lcov-report/src/controllers/audit.js.html +517 -0
  55. package/coverage/lcov-report/src/controllers/index.html +161 -0
  56. package/coverage/lcov-report/src/controllers/index.js.html +112 -0
  57. package/coverage/lcov-report/src/controllers/realm.js.html +1057 -0
  58. package/coverage/lcov-report/src/controllers/user.js.html +1324 -0
  59. package/coverage/lcov-report/src/destroy.js.html +100 -0
  60. package/coverage/lcov-report/src/index.html +116 -0
  61. package/coverage/lcov-report/src/policies/can-access-realm.js.html +163 -0
  62. package/coverage/lcov-report/src/policies/index.html +146 -0
  63. package/coverage/lcov-report/src/policies/index.js.html +106 -0
  64. package/coverage/lcov-report/src/policies/is-authenticated.js.html +100 -0
  65. package/coverage/lcov-report/src/register.js.html +106 -0
  66. package/coverage/lcov-report/src/routes/admin.js.html +844 -0
  67. package/coverage/lcov-report/src/routes/index.html +131 -0
  68. package/coverage/lcov-report/src/routes/index.js.html +109 -0
  69. package/coverage/lcov-report/src/services/audit-log.js.html +673 -0
  70. package/coverage/lcov-report/src/services/index.html +176 -0
  71. package/coverage/lcov-report/src/services/index.js.html +124 -0
  72. package/coverage/lcov-report/src/services/keycloak-client.js.html +2359 -0
  73. package/coverage/lcov-report/src/services/permission.js.html +955 -0
  74. package/coverage/lcov-report/src/services/realm.js.html +1207 -0
  75. package/coverage/lcov-report/src/services/user.js.html +1924 -0
  76. package/coverage/lcov-report/src/utils/errors.js.html +274 -0
  77. package/coverage/lcov-report/src/utils/index.html +116 -0
  78. package/coverage/lcov-report/src/utils/index.js.html +103 -0
  79. package/coverage/lcov.info +804 -0
  80. package/dist/_chunks/App-BaKrvCeS.mjs +1975 -0
  81. package/dist/_chunks/App-DO6syS77.js +1975 -0
  82. package/dist/_chunks/en-Li-XBDe9.mjs +72 -0
  83. package/dist/_chunks/en-aCyfgNfr.js +72 -0
  84. package/dist/_chunks/fr-Cj33Q8jI.js +72 -0
  85. package/dist/_chunks/fr-vLrXph-Z.mjs +72 -0
  86. package/dist/_chunks/index-DwDO4-0C.js +69 -0
  87. package/dist/_chunks/index-jTVd7LdQ.mjs +70 -0
  88. package/dist/admin/index.js +3 -0
  89. package/dist/admin/index.mjs +4 -0
  90. package/dist/server/index.js +3003 -0
  91. package/dist/server/index.mjs +3004 -0
  92. package/jest.config.cjs +50 -0
  93. package/package.json +55 -0
  94. package/server/src/bootstrap.js +87 -0
  95. package/server/src/config/index.js +7 -0
  96. package/server/src/constants.js +255 -0
  97. package/server/src/content-types/audit-log/index.js +3 -0
  98. package/server/src/content-types/audit-log/schema.json +61 -0
  99. package/server/src/content-types/index.js +9 -0
  100. package/server/src/content-types/realm-admin/index.js +3 -0
  101. package/server/src/content-types/realm-admin/schema.json +45 -0
  102. package/server/src/content-types/realm-config/index.js +3 -0
  103. package/server/src/content-types/realm-config/schema.json +56 -0
  104. package/server/src/controllers/audit.js +144 -0
  105. package/server/src/controllers/index.js +9 -0
  106. package/server/src/controllers/realm.js +324 -0
  107. package/server/src/controllers/user.js +413 -0
  108. package/server/src/destroy.js +5 -0
  109. package/server/src/index.js +21 -0
  110. package/server/src/policies/can-access-realm.js +26 -0
  111. package/server/src/policies/index.js +7 -0
  112. package/server/src/policies/is-authenticated.js +5 -0
  113. package/server/src/register.js +7 -0
  114. package/server/src/routes/admin.js +253 -0
  115. package/server/src/routes/index.js +8 -0
  116. package/server/src/services/audit-log.js +196 -0
  117. package/server/src/services/index.js +13 -0
  118. package/server/src/services/keycloak-client.js +758 -0
  119. package/server/src/services/permission.js +290 -0
  120. package/server/src/services/realm.js +374 -0
  121. package/server/src/services/user.js +613 -0
  122. package/server/src/utils/errors.js +63 -0
  123. package/server/src/utils/index.js +6 -0
@@ -0,0 +1,1924 @@
1
+
2
+ <!doctype html>
3
+ <html lang="en">
4
+
5
+ <head>
6
+ <title>Code coverage report for src/services/user.js</title>
7
+ <meta charset="utf-8" />
8
+ <link rel="stylesheet" href="../../prettify.css" />
9
+ <link rel="stylesheet" href="../../base.css" />
10
+ <link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
11
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
12
+ <style type='text/css'>
13
+ .coverage-summary .sorter {
14
+ background-image: url(../../sort-arrow-sprite.png);
15
+ }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <div class='wrapper'>
21
+ <div class='pad1'>
22
+ <h1><a href="../../index.html">All files</a> / <a href="index.html">src/services</a> user.js</h1>
23
+ <div class='clearfix'>
24
+
25
+ <div class='fl pad1y space-right2'>
26
+ <span class="strong">98.95% </span>
27
+ <span class="quiet">Statements</span>
28
+ <span class='fraction'>95/96</span>
29
+ </div>
30
+
31
+
32
+ <div class='fl pad1y space-right2'>
33
+ <span class="strong">75% </span>
34
+ <span class="quiet">Branches</span>
35
+ <span class='fraction'>9/12</span>
36
+ </div>
37
+
38
+
39
+ <div class='fl pad1y space-right2'>
40
+ <span class="strong">100% </span>
41
+ <span class="quiet">Functions</span>
42
+ <span class='fraction'>24/24</span>
43
+ </div>
44
+
45
+
46
+ <div class='fl pad1y space-right2'>
47
+ <span class="strong">98.94% </span>
48
+ <span class="quiet">Lines</span>
49
+ <span class='fraction'>94/95</span>
50
+ </div>
51
+
52
+
53
+ </div>
54
+ <p class="quiet">
55
+ Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
56
+ </p>
57
+ <template id="filterTemplate">
58
+ <div class="quiet">
59
+ Filter:
60
+ <input type="search" id="fileSearch">
61
+ </div>
62
+ </template>
63
+ </div>
64
+ <div class='status-line high'></div>
65
+ <pre><table class="coverage">
66
+ <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
67
+ <a name='L2'></a><a href='#L2'>2</a>
68
+ <a name='L3'></a><a href='#L3'>3</a>
69
+ <a name='L4'></a><a href='#L4'>4</a>
70
+ <a name='L5'></a><a href='#L5'>5</a>
71
+ <a name='L6'></a><a href='#L6'>6</a>
72
+ <a name='L7'></a><a href='#L7'>7</a>
73
+ <a name='L8'></a><a href='#L8'>8</a>
74
+ <a name='L9'></a><a href='#L9'>9</a>
75
+ <a name='L10'></a><a href='#L10'>10</a>
76
+ <a name='L11'></a><a href='#L11'>11</a>
77
+ <a name='L12'></a><a href='#L12'>12</a>
78
+ <a name='L13'></a><a href='#L13'>13</a>
79
+ <a name='L14'></a><a href='#L14'>14</a>
80
+ <a name='L15'></a><a href='#L15'>15</a>
81
+ <a name='L16'></a><a href='#L16'>16</a>
82
+ <a name='L17'></a><a href='#L17'>17</a>
83
+ <a name='L18'></a><a href='#L18'>18</a>
84
+ <a name='L19'></a><a href='#L19'>19</a>
85
+ <a name='L20'></a><a href='#L20'>20</a>
86
+ <a name='L21'></a><a href='#L21'>21</a>
87
+ <a name='L22'></a><a href='#L22'>22</a>
88
+ <a name='L23'></a><a href='#L23'>23</a>
89
+ <a name='L24'></a><a href='#L24'>24</a>
90
+ <a name='L25'></a><a href='#L25'>25</a>
91
+ <a name='L26'></a><a href='#L26'>26</a>
92
+ <a name='L27'></a><a href='#L27'>27</a>
93
+ <a name='L28'></a><a href='#L28'>28</a>
94
+ <a name='L29'></a><a href='#L29'>29</a>
95
+ <a name='L30'></a><a href='#L30'>30</a>
96
+ <a name='L31'></a><a href='#L31'>31</a>
97
+ <a name='L32'></a><a href='#L32'>32</a>
98
+ <a name='L33'></a><a href='#L33'>33</a>
99
+ <a name='L34'></a><a href='#L34'>34</a>
100
+ <a name='L35'></a><a href='#L35'>35</a>
101
+ <a name='L36'></a><a href='#L36'>36</a>
102
+ <a name='L37'></a><a href='#L37'>37</a>
103
+ <a name='L38'></a><a href='#L38'>38</a>
104
+ <a name='L39'></a><a href='#L39'>39</a>
105
+ <a name='L40'></a><a href='#L40'>40</a>
106
+ <a name='L41'></a><a href='#L41'>41</a>
107
+ <a name='L42'></a><a href='#L42'>42</a>
108
+ <a name='L43'></a><a href='#L43'>43</a>
109
+ <a name='L44'></a><a href='#L44'>44</a>
110
+ <a name='L45'></a><a href='#L45'>45</a>
111
+ <a name='L46'></a><a href='#L46'>46</a>
112
+ <a name='L47'></a><a href='#L47'>47</a>
113
+ <a name='L48'></a><a href='#L48'>48</a>
114
+ <a name='L49'></a><a href='#L49'>49</a>
115
+ <a name='L50'></a><a href='#L50'>50</a>
116
+ <a name='L51'></a><a href='#L51'>51</a>
117
+ <a name='L52'></a><a href='#L52'>52</a>
118
+ <a name='L53'></a><a href='#L53'>53</a>
119
+ <a name='L54'></a><a href='#L54'>54</a>
120
+ <a name='L55'></a><a href='#L55'>55</a>
121
+ <a name='L56'></a><a href='#L56'>56</a>
122
+ <a name='L57'></a><a href='#L57'>57</a>
123
+ <a name='L58'></a><a href='#L58'>58</a>
124
+ <a name='L59'></a><a href='#L59'>59</a>
125
+ <a name='L60'></a><a href='#L60'>60</a>
126
+ <a name='L61'></a><a href='#L61'>61</a>
127
+ <a name='L62'></a><a href='#L62'>62</a>
128
+ <a name='L63'></a><a href='#L63'>63</a>
129
+ <a name='L64'></a><a href='#L64'>64</a>
130
+ <a name='L65'></a><a href='#L65'>65</a>
131
+ <a name='L66'></a><a href='#L66'>66</a>
132
+ <a name='L67'></a><a href='#L67'>67</a>
133
+ <a name='L68'></a><a href='#L68'>68</a>
134
+ <a name='L69'></a><a href='#L69'>69</a>
135
+ <a name='L70'></a><a href='#L70'>70</a>
136
+ <a name='L71'></a><a href='#L71'>71</a>
137
+ <a name='L72'></a><a href='#L72'>72</a>
138
+ <a name='L73'></a><a href='#L73'>73</a>
139
+ <a name='L74'></a><a href='#L74'>74</a>
140
+ <a name='L75'></a><a href='#L75'>75</a>
141
+ <a name='L76'></a><a href='#L76'>76</a>
142
+ <a name='L77'></a><a href='#L77'>77</a>
143
+ <a name='L78'></a><a href='#L78'>78</a>
144
+ <a name='L79'></a><a href='#L79'>79</a>
145
+ <a name='L80'></a><a href='#L80'>80</a>
146
+ <a name='L81'></a><a href='#L81'>81</a>
147
+ <a name='L82'></a><a href='#L82'>82</a>
148
+ <a name='L83'></a><a href='#L83'>83</a>
149
+ <a name='L84'></a><a href='#L84'>84</a>
150
+ <a name='L85'></a><a href='#L85'>85</a>
151
+ <a name='L86'></a><a href='#L86'>86</a>
152
+ <a name='L87'></a><a href='#L87'>87</a>
153
+ <a name='L88'></a><a href='#L88'>88</a>
154
+ <a name='L89'></a><a href='#L89'>89</a>
155
+ <a name='L90'></a><a href='#L90'>90</a>
156
+ <a name='L91'></a><a href='#L91'>91</a>
157
+ <a name='L92'></a><a href='#L92'>92</a>
158
+ <a name='L93'></a><a href='#L93'>93</a>
159
+ <a name='L94'></a><a href='#L94'>94</a>
160
+ <a name='L95'></a><a href='#L95'>95</a>
161
+ <a name='L96'></a><a href='#L96'>96</a>
162
+ <a name='L97'></a><a href='#L97'>97</a>
163
+ <a name='L98'></a><a href='#L98'>98</a>
164
+ <a name='L99'></a><a href='#L99'>99</a>
165
+ <a name='L100'></a><a href='#L100'>100</a>
166
+ <a name='L101'></a><a href='#L101'>101</a>
167
+ <a name='L102'></a><a href='#L102'>102</a>
168
+ <a name='L103'></a><a href='#L103'>103</a>
169
+ <a name='L104'></a><a href='#L104'>104</a>
170
+ <a name='L105'></a><a href='#L105'>105</a>
171
+ <a name='L106'></a><a href='#L106'>106</a>
172
+ <a name='L107'></a><a href='#L107'>107</a>
173
+ <a name='L108'></a><a href='#L108'>108</a>
174
+ <a name='L109'></a><a href='#L109'>109</a>
175
+ <a name='L110'></a><a href='#L110'>110</a>
176
+ <a name='L111'></a><a href='#L111'>111</a>
177
+ <a name='L112'></a><a href='#L112'>112</a>
178
+ <a name='L113'></a><a href='#L113'>113</a>
179
+ <a name='L114'></a><a href='#L114'>114</a>
180
+ <a name='L115'></a><a href='#L115'>115</a>
181
+ <a name='L116'></a><a href='#L116'>116</a>
182
+ <a name='L117'></a><a href='#L117'>117</a>
183
+ <a name='L118'></a><a href='#L118'>118</a>
184
+ <a name='L119'></a><a href='#L119'>119</a>
185
+ <a name='L120'></a><a href='#L120'>120</a>
186
+ <a name='L121'></a><a href='#L121'>121</a>
187
+ <a name='L122'></a><a href='#L122'>122</a>
188
+ <a name='L123'></a><a href='#L123'>123</a>
189
+ <a name='L124'></a><a href='#L124'>124</a>
190
+ <a name='L125'></a><a href='#L125'>125</a>
191
+ <a name='L126'></a><a href='#L126'>126</a>
192
+ <a name='L127'></a><a href='#L127'>127</a>
193
+ <a name='L128'></a><a href='#L128'>128</a>
194
+ <a name='L129'></a><a href='#L129'>129</a>
195
+ <a name='L130'></a><a href='#L130'>130</a>
196
+ <a name='L131'></a><a href='#L131'>131</a>
197
+ <a name='L132'></a><a href='#L132'>132</a>
198
+ <a name='L133'></a><a href='#L133'>133</a>
199
+ <a name='L134'></a><a href='#L134'>134</a>
200
+ <a name='L135'></a><a href='#L135'>135</a>
201
+ <a name='L136'></a><a href='#L136'>136</a>
202
+ <a name='L137'></a><a href='#L137'>137</a>
203
+ <a name='L138'></a><a href='#L138'>138</a>
204
+ <a name='L139'></a><a href='#L139'>139</a>
205
+ <a name='L140'></a><a href='#L140'>140</a>
206
+ <a name='L141'></a><a href='#L141'>141</a>
207
+ <a name='L142'></a><a href='#L142'>142</a>
208
+ <a name='L143'></a><a href='#L143'>143</a>
209
+ <a name='L144'></a><a href='#L144'>144</a>
210
+ <a name='L145'></a><a href='#L145'>145</a>
211
+ <a name='L146'></a><a href='#L146'>146</a>
212
+ <a name='L147'></a><a href='#L147'>147</a>
213
+ <a name='L148'></a><a href='#L148'>148</a>
214
+ <a name='L149'></a><a href='#L149'>149</a>
215
+ <a name='L150'></a><a href='#L150'>150</a>
216
+ <a name='L151'></a><a href='#L151'>151</a>
217
+ <a name='L152'></a><a href='#L152'>152</a>
218
+ <a name='L153'></a><a href='#L153'>153</a>
219
+ <a name='L154'></a><a href='#L154'>154</a>
220
+ <a name='L155'></a><a href='#L155'>155</a>
221
+ <a name='L156'></a><a href='#L156'>156</a>
222
+ <a name='L157'></a><a href='#L157'>157</a>
223
+ <a name='L158'></a><a href='#L158'>158</a>
224
+ <a name='L159'></a><a href='#L159'>159</a>
225
+ <a name='L160'></a><a href='#L160'>160</a>
226
+ <a name='L161'></a><a href='#L161'>161</a>
227
+ <a name='L162'></a><a href='#L162'>162</a>
228
+ <a name='L163'></a><a href='#L163'>163</a>
229
+ <a name='L164'></a><a href='#L164'>164</a>
230
+ <a name='L165'></a><a href='#L165'>165</a>
231
+ <a name='L166'></a><a href='#L166'>166</a>
232
+ <a name='L167'></a><a href='#L167'>167</a>
233
+ <a name='L168'></a><a href='#L168'>168</a>
234
+ <a name='L169'></a><a href='#L169'>169</a>
235
+ <a name='L170'></a><a href='#L170'>170</a>
236
+ <a name='L171'></a><a href='#L171'>171</a>
237
+ <a name='L172'></a><a href='#L172'>172</a>
238
+ <a name='L173'></a><a href='#L173'>173</a>
239
+ <a name='L174'></a><a href='#L174'>174</a>
240
+ <a name='L175'></a><a href='#L175'>175</a>
241
+ <a name='L176'></a><a href='#L176'>176</a>
242
+ <a name='L177'></a><a href='#L177'>177</a>
243
+ <a name='L178'></a><a href='#L178'>178</a>
244
+ <a name='L179'></a><a href='#L179'>179</a>
245
+ <a name='L180'></a><a href='#L180'>180</a>
246
+ <a name='L181'></a><a href='#L181'>181</a>
247
+ <a name='L182'></a><a href='#L182'>182</a>
248
+ <a name='L183'></a><a href='#L183'>183</a>
249
+ <a name='L184'></a><a href='#L184'>184</a>
250
+ <a name='L185'></a><a href='#L185'>185</a>
251
+ <a name='L186'></a><a href='#L186'>186</a>
252
+ <a name='L187'></a><a href='#L187'>187</a>
253
+ <a name='L188'></a><a href='#L188'>188</a>
254
+ <a name='L189'></a><a href='#L189'>189</a>
255
+ <a name='L190'></a><a href='#L190'>190</a>
256
+ <a name='L191'></a><a href='#L191'>191</a>
257
+ <a name='L192'></a><a href='#L192'>192</a>
258
+ <a name='L193'></a><a href='#L193'>193</a>
259
+ <a name='L194'></a><a href='#L194'>194</a>
260
+ <a name='L195'></a><a href='#L195'>195</a>
261
+ <a name='L196'></a><a href='#L196'>196</a>
262
+ <a name='L197'></a><a href='#L197'>197</a>
263
+ <a name='L198'></a><a href='#L198'>198</a>
264
+ <a name='L199'></a><a href='#L199'>199</a>
265
+ <a name='L200'></a><a href='#L200'>200</a>
266
+ <a name='L201'></a><a href='#L201'>201</a>
267
+ <a name='L202'></a><a href='#L202'>202</a>
268
+ <a name='L203'></a><a href='#L203'>203</a>
269
+ <a name='L204'></a><a href='#L204'>204</a>
270
+ <a name='L205'></a><a href='#L205'>205</a>
271
+ <a name='L206'></a><a href='#L206'>206</a>
272
+ <a name='L207'></a><a href='#L207'>207</a>
273
+ <a name='L208'></a><a href='#L208'>208</a>
274
+ <a name='L209'></a><a href='#L209'>209</a>
275
+ <a name='L210'></a><a href='#L210'>210</a>
276
+ <a name='L211'></a><a href='#L211'>211</a>
277
+ <a name='L212'></a><a href='#L212'>212</a>
278
+ <a name='L213'></a><a href='#L213'>213</a>
279
+ <a name='L214'></a><a href='#L214'>214</a>
280
+ <a name='L215'></a><a href='#L215'>215</a>
281
+ <a name='L216'></a><a href='#L216'>216</a>
282
+ <a name='L217'></a><a href='#L217'>217</a>
283
+ <a name='L218'></a><a href='#L218'>218</a>
284
+ <a name='L219'></a><a href='#L219'>219</a>
285
+ <a name='L220'></a><a href='#L220'>220</a>
286
+ <a name='L221'></a><a href='#L221'>221</a>
287
+ <a name='L222'></a><a href='#L222'>222</a>
288
+ <a name='L223'></a><a href='#L223'>223</a>
289
+ <a name='L224'></a><a href='#L224'>224</a>
290
+ <a name='L225'></a><a href='#L225'>225</a>
291
+ <a name='L226'></a><a href='#L226'>226</a>
292
+ <a name='L227'></a><a href='#L227'>227</a>
293
+ <a name='L228'></a><a href='#L228'>228</a>
294
+ <a name='L229'></a><a href='#L229'>229</a>
295
+ <a name='L230'></a><a href='#L230'>230</a>
296
+ <a name='L231'></a><a href='#L231'>231</a>
297
+ <a name='L232'></a><a href='#L232'>232</a>
298
+ <a name='L233'></a><a href='#L233'>233</a>
299
+ <a name='L234'></a><a href='#L234'>234</a>
300
+ <a name='L235'></a><a href='#L235'>235</a>
301
+ <a name='L236'></a><a href='#L236'>236</a>
302
+ <a name='L237'></a><a href='#L237'>237</a>
303
+ <a name='L238'></a><a href='#L238'>238</a>
304
+ <a name='L239'></a><a href='#L239'>239</a>
305
+ <a name='L240'></a><a href='#L240'>240</a>
306
+ <a name='L241'></a><a href='#L241'>241</a>
307
+ <a name='L242'></a><a href='#L242'>242</a>
308
+ <a name='L243'></a><a href='#L243'>243</a>
309
+ <a name='L244'></a><a href='#L244'>244</a>
310
+ <a name='L245'></a><a href='#L245'>245</a>
311
+ <a name='L246'></a><a href='#L246'>246</a>
312
+ <a name='L247'></a><a href='#L247'>247</a>
313
+ <a name='L248'></a><a href='#L248'>248</a>
314
+ <a name='L249'></a><a href='#L249'>249</a>
315
+ <a name='L250'></a><a href='#L250'>250</a>
316
+ <a name='L251'></a><a href='#L251'>251</a>
317
+ <a name='L252'></a><a href='#L252'>252</a>
318
+ <a name='L253'></a><a href='#L253'>253</a>
319
+ <a name='L254'></a><a href='#L254'>254</a>
320
+ <a name='L255'></a><a href='#L255'>255</a>
321
+ <a name='L256'></a><a href='#L256'>256</a>
322
+ <a name='L257'></a><a href='#L257'>257</a>
323
+ <a name='L258'></a><a href='#L258'>258</a>
324
+ <a name='L259'></a><a href='#L259'>259</a>
325
+ <a name='L260'></a><a href='#L260'>260</a>
326
+ <a name='L261'></a><a href='#L261'>261</a>
327
+ <a name='L262'></a><a href='#L262'>262</a>
328
+ <a name='L263'></a><a href='#L263'>263</a>
329
+ <a name='L264'></a><a href='#L264'>264</a>
330
+ <a name='L265'></a><a href='#L265'>265</a>
331
+ <a name='L266'></a><a href='#L266'>266</a>
332
+ <a name='L267'></a><a href='#L267'>267</a>
333
+ <a name='L268'></a><a href='#L268'>268</a>
334
+ <a name='L269'></a><a href='#L269'>269</a>
335
+ <a name='L270'></a><a href='#L270'>270</a>
336
+ <a name='L271'></a><a href='#L271'>271</a>
337
+ <a name='L272'></a><a href='#L272'>272</a>
338
+ <a name='L273'></a><a href='#L273'>273</a>
339
+ <a name='L274'></a><a href='#L274'>274</a>
340
+ <a name='L275'></a><a href='#L275'>275</a>
341
+ <a name='L276'></a><a href='#L276'>276</a>
342
+ <a name='L277'></a><a href='#L277'>277</a>
343
+ <a name='L278'></a><a href='#L278'>278</a>
344
+ <a name='L279'></a><a href='#L279'>279</a>
345
+ <a name='L280'></a><a href='#L280'>280</a>
346
+ <a name='L281'></a><a href='#L281'>281</a>
347
+ <a name='L282'></a><a href='#L282'>282</a>
348
+ <a name='L283'></a><a href='#L283'>283</a>
349
+ <a name='L284'></a><a href='#L284'>284</a>
350
+ <a name='L285'></a><a href='#L285'>285</a>
351
+ <a name='L286'></a><a href='#L286'>286</a>
352
+ <a name='L287'></a><a href='#L287'>287</a>
353
+ <a name='L288'></a><a href='#L288'>288</a>
354
+ <a name='L289'></a><a href='#L289'>289</a>
355
+ <a name='L290'></a><a href='#L290'>290</a>
356
+ <a name='L291'></a><a href='#L291'>291</a>
357
+ <a name='L292'></a><a href='#L292'>292</a>
358
+ <a name='L293'></a><a href='#L293'>293</a>
359
+ <a name='L294'></a><a href='#L294'>294</a>
360
+ <a name='L295'></a><a href='#L295'>295</a>
361
+ <a name='L296'></a><a href='#L296'>296</a>
362
+ <a name='L297'></a><a href='#L297'>297</a>
363
+ <a name='L298'></a><a href='#L298'>298</a>
364
+ <a name='L299'></a><a href='#L299'>299</a>
365
+ <a name='L300'></a><a href='#L300'>300</a>
366
+ <a name='L301'></a><a href='#L301'>301</a>
367
+ <a name='L302'></a><a href='#L302'>302</a>
368
+ <a name='L303'></a><a href='#L303'>303</a>
369
+ <a name='L304'></a><a href='#L304'>304</a>
370
+ <a name='L305'></a><a href='#L305'>305</a>
371
+ <a name='L306'></a><a href='#L306'>306</a>
372
+ <a name='L307'></a><a href='#L307'>307</a>
373
+ <a name='L308'></a><a href='#L308'>308</a>
374
+ <a name='L309'></a><a href='#L309'>309</a>
375
+ <a name='L310'></a><a href='#L310'>310</a>
376
+ <a name='L311'></a><a href='#L311'>311</a>
377
+ <a name='L312'></a><a href='#L312'>312</a>
378
+ <a name='L313'></a><a href='#L313'>313</a>
379
+ <a name='L314'></a><a href='#L314'>314</a>
380
+ <a name='L315'></a><a href='#L315'>315</a>
381
+ <a name='L316'></a><a href='#L316'>316</a>
382
+ <a name='L317'></a><a href='#L317'>317</a>
383
+ <a name='L318'></a><a href='#L318'>318</a>
384
+ <a name='L319'></a><a href='#L319'>319</a>
385
+ <a name='L320'></a><a href='#L320'>320</a>
386
+ <a name='L321'></a><a href='#L321'>321</a>
387
+ <a name='L322'></a><a href='#L322'>322</a>
388
+ <a name='L323'></a><a href='#L323'>323</a>
389
+ <a name='L324'></a><a href='#L324'>324</a>
390
+ <a name='L325'></a><a href='#L325'>325</a>
391
+ <a name='L326'></a><a href='#L326'>326</a>
392
+ <a name='L327'></a><a href='#L327'>327</a>
393
+ <a name='L328'></a><a href='#L328'>328</a>
394
+ <a name='L329'></a><a href='#L329'>329</a>
395
+ <a name='L330'></a><a href='#L330'>330</a>
396
+ <a name='L331'></a><a href='#L331'>331</a>
397
+ <a name='L332'></a><a href='#L332'>332</a>
398
+ <a name='L333'></a><a href='#L333'>333</a>
399
+ <a name='L334'></a><a href='#L334'>334</a>
400
+ <a name='L335'></a><a href='#L335'>335</a>
401
+ <a name='L336'></a><a href='#L336'>336</a>
402
+ <a name='L337'></a><a href='#L337'>337</a>
403
+ <a name='L338'></a><a href='#L338'>338</a>
404
+ <a name='L339'></a><a href='#L339'>339</a>
405
+ <a name='L340'></a><a href='#L340'>340</a>
406
+ <a name='L341'></a><a href='#L341'>341</a>
407
+ <a name='L342'></a><a href='#L342'>342</a>
408
+ <a name='L343'></a><a href='#L343'>343</a>
409
+ <a name='L344'></a><a href='#L344'>344</a>
410
+ <a name='L345'></a><a href='#L345'>345</a>
411
+ <a name='L346'></a><a href='#L346'>346</a>
412
+ <a name='L347'></a><a href='#L347'>347</a>
413
+ <a name='L348'></a><a href='#L348'>348</a>
414
+ <a name='L349'></a><a href='#L349'>349</a>
415
+ <a name='L350'></a><a href='#L350'>350</a>
416
+ <a name='L351'></a><a href='#L351'>351</a>
417
+ <a name='L352'></a><a href='#L352'>352</a>
418
+ <a name='L353'></a><a href='#L353'>353</a>
419
+ <a name='L354'></a><a href='#L354'>354</a>
420
+ <a name='L355'></a><a href='#L355'>355</a>
421
+ <a name='L356'></a><a href='#L356'>356</a>
422
+ <a name='L357'></a><a href='#L357'>357</a>
423
+ <a name='L358'></a><a href='#L358'>358</a>
424
+ <a name='L359'></a><a href='#L359'>359</a>
425
+ <a name='L360'></a><a href='#L360'>360</a>
426
+ <a name='L361'></a><a href='#L361'>361</a>
427
+ <a name='L362'></a><a href='#L362'>362</a>
428
+ <a name='L363'></a><a href='#L363'>363</a>
429
+ <a name='L364'></a><a href='#L364'>364</a>
430
+ <a name='L365'></a><a href='#L365'>365</a>
431
+ <a name='L366'></a><a href='#L366'>366</a>
432
+ <a name='L367'></a><a href='#L367'>367</a>
433
+ <a name='L368'></a><a href='#L368'>368</a>
434
+ <a name='L369'></a><a href='#L369'>369</a>
435
+ <a name='L370'></a><a href='#L370'>370</a>
436
+ <a name='L371'></a><a href='#L371'>371</a>
437
+ <a name='L372'></a><a href='#L372'>372</a>
438
+ <a name='L373'></a><a href='#L373'>373</a>
439
+ <a name='L374'></a><a href='#L374'>374</a>
440
+ <a name='L375'></a><a href='#L375'>375</a>
441
+ <a name='L376'></a><a href='#L376'>376</a>
442
+ <a name='L377'></a><a href='#L377'>377</a>
443
+ <a name='L378'></a><a href='#L378'>378</a>
444
+ <a name='L379'></a><a href='#L379'>379</a>
445
+ <a name='L380'></a><a href='#L380'>380</a>
446
+ <a name='L381'></a><a href='#L381'>381</a>
447
+ <a name='L382'></a><a href='#L382'>382</a>
448
+ <a name='L383'></a><a href='#L383'>383</a>
449
+ <a name='L384'></a><a href='#L384'>384</a>
450
+ <a name='L385'></a><a href='#L385'>385</a>
451
+ <a name='L386'></a><a href='#L386'>386</a>
452
+ <a name='L387'></a><a href='#L387'>387</a>
453
+ <a name='L388'></a><a href='#L388'>388</a>
454
+ <a name='L389'></a><a href='#L389'>389</a>
455
+ <a name='L390'></a><a href='#L390'>390</a>
456
+ <a name='L391'></a><a href='#L391'>391</a>
457
+ <a name='L392'></a><a href='#L392'>392</a>
458
+ <a name='L393'></a><a href='#L393'>393</a>
459
+ <a name='L394'></a><a href='#L394'>394</a>
460
+ <a name='L395'></a><a href='#L395'>395</a>
461
+ <a name='L396'></a><a href='#L396'>396</a>
462
+ <a name='L397'></a><a href='#L397'>397</a>
463
+ <a name='L398'></a><a href='#L398'>398</a>
464
+ <a name='L399'></a><a href='#L399'>399</a>
465
+ <a name='L400'></a><a href='#L400'>400</a>
466
+ <a name='L401'></a><a href='#L401'>401</a>
467
+ <a name='L402'></a><a href='#L402'>402</a>
468
+ <a name='L403'></a><a href='#L403'>403</a>
469
+ <a name='L404'></a><a href='#L404'>404</a>
470
+ <a name='L405'></a><a href='#L405'>405</a>
471
+ <a name='L406'></a><a href='#L406'>406</a>
472
+ <a name='L407'></a><a href='#L407'>407</a>
473
+ <a name='L408'></a><a href='#L408'>408</a>
474
+ <a name='L409'></a><a href='#L409'>409</a>
475
+ <a name='L410'></a><a href='#L410'>410</a>
476
+ <a name='L411'></a><a href='#L411'>411</a>
477
+ <a name='L412'></a><a href='#L412'>412</a>
478
+ <a name='L413'></a><a href='#L413'>413</a>
479
+ <a name='L414'></a><a href='#L414'>414</a>
480
+ <a name='L415'></a><a href='#L415'>415</a>
481
+ <a name='L416'></a><a href='#L416'>416</a>
482
+ <a name='L417'></a><a href='#L417'>417</a>
483
+ <a name='L418'></a><a href='#L418'>418</a>
484
+ <a name='L419'></a><a href='#L419'>419</a>
485
+ <a name='L420'></a><a href='#L420'>420</a>
486
+ <a name='L421'></a><a href='#L421'>421</a>
487
+ <a name='L422'></a><a href='#L422'>422</a>
488
+ <a name='L423'></a><a href='#L423'>423</a>
489
+ <a name='L424'></a><a href='#L424'>424</a>
490
+ <a name='L425'></a><a href='#L425'>425</a>
491
+ <a name='L426'></a><a href='#L426'>426</a>
492
+ <a name='L427'></a><a href='#L427'>427</a>
493
+ <a name='L428'></a><a href='#L428'>428</a>
494
+ <a name='L429'></a><a href='#L429'>429</a>
495
+ <a name='L430'></a><a href='#L430'>430</a>
496
+ <a name='L431'></a><a href='#L431'>431</a>
497
+ <a name='L432'></a><a href='#L432'>432</a>
498
+ <a name='L433'></a><a href='#L433'>433</a>
499
+ <a name='L434'></a><a href='#L434'>434</a>
500
+ <a name='L435'></a><a href='#L435'>435</a>
501
+ <a name='L436'></a><a href='#L436'>436</a>
502
+ <a name='L437'></a><a href='#L437'>437</a>
503
+ <a name='L438'></a><a href='#L438'>438</a>
504
+ <a name='L439'></a><a href='#L439'>439</a>
505
+ <a name='L440'></a><a href='#L440'>440</a>
506
+ <a name='L441'></a><a href='#L441'>441</a>
507
+ <a name='L442'></a><a href='#L442'>442</a>
508
+ <a name='L443'></a><a href='#L443'>443</a>
509
+ <a name='L444'></a><a href='#L444'>444</a>
510
+ <a name='L445'></a><a href='#L445'>445</a>
511
+ <a name='L446'></a><a href='#L446'>446</a>
512
+ <a name='L447'></a><a href='#L447'>447</a>
513
+ <a name='L448'></a><a href='#L448'>448</a>
514
+ <a name='L449'></a><a href='#L449'>449</a>
515
+ <a name='L450'></a><a href='#L450'>450</a>
516
+ <a name='L451'></a><a href='#L451'>451</a>
517
+ <a name='L452'></a><a href='#L452'>452</a>
518
+ <a name='L453'></a><a href='#L453'>453</a>
519
+ <a name='L454'></a><a href='#L454'>454</a>
520
+ <a name='L455'></a><a href='#L455'>455</a>
521
+ <a name='L456'></a><a href='#L456'>456</a>
522
+ <a name='L457'></a><a href='#L457'>457</a>
523
+ <a name='L458'></a><a href='#L458'>458</a>
524
+ <a name='L459'></a><a href='#L459'>459</a>
525
+ <a name='L460'></a><a href='#L460'>460</a>
526
+ <a name='L461'></a><a href='#L461'>461</a>
527
+ <a name='L462'></a><a href='#L462'>462</a>
528
+ <a name='L463'></a><a href='#L463'>463</a>
529
+ <a name='L464'></a><a href='#L464'>464</a>
530
+ <a name='L465'></a><a href='#L465'>465</a>
531
+ <a name='L466'></a><a href='#L466'>466</a>
532
+ <a name='L467'></a><a href='#L467'>467</a>
533
+ <a name='L468'></a><a href='#L468'>468</a>
534
+ <a name='L469'></a><a href='#L469'>469</a>
535
+ <a name='L470'></a><a href='#L470'>470</a>
536
+ <a name='L471'></a><a href='#L471'>471</a>
537
+ <a name='L472'></a><a href='#L472'>472</a>
538
+ <a name='L473'></a><a href='#L473'>473</a>
539
+ <a name='L474'></a><a href='#L474'>474</a>
540
+ <a name='L475'></a><a href='#L475'>475</a>
541
+ <a name='L476'></a><a href='#L476'>476</a>
542
+ <a name='L477'></a><a href='#L477'>477</a>
543
+ <a name='L478'></a><a href='#L478'>478</a>
544
+ <a name='L479'></a><a href='#L479'>479</a>
545
+ <a name='L480'></a><a href='#L480'>480</a>
546
+ <a name='L481'></a><a href='#L481'>481</a>
547
+ <a name='L482'></a><a href='#L482'>482</a>
548
+ <a name='L483'></a><a href='#L483'>483</a>
549
+ <a name='L484'></a><a href='#L484'>484</a>
550
+ <a name='L485'></a><a href='#L485'>485</a>
551
+ <a name='L486'></a><a href='#L486'>486</a>
552
+ <a name='L487'></a><a href='#L487'>487</a>
553
+ <a name='L488'></a><a href='#L488'>488</a>
554
+ <a name='L489'></a><a href='#L489'>489</a>
555
+ <a name='L490'></a><a href='#L490'>490</a>
556
+ <a name='L491'></a><a href='#L491'>491</a>
557
+ <a name='L492'></a><a href='#L492'>492</a>
558
+ <a name='L493'></a><a href='#L493'>493</a>
559
+ <a name='L494'></a><a href='#L494'>494</a>
560
+ <a name='L495'></a><a href='#L495'>495</a>
561
+ <a name='L496'></a><a href='#L496'>496</a>
562
+ <a name='L497'></a><a href='#L497'>497</a>
563
+ <a name='L498'></a><a href='#L498'>498</a>
564
+ <a name='L499'></a><a href='#L499'>499</a>
565
+ <a name='L500'></a><a href='#L500'>500</a>
566
+ <a name='L501'></a><a href='#L501'>501</a>
567
+ <a name='L502'></a><a href='#L502'>502</a>
568
+ <a name='L503'></a><a href='#L503'>503</a>
569
+ <a name='L504'></a><a href='#L504'>504</a>
570
+ <a name='L505'></a><a href='#L505'>505</a>
571
+ <a name='L506'></a><a href='#L506'>506</a>
572
+ <a name='L507'></a><a href='#L507'>507</a>
573
+ <a name='L508'></a><a href='#L508'>508</a>
574
+ <a name='L509'></a><a href='#L509'>509</a>
575
+ <a name='L510'></a><a href='#L510'>510</a>
576
+ <a name='L511'></a><a href='#L511'>511</a>
577
+ <a name='L512'></a><a href='#L512'>512</a>
578
+ <a name='L513'></a><a href='#L513'>513</a>
579
+ <a name='L514'></a><a href='#L514'>514</a>
580
+ <a name='L515'></a><a href='#L515'>515</a>
581
+ <a name='L516'></a><a href='#L516'>516</a>
582
+ <a name='L517'></a><a href='#L517'>517</a>
583
+ <a name='L518'></a><a href='#L518'>518</a>
584
+ <a name='L519'></a><a href='#L519'>519</a>
585
+ <a name='L520'></a><a href='#L520'>520</a>
586
+ <a name='L521'></a><a href='#L521'>521</a>
587
+ <a name='L522'></a><a href='#L522'>522</a>
588
+ <a name='L523'></a><a href='#L523'>523</a>
589
+ <a name='L524'></a><a href='#L524'>524</a>
590
+ <a name='L525'></a><a href='#L525'>525</a>
591
+ <a name='L526'></a><a href='#L526'>526</a>
592
+ <a name='L527'></a><a href='#L527'>527</a>
593
+ <a name='L528'></a><a href='#L528'>528</a>
594
+ <a name='L529'></a><a href='#L529'>529</a>
595
+ <a name='L530'></a><a href='#L530'>530</a>
596
+ <a name='L531'></a><a href='#L531'>531</a>
597
+ <a name='L532'></a><a href='#L532'>532</a>
598
+ <a name='L533'></a><a href='#L533'>533</a>
599
+ <a name='L534'></a><a href='#L534'>534</a>
600
+ <a name='L535'></a><a href='#L535'>535</a>
601
+ <a name='L536'></a><a href='#L536'>536</a>
602
+ <a name='L537'></a><a href='#L537'>537</a>
603
+ <a name='L538'></a><a href='#L538'>538</a>
604
+ <a name='L539'></a><a href='#L539'>539</a>
605
+ <a name='L540'></a><a href='#L540'>540</a>
606
+ <a name='L541'></a><a href='#L541'>541</a>
607
+ <a name='L542'></a><a href='#L542'>542</a>
608
+ <a name='L543'></a><a href='#L543'>543</a>
609
+ <a name='L544'></a><a href='#L544'>544</a>
610
+ <a name='L545'></a><a href='#L545'>545</a>
611
+ <a name='L546'></a><a href='#L546'>546</a>
612
+ <a name='L547'></a><a href='#L547'>547</a>
613
+ <a name='L548'></a><a href='#L548'>548</a>
614
+ <a name='L549'></a><a href='#L549'>549</a>
615
+ <a name='L550'></a><a href='#L550'>550</a>
616
+ <a name='L551'></a><a href='#L551'>551</a>
617
+ <a name='L552'></a><a href='#L552'>552</a>
618
+ <a name='L553'></a><a href='#L553'>553</a>
619
+ <a name='L554'></a><a href='#L554'>554</a>
620
+ <a name='L555'></a><a href='#L555'>555</a>
621
+ <a name='L556'></a><a href='#L556'>556</a>
622
+ <a name='L557'></a><a href='#L557'>557</a>
623
+ <a name='L558'></a><a href='#L558'>558</a>
624
+ <a name='L559'></a><a href='#L559'>559</a>
625
+ <a name='L560'></a><a href='#L560'>560</a>
626
+ <a name='L561'></a><a href='#L561'>561</a>
627
+ <a name='L562'></a><a href='#L562'>562</a>
628
+ <a name='L563'></a><a href='#L563'>563</a>
629
+ <a name='L564'></a><a href='#L564'>564</a>
630
+ <a name='L565'></a><a href='#L565'>565</a>
631
+ <a name='L566'></a><a href='#L566'>566</a>
632
+ <a name='L567'></a><a href='#L567'>567</a>
633
+ <a name='L568'></a><a href='#L568'>568</a>
634
+ <a name='L569'></a><a href='#L569'>569</a>
635
+ <a name='L570'></a><a href='#L570'>570</a>
636
+ <a name='L571'></a><a href='#L571'>571</a>
637
+ <a name='L572'></a><a href='#L572'>572</a>
638
+ <a name='L573'></a><a href='#L573'>573</a>
639
+ <a name='L574'></a><a href='#L574'>574</a>
640
+ <a name='L575'></a><a href='#L575'>575</a>
641
+ <a name='L576'></a><a href='#L576'>576</a>
642
+ <a name='L577'></a><a href='#L577'>577</a>
643
+ <a name='L578'></a><a href='#L578'>578</a>
644
+ <a name='L579'></a><a href='#L579'>579</a>
645
+ <a name='L580'></a><a href='#L580'>580</a>
646
+ <a name='L581'></a><a href='#L581'>581</a>
647
+ <a name='L582'></a><a href='#L582'>582</a>
648
+ <a name='L583'></a><a href='#L583'>583</a>
649
+ <a name='L584'></a><a href='#L584'>584</a>
650
+ <a name='L585'></a><a href='#L585'>585</a>
651
+ <a name='L586'></a><a href='#L586'>586</a>
652
+ <a name='L587'></a><a href='#L587'>587</a>
653
+ <a name='L588'></a><a href='#L588'>588</a>
654
+ <a name='L589'></a><a href='#L589'>589</a>
655
+ <a name='L590'></a><a href='#L590'>590</a>
656
+ <a name='L591'></a><a href='#L591'>591</a>
657
+ <a name='L592'></a><a href='#L592'>592</a>
658
+ <a name='L593'></a><a href='#L593'>593</a>
659
+ <a name='L594'></a><a href='#L594'>594</a>
660
+ <a name='L595'></a><a href='#L595'>595</a>
661
+ <a name='L596'></a><a href='#L596'>596</a>
662
+ <a name='L597'></a><a href='#L597'>597</a>
663
+ <a name='L598'></a><a href='#L598'>598</a>
664
+ <a name='L599'></a><a href='#L599'>599</a>
665
+ <a name='L600'></a><a href='#L600'>600</a>
666
+ <a name='L601'></a><a href='#L601'>601</a>
667
+ <a name='L602'></a><a href='#L602'>602</a>
668
+ <a name='L603'></a><a href='#L603'>603</a>
669
+ <a name='L604'></a><a href='#L604'>604</a>
670
+ <a name='L605'></a><a href='#L605'>605</a>
671
+ <a name='L606'></a><a href='#L606'>606</a>
672
+ <a name='L607'></a><a href='#L607'>607</a>
673
+ <a name='L608'></a><a href='#L608'>608</a>
674
+ <a name='L609'></a><a href='#L609'>609</a>
675
+ <a name='L610'></a><a href='#L610'>610</a>
676
+ <a name='L611'></a><a href='#L611'>611</a>
677
+ <a name='L612'></a><a href='#L612'>612</a>
678
+ <a name='L613'></a><a href='#L613'>613</a>
679
+ <a name='L614'></a><a href='#L614'>614</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
680
+ <span class="cline-any cline-neutral">&nbsp;</span>
681
+ <span class="cline-any cline-neutral">&nbsp;</span>
682
+ <span class="cline-any cline-neutral">&nbsp;</span>
683
+ <span class="cline-any cline-neutral">&nbsp;</span>
684
+ <span class="cline-any cline-neutral">&nbsp;</span>
685
+ <span class="cline-any cline-neutral">&nbsp;</span>
686
+ <span class="cline-any cline-neutral">&nbsp;</span>
687
+ <span class="cline-any cline-neutral">&nbsp;</span>
688
+ <span class="cline-any cline-neutral">&nbsp;</span>
689
+ <span class="cline-any cline-neutral">&nbsp;</span>
690
+ <span class="cline-any cline-neutral">&nbsp;</span>
691
+ <span class="cline-any cline-neutral">&nbsp;</span>
692
+ <span class="cline-any cline-neutral">&nbsp;</span>
693
+ <span class="cline-any cline-neutral">&nbsp;</span>
694
+ <span class="cline-any cline-neutral">&nbsp;</span>
695
+ <span class="cline-any cline-neutral">&nbsp;</span>
696
+ <span class="cline-any cline-neutral">&nbsp;</span>
697
+ <span class="cline-any cline-neutral">&nbsp;</span>
698
+ <span class="cline-any cline-neutral">&nbsp;</span>
699
+ <span class="cline-any cline-neutral">&nbsp;</span>
700
+ <span class="cline-any cline-neutral">&nbsp;</span>
701
+ <span class="cline-any cline-neutral">&nbsp;</span>
702
+ <span class="cline-any cline-neutral">&nbsp;</span>
703
+ <span class="cline-any cline-neutral">&nbsp;</span>
704
+ <span class="cline-any cline-neutral">&nbsp;</span>
705
+ <span class="cline-any cline-neutral">&nbsp;</span>
706
+ <span class="cline-any cline-neutral">&nbsp;</span>
707
+ <span class="cline-any cline-neutral">&nbsp;</span>
708
+ <span class="cline-any cline-neutral">&nbsp;</span>
709
+ <span class="cline-any cline-neutral">&nbsp;</span>
710
+ <span class="cline-any cline-neutral">&nbsp;</span>
711
+ <span class="cline-any cline-neutral">&nbsp;</span>
712
+ <span class="cline-any cline-neutral">&nbsp;</span>
713
+ <span class="cline-any cline-neutral">&nbsp;</span>
714
+ <span class="cline-any cline-neutral">&nbsp;</span>
715
+ <span class="cline-any cline-neutral">&nbsp;</span>
716
+ <span class="cline-any cline-neutral">&nbsp;</span>
717
+ <span class="cline-any cline-neutral">&nbsp;</span>
718
+ <span class="cline-any cline-neutral">&nbsp;</span>
719
+ <span class="cline-any cline-neutral">&nbsp;</span>
720
+ <span class="cline-any cline-neutral">&nbsp;</span>
721
+ <span class="cline-any cline-neutral">&nbsp;</span>
722
+ <span class="cline-any cline-neutral">&nbsp;</span>
723
+ <span class="cline-any cline-neutral">&nbsp;</span>
724
+ <span class="cline-any cline-neutral">&nbsp;</span>
725
+ <span class="cline-any cline-neutral">&nbsp;</span>
726
+ <span class="cline-any cline-neutral">&nbsp;</span>
727
+ <span class="cline-any cline-neutral">&nbsp;</span>
728
+ <span class="cline-any cline-neutral">&nbsp;</span>
729
+ <span class="cline-any cline-neutral">&nbsp;</span>
730
+ <span class="cline-any cline-neutral">&nbsp;</span>
731
+ <span class="cline-any cline-yes">27x</span>
732
+ <span class="cline-any cline-neutral">&nbsp;</span>
733
+ <span class="cline-any cline-neutral">&nbsp;</span>
734
+ <span class="cline-any cline-neutral">&nbsp;</span>
735
+ <span class="cline-any cline-neutral">&nbsp;</span>
736
+ <span class="cline-any cline-neutral">&nbsp;</span>
737
+ <span class="cline-any cline-neutral">&nbsp;</span>
738
+ <span class="cline-any cline-yes">49x</span>
739
+ <span class="cline-any cline-neutral">&nbsp;</span>
740
+ <span class="cline-any cline-neutral">&nbsp;</span>
741
+ <span class="cline-any cline-neutral">&nbsp;</span>
742
+ <span class="cline-any cline-neutral">&nbsp;</span>
743
+ <span class="cline-any cline-neutral">&nbsp;</span>
744
+ <span class="cline-any cline-neutral">&nbsp;</span>
745
+ <span class="cline-any cline-neutral">&nbsp;</span>
746
+ <span class="cline-any cline-neutral">&nbsp;</span>
747
+ <span class="cline-any cline-yes">36x</span>
748
+ <span class="cline-any cline-neutral">&nbsp;</span>
749
+ <span class="cline-any cline-neutral">&nbsp;</span>
750
+ <span class="cline-any cline-neutral">&nbsp;</span>
751
+ <span class="cline-any cline-neutral">&nbsp;</span>
752
+ <span class="cline-any cline-neutral">&nbsp;</span>
753
+ <span class="cline-any cline-neutral">&nbsp;</span>
754
+ <span class="cline-any cline-neutral">&nbsp;</span>
755
+ <span class="cline-any cline-neutral">&nbsp;</span>
756
+ <span class="cline-any cline-yes">26x</span>
757
+ <span class="cline-any cline-neutral">&nbsp;</span>
758
+ <span class="cline-any cline-neutral">&nbsp;</span>
759
+ <span class="cline-any cline-neutral">&nbsp;</span>
760
+ <span class="cline-any cline-neutral">&nbsp;</span>
761
+ <span class="cline-any cline-neutral">&nbsp;</span>
762
+ <span class="cline-any cline-neutral">&nbsp;</span>
763
+ <span class="cline-any cline-neutral">&nbsp;</span>
764
+ <span class="cline-any cline-neutral">&nbsp;</span>
765
+ <span class="cline-any cline-yes">12x</span>
766
+ <span class="cline-any cline-neutral">&nbsp;</span>
767
+ <span class="cline-any cline-neutral">&nbsp;</span>
768
+ <span class="cline-any cline-neutral">&nbsp;</span>
769
+ <span class="cline-any cline-neutral">&nbsp;</span>
770
+ <span class="cline-any cline-neutral">&nbsp;</span>
771
+ <span class="cline-any cline-neutral">&nbsp;</span>
772
+ <span class="cline-any cline-neutral">&nbsp;</span>
773
+ <span class="cline-any cline-neutral">&nbsp;</span>
774
+ <span class="cline-any cline-neutral">&nbsp;</span>
775
+ <span class="cline-any cline-neutral">&nbsp;</span>
776
+ <span class="cline-any cline-neutral">&nbsp;</span>
777
+ <span class="cline-any cline-neutral">&nbsp;</span>
778
+ <span class="cline-any cline-neutral">&nbsp;</span>
779
+ <span class="cline-any cline-neutral">&nbsp;</span>
780
+ <span class="cline-any cline-neutral">&nbsp;</span>
781
+ <span class="cline-any cline-neutral">&nbsp;</span>
782
+ <span class="cline-any cline-neutral">&nbsp;</span>
783
+ <span class="cline-any cline-neutral">&nbsp;</span>
784
+ <span class="cline-any cline-neutral">&nbsp;</span>
785
+ <span class="cline-any cline-yes">28x</span>
786
+ <span class="cline-any cline-neutral">&nbsp;</span>
787
+ <span class="cline-any cline-yes">28x</span>
788
+ <span class="cline-any cline-yes">2x</span>
789
+ <span class="cline-any cline-neutral">&nbsp;</span>
790
+ <span class="cline-any cline-neutral">&nbsp;</span>
791
+ <span class="cline-any cline-neutral">&nbsp;</span>
792
+ <span class="cline-any cline-yes">26x</span>
793
+ <span class="cline-any cline-neutral">&nbsp;</span>
794
+ <span class="cline-any cline-yes">26x</span>
795
+ <span class="cline-any cline-yes">5x</span>
796
+ <span class="cline-any cline-neutral">&nbsp;</span>
797
+ <span class="cline-any cline-neutral">&nbsp;</span>
798
+ <span class="cline-any cline-neutral">&nbsp;</span>
799
+ <span class="cline-any cline-neutral">&nbsp;</span>
800
+ <span class="cline-any cline-neutral">&nbsp;</span>
801
+ <span class="cline-any cline-neutral">&nbsp;</span>
802
+ <span class="cline-any cline-yes">21x</span>
803
+ <span class="cline-any cline-neutral">&nbsp;</span>
804
+ <span class="cline-any cline-neutral">&nbsp;</span>
805
+ <span class="cline-any cline-neutral">&nbsp;</span>
806
+ <span class="cline-any cline-neutral">&nbsp;</span>
807
+ <span class="cline-any cline-neutral">&nbsp;</span>
808
+ <span class="cline-any cline-neutral">&nbsp;</span>
809
+ <span class="cline-any cline-neutral">&nbsp;</span>
810
+ <span class="cline-any cline-neutral">&nbsp;</span>
811
+ <span class="cline-any cline-neutral">&nbsp;</span>
812
+ <span class="cline-any cline-neutral">&nbsp;</span>
813
+ <span class="cline-any cline-neutral">&nbsp;</span>
814
+ <span class="cline-any cline-neutral">&nbsp;</span>
815
+ <span class="cline-any cline-neutral">&nbsp;</span>
816
+ <span class="cline-any cline-neutral">&nbsp;</span>
817
+ <span class="cline-any cline-neutral">&nbsp;</span>
818
+ <span class="cline-any cline-neutral">&nbsp;</span>
819
+ <span class="cline-any cline-neutral">&nbsp;</span>
820
+ <span class="cline-any cline-neutral">&nbsp;</span>
821
+ <span class="cline-any cline-neutral">&nbsp;</span>
822
+ <span class="cline-any cline-neutral">&nbsp;</span>
823
+ <span class="cline-any cline-neutral">&nbsp;</span>
824
+ <span class="cline-any cline-yes">3x</span>
825
+ <span class="cline-any cline-neutral">&nbsp;</span>
826
+ <span class="cline-any cline-yes">3x</span>
827
+ <span class="cline-any cline-yes">3x</span>
828
+ <span class="cline-any cline-neutral">&nbsp;</span>
829
+ <span class="cline-any cline-neutral">&nbsp;</span>
830
+ <span class="cline-any cline-neutral">&nbsp;</span>
831
+ <span class="cline-any cline-neutral">&nbsp;</span>
832
+ <span class="cline-any cline-yes">3x</span>
833
+ <span class="cline-any cline-neutral">&nbsp;</span>
834
+ <span class="cline-any cline-neutral">&nbsp;</span>
835
+ <span class="cline-any cline-neutral">&nbsp;</span>
836
+ <span class="cline-any cline-neutral">&nbsp;</span>
837
+ <span class="cline-any cline-neutral">&nbsp;</span>
838
+ <span class="cline-any cline-neutral">&nbsp;</span>
839
+ <span class="cline-any cline-neutral">&nbsp;</span>
840
+ <span class="cline-any cline-neutral">&nbsp;</span>
841
+ <span class="cline-any cline-neutral">&nbsp;</span>
842
+ <span class="cline-any cline-neutral">&nbsp;</span>
843
+ <span class="cline-any cline-neutral">&nbsp;</span>
844
+ <span class="cline-any cline-neutral">&nbsp;</span>
845
+ <span class="cline-any cline-neutral">&nbsp;</span>
846
+ <span class="cline-any cline-neutral">&nbsp;</span>
847
+ <span class="cline-any cline-neutral">&nbsp;</span>
848
+ <span class="cline-any cline-neutral">&nbsp;</span>
849
+ <span class="cline-any cline-neutral">&nbsp;</span>
850
+ <span class="cline-any cline-neutral">&nbsp;</span>
851
+ <span class="cline-any cline-neutral">&nbsp;</span>
852
+ <span class="cline-any cline-neutral">&nbsp;</span>
853
+ <span class="cline-any cline-neutral">&nbsp;</span>
854
+ <span class="cline-any cline-neutral">&nbsp;</span>
855
+ <span class="cline-any cline-yes">1x</span>
856
+ <span class="cline-any cline-yes">1x</span>
857
+ <span class="cline-any cline-neutral">&nbsp;</span>
858
+ <span class="cline-any cline-neutral">&nbsp;</span>
859
+ <span class="cline-any cline-neutral">&nbsp;</span>
860
+ <span class="cline-any cline-neutral">&nbsp;</span>
861
+ <span class="cline-any cline-neutral">&nbsp;</span>
862
+ <span class="cline-any cline-neutral">&nbsp;</span>
863
+ <span class="cline-any cline-neutral">&nbsp;</span>
864
+ <span class="cline-any cline-neutral">&nbsp;</span>
865
+ <span class="cline-any cline-neutral">&nbsp;</span>
866
+ <span class="cline-any cline-neutral">&nbsp;</span>
867
+ <span class="cline-any cline-neutral">&nbsp;</span>
868
+ <span class="cline-any cline-neutral">&nbsp;</span>
869
+ <span class="cline-any cline-neutral">&nbsp;</span>
870
+ <span class="cline-any cline-neutral">&nbsp;</span>
871
+ <span class="cline-any cline-neutral">&nbsp;</span>
872
+ <span class="cline-any cline-neutral">&nbsp;</span>
873
+ <span class="cline-any cline-neutral">&nbsp;</span>
874
+ <span class="cline-any cline-neutral">&nbsp;</span>
875
+ <span class="cline-any cline-neutral">&nbsp;</span>
876
+ <span class="cline-any cline-neutral">&nbsp;</span>
877
+ <span class="cline-any cline-neutral">&nbsp;</span>
878
+ <span class="cline-any cline-neutral">&nbsp;</span>
879
+ <span class="cline-any cline-neutral">&nbsp;</span>
880
+ <span class="cline-any cline-yes">2x</span>
881
+ <span class="cline-any cline-neutral">&nbsp;</span>
882
+ <span class="cline-any cline-yes">1x</span>
883
+ <span class="cline-any cline-neutral">&nbsp;</span>
884
+ <span class="cline-any cline-neutral">&nbsp;</span>
885
+ <span class="cline-any cline-yes">1x</span>
886
+ <span class="cline-any cline-neutral">&nbsp;</span>
887
+ <span class="cline-any cline-neutral">&nbsp;</span>
888
+ <span class="cline-any cline-neutral">&nbsp;</span>
889
+ <span class="cline-any cline-neutral">&nbsp;</span>
890
+ <span class="cline-any cline-neutral">&nbsp;</span>
891
+ <span class="cline-any cline-neutral">&nbsp;</span>
892
+ <span class="cline-any cline-neutral">&nbsp;</span>
893
+ <span class="cline-any cline-neutral">&nbsp;</span>
894
+ <span class="cline-any cline-neutral">&nbsp;</span>
895
+ <span class="cline-any cline-neutral">&nbsp;</span>
896
+ <span class="cline-any cline-neutral">&nbsp;</span>
897
+ <span class="cline-any cline-neutral">&nbsp;</span>
898
+ <span class="cline-any cline-neutral">&nbsp;</span>
899
+ <span class="cline-any cline-neutral">&nbsp;</span>
900
+ <span class="cline-any cline-yes">1x</span>
901
+ <span class="cline-any cline-yes">1x</span>
902
+ <span class="cline-any cline-neutral">&nbsp;</span>
903
+ <span class="cline-any cline-neutral">&nbsp;</span>
904
+ <span class="cline-any cline-no">&nbsp;</span>
905
+ <span class="cline-any cline-neutral">&nbsp;</span>
906
+ <span class="cline-any cline-neutral">&nbsp;</span>
907
+ <span class="cline-any cline-neutral">&nbsp;</span>
908
+ <span class="cline-any cline-neutral">&nbsp;</span>
909
+ <span class="cline-any cline-neutral">&nbsp;</span>
910
+ <span class="cline-any cline-neutral">&nbsp;</span>
911
+ <span class="cline-any cline-neutral">&nbsp;</span>
912
+ <span class="cline-any cline-neutral">&nbsp;</span>
913
+ <span class="cline-any cline-neutral">&nbsp;</span>
914
+ <span class="cline-any cline-neutral">&nbsp;</span>
915
+ <span class="cline-any cline-neutral">&nbsp;</span>
916
+ <span class="cline-any cline-neutral">&nbsp;</span>
917
+ <span class="cline-any cline-neutral">&nbsp;</span>
918
+ <span class="cline-any cline-neutral">&nbsp;</span>
919
+ <span class="cline-any cline-neutral">&nbsp;</span>
920
+ <span class="cline-any cline-neutral">&nbsp;</span>
921
+ <span class="cline-any cline-neutral">&nbsp;</span>
922
+ <span class="cline-any cline-neutral">&nbsp;</span>
923
+ <span class="cline-any cline-neutral">&nbsp;</span>
924
+ <span class="cline-any cline-neutral">&nbsp;</span>
925
+ <span class="cline-any cline-yes">1x</span>
926
+ <span class="cline-any cline-neutral">&nbsp;</span>
927
+ <span class="cline-any cline-neutral">&nbsp;</span>
928
+ <span class="cline-any cline-yes">1x</span>
929
+ <span class="cline-any cline-neutral">&nbsp;</span>
930
+ <span class="cline-any cline-yes">1x</span>
931
+ <span class="cline-any cline-neutral">&nbsp;</span>
932
+ <span class="cline-any cline-yes">1x</span>
933
+ <span class="cline-any cline-neutral">&nbsp;</span>
934
+ <span class="cline-any cline-neutral">&nbsp;</span>
935
+ <span class="cline-any cline-neutral">&nbsp;</span>
936
+ <span class="cline-any cline-neutral">&nbsp;</span>
937
+ <span class="cline-any cline-neutral">&nbsp;</span>
938
+ <span class="cline-any cline-neutral">&nbsp;</span>
939
+ <span class="cline-any cline-neutral">&nbsp;</span>
940
+ <span class="cline-any cline-neutral">&nbsp;</span>
941
+ <span class="cline-any cline-neutral">&nbsp;</span>
942
+ <span class="cline-any cline-yes">1x</span>
943
+ <span class="cline-any cline-neutral">&nbsp;</span>
944
+ <span class="cline-any cline-neutral">&nbsp;</span>
945
+ <span class="cline-any cline-neutral">&nbsp;</span>
946
+ <span class="cline-any cline-neutral">&nbsp;</span>
947
+ <span class="cline-any cline-neutral">&nbsp;</span>
948
+ <span class="cline-any cline-neutral">&nbsp;</span>
949
+ <span class="cline-any cline-neutral">&nbsp;</span>
950
+ <span class="cline-any cline-neutral">&nbsp;</span>
951
+ <span class="cline-any cline-neutral">&nbsp;</span>
952
+ <span class="cline-any cline-neutral">&nbsp;</span>
953
+ <span class="cline-any cline-neutral">&nbsp;</span>
954
+ <span class="cline-any cline-neutral">&nbsp;</span>
955
+ <span class="cline-any cline-neutral">&nbsp;</span>
956
+ <span class="cline-any cline-neutral">&nbsp;</span>
957
+ <span class="cline-any cline-yes">1x</span>
958
+ <span class="cline-any cline-neutral">&nbsp;</span>
959
+ <span class="cline-any cline-neutral">&nbsp;</span>
960
+ <span class="cline-any cline-yes">1x</span>
961
+ <span class="cline-any cline-neutral">&nbsp;</span>
962
+ <span class="cline-any cline-yes">1x</span>
963
+ <span class="cline-any cline-neutral">&nbsp;</span>
964
+ <span class="cline-any cline-yes">1x</span>
965
+ <span class="cline-any cline-neutral">&nbsp;</span>
966
+ <span class="cline-any cline-neutral">&nbsp;</span>
967
+ <span class="cline-any cline-neutral">&nbsp;</span>
968
+ <span class="cline-any cline-neutral">&nbsp;</span>
969
+ <span class="cline-any cline-neutral">&nbsp;</span>
970
+ <span class="cline-any cline-neutral">&nbsp;</span>
971
+ <span class="cline-any cline-neutral">&nbsp;</span>
972
+ <span class="cline-any cline-neutral">&nbsp;</span>
973
+ <span class="cline-any cline-neutral">&nbsp;</span>
974
+ <span class="cline-any cline-yes">1x</span>
975
+ <span class="cline-any cline-neutral">&nbsp;</span>
976
+ <span class="cline-any cline-neutral">&nbsp;</span>
977
+ <span class="cline-any cline-neutral">&nbsp;</span>
978
+ <span class="cline-any cline-neutral">&nbsp;</span>
979
+ <span class="cline-any cline-neutral">&nbsp;</span>
980
+ <span class="cline-any cline-neutral">&nbsp;</span>
981
+ <span class="cline-any cline-neutral">&nbsp;</span>
982
+ <span class="cline-any cline-neutral">&nbsp;</span>
983
+ <span class="cline-any cline-neutral">&nbsp;</span>
984
+ <span class="cline-any cline-neutral">&nbsp;</span>
985
+ <span class="cline-any cline-neutral">&nbsp;</span>
986
+ <span class="cline-any cline-neutral">&nbsp;</span>
987
+ <span class="cline-any cline-neutral">&nbsp;</span>
988
+ <span class="cline-any cline-neutral">&nbsp;</span>
989
+ <span class="cline-any cline-neutral">&nbsp;</span>
990
+ <span class="cline-any cline-neutral">&nbsp;</span>
991
+ <span class="cline-any cline-neutral">&nbsp;</span>
992
+ <span class="cline-any cline-neutral">&nbsp;</span>
993
+ <span class="cline-any cline-yes">2x</span>
994
+ <span class="cline-any cline-neutral">&nbsp;</span>
995
+ <span class="cline-any cline-yes">1x</span>
996
+ <span class="cline-any cline-yes">1x</span>
997
+ <span class="cline-any cline-neutral">&nbsp;</span>
998
+ <span class="cline-any cline-yes">1x</span>
999
+ <span class="cline-any cline-neutral">&nbsp;</span>
1000
+ <span class="cline-any cline-neutral">&nbsp;</span>
1001
+ <span class="cline-any cline-neutral">&nbsp;</span>
1002
+ <span class="cline-any cline-neutral">&nbsp;</span>
1003
+ <span class="cline-any cline-neutral">&nbsp;</span>
1004
+ <span class="cline-any cline-neutral">&nbsp;</span>
1005
+ <span class="cline-any cline-neutral">&nbsp;</span>
1006
+ <span class="cline-any cline-neutral">&nbsp;</span>
1007
+ <span class="cline-any cline-neutral">&nbsp;</span>
1008
+ <span class="cline-any cline-yes">1x</span>
1009
+ <span class="cline-any cline-neutral">&nbsp;</span>
1010
+ <span class="cline-any cline-neutral">&nbsp;</span>
1011
+ <span class="cline-any cline-neutral">&nbsp;</span>
1012
+ <span class="cline-any cline-neutral">&nbsp;</span>
1013
+ <span class="cline-any cline-neutral">&nbsp;</span>
1014
+ <span class="cline-any cline-neutral">&nbsp;</span>
1015
+ <span class="cline-any cline-neutral">&nbsp;</span>
1016
+ <span class="cline-any cline-neutral">&nbsp;</span>
1017
+ <span class="cline-any cline-neutral">&nbsp;</span>
1018
+ <span class="cline-any cline-neutral">&nbsp;</span>
1019
+ <span class="cline-any cline-neutral">&nbsp;</span>
1020
+ <span class="cline-any cline-yes">1x</span>
1021
+ <span class="cline-any cline-neutral">&nbsp;</span>
1022
+ <span class="cline-any cline-yes">1x</span>
1023
+ <span class="cline-any cline-yes">1x</span>
1024
+ <span class="cline-any cline-neutral">&nbsp;</span>
1025
+ <span class="cline-any cline-yes">1x</span>
1026
+ <span class="cline-any cline-neutral">&nbsp;</span>
1027
+ <span class="cline-any cline-neutral">&nbsp;</span>
1028
+ <span class="cline-any cline-neutral">&nbsp;</span>
1029
+ <span class="cline-any cline-neutral">&nbsp;</span>
1030
+ <span class="cline-any cline-neutral">&nbsp;</span>
1031
+ <span class="cline-any cline-neutral">&nbsp;</span>
1032
+ <span class="cline-any cline-neutral">&nbsp;</span>
1033
+ <span class="cline-any cline-neutral">&nbsp;</span>
1034
+ <span class="cline-any cline-yes">1x</span>
1035
+ <span class="cline-any cline-neutral">&nbsp;</span>
1036
+ <span class="cline-any cline-neutral">&nbsp;</span>
1037
+ <span class="cline-any cline-neutral">&nbsp;</span>
1038
+ <span class="cline-any cline-neutral">&nbsp;</span>
1039
+ <span class="cline-any cline-neutral">&nbsp;</span>
1040
+ <span class="cline-any cline-neutral">&nbsp;</span>
1041
+ <span class="cline-any cline-neutral">&nbsp;</span>
1042
+ <span class="cline-any cline-neutral">&nbsp;</span>
1043
+ <span class="cline-any cline-neutral">&nbsp;</span>
1044
+ <span class="cline-any cline-neutral">&nbsp;</span>
1045
+ <span class="cline-any cline-neutral">&nbsp;</span>
1046
+ <span class="cline-any cline-yes">1x</span>
1047
+ <span class="cline-any cline-neutral">&nbsp;</span>
1048
+ <span class="cline-any cline-yes">1x</span>
1049
+ <span class="cline-any cline-yes">1x</span>
1050
+ <span class="cline-any cline-neutral">&nbsp;</span>
1051
+ <span class="cline-any cline-yes">1x</span>
1052
+ <span class="cline-any cline-neutral">&nbsp;</span>
1053
+ <span class="cline-any cline-neutral">&nbsp;</span>
1054
+ <span class="cline-any cline-neutral">&nbsp;</span>
1055
+ <span class="cline-any cline-neutral">&nbsp;</span>
1056
+ <span class="cline-any cline-neutral">&nbsp;</span>
1057
+ <span class="cline-any cline-neutral">&nbsp;</span>
1058
+ <span class="cline-any cline-neutral">&nbsp;</span>
1059
+ <span class="cline-any cline-neutral">&nbsp;</span>
1060
+ <span class="cline-any cline-yes">1x</span>
1061
+ <span class="cline-any cline-neutral">&nbsp;</span>
1062
+ <span class="cline-any cline-neutral">&nbsp;</span>
1063
+ <span class="cline-any cline-neutral">&nbsp;</span>
1064
+ <span class="cline-any cline-neutral">&nbsp;</span>
1065
+ <span class="cline-any cline-neutral">&nbsp;</span>
1066
+ <span class="cline-any cline-neutral">&nbsp;</span>
1067
+ <span class="cline-any cline-neutral">&nbsp;</span>
1068
+ <span class="cline-any cline-neutral">&nbsp;</span>
1069
+ <span class="cline-any cline-neutral">&nbsp;</span>
1070
+ <span class="cline-any cline-neutral">&nbsp;</span>
1071
+ <span class="cline-any cline-neutral">&nbsp;</span>
1072
+ <span class="cline-any cline-yes">1x</span>
1073
+ <span class="cline-any cline-neutral">&nbsp;</span>
1074
+ <span class="cline-any cline-yes">1x</span>
1075
+ <span class="cline-any cline-yes">1x</span>
1076
+ <span class="cline-any cline-neutral">&nbsp;</span>
1077
+ <span class="cline-any cline-yes">1x</span>
1078
+ <span class="cline-any cline-neutral">&nbsp;</span>
1079
+ <span class="cline-any cline-neutral">&nbsp;</span>
1080
+ <span class="cline-any cline-neutral">&nbsp;</span>
1081
+ <span class="cline-any cline-neutral">&nbsp;</span>
1082
+ <span class="cline-any cline-neutral">&nbsp;</span>
1083
+ <span class="cline-any cline-neutral">&nbsp;</span>
1084
+ <span class="cline-any cline-neutral">&nbsp;</span>
1085
+ <span class="cline-any cline-neutral">&nbsp;</span>
1086
+ <span class="cline-any cline-yes">1x</span>
1087
+ <span class="cline-any cline-neutral">&nbsp;</span>
1088
+ <span class="cline-any cline-neutral">&nbsp;</span>
1089
+ <span class="cline-any cline-neutral">&nbsp;</span>
1090
+ <span class="cline-any cline-neutral">&nbsp;</span>
1091
+ <span class="cline-any cline-neutral">&nbsp;</span>
1092
+ <span class="cline-any cline-neutral">&nbsp;</span>
1093
+ <span class="cline-any cline-neutral">&nbsp;</span>
1094
+ <span class="cline-any cline-neutral">&nbsp;</span>
1095
+ <span class="cline-any cline-neutral">&nbsp;</span>
1096
+ <span class="cline-any cline-neutral">&nbsp;</span>
1097
+ <span class="cline-any cline-neutral">&nbsp;</span>
1098
+ <span class="cline-any cline-neutral">&nbsp;</span>
1099
+ <span class="cline-any cline-yes">2x</span>
1100
+ <span class="cline-any cline-neutral">&nbsp;</span>
1101
+ <span class="cline-any cline-yes">1x</span>
1102
+ <span class="cline-any cline-yes">1x</span>
1103
+ <span class="cline-any cline-neutral">&nbsp;</span>
1104
+ <span class="cline-any cline-yes">1x</span>
1105
+ <span class="cline-any cline-neutral">&nbsp;</span>
1106
+ <span class="cline-any cline-neutral">&nbsp;</span>
1107
+ <span class="cline-any cline-neutral">&nbsp;</span>
1108
+ <span class="cline-any cline-neutral">&nbsp;</span>
1109
+ <span class="cline-any cline-neutral">&nbsp;</span>
1110
+ <span class="cline-any cline-neutral">&nbsp;</span>
1111
+ <span class="cline-any cline-neutral">&nbsp;</span>
1112
+ <span class="cline-any cline-neutral">&nbsp;</span>
1113
+ <span class="cline-any cline-yes">1x</span>
1114
+ <span class="cline-any cline-neutral">&nbsp;</span>
1115
+ <span class="cline-any cline-neutral">&nbsp;</span>
1116
+ <span class="cline-any cline-neutral">&nbsp;</span>
1117
+ <span class="cline-any cline-neutral">&nbsp;</span>
1118
+ <span class="cline-any cline-neutral">&nbsp;</span>
1119
+ <span class="cline-any cline-neutral">&nbsp;</span>
1120
+ <span class="cline-any cline-neutral">&nbsp;</span>
1121
+ <span class="cline-any cline-neutral">&nbsp;</span>
1122
+ <span class="cline-any cline-neutral">&nbsp;</span>
1123
+ <span class="cline-any cline-neutral">&nbsp;</span>
1124
+ <span class="cline-any cline-yes">1x</span>
1125
+ <span class="cline-any cline-yes">1x</span>
1126
+ <span class="cline-any cline-neutral">&nbsp;</span>
1127
+ <span class="cline-any cline-neutral">&nbsp;</span>
1128
+ <span class="cline-any cline-neutral">&nbsp;</span>
1129
+ <span class="cline-any cline-neutral">&nbsp;</span>
1130
+ <span class="cline-any cline-neutral">&nbsp;</span>
1131
+ <span class="cline-any cline-neutral">&nbsp;</span>
1132
+ <span class="cline-any cline-neutral">&nbsp;</span>
1133
+ <span class="cline-any cline-neutral">&nbsp;</span>
1134
+ <span class="cline-any cline-neutral">&nbsp;</span>
1135
+ <span class="cline-any cline-neutral">&nbsp;</span>
1136
+ <span class="cline-any cline-neutral">&nbsp;</span>
1137
+ <span class="cline-any cline-yes">1x</span>
1138
+ <span class="cline-any cline-yes">1x</span>
1139
+ <span class="cline-any cline-neutral">&nbsp;</span>
1140
+ <span class="cline-any cline-neutral">&nbsp;</span>
1141
+ <span class="cline-any cline-neutral">&nbsp;</span>
1142
+ <span class="cline-any cline-neutral">&nbsp;</span>
1143
+ <span class="cline-any cline-neutral">&nbsp;</span>
1144
+ <span class="cline-any cline-neutral">&nbsp;</span>
1145
+ <span class="cline-any cline-neutral">&nbsp;</span>
1146
+ <span class="cline-any cline-neutral">&nbsp;</span>
1147
+ <span class="cline-any cline-neutral">&nbsp;</span>
1148
+ <span class="cline-any cline-neutral">&nbsp;</span>
1149
+ <span class="cline-any cline-neutral">&nbsp;</span>
1150
+ <span class="cline-any cline-neutral">&nbsp;</span>
1151
+ <span class="cline-any cline-yes">2x</span>
1152
+ <span class="cline-any cline-neutral">&nbsp;</span>
1153
+ <span class="cline-any cline-yes">1x</span>
1154
+ <span class="cline-any cline-yes">1x</span>
1155
+ <span class="cline-any cline-neutral">&nbsp;</span>
1156
+ <span class="cline-any cline-yes">1x</span>
1157
+ <span class="cline-any cline-neutral">&nbsp;</span>
1158
+ <span class="cline-any cline-neutral">&nbsp;</span>
1159
+ <span class="cline-any cline-neutral">&nbsp;</span>
1160
+ <span class="cline-any cline-neutral">&nbsp;</span>
1161
+ <span class="cline-any cline-neutral">&nbsp;</span>
1162
+ <span class="cline-any cline-yes">1x</span>
1163
+ <span class="cline-any cline-neutral">&nbsp;</span>
1164
+ <span class="cline-any cline-neutral">&nbsp;</span>
1165
+ <span class="cline-any cline-neutral">&nbsp;</span>
1166
+ <span class="cline-any cline-yes">1x</span>
1167
+ <span class="cline-any cline-neutral">&nbsp;</span>
1168
+ <span class="cline-any cline-neutral">&nbsp;</span>
1169
+ <span class="cline-any cline-neutral">&nbsp;</span>
1170
+ <span class="cline-any cline-neutral">&nbsp;</span>
1171
+ <span class="cline-any cline-neutral">&nbsp;</span>
1172
+ <span class="cline-any cline-neutral">&nbsp;</span>
1173
+ <span class="cline-any cline-neutral">&nbsp;</span>
1174
+ <span class="cline-any cline-neutral">&nbsp;</span>
1175
+ <span class="cline-any cline-neutral">&nbsp;</span>
1176
+ <span class="cline-any cline-neutral">&nbsp;</span>
1177
+ <span class="cline-any cline-neutral">&nbsp;</span>
1178
+ <span class="cline-any cline-neutral">&nbsp;</span>
1179
+ <span class="cline-any cline-yes">1x</span>
1180
+ <span class="cline-any cline-neutral">&nbsp;</span>
1181
+ <span class="cline-any cline-yes">1x</span>
1182
+ <span class="cline-any cline-yes">1x</span>
1183
+ <span class="cline-any cline-neutral">&nbsp;</span>
1184
+ <span class="cline-any cline-yes">1x</span>
1185
+ <span class="cline-any cline-neutral">&nbsp;</span>
1186
+ <span class="cline-any cline-neutral">&nbsp;</span>
1187
+ <span class="cline-any cline-neutral">&nbsp;</span>
1188
+ <span class="cline-any cline-neutral">&nbsp;</span>
1189
+ <span class="cline-any cline-neutral">&nbsp;</span>
1190
+ <span class="cline-any cline-yes">1x</span>
1191
+ <span class="cline-any cline-neutral">&nbsp;</span>
1192
+ <span class="cline-any cline-neutral">&nbsp;</span>
1193
+ <span class="cline-any cline-neutral">&nbsp;</span>
1194
+ <span class="cline-any cline-yes">1x</span>
1195
+ <span class="cline-any cline-neutral">&nbsp;</span>
1196
+ <span class="cline-any cline-neutral">&nbsp;</span>
1197
+ <span class="cline-any cline-neutral">&nbsp;</span>
1198
+ <span class="cline-any cline-neutral">&nbsp;</span>
1199
+ <span class="cline-any cline-neutral">&nbsp;</span>
1200
+ <span class="cline-any cline-neutral">&nbsp;</span>
1201
+ <span class="cline-any cline-neutral">&nbsp;</span>
1202
+ <span class="cline-any cline-neutral">&nbsp;</span>
1203
+ <span class="cline-any cline-neutral">&nbsp;</span>
1204
+ <span class="cline-any cline-neutral">&nbsp;</span>
1205
+ <span class="cline-any cline-neutral">&nbsp;</span>
1206
+ <span class="cline-any cline-neutral">&nbsp;</span>
1207
+ <span class="cline-any cline-neutral">&nbsp;</span>
1208
+ <span class="cline-any cline-neutral">&nbsp;</span>
1209
+ <span class="cline-any cline-neutral">&nbsp;</span>
1210
+ <span class="cline-any cline-neutral">&nbsp;</span>
1211
+ <span class="cline-any cline-neutral">&nbsp;</span>
1212
+ <span class="cline-any cline-neutral">&nbsp;</span>
1213
+ <span class="cline-any cline-neutral">&nbsp;</span>
1214
+ <span class="cline-any cline-yes">2x</span>
1215
+ <span class="cline-any cline-neutral">&nbsp;</span>
1216
+ <span class="cline-any cline-yes">2x</span>
1217
+ <span class="cline-any cline-neutral">&nbsp;</span>
1218
+ <span class="cline-any cline-neutral">&nbsp;</span>
1219
+ <span class="cline-any cline-neutral">&nbsp;</span>
1220
+ <span class="cline-any cline-neutral">&nbsp;</span>
1221
+ <span class="cline-any cline-neutral">&nbsp;</span>
1222
+ <span class="cline-any cline-yes">2x</span>
1223
+ <span class="cline-any cline-yes">3x</span>
1224
+ <span class="cline-any cline-yes">3x</span>
1225
+ <span class="cline-any cline-yes">2x</span>
1226
+ <span class="cline-any cline-neutral">&nbsp;</span>
1227
+ <span class="cline-any cline-neutral">&nbsp;</span>
1228
+ <span class="cline-any cline-neutral">&nbsp;</span>
1229
+ <span class="cline-any cline-neutral">&nbsp;</span>
1230
+ <span class="cline-any cline-neutral">&nbsp;</span>
1231
+ <span class="cline-any cline-yes">1x</span>
1232
+ <span class="cline-any cline-neutral">&nbsp;</span>
1233
+ <span class="cline-any cline-neutral">&nbsp;</span>
1234
+ <span class="cline-any cline-neutral">&nbsp;</span>
1235
+ <span class="cline-any cline-neutral">&nbsp;</span>
1236
+ <span class="cline-any cline-neutral">&nbsp;</span>
1237
+ <span class="cline-any cline-neutral">&nbsp;</span>
1238
+ <span class="cline-any cline-neutral">&nbsp;</span>
1239
+ <span class="cline-any cline-yes">2x</span>
1240
+ <span class="cline-any cline-neutral">&nbsp;</span>
1241
+ <span class="cline-any cline-neutral">&nbsp;</span>
1242
+ <span class="cline-any cline-neutral">&nbsp;</span>
1243
+ <span class="cline-any cline-neutral">&nbsp;</span>
1244
+ <span class="cline-any cline-neutral">&nbsp;</span>
1245
+ <span class="cline-any cline-neutral">&nbsp;</span>
1246
+ <span class="cline-any cline-neutral">&nbsp;</span>
1247
+ <span class="cline-any cline-neutral">&nbsp;</span>
1248
+ <span class="cline-any cline-neutral">&nbsp;</span>
1249
+ <span class="cline-any cline-neutral">&nbsp;</span>
1250
+ <span class="cline-any cline-neutral">&nbsp;</span>
1251
+ <span class="cline-any cline-yes">2x</span>
1252
+ <span class="cline-any cline-neutral">&nbsp;</span>
1253
+ <span class="cline-any cline-neutral">&nbsp;</span>
1254
+ <span class="cline-any cline-neutral">&nbsp;</span>
1255
+ <span class="cline-any cline-neutral">&nbsp;</span>
1256
+ <span class="cline-any cline-neutral">&nbsp;</span>
1257
+ <span class="cline-any cline-neutral">&nbsp;</span>
1258
+ <span class="cline-any cline-neutral">&nbsp;</span>
1259
+ <span class="cline-any cline-neutral">&nbsp;</span>
1260
+ <span class="cline-any cline-neutral">&nbsp;</span>
1261
+ <span class="cline-any cline-neutral">&nbsp;</span>
1262
+ <span class="cline-any cline-neutral">&nbsp;</span>
1263
+ <span class="cline-any cline-neutral">&nbsp;</span>
1264
+ <span class="cline-any cline-neutral">&nbsp;</span>
1265
+ <span class="cline-any cline-neutral">&nbsp;</span>
1266
+ <span class="cline-any cline-neutral">&nbsp;</span>
1267
+ <span class="cline-any cline-yes">2x</span>
1268
+ <span class="cline-any cline-neutral">&nbsp;</span>
1269
+ <span class="cline-any cline-yes">2x</span>
1270
+ <span class="cline-any cline-yes">2x</span>
1271
+ <span class="cline-any cline-yes">2x</span>
1272
+ <span class="cline-any cline-yes">2x</span>
1273
+ <span class="cline-any cline-neutral">&nbsp;</span>
1274
+ <span class="cline-any cline-neutral">&nbsp;</span>
1275
+ <span class="cline-any cline-yes">2x</span>
1276
+ <span class="cline-any cline-yes">3x</span>
1277
+ <span class="cline-any cline-neutral">&nbsp;</span>
1278
+ <span class="cline-any cline-neutral">&nbsp;</span>
1279
+ <span class="cline-any cline-neutral">&nbsp;</span>
1280
+ <span class="cline-any cline-neutral">&nbsp;</span>
1281
+ <span class="cline-any cline-neutral">&nbsp;</span>
1282
+ <span class="cline-any cline-yes">3x</span>
1283
+ <span class="cline-any cline-yes">3x</span>
1284
+ <span class="cline-any cline-yes">3x</span>
1285
+ <span class="cline-any cline-neutral">&nbsp;</span>
1286
+ <span class="cline-any cline-neutral">&nbsp;</span>
1287
+ <span class="cline-any cline-yes">2x</span>
1288
+ <span class="cline-any cline-neutral">&nbsp;</span>
1289
+ <span class="cline-any cline-neutral">&nbsp;</span>
1290
+ <span class="cline-any cline-neutral">&nbsp;</span>
1291
+ <span class="cline-any cline-neutral">&nbsp;</span>
1292
+ <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/**
1293
+ * @fileoverview User management service orchestrating Keycloak operations.
1294
+ * Provides high-level user management with permission checking and audit logging.
1295
+ *
1296
+ * @module services/user
1297
+ */
1298
+ &nbsp;
1299
+ import {
1300
+ PLUGIN_ID,
1301
+ SERVICES,
1302
+ AUDIT_ACTIONS,
1303
+ ERROR_MESSAGES,
1304
+ PAGINATION,
1305
+ } from '../constants.js';
1306
+ import { createSanitizedError } from '../utils/errors.js';
1307
+ &nbsp;
1308
+ /**
1309
+ * @typedef {import('./keycloak-client.js').KeycloakUser} KeycloakUser
1310
+ * @typedef {import('./keycloak-client.js').KeycloakRole} KeycloakRole
1311
+ * @typedef {import('./permission.js').StrapiUser} StrapiUser
1312
+ * @typedef {import('./permission.js').RealmPermissions} RealmPermissions
1313
+ */
1314
+ &nbsp;
1315
+ /**
1316
+ * @typedef {Object} PaginatedUsersResult
1317
+ * @property {KeycloakUser[]} users - Array of users
1318
+ * @property {Object} pagination - Pagination metadata
1319
+ * @property {number} pagination.page - Current page number
1320
+ * @property {number} pagination.pageSize - Items per page
1321
+ * @property {number} pagination.total - Total items
1322
+ * @property {number} pagination.pageCount - Total pages
1323
+ */
1324
+ &nbsp;
1325
+ /**
1326
+ * @typedef {Object} BulkImportResult
1327
+ * @property {Object[]} success - Successfully created users
1328
+ * @property {string} success[].username - Username
1329
+ * @property {string} success[].email - Email
1330
+ * @property {string} success[].userId - Created user ID
1331
+ * @property {Object[]} failed - Failed user creations
1332
+ * @property {string} failed[].username - Username
1333
+ * @property {string} failed[].email - Email
1334
+ * @property {string} failed[].error - Error message
1335
+ */
1336
+ &nbsp;
1337
+ /**
1338
+ * Creates the user management service.
1339
+ *
1340
+ * @param {Object} params - Service parameters
1341
+ * @param {Object} params.strapi - Strapi instance
1342
+ * @returns {Object} User service methods
1343
+ */
1344
+ const userService = ({ strapi }) =&gt; ({
1345
+ /**
1346
+ * Gets the realm service instance.
1347
+ * @returns {Object} Realm service
1348
+ * @private
1349
+ */
1350
+ get realmService() {
1351
+ return strapi.plugin(PLUGIN_ID).service(SERVICES.REALM);
1352
+ },
1353
+ &nbsp;
1354
+ /**
1355
+ * Gets the Keycloak client service instance.
1356
+ * @returns {Object} Keycloak client service
1357
+ * @private
1358
+ */
1359
+ get keycloakClient() {
1360
+ return strapi.plugin(PLUGIN_ID).service(SERVICES.KEYCLOAK_CLIENT);
1361
+ },
1362
+ &nbsp;
1363
+ /**
1364
+ * Gets the permission service instance.
1365
+ * @returns {Object} Permission service
1366
+ * @private
1367
+ */
1368
+ get permissionService() {
1369
+ return strapi.plugin(PLUGIN_ID).service(SERVICES.PERMISSION);
1370
+ },
1371
+ &nbsp;
1372
+ /**
1373
+ * Gets the audit log service instance.
1374
+ * @returns {Object} Audit log service
1375
+ * @private
1376
+ */
1377
+ get auditLogService() {
1378
+ return strapi.plugin(PLUGIN_ID).service(SERVICES.AUDIT_LOG);
1379
+ },
1380
+ &nbsp;
1381
+ /**
1382
+ * Validates user permission and returns realm config with credentials.
1383
+ * This is the primary permission gate for all user operations.
1384
+ *
1385
+ * @param {string} realmId - Realm document ID
1386
+ * @param {StrapiUser} user - Strapi user requesting access
1387
+ * @param {keyof RealmPermissions} permission - Required permission
1388
+ * @returns {Promise&lt;Object&gt;} Realm config with clientSecret for API calls
1389
+ * @throws {Error} If realm disabled or user lacks permission
1390
+ * @private
1391
+ *
1392
+ * @example
1393
+ * const realm = await this.getRealmWithPermission(realmId, user, 'canCreate');
1394
+ * // Now safe to call Keycloak APIs
1395
+ */
1396
+ async getRealmWithPermission(realmId, user, permission) {
1397
+ // First check if realm exists and is enabled
1398
+ const realmBasic = await this.realmService.findOne(realmId);
1399
+ &nbsp;
1400
+ if (!realmBasic.enabled) {
1401
+ throw createSanitizedError('Realm disabled', ERROR_MESSAGES.REALM_DISABLED);
1402
+ }
1403
+ &nbsp;
1404
+ // Check user permission
1405
+ const hasPermission = await this.permissionService.canAccessRealm(user, realmId, permission);
1406
+ &nbsp;
1407
+ if (!hasPermission) {
1408
+ throw createSanitizedError(
1409
+ `User ${user.id} lacks ${permission} for realm ${realmId}`,
1410
+ ERROR_MESSAGES.REALM_ACCESS_DENIED
1411
+ );
1412
+ }
1413
+ &nbsp;
1414
+ // Get realm with clientSecret for Keycloak API calls
1415
+ return this.realmService.findOneWithSecret(realmId);
1416
+ },
1417
+ &nbsp;
1418
+ /**
1419
+ * Lists users from a Keycloak realm with pagination.
1420
+ *
1421
+ * @param {string} realmId - Realm document ID
1422
+ * @param {Object} [options={}] - Query options
1423
+ * @param {string} [options.search=''] - Search query
1424
+ * @param {number} [options.page=1] - Page number (1-indexed)
1425
+ * @param {number} [options.pageSize=25] - Items per page
1426
+ * @param {StrapiUser} strapiUser - User making the request
1427
+ * @returns {Promise&lt;PaginatedUsersResult&gt;} Paginated users
1428
+ *
1429
+ * @example
1430
+ * const { users, pagination } = await userService.listUsers(
1431
+ * realmId,
1432
+ * { search: 'john', page: 1, pageSize: 10 },
1433
+ * ctx.state.user
1434
+ * );
1435
+ */
1436
+ async listUsers(realmId, { search = '', page = 1, pageSize = PAGINATION.PAGE_SIZE } = <span class="branch-0 cbranch-no" title="branch not covered" >{},</span> strapiUser) {
1437
+ const realm = await this.getRealmWithPermission(realmId, strapiUser, 'canRead');
1438
+ &nbsp;
1439
+ const first = (page - 1) * pageSize;
1440
+ const [users, total] = await Promise.all([
1441
+ this.keycloakClient.getUsers(realm, { search, first, max: pageSize }),
1442
+ this.keycloakClient.countUsers(realm, { search }),
1443
+ ]);
1444
+ &nbsp;
1445
+ return {
1446
+ users,
1447
+ pagination: {
1448
+ page,
1449
+ pageSize,
1450
+ total,
1451
+ pageCount: Math.ceil(total / pageSize),
1452
+ },
1453
+ };
1454
+ },
1455
+ &nbsp;
1456
+ /**
1457
+ * Retrieves a single user by Keycloak ID.
1458
+ *
1459
+ * @param {string} realmId - Realm document ID
1460
+ * @param {string} keycloakUserId - Keycloak user ID
1461
+ * @param {StrapiUser} strapiUser - User making the request
1462
+ * @returns {Promise&lt;KeycloakUser&gt;} User details
1463
+ *
1464
+ * @example
1465
+ * const user = await userService.getUser(realmId, 'kc-user-123', ctx.state.user);
1466
+ */
1467
+ async getUser(realmId, keycloakUserId, strapiUser) {
1468
+ const realm = await this.getRealmWithPermission(realmId, strapiUser, 'canRead');
1469
+ return this.keycloakClient.getUserById(realm, keycloakUserId);
1470
+ },
1471
+ &nbsp;
1472
+ /**
1473
+ * Creates a new user in Keycloak.
1474
+ *
1475
+ * @param {string} realmId - Realm document ID
1476
+ * @param {Object} userData - User data
1477
+ * @param {string} userData.username - Required username
1478
+ * @param {string} [userData.email] - Email address
1479
+ * @param {string} [userData.firstName] - First name
1480
+ * @param {string} [userData.lastName] - Last name
1481
+ * @param {boolean} [userData.enabled=true] - Account enabled
1482
+ * @param {StrapiUser} strapiUser - User making the request
1483
+ * @returns {Promise&lt;KeycloakUser&gt;} Created user
1484
+ *
1485
+ * @example
1486
+ * const user = await userService.createUser(
1487
+ * realmId,
1488
+ * { username: 'newuser', email: 'new@example.com' },
1489
+ * ctx.state.user
1490
+ * );
1491
+ */
1492
+ async createUser(realmId, userData, strapiUser) {
1493
+ const realm = await this.getRealmWithPermission(realmId, strapiUser, 'canCreate');
1494
+ &nbsp;
1495
+ const result = await this.keycloakClient.createUser(realm, userData);
1496
+ &nbsp;
1497
+ // Log the action
1498
+ await this.auditLogService.log({
1499
+ realmName: realm.name,
1500
+ realmDisplayName: realm.displayName,
1501
+ action: AUDIT_ACTIONS.CREATE_USER,
1502
+ keycloakUserId: result.userId,
1503
+ keycloakUsername: userData.username,
1504
+ details: {
1505
+ email: userData.email,
1506
+ firstName: userData.firstName,
1507
+ lastName: userData.lastName
1508
+ },
1509
+ user: strapiUser,
1510
+ });
1511
+ &nbsp;
1512
+ // Fetch and return the created user if we have the ID
1513
+ <span class="missing-if-branch" title="else path not taken" >E</span>if (result.userId) {
1514
+ return this.keycloakClient.getUserById(realm, result.userId);
1515
+ }
1516
+ &nbsp;
1517
+ <span class="cstat-no" title="statement not covered" > return result;</span>
1518
+ },
1519
+ &nbsp;
1520
+ /**
1521
+ * Updates an existing user's attributes.
1522
+ *
1523
+ * @param {string} realmId - Realm document ID
1524
+ * @param {string} keycloakUserId - Keycloak user ID
1525
+ * @param {Object} userData - Fields to update
1526
+ * @param {StrapiUser} strapiUser - User making the request
1527
+ * @returns {Promise&lt;KeycloakUser&gt;} Updated user
1528
+ *
1529
+ * @example
1530
+ * const user = await userService.updateUser(
1531
+ * realmId,
1532
+ * 'kc-user-123',
1533
+ * { firstName: 'Updated', lastName: 'Name' },
1534
+ * ctx.state.user
1535
+ * );
1536
+ */
1537
+ async updateUser(realmId, keycloakUserId, userData, strapiUser) {
1538
+ const realm = await this.getRealmWithPermission(realmId, strapiUser, 'canUpdate');
1539
+ &nbsp;
1540
+ // Get current user data for audit
1541
+ const currentUser = await this.keycloakClient.getUserById(realm, keycloakUserId);
1542
+ &nbsp;
1543
+ await this.keycloakClient.updateUser(realm, keycloakUserId, userData);
1544
+ &nbsp;
1545
+ await this.auditLogService.log({
1546
+ realmName: realm.name,
1547
+ realmDisplayName: realm.displayName,
1548
+ action: AUDIT_ACTIONS.UPDATE_USER,
1549
+ keycloakUserId,
1550
+ keycloakUsername: currentUser.username,
1551
+ details: { changes: userData },
1552
+ user: strapiUser,
1553
+ });
1554
+ &nbsp;
1555
+ return this.keycloakClient.getUserById(realm, keycloakUserId);
1556
+ },
1557
+ &nbsp;
1558
+ /**
1559
+ * Permanently deletes a user from Keycloak.
1560
+ *
1561
+ * @param {string} realmId - Realm document ID
1562
+ * @param {string} keycloakUserId - Keycloak user ID
1563
+ * @param {StrapiUser} strapiUser - User making the request
1564
+ * @returns {Promise&lt;{success: boolean}&gt;} Success indicator
1565
+ *
1566
+ * @example
1567
+ * await userService.deleteUser(realmId, 'kc-user-123', ctx.state.user);
1568
+ */
1569
+ async deleteUser(realmId, keycloakUserId, strapiUser) {
1570
+ const realm = await this.getRealmWithPermission(realmId, strapiUser, 'canDelete');
1571
+ &nbsp;
1572
+ // Get user data for audit before deletion
1573
+ const user = await this.keycloakClient.getUserById(realm, keycloakUserId);
1574
+ &nbsp;
1575
+ await this.keycloakClient.deleteUser(realm, keycloakUserId);
1576
+ &nbsp;
1577
+ await this.auditLogService.log({
1578
+ realmName: realm.name,
1579
+ realmDisplayName: realm.displayName,
1580
+ action: AUDIT_ACTIONS.DELETE_USER,
1581
+ keycloakUserId,
1582
+ keycloakUsername: user.username,
1583
+ details: { email: user.email },
1584
+ user: strapiUser,
1585
+ });
1586
+ &nbsp;
1587
+ return { success: true };
1588
+ },
1589
+ &nbsp;
1590
+ /**
1591
+ * Resets a user's password.
1592
+ * Requires canResetPassword permission (separate from canUpdate for compliance).
1593
+ *
1594
+ * @param {string} realmId - Realm document ID
1595
+ * @param {string} keycloakUserId - Keycloak user ID
1596
+ * @param {string} password - New password
1597
+ * @param {boolean} temporary - Whether password must be changed on next login
1598
+ * @param {StrapiUser} strapiUser - User making the request
1599
+ * @returns {Promise&lt;{success: boolean}&gt;} Success indicator
1600
+ *
1601
+ * @example
1602
+ * // Set temporary password
1603
+ * await userService.resetPassword(realmId, userId, 'TempPass123!', true, user);
1604
+ */
1605
+ async resetPassword(realmId, keycloakUserId, password, temporary, strapiUser) {
1606
+ const realm = await this.getRealmWithPermission(realmId, strapiUser, 'canResetPassword');
1607
+ &nbsp;
1608
+ const user = await this.keycloakClient.getUserById(realm, keycloakUserId);
1609
+ await this.keycloakClient.resetPassword(realm, keycloakUserId, password, temporary);
1610
+ &nbsp;
1611
+ await this.auditLogService.log({
1612
+ realmName: realm.name,
1613
+ realmDisplayName: realm.displayName,
1614
+ action: AUDIT_ACTIONS.RESET_PASSWORD,
1615
+ keycloakUserId,
1616
+ keycloakUsername: user.username,
1617
+ details: { temporary },
1618
+ user: strapiUser,
1619
+ });
1620
+ &nbsp;
1621
+ return { success: true };
1622
+ },
1623
+ &nbsp;
1624
+ /**
1625
+ * Enables a user account.
1626
+ *
1627
+ * @param {string} realmId - Realm document ID
1628
+ * @param {string} keycloakUserId - Keycloak user ID
1629
+ * @param {StrapiUser} strapiUser - User making the request
1630
+ * @returns {Promise&lt;{success: boolean}&gt;} Success indicator
1631
+ */
1632
+ async enableUser(realmId, keycloakUserId, strapiUser) {
1633
+ const realm = await this.getRealmWithPermission(realmId, strapiUser, 'canUpdate');
1634
+ &nbsp;
1635
+ const user = await this.keycloakClient.getUserById(realm, keycloakUserId);
1636
+ await this.keycloakClient.enableUser(realm, keycloakUserId);
1637
+ &nbsp;
1638
+ await this.auditLogService.log({
1639
+ realmName: realm.name,
1640
+ realmDisplayName: realm.displayName,
1641
+ action: AUDIT_ACTIONS.ENABLE_USER,
1642
+ keycloakUserId,
1643
+ keycloakUsername: user.username,
1644
+ user: strapiUser,
1645
+ });
1646
+ &nbsp;
1647
+ return { success: true };
1648
+ },
1649
+ &nbsp;
1650
+ /**
1651
+ * Disables a user account.
1652
+ *
1653
+ * @param {string} realmId - Realm document ID
1654
+ * @param {string} keycloakUserId - Keycloak user ID
1655
+ * @param {StrapiUser} strapiUser - User making the request
1656
+ * @returns {Promise&lt;{success: boolean}&gt;} Success indicator
1657
+ */
1658
+ async disableUser(realmId, keycloakUserId, strapiUser) {
1659
+ const realm = await this.getRealmWithPermission(realmId, strapiUser, 'canUpdate');
1660
+ &nbsp;
1661
+ const user = await this.keycloakClient.getUserById(realm, keycloakUserId);
1662
+ await this.keycloakClient.disableUser(realm, keycloakUserId);
1663
+ &nbsp;
1664
+ await this.auditLogService.log({
1665
+ realmName: realm.name,
1666
+ realmDisplayName: realm.displayName,
1667
+ action: AUDIT_ACTIONS.DISABLE_USER,
1668
+ keycloakUserId,
1669
+ keycloakUsername: user.username,
1670
+ user: strapiUser,
1671
+ });
1672
+ &nbsp;
1673
+ return { success: true };
1674
+ },
1675
+ &nbsp;
1676
+ /**
1677
+ * Sends an email verification link to the user.
1678
+ *
1679
+ * @param {string} realmId - Realm document ID
1680
+ * @param {string} keycloakUserId - Keycloak user ID
1681
+ * @param {StrapiUser} strapiUser - User making the request
1682
+ * @returns {Promise&lt;{success: boolean}&gt;} Success indicator
1683
+ */
1684
+ async sendVerificationEmail(realmId, keycloakUserId, strapiUser) {
1685
+ const realm = await this.getRealmWithPermission(realmId, strapiUser, 'canUpdate');
1686
+ &nbsp;
1687
+ const user = await this.keycloakClient.getUserById(realm, keycloakUserId);
1688
+ await this.keycloakClient.sendVerificationEmail(realm, keycloakUserId);
1689
+ &nbsp;
1690
+ await this.auditLogService.log({
1691
+ realmName: realm.name,
1692
+ realmDisplayName: realm.displayName,
1693
+ action: AUDIT_ACTIONS.SEND_VERIFY_EMAIL,
1694
+ keycloakUserId,
1695
+ keycloakUsername: user.username,
1696
+ user: strapiUser,
1697
+ });
1698
+ &nbsp;
1699
+ return { success: true };
1700
+ },
1701
+ &nbsp;
1702
+ /**
1703
+ * Sends a password reset email to the user.
1704
+ * Requires canResetPassword permission.
1705
+ *
1706
+ * @param {string} realmId - Realm document ID
1707
+ * @param {string} keycloakUserId - Keycloak user ID
1708
+ * @param {StrapiUser} strapiUser - User making the request
1709
+ * @returns {Promise&lt;{success: boolean}&gt;} Success indicator
1710
+ */
1711
+ async sendResetPasswordEmail(realmId, keycloakUserId, strapiUser) {
1712
+ const realm = await this.getRealmWithPermission(realmId, strapiUser, 'canResetPassword');
1713
+ &nbsp;
1714
+ const user = await this.keycloakClient.getUserById(realm, keycloakUserId);
1715
+ await this.keycloakClient.sendResetPasswordEmail(realm, keycloakUserId);
1716
+ &nbsp;
1717
+ await this.auditLogService.log({
1718
+ realmName: realm.name,
1719
+ realmDisplayName: realm.displayName,
1720
+ action: AUDIT_ACTIONS.SEND_RESET_PASSWORD_EMAIL,
1721
+ keycloakUserId,
1722
+ keycloakUsername: user.username,
1723
+ user: strapiUser,
1724
+ });
1725
+ &nbsp;
1726
+ return { success: true };
1727
+ },
1728
+ &nbsp;
1729
+ /**
1730
+ * Retrieves all realm roles.
1731
+ *
1732
+ * @param {string} realmId - Realm document ID
1733
+ * @param {StrapiUser} strapiUser - User making the request
1734
+ * @returns {Promise&lt;KeycloakRole[]&gt;} Array of realm roles
1735
+ */
1736
+ async getRoles(realmId, strapiUser) {
1737
+ const realm = await this.getRealmWithPermission(realmId, strapiUser, 'canRead');
1738
+ return this.keycloakClient.getRealmRoles(realm);
1739
+ },
1740
+ &nbsp;
1741
+ /**
1742
+ * Gets roles assigned to a user.
1743
+ *
1744
+ * @param {string} realmId - Realm document ID
1745
+ * @param {string} keycloakUserId - Keycloak user ID
1746
+ * @param {StrapiUser} strapiUser - User making the request
1747
+ * @returns {Promise&lt;KeycloakRole[]&gt;} User's assigned roles
1748
+ */
1749
+ async getUserRoles(realmId, keycloakUserId, strapiUser) {
1750
+ const realm = await this.getRealmWithPermission(realmId, strapiUser, 'canRead');
1751
+ return this.keycloakClient.getUserRoles(realm, keycloakUserId);
1752
+ },
1753
+ &nbsp;
1754
+ /**
1755
+ * Assigns roles to a user.
1756
+ *
1757
+ * @param {string} realmId - Realm document ID
1758
+ * @param {string} keycloakUserId - Keycloak user ID
1759
+ * @param {KeycloakRole[]} roles - Roles to assign
1760
+ * @param {StrapiUser} strapiUser - User making the request
1761
+ * @returns {Promise&lt;{success: boolean}&gt;} Success indicator
1762
+ */
1763
+ async assignRoles(realmId, keycloakUserId, roles, strapiUser) {
1764
+ const realm = await this.getRealmWithPermission(realmId, strapiUser, 'canManageRoles');
1765
+ &nbsp;
1766
+ const user = await this.keycloakClient.getUserById(realm, keycloakUserId);
1767
+ await this.keycloakClient.assignRoles(realm, keycloakUserId, roles);
1768
+ &nbsp;
1769
+ await this.auditLogService.log({
1770
+ realmName: realm.name,
1771
+ realmDisplayName: realm.displayName,
1772
+ action: AUDIT_ACTIONS.ASSIGN_ROLE,
1773
+ keycloakUserId,
1774
+ keycloakUsername: user.username,
1775
+ details: { roles: roles.map((r) =&gt; r.name) },
1776
+ user: strapiUser,
1777
+ });
1778
+ &nbsp;
1779
+ return { success: true };
1780
+ },
1781
+ &nbsp;
1782
+ /**
1783
+ * Removes roles from a user.
1784
+ *
1785
+ * @param {string} realmId - Realm document ID
1786
+ * @param {string} keycloakUserId - Keycloak user ID
1787
+ * @param {KeycloakRole[]} roles - Roles to remove
1788
+ * @param {StrapiUser} strapiUser - User making the request
1789
+ * @returns {Promise&lt;{success: boolean}&gt;} Success indicator
1790
+ */
1791
+ async removeRoles(realmId, keycloakUserId, roles, strapiUser) {
1792
+ const realm = await this.getRealmWithPermission(realmId, strapiUser, 'canManageRoles');
1793
+ &nbsp;
1794
+ const user = await this.keycloakClient.getUserById(realm, keycloakUserId);
1795
+ await this.keycloakClient.removeRoles(realm, keycloakUserId, roles);
1796
+ &nbsp;
1797
+ await this.auditLogService.log({
1798
+ realmName: realm.name,
1799
+ realmDisplayName: realm.displayName,
1800
+ action: AUDIT_ACTIONS.REMOVE_ROLE,
1801
+ keycloakUserId,
1802
+ keycloakUsername: user.username,
1803
+ details: { roles: roles.map((r) =&gt; r.name) },
1804
+ user: strapiUser,
1805
+ });
1806
+ &nbsp;
1807
+ return { success: true };
1808
+ },
1809
+ &nbsp;
1810
+ /**
1811
+ * Bulk imports users from an array of user data.
1812
+ * Processes users sequentially to handle errors gracefully.
1813
+ *
1814
+ * @param {string} realmId - Realm document ID
1815
+ * @param {Object[]} users - Array of user data objects
1816
+ * @param {StrapiUser} strapiUser - User making the request
1817
+ * @returns {Promise&lt;BulkImportResult&gt;} Import results with success/failed arrays
1818
+ *
1819
+ * @example
1820
+ * const result = await userService.bulkImport(realmId, [
1821
+ * { username: 'user1', email: 'user1@example.com' },
1822
+ * { username: 'user2', email: 'user2@example.com' }
1823
+ * ], ctx.state.user);
1824
+ * console.log(`Created ${result.success.length}, Failed ${result.failed.length}`);
1825
+ */
1826
+ async bulkImport(realmId, users, strapiUser) {
1827
+ const realm = await this.getRealmWithPermission(realmId, strapiUser, 'canCreate');
1828
+ &nbsp;
1829
+ const results = {
1830
+ success: [],
1831
+ failed: [],
1832
+ };
1833
+ &nbsp;
1834
+ // Process users sequentially for better error handling
1835
+ for (const userData of users) {
1836
+ try {
1837
+ const result = await this.keycloakClient.createUser(realm, userData);
1838
+ results.success.push({
1839
+ username: userData.username,
1840
+ email: userData.email,
1841
+ userId: result.userId,
1842
+ });
1843
+ } catch (err) {
1844
+ results.failed.push({
1845
+ username: userData.username,
1846
+ email: userData.email,
1847
+ error: err.sanitizedMessage || <span class="branch-1 cbranch-no" title="branch not covered" >err.message,</span>
1848
+ });
1849
+ }
1850
+ }
1851
+ &nbsp;
1852
+ await this.auditLogService.log({
1853
+ realmName: realm.name,
1854
+ realmDisplayName: realm.displayName,
1855
+ action: AUDIT_ACTIONS.BULK_IMPORT,
1856
+ details: {
1857
+ totalAttempted: users.length,
1858
+ successCount: results.success.length,
1859
+ failedCount: results.failed.length,
1860
+ },
1861
+ user: strapiUser,
1862
+ });
1863
+ &nbsp;
1864
+ return results;
1865
+ },
1866
+ &nbsp;
1867
+ /**
1868
+ * Exports all users from a realm.
1869
+ * Fetches users in batches for performance.
1870
+ *
1871
+ * @param {string} realmId - Realm document ID
1872
+ * @param {StrapiUser} strapiUser - User making the request
1873
+ * @returns {Promise&lt;KeycloakUser[]&gt;} All users in the realm
1874
+ *
1875
+ * @example
1876
+ * const users = await userService.exportUsers(realmId, ctx.state.user);
1877
+ * // Convert to CSV or JSON for download
1878
+ */
1879
+ async exportUsers(realmId, strapiUser) {
1880
+ const realm = await this.getRealmWithPermission(realmId, strapiUser, 'canRead');
1881
+ &nbsp;
1882
+ const allUsers = [];
1883
+ let first = 0;
1884
+ const max = PAGINATION.EXPORT_BATCH_SIZE;
1885
+ let hasMore = true;
1886
+ &nbsp;
1887
+ // Fetch users in batches
1888
+ while (hasMore) {
1889
+ const users = await this.keycloakClient.getUsers(realm, {
1890
+ first,
1891
+ max,
1892
+ briefRepresentation: false,
1893
+ });
1894
+ &nbsp;
1895
+ allUsers.push(...users);
1896
+ first += max;
1897
+ hasMore = users.length === max;
1898
+ }
1899
+ &nbsp;
1900
+ return allUsers;
1901
+ },
1902
+ });
1903
+ &nbsp;
1904
+ export default userService;
1905
+ &nbsp;</pre></td></tr></table></pre>
1906
+
1907
+ <div class='push'></div><!-- for sticky footer -->
1908
+ </div><!-- /wrapper -->
1909
+ <div class='footer quiet pad2 space-top1 center small'>
1910
+ Code coverage generated by
1911
+ <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
1912
+ at 2026-02-05T14:19:59.409Z
1913
+ </div>
1914
+ <script src="../../prettify.js"></script>
1915
+ <script>
1916
+ window.onload = function () {
1917
+ prettyPrint();
1918
+ };
1919
+ </script>
1920
+ <script src="../../sorter.js"></script>
1921
+ <script src="../../block-navigation.js"></script>
1922
+ </body>
1923
+ </html>
1924
+