@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.
- package/.env.example +46 -0
- package/README.md +260 -0
- package/dist/config.js +128 -0
- package/dist/index.js +88 -0
- package/dist/knowledge/componentCatalog.js +438 -0
- package/dist/knowledge/componentEvents.js +164 -0
- package/dist/knowledge/coverage.js +60 -0
- package/dist/knowledge/normalize.js +1309 -0
- package/dist/knowledge/outlines.js +167 -0
- package/dist/knowledge/policy.js +44 -0
- package/dist/knowledge/reservedTables.js +75 -0
- package/dist/knowledge/rules.js +1725 -0
- package/dist/observability/trace.js +337 -0
- package/dist/platform/errors.js +132 -0
- package/dist/platform/http.js +169 -0
- package/dist/platform/responses.js +94 -0
- package/dist/platform/token.js +79 -0
- package/dist/resources/examples/entity-pair-m2m.json +105 -0
- package/dist/resources/examples/flow-list-to-form.json +65 -0
- package/dist/resources/examples/flow-load-table.json +178 -0
- package/dist/resources/examples/microservice-table-list.json +49 -0
- package/dist/resources/examples/middleware-authenticate.json +194 -0
- package/dist/resources/examples/page-detail-profile.json +95 -0
- package/dist/resources/examples/page-dynamic-form.json +221 -0
- package/dist/resources/examples/page-form-upsert.json +279 -0
- package/dist/resources/examples/page-html-embed.json +106 -0
- package/dist/resources/examples/page-sign-in.json +194 -0
- package/dist/resources/examples/workflow-basic.json +94 -0
- package/dist/resources/guides/component.md +108 -0
- package/dist/resources/guides/entity.md +348 -0
- package/dist/resources/guides/expressions.md +139 -0
- package/dist/resources/guides/getting-started.md +109 -0
- package/dist/resources/guides/html.md +106 -0
- package/dist/resources/guides/microservice.md +313 -0
- package/dist/resources/guides/middleware.md +271 -0
- package/dist/resources/guides/modify-existing.md +35 -0
- package/dist/resources/guides/page.md +1574 -0
- package/dist/resources/guides/view.md +89 -0
- package/dist/resources/guides/workflow.md +359 -0
- package/dist/resources/knowledge.js +44 -0
- package/dist/resources/schemas/CreateDatasourceCommand.json +54 -0
- package/dist/resources/schemas/CreateDiscoveredEntitiesCommand.json +133 -0
- package/dist/resources/schemas/CreateEntityCommand.json +387 -0
- package/dist/resources/schemas/CreateMicroserviceCommand.json +528 -0
- package/dist/resources/schemas/CreateQueryCommand.json +43 -0
- package/dist/resources/schemas/CreateViewCommand.json +158 -0
- package/dist/resources/schemas/CreateWorkflowCommand.json +1719 -0
- package/dist/resources/schemas/Filter.json +124 -0
- package/dist/resources/schemas/GetRowsQuery.json +122 -0
- package/dist/resources/schemas/PreviewMicroserviceCommand.json +565 -0
- package/dist/resources/schemas/PreviewQueryCommand.json +74 -0
- package/dist/resources/schemas/PreviewWorkflowCommand.json +981 -0
- package/dist/resources/schemas/StepDto.json +900 -0
- package/dist/resources/schemas/UpdateDatasourceCommand.json +58 -0
- package/dist/resources/schemas/UpdateEntityCommand.json +679 -0
- package/dist/resources/schemas/UpdateMicroserviceCommand.json +512 -0
- package/dist/resources/schemas/UpdateQueryCommand.json +86 -0
- package/dist/resources/schemas/UpdateViewCommand.json +153 -0
- package/dist/resources/schemas/UpdateWorkflowCommand.json +1719 -0
- package/dist/resources/schemas.js +40 -0
- package/dist/server.js +63 -0
- package/dist/tools/access.js +27 -0
- package/dist/tools/applications.js +59 -0
- package/dist/tools/automations.js +103 -0
- package/dist/tools/common.js +18 -0
- package/dist/tools/components.js +280 -0
- package/dist/tools/datasources.js +82 -0
- package/dist/tools/dependencies.js +17 -0
- package/dist/tools/deployments.js +17 -0
- package/dist/tools/dtos.js +54 -0
- package/dist/tools/entities.js +166 -0
- package/dist/tools/files.js +138 -0
- package/dist/tools/flows.js +226 -0
- package/dist/tools/folders.js +51 -0
- package/dist/tools/layouts.js +105 -0
- package/dist/tools/microservices.js +198 -0
- package/dist/tools/organizations.js +65 -0
- package/dist/tools/pages.js +597 -0
- package/dist/tools/platformInfo.js +44 -0
- package/dist/tools/projects.js +152 -0
- package/dist/tools/queries.js +48 -0
- package/dist/tools/rows.js +115 -0
- package/dist/tools/screens.js +97 -0
- package/dist/tools/templates.js +65 -0
- package/dist/tools/variables.js +94 -0
- package/dist/tools/views.js +58 -0
- package/dist/tools/workflows.js +196 -0
- 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.
|