Вікі-код для 2. Налаштування бандлу
Показати останніх авторів
author | version | line-number | content |
---|---|---|---|
1 | (% class="box floatinginfobox" %) | ||
2 | ((( | ||
3 | **Зміст** | ||
4 | |||
5 | (% class="wikitoc" %) | ||
6 | * [[Блок security>>path:#H41143B43E43AA0]] | ||
7 | ** [[Параметр protected_methods>>path:#H41F43044043043C435442440]] | ||
8 | ** [[Параметр token_key_in_header>>path:#H41F43044043043C435442440-1]] | ||
9 | ** [[Параметр clients_tokens>>path:#H41F43044043043C435442440-2]] | ||
10 | *** [[Токени в параметрах>>path:#H42243E43A43543D43843243F43044043043C435442440430445]] | ||
11 | *** [[Токени в змінних оточення>>path:#H42243E43A43543D43843243743C45643D43D43844543E44243E44743543D43D44F]] | ||
12 | *** [[Токени для користувача>>path:#H42243E43A43543D43843443B44F43A43E440438441442443432430447430]] | ||
13 | **** [[Приклад власного валідатора токенів>>path:#H41F44043843A43B43043443243B43044143D43E43343E43243043B45643443044243E44043044243E43A43543D456432]] | ||
14 | * [[Блок async>>path:#H41143B43E43AA0-1]] | ||
15 | * [[Блок docs>>path:#H41143B43E43AA0-2]] | ||
16 | ** [[Секція response>>path:#H42143543A44645644F]] | ||
17 | *** [[Параметр key_for_methods>>path:#H41F43044043043C435442440A0]] | ||
18 | *** [[Параметр async_dsn_info>>path:#H41F43044043043C435442440A0-1]] | ||
19 | **** [[Приклад документації >>path:#H41F44043843A43B43043443443E43A44343C43543D442430446456457]] | ||
20 | *** [[Параметр validations>>path:#H41F43044043043C435442440A0-2]] | ||
21 | **** [[Приклад документації >>path:#H41F44043843A43B43043443443E43A44343C43543D442430446456457-1]] | ||
22 | ))) | ||
23 | |||
24 | (% class="wikigeneratedid" %) | ||
25 | Всі налаштування бандла знаходяться в файлі (% class="box code" %)config/packages/ufo_json_rpc.yaml(%%). | ||
26 | |||
27 | (% class="wikigeneratedid" %) | ||
28 | Є можливість налаштувати параметри захисту API та деякі параметри формату даних, що віддається при запиті документації. | ||
29 | |||
30 | = Блок (% class="box code" %)security(%%) = | ||
31 | |||
32 | Наразі єдиним механізмом захисту доступу до вашого API є встановлення перевірки ключа доступу (api_token). | ||
33 | |||
34 | == Параметр (% class="box code" %)protected_methods(%%) == | ||
35 | |||
36 | (% class="wikigeneratedid" %) | ||
37 | Цей параметр приймає масив назв http методів, які мають бути захищені. | ||
38 | |||
39 | (% class="wikigeneratedid" %) | ||
40 | За замовченням ввімкнут захист лише для методу POST. Ви можете: | ||
41 | |||
42 | * вказати пустий масив (% class="box code" %)[](%%) щоб зробити API повністю відкритим | ||
43 | |||
44 | (% class="box" %) | ||
45 | ((( | ||
46 | config/packages/ufo_json_rpc.yaml | ||
47 | |||
48 | (% class="code" %) | ||
49 | ((( | ||
50 | (% class="linenoswrapper" %) | ||
51 | ((( | ||
52 | (% class="linenos" %) | ||
53 | ((( | ||
54 | 1 | ||
55 | 2 | ||
56 | 3\\ | ||
57 | ))) | ||
58 | |||
59 | ((( | ||
60 | (% style="font-weight: bold; color: #008000;" %)ufo_json_rpc(%%): | ||
61 | (% style="font-weight: bold; color: #008000;" %)security(%%): | ||
62 | (% style="font-weight: bold; color: #008000;" %)protected_methods(%%): [] | ||
63 | ))) | ||
64 | ))) | ||
65 | ))) | ||
66 | ))) | ||
67 | |||
68 | * вказати додатково захист для методу GET, що зробить запит документації недоступним без токену в заголовках запиту | ||
69 | |||
70 | (% class="box" %) | ||
71 | ((( | ||
72 | config/packages/ufo_json_rpc.yaml | ||
73 | |||
74 | (% class="code" %) | ||
75 | ((( | ||
76 | (% class="linenoswrapper" %) | ||
77 | ((( | ||
78 | (% class="linenos" %) | ||
79 | ((( | ||
80 | 1 | ||
81 | 2 | ||
82 | 3\\ | ||
83 | ))) | ||
84 | |||
85 | ((( | ||
86 | (% style="font-weight: bold; color: #008000;" %)ufo_json_rpc(%%): | ||
87 | (% style="font-weight: bold; color: #008000;" %)security(%%): | ||
88 | (% style="font-weight: bold; color: #008000;" %)protected_methods(%%): [(% style="color: #BA2121;" %)'GET'(%%), (% style="color: #BA2121;" %)'POST'(%%)] | ||
89 | ))) | ||
90 | ))) | ||
91 | ))) | ||
92 | ))) | ||
93 | |||
94 | (% id="cke_bm_164641S" style="display:none" %) (%%)Якщо ви захищаєте ваш API через (% class="box code" %)protected_methods(%%), вам необхідно налаштувати токени, по яким буде відкритий доступ. | ||
95 | |||
96 | Перш за все, треба визначитися з назвою токену. | ||
97 | |||
98 | == Параметр (% class="box code" %)token_key_in_header(%%) == | ||
99 | |||
100 | Компонент (% class="box code" %)RpcSecurity(%%) буде шукати в заголовках запиту специфічний ключ, який ви можете встановити в налаштуваннях пакету, значення за замовченням (% class="box code" %)token_key_in_header: 'Ufo-RPC-Token'(%%), ви можете встановити будь-яке інше значення яке відповідає наступним вимогам. | ||
101 | |||
102 | \\ | ||
103 | |||
104 | (% class="spoiler" %) | ||
105 | ((( | ||
106 | (% class="spoilerTitle" %) | ||
107 | ((( | ||
108 | Вимоги до формування заголовків протоколу HTTP | ||
109 | ))) | ||
110 | |||
111 | (% class="spoilerContent hidden" %) | ||
112 | ((( | ||
113 | Вимоги до назв заголовків HTTP не є строго регульованими щодо капіталізації, оскільки HTTP заголовки нечутливі до регістру. Однак, існують деякі загальні практики і стандарти, які зазвичай дотримуються для кращої читабельності та узгодженості: | ||
114 | |||
115 | - Капіталізація: Зазвичай назви HTTP заголовків пишуться з використанням CamelCase, де кожне слово починається з великої літери, наприклад, Content-Type, User-Agent, Accept-Encoding. Це не впливає на технічну обробку заголовків, але робить їх легше читати. | ||
116 | - Унікальність: Кожен заголовок повинен мати унікальну назву у контексті одного HTTP запиту або відповіді. Не можна використовувати однакові назви для різних заголовків у тому самому запиті чи відповіді. | ||
117 | - Спеціальні заголовки: Існують заголовки, які використовуються специфічно для контролю поведінки кешування (Cache-Control), безпеки (Strict-Transport-Security), аутентифікації (Authorization), тощо. | ||
118 | - Норми RFC: Вимоги до заголовків регулюються документами RFC, які визначають стандарти для протоколів Інтернету. Наприклад, загальні заголовки і їхнє використання описані в RFC 7231. | ||
119 | ))) | ||
120 | ))) | ||
121 | |||
122 | |||
123 | |||
124 | |||
125 | |||
126 | == Параметр (% class="box code" %)clients_tokens(%%) == | ||
127 | |||
128 | Тепер слід вказати масив клієнтськіх токенів, які будуть мати доступ до API. | ||
129 | |||
130 | Тут є певна варіативність. | ||
131 | |||
132 | === Токени в параметрах === | ||
133 | |||
134 | (% class="box errormessage" %) | ||
135 | ((( | ||
136 | **НЕ РЕКОМЕНДОВАНО!!!** | ||
137 | \\Цей підхід допускається лише для локального тестування API | ||
138 | ))) | ||
139 | |||
140 | Є можливість прописати токени хардкодом прямо в файлі налаштувань. | ||
141 | |||
142 | Це погано з позиції безпеки, якщо код зберігається в публічному репозиторію, то до цього токену буде мати доступ кожен. | ||
143 | |||
144 | (% class="box" %) | ||
145 | ((( | ||
146 | config/packages/ufo_json_rpc.yaml | ||
147 | |||
148 | (% class="code" %) | ||
149 | ((( | ||
150 | (% class="linenoswrapper" %) | ||
151 | ((( | ||
152 | (% class="linenos" %) | ||
153 | ((( | ||
154 | 1 | ||
155 | 2 | ||
156 | 3 | ||
157 | 4 | ||
158 | 5 | ||
159 | 6 | ||
160 | 7\\ | ||
161 | ))) | ||
162 | |||
163 | ((( | ||
164 | (% style="font-weight: bold; color: #008000;" %)ufo_json_rpc(%%): | ||
165 | (% style="font-weight: bold; color: #008000;" %)security(%%): | ||
166 | (% style="font-weight: bold; color: #008000;" %)protected_methods(%%): [(% style="color: #BA2121;" %)'GET'(%%), (% style="color: #BA2121;" %)'POST'(%%)] | ||
167 | (% style="font-weight: bold; color: #008000;" %)token_key_in_header(%%): (% style="color: #BA2121;" %)'Ufo-RPC-Token'(%%) | ||
168 | (% style="font-weight: bold; color: #008000;" %)clients_tokens(%%): | ||
169 | - (% style="color: #BA2121;" %)'ClientTokenExample'(%%) (% style="font-style: italic; color: #408080;" %)# hardcoded token example. Importantly!!! Replace or delete it!(%%) | ||
170 | |||
171 | ))) | ||
172 | ))) | ||
173 | ))) | ||
174 | ))) | ||
175 | |||
176 | === Токени в змінних оточення === | ||
177 | |||
178 | Це найбільш доцільний механізм у разі, якщо ви розробляєте сервіс для розподіленого бекенду, що написаний на SOA (Сервіс-Орієнтована Архітектура). Зазвичай, в такому випадку, вам треба відкрити доступ до апі одному або обмеженій кількості клієнтських додатків і оновлення ключів не буде відбуватися занадто часто. | ||
179 | |||
180 | В такому випадку можна прописати токени в змінних оточення (файл (% class="box code" %).env.local(%%) під час локальної розробки). Цей механізм достатньо безпечний з боку збереження доступів. | ||
181 | |||
182 | ((( | ||
183 | (% class="box" %) | ||
184 | ((( | ||
185 | .env.local | ||
186 | |||
187 | (% class="code" %) | ||
188 | ((( | ||
189 | (% class="linenoswrapper" %) | ||
190 | ((( | ||
191 | (% class="linenos" %) | ||
192 | ((( | ||
193 | 1 | ||
194 | 2\\ | ||
195 | ))) | ||
196 | |||
197 | ((( | ||
198 | (% style="color: #7D9029;" %)TOKEN_FOR_APP_1(% style="color: #666666;" %)=(% style="color: #BA2121;" %)9363074966579432364f8b73b3318f71(%%) | ||
199 | (% style="color: #7D9029;" %)TOKEN_FOR_APP_2(% style="color: #666666;" %)=(% style="color: #BA2121;" %)456fg87g8h98jmnb8675r4445n8up365 | ||
200 | ))) | ||
201 | ))) | ||
202 | ))) | ||
203 | ))) | ||
204 | |||
205 | (% class="box" %) | ||
206 | ((( | ||
207 | config/packages/ufo_json_rpc.yaml | ||
208 | |||
209 | (% class="code" %) | ||
210 | ((( | ||
211 | (% class="linenoswrapper" %) | ||
212 | ((( | ||
213 | (% class="linenos" %) | ||
214 | ((( | ||
215 | 1 | ||
216 | 2 | ||
217 | 3 | ||
218 | 4 | ||
219 | 5 | ||
220 | 6 | ||
221 | 7\\ | ||
222 | ))) | ||
223 | |||
224 | ((( | ||
225 | (% style="font-weight: bold; color: #008000;" %)ufo_json_rpc(%%): | ||
226 | (% style="font-weight: bold; color: #008000;" %)security(%%): | ||
227 | (% style="font-weight: bold; color: #008000;" %)protected_methods(%%): [(% style="color: #BA2121;" %)'GET'(%%), (% style="color: #BA2121;" %)'POST'(%%)] | ||
228 | (% style="font-weight: bold; color: #008000;" %)token_key_in_header(%%): (% style="color: #BA2121;" %)'Ufo-RPC-Token'(%%) | ||
229 | (% style="font-weight: bold; color: #008000;" %)clients_tokens(%%): | ||
230 | - (% style="color: #BA2121;" %)'%env(resolve:TOKEN_FOR_APP_1)%'(%%) (% style="font-style: italic; color: #408080;" %)# token example from .env.local(%%) | ||
231 | - (% style="color: #BA2121;" %)'%env(resolve:TOKEN_FOR_APP_2)%'(%%) (% style="font-style: italic; color: #408080;" %)# token example from .env.local | ||
232 | ))) | ||
233 | ))) | ||
234 | ))) | ||
235 | ))) | ||
236 | ))) | ||
237 | |||
238 | === Токени для користувача === | ||
239 | |||
240 | Припускаю, що у вас може виникнути потреба зробити персональні ключі для користувачів вашого додатку, можливо ви захочете впровадити ліміти або інші обмеження. | ||
241 | В такому випадку вам не потрібно вказувати перелік токенів в конфігах, ви можете зберігати їх в базі даних або іншому місці згідно вашій бізнес-логіки. Єдина вимога, у вас має бути сервіс, який вміє перевіряти чи існує наданий токен. | ||
242 | |||
243 | Для того, щоб JsonRpcServer міг використовувати вашу логіку, доведеться реалізувати власний клас, що реалізує інтерфейс (% class="box code" %)Ufo\JsonRpcBundle\Security\Interfaces\ITokenValidator | ||
244 | |||
245 | (% class="box" %) | ||
246 | ((( | ||
247 | ==== Приклад власного валідатора токенів ==== | ||
248 | |||
249 | (% class="code" %) | ||
250 | ((( | ||
251 | (% class="linenoswrapper" %) | ||
252 | ((( | ||
253 | (% class="linenos" %) | ||
254 | ((( | ||
255 | 1 | ||
256 | 2 | ||
257 | 3 | ||
258 | 4 | ||
259 | 5 | ||
260 | 6 | ||
261 | 7 | ||
262 | 8 | ||
263 | 9 | ||
264 | 10 | ||
265 | 11 | ||
266 | 12 | ||
267 | 13 | ||
268 | 14 | ||
269 | 15 | ||
270 | 16 | ||
271 | 17 | ||
272 | 18 | ||
273 | 19 | ||
274 | 20 | ||
275 | 21 | ||
276 | 22 | ||
277 | 23 | ||
278 | 24\\ | ||
279 | ))) | ||
280 | |||
281 | ((( | ||
282 | (% style="color: #BC7A00;" %)<?php(%%) | ||
283 | \\(% style="font-weight: bold; color: #008000;" %)namespace(%%) App\Services\RpcSecurity; | ||
284 | \\(% style="font-weight: bold; color: #008000;" %)use(%%) App\Services\UserService; | ||
285 | (% style="font-weight: bold; color: #008000;" %)use(%%) Symfony\Component\Security\Core\Exception\UserNotFoundException; | ||
286 | (% style="font-weight: bold; color: #008000;" %)use(%%) Ufo\JsonRpcBundle\Security\Interfaces\ITokenValidator; | ||
287 | (% style="font-weight: bold; color: #008000;" %)use(%%) Ufo\RpcError\RpcInvalidTokenException; | ||
288 | \\(% style="font-weight: bold; color: #008000;" %)class(%%) (% style="font-weight: bold; color: #0000FF;" %)UserTokenValidator(%%) (% style="font-weight: bold; color: #008000;" %)implements(%%) ITokenValidator | ||
289 | { | ||
290 | \\ (% style="font-weight: bold; color: #008000;" %)public(%%) (% style="font-weight: bold; color: #008000;" %)function(%%) (% style="color: #0000FF;" %)~_~_construct(%%)((% style="font-weight: bold; color: #008000;" %)protected(%%) UserService (% style="color: #19177C;" %)$userService(%%)) {} | ||
291 | \\ (% style="font-weight: bold; color: #008000;" %)public(%%) (% style="font-weight: bold; color: #008000;" %)function(%%) (% style="color: #0000FF;" %)isValid(%%)(string (% style="color: #19177C;" %)$token(%%))(% style="color: #666666;" %):(%%) (% style="font-weight: bold; color: #008000;" %)true(%%) | ||
292 | { | ||
293 | (% style="font-weight: bold; color: #008000;" %)try(%%) { | ||
294 | (% style="color: #19177C;" %)$this(% style="color: #666666;" %)->(% style="color: #7D9029;" %)userService(% style="color: #666666;" %)->(% style="color: #7D9029;" %)getUserByToken(%%)((% style="color: #19177C;" %)$token(%%)); | ||
295 | (% style="font-weight: bold; color: #008000;" %)return(%%) (% style="font-weight: bold; color: #008000;" %)true(%%); | ||
296 | } (% style="font-weight: bold; color: #008000;" %)catch(%%) (UserNotFoundException (% style="color: #19177C;" %)$e(%%)) { | ||
297 | (% style="font-weight: bold; color: #008000;" %)throw(%%) (% style="font-weight: bold; color: #008000;" %)new(%%) RpcInvalidTokenException(previous(% style="color: #666666;" %):(%%) (% style="color: #19177C;" %)$e(%%)); | ||
298 | } | ||
299 | } | ||
300 | } | ||
301 | ))) | ||
302 | ))) | ||
303 | ))) | ||
304 | ))) | ||
305 | |||
306 | (% class="box warningmessage" %) | ||
307 | ((( | ||
308 | **ВАЖЛИВО!!!** | ||
309 | Метод (% class="box code" %)isValid(%%) має повертати (% class="box code" %)true(%%) якщо токен існує і валідний, або викидати (% class="box code" %)Ufo\RpcError\RpcInvalidTokenException(%%) в іншому разі. | ||
310 | ))) | ||
311 | |||
312 | Після цього вам потрібно в файлі (% class="box code" %)config/services.yaml(%%) прописати що класи, що мають залежність від інтерфейса (% class="box code" %)ITokenValidator(%%) мають приймати ваш новий клас. | ||
313 | |||
314 | (% class="box" %) | ||
315 | ((( | ||
316 | config/services.yaml | ||
317 | |||
318 | (% class="code" %) | ||
319 | ((( | ||
320 | (% class="linenoswrapper" %) | ||
321 | ((( | ||
322 | (% class="linenos" %) | ||
323 | ((( | ||
324 | 1 | ||
325 | 2 | ||
326 | 3 | ||
327 | 4 | ||
328 | 5 | ||
329 | 6 | ||
330 | 7 | ||
331 | 8 | ||
332 | 9 | ||
333 | 10\\ | ||
334 | ))) | ||
335 | |||
336 | ((( | ||
337 | (% style="font-weight: bold; color: #008000;" %)parameters(%%): | ||
338 | (% style="font-style: italic; color: #408080;" %)# some parameters list(%%) | ||
339 | (% style="font-style: italic; color: #408080;" %)# ...(%%) | ||
340 | \\(% style="font-weight: bold; color: #008000;" %)services(%%): | ||
341 | (% style="font-style: italic; color: #408080;" %)# some services list(%%) | ||
342 | (% style="font-style: italic; color: #408080;" %)# ...(%%) | ||
343 | \\ (% style="font-weight: bold; color: #008000;" %)Ufo\JsonRpcBundle\Security\Interfaces\ITokenValidator(%%): | ||
344 | (% style="font-weight: bold; color: #008000;" %)class(%%): App\Services\RpcSecurity\UserTokenValidator | ||
345 | ))) | ||
346 | ))) | ||
347 | ))) | ||
348 | ))) | ||
349 | |||
350 | = Блок (% class="box code" %)async(%%) = | ||
351 | |||
352 | Цей блок для налаштування [[асинхронного транспорту>>path:/bin/view/docs/JsonRpcBundle/functionality/async/]]. | ||
353 | |||
354 | Додайте параметр (% class="box code" %)rpc_async(%%) який містить рядок у форматі DSN. Цей рядок є конфігурацією [[Symfony Messenger>>url:https://symfony.com/doc/current/messenger.html]], він вказує на асинхронний транспорт по якому RPC Server буде очікувати вхідні запити якщо у вас запущений консюмер ((% class="box code" %)php bin/console messenger:consume rpc_async(%%)). Для більш детального розуміння цього процесу читайте документацію [[Symfony Messenge>>url:https://symfony.com/doc/current/messenger.html]]. | ||
355 | |||
356 | (% class="box" %) | ||
357 | ((( | ||
358 | config/packages/ufo_json_rpc.yaml | ||
359 | |||
360 | (% class="code" %) | ||
361 | ((( | ||
362 | (% class="linenoswrapper" %) | ||
363 | ((( | ||
364 | (% class="linenos" %) | ||
365 | ((( | ||
366 | 1 | ||
367 | 2 | ||
368 | 3 | ||
369 | 4\\ | ||
370 | ))) | ||
371 | |||
372 | ((( | ||
373 | (% style="font-weight: bold; color: #008000;" %)ufo_json_rpc(%%): | ||
374 | (% style="font-weight: bold; color: #008000;" %)async(%%): | ||
375 | (% style="font-weight: bold; color: #008000;" %)rpc_async(%%): (% style="color: #BA2121;" %)'%env(resolve:RPC_TRANSPORT_DSN)%'(%%)\\ | ||
376 | ))) | ||
377 | ))) | ||
378 | ))) | ||
379 | ))) | ||
380 | |||
381 | (% class="box warningmessage" %) | ||
382 | ((( | ||
383 | Це налаштування має на увазі, що у вас в змінних оточення встановлена змінна RPC_TRANSPORT_DSN що містить DSN рядок. | ||
384 | ))) | ||
385 | |||
386 | = Блок (% class="box code" %)docs(%%) = | ||
387 | |||
388 | Це блок який налаштовує генерацію документації коли ви робите GET запит на RPC Server | ||
389 | |||
390 | == Секція (% class="box code" %)response(%%) == | ||
391 | |||
392 | Містить налаштування, що відповідають за вміст відповіді. | ||
393 | |||
394 | === Параметр (% class="box code" %)key_for_methods(%%) === | ||
395 | |||
396 | Цей параметр дозволяє вказати назву ключа у відповіді в якій буде віддаватися масив доступних сервісів. | ||
397 | |||
398 | Значення за замовченням (% class="box code" %)methods(%%). Може мати будь-яке значення типу рядок. | ||
399 | |||
400 | (% class="box" %) | ||
401 | ((( | ||
402 | config/packages/ufo_json_rpc.yaml | ||
403 | |||
404 | (% class="code" %) | ||
405 | ((( | ||
406 | (% class="linenoswrapper" %) | ||
407 | ((( | ||
408 | (% class="linenos" %) | ||
409 | ((( | ||
410 | 1 | ||
411 | 2 | ||
412 | 3\\ | ||
413 | ))) | ||
414 | |||
415 | ((( | ||
416 | (% style="font-weight: bold; color: #008000;" %)ufo_json_rpc(%%): | ||
417 | (% style="font-weight: bold; color: #008000;" %)docs(%%): | ||
418 | (% style="font-weight: bold; color: #008000;" %)key_for_methods(%%): methods | ||
419 | ))) | ||
420 | ))) | ||
421 | ))) | ||
422 | ))) | ||
423 | |||
424 | (% class="box" %) | ||
425 | ((( | ||
426 | config/packages/ufo_json_rpc.yaml | ||
427 | |||
428 | (% class="code" %) | ||
429 | ((( | ||
430 | (% class="linenoswrapper" %) | ||
431 | ((( | ||
432 | (% class="linenos" %) | ||
433 | ((( | ||
434 | 1 | ||
435 | 2 | ||
436 | 3\\ | ||
437 | ))) | ||
438 | |||
439 | ((( | ||
440 | (% style="font-weight: bold; color: #008000;" %)ufo_json_rpc(%%): | ||
441 | (% style="font-weight: bold; color: #008000;" %)docs(%%): | ||
442 | (% style="font-weight: bold; color: #008000;" %)key_for_methods(%%): services | ||
443 | ))) | ||
444 | ))) | ||
445 | ))) | ||
446 | ))) | ||
447 | |||
448 | (% class="box" %) | ||
449 | ((( | ||
450 | config/packages/ufo_json_rpc.yaml | ||
451 | |||
452 | (% class="code" %) | ||
453 | ((( | ||
454 | (% class="linenoswrapper" %) | ||
455 | ((( | ||
456 | (% class="linenos" %) | ||
457 | ((( | ||
458 | 1 | ||
459 | 2 | ||
460 | 3\\ | ||
461 | ))) | ||
462 | |||
463 | ((( | ||
464 | (% style="font-weight: bold; color: #008000;" %)ufo_json_rpc(%%): | ||
465 | (% style="font-weight: bold; color: #008000;" %)docs(%%): | ||
466 | (% style="font-weight: bold; color: #008000;" %)key_for_methods(%%): some_custom_key | ||
467 | ))) | ||
468 | ))) | ||
469 | ))) | ||
470 | ))) | ||
471 | |||
472 | === Параметр (% class="box code" %)async_dsn_info(%%) === | ||
473 | |||
474 | Відповідає за відображення в документації інформації про асинхронний транспорт. | ||
475 | |||
476 | (% class="box" %) | ||
477 | ((( | ||
478 | config/packages/ufo_json_rpc.yaml | ||
479 | |||
480 | (% class="code" %) | ||
481 | ((( | ||
482 | (% class="linenoswrapper" %) | ||
483 | ((( | ||
484 | (% class="linenos" %) | ||
485 | ((( | ||
486 | 1 | ||
487 | 2\\ | ||
488 | ))) | ||
489 | |||
490 | ((( | ||
491 | (% style="font-weight: bold; color: #008000;" %)ufo_json_rpc(%%): | ||
492 | (% style="font-weight: bold; color: #008000;" %)async_dsn_info(%%): true (% style="font-style: italic; color: #408080;" %)# or false | ||
493 | ))) | ||
494 | ))) | ||
495 | ))) | ||
496 | ))) | ||
497 | |||
498 | ==== **Приклад документації ** ==== | ||
499 | |||
500 | (% class="box" %) | ||
501 | ((( | ||
502 | GET: /api | ||
503 | |||
504 | (% class="code" %) | ||
505 | ((( | ||
506 | (% class="linenoswrapper" %) | ||
507 | ((( | ||
508 | (% class="linenos" %) | ||
509 | ((( | ||
510 | 1 | ||
511 | 2 | ||
512 | 3 | ||
513 | 4 | ||
514 | 5 | ||
515 | 6 | ||
516 | 7 | ||
517 | 8 | ||
518 | 9 | ||
519 | 10 | ||
520 | 11 | ||
521 | 12 | ||
522 | 13 | ||
523 | 14 | ||
524 | 15 | ||
525 | 16 | ||
526 | 17 | ||
527 | 18 | ||
528 | 19 | ||
529 | 20 | ||
530 | 21 | ||
531 | 22 | ||
532 | 23 | ||
533 | 24\\ | ||
534 | ))) | ||
535 | |||
536 | ((( | ||
537 | { | ||
538 | (% style="font-weight: bold; color: #008000;" %)"envelope"(%%): (% style="color: #BA2121;" %)"JSON-RPC-2.0/UFO-RPC-6"(%%), | ||
539 | (% style="font-weight: bold; color: #008000;" %)"contentType"(%%): (% style="color: #BA2121;" %)"application/json"(%%), | ||
540 | (% style="font-weight: bold; color: #008000;" %)"description"(%%): (% style="color: #BA2121;" %)""(%%), | ||
541 | (% style="font-weight: bold; color: #008000;" %)"transport"(%%): { | ||
542 | (% style="font-weight: bold; color: #008000;" %)"sync"(%%): { | ||
543 | (% style="font-weight: bold; color: #008000;" %)"scheme"(%%): (% style="color: #BA2121;" %)"http"(%%), | ||
544 | (% style="font-weight: bold; color: #008000;" %)"host"(%%): (% style="color: #BA2121;" %)"example.com"(%%), | ||
545 | (% style="font-weight: bold; color: #008000;" %)"path"(%%): (% style="color: #BA2121;" %)"/api"(%%), | ||
546 | (% style="font-weight: bold; color: #008000;" %)"method"(%%): (% style="color: #BA2121;" %)"POST"(%%) | ||
547 | }, | ||
548 | (% style="font-weight: bold; color: #008000;" %)"async"(%%): { | ||
549 | (% style="font-weight: bold; color: #008000;" %)"scheme"(%%): (% style="color: #BA2121;" %)"amqp"(%%), | ||
550 | (% style="font-weight: bold; color: #008000;" %)"user"(%%): (% style="color: #BA2121;" %)"{user}"(%%), | ||
551 | (% style="font-weight: bold; color: #008000;" %)"pass"(%%): (% style="color: #BA2121;" %)"{pass}"(%%), | ||
552 | (% style="font-weight: bold; color: #008000;" %)"host"(%%): (% style="color: #BA2121;" %)"async_rabbit"(%%), | ||
553 | (% style="font-weight: bold; color: #008000;" %)"port"(%%): (% style="color: #666666;" %)5672(%%), | ||
554 | (% style="font-weight: bold; color: #008000;" %)"path"(%%): (% style="color: #BA2121;" %)"/%2f/json-rpc"(%%) | ||
555 | } | ||
556 | }, | ||
557 | (% style="font-weight: bold; color: #008000;" %)"methods"(%%): { | ||
558 | (% style="border: 1px solid #FF0000;" %)...(%%) | ||
559 | } | ||
560 | } | ||
561 | ))) | ||
562 | ))) | ||
563 | ))) | ||
564 | ))) | ||
565 | |||
566 | (% class="box infomessage" %) | ||
567 | ((( | ||
568 | Не переймайтеся щодо безпеки ваших авторизаційних даних. що містяться в DSN. | ||
569 | |||
570 | Документатор побудований таким чином, що перед виводом інформації про DSN він видаляє дані про користувача і його пароль, а також інші секретні дані, як то токени, секретні ключі, тощо. | ||
571 | |||
572 | Шаблон, по якому відбувається захист (% class="box code" %)/([\w\d_]*(?:secret|access|token|key)[_\w]*)=((?:\w|\d)+(?=&?))/(%%). | ||
573 | |||
574 | Приклад: | ||
575 | |||
576 | (% class="box" %) | ||
577 | ((( | ||
578 | RPC_TRANSPORT_DSN=https://sqs.eu-west-3.amazonaws.com/123456789012/messages?access_key=AKIAIOSFODNN7EXAMPLE&secret_key=j17M97ffSVoKI0briFoo9a | ||
579 | |||
580 | (% class="code" %) | ||
581 | ((( | ||
582 | (% class="linenoswrapper" %) | ||
583 | ((( | ||
584 | (% class="linenos" %) | ||
585 | ((( | ||
586 | 1 | ||
587 | 2 | ||
588 | 3 | ||
589 | 4 | ||
590 | 5 | ||
591 | 6 | ||
592 | 7 | ||
593 | 8\\ | ||
594 | ))) | ||
595 | |||
596 | ((( | ||
597 | { | ||
598 | (% style="font-weight: bold; color: #008000;" %)"async"(%%): { | ||
599 | (% style="font-weight: bold; color: #008000;" %)"scheme"(%%): (% style="color: #BA2121;" %)"https"(%%), | ||
600 | (% style="font-weight: bold; color: #008000;" %)"host"(%%): (% style="color: #BA2121;" %)"sqs.eu-west-3.amazonaws.com"(%%), | ||
601 | (% style="font-weight: bold; color: #008000;" %)"path"(%%): (% style="color: #BA2121;" %)"/123456789012/messages"(%%), | ||
602 | (% style="font-weight: bold; color: #008000;" %)"query"(%%): (% style="color: #BA2121;" %)"access_key={access_key}&secret_key={secret_key}"(%%) | ||
603 | } | ||
604 | } | ||
605 | ))) | ||
606 | ))) | ||
607 | ))) | ||
608 | ))) | ||
609 | ))) | ||
610 | |||
611 | === Параметр (% class="box code" %)validations(%%) === | ||
612 | |||
613 | Відповідає за відображення в документації методів додаткових блоків, що вказують на вимоги до валідації даних. | ||
614 | |||
615 | Наразі цей блок має два можливих налаштування: | ||
616 | |||
617 | * (% class="box code" %)json_schema: <bool> | ||
618 | * (% class="box code" %)symfony_asserts: <bool> | ||
619 | |||
620 | У всіх опцій в цьому параметрі значення за замовченням (% class="box code" %)false(%%), тобто ці блоки не будуть відображатися в документації. | ||
621 | Якщо ви потребуєте якийсь з цих блоків інформації при запиті документації, то встановіть значення в (% class="box code" %)true(%%). | ||
622 | |||
623 | (% class="box" %) | ||
624 | ((( | ||
625 | config/packages/ufo_json_rpc.yaml | ||
626 | |||
627 | (% class="code" %) | ||
628 | ((( | ||
629 | (% class="linenoswrapper" %) | ||
630 | ((( | ||
631 | (% class="linenos" %) | ||
632 | ((( | ||
633 | 1 | ||
634 | 2 | ||
635 | 3 | ||
636 | 4\\ | ||
637 | ))) | ||
638 | |||
639 | ((( | ||
640 | (% style="font-weight: bold; color: #008000;" %)ufo_json_rpc(%%): | ||
641 | (% style="font-weight: bold; color: #008000;" %)docs(%%): | ||
642 | (% style="font-weight: bold; color: #008000;" %)json_schema(%%): true | ||
643 | (% style="font-weight: bold; color: #008000;" %)symfony_asserts(%%): true | ||
644 | ))) | ||
645 | ))) | ||
646 | ))) | ||
647 | ))) | ||
648 | |||
649 | ==== **Приклад документації ** ==== | ||
650 | |||
651 | (% class="box infomessage" %) | ||
652 | ((( | ||
653 | В цьому прикладі я видалив вміст обʼєктів symfony_assertions для спрощення прикладу. | ||
654 | Детальніше про валідацію методів дивись сторінку **[[path:/bin/view/docs/JsonRpcBundle/add_rpc_service/assertions/]]** | ||
655 | ))) | ||
656 | |||
657 | (% class="box" %) | ||
658 | ((( | ||
659 | GET: /api | ||
660 | |||
661 | (% class="code" %) | ||
662 | ((( | ||
663 | (% class="linenoswrapper" %) | ||
664 | ((( | ||
665 | (% class="linenos" %) | ||
666 | ((( | ||
667 | 1 | ||
668 | 2 | ||
669 | 3 | ||
670 | 4 | ||
671 | 5 | ||
672 | 6 | ||
673 | 7 | ||
674 | 8 | ||
675 | 9 | ||
676 | 10 | ||
677 | 11 | ||
678 | 12 | ||
679 | 13 | ||
680 | 14 | ||
681 | 15 | ||
682 | 16 | ||
683 | 17 | ||
684 | 18 | ||
685 | 19 | ||
686 | 20 | ||
687 | 21 | ||
688 | 22 | ||
689 | 23 | ||
690 | 24 | ||
691 | 25 | ||
692 | 26 | ||
693 | 27 | ||
694 | 28 | ||
695 | 29 | ||
696 | 30 | ||
697 | 31 | ||
698 | 32 | ||
699 | 33 | ||
700 | 34 | ||
701 | 35 | ||
702 | 36 | ||
703 | 37 | ||
704 | 38 | ||
705 | 39 | ||
706 | 40 | ||
707 | 41 | ||
708 | 42 | ||
709 | 43 | ||
710 | 44 | ||
711 | 45 | ||
712 | 46 | ||
713 | 47 | ||
714 | 48 | ||
715 | 49 | ||
716 | 50 | ||
717 | 51 | ||
718 | 52 | ||
719 | 53 | ||
720 | 54 | ||
721 | 55 | ||
722 | 56 | ||
723 | 57 | ||
724 | 58 | ||
725 | 59 | ||
726 | 60 | ||
727 | 61 | ||
728 | 62 | ||
729 | 63 | ||
730 | 64 | ||
731 | 65 | ||
732 | 66 | ||
733 | 67 | ||
734 | 68 | ||
735 | 69 | ||
736 | 70 | ||
737 | 71 | ||
738 | 72 | ||
739 | 73 | ||
740 | 74 | ||
741 | 75 | ||
742 | 76 | ||
743 | 77 | ||
744 | 78 | ||
745 | 79 | ||
746 | 80 | ||
747 | 81 | ||
748 | 82 | ||
749 | 83 | ||
750 | 84 | ||
751 | 85 | ||
752 | 86 | ||
753 | 87 | ||
754 | 88 | ||
755 | 89 | ||
756 | 90 | ||
757 | 91 | ||
758 | 92 | ||
759 | 93 | ||
760 | 94 | ||
761 | 95 | ||
762 | 96 | ||
763 | 97 | ||
764 | 98 | ||
765 | 99 | ||
766 | 100 | ||
767 | 101 | ||
768 | 102 | ||
769 | 103 | ||
770 | 104 | ||
771 | 105 | ||
772 | 106 | ||
773 | 107 | ||
774 | 108 | ||
775 | 109 | ||
776 | 110 | ||
777 | 111 | ||
778 | 112 | ||
779 | 113 | ||
780 | 114 | ||
781 | 115 | ||
782 | 116 | ||
783 | 117 | ||
784 | 118 | ||
785 | 119 | ||
786 | 120 | ||
787 | 121 | ||
788 | 122 | ||
789 | 123 | ||
790 | 124 | ||
791 | 125 | ||
792 | 126 | ||
793 | 127\\ | ||
794 | ))) | ||
795 | |||
796 | ((( | ||
797 | { | ||
798 | (% style="font-weight: bold; color: #008000;" %)"envelope"(%%): (% style="color: #BA2121;" %)"JSON-RPC-2.0/UFO-RPC-6"(%%), | ||
799 | (% style="font-weight: bold; color: #008000;" %)"contentType"(%%): (% style="color: #BA2121;" %)"application/json"(%%), | ||
800 | (% style="font-weight: bold; color: #008000;" %)"description"(%%): (% style="color: #BA2121;" %)""(%%), | ||
801 | (% style="font-weight: bold; color: #008000;" %)"transport"(%%): { | ||
802 | (% style="font-weight: bold; color: #008000;" %)"sync"(%%): { | ||
803 | (% style="font-weight: bold; color: #008000;" %)"scheme"(%%): (% style="color: #BA2121;" %)"https"(%%), | ||
804 | (% style="font-weight: bold; color: #008000;" %)"host"(%%): (% style="color: #BA2121;" %)"example.com"(%%), | ||
805 | (% style="font-weight: bold; color: #008000;" %)"path"(%%): (% style="color: #BA2121;" %)"/api"(%%), | ||
806 | (% style="font-weight: bold; color: #008000;" %)"method"(%%): (% style="color: #BA2121;" %)"POST"(%%) | ||
807 | } | ||
808 | }, | ||
809 | (% style="font-weight: bold; color: #008000;" %)"methods"(%%): { | ||
810 | (% style="font-weight: bold; color: #008000;" %)"getUserNameByUuid"(%%): { | ||
811 | (% style="font-weight: bold; color: #008000;" %)"name"(%%): (% style="color: #BA2121;" %)"getUserNameByUuid"(%%), | ||
812 | (% style="font-weight: bold; color: #008000;" %)"description"(%%): (% style="color: #BA2121;" %)"Get username by id"(%%), | ||
813 | (% style="font-weight: bold; color: #008000;" %)"parameters"(%%): { | ||
814 | (% style="font-weight: bold; color: #008000;" %)"userId"(%%): { | ||
815 | (% style="font-weight: bold; color: #008000;" %)"type"(%%): (% style="color: #BA2121;" %)"string"(%%), | ||
816 | (% style="font-weight: bold; color: #008000;" %)"name"(%%): (% style="color: #BA2121;" %)"userId"(%%), | ||
817 | (% style="font-weight: bold; color: #008000;" %)"description"(%%): (% style="color: #BA2121;" %)"User id in uuid format"(%%), | ||
818 | (% style="font-weight: bold; color: #008000;" %)"optional"(%%): (% style="font-weight: bold; color: #008000;" %)false(%%) | ||
819 | } | ||
820 | }, | ||
821 | (% style="font-weight: bold; color: #008000;" %)"returns"(%%): (% style="color: #BA2121;" %)"string"(%%), | ||
822 | (% style="font-weight: bold; color: #008000;" %)"responseFormat"(%%): (% style="color: #BA2121;" %)"string"(%%), | ||
823 | (% style="font-weight: bold; color: #008000;" %)"json_schema"(%%): { | ||
824 | (% style="font-weight: bold; color: #008000;" %)"$schema"(%%): (% style="color: #BA2121;" %)"http:~/~/json-schema.org/draft-07/schema#"(%%), | ||
825 | (% style="font-weight: bold; color: #008000;" %)"type"(%%): (% style="color: #BA2121;" %)"object"(%%), | ||
826 | (% style="font-weight: bold; color: #008000;" %)"properties"(%%): { | ||
827 | (% style="font-weight: bold; color: #008000;" %)"userId"(%%): { | ||
828 | (% style="font-weight: bold; color: #008000;" %)"type"(%%): (% style="color: #BA2121;" %)"string"(%%) | ||
829 | } | ||
830 | }, | ||
831 | (% style="font-weight: bold; color: #008000;" %)"required"(%%): [ | ||
832 | (% style="color: #BA2121;" %)"userId"(%%) | ||
833 | ] | ||
834 | }, | ||
835 | (% style="font-weight: bold; color: #008000;" %)"symfony_assertions"(%%): { | ||
836 | (% style="font-weight: bold; color: #008000;" %)"userId"(%%): [ | ||
837 | { | ||
838 | (% style="font-weight: bold; color: #008000;" %)"class"(%%): (% style="color: #BA2121;" %)"Symfony~\~\Component~\~\Validator~\~\Constraints~\~\Uuid"(%%), | ||
839 | (% style="font-weight: bold; color: #008000;" %)"context"(%%): {} | ||
840 | } | ||
841 | ] | ||
842 | } | ||
843 | }, | ||
844 | (% style="font-weight: bold; color: #008000;" %)"sendEmail"(%%): { | ||
845 | (% style="font-weight: bold; color: #008000;" %)"name"(%%): (% style="color: #BA2121;" %)"sendEmail"(%%), | ||
846 | (% style="font-weight: bold; color: #008000;" %)"description"(%%): (% style="color: #BA2121;" %)"Send mail"(%%), | ||
847 | (% style="font-weight: bold; color: #008000;" %)"parameters"(%%): { | ||
848 | (% style="font-weight: bold; color: #008000;" %)"email"(%%): { | ||
849 | (% style="font-weight: bold; color: #008000;" %)"type"(%%): (% style="color: #BA2121;" %)"string"(%%), | ||
850 | (% style="font-weight: bold; color: #008000;" %)"name"(%%): (% style="color: #BA2121;" %)"email"(%%), | ||
851 | (% style="font-weight: bold; color: #008000;" %)"description"(%%): (% style="color: #BA2121;" %)""(%%), | ||
852 | (% style="font-weight: bold; color: #008000;" %)"optional"(%%): (% style="font-weight: bold; color: #008000;" %)false(%%) | ||
853 | }, | ||
854 | (% style="font-weight: bold; color: #008000;" %)"subject"(%%): { | ||
855 | (% style="font-weight: bold; color: #008000;" %)"type"(%%): (% style="color: #BA2121;" %)"string"(%%), | ||
856 | (% style="font-weight: bold; color: #008000;" %)"name"(%%): (% style="color: #BA2121;" %)"subject"(%%), | ||
857 | (% style="font-weight: bold; color: #008000;" %)"description"(%%): (% style="color: #BA2121;" %)""(%%), | ||
858 | (% style="font-weight: bold; color: #008000;" %)"optional"(%%): (% style="font-weight: bold; color: #008000;" %)false(%%) | ||
859 | }, | ||
860 | (% style="font-weight: bold; color: #008000;" %)"text"(%%): { | ||
861 | (% style="font-weight: bold; color: #008000;" %)"type"(%%): (% style="color: #BA2121;" %)"string"(%%), | ||
862 | (% style="font-weight: bold; color: #008000;" %)"name"(%%): (% style="color: #BA2121;" %)"text"(%%), | ||
863 | (% style="font-weight: bold; color: #008000;" %)"description"(%%): (% style="color: #BA2121;" %)""(%%), | ||
864 | (% style="font-weight: bold; color: #008000;" %)"optional"(%%): (% style="font-weight: bold; color: #008000;" %)false(%%) | ||
865 | } | ||
866 | }, | ||
867 | (% style="font-weight: bold; color: #008000;" %)"returns"(%%): (% style="color: #BA2121;" %)"boolean"(%%), | ||
868 | (% style="font-weight: bold; color: #008000;" %)"responseFormat"(%%): (% style="color: #BA2121;" %)"boolean"(%%), | ||
869 | (% style="font-weight: bold; color: #008000;" %)"json_schema"(%%): { | ||
870 | (% style="font-weight: bold; color: #008000;" %)"$schema"(%%): (% style="color: #BA2121;" %)"http:~/~/json-schema.org/draft-07/schema#"(%%), | ||
871 | (% style="font-weight: bold; color: #008000;" %)"type"(%%): (% style="color: #BA2121;" %)"object"(%%), | ||
872 | (% style="font-weight: bold; color: #008000;" %)"properties"(%%): { | ||
873 | (% style="font-weight: bold; color: #008000;" %)"email"(%%): { | ||
874 | (% style="font-weight: bold; color: #008000;" %)"type"(%%): (% style="color: #BA2121;" %)"string"(%%), | ||
875 | (% style="font-weight: bold; color: #008000;" %)"format"(%%): (% style="color: #BA2121;" %)"email"(%%) | ||
876 | }, | ||
877 | (% style="font-weight: bold; color: #008000;" %)"subject"(%%): { | ||
878 | (% style="font-weight: bold; color: #008000;" %)"type"(%%): (% style="color: #BA2121;" %)"string"(%%), | ||
879 | (% style="font-weight: bold; color: #008000;" %)"minLength"(%%): (% style="color: #666666;" %)1(%%), | ||
880 | (% style="font-weight: bold; color: #008000;" %)"maxLength"(%%): (% style="color: #666666;" %)100(%%) | ||
881 | }, | ||
882 | (% style="font-weight: bold; color: #008000;" %)"text"(%%): { | ||
883 | (% style="font-weight: bold; color: #008000;" %)"type"(%%): (% style="color: #BA2121;" %)"string"(%%), | ||
884 | (% style="font-weight: bold; color: #008000;" %)"minLength"(%%): (% style="color: #666666;" %)10(%%) | ||
885 | } | ||
886 | }, | ||
887 | (% style="font-weight: bold; color: #008000;" %)"required"(%%): [ | ||
888 | (% style="color: #BA2121;" %)"email"(%%), | ||
889 | (% style="color: #BA2121;" %)"subject"(%%), | ||
890 | (% style="color: #BA2121;" %)"text"(%%) | ||
891 | ] | ||
892 | }, | ||
893 | (% style="font-weight: bold; color: #008000;" %)"symfony_assertions"(%%): { | ||
894 | (% style="font-weight: bold; color: #008000;" %)"email"(%%): [ | ||
895 | { | ||
896 | (% style="font-weight: bold; color: #008000;" %)"class"(%%): (% style="color: #BA2121;" %)"Symfony~\~\Component~\~\Validator~\~\Constraints~\~\Email"(%%), | ||
897 | (% style="font-weight: bold; color: #008000;" %)"context"(%%): {} | ||
898 | } | ||
899 | ], | ||
900 | (% style="font-weight: bold; color: #008000;" %)"subject"(%%): [ | ||
901 | { | ||
902 | (% style="font-weight: bold; color: #008000;" %)"class"(%%): (% style="color: #BA2121;" %)"Symfony~\~\Component~\~\Validator~\~\Constraints~\~\NotBlank"(%%), | ||
903 | (% style="font-weight: bold; color: #008000;" %)"context"(%%): {} | ||
904 | }, | ||
905 | { | ||
906 | (% style="font-weight: bold; color: #008000;" %)"class"(%%): (% style="color: #BA2121;" %)"Symfony~\~\Component~\~\Validator~\~\Constraints~\~\Length"(%%), | ||
907 | (% style="font-weight: bold; color: #008000;" %)"context"(%%): {} | ||
908 | } | ||
909 | ], | ||
910 | (% style="font-weight: bold; color: #008000;" %)"text"(%%): [ | ||
911 | { | ||
912 | (% style="font-weight: bold; color: #008000;" %)"class"(%%): (% style="color: #BA2121;" %)"Symfony~\~\Component~\~\Validator~\~\Constraints~\~\NotBlank"(%%), | ||
913 | (% style="font-weight: bold; color: #008000;" %)"context"(%%): {} | ||
914 | }, | ||
915 | { | ||
916 | (% style="font-weight: bold; color: #008000;" %)"class"(%%): (% style="color: #BA2121;" %)"Symfony~\~\Component~\~\Validator~\~\Constraints~\~\Length"(%%), | ||
917 | (% style="font-weight: bold; color: #008000;" %)"context"(%%): {} | ||
918 | } | ||
919 | ] | ||
920 | } | ||
921 | } | ||
922 | } | ||
923 | } | ||
924 | ))) | ||
925 | ))) | ||
926 | ))) | ||
927 | ))) |