@appaflytech/zeyrek-ai-mcp 0.0.1

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 (88) hide show
  1. package/.env.example +46 -0
  2. package/README.md +260 -0
  3. package/dist/config.js +128 -0
  4. package/dist/index.js +88 -0
  5. package/dist/knowledge/componentCatalog.js +438 -0
  6. package/dist/knowledge/componentEvents.js +164 -0
  7. package/dist/knowledge/coverage.js +60 -0
  8. package/dist/knowledge/normalize.js +1309 -0
  9. package/dist/knowledge/outlines.js +167 -0
  10. package/dist/knowledge/policy.js +44 -0
  11. package/dist/knowledge/reservedTables.js +75 -0
  12. package/dist/knowledge/rules.js +1725 -0
  13. package/dist/observability/trace.js +337 -0
  14. package/dist/platform/errors.js +132 -0
  15. package/dist/platform/http.js +169 -0
  16. package/dist/platform/responses.js +94 -0
  17. package/dist/platform/token.js +79 -0
  18. package/dist/resources/examples/entity-pair-m2m.json +105 -0
  19. package/dist/resources/examples/flow-list-to-form.json +65 -0
  20. package/dist/resources/examples/flow-load-table.json +178 -0
  21. package/dist/resources/examples/microservice-table-list.json +49 -0
  22. package/dist/resources/examples/middleware-authenticate.json +194 -0
  23. package/dist/resources/examples/page-detail-profile.json +95 -0
  24. package/dist/resources/examples/page-dynamic-form.json +221 -0
  25. package/dist/resources/examples/page-form-upsert.json +279 -0
  26. package/dist/resources/examples/page-html-embed.json +106 -0
  27. package/dist/resources/examples/page-sign-in.json +194 -0
  28. package/dist/resources/examples/workflow-basic.json +94 -0
  29. package/dist/resources/guides/component.md +108 -0
  30. package/dist/resources/guides/entity.md +348 -0
  31. package/dist/resources/guides/expressions.md +139 -0
  32. package/dist/resources/guides/getting-started.md +109 -0
  33. package/dist/resources/guides/html.md +106 -0
  34. package/dist/resources/guides/microservice.md +313 -0
  35. package/dist/resources/guides/middleware.md +271 -0
  36. package/dist/resources/guides/modify-existing.md +35 -0
  37. package/dist/resources/guides/page.md +1574 -0
  38. package/dist/resources/guides/view.md +89 -0
  39. package/dist/resources/guides/workflow.md +359 -0
  40. package/dist/resources/knowledge.js +44 -0
  41. package/dist/resources/schemas/CreateDatasourceCommand.json +54 -0
  42. package/dist/resources/schemas/CreateDiscoveredEntitiesCommand.json +133 -0
  43. package/dist/resources/schemas/CreateEntityCommand.json +387 -0
  44. package/dist/resources/schemas/CreateMicroserviceCommand.json +528 -0
  45. package/dist/resources/schemas/CreateQueryCommand.json +43 -0
  46. package/dist/resources/schemas/CreateViewCommand.json +158 -0
  47. package/dist/resources/schemas/CreateWorkflowCommand.json +1719 -0
  48. package/dist/resources/schemas/Filter.json +124 -0
  49. package/dist/resources/schemas/GetRowsQuery.json +122 -0
  50. package/dist/resources/schemas/PreviewMicroserviceCommand.json +565 -0
  51. package/dist/resources/schemas/PreviewQueryCommand.json +74 -0
  52. package/dist/resources/schemas/PreviewWorkflowCommand.json +981 -0
  53. package/dist/resources/schemas/StepDto.json +900 -0
  54. package/dist/resources/schemas/UpdateDatasourceCommand.json +58 -0
  55. package/dist/resources/schemas/UpdateEntityCommand.json +679 -0
  56. package/dist/resources/schemas/UpdateMicroserviceCommand.json +512 -0
  57. package/dist/resources/schemas/UpdateQueryCommand.json +86 -0
  58. package/dist/resources/schemas/UpdateViewCommand.json +153 -0
  59. package/dist/resources/schemas/UpdateWorkflowCommand.json +1719 -0
  60. package/dist/resources/schemas.js +40 -0
  61. package/dist/server.js +63 -0
  62. package/dist/tools/access.js +27 -0
  63. package/dist/tools/applications.js +59 -0
  64. package/dist/tools/automations.js +103 -0
  65. package/dist/tools/common.js +18 -0
  66. package/dist/tools/components.js +280 -0
  67. package/dist/tools/datasources.js +82 -0
  68. package/dist/tools/dependencies.js +17 -0
  69. package/dist/tools/deployments.js +17 -0
  70. package/dist/tools/dtos.js +54 -0
  71. package/dist/tools/entities.js +166 -0
  72. package/dist/tools/files.js +138 -0
  73. package/dist/tools/flows.js +226 -0
  74. package/dist/tools/folders.js +51 -0
  75. package/dist/tools/layouts.js +105 -0
  76. package/dist/tools/microservices.js +198 -0
  77. package/dist/tools/organizations.js +65 -0
  78. package/dist/tools/pages.js +597 -0
  79. package/dist/tools/platformInfo.js +44 -0
  80. package/dist/tools/projects.js +152 -0
  81. package/dist/tools/queries.js +48 -0
  82. package/dist/tools/rows.js +115 -0
  83. package/dist/tools/screens.js +97 -0
  84. package/dist/tools/templates.js +65 -0
  85. package/dist/tools/variables.js +94 -0
  86. package/dist/tools/views.js +58 -0
  87. package/dist/tools/workflows.js +196 -0
  88. package/package.json +49 -0
@@ -0,0 +1,313 @@
1
+ # Microservice Rehberi
2
+
3
+ ## Karar kuralları
4
+
5
+ 1. **Kaynak seçimi sırası: `table` → `view` → `workflow` → `query`.**
6
+ - `table`: tek entity üzerinde CRUD/listeleme — varsayılan tercih. Filtre, join, sıralama,
7
+ alan seçimi hepsi `sourceParameters` ile platform-native.
8
+ - `view`: **tam metin ARAMA** — kaynak bir view arama indeksidir (raporlama değil!). Uç
9
+ **zorunlu `searchText`** parametresi ister (yoksa `MissingParameterException`), `limit`
10
+ (varsayılan 20) ve `offset` alır. Kurulum sırası: `view_create` (aranacak tablo/alanlar +
11
+ analizör) → `view_rebuild` → `source:'view'` microservice. Satış özeti / top-N gibi
12
+ rapor işleri view'e DEĞİL raw query'e aittir (`query_create` + query kaynaklı uç).
13
+ - `workflow`: çok adımlı iş mantığı gerekiyorsa microservice'i workflow'a bağla
14
+ (`workflowId`).
15
+ - `query`: yalnız ham SQL gerçekten şartsa. Politika lint'i her query kullanımında gerekçe
16
+ ister (MS-002, warn).
17
+ 2. **`selectFields` asla boş bırakılmaz.** Boş = platform TÜM alanları döndürür; join'li
18
+ sorguda maliyet katlanır. Join varken boş bırakmak block'tur (MS-001).
19
+ 3. **Join'ler ilişkilerden türetilir.** `sourceParameters.joins` öğesi:
20
+ `{ alias, entityId, fieldId, path, joins }` — iç içe join mümkündür. Her join'in getirdiği
21
+ alanlar da `selectFields`'ta `{ alias, fieldId, path, fieldSlug }` ile açıkça seçilir.
22
+ **`path` boş bırakılamaz** — Studio join'li seçimleri yalnız `path` üzerinden eşleştirir
23
+ (aşağıdaki bölüme bak).
24
+ 4. **HTTP sözleşmesi:** `type: 'rest-api'` + `httpMethod: get|post|put|delete` + `url`
25
+ (route şablonu). Zamanlanmış işler `type: 'schedule-job'`, kuyruk tüketicileri `consumer`.
26
+ 5. **Doğrulama akışı:** `microservice_preview` (kaydetmeden) → kaydet → `microservice_execute`
27
+ (gerçek runtime yolu) → sonuç beklenmedikse **aynı parametrelerle `microservice_debug`** —
28
+ adım adım executionItems ağacı döner.
29
+
30
+ ## Asgari doğru table-source microservice (platformda doğrulanmış kurallar)
31
+
32
+ - `operation` enum'u **tam olarak** şudur ([MicroserviceTableOperation](../../../../zeyrek-backend/src/NgApps.Domain/Contexts/Studio/Microservices/MicroserviceTableOperation.cs)):
33
+ `get · create · update · delete · upsert · count`. **`list` yoktur** — liste için
34
+ `operation: 'get'` + `isSingle: false`, tek kayıt için `isSingle: true`.
35
+ - **`upsert` vardır ve tercih edilmelidir.** Aynı formun hem yeni kayıt hem düzenleme
36
+ yapmasını iki ayrı uç + akışta condition dallanmasıyla kurmayın: tek `operation: 'upsert'`
37
+ ucu yeterlidir. Aynı işlem workflow **table step**'inde de desteklenir
38
+ ([TableStep](../../../../zeyrek-backend/src/NgApps.Domain/Contexts/Flow/Workflows/Steps/TableStep.cs)).
39
+ - **`count` vardır** — kayıt sayısı için tüm satırları çekip uzunluğuna bakmayın; panel/KPI
40
+ sayaçlarının doğru şekli budur.
41
+ - Gövde kökünde **`entityId` zorunlu**. (Eski "kökte datasourceId zorunlu" bilgisi yanlıştı —
42
+ komutta öyle bir alan yok, gönderilirse yok sayılır; asıl zorunluluk selectFields'taki fieldId.)
43
+ - `selectFields[]` öğelerinde **`fieldId` zorunlu** (yalnız `fieldSlug` yetmez); kök tablo
44
+ alanlarında alias = **entity'nin slug'ı** ve `path` **yazılmaz**. Alias verilmezse
45
+ `ArgumentNullException(key)`, farklı alias verilirse "missing FROM-clause entry" gelir.
46
+ - `sort` ve `limit` **her zaman** verilir (MS-008, block) — aşağıya bak.
47
+ - Filtrelerde `value` bir **JavaScript ifadesidir** — sabit metin tırnak içinde
48
+ (`"'ACTIVE'"`), dinamik değer ifade olarak (`"input.status"`).
49
+ - **Operatör sözlüğü kapalıdır:** `eq · neq · lt · lte · gt · gte · contains · icontains ·
50
+ in · notin · startswith · endswith · istartswith · iendswith`. **`like` YOKTUR** — metin
51
+ araması `contains`/`icontains` iledir. Kritik tuzak: platform bilinmeyen operatörü 400 ile
52
+ reddetmez, **sessizce `eq`'e düşürür** (enum çözümlemesi unknown'da default'a düşer) — uç
53
+ testten geçer ama arama hiçbir şey döndürmez. Gateway MS-012/WF-015 bunu istek gitmeden keser.
54
+ - **Write operasyonunda (`create`/`update`) yanıt yalnız primary key'dir** — platform sonucu
55
+ PK'ya indirger ve `selectFields`'ı kaydederken sessizce boşaltır. Yanıtın başka alan da
56
+ dönmesi gerekiyorsa **`returnFields`** kullanılır (öğeler `selectFields` ile aynı şekildedir:
57
+ `{ alias, fieldId, fieldSlug }`). Aynı kural workflow table create/update step'lerinde de
58
+ geçerlidir. Boş `returnFields` ile write operasyonu yazarsan MS-009 uyarır; buna karşılık
59
+ write operasyonunda boş `selectFields` **uyarı üretmez** (platform onu zaten yok sayar).
60
+
61
+ ```jsonc
62
+ {
63
+ "name": "Get Orders", "slug": "get-orders",
64
+ "type": "rest-api", "httpMethod": "get", "url": "orders",
65
+ "source": "table", "returnType": "flat",
66
+ "isReturnSchemaArray": true, "isParameterSchemaArray": false,
67
+ "entityId": "<orders entity id>",
68
+ "sourceParameters": {
69
+ "operation": "get",
70
+ "entityId": "<orders entity id>",
71
+ "alias": "orders",
72
+ "isSingle": false,
73
+ "selectFields": [
74
+ { "alias": "orders", "fieldId": "<code alanı id>", "fieldSlug": "code" },
75
+ { "alias": "orders", "fieldId": "<total alanı id>", "fieldSlug": "total" }
76
+ ],
77
+ "joins": [],
78
+ "filters": [],
79
+ "sort": { "name": "sortField", "type": "sortType", "source": "query-string" },
80
+ "limit": { "name": "limit", "offset": "offset", "source": "query-string" }
81
+ }
82
+ }
83
+ ```
84
+
85
+ ## İlişki alanı: `row_list` NESNE döner, uç/step JOIN'siz DÜZ FK döner
86
+
87
+ Aynı alan iki yoldan farklı şekilde gelir ve bu fark sessizce yanlış sonuç üretir:
88
+
89
+ | Okuma yolu | İlişki alanının şekli |
90
+ |---|---|
91
+ | `row_list` | **her zaman** `{ id, <displayAlanı> }` — handler her ilişki alanı için join'i kendisi üretir (`GetRowsQuery.ProcessRelation`) |
92
+ | microservice / workflow table adımı | **yalnız `joins` içinde bildirdiğin ilişki** nesne olur (`ModifyResponse` → `ProcessJoins`); bildirmediğin alan **düz FK string**'idir |
93
+
94
+ Yani `row_list` çıktısına bakıp yazdığın `x.owner_id.email` ifadesi bir uçta `undefined` döner —
95
+ hata vermez, sessizce boş sonuç üretir (rol dizisi boşalırsa yetki kapısı yanlış karar verir).
96
+ Gateway bu okumayı **WF-020** ile uyarır. Çözüm: ya alanı `joins` + `selectFields` ile getir,
97
+ ya hedef tabloyu ayrı okuyup id ile eşleştir; her iki şekle dayanan yazım
98
+ `var v = x.owner_id && x.owner_id.id ? x.owner_id.id : x.owner_id;`.
99
+
100
+ ## Table join kuralları (\"null key\" hatasının gerçek sebebi)
101
+
102
+ Join motoru çalışır; hatalar **alias disiplinsizliğinden** çıkar (`PostgreSqlRelationHandler`
103
+ girdileri doğrulamadan sözlük kurar, eksik alias opak 500'e dönüşür — bug değil):
104
+
105
+ - **Kök `alias` (sourceParameters.alias) join varken ZORUNLUDUR** ve kök entity'nin slug'ı
106
+ olmalıdır. Join'in ON koşulunun sol tarafını bu alias kurar
107
+ (`PostgreSqlRelationHandler.HandleJoin` → `$"{alias}.{ilişkiAlanı}"`); boş bırakılırsa SQL'e
108
+ `.<alan>` gider, SqlKata onu `""."<alan>"` diye tırnaklar ve Postgres **`42601: zero-length
109
+ delimited identifier`** ile reddeder — hata ne alanı ne sebebi söyler. Runtime'da varsayılan
110
+ ATANMAZ (şema üretimi `Entity.Slug`'a düşer, çalıştırma düşmez). Gateway alias'ı kök
111
+ `selectFields`'tan türetebiliyorsa doldurur (`gatewayNormalized`), türetemezse **MS-013**
112
+ block'uyla isteği hiç göndermez. Aynı kural workflow/middleware table step'i için de geçerli
113
+ (**WF-019**).
114
+ - Her join'de `alias`, `entityId`, `fieldId` ZORUNLU; alias'lar benzersiz olmalı.
115
+ - `fieldId` = ana entity'deki **ilişki alanının** id'si (hedef entity'nin değil).
116
+ - Join'in getirdiği her alan `selectFields`'ta **join'in alias'ı + `path` zinciriyle** seçilir;
117
+ ana tablo alanları entity slug alias'ıyla, `path` yazılmadan.
118
+ - Alias'sız join/select → 500 "null key"; tanımsız alias → "missing FROM-clause entry".
119
+
120
+ ### `path` kuralı (Studio bunsuz join alanlarını GÖSTEREMEZ)
121
+
122
+ `path`, kökten o düğüme kadarki **ilişki alanı id'lerinin nokta ile birleşimidir**. İki farklı
123
+ biçim var, karıştırma:
124
+
125
+ | Nerede | Değer | Örnek |
126
+ |---|---|---|
127
+ | `joins[].path` | `<kökEntityId>` **+** ilişki alanı zinciri | `<ordersId>.<orders.customer>` |
128
+ | iç içe `joins[].joins[].path` | zincir uzar | `<ordersId>.<orders.customer>.<customers.city>` |
129
+ | join'li `selectFields[].path` | **kök entity id'si OLMADAN** aynı zincir | `<orders.customer>` |
130
+ | kök tablo `selectFields[].path` | alan hiç yazılmaz (ya da boş) | — |
131
+ | kök tablo `filters[].path` | alan hiç yazılmaz | — |
132
+
133
+ Bu kural **MS-007 (block)** olarak denetlenir. `path: ""` verilen join'ler **runtime'da çalışır** (SQL alias üzerinden kurulur, `ms_source_entity_joins`
134
+ satırları da oluşur) ama Studio formu seçili alanları `path` eşleşmesiyle bulduğu için
135
+ (`MicroserviceFormTableRelations.svelte`: `item.path === getPath(relationFieldId)`) hiçbir join
136
+ alanı seçili görünmez. Sessiz bozulma — execute testi bunu yakalamaz, ancak Studio'da bakınca
137
+ anlaşılır.
138
+
139
+ ```jsonc
140
+ "sourceParameters": {
141
+ "operation": "get", "entityId": "<orders id>", "alias": "orders", "isSingle": false,
142
+ "selectFields": [
143
+ { "alias": "orders", "fieldId": "<code id>", "fieldSlug": "code" },
144
+ { "alias": "customers", "fieldId": "<name id>", "fieldSlug": "name", "path": "<orders.customer ilişki alanı id>" }
145
+ ],
146
+ "joins": [
147
+ { "alias": "customers", "entityId": "<customers id>", "fieldId": "<orders.customer ilişki alanı id>",
148
+ "path": "<orders entity id>.<orders.customer ilişki alanı id>", "joins": [] }
149
+ ],
150
+ "sort": { "name": "sortField", "type": "sortType", "source": "query-string" },
151
+ "limit": { "name": "limit", "offset": "offset", "source": "query-string" }
152
+ }
153
+ ```
154
+
155
+ > **Gateway bunları senin yerine dolduruyor.** `path`, `sort` ve `limit` eksikse gateway
156
+ > create/update/patch/preview yollarında hesaplayıp ekler ve yanıtta `gatewayNormalized` ile
157
+ > bildirir. Elle yazarsan dokunmaz — o yüzden **yanlış** yazılmış bir path yine MS-007 block'una
158
+ > takılır. Kuralı bilmen yine de gerekir: geri okuduğun kayıtta ne göreceğini bu belirler.
159
+
160
+ ### Filtre zorunlu mu, opsiyonel mi (`isRequired`)
161
+
162
+ `isRequired: true` verilen filtre **istekte parametre yoksa çalışmayı engeller**. Varsayılan
163
+ olarak zorunlu yazmak, tek uçla hem "hepsini listele" hem "filtreleyerek listele" senaryosunu
164
+ karşılamayı imkânsız kılar. Karar kuralı:
165
+
166
+ | Uç | `isRequired` | Neden |
167
+ |---|:-:|---|
168
+ | Liste uçları (`isSingle: false`) | `false` | Parametre gelirse süzer, gelmezse tam liste döner |
169
+ | Tekil kayıt (`isSingle: true`) | `true` | Anahtar olmadan "tek kayıt" anlamsız |
170
+ | `update` / `delete` | `true` | **Güvenlik**: filtresiz update tüm tabloyu yazar |
171
+
172
+ ```jsonc
173
+ { "alias": "announcements", "fieldId": "<id alanı>", "operator": "eq",
174
+ "source": "query-string", "filterType": "single", "conditionType": "or",
175
+ "isRequired": false, "children": [], "order": 0 }
176
+ ```
177
+
178
+ ### Dinamik filtre (`enableDynamicFilters`) — süzülebilir tablonun uç şekli
179
+
180
+ `enableDynamicFilters: true` verilen uç, süzme koşullarını **tanımdan değil çağrıdan** alır.
181
+ Validator dört şey dayatır ([CreateMicroserviceCommandValidator:76-86](../../../../zeyrek-backend/src/NgApps.Application/Contexts/Studio/Microservices/CreateMicroserviceCommand.cs)):
182
+ `source: 'table'` · `operation: 'get'` · `isSingle: false` · **tüm single filtrelerin
183
+ `source: 'expression'` olması** (aksi hâlde `PrefilterMustBeExpression`). Yani statik
184
+ `query-string` filtresiyle birlikte kullanılamaz; `expression` filtreleri burada süzme değil
185
+ **sabit prefilter**dir (tenant/aktiflik gibi). Gateway bunu MS-010 ile istek gitmeden yakalar.
186
+
187
+ Çağrıdaki `filters` üç şekilde kabul edilir
188
+ ([DynamicFilter.Parse](../../../../zeyrek-backend/src/NgApps.Domain/Contexts/Studio/Microservices/DynamicFilter.cs)):
189
+
190
+ | Şekil | Örnek |
191
+ |---|---|
192
+ | JSON **string** | `?filters=[{"field":"status","operator":"eq","value":"Bekliyor"}]` |
193
+ | Gövde/nesne | `body.filters = [{ field, operator, value, conditionType, filterType, children }]` |
194
+ | **Köşeli parantez query-string** | `?filters[0][field]=status&filters[0][operator]=eq&filters[0][value]=Bekliyor` |
195
+
196
+ Üçüncü biçimin regex'i `^filters((?:\[[^\[\]]*\])+)$` — anahtarın tamamı `filters` + köşeli
197
+ parantez grupları olmalı ve **ilk segment tam sayı** olmalı. `filters[0].field` gibi noktalı
198
+ karışık biçim **eşleşmez**; iç içe grup için de parantez kullanılır
199
+ (`filters[0][children][0][field]`). Boş köşeli parantez (`filters[]`) otomatik indekslenir.
200
+
201
+ Runtime'da dinamik filtreler statik olanların **yerine geçmez, onlarla birleştirilir**
202
+ (`TableMicroserviceExecutor:166` → `DynamicFilter.Combine`).
203
+
204
+ #### `field` **join'li nokta yolu kabul eder** (`hasta.ad`, `manager.departman.name`)
205
+
206
+ Dinamik filtre alanı düz kolonla sınırlı değildir; `DynamicFilter.ResolveField` yolu
207
+ [EntityPathResolver](../../../../zeyrek-backend/src/NgApps.Domain/Contexts/Datastore/Entities/EntityPathResolver.cs)
208
+ ile çözer. Üç şekil de geçerlidir:
209
+
210
+ | `field` | Anlamı |
211
+ |---|---|
212
+ | `status` | Kök tablonun kolonu |
213
+ | `hasta.ad` | `hasta` join'inin `ad` kolonu |
214
+ | `manager.departman.name` | İç içe join zinciri (`join.Children` üzerinden yürür) |
215
+
216
+ Ara segmentler **ilişki alanının slug'ı ya da join'in alias'ı** ile eşleşir; son segment
217
+ hedef entity'deki alan slug'ıdır. Tek koşul: alan **`selectFields` içinde
218
+ `mode: 'default'` ile seçili olmalı** — değilse `FilterFieldNotInSelectedException`
219
+ ("filtre alanı seçili değil") alırsınız. Yani "hasta adına göre randevu ara" için hasta adını
220
+ hem `joins` ile getirin hem `selectFields`'a koyun; ondan sonra `filters[0][field]=hasta.ad`
221
+ çalışır. m2m alanlar da desteklenir (`EXISTS` alt sorgusuna çevrilir).
222
+
223
+ > **Expression filtresi istek parametresi TAŞIYAMAZ.** Microservice'te expression'lar
224
+ > `workflowContext.JavascriptEngineInstance` ile eval edilir ve o bağlamda `input` global'i
225
+ > **yoktur** — `value: "input.doctor_id"` yazarsan uç `500 JavaScriptException: input is not
226
+ > defined` verir (`input` workflow table step'inin sözlüğüdür, microservice'in değil).
227
+ > Expression'ın okuyabildiği iki şey vardır: **sabit değerler** (`"'Aktif'"`, `new Date()`) ve
228
+ > **middleware değişkenleri** (`user.id`, `settings.x` — bağlı middleware'in set-user/variable
229
+ > step'leri kurar).
230
+ >
231
+ > **Kimlik kapsaması (tenant/hasta/doktor) bu yüzden parametreyle DEĞİL middleware'le yapılır:**
232
+ > uca auth middleware'ini bağla (`definition.middlewareIds`), owner filtresini
233
+ > `source: 'expression'`, `value: 'user.id'` yap — dinamik filtreyle birlikte sorunsuz çalışır
234
+ > (qm kanıtı: `sy-users-get-my-profile`). Ayrıntı ve tarifler: `platform_guide('middleware')`.
235
+ > Middleware bağlamadan `user.` okuyan expression yazarsan MS-011 uyarır — runtime'da
236
+ > `user is not defined` 500'ü yersin.
237
+
238
+ > Sayfa tarafındaki karşılığı: kullanıcı ekrandan süzüyorsa (`table` + `filterMode` açık, ya da
239
+ > `datatable` + `isFilterable` kolonlar) uç `enableDynamicFilters: true` olmalı; komponent seçilen
240
+ > koşulları kendi `filters` prop'una yazar ve akış onu uca `filters` parametresi olarak geçirir.
241
+ > Süzme yoksa statik query-string filtreleri yeterlidir — bkz. `platform_guide('page')` Kural 1e.
242
+
243
+ ### `sort` ve `limit` her zaman verilir
244
+
245
+ **MS-008 (block).** Platform bunları null bırakır; Studio formu ise `data.sort.name` /
246
+ `data.sort.sortValue` (ve `data.limit.name` / `data.limit.limitValue`) diye guard'sız okur —
247
+ aynı bileşende 8 ayrı dereference var — ve **sayfayı komple patlatır** (workflow table step'inde
248
+ `TypeError: Cannot read properties of null (reading 'sortValue')`). Yukarıdaki iki varsayılanı
249
+ her table kaynaklı microservice'e ve her workflow table step'ine koy.
250
+
251
+ Gateway bu kuralları MS-005/MS-006 olarak istek gitmeden denetler; platform validator'ı da artık alias/fieldId eksiklerini kayıt anında anlaşılır 400 ile reddeder (TableSourceJoinIncomplete / TableSourceSelectFieldIncomplete). Kurallara uyduğun hâlde join
252
+ hatası alırsan `microservice_debug` çıktısıyla raporla — o zaman gerçek bir bug'dır.
253
+ Query source'a kaçış yalnız ham SQL şartsa; query yazarken parser `SELECT` sonrası satır sonu
254
+ kabul etmez — ilk kolon `SELECT` ile aynı satırda olmalı ("Please use a supported query type").
255
+
256
+ ## Middleware'siz uçta çok adımlı yazım ATOMİK DEĞİL (ölçüm 2026-08-19)
257
+
258
+ `middlewareIds` boş bir uçta, akış non-2xx bitse (`response 422`) ya da bir adım istisna atsa
259
+ (500) bile **önceden yazılan satırlar commit edilir.** Aynı akış herhangi bir middleware'in
260
+ arkasına alındığında her iki durumda da düzgün rollback olur.
261
+
262
+ | Uç | `response 422` | adım içi istisna (500) |
263
+ |---|:-:|:-:|
264
+ | `middlewareIds: []` | satır **kalıyor** ✕ | satır **kalıyor** ✕ |
265
+ | middleware bağlı | geri alınıyor ✅ | geri alınıyor ✅ |
266
+
267
+ Sebep: `ExecuteMicroserviceBehavior.HandleMiddlewaresAsync` `middlewares.Count is 0` iken
268
+ `BeginTransactionAsync("execute-microservice")`'e **hiç ulaşmadan** döner. O zaman transaction
269
+ anahtarını içteki `TransactionPipelineBehaviorBase` alır ve iç handler döner dönmez commit eder;
270
+ dış davranışın `IsSuccessStatusCode()` kontrolü ve `RollbackTransactionAsync()` çağrısı kapanmış
271
+ bir transaction üzerinde koşup `catch { }` içinde yutulur.
272
+
273
+ **Kural:** çok adımlı yazan (response + N satır) bir uç kuruyorsan, **kimlik doğrulaman
274
+ gerekmese bile** ona bir middleware bağla. Tek `variable` adımlı, hiçbir şeyi reddetmeyen bir
275
+ "geçirgen" middleware yeterlidir ve **anonim erişimi bozmaz** (tokensız POST → 200 + satır,
276
+ ölçüldü). Aynı desen sayfa yolunda da vardır (`ExecutePageBehavior`).
277
+
278
+ Zayıf alternatif: bütün doğrulamaları ilk yazmadan **önce** bitirip yazma bölümünde hiç
279
+ `response`/hata üretmemek — beklenmedik istisnaya karşı korumasızdır.
280
+
281
+ ## Uçlar varsayılan olarak ANONİMDİR
282
+
283
+ Çalıştırma rotası `[AllowAnonymous]`'tur (`MicroservicesController.cs:90`): `middlewareIds` boş
284
+ bir uç, `Authorization` header'ı hiç olmayan bir isteği kabul eder (ölçüldü: 200 + satır).
285
+ Anonim veri toplama (anket, başvuru, geri bildirim) bu sayede kurulabilir — ama tersi de doğru:
286
+ **koruma yalnız `middlewareIds` ile gelir.**
287
+
288
+ `roles` bir kapı değildir: `roleIds` dolu + `middlewareIds` boş bir uç tokensız POST'u 200 ile
289
+ kabul etti. Ayrıntı ve doğru rol kapısı tarifi: `platform_guide('middleware')` Tarif 3b.
290
+
291
+ > **Anonimlik/erişim testi `microservice_execute` + `anonymous: true` ile yapılır.** Bayrak
292
+ > verildiğinde gateway isteğe HİÇBİR `Authorization` header'ı koymaz (varsayılan davranışta
293
+ > kendi token'ını koyar ve test anlamsızlaşır): korumalı uçtan 401, anonim uçtan 200 beklenir.
294
+ > Ham `curl` tercih edersen yolu `microservice_get` yanıtındaki **`resolvedUrl`**'den al —
295
+ > rota `/{project}/m/{application}/{microservice SLUG'ı}`'dır ( `url` alanı rotaya GİRMEZ) ve
296
+ > `Host: <org>.<suffix>` + `X-Environment` başlıkları zorunludur.
297
+
298
+ ## Sık hatalar
299
+
300
+ - `selectFields: []` + join → tüm alanlar + katlanmış maliyet (MS-001, block)
301
+ - `operation: 'list'` → geçersiz; `get` + `isSingle: false` kullanın (PLT-006)
302
+ - Üst seviye `entityId` eksik (MS-004, warn)
303
+ - Gerekçesiz `source: 'query'` (MS-002, warn)
304
+ - Execute'ta dönen `httpStatus` microservice'in kendi sonucudur — 4xx/5xx görünce önce
305
+ `microservice_debug` ile hangi adımın ürettiğine bak, tanımı ona göre düzelt.
306
+ - **`microservice_update` / `microservice_patch`, `operation: 'get'` tipli microservice'lerde
307
+ HER ZAMAN 500 verir** (`The instance of entity type 'Application' cannot be tracked` —
308
+ platform regresyonu, P-02 olarak düzeltilecek). Ayrım **operasyona** bağlıdır, tool'a değil:
309
+ `operation: 'create' | 'update' | 'delete' | 'upsert'` kayıtlarında PUT/PATCH sorunsuz çalışır
310
+ (crm koşumunda `update` uçlarında yeniden doğrulandı, 2026-08-25); yalnız `get` (liste ya da
311
+ `isSingle`) uçlarında patlar. Get-tipi bir microservice'i değiştirmen gerekiyorsa **`microservice_delete` +
312
+ `microservice_create`** kullan ve tanımı ilk POST'ta doğru üret; silme kümesini kendi
313
+ ürettiğin slug listesiyle sınırla. `entity_patch`, `workflow_patch`, `row_update` sağlamdır.
@@ -0,0 +1,271 @@
1
+ # Middleware Rehberi
2
+
3
+ Middleware bir **workflow tipidir** (`type: 'middleware'`): seçildiği microservice ya da sayfa
4
+ çalışmadan **önce** koşar. İki iş için kullanılır: (1) isteği **doğrulamak** ve geçmeyeni
5
+ kestirmeden reddetmek (auth guard), (2) isteğin devamında kullanılacak **değişkenleri kurmak**
6
+ (`user`, `settings`, `permissions`…). Genel amaçlıdır — bir kez yazılır, `middlewareIds` ile
7
+ istenen her uca/sayfaya bağlanır. Buradaki kurallar backend kodundan ve qm projesinin canlı
8
+ kayıtlarından doğrulandı.
9
+
10
+ **Önce "gerekli mi?" sorusu.** Middleware kurulum listesinin zorunlu maddesi değildir: oturum
11
+ kavramı olmayan bir üründe auth middleware'i, ayar tablosu olmayan bir üründe setter gereksizdir
12
+ (karar tablosu: `platform_guide('getting-started')`). Buna karşılık **kimlik kurduysan eksiksiz
13
+ kur** — giriş sayfası + korunacak her uç ve her sayfa `middlewareIds`'e bağlı. Kısmi koruma,
14
+ sistemi güvenli gösterip açık bırakır.
15
+
16
+ ## Çalışma modeli — bunları bilmeden middleware yazma
17
+
18
+ 1. **Sıra ada bağlıdır.** Bağlı middleware'ler **ada göre alfabetik** koşar — uç yolunda
19
+ `ExecuteMicroserviceBehavior.cs:96`, sayfa yolunda `ExecutePageBehavior.cs:121`, ikisi de
20
+ `middlewares.OrderBy(m => m.Name)`. `middlewareIds` dizisinin sırası **hiç okunmaz** (kayıt
21
+ onu gönderdiğin sırada saklamaz bile). "Önce settings, sonra auth" gibi bir sıra istiyorsan
22
+ adlandırmayla kur — qm'deki `[SY] Settings Setter` adının `[` öneki bilinçli bir sıralama
23
+ hilesidir.
24
+ 2. **Non-2xx yanıt = kesme.** Bir middleware'in `response` step'i 2xx dışı dönerse hedef
25
+ microservice/sayfa **hiç çalışmaz**; middleware'in yanıtı (status + body + header +
26
+ uiActions) istemciye döner. Guard davranışı budur. Response'suz biten middleware sorunsuz
27
+ geçer (setter deseni).
28
+ **`redirect` bu kurala DAHİL DEĞİLDİR** — durum kodunu değiştirmez, akış 2xx biter ve hedef
29
+ normal çalışır. Ayrıntı ve doğru tarif: Tarif 1b (ölçüm 2026-08-19).
30
+ 3. **Değişkenler istek boyunca yaşar.** `set-user` / `variable` step'lerinin kurduğu
31
+ değişkenler istek kapsamındaki `WorkflowContext`'e yazılır (`AddVariable`) ve her akış
32
+ koşusunun JS motoru o anki değişkenlerden kurulur (`WorkflowRunner` →
33
+ `JavascriptEngineInstance`). Hedef microservice'in **expression filtrelerinde/alanlarında**
34
+ ve step ifadelerinde doğrudan okunur (`user.id`, `settings.auth_method`). Ayrıca microservice
35
+ yolunda middleware'in response gövdesindeki `user` / `data` anahtarları da değişkene
36
+ terfi eder (`ExecuteMicroserviceBehavior.cs:105` — sayfa yolunda bu terfi YOKTUR).
37
+
38
+ **Ama başka bir middleware'in kurduğuna GÜVENME.** Değişken teknik olarak sonraki
39
+ middleware'e geçer; pratikte kuran middleware `set-user`'a **ulaşmadan** bitebilir (çerez
40
+ yok, koşul else dalı, erken `response`) ve o zaman `user` hiç tanımlanmaz: okuyan middleware
41
+ `user is not defined` ile **500** verir — kullanıcı 401 yerine 500 görür, log da sebebi
42
+ söylemez (crm koşumu 2026-08-25). Kural: **rol/yetki kapısını ayrı bir middleware'e
43
+ ayırma**, kimlik middleware'inin içine `set-user`'dan sonra koy (Tarif 3b). Zinciri yine de
44
+ kuracaksan ifadeyi guard'la: `typeof user === 'undefined' ? false : Authorized(user.roles)`.
45
+ Gateway `set-user` adımı olmadan `user` okuyan middleware'i **MW-003** ile uyarır.
46
+ 4. **`input` YOKTUR.** Middleware girdisini HTTP bağlamından okur: `cookie`, `header`,
47
+ `queryString`, `body`, `triggerSource` (hangi microservice/sayfa tetikledi: type, name,
48
+ slug, roles, impersonatedUserId). Hazır fonksiyonlar: `system.validateToken(...)`,
49
+ `system.generateToken(...)`, `crypto.JWT.encode/validate`, `Authorized(user.roles)`,
50
+ `request.url.searchParams`, `GenerateGuid()`, `UtcNow()`, `system.getPageUrl(slug)` —
51
+ **tam sözlük ve gerçek örnekler: `platform_guide('expressions')`**.
52
+ 5. `returnDtoId` middleware'de **zorunlu değildir** ve `state: 'unready'` normaldir —
53
+ koşmayı etkilemez.
54
+ 6. Middleware transaction içinde koşar: hedef başarısız olursa middleware'in yazdıkları da
55
+ geri alınır (microservice yolu).
56
+
57
+ ## Yalnız middleware'de geçerli step'ler
58
+
59
+ | Step | Ne yapar | Kritik alanlar |
60
+ |---|---|---|
61
+ | `set-user` | Oturum kullanıcısını kurar; değişken adı **sabit `user`** | `rule.expression` (JS — değeri üretir), opsiyonel `rule.dataTransferObjectId` (şekli tanımlar) |
62
+ | `variable` (Set Variable) | Keyfî adlı değişken kurar | **`rule.name` = değişken adı**, `rule.expression`, opsiyonel `rule.dataTransferObjectId` |
63
+ | `setcookie` | Yanıta cookie yazar | token tazeleme |
64
+ | `redirect` | Tarayıcıyı yönlendirir (`uiActions`) | **`rule.expression` bir JS ifadesidir ve DEĞERİ yoldur** — aşağıdaki Tarif 1b |
65
+
66
+ `response` workflow+middleware'de; diğer adımlar (start, condition, code, loop, table, query,
67
+ http, workflow, email, file-upload) üç tipte de geçerlidir. `rule.dataTransferObjectId`
68
+ verilirse değişkenin şeması `workflow_middleware_variables` çıktısında görünür — Studio'nun
69
+ expression editörü ve senin keşif yolun budur; **değişken adını/alanını tahmin etme, oradan oku.**
70
+
71
+ ## Tarif 1 — Auth guard (qm `Authenticate`'ten damıtıldı)
72
+
73
+ **Önce dönüş şeklini bil:** `system.validateToken(token)` bir `JwtValidationResult` döner —
74
+ `{ isValid: bool, claims: { … } }`. **Claim'ler bir seviye içeridedir** ve anahtarları,
75
+ token'ı üretirken `system.generateToken({ … })`'a verdiğin sözlüğün anahtarlarıdır
76
+ (`claims.userId`, `claims.email` …) — `token.data.id` diye evrensel bir alan YOKTUR.
77
+
78
+ ```
79
+ start → token [code: return system.validateToken(cookie.<TokenCookie>)]
80
+ token → valid? [condition: token.data.isValid && token.data.claims.<idAnahtarın>]
81
+ ├─ else → response 401 (ya da redirect → login sayfası)
82
+ └─ ok → userTable [table get: users, filtre source:'expression'
83
+ value:'token.data.claims.<idAnahtarın>']
84
+ → var mı? [condition] ─ else → response 401
85
+ → roles [table get: user_roles — kullanıcının rol BAĞLARI]
86
+ → allRoles [table get: roles — id + slug]
87
+ → setUser [set-user: aşağıdaki eşleştirme ifadesi]
88
+ ```
89
+
90
+ ### ⚠️ `roles.data[].role_id` bir NESNE DEĞİL, düz FK'dır
91
+
92
+ `row_list` bir ilişki alanını her zaman `{ id, <displayAlanı> }` nesnesi döndürür (her ilişki
93
+ için join'i kendisi üretir). **Table adımı üretmez:** yalnız `joins` içinde bildirdiğin ilişki
94
+ nesneye çevrilir (`PostgreSqlRelationHandler.ModifyResponse` → `ProcessJoins`), bildirmediğin
95
+ alan **düz FK string'i** olarak gelir. Yani `p.role_id.slug` `undefined`'dır, `.filter(Boolean)`
96
+ onu eler ve `user.roles` **boş dizi** olur → `Authorized(user.roles)` her zaman `false`, rolü
97
+ olan kullanıcı bile 403 alır. Hata yoktur, log yoktur — sessiz yetki kaybı (crm koşumu B-1).
98
+ Gateway bu okumayı **WF-020** ile uyarır.
99
+
100
+ İki doğru çözüm var:
101
+
102
+ 1. **Adıma join ekle** (`joins: [{ alias: 'roles', entityId: '<roles>', fieldId: '<user_roles.role_id>', path: '<user_roles entityId>.<user_roles.role_id>' }]`)
103
+ ve `selectFields`'a join'li alanı koy. Bu yolda kök `alias` ZORUNLUDUR (bkz. workflow rehberi
104
+ — alias'sız join Postgres'te `42601` verir).
105
+ 2. **Hedef tabloyu ayrı bir adımla oku ve id üzerinden eşleştir** (join derdi olmayan, her iki
106
+ şekle de dayanıklı yol):
107
+
108
+ ```js
109
+ (function () {
110
+ var link = roles.data || [];
111
+ var all = allRoles.data || [];
112
+ var slugs = link.map(function (ur) {
113
+ var rid = ur.role_id && ur.role_id.id ? ur.role_id.id : ur.role_id; // iki şekli de karşılar
114
+ var m = all.filter(function (x) { return x.id === rid; })[0];
115
+ return m && m.slug;
116
+ }).filter(function (s) { return !!s; });
117
+ return Object.assign({}, userTable.data, { roles: slugs, roles_csv: ',' + slugs.join(',') + ',' });
118
+ })()
119
+ ```
120
+
121
+ `roles_csv`'yi de kurmak sayfa akışlarını kolaylaştırır: `Page.user.roles` dizisi set-user
122
+ DTO'sundan düşebilir, CSV metin alanı düşmez (bkz. FLOW-016).
123
+
124
+ **Rol karşılaştırması SLUG üzerindendir:** `microservice.roles` rolü `{ id, name: '<slug>' }`
125
+ olarak taşır (rolün gerçek adı "Yönetici" olsa bile), `Authorized` bunlarla karşılaştırır —
126
+ `user.roles` da slug taşımalıdır.
127
+
128
+ Tam gövde iskeleti: `ngapps://examples/middleware-authenticate`. Bağlamak için:
129
+ microservice'te `definition.middlewareIds: ["<middleware workflow id>"]`
130
+ (create/update gövdesinde), sayfada `page_create/page_update` `middlewareIds` parametresi.
131
+
132
+ ## Tarif 1b — Girişe yönlendirme (`redirect` step'inin tam şekli)
133
+
134
+ Aynı middleware hem sayfaya hem uca bağlıysa, **başarısızlık cevabı ikisinde farklı olmalı**:
135
+ tarayıcıdaki kullanıcı giriş sayfasına gitmeli, uç çağrısı 401 almalı. Ayrımı
136
+ `triggerSource.type` yapar.
137
+
138
+ `redirect` adımı `rule.expression`'ı **JavaScript olarak değerlendirir ve sonucu yol (path)
139
+ olur** (`RedirectStep.cs`) — sabit bir yol yazacaksan bile **tırnak içinde** yazmalısın, çünkü
140
+ tırnaksız `giris` runtime'da `giris is not defined` verir. Yol daha sonra
141
+ `{scheme}://{host}/view/{proje}/{application}/{path}` mutlak adresine çevrilir
142
+ (`ServeFileBehavior.cs`), yani başına `/` koyma.
143
+
144
+ ```jsonc
145
+ // condition: triggerSource.type === 'page' → toLogin ; else → response 401
146
+ { "id": "<GUID>", "name": "toLogin", "type": "redirect",
147
+ "rule": { "stepType": "redirect", "expression": "'giris'", "isPostBack": false },
148
+ "branches": [{ "id": "<GUID>", "name": "success", "type": "success", "nextStepId": null }] }
149
+ ```
150
+
151
+ ### ⚠️ `redirect` TEK BAŞINA kapı tutmaz — arkasına `response` bağla
152
+
153
+ **Ölçüm (2026-08-19):** yalnız `redirect` ile biten bir middleware'e bağlı korunan sayfaya
154
+ **tokensız** `GET {app}/p/{slug}` isteği atıldı → **HTTP 200 ve şemanın tamamı döndü**, yanıtta
155
+ `uiActions` bile yoktu. Kontrol ölçümü (koşulsuz `response 401` dönen middleware) aynı sayfada
156
+ 401 verdi ve şema sızmadı — yani sayfa middleware'leri koşuyor, sorun `redirect` adımında.
157
+
158
+ Sebep zincirin iki ucunda:
159
+
160
+ - `RedirectStep.cs` yalnız bir `UiAction` kaydedip **success dalından devam ediyor**; durum
161
+ kodunu değiştirmiyor. `response` adımı gelmezse akış 2xx biter.
162
+ - `ExecutePageBehavior` yalnız `!result.IsSuccessStatusCode()` iken kesiyor, dolayısıyla sayfa
163
+ normal servis ediliyor.
164
+ - `PagesController.HandleResult` `uiActions`'ı **yalnız** `!IsSuccessStatusCode() &&
165
+ UiActions.Any()` dalında yayınlıyor — middleware geçtiğinde yönlendirme de kayboluyor.
166
+ Yani response'suz `redirect` ne kapıyı tutar ne tarayıcıyı yönlendirir: tamamen ölüdür.
167
+
168
+ **Doğru şekil** (ölçülerek doğrulandı — 401 döndü, şema sızmadı, `uiActions` yönlendirmeyi
169
+ taşıdı):
170
+
171
+ ```
172
+ guard → [condition] triggerSource.type === 'page'
173
+ ├─ if → [redirect] "'giris'" → [response] 401 ← kapıyı BU response tutuyor
174
+ └─ else → [response] 401
175
+ ```
176
+
177
+ `redirect` adımının `branches[].nextStepId`'si `null` değil, **`response` adımını** göstermeli.
178
+
179
+ - İfade olduğu için yol dinamik yazılabilir: `"'giris?next=' + encodeURIComponent(request.url.pathname)"`.
180
+ - `page.roleIds` erişim kapatmaz; **`microservice.roles` de kapatmaz** (aşağıdaki Tarif 3b).
181
+ Korumalı her sayfada yukarıdaki `redirect → response` zinciri kurulmalı ve **tokensız bir
182
+ istekle 401 verdiği görülerek** kabul edilmeli. Karşılığı: `platform_guide('page')` Kural 8.
183
+
184
+ ## Tarif 3b — Rol kapısı: `microservice.roles` TEK BAŞINA hiçbir şeyi kapatmaz
185
+
186
+ **Ölçüm (2026-08-19):** `roleIds: ['<admin rol id>']` verilmiş ama `middlewareIds` **boş** bir
187
+ uca **tokensız** POST atıldı → **200**, satır yazıldı.
188
+
189
+ Rol karşılaştırması yürütme hattında yoktur. Tek mekanizma, middleware ifadelerinde
190
+ çağrılabilen **`Authorized(userRoles)`** yardımcısıdır (`WorkflowContext.cs:411`) ve
191
+ karşılaştırdığı `triggerSource.roles`, `ExecuteMicroserviceBehavior`'ın middleware'e taşıdığı
192
+ `microservice.roles` listesidir. Yani `roles` bir kapı değil, **middleware'e verilen veridir**;
193
+ middleware `Authorized` çağırmazsa tamamen dekoratiftir.
194
+
195
+ İki tuzak:
196
+
197
+ - `Authorized`, kaynağın rolleri **boşsa `true` döner** — rolsüz bir uç, kimliği doğrulanmış
198
+ herkese açıktır.
199
+ - `roles` dolu + `middlewareIds` boş olan uç, Studio'da "rol kapılı" görünür ama **tamamen
200
+ açıktır**.
201
+
202
+ ```
203
+ guard → … → setUser → [condition] Authorized(user.roles)
204
+ ├─ else → [response] 403
205
+ └─ ok → (akış biter, hedef uç çalışır)
206
+ ```
207
+
208
+ Doğrulama: korumalı ucu **kimliksiz** çağır — `microservice_execute` (ya da `microservice_debug`)
209
+ **`anonymous: true`** ile hiçbir `Authorization` header'ı göndermez; korumalı uçtan 401, anonim
210
+ uçtan 200 beklenir. Sayfa tarafının karşılığı `page_runtime_get` + `anonymous: true`
211
+ (`middlewareBlocked` dönmeli ve şema sızmamalı). Bayrağı vermezsen gateway her çağrıya kendi
212
+ token'ını koyar ve test anlamsızlaşır.
213
+
214
+ ## Tarif 2 — Setter (qm `[SY] Settings Setter`: 3 step)
215
+
216
+ **Yalnız ayarların satır olarak tutulduğu bir tablo varsa ve uçlar gerçekten bu değerleri okuyorsa
217
+ kurulur.** Her projenin ayar middleware'ine ihtiyacı yoktur; okunmayan bir `settings` değişkeni
218
+ sadece fazladan parça demektir. Karar tablosu: `platform_guide('getting-started')`.
219
+
220
+ ```
221
+ start → settings [table get: ayarlar tablosu] → setVar [variable name:'settings',
222
+ expression: '{ ...settings.data }', dataTransferObjectId: <ayarlar DTO>]
223
+ ```
224
+
225
+ Response yok — tek işi değişken kurmak; bağlandığı her uçta `settings.<alan>` okunur.
226
+
227
+ ## Tarif 3 — Kimlik kapsaması: "sadece benim kayıtlarım" ucu
228
+
229
+ Kullanıcıya özel veri dönen uçlarda kimliği **istemci parametresinden alma** (sahtelenir);
230
+ middleware'in kurduğu `user`'dan oku:
231
+
232
+ 1. Uca auth middleware'ini bağla (`middlewareIds`).
233
+ 2. Filtreyi expression yap: `{ fieldId: <owner alanı>, operator: 'eq',
234
+ source: 'expression', value: 'user.id', filterType: 'single', isRequired: false }`.
235
+
236
+ qm kanıtı: `sy-users-get-my-profile` (`id eq user.id`), `qm-document-revision-requests-get`
237
+ (`requested_by eq user.id`). **Bu desen `enableDynamicFilters: true` ile birlikte çalışır** —
238
+ "dinamik filtre + zorunlu kimlik parametresi olmaz" kısıtının çözümü budur: kimlik parametre
239
+ değil, middleware değişkenidir. (Sayfalardaki sabit `patient_id` benzeri geçici çözümlerin
240
+ kalıcı hâli de bu.)
241
+
242
+ ## Tuzaklar
243
+
244
+ - Expression'daki değişken adı, bağlı middleware'lerden birinin kurduğu adla **birebir**
245
+ eşleşmeli — eşleşmezse runtime'da `user is not defined` 500'ü. Gateway MS-011 uyarır:
246
+ `user.` okuyan expression varken `middlewareIds` boşsa warn alırsın.
247
+ - `set-user` adı sabittir; ikinci kullanıcı değişkeni gerekiyorsa `variable` step'i kullan.
248
+ - Middleware'i `workflow_preview` ile test etmek sınırlıdır (HTTP bağlamı preview'da yoktur);
249
+ gerçek doğrulama, middleware'i bağladığın ucu `microservice_execute` ile çağırmaktır —
250
+ korumalı uçta tokensız çağrı guard yanıtını (401/redirect) döndürmeli.
251
+ - Middleware yazdıktan sonra `workflow_middleware_variables` ile değişkenin (ve şemasının)
252
+ gerçekten listelendiğini doğrula.
253
+ - Middleware'li ucu **üye token'ıyla** test etmek için `microservice_execute`'a
254
+ `headers: { Authorization: 'Bearer <token>' }` ver — gateway o çağrıda kendi admin
255
+ token'ını KOYMAZ, istek tamamen verdiğin kimlikle gider. Tarayıcı yolunu test etmek için
256
+ token'ı cookie header'ıyla da geçebilirsin (`headers: { Cookie: '<ad>=<token>' }`).
257
+
258
+ ## Korumalı bir ucu TEST ederken: token ÇEREZDEN okunur
259
+
260
+ `microservice_execute` ile middleware'li bir ucu denerken `Authorization: Bearer <token>` vermek
261
+ **yetmez** — bu tarifle kurulan auth middleware'i token'ı **çerezten** okur (`cookie.<AD>_Token`).
262
+ Ölçüm (anket projesi, 2026-08-19, `anket-kaydet` ucu):
263
+
264
+ ```
265
+ headers: { "Authorization": "Bearer <token>" } → 401 unauthorized
266
+ headers: { "Cookie": "ANKET_Token=<token>" } → 200
267
+ ```
268
+
269
+ Token'ı önce giriş ucundan al (`microservice_execute` ile `giris`), sonra `Cookie` başlığıyla
270
+ korumalı ucu çağır. Çerez adı middleware'in `cookie.<ad>` ifadesindedir; `workflow_get` ile oku,
271
+ tahmin etme. **401 alman middleware'in çalıştığının kanıtıdır** — uç bozuk değildir.
@@ -0,0 +1,35 @@
1
+ # Var Olan Projede Güvenli Değişiklik
2
+
3
+ Gerçek projelerde tek tanım 20-100 bin satır JSON olabilir. **Tam gövdeyi bağlama çekme.**
4
+
5
+ ## Akış
6
+
7
+ ```
8
+ 1. project_overview → neyi değiştireceğini ve id'sini bul
9
+ 2. *_get view:'outline' → kaydın iskeleti (step grafı / alan listesi)
10
+ 3. *_get view:'part', part:'steps[id=...]' → yalnız değişecek parçanın tam hâli
11
+ 4. *_patch operations:[...] → RFC 6902 işlemleri; gateway tam gövdeyi kendi
12
+ belleğinde çeker, patch'i uygular, politika
13
+ kontrolünden geçirir, geri yazar
14
+ 5. *_get view:'outline' → değişikliği teyit et
15
+ ```
16
+
17
+ `view:'full'` **son çaredir** — yalnız küçük kayıtlarda ya da patch'in kurulamadığı köklü
18
+ yeniden yazımlarda.
19
+
20
+ ## Patch yazım notları
21
+
22
+ - `path` JSON Pointer'dır: `/steps/2/rule/limit`, `/sourceParameters/selectFields/-` (diziye
23
+ ekleme), `/fields/5/nullable`.
24
+ - Dizin indekslerini tahmin etme — `view:'outline'` çıktısındaki sıradan ya da
25
+ `view:'part'` ile doğrulayarak yaz.
26
+ - Silme yerine `replace` tercih et; `remove` kullanacaksan önce o path'i `part` ile görüntüle.
27
+ - Yanıttaki outline özeti değişikliğin uygulandığı hâli gösterir — teyit için oku.
28
+
29
+ ## Uyum kuralları
30
+
31
+ - Yeni modül eklerken önce `project_overview` ile mevcut adlandırma/ilişki düzenine bak;
32
+ projenin dilinden kopuk slug/ad üretme.
33
+ - Silmeden önce `entity_dependencies` / `workflow_dependencies`.
34
+ - Studio'da aynı anda düzenleme yapılıyor olabilir: patch, get ile put arasındaki hâlin üstüne
35
+ yazar. Kritik kayıtlarda değişiklik öncesi kullanıcıya haber ver.