Compare commits

..

89 commits
1.2.0 ... main

Author SHA1 Message Date
Sedat ÖZTÜRK
6dee1c898b Mcp güncellemesi 2026-09-10 17:40:43 +03:00
Sedat ÖZTÜRK
42d3c55ae3 Optimizasyon ölü kodlar kaldırıldı 2026-09-09 12:01:01 +03:00
Sedat ÖZTÜRK
343bdc2c5d Public sayfalar Custom componente taşındı 2026-09-09 09:06:25 +03:00
Sedat ÖZTÜRK
ad00e75911 Wizard Import ve Export işlemi mükemmelleştirildi. 2026-09-07 17:43:46 +03:00
Sedat ÖZTÜRK
9b9ef9c100 FormDevExpress Buton yapısı değiştirildi 2026-09-07 09:14:00 +03:00
Sedat ÖZTÜRK
59ec8950a3 Custom Component ve Wizard Import/Export özelliği 2026-09-05 21:20:18 +03:00
Sedat ÖZTÜRK
54e3390cbf Dynamic Services için LsitForm tanımlandı 2026-09-05 12:06:14 +03:00
Sedat ÖZTÜRK
76091c0518 seed/data Seeder SocialPost 2026-09-04 17:27:55 +03:00
Sedat ÖZTÜRK
a20d47cab8 seed/data Seeder Event 2026-09-04 16:09:43 +03:00
Sedat ÖZTÜRK
3ec9a94860 seed/data Seeder Forum 2026-09-04 15:51:58 +03:00
Sedat ÖZTÜRK
ed143c01d0 seed/data Seeder düzeltme 2026-09-04 15:23:02 +03:00
Sedat ÖZTÜRK
1423865a8c seed/data Seeder Sequences 2026-09-04 15:06:11 +03:00
Sedat ÖZTÜRK
c33926d6e5 seed/data Seeder Surveys 2026-09-04 14:56:31 +03:00
Sedat ÖZTÜRK
8be8fef972 seed/data Seeder Announcements 2026-09-04 14:52:34 +03:00
Sedat ÖZTÜRK
4bf6b4c257 seed/data Seeder Departments 2026-09-04 14:48:03 +03:00
Sedat ÖZTÜRK
fd8d2ef73a seed/data Seeder WorkHours 2026-09-04 14:46:23 +03:00
Sedat ÖZTÜRK
6f756f33d0 seed/data Seeder Reports 2026-09-04 14:44:50 +03:00
Sedat ÖZTÜRK
63653dd974 seed/data Seeder Uoms 2026-09-04 14:41:51 +03:00
Sedat ÖZTÜRK
43e3aeebd0 seed/data Seeder Skills 2026-09-04 14:39:49 +03:00
Sedat ÖZTÜRK
2e4e089e4a seed/data Seeder InstallmentOptions 2026-09-04 14:36:08 +03:00
Sedat ÖZTÜRK
e80fc2337c seed/data Seeder PaymentMethods 2026-09-04 14:34:27 +03:00
Sedat ÖZTÜRK
3b93cd3895 seed/data Seeder Products 2026-09-04 14:32:20 +03:00
Sedat ÖZTÜRK
a0491f9db8 seed/data Seeder CustomEndpoints 2026-09-04 14:29:27 +03:00
Sedat ÖZTÜRK
a1a76b1a95 seed/data Seeder GlobalSearch 2026-09-04 14:25:35 +03:00
Sedat ÖZTÜRK
3db82df964 seed/data Seeder Blogs 2026-09-04 14:22:20 +03:00
Sedat ÖZTÜRK
7c9934f56e seed/data Seeder Sectors 2026-09-04 14:18:26 +03:00
Sedat ÖZTÜRK
9e99475a3e seed/data Seeder düzenlemesi 2026-09-04 12:05:26 +03:00
Sedat ÖZTÜRK
e237cacc2a Wizard AutoFill Required Fields 2026-09-04 09:28:42 +03:00
Sedat ÖZTÜRK
47d9a20998 Intranet üzerinde Recent Documents komponenti yapıldı 2026-09-03 12:17:07 +03:00
Sedat ÖZTÜRK
7a0839a3f6 Intranet üzerinde Widgets yapısı dinamik hale getirildi 2026-09-03 11:26:35 +03:00
Sedat ÖZTÜRK
0941b9143b Wizard dil desteği ve hatalar giderildi. 2026-09-02 17:31:39 +03:00
Sedat ÖZTÜRK
3622764923 Wizard ile ListFormComponent arasında bağlantı kuruldu. 2026-09-02 11:32:53 +03:00
Sedat ÖZTÜRK
4d7aaef901 Seeds -> Project 2026-09-01 21:59:56 +03:00
Sedat ÖZTÜRK
78bc20b645 MultiValueStringJsonConverter 2026-09-01 16:44:05 +03:00
Sedat ÖZTÜRK
c4de55d459 Wizard Dosyalarından IMultiTenant özelliği kaldırıldı 2026-09-01 11:28:52 +03:00
Sedat ÖZTÜRK
7ec77c551f Wizard ile eklenen Permission module eklendi 2026-08-31 17:53:40 +03:00
Sedat ÖZTÜRK
462dd20edb Module ve Menu yapısı değiştirildi. 2026-08-31 17:17:50 +03:00
Sedat ÖZTÜRK
1fc7111da2 MenuDto içerisinde group sütunu kaldırıldı 2026-08-31 11:22:35 +03:00
Sedat ÖZTÜRK
a3667539d6 List komponentinin Description bilgisi gösterildi 2026-08-28 16:44:14 +03:00
Sedat ÖZTÜRK
a670d2e966 Telegram entegrasyonu 2026-08-28 15:03:36 +03:00
Sedat ÖZTÜRK
91de623a85 fitness_sedat_workflow 2026-08-28 12:10:17 +03:00
Sedat ÖZTÜRK
ae7ddf6c7f claude instructions düzenlemesi 2026-08-28 10:47:17 +03:00
Sedat ÖZTÜRK
9981d5b19a claude instructions dosyaları düzenlendi 2026-08-26 21:44:53 +03:00
Sedat ÖZTÜRK
422e32489f ListForm EditorScript ve EditorOptions düzenlemeleri 2026-08-26 14:24:13 +03:00
Sedat ÖZTÜRK
e4cecc8a62 Wizard Stepleri Lazy ile çalıştırıldı 2026-08-26 12:10:12 +03:00
Sedat ÖZTÜRK
9de6b1d05c Wizard Permission Group Name düzenlemesi 2026-08-26 09:40:18 +03:00
Sedat ÖZTÜRK
ec6b35d6d5 Menüsüz Wizard ekleme 2026-08-24 15:00:31 +03:00
Sedat ÖZTÜRK
b1dac82d56 DevRequest Timesheet ve Subtask sütunları açıldı 2026-08-24 13:33:03 +03:00
Sedat ÖZTÜRK
0d0834efaf TodoBoard -> New Kanban 2026-08-21 22:41:01 +03:00
Sedat ÖZTÜRK
9e362f6671 Ölü kodlar kaldırıldı. Api tarafı için 2026-08-21 22:27:59 +03:00
Sedat ÖZTÜRK
f744d18f83 Ölü kodlar kaldırıldı. Optimizasyon 2026-08-21 16:35:57 +03:00
Sedat ÖZTÜRK
b13f0b6a57 SEED=${SEED:-true} 2026-08-21 15:34:07 +03:00
Sedat ÖZTÜRK
baef565dd4 Custom Component ve Wizard için CRUD yetkiler tanımlandı 2026-08-21 15:21:58 +03:00
Sedat ÖZTÜRK
90a66562e7 Wizard ve Component Manager List ve Card görünümleri 2026-08-20 18:20:49 +03:00
Sedat ÖZTÜRK
4c09de9ad0 <select/> -> <Select /> olarak değiştirildi. 2026-08-20 17:54:11 +03:00
Sedat ÖZTÜRK
7186cb0ab0 <input/> -> <Input/> 2026-08-20 15:01:10 +03:00
Sedat ÖZTÜRK
870c98690c Custom Component genel düzenlemeleri 2026-08-20 11:50:16 +03:00
Sedat ÖZTÜRK
4003dc1845 Grid komponentindeki sütunlarını SelectBox olarak gösterilmesi 2026-08-20 09:42:23 +03:00
Sedat ÖZTÜRK
61234dec3f Form komponentinin column sayısı eklendi 2026-08-20 08:57:55 +03:00
Sedat ÖZTÜRK
690da79b87 Mobil görünümdeki Popup Edit Form problemi giderildi. 2026-08-19 17:58:24 +03:00
Sedat ÖZTÜRK
6da6654ec4 Seeds Roles Component 2026-08-19 16:25:22 +03:00
Sedat ÖZTÜRK
7659dbdaa1 SqlDataSource -> Form olarak değiştirildi. 2026-08-19 15:13:32 +03:00
Sedat ÖZTÜRK
003e74101f Uygulamanın Default Modu Dark Mod olarak değiştirildi. 2026-08-19 11:47:19 +03:00
Sedat ÖZTÜRK
ece4ee9783 SqlViewDesignerDialog 2026-08-19 10:50:33 +03:00
Sedat ÖZTÜRK
eb77285e59 WizardManager için Import özelliği eklendi 2026-08-18 22:36:51 +03:00
Sedat ÖZTÜRK
010594ac27 Custom Component Default Value özelliği 2026-08-18 14:48:16 +03:00
Sedat ÖZTÜRK
3aa74318c2 Wizard Export Özelliği 2026-08-18 13:33:50 +03:00
Sedat ÖZTÜRK
3152cd681b Custom Component json dosyasının oluşturulması 2026-08-18 12:36:40 +03:00
Sedat ÖZTÜRK
7870ebb7bc ListFormWizarda Custom Component yapısı eklendi 2026-08-18 11:00:53 +03:00
Sedat ÖZTÜRK
a0121f8e11 CrudEndpoint kaldırıldı o kısım SqlQueryManager taşındı 2026-08-17 17:26:51 +03:00
Sedat ÖZTÜRK
f5ba73f875 seeds klasör yapısı değişikliği 2026-08-17 12:23:47 +03:00
Sedat ÖZTÜRK
9a2d88b93b ReleaseNote -> ChangeLog 2026-08-17 11:51:46 +03:00
Sedat ÖZTÜRK
c993b77b5d Seeds klasörü path değiştirildi. 2026-08-17 08:56:52 +03:00
Sedat ÖZTÜRK
39ad5cab12 Ölü Paketler kaldırıldı 2026-08-16 18:30:07 +03:00
Sedat ÖZTÜRK
b517b7346e Public Demo güncellemeleri 2026-08-15 19:46:14 +03:00
Sedat ÖZTÜRK
853ad0240e AppUpdateDialog güncellemesi 2026-08-15 12:42:34 +03:00
Sedat ÖZTÜRK
abece2b33e Claude güncellemesi Crudendpoint değişiklikleri 2026-08-15 12:41:49 +03:00
Sedat ÖZTÜRK
c2f1b203ab Claude güncellemesi DynamicAppService değişiklikleri 2026-08-15 11:29:50 +03:00
Sedat ÖZTÜRK
b4c8fc3a2b ReleaseNotes güncellemesi 2026-08-15 09:30:36 +03:00
Sedat ÖZTÜRK
cedf788787 Claude güncellemesi Forum güncellemesi 2026-08-15 08:57:47 +03:00
Sedat ÖZTÜRK
c1a74bc245 LanguageText aynı olan Keyler unique hale getirildi 2026-08-14 17:03:34 +03:00
Sedat ÖZTÜRK
f372c17249 Eksik LanguageText keyler tanımlandı 2026-08-14 14:32:31 +03:00
Sedat ÖZTÜRK
b5cba69e28 Deploy Devexpress new version 2026-08-13 14:34:43 +03:00
Sedat ÖZTÜRK
cced417ede ReleaseNotes düzeltmesi 2026-08-13 13:43:48 +03:00
Sedat ÖZTÜRK
ee121db521 Vulnerabilities, React 19, Devexpress 26.1.4 2026-08-13 11:41:48 +03:00
Sedat ÖZTÜRK
f949d0ae78 ReleaseNoteAppService hataların düzeltmesi 2026-08-13 00:52:49 +03:00
Sedat ÖZTÜRK
0abf181a9c ReleaseNoteAppService güncellemesi 2026-08-13 00:26:36 +03:00
Sedat ÖZTÜRK
534dcce6da Yeni versiyon güncellemesi 2026-08-12 22:08:14 +03:00
Sedat ÖZTÜRK
f292b6b6a5 Migrations ve Seeds düzenlemesi 2026-08-12 15:20:00 +03:00
1125 changed files with 99926 additions and 50059 deletions

View file

@ -14,14 +14,25 @@ Primary principle:
- Configuration first, code last. - Configuration first, code last.
Companion documents (this file wins on conflict):
| Document | Scope |
| --- | --- |
| `lowcode.instructions.md` | **Authoring reference**: exact schemas, enum values and worked examples for producing wizard screens, editor scripts, custom components, SQL objects and endpoints. Read it before writing any artifact. |
| `lowcode-reference.instructions.md` | **Capability inventory**: field-by-field contract of every ListForm/ListFormField JSON column, chart and pivot options, SQL command hooks, Visual Designer toolbox, the scheduled-mail module and (§12) every ready-made screen under the Saas and Administration menus. Sweep its §0 checklist before producing a screen or component; a request whose answer already exists as a platform screen (§12.5) is configured there, not rebuilt. |
| `dotnet.instructions.md` | Binding code standard for everything under `api/` |
| `list.instructions.md` | Step-by-step procedure for adding a module / list through seeders |
| `../../README.md` | Technical map: what exists, where it lives, how it is wired |
| `../../CLAUDE.md` | Operating rules and standing defaults for AI agents in this repo |
--- ---
## 2. Platform Scope ## 2. Platform Scope
Core stack: Core stack:
- Backend: C# .NET 9 + ABP 9.x - Backend: C# .NET 10 (C# 14) + ABP 10.x — kod standardı: `dotnet.instructions.md`
- Frontend: React 18 + DevExtreme - Frontend: React 19 + DevExtreme
- Database: SQL Server (dynamic datasource support) - Database: SQL Server (dynamic datasource support)
- Cache: Redis - Cache: Redis
- Background jobs: Hangfire + ABP Background Workers - Background jobs: Hangfire + ABP Background Workers
@ -41,11 +52,25 @@ System nature:
3. Every proposal must include tenant and permission design. 3. Every proposal must include tenant and permission design.
4. Never bypass platform authorization patterns. 4. Never bypass platform authorization patterns.
5. Never hardcode secrets, tenant ids, or connection strings. 5. Never hardcode secrets, tenant ids, or connection strings.
6. **Stay inside the framework; ask before stepping out.** The platform may extend itself through
its own mechanisms (seed artifacts; adding a missing event, prop, or small backward-compatible
capability to an existing UI component — when Developer Kit tooling builds ~70% of the
requested component, the rest is completed with such small additions, never with a new
component; backward-compatible additions to script recipes / editor options / tokens).
Anything beyond that — a new React component or page, a new toolbox family, core engine changes (ListForm
runtime, seeders, code generator, auth, DataSourceManager, dispatcher/compiler), new ABP
entity/module/migration, platform table schema changes, new NuGet/npm dependencies, `Routes`
in `MenusData.json`, new external integrations — is **out of frame**: finish everything that
fits inside the frame first, then ask the user with a single question (why configuration is
insufficient, the smallest change that works, files touched, rollback) and proceed **only with
explicit approval**. Approval is per request and does not carry over. Full lists:
`lowcode.instructions.md` §0.8.
Exception policy: Exception policy:
- Code-level React or backend development can be considered only if user explicitly requests code implementation and configuration path is insufficient. - Code-level React or backend development can be considered only if user explicitly requests code implementation and configuration path is insufficient.
- If exception is required, AI must explain why configuration-based options are not enough. - If exception is required, AI must explain why configuration-based options are not enough.
- Never say "cannot be done": the answer is either an in-frame build or an approval question.
--- ---
@ -116,7 +141,7 @@ Capabilities:
- Query - Query
- WebService - WebService
- Cascading lookup parent-child behavior - Cascading lookup parent-child behavior
- Dynamic validation, editor options, editor scripts - Dynamic validation, editor options, editor scripts (see 7.1)
- Conditional formatting and style injection - Conditional formatting and style injection
- Grid state save/load/reset - Grid state save/load/reset
- User filter save/apply/delete flows - User filter save/apply/delete flows
@ -146,6 +171,34 @@ Supported editor types:
- dxTextArea - dxTextArea
- dxTextBox - dxTextBox
### 7.1 Editor Script and Editor Options (builder-backed)
`EditorScript` and `EditorOptions` are produced from a shared vocabulary, in two places that
must stay identical:
| Surface | Location |
| --- | --- |
| Visual builder (UI) | `ui/src/views/admin/listForm/edit/json-row-operations/editor-script/scriptRecipes.ts` and `.../editor-options/optionSpecs.ts` + `presets.ts` |
| Seeder API (C#) | `api/src/Sozsoft.Platform.Domain.Shared/Editors/``EditorScript`, `EditorScriptRule`, `EditorScriptCondition`, `EditorOptions`, `EditorOptionsBuilder` |
Rules AI must follow:
- Prefer a recipe over hand-written JS. Recipes cover arithmetic (`Multiply`, `Subtract`,
`Percent`, `Sum`, `Formula`), dates (`Today`, `Days`, `Hours`), data movement (`Copy`,
`SetValue`, `Clear`, `ApiToField`), and view/notify actions (`ReadOnly`, `Notify`, `Ask`,
`OpenUrl`). Free-form JS is `Custom` and is the last option.
- Generated script carries a `// @builder {...}` header on the first line that stores the rules,
plus `// @runOnOpen` when any rule runs on form open. **Never edit the header by hand and never
emit a script that does not match what the builder would emit** — the dialog then treats the
script as manually edited and closes the rule editor.
- Rule triggers: `change` (default), `open` (default-value generation), `both`. Conditions join
with `and` / `or`.
- When a recipe changes on one side, change its port on the other side in the same task.
- In seeders write `EditorOptions = EditorOptions.Number(2).ShowClearButton()` style expressions
instead of raw JSON strings. Options marked `platform: true` in `optionSpecs.ts` deserialize
into typed DTOs (`GridBoxOptionsDto`, `TagBoxOptionsDto`, `ImageUploadOptionsDto`); wrong types
are silently ignored.
--- ---
## 8. Dynamic Form System ## 8. Dynamic Form System
@ -172,6 +225,8 @@ Supported component families:
- TreeList / Tree view - TreeList / Tree view
- SubForm tabs (List, Tree, Gantt, Scheduler, Form, Chart) - SubForm tabs (List, Tree, Gantt, Scheduler, Form, Chart)
- Widget Group (dashboard KPI cards) - Widget Group (dashboard KPI cards)
- TodoBoard — a Kanban board: columns come from `statusExpr` values, cards change status by drag
and drop, new columns (statuses) can be added from the board
Runtime UI capabilities: Runtime UI capabilities:
@ -212,6 +267,54 @@ If user requests custom code explicitly:
- Provide a warning that low-code path is preferred. - Provide a warning that low-code path is preferred.
- Offer configuration-first alternative first. - Offer configuration-first alternative first.
### 10.1 Custom Component path (still not hand-written React)
When a screen genuinely cannot be expressed as a ListForm, the next step is **not** a physical
React page. It is a Custom Component built in the Visual Designer and attached to a menu through
the Wizard's `Custom` path.
- The component is stored in the database (`Name`, `RoutePath`, `Code`, `Props`, `Dependencies`,
`DataSources`) and compiled in the browser with `@babel/standalone`.
- The designer document lives in `Props.visualDesigner` **and** in the
`/*__SOZSOFT_VISUAL_DESIGNER__…__*/` header of the generated code. `sourceMode` is `visual` or
`code`; switching to `code` is one-way — the canvas cannot be restored afterwards, so do not
propose it casually.
- Toolbox families: `layout` (`PageContainer`, `FlexRow`, `Spacer`), `data` (`Form` — owns the
four CRUD endpoints and acts as a container), `platform` (`ListView`, `DataGridView`,
`TreeView`, `GanttView`, `TodoBoard`, `CardView`, `SchedulerView`, `PivotView`, `ChartView`
each embeds a ListForm screen through `listFormCode`), `html`/`ui`, and other custom
components as `custom`.
- **Reuse before drawing:** if the requirement is a list/tree/chart that a ListForm already
serves, drop the matching `platform` node with its `listFormCode` instead of rebuilding it out
of primitives.
- Data binding goes through CRUD endpoints. Filter operators map to the CrudEndpoint `GetList`
contract: `eq` is a bare query parameter, every other operator carries its name as a suffix
(`?Name.contains=…`).
- Permissions are two-layered and mandatory: node-level `designerPermission` for visibility, and
per-command Form permissions (`selectPermission`, `insertPermission`, `updatePermission`,
`deletePermission`). In `Otomatik` mode the base is the read permission the Wizard created for
the component and the commands get `''` / `.Create` / `.Update` / `.Delete` suffixes.
- List screen permissions: `App.DeveloperKit.CustomComponents{,.Create,.Update,.Delete,.Export,.Import}`.
- Every save/delete is mirrored into `configs/seeds/{scope}/data/App.DeveloperKit.CustomComponents.json`; the endpoints it uses are
referenced through `crud/{Entity}.json`. A component that is not seed-backed is not done.
### 10.2 SQL View Designer
For shaping data before a screen binds to it, prefer a designed view over an inline query:
`SqlViewDesignerDialog` produces T-SQL from a diagram (sources, joins, `CROSS/OUTER APPLY`,
derived subqueries) plus an SSMS-style criteria grid (Column / Alias / Output / Group By / Sort /
Filter / Or…). It is **one-way** — model to T-SQL. A view that does not parse back into the
canonical shape drops the dialog to raw SQL mode, so hand-editing a designed view costs the
designer. Aggregations available in the Group By column: `GroupBy`, `Where`, `SUM`, `COUNT`,
`COUNT_DISTINCT`, `AVG`, `MIN`, `MAX`.
Persistence: only the **table designer's** deploy writes a seed file automatically
(`{sql|postgres}/object/{TableName}.sql`, full CREATE script, via `save-table-script`). View
Designer deploys and any DDL run from the SQL editor (views, procedures, functions) reach the
database only — their seed files under `{sql|postgres}/object/` must be written explicitly.
Full mechanics and the SP/function production patterns: `lowcode.instructions.md` §8.1§8.2.1
and §9.5.
--- ---
## 11. Integrations ## 11. Integrations
@ -342,7 +445,8 @@ When configuration cannot satisfy requirement:
1. Try SQL-based design 1. Try SQL-based design
2. Try Custom Endpoint 2. Try Custom Endpoint
3. Try Dynamic Service 3. Try Dynamic Service
4. Propose minimal code change with explicit justification 4. Propose minimal code change with explicit justification — this step is out of frame:
**ask and wait for approval before touching code** (rule 3.6).
--- ---
@ -540,13 +644,39 @@ When in doubt, AI must choose the path that preserves:
## 31. Seeder-Driven Low-Code Development Guide (Authoritative) ## 31. Seeder-Driven Low-Code Development Guide (Authoritative)
AI must learn and teach implementation flow primarily from these seed assets: There are **two seed surfaces**. AI must not mix them.
- api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs **A. Repository seeds (shipped with code)** — `api/src/Sozsoft.Platform.DbMigrator/Migrations/`.
- api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json Platform-owned screens, menus, permissions and language texts. Changing them requires a build and
- api/src/Sozsoft.Platform.DbMigrator/Seeds/PermissionsData.json a deploy. AI must learn and teach implementation flow primarily from these assets:
- api/src/Sozsoft.Platform.DbMigrator/Seeds/HostData.json
- api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json - ListFormSeeder_Saas.cs / ListFormSeeder_Administration.cs / ListFormSeeder_{Modul}.cs
- MenusData.json
- PermissionsData.json
- HostData.json
- LanguagesData.json
- WizardDataSeeder.cs / CrudDataSeeder.cs — the seeders that read
surface B below
- ListFormSeedDataApplier, CrudEndpointSeedApplier (Domain/Data/) — the single home of the apply
rules; both the migrate/seed flow and the wizard import's completion call the same services.
`data/` rows are inserted-only on migrate (a possibly stale file must not overwrite live data);
`crud/` entries are timestamp-guarded — an existing record is updated or deleted only when the
file's `GeneratedAt` is newer than the record's `LastModificationTime`/`CreationTime`. **Migrate
adds; it does not overwrite.** Follow this rule in any new seeder.
**B. Runtime seeds (written by the running application)** — `configs/seeds/`, resolved through
`SeedPathResolver`. The Wizard, the Custom Components screen / Visual Designer, the CRUD
Endpoint Manager and every ListForm screen with a `SeedFilePath` write here; the
seeders above restore them when the database is recreated. Scope folders mirror the CDN layout:
```
configs/seeds/host/{wizard,data,crud,sql/{object,execute},postgres/{object,execute}}
configs/seeds/tenants/{tenantId}/… same layout, tenant scope
configs/seeds/.imports/{importId}/… wizard import staging + backup (not scanned)
```
Rule: anything produced at runtime must land in surface B. **A feature that disappears when the
database is recreated is not delivered.**
If user asks "how to add a new module/screen", AI must answer with this exact operational sequence. If user asks "how to add a new module/screen", AI must answer with this exact operational sequence.
@ -659,6 +789,39 @@ When user asks for a screen/module, AI must answer in this order:
AI should produce practical, copy-adaptable artifact definitions and avoid abstract-only explanations. AI should produce practical, copy-adaptable artifact definitions and avoid abstract-only explanations.
### 31.9 Screen brief, discovery questions and image-based requests
A request arrives either as prose or as a **screenshot / mockup / photo** of the desired screen.
Both enter the same pipeline — and for a **new screen/component the pipeline starts with
questions, not files**:
1. **Run the discovery round** (`lowcode.instructions.md` §0.6). Ask in batches (max 4 questions
per round, each with 24 options and a recommended default), max three rounds. Round 1 always
covers: screen kind (List/Wizard vs Custom Component vs single-record form), data source,
owning module, and tenancy scope. Round 2 uses the path-specific question set (§0.6.3 for
List, §0.6.4 for Custom). Never ask what the repo already answers — search first, confirm
findings instead of re-asking. If the user says "you decide / don't ask", skip the round and
build on defaults.
2. Turn the answers into a single screen brief — purpose, data source, columns and editors,
layouts, actions, sub-screens, widgets, approval flow, menu, permissions, tenancy, language
texts. For an image, read it first (page regions → main region type → toolbar → columns →
filters → form controls → row actions); the image answers most of round 2, so ask only what
an image cannot show (§0.7.4).
3. **Get the brief confirmed before writing any seed file.** Mark defaulted lines `(varsayılan)`
so the user sees what they are approving.
4. Pick the tool: grid-shaped work → SQL Query Manager + Wizard; free layout / dashboard /
single-record custom form → Custom Component. **When in doubt, recommend the Wizard.**
5. Produce the seed files in dependency order, then list them and restate the defaulted choices.
An image is a reference, not a pixel contract: build the closest thing the platform's own
components give, and state at the end what had no equivalent. Three things are never assumed,
always asked: which table is written to, who approves (when there is an approval flow), and the
formula of any calculation/business rule. This question-first rule applies only to producing a
**new** screen/component; fixes, field additions, debugging and explanations proceed directly.
The concrete question sets, brief template, the image-element → artifact mapping and the
visual-control → `EditorType` mapping are in `lowcode.instructions.md` §0.6§0.7.
--- ---
## 32. Mandatory Default Behaviors (Do Not Ask Repeatedly) ## 32. Mandatory Default Behaviors (Do Not Ask Repeatedly)
@ -701,7 +864,131 @@ Rules:
- Permission contract must be created/bound for both parent and child menu items. - Permission contract must be created/bound for both parent and child menu items.
- Route/menu consistency remains mandatory (`Url` and screen contract must match). - Route/menu consistency remains mandatory (`Url` and screen contract must match).
### 32.3 AI enforcement requirement ### 32.3 Wizard component kind and menu creation
`ListFormWizardDto.ComponentKind` is the wizard's fork and the first field in the seed file.
Default is `List`, so older seed files that lack it keep working.
| Kind | Produces | Menu URL | Child permissions |
| --- | --- | --- | --- |
| `List` | ListForm + ListFormFields + Route + Menu + Permissions + language keys | `/admin/list/{ListFormCode}` | `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| `Custom` | Menu + Permissions + language keys; the screen is the selected Custom Component | the component's `RoutePath` | `.Create`, `.Update`, `.Delete` only |
`Export` / `Import` / `Note` exist only in the ListForm ecosystem. Never propose them on the
`Custom` path.
`CreateMenu = false` (menu-less wizard): no menu record — and no parent menu record — is created;
ListForm, permissions and language keys are still produced. Use it when the screen is meant to be
embedded elsewhere as a SubGrid/part. Default is `true`.
### 32.4 Permission group selection
The wizard's permission group comes from a select list backed by `GetWizardPermissionGroups`.
Display names are read from the language key whose name equals the group name.
- Selecting an existing group fills `PermissionGroupEn/Tr` (seed file: `Language.PermissionGroupEn/Tr`);
if left blank the server writes the database values back, so the seed file always carries both
languages.
- Defining a new group writes those texts to the language key with the same name as the group.
- AI must never propose a permission group without EN + TR display names.
### 32.5 Wizard export/import is a package, not a file
Exporting a wizard produces a zip whose paths are relative to the scope folder
(`host` / `tenants/{tenantId}`) and which carries every dependency:
The closure is **recursive** (`WizardExportCollector`): a sub-form's wizard file is queued and its
own dependencies are collected under the same rules, so the archive stands on its own.
- `wizard/{file}.json` — the screen itself plus the wizard files of the sub-forms it references
through `SubFormsDto[].Code`, recursively
- `data/App.DeveloperKit.CustomComponents.json``Custom` path; the file is filtered down to the
exported component and its dependency chain, never exported whole
- `data/{ListFormCode}.json` — the screen's own data mirror when `SeedFilePath` is set
- `crud/{entity}.json` — endpoints those components use
- `{sql|postgres}/{object|execute}/{object}.sql` — the object behind `SelectCommand`, the objects
a lookup query (`LookupDto.LookupQuery`) references through `FROM`/`JOIN`, and on the `Custom`
path the tables the CRUD endpoints sit on
Script files are never guessed from a fixed provider/folder combination: the scope's script folders
are scanned from disk, and every file matching the object name is added. A missing script is **not**
a warning — not every table is seed-managed (ABP's own tables come from EF migrations); whether the
target has it is decided by the import analysis against the real database, never assumed here.
An unresolved **recorded reference** (a sub-form, a `SeedFilePath` mirror, a `DataSources[].SeedFile`,
a component row named in a seed file but with no counterpart) does not block the download but is
never silent: `ExportAnalysis` returns the full archive contents plus what is missing, and the File
Manager shows that list before downloading.
The import screen is a four-step wizard (`Steps`): **Files** (resolve conflicts) → **Checks**
(findings; a schema finding opens the existing `SqlTableDesignerDialog`, and the analysis re-runs
after a deploy) → **Plan** (the full list of what confirming will do, in the server's completion
order) → **Apply**. Going back is always allowed; going forward is gated — unresolved conflicts
block Checks, a blocking finding blocks Plan.
Server-side it is two phases: `AnalyzeImport` (unzip to `.imports/{id}/staged`, classify each entry
as `New` / `Identical` / `Conflict`) then `ApplyImport` (file-by-file write, previous content backed
up so `RollbackImport` can undo the whole batch), closed by `CompleteImport`.
`AnalyzeImport` also **validates** before anything is written (`WizardImportValidator`). With at
least one `Error` finding the session is marked `blocked` on the server and `ApplyImport` refuses,
so the check cannot be bypassed by calling the endpoint directly. Errors: malformed JSON; a wizard
file with no `Wizard`/`ListForm` section; a screen's or a `crud/{Entity}.json`'s table missing in
the target **and** absent from the archive's scripts; a `data/{code}.json` whose ListForm exists
neither in the archive nor in the target; an unresolvable custom-component `Dependencies` /
`DataSources[].SeedFile`; an empty component name or route on the `Custom` path; a sub-form
reference with no counterpart. Warnings: object existence could not be queried, provider mismatch
(`sql` ↔ `postgres`), a missing `SeedFilePath` data file, an empty file.
The analysis also **compares the table schema**. Generated table scripts are wrapped in
`IF OBJECT_ID(...) IS NULL BEGIN CREATE TABLE ... END`, so when the target already has the table the
`CREATE` block is skipped entirely and a new column never arrives from the script itself — the screen
would then fail with an invalid-column error. The incoming `CREATE TABLE` declaration (`SqlCreateTableParser`) is
compared against the live `INFORMATION_SCHEMA.COLUMNS` shape. The split follows the risk of loss:
**a missing column is added** — adding is lossless, so it is reported as a Warning with the statement
it will run (`WizardImportIssueDto.Statement`) and applied at completion; the one exception is a
`NOT NULL` column with no default on a non-empty table, which the database itself would reject and
where the value to write is a business decision — that blocks. **An existing column is never
changed**: a length / precision / scale / nullability difference is an Error and stops the import,
because narrowing truncates and a type conversion can fail. An extra column in the target is a
Warning and is never dropped; no path emits `DROP TABLE`, `DROP COLUMN` or `ALTER COLUMN`. The
comparison lives in one place (`SqlTableSchemaComparer`) and both the analysis and the completion
use it. Type **names** are deliberately not compared: the provider catalog reports its own
canonical name (`VARCHAR` → `character varying` on PostgreSQL), so a textual comparison would raise
a false difference on every `VARCHAR` column.
A `data/*.json` entry is compared and merged **by row, not by file**: the diff shows only the
rows whose keys the archive carries (file-level `GeneratedAt`/`Order` are excluded), so unrelated
records never surface as a difference — no matching row means `New` (insert), a matching identical
row means `Identical` (nothing is transferred), a matching different row means `Conflict` (update).
The file itself is never overwritten: rows are merged into the target by key (incoming row wins,
target-only rows are kept). `CompleteImport` then applies exactly the
`data/` files it wrote to the database through `IListFormSeedDataApplier` with
`overwriteExisting: true` — a missing row is inserted, an existing one is updated from the file
(unlike migrate/seed, where a possibly stale file must not overwrite live data), and a row that was
soft deleted is revived by clearing the columns the screen's `DeleteCommand` sets
(`IsDeleted`/`DeletionTime`/`DeleterId`), otherwise the default filter would keep hiding it — and
reports the inserted/updated/skipped counts. Data files that were already identical to the target
are applied too: there was nothing to write, but the rows may be missing or soft deleted in the
database. A file the user chose to keep (different, not written) is left alone. This step is not
covered by `RollbackImport`, which only restores files and must be run before it.
`CompleteImport` runs in **dependency order and the order is fixed**: (a) the `.sql` scripts under
`{sql|postgres}/{object,execute}` (`object` first) — a missing table is created here, otherwise the
later steps would fail on it; (b) columns missing from existing tables are added; (c) `crud/*.json`
through `ICrudEndpointSeedApplier`; (d) the `data/*.json` rows; (e) `wizard/*.json` — the screens
are installed through the same path the wizard screen uses (`DeployWizardAsync`), reusing the file's
`ListForm`/`Fields` contract as-is instead of rebuilding it from the wizard answers. A screen that
already exists is left untouched, and each screen is installed in its own unit of work. No migrate
run is needed: the screen works as soon as the import finishes.
Limits: allowed root folders `wizard`, `crud`, `data`, `sql`, `postgres` (and only `object` /
`execute` under the SQL provider folders); 5 MB per entry, 50 MB per archive, 500 entries.
Permissions: `App.Listforms.Wizard.Export` / `.Import`.
AI must not propose moving a screen between environments by copying a single json file.
### 32.6 AI enforcement requirement
AI must proactively apply these defaults in: AI must proactively apply these defaults in:

View file

@ -0,0 +1,150 @@
# .NET / ABP Kod Standardı
Bu doküman `api/` altındaki tüm projeler için bağlayıcıdır. Platform davranışına dair
kurallar için `ai.instructions.md` esastır; çelişki olursa o dosya kazanır.
Standart derlemede zorlanır: `api/Directory.Build.props` analizörleri açar,
`api/.editorconfig` kural şiddetlerini tanımlar. Bu iki dosya standardın tek
kaynağıdır; proje dosyalarında tekrar edilmez.
## 1. Hedef sürümler
| Katman | Sürüm | Nerede tanımlı |
| --- | --- | --- |
| SDK | .NET 10 (`10.0.300`) | `api/global.json` |
| TargetFramework | `net10.0` | proje dosyaları |
| Dil | C# 14 (`LangVersion=latest`) | `api/common.props` |
| ABP | 10.0.0 | `Volo.Abp.*` paket referansları |
Sürüm yükseltmesi tek noktadan yapılır: `global.json` + `common.props` + paket sürümleri.
Proje dosyalarına TFM/LangVersion tekrar yazılmaz.
## 2. Nullable
`Directory.Build.props` içinde **çözüm geneli `Nullable` ayarı yapılmaz.** Sebebi
EF Core: kolonun zorunlu olup olmadığını, entity assembly'sinin nullable annotation
bağlamından türetir. Global bir `enable`/`annotations` anahtarı, bir sonraki
`dotnet ef migrations add` çağrısında tüm `string` özelliklerini sessizce NOT NULL
kolona çevirir — seeder'ın doldurmadığı her alan da o anda kırılır.
Kural:
- Nullable, proje bazında ve bilinçli olarak açılır (`<Nullable>enable</Nullable>`).
Şu an açık olanlar: `Sozsoft.Platform.Application`, `Sozsoft.Notifications.*`,
test projeleri.
- **Entity taşıyan projelerde** (`*.Domain`) nullable açılacaksa, aynı PR'da
`dotnet ef migrations add` çıktısı gözden geçirilir; şema değişikliği isteniyorsa
kabul edilir, istenmiyorsa kolon zorunluluğu `PlatformDbContext` içinde
`.IsRequired(false)` ile açıkça sabitlenir.
- Kolon zorunluluğu tercihen NRT'ye değil, `PlatformDbContext` yapılandırmasına
yazılır; böylece derleyici anahtarından bağımsız olur.
## 3. Ölü kod politikası
Aşağıdaki kurallar ihlal edilmiş kod merge edilmez:
| Kural | Anlamı |
| --- | --- |
| IDE0005 | Kullanılmayan `using` |
| IDE0051 | Çağrılmayan private üye |
| IDE0052 | Yazılıp hiç okunmayan private alan (kullanılmayan enjekte bağımlılık) |
| IDE0059 | Gereksiz değer ataması |
| IDE0161 | `namespace X;` (file-scoped) |
Tarama ve otomatik düzeltme (kalıcı yapılandırma gerektirmez):
```bash
# Rapor
dotnet build api/Sozsoft.Platform.sln --no-incremental \
-p:EnforceCodeStyleInBuild=true -p:GenerateDocumentationFile=true
# Düzeltme
dotnet format api/Sozsoft.Platform.sln --diagnostics IDE0005,IDE0161 --severity warn
```
> Kurallar `.editorconfig`'te `warning` seviyesindedir ve `EnforceCodeStyleInBuild`
> açık olduğu için normal `dotnet build` de bunları raporlar.
IDE0052 düzeltilirken alan ile birlikte **constructor parametresi de** silinir; yoksa
DI hâlâ gereksiz servisi çözer.
`CS0162` (erişilemez kod) `Directory.Build.props` içinde gerekçesiyle susturulmuştur:
veritabanı sağlayıcısı derleme zamanı sabiti olduğu için diğer sağlayıcının dalları
erişilemez görünür. Bu durum dışında erişilemez kod bırakılmaz.
Ek kurallar:
- Bir tip/dosya silinmeden önce **repo genelinde** (api + ui + configs) referans
taraması yapılır. DI ile çözülen tipler (AppService, Manager, Provider, Repository,
Seeder, Localizer, Notification sağlayıcıları) düz metin aramasında "referanssız"
görünür — bunlar yalnızca modül/DI kaydı incelendikten sonra silinir.
- Yorum satırına alınmış kod bloğu bırakılmaz; ihtiyaç varsa git geçmişinden alınır.
- "İleride lazım olur" gerekçesiyle kullanılmayan DTO/Input/Consts tutulmaz.
- `Migrations/` ve `*.g.cs` üretilmiş koddur, bu kuralların dışındadır.
## 4. Modern C# kullanımı (yeni kod)
- File-scoped namespace: `namespace Sozsoft.Platform.ListForms;`
- Primary constructor: `public class XAppService(IRepository<X, Guid> repo) : PlatformAppService`
— parametre doğrudan kullanılır, `private readonly ... = param;` şeklinde
tekrar tanımlanmaz.
- Koleksiyon ifadeleri (`[]`, `[.. items]`), `is not null`, `switch` ifadesi,
desen eşleme, `Index/Range`, hedefi belli `new()`.
- `out var`, tuple deconstruction; kullanılmayan çıktı için `_`.
- `async` metotlar `Async` ile biter ve `Task`/`Task<T>` döner; `async void` yasak.
ABP'de `ConfigureAwait` kullanılmaz.
- Biçim: `api/.editorconfig` geçerlidir (4 boşluk girinti, `using` blokları namespace
dışında ve System önce, dosya sonunda satır sonu, UTF-8 BOM).
## 5. ABP kuralları
- **Katman sınırı**: Domain.Shared → Domain → Application.Contracts → Application →
HttpApi → HttpApi.Host. Ters yönde referans verilmez; `EntityFrameworkCore` yalnızca
Domain'e bakar. `DbMigrator` yalnızca `Application.Contracts` + `EntityFrameworkCore`
referansı taşır; **Application'a referans verilmez** — seeder'ın uygulama servislerine
ihtiyaç duyması, kodun yanlış katmanda olduğunun işaretidir.
- **Hem seeder'ın hem AppService'in ihtiyaç duyduğu dönüşüm**: ortak kod
`Application.Contracts`'a konur (DTO'nun kendi metodu olarak; yeni yardımcı sınıf
üretilmez). Dönüşümün entity'ye yazması gerekiyorsa entity'nin **yazılabilir kolon
sözleşmesi** Domain.Shared'de bir arayüz olarak tanımlanır (`IListFormArtifact`,
`IListFormFieldArtifact`), entity bu arayüzü uygular ve dönüşüm arayüz üzerinden yazar.
Böylece Contracts, Domain'i tanımadan entity doldurabilir ve aynı dönüşüm iki yerde
kopyalanmaz.
- **AppService**: `PlatformAppService`'ten türer, arayüzü Application.Contracts'ta
tanımlanır. Controller yazılmaz — ABP otomatik API üretir. Controller sadece ABP
konvansiyonunun karşılamadığı durumda (dosya indirme, harici webhook) eklenir.
- **Yetki**: her AppService/metot `[Authorize(PlatformConsts.AppCodes....)]` ile
korunur; izin adları `PermissionDefinitionProvider` içinde tanımlanır. Yetki
kontrolü koda gömülmez, izin adı sabitten okunur.
- **Tenant izolasyonu**: sorgular `ICurrentTenant` bağlamında çalışır; `IMultiTenant`
entity'lerde manuel `TenantId` filtresi yazılmaz, veri filtreleri devre dışı
bırakılacaksa (`ICurrentTenant.Change`, `IDataFilter`) gerekçe yorumla yazılır.
- **Repository**: özel sorgu gerekiyorsa Domain'de arayüz + EntityFrameworkCore'da
implementasyon. AppService içinde `DbContext` doğrudan kullanılmaz.
- **Dinamik SQL**: parametreli çalıştırılır; string birleştirme ile sorgu kurulmaz.
- **Mapping**: Mapperly (`[Mapper]` partial class) kullanılır; AutoMapper eklenmez.
RMG012 uyarısı "hedefte karşılığı olmayan alan" demektir; yeni mapper eklerken bu
uyarı bırakılmaz.
- **Localization**: kullanıcıya görünen metin koda gömülmez, `PlatformResource`
anahtarı üzerinden verilir.
- **Çok değerli alan taşıyan girdi DTO'su**: ekranda `dxTagBox` (ya da çoklu seçimli
`dxGridBox`) ile doldurulan bir alan tipli bir DTO'ya bağlanıyorsa property `string`
kalır ve `[JsonConverter(typeof(MultiValueStringJsonConverter))]` ile işaretlenir —
UI diziyi olduğu gibi yollar, converter `PlatformConsts.MultiValueDelimiter` ile tek
string üretir. Varsayılan `list-form-data/*` ucundan geçen ekranlarda bu iş
`QueryHelper` tarafından yapılır, DTO yoktur; okuma DTO'larına öznitelik eklenmez.
- **Sabitler**: bağlantı dizesi, tenant id, anahtar/secret koda yazılmaz; konfigürasyon
veya `SettingDefinition` üzerinden okunur.
## 6. Değişiklik öncesi kontrol listesi
```bash
dotnet build api/Sozsoft.Platform.sln --no-incremental \
-p:EnforceCodeStyleInBuild=true -p:GenerateDocumentationFile=true # 0 hata, yeni IDE00xx yok
dotnet format api/Sozsoft.Platform.sln --verify-no-changes --diagnostics IDE0005,IDE0161 --severity warn
dotnet test api/test/Sozsoft.Platform.EntityFrameworkCore.Tests
```
> Not: EntityFrameworkCore testleri şu an SQLite şema oluşturmada
> (`nvarchar(max)` → `SQLite Error 1`) kırık; bu kırıklık bu standarttan önce de vardı
> ve ayrı bir iş olarak ele alınmalıdır.

View file

@ -7,6 +7,10 @@ This document summarizes the rules, standards, and step-by-step instructions for
--- ---
> **Bu doküman depo içi seeder'larla (DbMigrator/Migrations) modül eklemeyi anlatır.**
> Çalışma zamanında Wizard / Custom Components / CRUD Endpoint Manager ile üretim yapacaksan
> `lowcode.instructions.md` esastır; oradaki seed dosyaları `configs/seeds` altına düşer.
## General Principles ## General Principles
- **Configuration First:** Always prefer platform configuration (menus, permissions, forms, localization) over custom code. - **Configuration First:** Always prefer platform configuration (menus, permissions, forms, localization) over custom code.
- **Modularization:** Each module (e.g., {Modul}) must have its own seeder, permission group, and localization entries. - **Modularization:** Each module (e.g., {Modul}) must have its own seeder, permission group, and localization entries.
@ -24,7 +28,7 @@ This document summarizes the rules, standards, and step-by-step instructions for
## Special File Handling Rules ## Special File Handling Rules
- **MenusData.json:** - **MenusData.json:**
- Never modify the `Routes` section directly. Only `MenuGroups` and `Menus` sections can be changed for menu additions or updates. All menu additions must use the platform's configuration mechanisms or be added to `MenuGroups` and `Menus` as required by platform design. - Never modify the `Routes` section directly. Only `Modules` and `Menus` sections can be changed for menu additions or updates. All menu additions must use the platform's configuration mechanisms or be added to `Modules` and `Menus` as required by platform design.
- **LanguagesData.json:** - **LanguagesData.json:**
- When adding a new key, always check if the key already exists. Only add the key if it does not exist to prevent duplicates. - When adding a new key, always check if the key already exists. Only add the key if it does not exist to prevent duplicates.
@ -35,7 +39,7 @@ This document summarizes the rules, standards, and step-by-step instructions for
### 1. MenusData.json ### 1. MenusData.json
- **{Modul} menu** added as a top-level menu with `ShortName: {Modul}`. - **{Modul} menu** added as a top-level menu with `ModuleId: {Modul}`.
- **{Liste}** added as a child menu under {Modul}. - **{Liste}** added as a child menu under {Modul}.
### 2. PermissionsData.json ### 2. PermissionsData.json

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

11
.gitignore vendored
View file

@ -1,5 +1,14 @@
# Konteyner veri klasorleri (mssql/postgres/redis vb.); seed veri dosyalari haric tutulur.
configs/**/data/** configs/**/data/**
!configs/seeds/**/data/**
*.bak *.bak
**/node_modules **/node_modules
**/.DS_Store **/.DS_Store
logs/ logs/
# Deploy sırasında git tag'lerinden üretilir (configs/change-logs/generate-change-logs.sh)
# ve API konteynerine mount edilir. Takip edilirse deploy'daki `git pull` yerel
# değişiklikle çakışır ve dosya eskir.
configs/change-logs/change-logs.json
# Wizard import staging klasoru (gecici; oturum bitince silinir)
configs/seeds/.imports/

829
README.md

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,9 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<NoWarn>$(NoWarn);CS0162;CS8321;CS8618;CS8632</NoWarn> <NoWarn>$(NoWarn);CS0162;CS8321;CS8618;CS8632;CS1591;CS1573;CS1572;CS1570;CS1574</NoWarn>
<LangVersion>latest</LangVersion>
<AnalysisLevel>latest</AnalysisLevel>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View file

@ -5,6 +5,7 @@ WORKDIR /app
COPY global.json ./ COPY global.json ./
COPY common.props ./ COPY common.props ./
COPY "modules/Sozsoft.Languages/common.props" "modules/Sozsoft.Languages/" COPY "modules/Sozsoft.Languages/common.props" "modules/Sozsoft.Languages/"
COPY "modules/Sozsoft.Mcp/common.props" "modules/Sozsoft.Mcp/"
COPY "modules/Sozsoft.Notifications/common.props" "modules/Sozsoft.Notifications/" COPY "modules/Sozsoft.Notifications/common.props" "modules/Sozsoft.Notifications/"
COPY "modules/Sozsoft.Settings/common.props" "modules/Sozsoft.Settings/" COPY "modules/Sozsoft.Settings/common.props" "modules/Sozsoft.Settings/"
COPY "modules/Sozsoft.SqlQueryManager/common.props" "modules/Sozsoft.SqlQueryManager/" COPY "modules/Sozsoft.SqlQueryManager/common.props" "modules/Sozsoft.SqlQueryManager/"
@ -13,6 +14,12 @@ COPY "modules/Sozsoft.Languages/Sozsoft.Languages.Domain/Sozsoft.Languages.Domai
COPY "modules/Sozsoft.Languages/Sozsoft.Languages.Domain.Shared/Sozsoft.Languages.Domain.Shared.csproj" "modules/Sozsoft.Languages/Sozsoft.Languages.Domain.Shared/" COPY "modules/Sozsoft.Languages/Sozsoft.Languages.Domain.Shared/Sozsoft.Languages.Domain.Shared.csproj" "modules/Sozsoft.Languages/Sozsoft.Languages.Domain.Shared/"
COPY "modules/Sozsoft.Languages/Sozsoft.Languages.EntityFrameworkCore/Sozsoft.Languages.EntityFrameworkCore.csproj" "modules/Sozsoft.Languages/Sozsoft.Languages.EntityFrameworkCore/" COPY "modules/Sozsoft.Languages/Sozsoft.Languages.EntityFrameworkCore/Sozsoft.Languages.EntityFrameworkCore.csproj" "modules/Sozsoft.Languages/Sozsoft.Languages.EntityFrameworkCore/"
COPY "modules/Sozsoft.MailQueue/Sozsoft.MailQueue.csproj" "modules/Sozsoft.MailQueue/" COPY "modules/Sozsoft.MailQueue/Sozsoft.MailQueue.csproj" "modules/Sozsoft.MailQueue/"
COPY "modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/Sozsoft.Mcp.Application.csproj" "modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/"
COPY "modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/Sozsoft.Mcp.Application.Contracts.csproj" "modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/"
COPY "modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Sozsoft.Mcp.Domain.csproj" "modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/"
COPY "modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/Sozsoft.Mcp.Domain.Shared.csproj" "modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/"
COPY "modules/Sozsoft.Mcp/Sozsoft.Mcp.EntityFrameworkCore/Sozsoft.Mcp.EntityFrameworkCore.csproj" "modules/Sozsoft.Mcp/Sozsoft.Mcp.EntityFrameworkCore/"
COPY "modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Sozsoft.Mcp.HttpApi.csproj" "modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/"
COPY "modules/Sozsoft.Notifications/Sozsoft.Notifications.Application.Contracts/Sozsoft.Notifications.Application.Contracts.csproj" "modules/Sozsoft.Notifications/Sozsoft.Notifications.Application.Contracts/" COPY "modules/Sozsoft.Notifications/Sozsoft.Notifications.Application.Contracts/Sozsoft.Notifications.Application.Contracts.csproj" "modules/Sozsoft.Notifications/Sozsoft.Notifications.Application.Contracts/"
COPY "modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/Sozsoft.Notifications.Domain.csproj" "modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/" COPY "modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/Sozsoft.Notifications.Domain.csproj" "modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/"
COPY "modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain.Shared/Sozsoft.Notifications.Domain.Shared.csproj" "modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain.Shared/" COPY "modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain.Shared/Sozsoft.Notifications.Domain.Shared.csproj" "modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain.Shared/"

View file

@ -7,6 +7,7 @@ RUN apk add nodejs~=24 npm~=11
COPY global.json ./ COPY global.json ./
COPY common.props ./ COPY common.props ./
COPY "modules/Sozsoft.Languages/common.props" "modules/Sozsoft.Languages/" COPY "modules/Sozsoft.Languages/common.props" "modules/Sozsoft.Languages/"
COPY "modules/Sozsoft.Mcp/common.props" "modules/Sozsoft.Mcp/"
COPY "modules/Sozsoft.Notifications/common.props" "modules/Sozsoft.Notifications/" COPY "modules/Sozsoft.Notifications/common.props" "modules/Sozsoft.Notifications/"
COPY "modules/Sozsoft.Settings/common.props" "modules/Sozsoft.Settings/" COPY "modules/Sozsoft.Settings/common.props" "modules/Sozsoft.Settings/"
COPY "modules/Sozsoft.SqlQueryManager/common.props" "modules/Sozsoft.SqlQueryManager/" COPY "modules/Sozsoft.SqlQueryManager/common.props" "modules/Sozsoft.SqlQueryManager/"
@ -26,6 +27,12 @@ COPY "modules/Sozsoft.Languages/Sozsoft.Languages.Domain/Sozsoft.Languages.Domai
COPY "modules/Sozsoft.Languages/Sozsoft.Languages.Domain.Shared/Sozsoft.Languages.Domain.Shared.csproj" "modules/Sozsoft.Languages/Sozsoft.Languages.Domain.Shared/" COPY "modules/Sozsoft.Languages/Sozsoft.Languages.Domain.Shared/Sozsoft.Languages.Domain.Shared.csproj" "modules/Sozsoft.Languages/Sozsoft.Languages.Domain.Shared/"
COPY "modules/Sozsoft.Languages/Sozsoft.Languages.EntityFrameworkCore/Sozsoft.Languages.EntityFrameworkCore.csproj" "modules/Sozsoft.Languages/Sozsoft.Languages.EntityFrameworkCore/" COPY "modules/Sozsoft.Languages/Sozsoft.Languages.EntityFrameworkCore/Sozsoft.Languages.EntityFrameworkCore.csproj" "modules/Sozsoft.Languages/Sozsoft.Languages.EntityFrameworkCore/"
COPY "modules/Sozsoft.MailQueue/Sozsoft.MailQueue.csproj" "modules/Sozsoft.MailQueue/" COPY "modules/Sozsoft.MailQueue/Sozsoft.MailQueue.csproj" "modules/Sozsoft.MailQueue/"
COPY "modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/Sozsoft.Mcp.Application.csproj" "modules/Sozsoft.Mcp/Sozsoft.Mcp.Application/"
COPY "modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/Sozsoft.Mcp.Application.Contracts.csproj" "modules/Sozsoft.Mcp/Sozsoft.Mcp.Application.Contracts/"
COPY "modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/Sozsoft.Mcp.Domain.csproj" "modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain/"
COPY "modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/Sozsoft.Mcp.Domain.Shared.csproj" "modules/Sozsoft.Mcp/Sozsoft.Mcp.Domain.Shared/"
COPY "modules/Sozsoft.Mcp/Sozsoft.Mcp.EntityFrameworkCore/Sozsoft.Mcp.EntityFrameworkCore.csproj" "modules/Sozsoft.Mcp/Sozsoft.Mcp.EntityFrameworkCore/"
COPY "modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/Sozsoft.Mcp.HttpApi.csproj" "modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/"
COPY "modules/Sozsoft.Notifications/Sozsoft.Notifications.Application/Sozsoft.Notifications.Application.csproj" "modules/Sozsoft.Notifications/Sozsoft.Notifications.Application/" COPY "modules/Sozsoft.Notifications/Sozsoft.Notifications.Application/Sozsoft.Notifications.Application.csproj" "modules/Sozsoft.Notifications/Sozsoft.Notifications.Application/"
COPY "modules/Sozsoft.Notifications/Sozsoft.Notifications.Application.Contracts/Sozsoft.Notifications.Application.Contracts.csproj" "modules/Sozsoft.Notifications/Sozsoft.Notifications.Application.Contracts/" COPY "modules/Sozsoft.Notifications/Sozsoft.Notifications.Application.Contracts/Sozsoft.Notifications.Application.Contracts.csproj" "modules/Sozsoft.Notifications/Sozsoft.Notifications.Application.Contracts/"
COPY "modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/Sozsoft.Notifications.Domain.csproj" "modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/" COPY "modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/Sozsoft.Notifications.Domain.csproj" "modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/"

View file

@ -92,144 +92,496 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sozsoft.SqlQueryManager.App
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sozsoft.SqlQueryManager.EntityFrameworkCore", "modules\Sozsoft.SqlQueryManager\Sozsoft.SqlQueryManager.EntityFrameworkCore\Sozsoft.SqlQueryManager.EntityFrameworkCore.csproj", "{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sozsoft.SqlQueryManager.EntityFrameworkCore", "modules\Sozsoft.SqlQueryManager\Sozsoft.SqlQueryManager.EntityFrameworkCore\Sozsoft.SqlQueryManager.EntityFrameworkCore.csproj", "{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sozsoft.Mcp.Domain.Shared", "modules\Sozsoft.Mcp\Sozsoft.Mcp.Domain.Shared\Sozsoft.Mcp.Domain.Shared.csproj", "{D6DE6894-DF5E-4F59-A4DD-145FBEFBDD36}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sozsoft.Mcp.Domain", "modules\Sozsoft.Mcp\Sozsoft.Mcp.Domain\Sozsoft.Mcp.Domain.csproj", "{E99A894F-1731-4A48-B345-1D14F70EA23F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sozsoft.Mcp.Application.Contracts", "modules\Sozsoft.Mcp\Sozsoft.Mcp.Application.Contracts\Sozsoft.Mcp.Application.Contracts.csproj", "{30F4D3DF-D777-40C2-B434-49173AC06A4B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sozsoft.Mcp.Application", "modules\Sozsoft.Mcp\Sozsoft.Mcp.Application\Sozsoft.Mcp.Application.csproj", "{A4BC1880-5492-41FF-81BA-963AC4C60A02}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sozsoft.Mcp.EntityFrameworkCore", "modules\Sozsoft.Mcp\Sozsoft.Mcp.EntityFrameworkCore\Sozsoft.Mcp.EntityFrameworkCore.csproj", "{BE7FDBA3-58CD-41C0-A748-E636DD11F137}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sozsoft.Mcp.HttpApi", "modules\Sozsoft.Mcp\Sozsoft.Mcp.HttpApi\Sozsoft.Mcp.HttpApi.csproj", "{594F56D4-CDDD-451A-B487-D49EEB9F5B98}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Debug|Any CPU.Build.0 = Debug|Any CPU {554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Debug|x64.ActiveCfg = Debug|Any CPU
{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Debug|x64.Build.0 = Debug|Any CPU
{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Debug|x86.ActiveCfg = Debug|Any CPU
{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Debug|x86.Build.0 = Debug|Any CPU
{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Release|Any CPU.ActiveCfg = Release|Any CPU {554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Release|Any CPU.Build.0 = Release|Any CPU {554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Release|Any CPU.Build.0 = Release|Any CPU
{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Release|x64.ActiveCfg = Release|Any CPU
{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Release|x64.Build.0 = Release|Any CPU
{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Release|x86.ActiveCfg = Release|Any CPU
{554AD327-6DBA-4F8F-96F8-81CE7A0C863F}.Release|x86.Build.0 = Release|Any CPU
{1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Debug|Any CPU.Build.0 = Debug|Any CPU {1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Debug|x64.ActiveCfg = Debug|Any CPU
{1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Debug|x64.Build.0 = Debug|Any CPU
{1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Debug|x86.ActiveCfg = Debug|Any CPU
{1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Debug|x86.Build.0 = Debug|Any CPU
{1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Release|Any CPU.ActiveCfg = Release|Any CPU {1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Release|Any CPU.Build.0 = Release|Any CPU {1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Release|Any CPU.Build.0 = Release|Any CPU
{1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Release|x64.ActiveCfg = Release|Any CPU
{1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Release|x64.Build.0 = Release|Any CPU
{1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Release|x86.ActiveCfg = Release|Any CPU
{1A94A50E-06DC-43C1-80B5-B662820EC3EB}.Release|x86.Build.0 = Release|Any CPU
{C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Debug|Any CPU.Build.0 = Debug|Any CPU {C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Debug|x64.ActiveCfg = Debug|Any CPU
{C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Debug|x64.Build.0 = Debug|Any CPU
{C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Debug|x86.ActiveCfg = Debug|Any CPU
{C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Debug|x86.Build.0 = Debug|Any CPU
{C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Release|Any CPU.ActiveCfg = Release|Any CPU {C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Release|Any CPU.Build.0 = Release|Any CPU {C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Release|Any CPU.Build.0 = Release|Any CPU
{C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Release|x64.ActiveCfg = Release|Any CPU
{C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Release|x64.Build.0 = Release|Any CPU
{C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Release|x86.ActiveCfg = Release|Any CPU
{C956DD76-69C8-4A9C-83EA-D17DF83340FD}.Release|x86.Build.0 = Release|Any CPU
{42F719ED-8413-4895-B5B4-5AB56079BC66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {42F719ED-8413-4895-B5B4-5AB56079BC66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{42F719ED-8413-4895-B5B4-5AB56079BC66}.Debug|Any CPU.Build.0 = Debug|Any CPU {42F719ED-8413-4895-B5B4-5AB56079BC66}.Debug|Any CPU.Build.0 = Debug|Any CPU
{42F719ED-8413-4895-B5B4-5AB56079BC66}.Debug|x64.ActiveCfg = Debug|Any CPU
{42F719ED-8413-4895-B5B4-5AB56079BC66}.Debug|x64.Build.0 = Debug|Any CPU
{42F719ED-8413-4895-B5B4-5AB56079BC66}.Debug|x86.ActiveCfg = Debug|Any CPU
{42F719ED-8413-4895-B5B4-5AB56079BC66}.Debug|x86.Build.0 = Debug|Any CPU
{42F719ED-8413-4895-B5B4-5AB56079BC66}.Release|Any CPU.ActiveCfg = Release|Any CPU {42F719ED-8413-4895-B5B4-5AB56079BC66}.Release|Any CPU.ActiveCfg = Release|Any CPU
{42F719ED-8413-4895-B5B4-5AB56079BC66}.Release|Any CPU.Build.0 = Release|Any CPU {42F719ED-8413-4895-B5B4-5AB56079BC66}.Release|Any CPU.Build.0 = Release|Any CPU
{42F719ED-8413-4895-B5B4-5AB56079BC66}.Release|x64.ActiveCfg = Release|Any CPU
{42F719ED-8413-4895-B5B4-5AB56079BC66}.Release|x64.Build.0 = Release|Any CPU
{42F719ED-8413-4895-B5B4-5AB56079BC66}.Release|x86.ActiveCfg = Release|Any CPU
{42F719ED-8413-4895-B5B4-5AB56079BC66}.Release|x86.Build.0 = Release|Any CPU
{520659C8-C734-4298-A3DA-B539DB9DFC0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {520659C8-C734-4298-A3DA-B539DB9DFC0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{520659C8-C734-4298-A3DA-B539DB9DFC0B}.Debug|Any CPU.Build.0 = Debug|Any CPU {520659C8-C734-4298-A3DA-B539DB9DFC0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{520659C8-C734-4298-A3DA-B539DB9DFC0B}.Debug|x64.ActiveCfg = Debug|Any CPU
{520659C8-C734-4298-A3DA-B539DB9DFC0B}.Debug|x64.Build.0 = Debug|Any CPU
{520659C8-C734-4298-A3DA-B539DB9DFC0B}.Debug|x86.ActiveCfg = Debug|Any CPU
{520659C8-C734-4298-A3DA-B539DB9DFC0B}.Debug|x86.Build.0 = Debug|Any CPU
{520659C8-C734-4298-A3DA-B539DB9DFC0B}.Release|Any CPU.ActiveCfg = Release|Any CPU {520659C8-C734-4298-A3DA-B539DB9DFC0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{520659C8-C734-4298-A3DA-B539DB9DFC0B}.Release|Any CPU.Build.0 = Release|Any CPU {520659C8-C734-4298-A3DA-B539DB9DFC0B}.Release|Any CPU.Build.0 = Release|Any CPU
{520659C8-C734-4298-A3DA-B539DB9DFC0B}.Release|x64.ActiveCfg = Release|Any CPU
{520659C8-C734-4298-A3DA-B539DB9DFC0B}.Release|x64.Build.0 = Release|Any CPU
{520659C8-C734-4298-A3DA-B539DB9DFC0B}.Release|x86.ActiveCfg = Release|Any CPU
{520659C8-C734-4298-A3DA-B539DB9DFC0B}.Release|x86.Build.0 = Release|Any CPU
{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Debug|Any CPU.Build.0 = Debug|Any CPU {4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Debug|x64.ActiveCfg = Debug|Any CPU
{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Debug|x64.Build.0 = Debug|Any CPU
{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Debug|x86.ActiveCfg = Debug|Any CPU
{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Debug|x86.Build.0 = Debug|Any CPU
{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Release|Any CPU.ActiveCfg = Release|Any CPU {4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Release|Any CPU.Build.0 = Release|Any CPU {4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Release|Any CPU.Build.0 = Release|Any CPU
{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Release|x64.ActiveCfg = Release|Any CPU
{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Release|x64.Build.0 = Release|Any CPU
{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Release|x86.ActiveCfg = Release|Any CPU
{4164BDF7-F527-4E85-9CE6-E3C2D7426A27}.Release|x86.Build.0 = Release|Any CPU
{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Debug|Any CPU.Build.0 = Debug|Any CPU {3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Debug|x64.ActiveCfg = Debug|Any CPU
{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Debug|x64.Build.0 = Debug|Any CPU
{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Debug|x86.ActiveCfg = Debug|Any CPU
{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Debug|x86.Build.0 = Debug|Any CPU
{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Release|Any CPU.ActiveCfg = Release|Any CPU {3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Release|Any CPU.Build.0 = Release|Any CPU {3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Release|Any CPU.Build.0 = Release|Any CPU
{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Release|x64.ActiveCfg = Release|Any CPU
{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Release|x64.Build.0 = Release|Any CPU
{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Release|x86.ActiveCfg = Release|Any CPU
{3B5A0094-670D-4BB1-BFDD-61B88A8773DC}.Release|x86.Build.0 = Release|Any CPU
{AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Debug|Any CPU.Build.0 = Debug|Any CPU {AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Debug|x64.ActiveCfg = Debug|Any CPU
{AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Debug|x64.Build.0 = Debug|Any CPU
{AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Debug|x86.ActiveCfg = Debug|Any CPU
{AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Debug|x86.Build.0 = Debug|Any CPU
{AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Release|Any CPU.ActiveCfg = Release|Any CPU {AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Release|Any CPU.Build.0 = Release|Any CPU {AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Release|Any CPU.Build.0 = Release|Any CPU
{AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Release|x64.ActiveCfg = Release|Any CPU
{AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Release|x64.Build.0 = Release|Any CPU
{AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Release|x86.ActiveCfg = Release|Any CPU
{AA94D832-1CCC-4715-95A9-A483F23A1A5D}.Release|x86.Build.0 = Release|Any CPU
{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Debug|Any CPU.Build.0 = Debug|Any CPU {748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Debug|x64.ActiveCfg = Debug|Any CPU
{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Debug|x64.Build.0 = Debug|Any CPU
{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Debug|x86.ActiveCfg = Debug|Any CPU
{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Debug|x86.Build.0 = Debug|Any CPU
{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Release|Any CPU.ActiveCfg = Release|Any CPU {748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Release|Any CPU.Build.0 = Release|Any CPU {748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Release|Any CPU.Build.0 = Release|Any CPU
{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Release|x64.ActiveCfg = Release|Any CPU
{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Release|x64.Build.0 = Release|Any CPU
{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Release|x86.ActiveCfg = Release|Any CPU
{748584B1-BA69-4F6A-81AA-F4BDE6BCE29D}.Release|x86.Build.0 = Release|Any CPU
{21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Debug|Any CPU.Build.0 = Debug|Any CPU {21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Debug|x64.ActiveCfg = Debug|Any CPU
{21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Debug|x64.Build.0 = Debug|Any CPU
{21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Debug|x86.ActiveCfg = Debug|Any CPU
{21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Debug|x86.Build.0 = Debug|Any CPU
{21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Release|Any CPU.ActiveCfg = Release|Any CPU {21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Release|Any CPU.Build.0 = Release|Any CPU {21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Release|Any CPU.Build.0 = Release|Any CPU
{21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Release|x64.ActiveCfg = Release|Any CPU
{21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Release|x64.Build.0 = Release|Any CPU
{21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Release|x86.ActiveCfg = Release|Any CPU
{21B52B6A-FC09-4EDA-8A73-9516726BF50B}.Release|x86.Build.0 = Release|Any CPU
{E9D256F2-B12F-4A90-8CF6-B52724716A94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E9D256F2-B12F-4A90-8CF6-B52724716A94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E9D256F2-B12F-4A90-8CF6-B52724716A94}.Debug|Any CPU.Build.0 = Debug|Any CPU {E9D256F2-B12F-4A90-8CF6-B52724716A94}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E9D256F2-B12F-4A90-8CF6-B52724716A94}.Debug|x64.ActiveCfg = Debug|Any CPU
{E9D256F2-B12F-4A90-8CF6-B52724716A94}.Debug|x64.Build.0 = Debug|Any CPU
{E9D256F2-B12F-4A90-8CF6-B52724716A94}.Debug|x86.ActiveCfg = Debug|Any CPU
{E9D256F2-B12F-4A90-8CF6-B52724716A94}.Debug|x86.Build.0 = Debug|Any CPU
{E9D256F2-B12F-4A90-8CF6-B52724716A94}.Release|Any CPU.ActiveCfg = Release|Any CPU {E9D256F2-B12F-4A90-8CF6-B52724716A94}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E9D256F2-B12F-4A90-8CF6-B52724716A94}.Release|Any CPU.Build.0 = Release|Any CPU {E9D256F2-B12F-4A90-8CF6-B52724716A94}.Release|Any CPU.Build.0 = Release|Any CPU
{E9D256F2-B12F-4A90-8CF6-B52724716A94}.Release|x64.ActiveCfg = Release|Any CPU
{E9D256F2-B12F-4A90-8CF6-B52724716A94}.Release|x64.Build.0 = Release|Any CPU
{E9D256F2-B12F-4A90-8CF6-B52724716A94}.Release|x86.ActiveCfg = Release|Any CPU
{E9D256F2-B12F-4A90-8CF6-B52724716A94}.Release|x86.Build.0 = Release|Any CPU
{F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Debug|Any CPU.Build.0 = Debug|Any CPU {F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Debug|x64.ActiveCfg = Debug|Any CPU
{F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Debug|x64.Build.0 = Debug|Any CPU
{F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Debug|x86.ActiveCfg = Debug|Any CPU
{F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Debug|x86.Build.0 = Debug|Any CPU
{F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Release|Any CPU.ActiveCfg = Release|Any CPU {F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Release|Any CPU.Build.0 = Release|Any CPU {F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Release|Any CPU.Build.0 = Release|Any CPU
{F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Release|x64.ActiveCfg = Release|Any CPU
{F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Release|x64.Build.0 = Release|Any CPU
{F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Release|x86.ActiveCfg = Release|Any CPU
{F1EC497F-D361-4927-AFE6-156A0F7D8A1C}.Release|x86.Build.0 = Release|Any CPU
{9020ECD8-81D6-4604-A379-260796918C28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9020ECD8-81D6-4604-A379-260796918C28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9020ECD8-81D6-4604-A379-260796918C28}.Debug|Any CPU.Build.0 = Debug|Any CPU {9020ECD8-81D6-4604-A379-260796918C28}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9020ECD8-81D6-4604-A379-260796918C28}.Debug|x64.ActiveCfg = Debug|Any CPU
{9020ECD8-81D6-4604-A379-260796918C28}.Debug|x64.Build.0 = Debug|Any CPU
{9020ECD8-81D6-4604-A379-260796918C28}.Debug|x86.ActiveCfg = Debug|Any CPU
{9020ECD8-81D6-4604-A379-260796918C28}.Debug|x86.Build.0 = Debug|Any CPU
{9020ECD8-81D6-4604-A379-260796918C28}.Release|Any CPU.ActiveCfg = Release|Any CPU {9020ECD8-81D6-4604-A379-260796918C28}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9020ECD8-81D6-4604-A379-260796918C28}.Release|Any CPU.Build.0 = Release|Any CPU {9020ECD8-81D6-4604-A379-260796918C28}.Release|Any CPU.Build.0 = Release|Any CPU
{9020ECD8-81D6-4604-A379-260796918C28}.Release|x64.ActiveCfg = Release|Any CPU
{9020ECD8-81D6-4604-A379-260796918C28}.Release|x64.Build.0 = Release|Any CPU
{9020ECD8-81D6-4604-A379-260796918C28}.Release|x86.ActiveCfg = Release|Any CPU
{9020ECD8-81D6-4604-A379-260796918C28}.Release|x86.Build.0 = Release|Any CPU
{A3933E06-9DFE-480D-85C4-66F28F076164}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A3933E06-9DFE-480D-85C4-66F28F076164}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A3933E06-9DFE-480D-85C4-66F28F076164}.Debug|Any CPU.Build.0 = Debug|Any CPU {A3933E06-9DFE-480D-85C4-66F28F076164}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A3933E06-9DFE-480D-85C4-66F28F076164}.Debug|x64.ActiveCfg = Debug|Any CPU
{A3933E06-9DFE-480D-85C4-66F28F076164}.Debug|x64.Build.0 = Debug|Any CPU
{A3933E06-9DFE-480D-85C4-66F28F076164}.Debug|x86.ActiveCfg = Debug|Any CPU
{A3933E06-9DFE-480D-85C4-66F28F076164}.Debug|x86.Build.0 = Debug|Any CPU
{A3933E06-9DFE-480D-85C4-66F28F076164}.Release|Any CPU.ActiveCfg = Release|Any CPU {A3933E06-9DFE-480D-85C4-66F28F076164}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A3933E06-9DFE-480D-85C4-66F28F076164}.Release|Any CPU.Build.0 = Release|Any CPU {A3933E06-9DFE-480D-85C4-66F28F076164}.Release|Any CPU.Build.0 = Release|Any CPU
{A3933E06-9DFE-480D-85C4-66F28F076164}.Release|x64.ActiveCfg = Release|Any CPU
{A3933E06-9DFE-480D-85C4-66F28F076164}.Release|x64.Build.0 = Release|Any CPU
{A3933E06-9DFE-480D-85C4-66F28F076164}.Release|x86.ActiveCfg = Release|Any CPU
{A3933E06-9DFE-480D-85C4-66F28F076164}.Release|x86.Build.0 = Release|Any CPU
{556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Debug|Any CPU.Build.0 = Debug|Any CPU {556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Debug|x64.ActiveCfg = Debug|Any CPU
{556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Debug|x64.Build.0 = Debug|Any CPU
{556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Debug|x86.ActiveCfg = Debug|Any CPU
{556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Debug|x86.Build.0 = Debug|Any CPU
{556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Release|Any CPU.ActiveCfg = Release|Any CPU {556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Release|Any CPU.Build.0 = Release|Any CPU {556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Release|Any CPU.Build.0 = Release|Any CPU
{556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Release|x64.ActiveCfg = Release|Any CPU
{556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Release|x64.Build.0 = Release|Any CPU
{556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Release|x86.ActiveCfg = Release|Any CPU
{556AC2C2-9765-4B3D-8F38-9EAF3DA61AC5}.Release|x86.Build.0 = Release|Any CPU
{5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Debug|Any CPU.Build.0 = Debug|Any CPU {5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Debug|x64.ActiveCfg = Debug|Any CPU
{5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Debug|x64.Build.0 = Debug|Any CPU
{5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Debug|x86.ActiveCfg = Debug|Any CPU
{5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Debug|x86.Build.0 = Debug|Any CPU
{5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Release|Any CPU.ActiveCfg = Release|Any CPU {5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Release|Any CPU.Build.0 = Release|Any CPU {5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Release|Any CPU.Build.0 = Release|Any CPU
{5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Release|x64.ActiveCfg = Release|Any CPU
{5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Release|x64.Build.0 = Release|Any CPU
{5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Release|x86.ActiveCfg = Release|Any CPU
{5F288ACC-A9CF-470B-BB68-B1C8B102CDDD}.Release|x86.Build.0 = Release|Any CPU
{A926F04F-631B-4A05-853C-6C76555BF26A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A926F04F-631B-4A05-853C-6C76555BF26A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A926F04F-631B-4A05-853C-6C76555BF26A}.Debug|Any CPU.Build.0 = Debug|Any CPU {A926F04F-631B-4A05-853C-6C76555BF26A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A926F04F-631B-4A05-853C-6C76555BF26A}.Debug|x64.ActiveCfg = Debug|Any CPU
{A926F04F-631B-4A05-853C-6C76555BF26A}.Debug|x64.Build.0 = Debug|Any CPU
{A926F04F-631B-4A05-853C-6C76555BF26A}.Debug|x86.ActiveCfg = Debug|Any CPU
{A926F04F-631B-4A05-853C-6C76555BF26A}.Debug|x86.Build.0 = Debug|Any CPU
{A926F04F-631B-4A05-853C-6C76555BF26A}.Release|Any CPU.ActiveCfg = Release|Any CPU {A926F04F-631B-4A05-853C-6C76555BF26A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A926F04F-631B-4A05-853C-6C76555BF26A}.Release|Any CPU.Build.0 = Release|Any CPU {A926F04F-631B-4A05-853C-6C76555BF26A}.Release|Any CPU.Build.0 = Release|Any CPU
{A926F04F-631B-4A05-853C-6C76555BF26A}.Release|x64.ActiveCfg = Release|Any CPU
{A926F04F-631B-4A05-853C-6C76555BF26A}.Release|x64.Build.0 = Release|Any CPU
{A926F04F-631B-4A05-853C-6C76555BF26A}.Release|x86.ActiveCfg = Release|Any CPU
{A926F04F-631B-4A05-853C-6C76555BF26A}.Release|x86.Build.0 = Release|Any CPU
{E14E0128-D4FF-462D-B684-7238EA876D69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E14E0128-D4FF-462D-B684-7238EA876D69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E14E0128-D4FF-462D-B684-7238EA876D69}.Debug|Any CPU.Build.0 = Debug|Any CPU {E14E0128-D4FF-462D-B684-7238EA876D69}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E14E0128-D4FF-462D-B684-7238EA876D69}.Debug|x64.ActiveCfg = Debug|Any CPU
{E14E0128-D4FF-462D-B684-7238EA876D69}.Debug|x64.Build.0 = Debug|Any CPU
{E14E0128-D4FF-462D-B684-7238EA876D69}.Debug|x86.ActiveCfg = Debug|Any CPU
{E14E0128-D4FF-462D-B684-7238EA876D69}.Debug|x86.Build.0 = Debug|Any CPU
{E14E0128-D4FF-462D-B684-7238EA876D69}.Release|Any CPU.ActiveCfg = Release|Any CPU {E14E0128-D4FF-462D-B684-7238EA876D69}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E14E0128-D4FF-462D-B684-7238EA876D69}.Release|Any CPU.Build.0 = Release|Any CPU {E14E0128-D4FF-462D-B684-7238EA876D69}.Release|Any CPU.Build.0 = Release|Any CPU
{E14E0128-D4FF-462D-B684-7238EA876D69}.Release|x64.ActiveCfg = Release|Any CPU
{E14E0128-D4FF-462D-B684-7238EA876D69}.Release|x64.Build.0 = Release|Any CPU
{E14E0128-D4FF-462D-B684-7238EA876D69}.Release|x86.ActiveCfg = Release|Any CPU
{E14E0128-D4FF-462D-B684-7238EA876D69}.Release|x86.Build.0 = Release|Any CPU
{DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Debug|Any CPU.Build.0 = Debug|Any CPU {DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Debug|x64.ActiveCfg = Debug|Any CPU
{DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Debug|x64.Build.0 = Debug|Any CPU
{DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Debug|x86.ActiveCfg = Debug|Any CPU
{DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Debug|x86.Build.0 = Debug|Any CPU
{DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Release|Any CPU.ActiveCfg = Release|Any CPU {DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Release|Any CPU.Build.0 = Release|Any CPU {DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Release|Any CPU.Build.0 = Release|Any CPU
{DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Release|x64.ActiveCfg = Release|Any CPU
{DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Release|x64.Build.0 = Release|Any CPU
{DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Release|x86.ActiveCfg = Release|Any CPU
{DBFD0F4B-5592-4E34-8624-04F7540E34B9}.Release|x86.Build.0 = Release|Any CPU
{C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Debug|Any CPU.Build.0 = Debug|Any CPU {C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Debug|x64.ActiveCfg = Debug|Any CPU
{C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Debug|x64.Build.0 = Debug|Any CPU
{C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Debug|x86.ActiveCfg = Debug|Any CPU
{C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Debug|x86.Build.0 = Debug|Any CPU
{C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Release|Any CPU.ActiveCfg = Release|Any CPU {C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Release|Any CPU.Build.0 = Release|Any CPU {C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Release|Any CPU.Build.0 = Release|Any CPU
{C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Release|x64.ActiveCfg = Release|Any CPU
{C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Release|x64.Build.0 = Release|Any CPU
{C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Release|x86.ActiveCfg = Release|Any CPU
{C2F5630E-A6E2-48D5-B182-5FE9278D3E37}.Release|x86.Build.0 = Release|Any CPU
{3E13749E-82D1-434B-B867-F7B094B66065}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3E13749E-82D1-434B-B867-F7B094B66065}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3E13749E-82D1-434B-B867-F7B094B66065}.Debug|Any CPU.Build.0 = Debug|Any CPU {3E13749E-82D1-434B-B867-F7B094B66065}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3E13749E-82D1-434B-B867-F7B094B66065}.Debug|x64.ActiveCfg = Debug|Any CPU
{3E13749E-82D1-434B-B867-F7B094B66065}.Debug|x64.Build.0 = Debug|Any CPU
{3E13749E-82D1-434B-B867-F7B094B66065}.Debug|x86.ActiveCfg = Debug|Any CPU
{3E13749E-82D1-434B-B867-F7B094B66065}.Debug|x86.Build.0 = Debug|Any CPU
{3E13749E-82D1-434B-B867-F7B094B66065}.Release|Any CPU.ActiveCfg = Release|Any CPU {3E13749E-82D1-434B-B867-F7B094B66065}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3E13749E-82D1-434B-B867-F7B094B66065}.Release|Any CPU.Build.0 = Release|Any CPU {3E13749E-82D1-434B-B867-F7B094B66065}.Release|Any CPU.Build.0 = Release|Any CPU
{3E13749E-82D1-434B-B867-F7B094B66065}.Release|x64.ActiveCfg = Release|Any CPU
{3E13749E-82D1-434B-B867-F7B094B66065}.Release|x64.Build.0 = Release|Any CPU
{3E13749E-82D1-434B-B867-F7B094B66065}.Release|x86.ActiveCfg = Release|Any CPU
{3E13749E-82D1-434B-B867-F7B094B66065}.Release|x86.Build.0 = Release|Any CPU
{620D9F08-EB9F-469F-806A-64E915437154}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {620D9F08-EB9F-469F-806A-64E915437154}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{620D9F08-EB9F-469F-806A-64E915437154}.Debug|Any CPU.Build.0 = Debug|Any CPU {620D9F08-EB9F-469F-806A-64E915437154}.Debug|Any CPU.Build.0 = Debug|Any CPU
{620D9F08-EB9F-469F-806A-64E915437154}.Debug|x64.ActiveCfg = Debug|Any CPU
{620D9F08-EB9F-469F-806A-64E915437154}.Debug|x64.Build.0 = Debug|Any CPU
{620D9F08-EB9F-469F-806A-64E915437154}.Debug|x86.ActiveCfg = Debug|Any CPU
{620D9F08-EB9F-469F-806A-64E915437154}.Debug|x86.Build.0 = Debug|Any CPU
{620D9F08-EB9F-469F-806A-64E915437154}.Release|Any CPU.ActiveCfg = Release|Any CPU {620D9F08-EB9F-469F-806A-64E915437154}.Release|Any CPU.ActiveCfg = Release|Any CPU
{620D9F08-EB9F-469F-806A-64E915437154}.Release|Any CPU.Build.0 = Release|Any CPU {620D9F08-EB9F-469F-806A-64E915437154}.Release|Any CPU.Build.0 = Release|Any CPU
{620D9F08-EB9F-469F-806A-64E915437154}.Release|x64.ActiveCfg = Release|Any CPU
{620D9F08-EB9F-469F-806A-64E915437154}.Release|x64.Build.0 = Release|Any CPU
{620D9F08-EB9F-469F-806A-64E915437154}.Release|x86.ActiveCfg = Release|Any CPU
{620D9F08-EB9F-469F-806A-64E915437154}.Release|x86.Build.0 = Release|Any CPU
{088B3139-68D3-4A5F-B159-0C8FE94CD969}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {088B3139-68D3-4A5F-B159-0C8FE94CD969}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{088B3139-68D3-4A5F-B159-0C8FE94CD969}.Debug|Any CPU.Build.0 = Debug|Any CPU {088B3139-68D3-4A5F-B159-0C8FE94CD969}.Debug|Any CPU.Build.0 = Debug|Any CPU
{088B3139-68D3-4A5F-B159-0C8FE94CD969}.Debug|x64.ActiveCfg = Debug|Any CPU
{088B3139-68D3-4A5F-B159-0C8FE94CD969}.Debug|x64.Build.0 = Debug|Any CPU
{088B3139-68D3-4A5F-B159-0C8FE94CD969}.Debug|x86.ActiveCfg = Debug|Any CPU
{088B3139-68D3-4A5F-B159-0C8FE94CD969}.Debug|x86.Build.0 = Debug|Any CPU
{088B3139-68D3-4A5F-B159-0C8FE94CD969}.Release|Any CPU.ActiveCfg = Release|Any CPU {088B3139-68D3-4A5F-B159-0C8FE94CD969}.Release|Any CPU.ActiveCfg = Release|Any CPU
{088B3139-68D3-4A5F-B159-0C8FE94CD969}.Release|Any CPU.Build.0 = Release|Any CPU {088B3139-68D3-4A5F-B159-0C8FE94CD969}.Release|Any CPU.Build.0 = Release|Any CPU
{088B3139-68D3-4A5F-B159-0C8FE94CD969}.Release|x64.ActiveCfg = Release|Any CPU
{088B3139-68D3-4A5F-B159-0C8FE94CD969}.Release|x64.Build.0 = Release|Any CPU
{088B3139-68D3-4A5F-B159-0C8FE94CD969}.Release|x86.ActiveCfg = Release|Any CPU
{088B3139-68D3-4A5F-B159-0C8FE94CD969}.Release|x86.Build.0 = Release|Any CPU
{A972109D-D974-4BB2-BA01-72BD845ACBBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A972109D-D974-4BB2-BA01-72BD845ACBBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A972109D-D974-4BB2-BA01-72BD845ACBBA}.Debug|Any CPU.Build.0 = Debug|Any CPU {A972109D-D974-4BB2-BA01-72BD845ACBBA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A972109D-D974-4BB2-BA01-72BD845ACBBA}.Debug|x64.ActiveCfg = Debug|Any CPU
{A972109D-D974-4BB2-BA01-72BD845ACBBA}.Debug|x64.Build.0 = Debug|Any CPU
{A972109D-D974-4BB2-BA01-72BD845ACBBA}.Debug|x86.ActiveCfg = Debug|Any CPU
{A972109D-D974-4BB2-BA01-72BD845ACBBA}.Debug|x86.Build.0 = Debug|Any CPU
{A972109D-D974-4BB2-BA01-72BD845ACBBA}.Release|Any CPU.ActiveCfg = Release|Any CPU {A972109D-D974-4BB2-BA01-72BD845ACBBA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A972109D-D974-4BB2-BA01-72BD845ACBBA}.Release|Any CPU.Build.0 = Release|Any CPU {A972109D-D974-4BB2-BA01-72BD845ACBBA}.Release|Any CPU.Build.0 = Release|Any CPU
{A972109D-D974-4BB2-BA01-72BD845ACBBA}.Release|x64.ActiveCfg = Release|Any CPU
{A972109D-D974-4BB2-BA01-72BD845ACBBA}.Release|x64.Build.0 = Release|Any CPU
{A972109D-D974-4BB2-BA01-72BD845ACBBA}.Release|x86.ActiveCfg = Release|Any CPU
{A972109D-D974-4BB2-BA01-72BD845ACBBA}.Release|x86.Build.0 = Release|Any CPU
{297B7268-DEF3-4669-98DF-78FA08619EDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {297B7268-DEF3-4669-98DF-78FA08619EDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{297B7268-DEF3-4669-98DF-78FA08619EDF}.Debug|Any CPU.Build.0 = Debug|Any CPU {297B7268-DEF3-4669-98DF-78FA08619EDF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{297B7268-DEF3-4669-98DF-78FA08619EDF}.Debug|x64.ActiveCfg = Debug|Any CPU
{297B7268-DEF3-4669-98DF-78FA08619EDF}.Debug|x64.Build.0 = Debug|Any CPU
{297B7268-DEF3-4669-98DF-78FA08619EDF}.Debug|x86.ActiveCfg = Debug|Any CPU
{297B7268-DEF3-4669-98DF-78FA08619EDF}.Debug|x86.Build.0 = Debug|Any CPU
{297B7268-DEF3-4669-98DF-78FA08619EDF}.Release|Any CPU.ActiveCfg = Release|Any CPU {297B7268-DEF3-4669-98DF-78FA08619EDF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{297B7268-DEF3-4669-98DF-78FA08619EDF}.Release|Any CPU.Build.0 = Release|Any CPU {297B7268-DEF3-4669-98DF-78FA08619EDF}.Release|Any CPU.Build.0 = Release|Any CPU
{297B7268-DEF3-4669-98DF-78FA08619EDF}.Release|x64.ActiveCfg = Release|Any CPU
{297B7268-DEF3-4669-98DF-78FA08619EDF}.Release|x64.Build.0 = Release|Any CPU
{297B7268-DEF3-4669-98DF-78FA08619EDF}.Release|x86.ActiveCfg = Release|Any CPU
{297B7268-DEF3-4669-98DF-78FA08619EDF}.Release|x86.Build.0 = Release|Any CPU
{631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Debug|Any CPU.Build.0 = Debug|Any CPU {631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Debug|x64.ActiveCfg = Debug|Any CPU
{631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Debug|x64.Build.0 = Debug|Any CPU
{631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Debug|x86.ActiveCfg = Debug|Any CPU
{631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Debug|x86.Build.0 = Debug|Any CPU
{631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Release|Any CPU.ActiveCfg = Release|Any CPU {631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Release|Any CPU.Build.0 = Release|Any CPU {631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Release|Any CPU.Build.0 = Release|Any CPU
{631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Release|x64.ActiveCfg = Release|Any CPU
{631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Release|x64.Build.0 = Release|Any CPU
{631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Release|x86.ActiveCfg = Release|Any CPU
{631092C7-B59D-4EA7-92D0-5E181AB4F9F6}.Release|x86.Build.0 = Release|Any CPU
{23659070-58F7-403B-8973-B2E20B5E9BE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {23659070-58F7-403B-8973-B2E20B5E9BE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{23659070-58F7-403B-8973-B2E20B5E9BE1}.Debug|Any CPU.Build.0 = Debug|Any CPU {23659070-58F7-403B-8973-B2E20B5E9BE1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{23659070-58F7-403B-8973-B2E20B5E9BE1}.Debug|x64.ActiveCfg = Debug|Any CPU
{23659070-58F7-403B-8973-B2E20B5E9BE1}.Debug|x64.Build.0 = Debug|Any CPU
{23659070-58F7-403B-8973-B2E20B5E9BE1}.Debug|x86.ActiveCfg = Debug|Any CPU
{23659070-58F7-403B-8973-B2E20B5E9BE1}.Debug|x86.Build.0 = Debug|Any CPU
{23659070-58F7-403B-8973-B2E20B5E9BE1}.Release|Any CPU.ActiveCfg = Release|Any CPU {23659070-58F7-403B-8973-B2E20B5E9BE1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{23659070-58F7-403B-8973-B2E20B5E9BE1}.Release|Any CPU.Build.0 = Release|Any CPU {23659070-58F7-403B-8973-B2E20B5E9BE1}.Release|Any CPU.Build.0 = Release|Any CPU
{23659070-58F7-403B-8973-B2E20B5E9BE1}.Release|x64.ActiveCfg = Release|Any CPU
{23659070-58F7-403B-8973-B2E20B5E9BE1}.Release|x64.Build.0 = Release|Any CPU
{23659070-58F7-403B-8973-B2E20B5E9BE1}.Release|x86.ActiveCfg = Release|Any CPU
{23659070-58F7-403B-8973-B2E20B5E9BE1}.Release|x86.Build.0 = Release|Any CPU
{D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Debug|Any CPU.Build.0 = Debug|Any CPU {D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Debug|x64.ActiveCfg = Debug|Any CPU
{D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Debug|x64.Build.0 = Debug|Any CPU
{D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Debug|x86.ActiveCfg = Debug|Any CPU
{D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Debug|x86.Build.0 = Debug|Any CPU
{D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Release|Any CPU.ActiveCfg = Release|Any CPU {D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Release|Any CPU.Build.0 = Release|Any CPU {D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Release|Any CPU.Build.0 = Release|Any CPU
{D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Release|x64.ActiveCfg = Release|Any CPU
{D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Release|x64.Build.0 = Release|Any CPU
{D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Release|x86.ActiveCfg = Release|Any CPU
{D9E0D333-60F3-493F-A5B2-5758ACA42A17}.Release|x86.Build.0 = Release|Any CPU
{10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Debug|Any CPU.Build.0 = Debug|Any CPU {10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Debug|Any CPU.Build.0 = Debug|Any CPU
{10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Debug|x64.ActiveCfg = Debug|Any CPU
{10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Debug|x64.Build.0 = Debug|Any CPU
{10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Debug|x86.ActiveCfg = Debug|Any CPU
{10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Debug|x86.Build.0 = Debug|Any CPU
{10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Release|Any CPU.ActiveCfg = Release|Any CPU {10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Release|Any CPU.ActiveCfg = Release|Any CPU
{10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Release|Any CPU.Build.0 = Release|Any CPU {10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Release|Any CPU.Build.0 = Release|Any CPU
{10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Release|x64.ActiveCfg = Release|Any CPU
{10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Release|x64.Build.0 = Release|Any CPU
{10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Release|x86.ActiveCfg = Release|Any CPU
{10D71F44-C9FD-41F2-8F1A-D93FAE3CE696}.Release|x86.Build.0 = Release|Any CPU
{8730045F-91F5-4438-8772-C6E31E89AACC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8730045F-91F5-4438-8772-C6E31E89AACC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8730045F-91F5-4438-8772-C6E31E89AACC}.Debug|Any CPU.Build.0 = Debug|Any CPU {8730045F-91F5-4438-8772-C6E31E89AACC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8730045F-91F5-4438-8772-C6E31E89AACC}.Debug|x64.ActiveCfg = Debug|Any CPU
{8730045F-91F5-4438-8772-C6E31E89AACC}.Debug|x64.Build.0 = Debug|Any CPU
{8730045F-91F5-4438-8772-C6E31E89AACC}.Debug|x86.ActiveCfg = Debug|Any CPU
{8730045F-91F5-4438-8772-C6E31E89AACC}.Debug|x86.Build.0 = Debug|Any CPU
{8730045F-91F5-4438-8772-C6E31E89AACC}.Release|Any CPU.ActiveCfg = Release|Any CPU {8730045F-91F5-4438-8772-C6E31E89AACC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8730045F-91F5-4438-8772-C6E31E89AACC}.Release|Any CPU.Build.0 = Release|Any CPU {8730045F-91F5-4438-8772-C6E31E89AACC}.Release|Any CPU.Build.0 = Release|Any CPU
{8730045F-91F5-4438-8772-C6E31E89AACC}.Release|x64.ActiveCfg = Release|Any CPU
{8730045F-91F5-4438-8772-C6E31E89AACC}.Release|x64.Build.0 = Release|Any CPU
{8730045F-91F5-4438-8772-C6E31E89AACC}.Release|x86.ActiveCfg = Release|Any CPU
{8730045F-91F5-4438-8772-C6E31E89AACC}.Release|x86.Build.0 = Release|Any CPU
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Debug|Any CPU.Build.0 = Debug|Any CPU {B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Debug|x64.ActiveCfg = Debug|Any CPU
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Debug|x64.Build.0 = Debug|Any CPU
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Debug|x86.ActiveCfg = Debug|Any CPU
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Debug|x86.Build.0 = Debug|Any CPU
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Release|Any CPU.ActiveCfg = Release|Any CPU {B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Release|Any CPU.Build.0 = Release|Any CPU {B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Release|Any CPU.Build.0 = Release|Any CPU
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Release|x64.ActiveCfg = Release|Any CPU
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Release|x64.Build.0 = Release|Any CPU
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Release|x86.ActiveCfg = Release|Any CPU
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4}.Release|x86.Build.0 = Release|Any CPU
{ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Debug|Any CPU.Build.0 = Debug|Any CPU {ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Debug|x64.ActiveCfg = Debug|Any CPU
{ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Debug|x64.Build.0 = Debug|Any CPU
{ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Debug|x86.ActiveCfg = Debug|Any CPU
{ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Debug|x86.Build.0 = Debug|Any CPU
{ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Release|Any CPU.ActiveCfg = Release|Any CPU {ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Release|Any CPU.Build.0 = Release|Any CPU {ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Release|Any CPU.Build.0 = Release|Any CPU
{ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Release|x64.ActiveCfg = Release|Any CPU
{ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Release|x64.Build.0 = Release|Any CPU
{ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Release|x86.ActiveCfg = Release|Any CPU
{ED9C639A-A706-4ECB-9638-A15B3681BDEC}.Release|x86.Build.0 = Release|Any CPU
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Debug|Any CPU.Build.0 = Debug|Any CPU {1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Debug|x64.ActiveCfg = Debug|Any CPU
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Debug|x64.Build.0 = Debug|Any CPU
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Debug|x86.ActiveCfg = Debug|Any CPU
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Debug|x86.Build.0 = Debug|Any CPU
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Release|Any CPU.ActiveCfg = Release|Any CPU {1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Release|Any CPU.Build.0 = Release|Any CPU {1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Release|Any CPU.Build.0 = Release|Any CPU
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Release|x64.ActiveCfg = Release|Any CPU
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Release|x64.Build.0 = Release|Any CPU
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Release|x86.ActiveCfg = Release|Any CPU
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6}.Release|x86.Build.0 = Release|Any CPU
{D6DE6894-DF5E-4F59-A4DD-145FBEFBDD36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D6DE6894-DF5E-4F59-A4DD-145FBEFBDD36}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D6DE6894-DF5E-4F59-A4DD-145FBEFBDD36}.Debug|x64.ActiveCfg = Debug|Any CPU
{D6DE6894-DF5E-4F59-A4DD-145FBEFBDD36}.Debug|x64.Build.0 = Debug|Any CPU
{D6DE6894-DF5E-4F59-A4DD-145FBEFBDD36}.Debug|x86.ActiveCfg = Debug|Any CPU
{D6DE6894-DF5E-4F59-A4DD-145FBEFBDD36}.Debug|x86.Build.0 = Debug|Any CPU
{D6DE6894-DF5E-4F59-A4DD-145FBEFBDD36}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D6DE6894-DF5E-4F59-A4DD-145FBEFBDD36}.Release|Any CPU.Build.0 = Release|Any CPU
{D6DE6894-DF5E-4F59-A4DD-145FBEFBDD36}.Release|x64.ActiveCfg = Release|Any CPU
{D6DE6894-DF5E-4F59-A4DD-145FBEFBDD36}.Release|x64.Build.0 = Release|Any CPU
{D6DE6894-DF5E-4F59-A4DD-145FBEFBDD36}.Release|x86.ActiveCfg = Release|Any CPU
{D6DE6894-DF5E-4F59-A4DD-145FBEFBDD36}.Release|x86.Build.0 = Release|Any CPU
{E99A894F-1731-4A48-B345-1D14F70EA23F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E99A894F-1731-4A48-B345-1D14F70EA23F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E99A894F-1731-4A48-B345-1D14F70EA23F}.Debug|x64.ActiveCfg = Debug|Any CPU
{E99A894F-1731-4A48-B345-1D14F70EA23F}.Debug|x64.Build.0 = Debug|Any CPU
{E99A894F-1731-4A48-B345-1D14F70EA23F}.Debug|x86.ActiveCfg = Debug|Any CPU
{E99A894F-1731-4A48-B345-1D14F70EA23F}.Debug|x86.Build.0 = Debug|Any CPU
{E99A894F-1731-4A48-B345-1D14F70EA23F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E99A894F-1731-4A48-B345-1D14F70EA23F}.Release|Any CPU.Build.0 = Release|Any CPU
{E99A894F-1731-4A48-B345-1D14F70EA23F}.Release|x64.ActiveCfg = Release|Any CPU
{E99A894F-1731-4A48-B345-1D14F70EA23F}.Release|x64.Build.0 = Release|Any CPU
{E99A894F-1731-4A48-B345-1D14F70EA23F}.Release|x86.ActiveCfg = Release|Any CPU
{E99A894F-1731-4A48-B345-1D14F70EA23F}.Release|x86.Build.0 = Release|Any CPU
{30F4D3DF-D777-40C2-B434-49173AC06A4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{30F4D3DF-D777-40C2-B434-49173AC06A4B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{30F4D3DF-D777-40C2-B434-49173AC06A4B}.Debug|x64.ActiveCfg = Debug|Any CPU
{30F4D3DF-D777-40C2-B434-49173AC06A4B}.Debug|x64.Build.0 = Debug|Any CPU
{30F4D3DF-D777-40C2-B434-49173AC06A4B}.Debug|x86.ActiveCfg = Debug|Any CPU
{30F4D3DF-D777-40C2-B434-49173AC06A4B}.Debug|x86.Build.0 = Debug|Any CPU
{30F4D3DF-D777-40C2-B434-49173AC06A4B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{30F4D3DF-D777-40C2-B434-49173AC06A4B}.Release|Any CPU.Build.0 = Release|Any CPU
{30F4D3DF-D777-40C2-B434-49173AC06A4B}.Release|x64.ActiveCfg = Release|Any CPU
{30F4D3DF-D777-40C2-B434-49173AC06A4B}.Release|x64.Build.0 = Release|Any CPU
{30F4D3DF-D777-40C2-B434-49173AC06A4B}.Release|x86.ActiveCfg = Release|Any CPU
{30F4D3DF-D777-40C2-B434-49173AC06A4B}.Release|x86.Build.0 = Release|Any CPU
{A4BC1880-5492-41FF-81BA-963AC4C60A02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A4BC1880-5492-41FF-81BA-963AC4C60A02}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A4BC1880-5492-41FF-81BA-963AC4C60A02}.Debug|x64.ActiveCfg = Debug|Any CPU
{A4BC1880-5492-41FF-81BA-963AC4C60A02}.Debug|x64.Build.0 = Debug|Any CPU
{A4BC1880-5492-41FF-81BA-963AC4C60A02}.Debug|x86.ActiveCfg = Debug|Any CPU
{A4BC1880-5492-41FF-81BA-963AC4C60A02}.Debug|x86.Build.0 = Debug|Any CPU
{A4BC1880-5492-41FF-81BA-963AC4C60A02}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A4BC1880-5492-41FF-81BA-963AC4C60A02}.Release|Any CPU.Build.0 = Release|Any CPU
{A4BC1880-5492-41FF-81BA-963AC4C60A02}.Release|x64.ActiveCfg = Release|Any CPU
{A4BC1880-5492-41FF-81BA-963AC4C60A02}.Release|x64.Build.0 = Release|Any CPU
{A4BC1880-5492-41FF-81BA-963AC4C60A02}.Release|x86.ActiveCfg = Release|Any CPU
{A4BC1880-5492-41FF-81BA-963AC4C60A02}.Release|x86.Build.0 = Release|Any CPU
{BE7FDBA3-58CD-41C0-A748-E636DD11F137}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BE7FDBA3-58CD-41C0-A748-E636DD11F137}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BE7FDBA3-58CD-41C0-A748-E636DD11F137}.Debug|x64.ActiveCfg = Debug|Any CPU
{BE7FDBA3-58CD-41C0-A748-E636DD11F137}.Debug|x64.Build.0 = Debug|Any CPU
{BE7FDBA3-58CD-41C0-A748-E636DD11F137}.Debug|x86.ActiveCfg = Debug|Any CPU
{BE7FDBA3-58CD-41C0-A748-E636DD11F137}.Debug|x86.Build.0 = Debug|Any CPU
{BE7FDBA3-58CD-41C0-A748-E636DD11F137}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BE7FDBA3-58CD-41C0-A748-E636DD11F137}.Release|Any CPU.Build.0 = Release|Any CPU
{BE7FDBA3-58CD-41C0-A748-E636DD11F137}.Release|x64.ActiveCfg = Release|Any CPU
{BE7FDBA3-58CD-41C0-A748-E636DD11F137}.Release|x64.Build.0 = Release|Any CPU
{BE7FDBA3-58CD-41C0-A748-E636DD11F137}.Release|x86.ActiveCfg = Release|Any CPU
{BE7FDBA3-58CD-41C0-A748-E636DD11F137}.Release|x86.Build.0 = Release|Any CPU
{594F56D4-CDDD-451A-B487-D49EEB9F5B98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{594F56D4-CDDD-451A-B487-D49EEB9F5B98}.Debug|Any CPU.Build.0 = Debug|Any CPU
{594F56D4-CDDD-451A-B487-D49EEB9F5B98}.Debug|x64.ActiveCfg = Debug|Any CPU
{594F56D4-CDDD-451A-B487-D49EEB9F5B98}.Debug|x64.Build.0 = Debug|Any CPU
{594F56D4-CDDD-451A-B487-D49EEB9F5B98}.Debug|x86.ActiveCfg = Debug|Any CPU
{594F56D4-CDDD-451A-B487-D49EEB9F5B98}.Debug|x86.Build.0 = Debug|Any CPU
{594F56D4-CDDD-451A-B487-D49EEB9F5B98}.Release|Any CPU.ActiveCfg = Release|Any CPU
{594F56D4-CDDD-451A-B487-D49EEB9F5B98}.Release|Any CPU.Build.0 = Release|Any CPU
{594F56D4-CDDD-451A-B487-D49EEB9F5B98}.Release|x64.ActiveCfg = Release|Any CPU
{594F56D4-CDDD-451A-B487-D49EEB9F5B98}.Release|x64.Build.0 = Release|Any CPU
{594F56D4-CDDD-451A-B487-D49EEB9F5B98}.Release|x86.ActiveCfg = Release|Any CPU
{594F56D4-CDDD-451A-B487-D49EEB9F5B98}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -274,6 +626,12 @@ Global
{B45A3E8B-286B-4A74-9602-FC192ACEE8C4} = {2889482E-64CA-4A25-91D8-5B963D83681B} {B45A3E8B-286B-4A74-9602-FC192ACEE8C4} = {2889482E-64CA-4A25-91D8-5B963D83681B}
{ED9C639A-A706-4ECB-9638-A15B3681BDEC} = {2889482E-64CA-4A25-91D8-5B963D83681B} {ED9C639A-A706-4ECB-9638-A15B3681BDEC} = {2889482E-64CA-4A25-91D8-5B963D83681B}
{1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6} = {2889482E-64CA-4A25-91D8-5B963D83681B} {1DA666D8-DBFE-40F7-8EBF-95CC892E4EB6} = {2889482E-64CA-4A25-91D8-5B963D83681B}
{D6DE6894-DF5E-4F59-A4DD-145FBEFBDD36} = {03E1C8DA-035E-4882-AF81-F392139FCF38}
{E99A894F-1731-4A48-B345-1D14F70EA23F} = {03E1C8DA-035E-4882-AF81-F392139FCF38}
{30F4D3DF-D777-40C2-B434-49173AC06A4B} = {03E1C8DA-035E-4882-AF81-F392139FCF38}
{A4BC1880-5492-41FF-81BA-963AC4C60A02} = {03E1C8DA-035E-4882-AF81-F392139FCF38}
{BE7FDBA3-58CD-41C0-A748-E636DD11F137} = {03E1C8DA-035E-4882-AF81-F392139FCF38}
{594F56D4-CDDD-451A-B487-D49EEB9F5B98} = {03E1C8DA-035E-4882-AF81-F392139FCF38}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {28315BFD-90E7-4E14-A2EA-F3D23AF4126F} SolutionGuid = {28315BFD-90E7-4E14-A2EA-F3D23AF4126F}

View file

@ -1,6 +1,6 @@
using Volo.Abp.Application; using Volo.Abp.Application;
using Volo.Abp.Modularity;
using Volo.Abp.Authorization; using Volo.Abp.Authorization;
using Volo.Abp.Modularity;
namespace Sozsoft.Languages; namespace Sozsoft.Languages;

View file

@ -1,9 +0,0 @@
namespace Sozsoft.Languages;
public class LanguagesRemoteServiceConsts
{
public const string RemoteServiceName = "Languages";
public const string ModuleName = "languages";
}

View file

@ -1,4 +1,4 @@
using Volo.Abp.Authorization.Permissions; using Volo.Abp.Authorization.Permissions;
namespace Sozsoft.Languages.Permissions; namespace Sozsoft.Languages.Permissions;

View file

@ -1,9 +1,9 @@
using Sozsoft.Languages.Entities; using System;
using Sozsoft.Languages.Localization;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks; using System.Threading.Tasks;
using Sozsoft.Languages.Entities;
using Sozsoft.Languages.Localization;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services; using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Entities;

View file

@ -1,9 +1,9 @@
using Sozsoft.Languages.Entities; using System;
using Sozsoft.Languages.Localization;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks; using System.Threading.Tasks;
using Sozsoft.Languages.Entities;
using Sozsoft.Languages.Localization;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services; using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;

View file

@ -1,14 +1,14 @@
using Sozsoft.Languages.Entities; using System;
using Sozsoft.Languages.Localization;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using StackExchange.Redis;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Linq.Dynamic.Core; using System.Linq.Dynamic.Core;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Sozsoft.Languages.Entities;
using Sozsoft.Languages.Localization;
using StackExchange.Redis;
using Volo.Abp; using Volo.Abp;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services; using Volo.Abp.Application.Services;
@ -136,7 +136,7 @@ public class LanguageTextAppService : CrudAppService<
var db = redis.GetDatabase(); var db = redis.GetDatabase();
await db.ExecuteAsync("FLUSHALL"); await db.ExecuteAsync("FLUSHALL");
} }
protected virtual IQueryable<LanguageKey> ApplySorting(IQueryable<LanguageKey> query, LanguageTextTranslatedRequestDto input) protected virtual IQueryable<LanguageKey> ApplySorting(IQueryable<LanguageKey> query, LanguageTextTranslatedRequestDto input)
{ {
//Try to sort query if available //Try to sort query if available

View file

@ -1,6 +1,6 @@
using Riok.Mapperly.Abstractions;
using Sozsoft.Languages.Entities; using Sozsoft.Languages.Entities;
using Volo.Abp.Localization; using Volo.Abp.Localization;
using Riok.Mapperly.Abstractions;
using Volo.Abp.Mapperly; using Volo.Abp.Mapperly;
namespace Sozsoft.Languages; namespace Sozsoft.Languages;

View file

@ -1,7 +0,0 @@
namespace Sozsoft.Languages;
public static class LanguagesErrorCodes
{
//Add your business exception error codes here...
}

View file

@ -1,45 +1,23 @@
namespace Sozsoft.Languages.Languages namespace Sozsoft.Languages.Languages;
{
public static class LanguageCodes
{
public const string Ar = "ar";
public const string Cs = "cs";
public const string De = "de-DE";
public const string En = "en";
public const string Es = "es";
public const string Fi = "fi";
public const string Fr = "fr";
public const string Hi = "hi";
public const string Hr = "hr";
public const string Hu = "hu";
public const string It = "it";
public const string Pt = "pt-BR";
public const string Ru = "ru";
public const string Sk = "sk";
public const string Sl = "sl";
public const string Tr = "tr";
public const string Zh = "zh-Hans";
}
public static class LanguageNames public static class LanguageCodes
{ {
public const string Ar = "العربية"; public const string Ar = "ar";
public const string Cs = "Čeština"; public const string Cs = "cs";
public const string De = "Deutsch"; public const string De = "de-DE";
public const string En = "English"; public const string En = "en";
public const string Es = "Español"; public const string Es = "es";
public const string Fi = "Finnish"; public const string Fi = "fi";
public const string Fr = "Français"; public const string Fr = "fr";
public const string Hi = "Hindi"; public const string Hi = "hi";
public const string Hr = "Croatian"; public const string Hr = "hr";
public const string Hu = "Magyar"; public const string Hu = "hu";
public const string It = "Italiano"; public const string It = "it";
public const string Pt = "Português"; public const string Pt = "pt-BR";
public const string Ru = "Русский"; public const string Ru = "ru";
public const string Sk = "Slovak"; public const string Sk = "sk";
public const string Sl = "Slovenščina"; public const string Sl = "sl";
public const string Tr = "Türkçe"; public const string Tr = "tr";
public const string Zh = "繁體中文"; public const string Zh = "zh-Hans";
}
} }

View file

@ -1,10 +1,7 @@
using Sozsoft.Languages.Entities; using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using Volo.Abp.Caching; using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events.Distributed; using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.EventBus.Distributed; using Volo.Abp.EventBus.Distributed;
namespace Sozsoft.Languages; namespace Sozsoft.Languages;
@ -15,14 +12,11 @@ public class LanguageTextCacheItemInvalidatorDistributed :
IDistributedEventHandler<EntityDeletedEto<LanguageTextEto>>, IDistributedEventHandler<EntityDeletedEto<LanguageTextEto>>,
ITransientDependency ITransientDependency
{ {
private readonly IRepository<LanguageText, Guid> languageTextRepository;
private readonly IDistributedCache<LanguageTextCacheItem> cache; private readonly IDistributedCache<LanguageTextCacheItem> cache;
public LanguageTextCacheItemInvalidatorDistributed( public LanguageTextCacheItemInvalidatorDistributed(
IRepository<LanguageText, Guid> languageTextRepository,
IDistributedCache<LanguageTextCacheItem> cache) IDistributedCache<LanguageTextCacheItem> cache)
{ {
this.languageTextRepository = languageTextRepository;
this.cache = cache; this.cache = cache;
} }

View file

@ -1,9 +1,9 @@
using Sozsoft.Languages.Entities; using System;
using Microsoft.Extensions.Localization;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
using Sozsoft.Languages.Entities;
using Volo.Abp.Caching; using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;

View file

@ -1,6 +1,6 @@
using Microsoft.Extensions.Localization; using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
using Volo.Abp.Localization; using Volo.Abp.Localization;
namespace Sozsoft.Languages.Localization; namespace Sozsoft.Languages.Localization;

View file

@ -1,5 +1,5 @@
using Sozsoft.Languages.Entities; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore; using Sozsoft.Languages.Entities;
using Volo.Abp.Data; using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore;

View file

@ -1,5 +1,5 @@
using Sozsoft.Languages.Entities; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore; using Sozsoft.Languages.Entities;
using Volo.Abp; using Volo.Abp;
using Volo.Abp.EntityFrameworkCore.Modeling; using Volo.Abp.EntityFrameworkCore.Modeling;

View file

@ -1 +1 @@
namespace Sozsoft.MailQueue.Application; namespace Sozsoft.MailQueue.Application;

View file

@ -1,6 +1,6 @@
using Sozsoft.MailQueue.FileGeneration; using Microsoft.Extensions.Configuration;
using Sozsoft.MailQueue.FileGeneration;
using Sozsoft.MailQueue.MailGeneration.Models; using Sozsoft.MailQueue.MailGeneration.Models;
using Microsoft.Extensions.Configuration;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Guids; using Volo.Abp.Guids;
using Volo.Abp.TextTemplating; using Volo.Abp.TextTemplating;
@ -141,53 +141,53 @@ public class AttachmentGenerator : IAttachmentGenerator, ITransientDependency
return null; return null;
// if (model.Filtreler.ContainsKey("QUERY") && model.Filtreler.ContainsKey("QUERYSQL")) // if (model.Filtreler.ContainsKey("QUERY") && model.Filtreler.ContainsKey("QUERYSQL"))
// { // {
// /* // /*
//Parametrelerden asagidaki sekilde gelen queryleri en alttaki formata donusturuyoruz //Parametrelerden asagidaki sekilde gelen queryleri en alttaki formata donusturuyoruz
//params //params
//0 => //0 =>
// QUERY -> qryDepo_Cikis_CekiListesi_Detayli // QUERY -> qryDepo_Cikis_CekiListesi_Detayli
// QUERYSQL -> Ksp_Depo_Cikis_CekiListesi_Detayli 252993 // QUERYSQL -> Ksp_Depo_Cikis_CekiListesi_Detayli 252993
// QUERY2 -> qryDepo_Cikis_CekiListesi_Sub // QUERY2 -> qryDepo_Cikis_CekiListesi_Sub
// QUERY2SQL -> KSp_Depo_Cikis_CekiListesi_Sub 252993 // QUERY2SQL -> KSp_Depo_Cikis_CekiListesi_Sub 252993
//SQL olan keyvalue'lari cikar ve sadece querylerde foreach yap
// QUERY -> qryDepo_Cikis_CekiListesi_Detayli
// dict.add(item.Value, params[item.Key + "SQL"].Value)
// QUERY2 -> qryDepo_Cikis_CekiListesi_Sub
// dict.add(item.Value, params[item.Key + "SQL"].Value)
//Bunlari yeni dict'e ekle //SQL olan keyvalue'lari cikar ve sadece querylerde foreach yap
//dict // QUERY -> qryDepo_Cikis_CekiListesi_Detayli
//qryDepo_Cikis_CekiListesi_Detayli -> Ksp_Depo_Cikis_CekiListesi_Detayli 252993 // dict.add(item.Value, params[item.Key + "SQL"].Value)
//qryDepo_Cikis_CekiListesi_Sub -> KSp_Depo_Cikis_CekiListesi_Sub 252993 // QUERY2 -> qryDepo_Cikis_CekiListesi_Sub
// */ // dict.add(item.Value, params[item.Key + "SQL"].Value)
// //dict -> key (name), value (sql)
// var queries = model.Filtreler
// .Where(a => a.Key.StartsWith("QUERY"))
// .Where(a => !a.Key.EndsWith("SQL"))
// .Where(a => !a.Value.IsNullOrWhiteSpace())
// .ToDictionary(x => x.Value, x => model.Filtreler.GetValueOrDefault(x.Key + "SQL"));
// return MsAccessReportManager.GenerateAccessReport( //Bunlari yeni dict'e ekle
// model.DbAdi, //dict
// model.Index, //qryDepo_Cikis_CekiListesi_Detayli -> Ksp_Depo_Cikis_CekiListesi_Detayli 252993
// model.RaporAdi, //qryDepo_Cikis_CekiListesi_Sub -> KSp_Depo_Cikis_CekiListesi_Sub 252993
// model.ExportDosyaTipi, // */
// queries, // //dict -> key (name), value (sql)
// attachmentFolder); // var queries = model.Filtreler
// } // .Where(a => a.Key.StartsWith("QUERY"))
// else // .Where(a => !a.Key.EndsWith("SQL"))
// { // .Where(a => !a.Value.IsNullOrWhiteSpace())
// return MsAccessReportManager.GenerateAccessReport( // .ToDictionary(x => x.Value, x => model.Filtreler.GetValueOrDefault(x.Key + "SQL"));
// model.DbAdi,
// model.Index, // return MsAccessReportManager.GenerateAccessReport(
// model.RaporAdi, // model.DbAdi,
// string.Join(" AND ", model.Filtreler.Select(a => $"{a.Key}={a.Value}")), // model.Index,
// model.ExportDosyaTipi, // model.RaporAdi,
// attachmentFolder); // model.ExportDosyaTipi,
// } // queries,
// attachmentFolder);
// }
// else
// {
// return MsAccessReportManager.GenerateAccessReport(
// model.DbAdi,
// model.Index,
// model.RaporAdi,
// string.Join(" AND ", model.Filtreler.Select(a => $"{a.Key}={a.Value}")),
// model.ExportDosyaTipi,
// attachmentFolder);
// }
} }
private async Task<string> ConvertFileToBase64Async(string filePath) private async Task<string> ConvertFileToBase64Async(string filePath)

View file

@ -1,5 +1,5 @@
using Sozsoft.MailQueue.MailGeneration.Models; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration; using Sozsoft.MailQueue.MailGeneration.Models;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.TextTemplating; using Volo.Abp.TextTemplating;

View file

@ -1,8 +1,8 @@
using System.Data; using System.Data;
using Microsoft.EntityFrameworkCore;
using Sozsoft.MailQueue.Domain.Entities; using Sozsoft.MailQueue.Domain.Entities;
using Sozsoft.MailQueue.EntityFrameworkCore; using Sozsoft.MailQueue.EntityFrameworkCore;
using Sozsoft.MailQueue.MailGeneration.Models; using Sozsoft.MailQueue.MailGeneration.Models;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
using Volo.Abp.TextTemplating; using Volo.Abp.TextTemplating;

View file

@ -1,8 +1,8 @@
using Sozsoft.Sender.Mail; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Sozsoft.MailQueue.Domain.MailGeneration; using Sozsoft.MailQueue.Domain.MailGeneration;
using Sozsoft.MailQueue.Domain.Shared; using Sozsoft.MailQueue.Domain.Shared;
using Microsoft.Extensions.Configuration; using Sozsoft.Sender.Mail;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.BackgroundWorkers; using Volo.Abp.BackgroundWorkers;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
using Volo.Abp.Guids; using Volo.Abp.Guids;
@ -182,7 +182,7 @@ public class MailQueueWorker : BackgroundWorkerBase
} }
queue.SendStatus = result.Success; queue.SendStatus = result.Success;
queue.SendTime = Clock.Now; queue.SendTime = Clock.Now;
await Repository.UpdateAsync(queue); await Repository.UpdateAsync(queue, cancellationToken: cancellationToken);
// Attachmentlari silebiliriz // Attachmentlari silebiliriz
try try

View file

@ -2,9 +2,9 @@
using Amazon; using Amazon;
using Amazon.SQS; using Amazon.SQS;
using Amazon.SQS.Model; using Amazon.SQS.Model;
using Sozsoft.Sender.Mail.AmazonSes;
using Sozsoft.MailQueue.Domain.Entities;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Sozsoft.MailQueue.Domain.Entities;
using Sozsoft.Sender.Mail.AmazonSes;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Services; using Volo.Abp.Domain.Services;

View file

@ -1,5 +1,5 @@
using Sozsoft.MailQueue.Domain; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore; using Sozsoft.MailQueue.Domain;
using Volo.Abp.Data; using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore;

View file

@ -1,6 +1,6 @@
using Sozsoft.MailQueue.Domain; using Microsoft.EntityFrameworkCore;
using Sozsoft.MailQueue.Domain;
using Sozsoft.MailQueue.Domain.Entities; using Sozsoft.MailQueue.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using Volo.Abp; using Volo.Abp;
using Volo.Abp.EntityFrameworkCore.Modeling; using Volo.Abp.EntityFrameworkCore.Modeling;

View file

@ -1,6 +1,6 @@
using System.Data; using System.Data;
using Sozsoft.MailQueue.Domain.Entities;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Sozsoft.MailQueue.Domain.Entities;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;

View file

@ -1,5 +1,4 @@
using ClosedXML.Excel; using ClosedXML.Excel;
using DocumentFormat.OpenXml.Spreadsheet;
using Sozsoft.MailQueue.MailGeneration.Models; using Sozsoft.MailQueue.MailGeneration.Models;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
@ -26,7 +25,7 @@ public class XLSFileGenerator : IFileGenerator, ITransientDependency
using var stream = new MemoryStream(); using var stream = new MemoryStream();
workbook.SaveAs(stream); workbook.SaveAs(stream);
stream.Close(); stream.Close();
return await Task.FromResult(stream.ToArray()); return await Task.FromResult(stream.ToArray());
} }
@ -80,8 +79,11 @@ public class XLSFileGenerator : IFileGenerator, ITransientDependency
var col = table.TabloSutunlari[j]; var col = table.TabloSutunlari[j];
var cell = sheet.Cell(i + 2, j + 1); var cell = sheet.Cell(i + 2, j + 1);
var value = row.GetValueOrDefault(col.SutunAdi); var value = row.GetValueOrDefault(col.SutunAdi);
var dataType = XLDataType.Text; if (!Enum.TryParse(col.VeriTipi, out XLDataType dataType))
Enum.TryParse(col.VeriTipi, out dataType); {
dataType = XLDataType.Text;
}
switch (dataType) switch (dataType)
{ {
case XLDataType.Number: case XLDataType.Number:

View file

@ -1,16 +1,16 @@
using System.Reflection; using System.Reflection;
using Sozsoft.Sender;
using Sozsoft.MailQueue.Domain.Shared;
using Sozsoft.MailQueue.EntityFrameworkCore;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Sozsoft.MailQueue.Domain.Shared;
using Sozsoft.MailQueue.EntityFrameworkCore;
using Sozsoft.Sender;
using Volo.Abp.Application; using Volo.Abp.Application;
using Volo.Abp.Authorization; using Volo.Abp.Authorization;
using Volo.Abp.Mapperly;
using Volo.Abp.Domain; using Volo.Abp.Domain;
using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.Localization; using Volo.Abp.Localization;
using Volo.Abp.Localization.ExceptionHandling; using Volo.Abp.Localization.ExceptionHandling;
using Volo.Abp.Mapperly;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Abp.TextTemplating.Razor; using Volo.Abp.TextTemplating.Razor;
using Volo.Abp.VirtualFileSystem; using Volo.Abp.VirtualFileSystem;

View file

@ -0,0 +1,39 @@
using System.Collections.Generic;
namespace Sozsoft.Mcp.Dtos;
/// <summary>Dinamik istemci kaydi (RFC 7591) istegi.</summary>
public class McpClientRegistrationRequestDto
{
public string? ClientName { get; set; }
public List<string> RedirectUris { get; set; } = [];
}
/// <summary>
/// Dinamik istemci kaydinin sonucu. Kayit basarisizsa <see cref="Error"/> RFC 7591 hata kodunu
/// tasir ve diger alanlar bos kalir.
/// </summary>
public class McpClientRegistrationResultDto
{
public string? ClientId { get; set; }
public string? ClientName { get; set; }
public List<string> RedirectUris { get; set; } = [];
public List<string> GrantTypes { get; set; } = [];
public List<string> ResponseTypes { get; set; } = [];
/// <summary>Genel istemci: token ucunda gizli anahtar kullanilmaz, PKCE zorunludur.</summary>
public string TokenEndpointAuthMethod { get; set; } = "none";
public string? Scope { get; set; }
public long ClientIdIssuedAt { get; set; }
public string? Error { get; set; }
public string? ErrorDescription { get; set; }
}

View file

@ -0,0 +1,127 @@
using System.Collections.Generic;
namespace Sozsoft.Mcp.Dtos;
/// <summary>
/// Bir listenin verisini ekranin kendi sorgusuyla okuma istegi. Filtre, siralama ve gruplama
/// DevExtreme veri yukleme bicimindedir; ekranin grid'i de ayni bicimi kullanir.
/// </summary>
public class McpListQueryRequestDto
{
public string ListFormCode { get; set; } = string.Empty;
/// <summary>Ornek: <c>["Status","=","Open"]</c> ya da <c>[["A","&gt;",1],"and",["B","contains","x"]]</c>.</summary>
public string? Filter { get; set; }
/// <summary>Ornek: <c>[{"selector":"CreationTime","desc":true}]</c>.</summary>
public string? Sort { get; set; }
/// <summary>Ornek: <c>[{"selector":"CustomerId","desc":false,"isExpanded":false}]</c>.</summary>
public string? Group { get; set; }
/// <summary>Ornek: <c>[{"selector":"Id","summaryType":"count"}]</c>.</summary>
public string? GroupSummary { get; set; }
/// <summary>Ornek: <c>[{"selector":"Amount","summaryType":"sum"}]</c>.</summary>
public string? TotalSummary { get; set; }
public int Skip { get; set; }
/// <summary>Istenen satir sayisi; sunucu kaydindaki tavana kirpilir.</summary>
public int Take { get; set; }
}
/// <summary>Liste sorgusunun sonucu.</summary>
public class McpListQueryResultDto
{
public object? Data { get; set; }
/// <summary>Filtreye uyan toplam kayit sayisi (sayfalamadan once).</summary>
public int TotalCount { get; set; }
public int GroupCount { get; set; }
public object[]? Summary { get; set; }
/// <summary>Uygulanan satir tavani; <see cref="TotalCount"/> bundan buyukse sonuc sayfalanmistir.</summary>
public int Take { get; set; }
}
/// <summary>Parametreli tek SELECT istegi.</summary>
public class McpSelectRequestDto
{
public string Sql { get; set; } = string.Empty;
/// <summary>Sorgudaki adli parametreler.</summary>
public Dictionary<string, object?> Parameters { get; set; } = [];
/// <summary>Istenen satir sayisi; sunucu kaydindaki tavana kirpilir.</summary>
public int MaxRows { get; set; }
}
/// <summary>
/// Kayitli bir veritabani nesnesi (view, stored procedure, function) ya da Custom Endpoint.
/// </summary>
public class McpSavedQueryDto
{
/// <summary><c>McpConsts.SavedQueryKinds</c> degerlerinden biri.</summary>
public string Kind { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string? SchemaName { get; set; }
public string? Description { get; set; }
/// <summary>Custom Endpoint'te beklenen parametre adlari; veritabani nesnelerinde bostur.</summary>
public List<string> ParameterNames { get; set; } = [];
}
/// <summary>Kayitli bir nesnenin ya da Custom Endpoint'in cagrilmasi.</summary>
public class McpSavedQueryRequestDto
{
/// <summary><see cref="McpSavedQueryDto.Kind"/> ile ayni degerler.</summary>
public string Kind { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
/// <summary>Bos birakilirsa saglayicinin varsayilan semasi kullanilir.</summary>
public string? SchemaName { get; set; }
public Dictionary<string, object?> Parameters { get; set; } = [];
public int MaxRows { get; set; }
}
/// <summary>Sorgu sonucu.</summary>
public class McpQueryResultDto
{
public List<string> Columns { get; set; } = [];
public List<Dictionary<string, object?>> Rows { get; set; } = [];
public int RowCount { get; set; }
/// <summary>Satir sayisi tavana dayandiginda true; sonuc kesilmis demektir.</summary>
public bool Truncated { get; set; }
public long ExecutionTimeMs { get; set; }
}
/// <summary>Bir ListForm ekrani uzerinden kayit yazma/silme istegi.</summary>
public class McpRowRequestDto
{
public string ListFormCode { get; set; } = string.Empty;
/// <summary>Alan adi → deger. Silme isleminde bos birakilir.</summary>
public Dictionary<string, object?> Values { get; set; } = [];
/// <summary>Guncelleme ve silmede anahtar alan degerleri.</summary>
public List<object?> Keys { get; set; } = [];
}
/// <summary>Yazma isleminin sonucu.</summary>
public class McpWriteResultDto
{
/// <summary>Insert'te uretilen anahtar; guncelleme ve silmede bostur.</summary>
public string? Key { get; set; }
/// <summary>Guncelleme ve silmede etkilenen satir sayisi.</summary>
public int AffectedRows { get; set; }
}

View file

@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
namespace Sozsoft.Mcp.Dtos;
/// <summary>
/// Istegin kiracisina dusen MCP sunucu kaydinin etkin hali. /mcp ucu hangi araclari
/// listeleyecegine ve sorgularin hangi satir tavaniyla kosacagina bu kayda bakarak karar verir.
/// </summary>
public class McpServerConfigDto
{
public string Name { get; set; } = string.Empty;
/// <summary>Ekranda gosterilen baglanti adresi; sunucu tarafinda yonlendirme icin kullanilmaz.</summary>
public string? Url { get; set; }
/// <summary>Bos ise istegin kapsamindaki varsayilan veri kaynagi kullanilir.</summary>
public string? DataSourceCode { get; set; }
/// <summary>Bos liste "yazma disindaki tum araclar acik" anlamina gelir.</summary>
public List<string> AllowedTools { get; set; } = [];
public int MaxRows { get; set; }
public bool AllowWrite { get; set; }
public string? Description { get; set; }
/// <summary>Istegin cozumlenen kiraci adi; host baglaminda bostur.</summary>
public string? TenantName { get; set; }
}
/// <summary>
/// Veri sozlugunun tazelik bilgisi. Sozluk <c>Sas_H_DbTableColumn</c> tablosunda, sozluk
/// ureten prosedur tarafindan yazilmis JSON olarak durur.
/// </summary>
public class McpSchemaInfoDto
{
public bool Available { get; set; }
public string? CacheKey { get; set; }
public DateTime? RefreshedAt { get; set; }
public int FormCount { get; set; }
public int FieldCount { get; set; }
public string? TenantName { get; set; }
public string? DefinitionCultureName { get; set; }
public string? UiCultureName { get; set; }
/// <summary>Sozluk bulunamadiginda ne yapilacagini anlatan mesaj.</summary>
public string? Message { get; set; }
}
/// <summary>Bir listenin (ListForm) ozet tanimi.</summary>
public class McpListSummaryDto
{
public string ListFormCode { get; set; } = string.Empty;
public string? DisplayName { get; set; }
public string? Title { get; set; }
public string? Description { get; set; }
/// <summary>Listenin arkasindaki tablo adi; SELECT tabanli listelerde bostur.</summary>
public string? TableName { get; set; }
/// <summary>Tablo adi ya da SELECT metni — listenin gercek SQL kaynagi.</summary>
public string? SqlSourceName { get; set; }
/// <summary><c>table</c>, <c>selectSql</c> ya da <c>unknown</c>.</summary>
public string? SourceKind { get; set; }
public string? KeyFieldName { get; set; }
public int FieldCount { get; set; }
}
/// <summary>Bir listenin sutun tanimi.</summary>
public class McpColumnDto
{
public string Field { get; set; } = string.Empty;
public string? DisplayName { get; set; }
public string? SqlDataType { get; set; }
public bool IsVisible { get; set; }
public bool IsBooleanColumn { get; set; }
/// <summary>Lookup tasiyan sutunda hedef tablo; yoksa bostur.</summary>
public string? LookupTableName { get; set; }
public string? LookupValueColumn { get; set; }
public string? LookupDisplayColumn { get; set; }
/// <summary>Parola/token/anahtar gibi sutunlarda true; sorgu araclari bunlari onermez.</summary>
public bool IsSensitive { get; set; }
/// <summary>Varsayilan SELECT listesine girmesi uygun olan sutunlarda true.</summary>
public bool IsDefaultSelectColumn { get; set; }
}
/// <summary>Bir listenin sutunlariyla birlikte tam tanimi.</summary>
public class McpListDetailDto : McpListSummaryDto
{
public List<McpColumnDto> Columns { get; set; } = [];
}
/// <summary>Serbest metin aramasinin tek sonucu.</summary>
public class McpSchemaMatchDto
{
public string ListFormCode { get; set; } = string.Empty;
public string? DisplayName { get; set; }
public string? TableName { get; set; }
/// <summary>Eslesme yalnizca liste duzeyindeyse bos; sutun duzeyindeyse eslesen sutunlar.</summary>
public List<McpColumnDto> MatchedColumns { get; set; } = [];
}

View file

@ -0,0 +1,19 @@
using System.Threading.Tasks;
using Sozsoft.Mcp.Dtos;
using Volo.Abp.Application.Services;
namespace Sozsoft.Mcp;
/// <summary>
/// MCP veri yazma yuzeyi. Yazma her zaman bir ListForm ekraninin insert/update/delete
/// sozlesmesinden gecer; ekran yetkisi ve tenant filtresi aynen uygulanir. Sunucu kaydinda
/// <c>AllowWrite</c> kapali oldugunda bu araclar hic listelenmez.
/// </summary>
public interface IMcpDataAppService : IApplicationService
{
Task<McpWriteResultDto> InsertRowAsync(McpRowRequestDto input);
Task<McpWriteResultDto> UpdateRowAsync(McpRowRequestDto input);
Task<McpWriteResultDto> DeleteRowAsync(McpRowRequestDto input);
}

View file

@ -0,0 +1,16 @@
using System.Threading.Tasks;
using Sozsoft.Mcp.Dtos;
using Volo.Abp.Application.Services;
namespace Sozsoft.Mcp;
/// <summary>
/// Uzak MCP istemcilerinin (Claude, ChatGPT connector'lari) OAuth kaydi. Kayit yeni bir
/// OpenIddict uygulamasi uretmez: istek, seed edilmis tek MCP istemcisinin izinli geri donus
/// adresleriyle dogrulanir ve o istemcinin kimligi dondurulur. Boylece herkese acik kayit ucu
/// veritabaninda denetimsiz uygulama biriktiremez.
/// </summary>
public interface IMcpOAuthAppService : IApplicationService
{
Task<McpClientRegistrationResultDto> RegisterClientAsync(McpClientRegistrationRequestDto input);
}

View file

@ -0,0 +1,32 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Sozsoft.Mcp.Dtos;
using Volo.Abp.Application.Services;
namespace Sozsoft.Mcp;
/// <summary>
/// MCP salt-okunur sorgu yuzeyi. Her sorgu istegin kapsamindaki veri kaynaginda, sunucu
/// kaydindaki satir tavaniyla ve parametreli olarak kosar.
/// </summary>
public interface IMcpQueryAppService : IApplicationService
{
/// <summary>
/// Bir listenin verisini ekranin kendi sorgusuyla okur: liste okuma yetkisi, alan bazli
/// okuma yetkisi, kiraci/sube filtreleri ve kullanici ozellestirmeleri uygulamadaki gibi
/// gecerlidir. Normal kullanicinin veri okuma yolu budur.
/// </summary>
Task<McpListQueryResultDto> QueryListAsync(McpListQueryRequestDto input);
/// <summary>
/// Tek bir SELECT ifadesi kosturur. Coklu ifade, DDL ve DML reddedilir; satir sayisi
/// sunucu kaydindaki tavana kirpilir.
/// </summary>
Task<McpQueryResultDto> RunSelectAsync(McpSelectRequestDto input);
/// <summary>Veri kaynagindaki view/procedure/function nesneleri ve Custom Endpoint'ler.</summary>
Task<List<McpSavedQueryDto>> GetSavedQueriesAsync(string? search = null);
/// <summary>Kayitli bir nesneyi ya da Custom Endpoint'i parametreleriyle cagirir.</summary>
Task<McpQueryResultDto> RunSavedQueryAsync(McpSavedQueryRequestDto input);
}

View file

@ -0,0 +1,38 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Sozsoft.Mcp.Dtos;
using Volo.Abp.Application.Services;
namespace Sozsoft.Mcp;
/// <summary>
/// MCP sema kesfi. Tablo ve sutun bilgisi canli katalogdan degil, platformun ekran
/// tanimlarindan uretilen JSON veri sozlugunden gelir; boylece modelin gordugu adlar
/// kullanicinin ekranda gordugu adlarla ayni olur.
/// </summary>
public interface IMcpSchemaAppService : IApplicationService
{
/// <summary>Istegin kiracisina dusen etkin MCP sunucu kaydi; kayit yoksa null.</summary>
Task<McpServerConfigDto?> GetServerConfigAsync();
/// <summary>
/// Bu kullanicinin bu baglantida cagirabilecegi araclar: sunucu kaydinin actiklari ile
/// kullanicinin uygulamadaki yetkilerinin kesisimi.
/// </summary>
Task<List<string>> GetAvailableToolsAsync();
/// <summary>Veri sozlugunun bulunup bulunmadigi ve ne zaman tazelendigi.</summary>
Task<McpSchemaInfoDto> GetSchemaInfoAsync();
/// <param name="search">Bos birakilirsa tum listeler doner.</param>
/// <param name="maxResults">Donecek en fazla liste sayisi.</param>
Task<List<McpListSummaryDto>> GetListsAsync(string? search = null, int maxResults = 200);
/// <summary>Tek bir listenin sutunlariyla birlikte tanimi; bulunamazsa null.</summary>
Task<McpListDetailDto?> GetListAsync(string listFormCode);
/// <summary>
/// Liste ve sutun adlari ile basliklari uzerinde aksan ve buyuk/kucuk harf duyarsiz arama.
/// </summary>
Task<List<McpSchemaMatchDto>> SearchAsync(string term, int maxResults = 50);
}

View file

@ -0,0 +1,14 @@
using Volo.Abp.Application;
using Volo.Abp.Authorization;
using Volo.Abp.Modularity;
namespace Sozsoft.Mcp;
[DependsOn(
typeof(McpDomainSharedModule),
typeof(AbpDddApplicationContractsModule),
typeof(AbpAuthorizationModule)
)]
public class McpApplicationContractsModule : AbpModule
{
}

View file

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\common.props" />
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<RootNamespace>Sozsoft.Mcp</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Ddd.Application.Contracts" Version="10.0.0" />
<PackageReference Include="Volo.Abp.Authorization" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Sozsoft.Mcp.Domain.Shared\Sozsoft.Mcp.Domain.Shared.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,86 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Sozsoft.Mcp.Dtos;
using Sozsoft.Mcp.Services;
using Sozsoft.Platform.Localization;
using Volo.Abp;
using Volo.Abp.Application.Services;
namespace Sozsoft.Mcp;
/// <summary>
/// MCP uygulama servislerinin ortak tabani. Kullaniciya gorunen metinler platformun
/// <c>PlatformResource</c> kaynagindan okunur; sunucu kaydini cozme ve arac kapisi her
/// serviste ayni davranir.
/// </summary>
public abstract class McpAppService : ApplicationService
{
protected IMcpServerConfigProvider ServerConfigProvider { get; }
protected McpAppService(IMcpServerConfigProvider serverConfigProvider)
{
ServerConfigProvider = serverConfigProvider;
LocalizationResource = typeof(PlatformResource);
}
/// <summary>Etkin sunucu kaydinin DTO hali; kayit yoksa null.</summary>
protected async Task<McpServerConfigDto?> ResolveServerConfigAsync()
{
var server = await ServerConfigProvider.GetEffectiveServerAsync();
if (server is null)
{
return null;
}
return new McpServerConfigDto
{
Name = server.Name,
Url = server.Url,
DataSourceCode = server.DataSourceCode,
AllowedTools = [.. ServerConfigProvider.SplitMultiValue(server.AllowedTools)],
MaxRows = server.MaxRows,
AllowWrite = server.AllowWrite,
Description = server.Description,
TenantName = CurrentTenant.Name
};
}
/// <summary>
/// Kaydi zorunlu kilan islemler icin. Kayit yoksa istemcinin nedenini gorebilecegi bir
/// hata atar.
/// </summary>
protected async Task<McpServerConfigDto> RequireServerConfigAsync()
=> await ResolveServerConfigAsync()
?? throw new UserFriendlyException(L[McpErrorCodes.NoServerRecord]);
/// <summary>
/// Kapali bir araca yapilan cagriyi reddeder: arac sunucu kaydinda kapaliysa ya da
/// kullanicinin uygulamada karsilik gelen yetkisi yoksa. Arac listesini suzmek tek basina
/// yeterli degildir — istemci listede olmayan bir araci da cagirabilir.
/// </summary>
protected async Task<McpServerConfigDto> EnsureToolAllowedAsync(string toolName)
{
var config = await RequireServerConfigAsync();
if (!McpToolPolicy.IsAllowed(true, config.AllowWrite, config.AllowedTools, toolName))
{
throw new UserFriendlyException(L[McpErrorCodes.ToolDisabled, toolName, config.Name]);
}
if (McpToolNames.RequiredPermissions.TryGetValue(toolName, out var permission))
{
await AuthorizationService.CheckAsync(permission);
}
return config;
}
/// <summary>Istenen satir sayisini sunucu kaydindaki tavana kirpar.</summary>
protected static int ResolveMaxRows(McpServerConfigDto config, int requested)
{
var ceiling = config.MaxRows > 0 ? config.MaxRows : McpConsts.DefaultMaxRows;
return requested > 0 ? Math.Min(requested, ceiling) : ceiling;
}
}

View file

@ -0,0 +1,13 @@
using Volo.Abp.Application;
using Volo.Abp.Modularity;
namespace Sozsoft.Mcp;
[DependsOn(
typeof(McpDomainModule),
typeof(McpApplicationContractsModule),
typeof(AbpDddApplicationModule)
)]
public class McpApplicationModule : AbpModule
{
}

View file

@ -0,0 +1,47 @@
namespace Sozsoft.Mcp;
/// <summary>
/// Kayitli nesneleri listelemek icin kullanilan katalog sorgulari. Parametre almazlar;
/// sabit metin oldugu icin string birlestirme riski yoktur.
/// </summary>
internal static class McpCatalogQueries
{
public const string SqlServer = """
SELECT
SCHEMA_NAME(o.schema_id) AS SchemaName,
o.name AS ObjectName,
CASE o.type
WHEN 'V' THEN 'VIEW'
WHEN 'P' THEN 'PROCEDURE'
WHEN 'IF' THEN 'TABLE_FUNCTION'
WHEN 'TF' THEN 'TABLE_FUNCTION'
WHEN 'FN' THEN 'SCALAR_FUNCTION'
END AS ObjectKind
FROM sys.objects o
WHERE o.is_ms_shipped = 0
AND o.type IN ('V', 'P', 'IF', 'TF', 'FN')
ORDER BY SchemaName, ObjectName
""";
public const string Postgres = """
SELECT n.nspname AS "SchemaName",
c.relname AS "ObjectName",
'VIEW' AS "ObjectKind"
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('v', 'm')
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
UNION ALL
SELECT n.nspname AS "SchemaName",
p.proname AS "ObjectName",
CASE
WHEN p.prokind = 'p' THEN 'PROCEDURE'
WHEN p.proretset THEN 'TABLE_FUNCTION'
ELSE 'SCALAR_FUNCTION'
END AS "ObjectKind"
FROM pg_proc p
JOIN pg_namespace n ON n.oid = p.pronamespace
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
ORDER BY 1, 2
""";
}

View file

@ -0,0 +1,87 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Sozsoft.Mcp.Dtos;
using Sozsoft.Mcp.Services;
using Sozsoft.Platform;
using Sozsoft.Platform.ListForms.Select;
using Volo.Abp;
namespace Sozsoft.Mcp;
/// <summary>
/// Veri yazma yuzeyi. Yazma her zaman bir ListForm ekraninin insert/update/delete
/// sozlesmesinden gecer; ekran yetkisi, tenant filtresi ve seed senkronizasyonu oradaki
/// yoldan aynen uygulanir.
/// </summary>
[Authorize(PlatformConsts.AppCodes.DeveloperKits.McpServers.Write)]
public class McpDataAppService(
IMcpServerConfigProvider serverConfigProvider,
IListFormDataAppService listFormDataAppService)
: McpAppService(serverConfigProvider), IMcpDataAppService
{
public async Task<McpWriteResultDto> InsertRowAsync(McpRowRequestDto input)
{
var request = await BuildRequestAsync(input, McpToolNames.InsertRow, requireKeys: false, requireValues: true);
var key = await listFormDataAppService.PostInsertAsync(request);
return new McpWriteResultDto { Key = ((object?)key)?.ToString() };
}
public async Task<McpWriteResultDto> UpdateRowAsync(McpRowRequestDto input)
{
var request = await BuildRequestAsync(input, McpToolNames.UpdateRow, requireKeys: true, requireValues: true);
var affected = await listFormDataAppService.PostUpdateAsync(request);
return new McpWriteResultDto { AffectedRows = affected };
}
public async Task<McpWriteResultDto> DeleteRowAsync(McpRowRequestDto input)
{
var request = await BuildRequestAsync(input, McpToolNames.DeleteRow, requireKeys: true, requireValues: false);
var affected = await listFormDataAppService.PostDeleteAsync(request);
return new McpWriteResultDto { AffectedRows = affected };
}
/// <summary>
/// Istegi ekranin bekledigi bicime cevirir. <c>Data</c> JSON metni olarak verilir; ayni
/// bicim ekranin kopyalama yolunda da kullanilir.
/// </summary>
private async Task<DataRequestDto> BuildRequestAsync(
McpRowRequestDto input,
string toolName,
bool requireKeys,
bool requireValues)
{
Check.NotNull(input, nameof(input));
// AllowWrite kapaliysa yazma araclari McpToolPolicy tarafindan zaten reddedilir;
// burada ayri bir kontrol yok.
await EnsureToolAllowedAsync(toolName);
if (string.IsNullOrWhiteSpace(input.ListFormCode))
{
throw new UserFriendlyException(L[McpErrorCodes.ListFormCodeRequired]);
}
if (requireValues && (input.Values is null || input.Values.Count == 0))
{
throw new UserFriendlyException(L[McpErrorCodes.ValuesRequired]);
}
if (requireKeys && (input.Keys is null || input.Keys.Count == 0))
{
throw new UserFriendlyException(L[McpErrorCodes.KeysRequired]);
}
return new DataRequestDto
{
ListFormCode = input.ListFormCode,
Data = JsonSerializer.Serialize(input.Values ?? new Dictionary<string, object?>()),
Keys = [.. (input.Keys ?? []).Select(key => key!)]
};
}
}

View file

@ -0,0 +1,65 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Sozsoft.Mcp.Dtos;
using Sozsoft.Mcp.Services;
using Sozsoft.Platform.Localization;
using Volo.Abp;
using Volo.Abp.Application.Services;
namespace Sozsoft.Mcp;
/// <summary>
/// Dinamik istemci kaydi (RFC 7591). Uc kimlik dogrulamasizdir; istemci ne alirsa alsin token
/// kullanicinin kendi girisiyle ve kendi yetkileriyle uretilir. Kural ve kayit
/// <see cref="IMcpOAuthClientManager"/> icindedir.
/// </summary>
[AllowAnonymous]
public class McpOAuthAppService : ApplicationService, IMcpOAuthAppService
{
private const string AuthorizationCodeGrant = "authorization_code";
private const string RefreshTokenGrant = "refresh_token";
private readonly IMcpOAuthClientManager clientManager;
public McpOAuthAppService(IMcpOAuthClientManager clientManager)
{
this.clientManager = clientManager;
LocalizationResource = typeof(PlatformResource);
}
public async Task<McpClientRegistrationResultDto> RegisterClientAsync(McpClientRegistrationRequestDto input)
{
Check.NotNull(input, nameof(input));
var redirectUris = (input.RedirectUris ?? []).Distinct(StringComparer.Ordinal).ToList();
var registration = await clientManager.RegisterAsync(input.ClientName, redirectUris);
return registration.Error switch
{
McpClientRegistrationError.InvalidRedirectUri => new McpClientRegistrationResultDto
{
Error = "invalid_redirect_uri",
ErrorDescription = L[McpErrorCodes.RedirectUriNotAllowed, registration.ErrorValue ?? string.Empty]
},
McpClientRegistrationError.LimitReached => new McpClientRegistrationResultDto
{
Error = "invalid_client_metadata",
ErrorDescription = L[McpErrorCodes.RegistrationLimitReached, registration.ErrorValue ?? string.Empty]
},
_ => new McpClientRegistrationResultDto
{
ClientId = registration.ClientId,
ClientName = input.ClientName,
RedirectUris = redirectUris,
// Istemci hangi akislari isterse istesin gecerli olanlar bunlardir; RFC 7591
// sunucunun istenen degerleri daraltmasina izin verir.
GrantTypes = [AuthorizationCodeGrant, RefreshTokenGrant],
ResponseTypes = ["code"],
Scope = string.Join(' ', McpConsts.OAuthScopes),
ClientIdIssuedAt = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
}
};
}
}

View file

@ -0,0 +1,291 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Sozsoft.Mcp.Dtos;
using Sozsoft.Mcp.Services;
using Sozsoft.Platform;
using Sozsoft.Platform.Entities;
using Sozsoft.Platform.Enums;
using Sozsoft.Platform.ListForms.Select;
using Volo.Abp;
using Volo.Abp.Domain.Repositories;
namespace Sozsoft.Mcp;
/// <summary>
/// Salt-okunur sorgu yuzeyi. MCP kullaniciya yetki vermez, uygulamadaki yetkisini tasir:
/// <list type="bullet">
/// <item><c>query_list</c> ekranin kendi sorgusundan gecer; liste ve alan yetkileri, kiraci ve
/// sube filtreleri aynen uygulanir.</item>
/// <item>Ham SQL ve veritabani nesneleri (view/procedure/function) liste yetkilerini asar; bu
/// yuzden yalnizca uygulamada SQL Query Manager yetkisi olan kullaniciya aciktir.</item>
/// <item>Custom Endpoint'ler endpoint dispatcher'inin yetkisini ve endpoint'in kendi
/// kullanici/rol listesini ister.</item>
/// </list>
/// SQL kurallari <see cref="IMcpSqlGuard"/>, veri kaynagi cozumu ve calistirma
/// <see cref="IMcpQueryExecutor"/> icindedir.
/// </summary>
[Authorize(PlatformConsts.AppCodes.DeveloperKits.McpServers.Invoke)]
public class McpQueryAppService(
IMcpServerConfigProvider serverConfigProvider,
IMcpSqlGuard sqlGuard,
IMcpQueryExecutor queryExecutor,
IRepository<CustomEndpoint, Guid> customEndpointRepository,
IListFormSelectAppService listFormSelectAppService)
: McpAppService(serverConfigProvider), IMcpQueryAppService
{
private const string SqlPermission = PlatformConsts.AppCodes.SqlQueryManagers.Default;
private const string CustomEndpointPermission = PlatformConsts.AppCodes.DeveloperKits.Get;
public async Task<McpListQueryResultDto> QueryListAsync(McpListQueryRequestDto input)
{
Check.NotNull(input, nameof(input));
var config = await EnsureToolAllowedAsync(McpToolNames.QueryList);
var take = ResolveMaxRows(config, input.Take);
var result = await listFormSelectAppService.GetSelectAsync(new SelectRequestDto
{
ListFormCode = input.ListFormCode,
Filter = NullIfEmpty(input.Filter),
Sort = NullIfEmpty(input.Sort),
Group = NullIfEmpty(input.Group),
GroupSummary = NullIfEmpty(input.GroupSummary),
TotalSummary = NullIfEmpty(input.TotalSummary),
Skip = Math.Max(input.Skip, 0),
Take = take,
RequireTotalCount = true,
RequireGroupCount = !string.IsNullOrWhiteSpace(input.Group)
});
return new McpListQueryResultDto
{
Data = result.Data,
TotalCount = result.TotalCount,
GroupCount = result.GroupCount,
Summary = result.Summary,
Take = take
};
}
public async Task<McpQueryResultDto> RunSelectAsync(McpSelectRequestDto input)
{
Check.NotNull(input, nameof(input));
var config = await EnsureToolAllowedAsync(McpToolNames.RunSelect);
var sql = (input.Sql ?? string.Empty).Trim().TrimEnd(';').Trim();
sqlGuard.EnsureReadOnlyStatement(sql);
var maxRows = ResolveMaxRows(config, input.MaxRows);
var parameters = sqlGuard.NormalizeParameters(input.Parameters);
var context = await queryExecutor.ResolveDataSourceAsync(config.DataSourceCode);
var limitedSql = sqlGuard.ApplyRowLimit(sql, maxRows, context.DataSourceType);
return ToResult(await queryExecutor.ExecuteAsync(context, limitedSql, parameters, maxRows));
}
public async Task<List<McpSavedQueryDto>> GetSavedQueriesAsync(string? search = null)
{
var config = await EnsureToolAllowedAsync(McpToolNames.ListQueries);
var results = new List<McpSavedQueryDto>();
if (await AuthorizationService.IsGrantedAsync(SqlPermission))
{
results.AddRange(await GetDatabaseObjectsAsync(config));
}
if (await AuthorizationService.IsGrantedAsync(CustomEndpointPermission))
{
results.AddRange(await GetCallableCustomEndpointsAsync());
}
if (!string.IsNullOrWhiteSpace(search))
{
results = [.. results.Where(item =>
item.Name.Contains(search, StringComparison.OrdinalIgnoreCase)
|| (item.Description?.Contains(search, StringComparison.OrdinalIgnoreCase) ?? false))];
}
return [.. results
.OrderBy(item => item.Kind, StringComparer.Ordinal)
.ThenBy(item => item.Name, StringComparer.OrdinalIgnoreCase)];
}
private async Task<List<McpSavedQueryDto>> GetDatabaseObjectsAsync(McpServerConfigDto config)
{
var context = await queryExecutor.ResolveDataSourceAsync(config.DataSourceCode);
var catalogSql = context.DataSourceType == DataSourceTypeEnum.Postgresql
? McpCatalogQueries.Postgres
: McpCatalogQueries.SqlServer;
// Katalog sorgusunun kendi satir siniri yoktur; nesne sayisi tavani asmaz.
var catalog = await queryExecutor.ExecuteAsync(context, catalogSql, new Dictionary<string, object>(), int.MaxValue);
var results = new List<McpSavedQueryDto>();
foreach (var row in catalog.Rows)
{
var kind = MapCatalogKind(ReadString(row, "ObjectKind"));
if (kind is null)
{
continue;
}
results.Add(new McpSavedQueryDto
{
Kind = kind,
SchemaName = ReadString(row, "SchemaName"),
Name = ReadString(row, "ObjectName") ?? string.Empty
});
}
return results;
}
public async Task<McpQueryResultDto> RunSavedQueryAsync(McpSavedQueryRequestDto input)
{
Check.NotNull(input, nameof(input));
var config = await EnsureToolAllowedAsync(McpToolNames.RunQuery);
if (IsKind(input.Kind, McpConsts.SavedQueryKinds.CustomEndpoint))
{
await AuthorizationService.CheckAsync(CustomEndpointPermission);
return await RunCustomEndpointAsync(input, config);
}
await AuthorizationService.CheckAsync(SqlPermission);
var maxRows = ResolveMaxRows(config, input.MaxRows);
var parameters = sqlGuard.NormalizeParameters(input.Parameters);
var context = await queryExecutor.ResolveDataSourceAsync(config.DataSourceCode);
var schemaName = string.IsNullOrWhiteSpace(input.SchemaName)
? sqlGuard.DefaultSchemaName(context.DataSourceType)
: input.SchemaName;
var qualifiedName = sqlGuard.QuoteQualifiedName(schemaName, input.Name, context.DataSourceType);
var sql = BuildSavedQuerySql(input.Kind, qualifiedName, parameters, maxRows, context.DataSourceType);
return ToResult(await queryExecutor.ExecuteAsync(context, sql, parameters, maxRows));
}
private async Task<McpQueryResultDto> RunCustomEndpointAsync(
McpSavedQueryRequestDto input,
McpServerConfigDto config)
{
var endpoints = await GetCallableEndpointEntitiesAsync();
var endpoint = endpoints.Find(candidate => string.Equals(candidate.Name, input.Name, StringComparison.OrdinalIgnoreCase))
?? throw new UserFriendlyException(L[McpErrorCodes.CustomEndpointNotFound, input.Name]);
var maxRows = ResolveMaxRows(config, input.MaxRows);
var parameters = sqlGuard.NormalizeParameters(input.Parameters);
// Eksik parametre Dapper tarafinda belirsiz bir hataya donusmesin diye burada yakalanir.
var missing = endpoint.Parameters
.Where(parameter => parameter.IsRequired && !parameters.ContainsKey(parameter.Name))
.Select(parameter => parameter.Name)
.ToList();
if (missing.Count > 0)
{
throw new UserFriendlyException(L[McpErrorCodes.MissingParameters, string.Join(", ", missing)]);
}
var context = await queryExecutor.ResolveDataSourceAsync(endpoint.DataSourceCode);
return ToResult(await queryExecutor.ExecuteAsync(context, endpoint.Sql, parameters, maxRows));
}
private async Task<List<McpSavedQueryDto>> GetCallableCustomEndpointsAsync()
{
var endpoints = await GetCallableEndpointEntitiesAsync();
return [.. endpoints.Select(endpoint => new McpSavedQueryDto
{
Kind = McpConsts.SavedQueryKinds.CustomEndpoint,
Name = endpoint.Name,
Description = endpoint.Description,
ParameterNames = [.. endpoint.Parameters.Select(parameter => parameter.Name)]
})];
}
/// <summary>
/// Yalnizca okuma amacli (GET) ve istegi yapan kullaniciya acik Custom Endpoint kayitlari.
/// Yetki sozlesmesi endpoint dispatcher'indaki ile birebir aynidir.
/// </summary>
private async Task<List<CustomEndpoint>> GetCallableEndpointEntitiesAsync()
{
var query = await customEndpointRepository.GetQueryableAsync();
var candidates = query.Where(endpoint => endpoint.Method == "GET").ToList();
return [.. candidates.Where(endpoint => endpoint.Permissions.Any(permission =>
permission.ResourceType == "Global"
|| (permission.ResourceType == "User" && permission.ResourceId == CurrentUser.UserName)
|| (permission.ResourceType == "Role" && CurrentUser.Roles.Contains(permission.ResourceId))))];
}
private string BuildSavedQuerySql(
string? kind,
string qualifiedName,
Dictionary<string, object> parameters,
int maxRows,
DataSourceTypeEnum dataSourceType)
{
var parameterList = string.Join(", ", parameters.Keys.Select(name => $"@{name}"));
if (IsKind(kind, McpConsts.SavedQueryKinds.View))
{
return sqlGuard.ApplyRowLimit($"SELECT * FROM {qualifiedName}", maxRows, dataSourceType);
}
if (IsKind(kind, McpConsts.SavedQueryKinds.TableFunction))
{
return sqlGuard.ApplyRowLimit($"SELECT * FROM {qualifiedName}({parameterList})", maxRows, dataSourceType);
}
if (IsKind(kind, McpConsts.SavedQueryKinds.ScalarFunction))
{
return $"SELECT {qualifiedName}({parameterList}) AS Value";
}
if (IsKind(kind, McpConsts.SavedQueryKinds.Procedure))
{
// Prosedur cagrisina satir tavani yazilamaz; sonuc kumesi okunurken kesilir.
return dataSourceType == DataSourceTypeEnum.Postgresql
? $"CALL {qualifiedName}({parameterList})"
: $"EXEC {qualifiedName} {string.Join(", ", parameters.Keys.Select(name => $"@{name} = @{name}"))}".TrimEnd();
}
throw new UserFriendlyException(L[McpErrorCodes.UnsupportedObjectKind, kind ?? string.Empty]);
}
private static string? NullIfEmpty(string? value)
=> string.IsNullOrWhiteSpace(value) ? null : value;
private static McpQueryResultDto ToResult(McpQueryRows rows) => new()
{
Columns = rows.Columns,
Rows = rows.Rows,
RowCount = rows.Rows.Count,
Truncated = rows.Truncated,
ExecutionTimeMs = rows.ExecutionTimeMs
};
private static bool IsKind(string? kind, string expected)
=> string.Equals(kind, expected, StringComparison.OrdinalIgnoreCase);
private static string? MapCatalogKind(string? catalogKind) => catalogKind switch
{
McpConsts.CatalogKinds.View => McpConsts.SavedQueryKinds.View,
McpConsts.CatalogKinds.Procedure => McpConsts.SavedQueryKinds.Procedure,
McpConsts.CatalogKinds.TableFunction => McpConsts.SavedQueryKinds.TableFunction,
McpConsts.CatalogKinds.ScalarFunction => McpConsts.SavedQueryKinds.ScalarFunction,
_ => null
};
private static string? ReadString(Dictionary<string, object?> row, string key)
=> row.TryGetValue(key, out var value) ? value?.ToString() : null;
}

View file

@ -0,0 +1,214 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Sozsoft.Mcp.Dtos;
using Sozsoft.Mcp.Services;
using Sozsoft.Platform;
using Volo.Abp.Authorization.Permissions;
namespace Sozsoft.Mcp;
/// <summary>
/// Sema kesfini JSON veri sozlugu uzerinden karsilar; sozlugu okuma ve arama normalizasyonu
/// domain servisindedir (<see cref="IMcpSchemaDictionary"/>). Kullanici yalnizca uygulamada
/// okuma yetkisi olan listeleri gorur (<see cref="IMcpListFormAccessFilter"/>).
/// </summary>
[Authorize(PlatformConsts.AppCodes.DeveloperKits.McpServers.Invoke)]
public class McpSchemaAppService(
IMcpServerConfigProvider serverConfigProvider,
IMcpSchemaDictionary dictionary,
IMcpListFormAccessFilter accessFilter,
IPermissionChecker permissionChecker)
: McpAppService(serverConfigProvider), IMcpSchemaAppService
{
public async Task<McpServerConfigDto?> GetServerConfigAsync()
=> await ResolveServerConfigAsync();
public async Task<List<string>> GetAvailableToolsAsync()
{
var config = await ResolveServerConfigAsync();
var candidates = McpToolPolicy.VisibleTools(
config is not null,
config?.AllowWrite ?? false,
config?.AllowedTools ?? []);
var required = candidates
.Where(McpToolNames.RequiredPermissions.ContainsKey)
.Select(tool => McpToolNames.RequiredPermissions[tool])
.Distinct(StringComparer.Ordinal)
.ToArray();
var granted = new HashSet<string>(StringComparer.Ordinal);
if (required.Length > 0)
{
var grants = await permissionChecker.IsGrantedAsync(required);
granted.UnionWith(grants.Result
.Where(result => result.Value == PermissionGrantResult.Granted)
.Select(result => result.Key));
}
return [.. candidates.Where(tool =>
!McpToolNames.RequiredPermissions.TryGetValue(tool, out var permission) || granted.Contains(permission))];
}
public async Task<McpSchemaInfoDto> GetSchemaInfoAsync()
{
var snapshot = await dictionary.GetSnapshotAsync();
if (snapshot is null)
{
return new McpSchemaInfoDto
{
Available = false,
TenantName = dictionary.ResolveTenantName(),
Message = L[McpErrorCodes.DictionaryNotFound, McpConsts.DictionaryProcedureName]
};
}
return new McpSchemaInfoDto
{
Available = true,
CacheKey = snapshot.CacheKey,
RefreshedAt = snapshot.RefreshedAt,
FormCount = snapshot.FormCount,
FieldCount = snapshot.FieldCount,
TenantName = snapshot.TenantName,
DefinitionCultureName = snapshot.DefinitionCultureName,
UiCultureName = snapshot.UiCultureName
};
}
public async Task<List<McpListSummaryDto>> GetListsAsync(string? search = null, int maxResults = 200)
{
await EnsureToolAllowedAsync(McpToolNames.ListLists);
var forms = await GetReadableListFormsAsync();
var normalizedSearch = dictionary.Normalize(search);
var matches = forms.Where(form => normalizedSearch.Length == 0
|| dictionary.Normalize(form.SearchText).Contains(normalizedSearch, StringComparison.Ordinal));
return [.. matches.Take(NormalizeLimit(maxResults, 200)).Select(ToSummary)];
}
public async Task<McpListDetailDto?> GetListAsync(string listFormCode)
{
await EnsureToolAllowedAsync(McpToolNames.DescribeList);
if (string.IsNullOrWhiteSpace(listFormCode))
{
return null;
}
var forms = await GetReadableListFormsAsync();
var form = forms.FirstOrDefault(candidate =>
string.Equals(candidate.ListFormCode, listFormCode, StringComparison.OrdinalIgnoreCase));
if (form is null)
{
return null;
}
var detail = new McpListDetailDto();
CopySummary(form, detail);
detail.Columns = [.. (form.Fields ?? []).Select(ToColumn)];
return detail;
}
public async Task<List<McpSchemaMatchDto>> SearchAsync(string term, int maxResults = 50)
{
await EnsureToolAllowedAsync(McpToolNames.SearchSchema);
var normalized = dictionary.Normalize(term);
if (normalized.Length == 0)
{
return [];
}
var limit = NormalizeLimit(maxResults, 50);
var forms = await GetReadableListFormsAsync();
var results = new List<McpSchemaMatchDto>();
foreach (var form in forms)
{
var formMatches = dictionary.Normalize(form.SearchText).Contains(normalized, StringComparison.Ordinal);
var columnMatches = (form.Fields ?? [])
.Where(field => dictionary.Normalize(field.Field).Contains(normalized, StringComparison.Ordinal)
|| dictionary.Normalize(field.DisplayName).Contains(normalized, StringComparison.Ordinal))
.Select(ToColumn)
.ToList();
if (!formMatches && columnMatches.Count == 0)
{
continue;
}
results.Add(new McpSchemaMatchDto
{
ListFormCode = form.ListFormCode ?? string.Empty,
DisplayName = form.DisplayName,
TableName = NullIfEmpty(form.TableName),
MatchedColumns = columnMatches
});
if (results.Count >= limit)
{
break;
}
}
return results;
}
private async Task<List<McpDictionaryListForm>> GetReadableListFormsAsync()
{
var forms = await dictionary.GetListFormsAsync();
var readable = await accessFilter.GetReadableListFormCodesAsync();
return [.. forms.Where(form => form.ListFormCode is not null && readable.Contains(form.ListFormCode))];
}
private static McpListSummaryDto ToSummary(McpDictionaryListForm form)
{
var summary = new McpListSummaryDto();
CopySummary(form, summary);
return summary;
}
private static void CopySummary(McpDictionaryListForm form, McpListSummaryDto target)
{
target.ListFormCode = form.ListFormCode ?? string.Empty;
target.DisplayName = NullIfEmpty(form.DisplayName);
target.Title = NullIfEmpty(form.Title);
target.Description = NullIfEmpty(form.Description);
target.TableName = NullIfEmpty(form.TableName);
target.SqlSourceName = NullIfEmpty(form.SqlSourceName);
target.SourceKind = NullIfEmpty(form.SourceKind);
target.KeyFieldName = NullIfEmpty(form.KeyFieldName);
target.FieldCount = form.Fields?.Count ?? 0;
}
private static McpColumnDto ToColumn(McpDictionaryField field) => new()
{
Field = field.Field ?? string.Empty,
DisplayName = NullIfEmpty(field.DisplayName),
SqlDataType = NullIfEmpty(field.SqlDataType),
IsVisible = field.IsVisible,
IsBooleanColumn = field.IsBooleanColumn,
LookupTableName = NullIfEmpty(field.LookupTableName),
LookupValueColumn = NullIfEmpty(field.LookupValueColumn),
LookupDisplayColumn = NullIfEmpty(field.LookupDisplayColumn),
IsSensitive = field.IsSensitive,
IsDefaultSelectColumn = field.IsDefaultSelectColumn
};
private static int NormalizeLimit(int requested, int fallback)
=> requested <= 0 ? fallback : Math.Min(requested, fallback * 5);
private static string? NullIfEmpty(string? value)
=> string.IsNullOrWhiteSpace(value) ? null : value;
}

View file

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\common.props" />
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<RootNamespace>Sozsoft.Mcp</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Ddd.Application" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Sozsoft.Mcp.Application.Contracts\Sozsoft.Mcp.Application.Contracts.csproj" />
<ProjectReference Include="..\Sozsoft.Mcp.Domain\Sozsoft.Mcp.Domain.csproj" />
<!-- Veri yazma ekranin insert/update/delete sozlesmesinden gecer. -->
<ProjectReference Include="..\..\..\src\Sozsoft.Platform.Application.Contracts\Sozsoft.Platform.Application.Contracts.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,104 @@
namespace Sozsoft.Mcp;
/// <summary>MCP modulunun sabit sozlesme degerleri.</summary>
public static class McpConsts
{
/// <summary>
/// Veri sozlugu satirlarinin turu. <c>Sas_H_DbTableColumn</c> tablosunda ayni deger
/// <c>DictionaryType</c> kolonunda durur.
/// </summary>
public const string DictionaryType = "ListFormJson";
/// <summary>
/// Sozluk ureten prosedurun varsayilan <c>@TenantName</c> degeri. Host baglaminda sozluk
/// bu adla yazilir.
/// </summary>
public const string DefaultDictionaryTenantName = "Sozsoft";
/// <summary>Sunucu kaydinda satir tavani verilmediginde (ya da sifir oldugunda) uygulanan deger.</summary>
public const int DefaultMaxRows = 200;
/// <summary>
/// <c>CustomEndpoint.DataSourceCode</c> bu degeri tasidiginda istegi yapan kullanicinin
/// kiraci veritabani kullanilir.
/// </summary>
public const string TenantDataSourceMarker = "!Tenant";
/// <summary>
/// Sozluk ureten prosedurun adi. Sozluk bulunamadiginda istemciye ne yapacagi bu adla
/// bildirilir.
/// </summary>
public const string DictionaryProcedureName = "dbo.Sas_H_ListFormFieldsDictionaryJson_SaveDbTableColumns";
/// <summary>
/// Uzak MCP istemcilerinin (Claude, ChatGPT connector'lari) OAuth istemci kimligi. OpenIddict
/// uygulamasi DbMigrator'da <c>OpenIddict:Applications:Platform_Mcp</c> ayarindan seed edilir;
/// dinamik istemci kaydi yeni uygulama uretmez, bu kimligi dondurur.
/// </summary>
public const string OAuthClientId = "Platform_Mcp";
/// <summary>
/// Dinamik kayitla (RFC 7591) uretilen istemcilerin kimlik oneki. Bilinen saglayicilarin
/// (Claude, ChatGPT) disindaki MCP istemcileri bu onekle kendi OpenIddict uygulamasini alir.
/// </summary>
public const string DynamicClientIdPrefix = "mcp_";
/// <summary>
/// Dinamik istemci sayisinin tavani. Kayit ucu kimlik dogrulamasizdir; tavan, ucun
/// veritabaninda sinirsiz uygulama biriktirmesini onler.
/// </summary>
public const int MaxDynamicClients = 500;
/// <summary>
/// MCP istemcilerinin yenileme token omru. Web istemcisinin kisa omru (dakikalar) connector
/// icin uygun degil: kullanici birkac saat soru sormadiginda baglanti kopardi. Yenileme
/// token'i her kullanimda yenilenir; omur, son kullanimdan itibaren sayilir.
/// </summary>
public static readonly System.TimeSpan RefreshTokenLifetime = System.TimeSpan.FromDays(30);
/// <summary>Istemci kimligi bir MCP istemcisine (bilinen ya da dinamik) mi ait.</summary>
public static bool IsMcpClient(string? clientId)
=> clientId is not null
&& (string.Equals(clientId, OAuthClientId, System.StringComparison.Ordinal)
|| clientId.StartsWith(DynamicClientIdPrefix, System.StringComparison.Ordinal));
/// <summary>MCP ucunun sunucu adresine gore yolu.</summary>
public const string EndpointPath = "/mcp";
/// <summary>Dinamik istemci kaydi (RFC 7591) ucunun yolu.</summary>
public const string RegistrationEndpointPath = "/connect/register";
/// <summary>Korumali kaynak meta verisi (RFC 9728) ucunun yolu.</summary>
public const string ProtectedResourceMetadataPath = "/.well-known/oauth-protected-resource";
/// <summary>
/// Istemcinin istemesi gereken kapsamlar. <c>offline_access</c> yenileme token'i icindir;
/// olmadan baglanti her saat yeniden giris ister.
/// </summary>
public static readonly string[] OAuthScopes = [Sozsoft.Platform.PlatformConsts.AppName, "offline_access"];
/// <summary>
/// <c>run_query</c> araciyla cagrilabilen nesne turleri. Degerler istemciye oldugu gibi
/// gosterilir; degistirilirse arac sozlesmesi kirilir.
/// </summary>
public static class SavedQueryKinds
{
public const string View = "view";
public const string Procedure = "procedure";
public const string TableFunction = "tableFunction";
public const string ScalarFunction = "scalarFunction";
public const string CustomEndpoint = "customEndpoint";
}
/// <summary>
/// Veritaban&#305; katalog sorgusunun dondurdugu nesne turu etiketleri.
/// <see cref="SavedQueryKinds"/> degerlerine eslenir.
/// </summary>
public static class CatalogKinds
{
public const string View = "VIEW";
public const string Procedure = "PROCEDURE";
public const string TableFunction = "TABLE_FUNCTION";
public const string ScalarFunction = "SCALAR_FUNCTION";
}
}

View file

@ -0,0 +1,18 @@
using Volo.Abp.Domain;
using Volo.Abp.Modularity;
using Volo.Abp.Validation;
namespace Sozsoft.Mcp;
/// <summary>
/// Modulun paylasilan sozlesmesi. Kullaniciya gorunen metinler platformun kendi
/// <c>PlatformResource</c> kaynagindan (LanguagesData.json) okunur; modul ikinci bir dil
/// kaynagi acmaz.
/// </summary>
[DependsOn(
typeof(AbpValidationModule),
typeof(AbpDddDomainSharedModule)
)]
public class McpDomainSharedModule : AbpModule
{
}

View file

@ -0,0 +1,28 @@
namespace Sozsoft.Mcp;
/// <summary>
/// MCP hatalarinin dil anahtarlari. Karsiliklari platformun <c>LanguagesData.json</c>
/// dosyasinda EN + TR olarak durur; koda gomulu metin kullanilmaz.
/// </summary>
public static class McpErrorCodes
{
private const string Prefix = "App.Mcp.Error.";
public const string NoServerRecord = Prefix + "NoServerRecord";
public const string ToolDisabled = Prefix + "ToolDisabled";
public const string EmptyQuery = Prefix + "EmptyQuery";
public const string SingleStatementOnly = Prefix + "SingleStatementOnly";
public const string ReadOnlyStatementRequired = Prefix + "ReadOnlyStatementRequired";
public const string ForbiddenKeyword = Prefix + "ForbiddenKeyword";
public const string InvalidIdentifier = Prefix + "InvalidIdentifier";
public const string InvalidParameterName = Prefix + "InvalidParameterName";
public const string UnsupportedObjectKind = Prefix + "UnsupportedObjectKind";
public const string CustomEndpointNotFound = Prefix + "CustomEndpointNotFound";
public const string MissingParameters = Prefix + "MissingParameters";
public const string ListFormCodeRequired = Prefix + "ListFormCodeRequired";
public const string ValuesRequired = Prefix + "ValuesRequired";
public const string KeysRequired = Prefix + "KeysRequired";
public const string DictionaryNotFound = Prefix + "DictionaryNotFound";
public const string RegistrationLimitReached = Prefix + "RegistrationLimitReached";
public const string RedirectUriNotAllowed = Prefix + "RedirectUriNotAllowed";
}

View file

@ -0,0 +1,73 @@
using System.Collections.Generic;
using System.Linq;
using Sozsoft.Platform;
namespace Sozsoft.Mcp;
/// <summary>
/// /mcp ucunda yayinlanan arac adlari. Ayni degerler MCP Servers ekranindaki
/// <c>AllowedTools</c> alaninin secenek listesini besler; buraya bir arac eklenirse ekranin
/// statik lookup listesi de guncellenir.
/// </summary>
public static class McpToolNames
{
public const string ServerInfo = "server_info";
public const string ListLists = "list_lists";
public const string DescribeList = "describe_list";
public const string SearchSchema = "search_schema";
public const string QueryList = "query_list";
public const string RunSelect = "run_select";
public const string ListQueries = "list_queries";
public const string RunQuery = "run_query";
public const string InsertRow = "insert_row";
public const string UpdateRow = "update_row";
public const string DeleteRow = "delete_row";
/// <summary>
/// Veri yazan araclar. Sunucu kaydinda <c>AllowWrite</c> kapaliysa bunlar arac listesinde
/// hic yer almaz.
/// </summary>
public static readonly string[] WriteTools = [InsertRow, UpdateRow, DeleteRow];
/// <summary>
/// Sunucu kaydi olmasa da acik kalan araclar. Istemcinin baglantisinin neden bos
/// gorundugunu anlayabilmesi icin gereklidir.
/// </summary>
public static readonly string[] AlwaysAvailableTools = [ServerInfo];
/// <summary>
/// Kullanicinin uygulamada da sahip olmasi gereken yetki. MCP yetki vermez: ham SQL'i
/// uygulamada SQL Query Manager'i kullanabilen, veri yazmayi yazma yetkisi olan kullanici
/// gorur. Listede olmayan araclar kendi icinde liste bazli yetkiyle suzulur.
/// </summary>
public static readonly IReadOnlyDictionary<string, string> RequiredPermissions = new Dictionary<string, string>
{
[RunSelect] = PlatformConsts.AppCodes.SqlQueryManagers.Default,
[InsertRow] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
[UpdateRow] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
[DeleteRow] = PlatformConsts.AppCodes.DeveloperKits.McpServers.Write,
};
/// <summary>Yayinlanan tum araclar, arac listesindeki siralarina gore.</summary>
public static readonly string[] All =
[
ServerInfo,
ListLists,
DescribeList,
SearchSchema,
QueryList,
RunSelect,
ListQueries,
RunQuery,
InsertRow,
UpdateRow,
DeleteRow
];
/// <summary>
/// <c>AllowedTools</c> alaninda secilebilen araclar. Her zaman acik olanlar secenek
/// olarak sunulmaz; onlari kapatmak mumkun degildir.
/// </summary>
public static IEnumerable<string> SelectableTools
=> All.Where(name => !AlwaysAvailableTools.Contains(name));
}

View file

@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Sozsoft.Mcp;
/// <summary>
/// Bir aracin bir sunucu kaydina gore acik olup olmadigina karar veren kural. Karar iki yerde
/// kullanilir: arac listesi suzulurken ve arac cagrilirken. Listeyi suzmek tek basina bir
/// yetki siniri degildir — istemci listede olmayan bir araci da cagirabilir.
/// </summary>
public static class McpToolPolicy
{
/// <param name="hasServerRecord">Istegin kiracisina dusen aktif bir sunucu kaydi var mi.</param>
/// <param name="allowWrite">Sunucu kaydinda veri yazma acik mi.</param>
/// <param name="allowedTools">Acik arac adlari; bos liste "yazma disindaki tumu acik" demektir.</param>
public static bool IsAllowed(
bool hasServerRecord,
bool allowWrite,
IReadOnlyCollection<string> allowedTools,
string toolName)
{
if (McpToolNames.AlwaysAvailableTools.Contains(toolName, StringComparer.Ordinal))
{
return true;
}
if (!hasServerRecord)
{
return false;
}
if (!allowWrite && McpToolNames.WriteTools.Contains(toolName, StringComparer.Ordinal))
{
return false;
}
if (allowedTools.Count == 0)
{
return true;
}
return allowedTools.Contains(toolName, StringComparer.OrdinalIgnoreCase);
}
public static IReadOnlyList<string> VisibleTools(
bool hasServerRecord,
bool allowWrite,
IReadOnlyCollection<string> allowedTools)
=> [.. McpToolNames.All.Where(name => IsAllowed(hasServerRecord, allowWrite, allowedTools, name))];
}

View file

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\common.props" />
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<RootNamespace>Sozsoft.Mcp</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Ddd.Domain.Shared" Version="10.0.0" />
<PackageReference Include="Volo.Abp.Validation" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<!-- Yetki adlari ve tablo adi platformun tek kayit defterinde durur
(PlatformConsts.AppCodes, TableNameResolver); modul bunlari kopyalamaz. -->
<ProjectReference Include="..\..\..\src\Sozsoft.Platform.Domain.Shared\Sozsoft.Platform.Domain.Shared.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,36 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Sozsoft.Mcp.Services;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
namespace Sozsoft.Mcp.Data;
/// <summary>
/// Bilinen MCP istemcisini (<see cref="McpConsts.OAuthClientId"/>) seed eder. Geri donus
/// adresleri <c>OpenIddict:Applications:Platform_Mcp:RedirectUris</c> ayarindadir; yeni bir
/// saglayici eklemek bu listeye callback eklemektir.
/// </summary>
public class McpOAuthClientDataSeedContributor(
IConfiguration configuration,
IMcpOAuthClientManager clientManager) : IDataSeedContributor, ITransientDependency
{
private const string RedirectUrisKey = "OpenIddict:Applications:Platform_Mcp:RedirectUris";
public async Task SeedAsync(DataSeedContext context)
{
// OpenIddict uygulamalari host kapsamindadir; kiraci seed turlarinda tekrarlanmaz.
if (context.TenantId is not null)
{
return;
}
var redirectUris = configuration.GetSection(RedirectUrisKey).Get<string[]>();
if (redirectUris is null || redirectUris.Length == 0)
{
return;
}
await clientManager.EnsureKnownClientAsync(redirectUris);
}
}

View file

@ -0,0 +1,49 @@
using System;
using Volo.Abp.Domain.Entities.Auditing;
namespace Sozsoft.Mcp.Entities;
/// <summary>
/// MCP (Model Context Protocol) sunucu tanimi. Kayit host kapsamindadir; hangi kiracilarin
/// kullanabilecegi <see cref="Tenants"/> ile belirlenir. Istegin kiracisina dusen kayit, /mcp
/// ucunda hangi araclarin gorunecegini ve sorgularin hangi veri kaynaginda hangi satir
/// tavaniyla kosacagini belirler.
/// </summary>
public class McpServer : FullAuditedEntity<Guid>
{
public string Name { get; set; } = string.Empty;
public bool IsActive { get; set; } = true;
/// <summary>
/// Istemciye verilen baglanti adresi (ornek: https://api.sozsoft.com/mcp). Yonlendirme icin
/// kullanilmaz; ekranin kopyalanabilir baglanti komutu uretebilmesi icin tutulur.
/// </summary>
public string? Url { get; set; }
/// <summary>
/// Kaydin gecerli oldugu kiraci adlari, <c>PlatformConsts.MultiValueDelimiter</c> ile
/// ayrilir. Bos ise tum kiracilara aciktir.
/// </summary>
public string? Tenants { get; set; }
/// <summary>
/// Sorgu araclarinin kosacagi DataSource kodu. Bos ise istegin kapsamindaki varsayilan
/// veri kaynagi kullanilir.
/// </summary>
public string? DataSourceCode { get; set; }
/// <summary>
/// Acik arac adlari, <c>PlatformConsts.MultiValueDelimiter</c> ile ayrilir. Bos ise yazma
/// disindaki tum araclar aciktir.
/// </summary>
public string? AllowedTools { get; set; }
/// <summary>Tek sorgunun dondurebilecegi en fazla satir sayisi.</summary>
public int MaxRows { get; set; } = McpConsts.DefaultMaxRows;
/// <summary>Kapali oldugunda veri yazma araclari arac listesinde hic yer almaz.</summary>
public bool AllowWrite { get; set; }
public string? Description { get; set; }
}

View file

@ -0,0 +1,14 @@
using Volo.Abp.Domain;
using Volo.Abp.Modularity;
using Volo.Abp.OpenIddict;
namespace Sozsoft.Mcp;
[DependsOn(
typeof(AbpDddDomainModule),
typeof(AbpOpenIddictDomainModule),
typeof(McpDomainSharedModule)
)]
public class McpDomainModule : AbpModule
{
}

View file

@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Sozsoft.Platform.Entities;
using Sozsoft.Platform.Permissions;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Services;
namespace Sozsoft.Mcp.Services;
/// <summary>
/// Kullanicinin okuyabildigi listeleri belirler. Karar ekranin kendisiyle aynidir: ListForm'un
/// <c>PermissionJson.R</c> yetkisi (bkz. <c>ListFormAuthorizationManager</c>). MCP bu kurala
/// ek bir yetki tanimlamaz; uygulamada acamadigin listeyi MCP'de de goremezsin.
/// </summary>
public interface IMcpListFormAccessFilter
{
Task<IReadOnlySet<string>> GetReadableListFormCodesAsync();
}
public class McpListFormAccessFilter(
IRepository<ListForm, Guid> listFormRepository,
IPermissionChecker permissionChecker)
: DomainService, IMcpListFormAccessFilter
{
public async Task<IReadOnlySet<string>> GetReadableListFormCodesAsync()
{
// ListFormAuthorizationManager.CanAccess liste basina bir sorgu ve bir yetki kontrolu
// yapar; sozlukteki yuzlerce liste icin bu tek sorgu + tek toplu kontrole indirilir.
var query = await listFormRepository.GetQueryableAsync();
var definitions = query
.Select(listForm => new { listForm.ListFormCode, listForm.PermissionJson })
.ToList()
.GroupBy(definition => definition.ListFormCode, StringComparer.OrdinalIgnoreCase)
.Select(group => group.First())
.ToList();
var readPermissions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var definition in definitions)
{
var permission = ReadPermissionOf(definition.PermissionJson);
if (permission is not null)
{
readPermissions[definition.ListFormCode] = permission;
}
}
var distinctPermissions = readPermissions.Values.Distinct(StringComparer.Ordinal).ToArray();
if (distinctPermissions.Length == 0)
{
return new HashSet<string>(StringComparer.OrdinalIgnoreCase);
}
var grants = await permissionChecker.IsGrantedAsync(distinctPermissions);
var granted = grants.Result
.Where(result => result.Value == PermissionGrantResult.Granted)
.Select(result => result.Key)
.ToHashSet(StringComparer.Ordinal);
return readPermissions
.Where(entry => granted.Contains(entry.Value))
.Select(entry => entry.Key)
.ToHashSet(StringComparer.OrdinalIgnoreCase);
}
private static string? ReadPermissionOf(string? permissionJson)
{
if (string.IsNullOrWhiteSpace(permissionJson))
{
return null;
}
try
{
var permissions = JsonSerializer.Deserialize<PermissionCrudValueObject>(permissionJson);
return string.IsNullOrWhiteSpace(permissions?.R) ? null : permissions.R;
}
catch (JsonException)
{
// Bozuk yetki tanimi ekranda da erisimi kapatir (CanAccess false doner).
return null;
}
}
}

View file

@ -0,0 +1,246 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using OpenIddict.Abstractions;
using Sozsoft.Platform;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Services;
using Volo.Abp.MultiTenancy;
using Volo.Abp.OpenIddict.Applications;
namespace Sozsoft.Mcp.Services;
/// <summary>
/// Uzak MCP istemcilerinin OpenIddict uygulamalari. Iki tur istemci vardir:
/// <list type="bullet">
/// <item><b>Bilinen istemci</b> (<see cref="McpConsts.OAuthClientId"/>): geri donus adresleri
/// DbMigrator ayarinda sabit olan saglayicilar (Claude, ChatGPT). Onay ekrani gostermez.</item>
/// <item><b>Dinamik istemci</b> (<see cref="McpConsts.DynamicClientIdPrefix"/>): baska bir MCP
/// istemcisi (Cursor, VS Code, Claude Code, ...) kayit ucundan kendi uygulamasini alir. Kayit
/// herkese acik oldugu icin bu istemciler <b>acik onay</b> ister: kullanici giris yaptiktan sonra
/// hangi uygulamanin erisim istedigini gorup onaylar.</item>
/// </list>
/// Iki turde de token kullanicinin kendi yetkileriyle uretilir; istemci yetki kazanmaz.
/// </summary>
public interface IMcpOAuthClientManager
{
/// <summary>Bilinen istemciyi olusturur ya da geri donus adreslerini gunceller.</summary>
Task EnsureKnownClientAsync(IReadOnlyCollection<string> redirectUris);
/// <summary>
/// Istemci kaydi. Adresler bilinen istemcide kayitliysa onu, degilse ayni adres kumesine
/// sahip dinamik istemciyi dondurur; yoksa yenisini uretir.
/// </summary>
Task<McpClientRegistration> RegisterAsync(string? clientName, IReadOnlyCollection<string> redirectUris);
}
public class McpOAuthClientManager(
IAbpApplicationManager applicationManager,
IRepository<OpenIddictApplication, Guid> applicationRepository,
ICurrentTenant currentTenant)
: DomainService, IMcpOAuthClientManager
{
private const int MaxClientNameLength = 100;
/// <summary>Tarayici disi calistirilabilir icerik tasiyan, geri donus adresi olamayacak semalar.</summary>
private static readonly string[] ForbiddenSchemes = ["javascript", "data", "file", "vbscript", "about", "blob"];
public async Task EnsureKnownClientAsync(IReadOnlyCollection<string> redirectUris)
{
// OpenIddict uygulamalari host kapsamindadir.
using (currentTenant.Change(null))
{
var existing = await applicationManager.FindByClientIdAsync(McpConsts.OAuthClientId);
var descriptor = BuildDescriptor(
McpConsts.OAuthClientId,
"MCP Connectors",
redirectUris,
OpenIddictConstants.ConsentTypes.Implicit);
if (existing is null)
{
await applicationManager.CreateAsync(descriptor);
return;
}
// Tanim tamamen dosyadan gelir; ayar degistiyse (yeni saglayici callback'i, token omru)
// kayit yeniden yazilir. Kimlik ve istemci turu degismez.
await applicationManager.PopulateAsync(existing, descriptor);
await applicationManager.UpdateAsync(existing);
}
}
public async Task<McpClientRegistration> RegisterAsync(string? clientName, IReadOnlyCollection<string> redirectUris)
{
if (redirectUris.Count == 0)
{
return McpClientRegistration.Rejected(McpClientRegistrationError.InvalidRedirectUri, string.Empty);
}
var invalid = redirectUris.FirstOrDefault(uri => !IsAcceptableRedirectUri(uri));
if (invalid is not null)
{
return McpClientRegistration.Rejected(McpClientRegistrationError.InvalidRedirectUri, invalid);
}
using (currentTenant.Change(null))
{
var known = await applicationManager.FindByClientIdAsync(McpConsts.OAuthClientId);
if (known is not null)
{
var knownUris = await applicationManager.GetRedirectUrisAsync(known);
if (redirectUris.All(uri => knownUris.Any(registered => SameUri(registered, uri))))
{
return McpClientRegistration.Accepted(McpConsts.OAuthClientId);
}
}
var dynamicClients = await GetDynamicClientsAsync();
// Ayni istemci her baglantida yeniden kayit olabilir; ayni adres kumesi tek uygulamaya
// baglanir, tablo buyumez.
var match = dynamicClients.FirstOrDefault(client => SameUriSet(client.RedirectUris, redirectUris));
if (match.ClientId is not null)
{
return McpClientRegistration.Accepted(match.ClientId);
}
if (dynamicClients.Count >= McpConsts.MaxDynamicClients)
{
return McpClientRegistration.Rejected(McpClientRegistrationError.LimitReached, McpConsts.MaxDynamicClients.ToString(CultureInfo.InvariantCulture));
}
var clientId = McpConsts.DynamicClientIdPrefix + GuidGenerator.Create().ToString("N");
await applicationManager.CreateAsync(BuildDescriptor(
clientId,
DisplayNameOf(clientName, redirectUris.First()),
redirectUris,
OpenIddictConstants.ConsentTypes.Explicit));
return McpClientRegistration.Accepted(clientId);
}
}
private async Task<List<(string? ClientId, IReadOnlyList<string> RedirectUris)>> GetDynamicClientsAsync()
{
var query = await applicationRepository.GetQueryableAsync();
var rows = query
.Where(application => application.ClientId!.StartsWith(McpConsts.DynamicClientIdPrefix))
.Select(application => new { application.ClientId, application.RedirectUris })
.ToList();
return [.. rows.Select(row => ((string?)row.ClientId, ParseUris(row.RedirectUris)))];
}
private static AbpApplicationDescriptor BuildDescriptor(
string clientId,
string displayName,
IEnumerable<string> redirectUris,
string consentType)
{
var descriptor = new AbpApplicationDescriptor
{
ClientId = clientId,
ClientType = OpenIddictConstants.ClientTypes.Public,
ConsentType = consentType,
DisplayName = displayName
};
descriptor.Permissions.UnionWith(
[
OpenIddictConstants.Permissions.Endpoints.Authorization,
OpenIddictConstants.Permissions.Endpoints.Token,
OpenIddictConstants.Permissions.Endpoints.Revocation,
OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode,
OpenIddictConstants.Permissions.GrantTypes.RefreshToken,
OpenIddictConstants.Permissions.ResponseTypes.Code,
OpenIddictConstants.Permissions.Prefixes.Scope + PlatformConsts.AppName
]);
// Genel istemci gizli anahtar tasiyamaz; yetkilendirme kodunun calinmasina karsi PKCE
// zorunludur.
descriptor.Requirements.Add(OpenIddictConstants.Requirements.Features.ProofKeyForCodeExchange);
descriptor.Settings[OpenIddictConstants.Settings.TokenLifetimes.RefreshToken] =
McpConsts.RefreshTokenLifetime.ToString("c", CultureInfo.InvariantCulture);
foreach (var redirectUri in redirectUris)
{
descriptor.RedirectUris.Add(new Uri(redirectUri, UriKind.Absolute));
}
return descriptor;
}
/// <summary>
/// Kabul edilen geri donus adresleri: HTTPS, yerel makineye donen HTTP (RFC 8252 §7.3 —
/// masaustu istemciler rastgele portla dinler) ve masaustu uygulamalarin ozel semalari
/// (RFC 8252 §7.1, ornek: <c>cursor://...</c>). Yerel olmayan duz HTTP reddedilir.
/// </summary>
private static bool IsAcceptableRedirectUri(string value)
{
if (!Uri.TryCreate(value, UriKind.Absolute, out var uri) || !string.IsNullOrEmpty(uri.Fragment))
{
return false;
}
if (uri.Scheme == Uri.UriSchemeHttps)
{
return true;
}
if (uri.Scheme == Uri.UriSchemeHttp)
{
return uri.IsLoopback;
}
return !ForbiddenSchemes.Contains(uri.Scheme, StringComparer.OrdinalIgnoreCase);
}
/// <summary>
/// Onay ekraninda gorunen ad. Istemci adi kayit isteginden gelir ve dogrulanamaz (herkes
/// kendine "Claude" diyebilir); bu yuzden token'in gidecegi adresin sahibi de gosterilir.
/// </summary>
private static string DisplayNameOf(string? clientName, string redirectUri)
{
var name = string.IsNullOrWhiteSpace(clientName) ? "MCP Client" : clientName.Trim();
if (name.Length > MaxClientNameLength)
{
name = name[..MaxClientNameLength];
}
var uri = new Uri(redirectUri, UriKind.Absolute);
var owner = uri.IsLoopback ? "localhost" : string.IsNullOrEmpty(uri.Host) ? uri.Scheme : uri.Host;
return $"{name} ({owner})";
}
private static IReadOnlyList<string> ParseUris(string? json)
=> string.IsNullOrWhiteSpace(json) ? [] : JsonSerializer.Deserialize<List<string>>(json) ?? [];
private static bool SameUriSet(IReadOnlyCollection<string> left, IReadOnlyCollection<string> right)
=> left.Count == right.Count && right.All(uri => left.Any(candidate => SameUri(candidate, uri)));
/// <summary>OpenIddict adresleri <see cref="Uri"/> bicimiyle saklar; karsilastirma ayni bicimle yapilir.</summary>
private static bool SameUri(string left, string right)
=> Uri.TryCreate(left, UriKind.Absolute, out var a)
&& Uri.TryCreate(right, UriKind.Absolute, out var b)
&& string.Equals(a.AbsoluteUri, b.AbsoluteUri, StringComparison.Ordinal);
}
public enum McpClientRegistrationError
{
InvalidRedirectUri,
LimitReached
}
/// <summary>Kayit sonucu: basariliysa istemci kimligi, degilse hata ve hataya konu deger.</summary>
public sealed record McpClientRegistration(string? ClientId, McpClientRegistrationError? Error, string? ErrorValue)
{
public static McpClientRegistration Accepted(string clientId) => new(clientId, null, null);
public static McpClientRegistration Rejected(McpClientRegistrationError error, string value) => new(null, error, value);
}

View file

@ -0,0 +1,105 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Sozsoft.Platform.Data.Seeds;
using Sozsoft.Platform.DynamicData;
using Sozsoft.Platform.Enums;
using Volo.Abp.Domain.Services;
namespace Sozsoft.Mcp.Services;
/// <summary>
/// Sorguyu istegin kapsamindaki veri kaynaginda kosturur ve satir tavanini uygular.
/// Veri kaynagi cozumu tek yerde kalsin diye burada toplanmistir.
/// </summary>
public interface IMcpQueryExecutor
{
/// <param name="dataSourceCode">
/// Sunucu kaydindaki kod. Bos ya da <c>!Tenant</c> ise veri kaynagi istegin kiracisindan
/// cozulur; host baglaminda varsayilan kaynaga duser.
/// </param>
Task<McpDataSourceContext> ResolveDataSourceAsync(string? dataSourceCode);
/// <summary>
/// Sorguyu kosturur. Sonuc kumesi <paramref name="maxRows"/> satirda kesilir; kesilme
/// olduysa sonuc bunu bildirir.
/// </summary>
Task<McpQueryRows> ExecuteAsync(
McpDataSourceContext context,
string sql,
IReadOnlyDictionary<string, object> parameters,
int maxRows);
}
public class McpQueryExecutor(IDynamicDataManager dynamicDataManager) : DomainService, IMcpQueryExecutor
{
public async Task<McpDataSourceContext> ResolveDataSourceAsync(string? dataSourceCode)
{
var useTenantScope = string.IsNullOrWhiteSpace(dataSourceCode)
|| dataSourceCode == McpConsts.TenantDataSourceMarker;
var resolvedCode = useTenantScope
? SeedConsts.DataSources.DefaultCode
: dataSourceCode!;
var (repository, connectionString, dataSourceType) =
await dynamicDataManager.GetAsync(useTenantScope, resolvedCode);
return new McpDataSourceContext(repository, connectionString, dataSourceType);
}
public async Task<McpQueryRows> ExecuteAsync(
McpDataSourceContext context,
string sql,
IReadOnlyDictionary<string, object> parameters,
int maxRows)
{
var stopwatch = Stopwatch.StartNew();
var rows = await context.Repository.QueryAsync(
sql,
context.ConnectionString,
parameters.ToDictionary(entry => entry.Key, entry => entry.Value));
stopwatch.Stop();
var columns = new List<string>();
var materialized = new List<Dictionary<string, object?>>();
var truncated = false;
foreach (var row in rows)
{
if (materialized.Count >= maxRows)
{
truncated = true;
break;
}
if (row is not IDictionary<string, object> values)
{
continue;
}
if (columns.Count == 0)
{
columns = [.. values.Keys];
}
materialized.Add(values.ToDictionary(entry => entry.Key, entry => (object?)entry.Value));
}
return new McpQueryRows(columns, materialized, truncated, stopwatch.ElapsedMilliseconds);
}
}
/// <summary>Cozumlenmis veri kaynagi: hangi depo, hangi baglanti, hangi saglayici.</summary>
public sealed record McpDataSourceContext(
IDynamicDataRepository Repository,
string ConnectionString,
DataSourceTypeEnum DataSourceType);
/// <summary>Sorgu sonucunun ham hali.</summary>
public sealed record McpQueryRows(
List<string> Columns,
List<Dictionary<string, object?>> Rows,
bool Truncated,
long ExecutionTimeMs);

View file

@ -0,0 +1,188 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Sozsoft.Platform.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Services;
using Volo.Abp.MultiTenancy;
namespace Sozsoft.Mcp.Services;
/// <summary>
/// Sema bilgisinin tek kaynagi: <c>Sas_H_DbTableColumn</c> tablosundaki JSON veri sozlugu.
/// Sozluk kullaniciya gorunen basliklari ve lookup iliskilerini birlikte tasidigi icin canli
/// katalog yerine bu kullanilir — modelin gordugu adlar ekranda gorunen adlarla ayni olur.
/// </summary>
public interface IMcpSchemaDictionary
{
/// <summary>Sozlugun bulunup bulunmadigi ve ne zaman tazelendigi; yoksa null.</summary>
Task<McpDictionarySnapshot?> GetSnapshotAsync();
/// <summary>Sozlukteki listeler; sozluk yoksa bos liste.</summary>
Task<IReadOnlyList<McpDictionaryListForm>> GetListFormsAsync();
/// <summary>
/// Istegin kapsamina dusen sozluk kiraci adi. Sozluk ureten prosedurun
/// <c>@TenantName</c> parametresiyle ayni degeri tasimasi gerekir.
/// </summary>
string ResolveTenantName();
/// <summary>
/// Aksan ve buyuk/kucuk harf duyarsiz karsilastirma anahtari; "Musteri" aramasi
/// "Müşteri" basligini da bulur.
/// </summary>
string Normalize(string? value);
}
public class McpSchemaDictionary(
IRepository<DbTableColumn, Guid> dictionaryRepository,
ICurrentTenant currentTenant)
: DomainService, IMcpSchemaDictionary
{
private static readonly JsonSerializerOptions DictionaryJsonOptions = new()
{
PropertyNameCaseInsensitive = true
};
private static readonly CultureInfo TurkishCulture = CultureInfo.GetCultureInfo("tr-TR");
public string ResolveTenantName()
=> currentTenant.Name ?? McpConsts.DefaultDictionaryTenantName;
public async Task<McpDictionarySnapshot?> GetSnapshotAsync()
{
// Tazelik bilgisi icin JsonData (buyuk sozluklerde megabaytlar) okunmaz.
using (currentTenant.Change(null))
{
var query = await ActiveRowsAsync(ResolveTenantName());
return query
.Select(row => new McpDictionarySnapshot(
row.CacheKey,
row.RefreshedAt,
row.FormCount,
row.FieldCount,
row.TenantName,
row.DefinitionCultureName,
row.UiCultureName))
.FirstOrDefault();
}
}
public async Task<IReadOnlyList<McpDictionaryListForm>> GetListFormsAsync()
{
var json = await GetJsonDataAsync();
if (string.IsNullOrWhiteSpace(json))
{
return [];
}
var document = JsonSerializer.Deserialize<DictionaryDocument>(json, DictionaryJsonOptions);
return document?.ListForms ?? [];
}
public string Normalize(string? value)
{
if (string.IsNullOrWhiteSpace(value))
{
return string.Empty;
}
var decomposed = value.ToLower(TurkishCulture).Normalize(NormalizationForm.FormD);
var builder = new StringBuilder(decomposed.Length);
foreach (var character in decomposed)
{
if (CharUnicodeInfo.GetUnicodeCategory(character) == UnicodeCategory.NonSpacingMark)
{
continue;
}
builder.Append(character switch
{
'ı' => 'i',
'ğ' => 'g',
'ş' => 's',
_ => character
});
}
return builder.ToString();
}
private async Task<string?> GetJsonDataAsync()
{
// Sozluk host kapsamli bir tabloda tutulur; kiraci ayrimi TenantName kolonundadir.
using (currentTenant.Change(null))
{
var query = await ActiveRowsAsync(ResolveTenantName());
return query.Select(row => row.JsonData).FirstOrDefault();
}
}
private async Task<IQueryable<DbTableColumn>> ActiveRowsAsync(string tenantName)
{
var query = await dictionaryRepository.GetQueryableAsync();
return query
.Where(row => row.DictionaryType == McpConsts.DictionaryType
&& row.IsActive
&& row.TenantName == tenantName)
.OrderByDescending(row => row.RefreshedAt);
}
/// <summary>Sozluk JSON'unun kok nesnesi.</summary>
private sealed class DictionaryDocument
{
public List<McpDictionaryListForm>? ListForms { get; set; }
}
}
/// <summary>Sozluk satirinin tazelik bilgisi.</summary>
public sealed record McpDictionarySnapshot(
string? CacheKey,
DateTime RefreshedAt,
int FormCount,
int FieldCount,
string? TenantName,
string? DefinitionCultureName,
string? UiCultureName);
/// <summary>
/// Sozlukteki bir liste. Alan adlari <c>dbo.Sas_H_ListFormFieldsDictionaryJson</c> prosedurunun
/// urettigi JSON ile birebir ayni olmak zorundadir.
/// </summary>
public class McpDictionaryListForm
{
public string? ListFormCode { get; set; }
public string? DisplayName { get; set; }
public string? Title { get; set; }
public string? Description { get; set; }
public string? TableName { get; set; }
public string? SqlSourceName { get; set; }
public string? SourceKind { get; set; }
public string? KeyFieldName { get; set; }
public string? SearchText { get; set; }
public List<McpDictionaryField>? Fields { get; set; }
}
/// <summary>Sozlukteki bir sutun.</summary>
public class McpDictionaryField
{
public string? Field { get; set; }
public string? DisplayName { get; set; }
public string? SqlDataType { get; set; }
public bool IsVisible { get; set; }
public bool IsBooleanColumn { get; set; }
public string? LookupTableName { get; set; }
public string? LookupValueColumn { get; set; }
public string? LookupDisplayColumn { get; set; }
public bool IsSensitive { get; set; }
public bool IsDefaultSelectColumn { get; set; }
}

View file

@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Sozsoft.Mcp.Entities;
using Sozsoft.Platform;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Services;
using Volo.Abp.MultiTenancy;
namespace Sozsoft.Mcp.Services;
/// <summary>
/// Istegin kiracisina dusen etkin sunucu kaydini cozer.
/// </summary>
public interface IMcpServerConfigProvider
{
/// <summary>Etkin kayit; kiraciya acik aktif bir kayit yoksa null.</summary>
Task<McpServer?> GetEffectiveServerAsync();
/// <summary>Coklu deger tasiyan kolonu (kiraci/arac listesi) ogelerine ayirir.</summary>
IReadOnlyList<string> SplitMultiValue(string? value);
}
public class McpServerConfigProvider(
IRepository<McpServer, Guid> serverRepository,
ICurrentTenant currentTenant)
: DomainService, IMcpServerConfigProvider
{
public async Task<McpServer?> GetEffectiveServerAsync()
{
var tenantName = currentTenant.Name;
// Sunucu tanimlari host kapsaminda yasar; kiraci baglamindaki istek de ayni tabloyu
// okur, hangi kiracilara acik oldugu Tenants kolonunda durur.
List<McpServer> servers;
using (currentTenant.Change(null))
{
var query = await serverRepository.GetQueryableAsync();
servers = [.. query.Where(server => server.IsActive).OrderBy(server => server.Name)];
}
return servers.Find(server => IsAvailableForTenant(server.Tenants, tenantName));
}
public IReadOnlyList<string> SplitMultiValue(string? value)
=> string.IsNullOrWhiteSpace(value)
? []
: [.. value.Split(
PlatformConsts.MultiValueDelimiter,
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)];
/// <summary>
/// Kaydin kiraci listesi bos ise tum kiracilara aciktir; doluysa kiraci adi listede
/// gecmelidir (AiBot ekraniyla ayni sozlesme).
/// </summary>
private bool IsAvailableForTenant(string? tenants, string? tenantName)
{
var allowed = SplitMultiValue(tenants);
if (allowed.Count == 0)
{
return true;
}
return tenantName is not null
&& allowed.Any(name => string.Equals(name, tenantName, StringComparison.OrdinalIgnoreCase));
}
}

View file

@ -0,0 +1,175 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Localization;
using Sozsoft.Platform.Enums;
using Sozsoft.Platform.Localization;
using Volo.Abp;
using Volo.Abp.Domain.Services;
namespace Sozsoft.Mcp.Services;
/// <summary>
/// Salt-okunur sorgu kurali. Hem serbest SELECT hem kayitli nesne cagrisi buradan gecer:
/// ifadenin gercekten okuma oldugu dogrulanir, tanimlayici ve parametre adlari kalibi gecer,
/// satir tavani sorguya yazilir.
/// </summary>
public interface IMcpSqlGuard
{
/// <summary>Tek bir okuma ifadesi oldugunu dogrular; degilse anlasilir bir hata atar.</summary>
void EnsureReadOnlyStatement(string sql);
/// <summary>Sema/nesne adini dogrular ve saglayiciya gore tirnaklar.</summary>
string QuoteQualifiedName(string schemaName, string objectName, DataSourceTypeEnum dataSourceType);
/// <summary>Saglayicinin varsayilan semasi.</summary>
string DefaultSchemaName(DataSourceTypeEnum dataSourceType);
/// <summary>
/// Satir tavanini sorgunun kendisine yazar: SQL Server'da <c>TOP (n)</c>, PostgreSQL'de
/// <c>LIMIT n</c>. Sorgu kendi sinirini zaten tasiyorsa dokunulmaz.
/// </summary>
string ApplyRowLimit(string sql, int maxRows, DataSourceTypeEnum dataSourceType);
/// <summary>
/// Istemciden gelen parametre adlarini dogrular ve bastaki @ isaretini duserek Dapper'in
/// bekledigi bicime cevirir.
/// </summary>
Dictionary<string, object> NormalizeParameters(IReadOnlyDictionary<string, object?>? parameters);
}
public class McpSqlGuard(IStringLocalizer<PlatformResource> localizer) : DomainService, IMcpSqlGuard
{
/// <summary>Tek parcali SQL tanimlayicisi; sema ve nesne adlari bu kalibi gecmek zorundadir.</summary>
private static readonly Regex SafeIdentifierRegex = new(
@"^[A-Za-z_][A-Za-z0-9_]{0,127}$",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
private static readonly Regex SafeParameterNameRegex = new(
@"^@?[A-Za-z_][A-Za-z0-9_]{0,127}$",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
private static readonly Regex SqlCommentRegex = new(
@"--[^\r\n]*|/\*[\s\S]*?\*/",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
private static readonly Regex SqlStringLiteralRegex = new(
@"'(?:[^']|'')*'",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
/// <summary>
/// Okuma sozde kalmasi gereken ifadelerde yasakli anahtar kelimeler. Kontrol yorumlar ve
/// metin sabitleri cikarildiktan sonra yapilir; aksi halde bir yorum blogu ya da tirnak
/// icindeki metin yasakli bir kelimeyi gizleyebilir.
/// </summary>
private static readonly Regex ForbiddenStatementRegex = new(
@"\b(INSERT|UPDATE|DELETE|MERGE|UPSERT|CREATE|ALTER|DROP|TRUNCATE|GRANT|REVOKE|DENY|BACKUP|RESTORE|SHUTDOWN|RECONFIGURE|EXEC|EXECUTE|WAITFOR|OPENROWSET|OPENDATASOURCE|OPENQUERY|BULK|INTO)\b",
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
private static readonly Regex SelectPrefixRegex = new(
@"^\s*SELECT\s+(?:(?:DISTINCT|ALL)\s+)?",
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
private static readonly Regex ExistingTopRegex = new(
@"^\s*SELECT\s+(?:(?:DISTINCT|ALL)\s+)?TOP\s*[(\s]",
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
private static readonly Regex ExistingLimitRegex = new(
@"\bLIMIT\s+\d+",
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
public void EnsureReadOnlyStatement(string sql)
{
if (string.IsNullOrWhiteSpace(sql))
{
throw new UserFriendlyException(localizer[McpErrorCodes.EmptyQuery]);
}
var stripped = SqlStringLiteralRegex.Replace(SqlCommentRegex.Replace(sql, " "), "''");
if (stripped.Contains(';', StringComparison.Ordinal))
{
throw new UserFriendlyException(localizer[McpErrorCodes.SingleStatementOnly]);
}
var trimmed = stripped.TrimStart();
var startsWithRead = trimmed.StartsWith("SELECT", StringComparison.OrdinalIgnoreCase)
|| trimmed.StartsWith("WITH", StringComparison.OrdinalIgnoreCase);
if (!startsWithRead)
{
throw new UserFriendlyException(localizer[McpErrorCodes.ReadOnlyStatementRequired]);
}
var forbidden = ForbiddenStatementRegex.Match(stripped);
if (forbidden.Success)
{
throw new UserFriendlyException(
localizer[McpErrorCodes.ForbiddenKeyword, forbidden.Value.ToUpperInvariant()]);
}
}
public string QuoteQualifiedName(string schemaName, string objectName, DataSourceTypeEnum dataSourceType)
{
EnsureSafeIdentifier(schemaName);
EnsureSafeIdentifier(objectName);
return dataSourceType == DataSourceTypeEnum.Postgresql
? $"\"{schemaName}\".\"{objectName}\""
: $"[{schemaName}].[{objectName}]";
}
public string DefaultSchemaName(DataSourceTypeEnum dataSourceType)
=> dataSourceType == DataSourceTypeEnum.Postgresql ? "public" : "dbo";
public string ApplyRowLimit(string sql, int maxRows, DataSourceTypeEnum dataSourceType)
{
if (dataSourceType == DataSourceTypeEnum.Postgresql)
{
return ExistingLimitRegex.IsMatch(sql) ? sql : $"{sql} LIMIT {maxRows}";
}
if (ExistingTopRegex.IsMatch(sql))
{
return sql;
}
var match = SelectPrefixRegex.Match(sql);
// WITH ile baslayan ya da beklenmeyen bicimdeki sorgularda sarmalama yapilmaz: SQL
// Server'da ORDER BY tasiyan bir alt sorgu gecersizdir. Sonuc kumesi okunurken yine
// maxRows satirda kesilir.
return match.Success
? string.Concat(sql.AsSpan(0, match.Length), $"TOP ({maxRows}) ", sql.AsSpan(match.Length))
: sql;
}
public Dictionary<string, object> NormalizeParameters(IReadOnlyDictionary<string, object?>? parameters)
{
var normalized = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
if (parameters is null)
{
return normalized;
}
foreach (var (key, value) in parameters)
{
if (key is null || !SafeParameterNameRegex.IsMatch(key))
{
throw new UserFriendlyException(localizer[McpErrorCodes.InvalidParameterName, key ?? string.Empty]);
}
normalized[key.TrimStart('@')] = value!;
}
return normalized;
}
private void EnsureSafeIdentifier(string identifier)
{
if (!SafeIdentifierRegex.IsMatch(identifier ?? string.Empty))
{
throw new UserFriendlyException(localizer[McpErrorCodes.InvalidIdentifier, identifier ?? string.Empty]);
}
}
}

View file

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\common.props" />
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<RootNamespace>Sozsoft.Mcp</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Sozsoft.Mcp.Domain.Shared\Sozsoft.Mcp.Domain.Shared.csproj" />
<!-- Veri sozlugu (DbTableColumn) ve dinamik veri erisimi platform domain'inde yasar. -->
<ProjectReference Include="..\..\..\src\Sozsoft.Platform.Domain\Sozsoft.Platform.Domain.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,20 @@
using Microsoft.EntityFrameworkCore;
using Sozsoft.Mcp.Entities;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
namespace Sozsoft.Mcp.EntityFrameworkCore;
[ConnectionStringName(McpDbProperties.ConnectionStringName)]
public class McpDbContext(DbContextOptions<McpDbContext> options)
: AbpDbContext<McpDbContext>(options)
{
public DbSet<McpServer> McpServers { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ConfigureMcp();
}
}

View file

@ -0,0 +1,37 @@
using Microsoft.EntityFrameworkCore;
using Sozsoft.Mcp.Entities;
using Sozsoft.Platform.Enums;
using Volo.Abp;
using Volo.Abp.EntityFrameworkCore.Modeling;
using static Sozsoft.Platform.PlatformConsts;
namespace Sozsoft.Mcp.EntityFrameworkCore;
public static class McpDbContextModelCreatingExtensions
{
public static void ConfigureMcp(this ModelBuilder builder)
{
Check.NotNull(builder, nameof(builder));
builder.Entity<McpServer>(b =>
{
// Tablo adi platformun tek kayit defterinden gelir (TableNameEnum.McpServer);
// modul ikinci bir adlandirma kurali tanimlamaz.
b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.McpServer)), Prefix.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.Name).IsRequired().HasMaxLength(128);
b.Property(x => x.Url).HasMaxLength(512);
// Kiraci ve arac listeleri tek kolonda MultiValueDelimiter ile saklanir; ekranda
// dxTagBox ile doldurulur.
b.Property(x => x.Tenants).HasMaxLength(2048);
b.Property(x => x.AllowedTools).HasMaxLength(1024);
b.Property(x => x.DataSourceCode).HasMaxLength(64);
b.Property(x => x.MaxRows).HasDefaultValue(McpConsts.DefaultMaxRows);
b.Property(x => x.AllowWrite).HasDefaultValue(false);
b.Property(x => x.Description).HasMaxLength(1024);
b.HasIndex(x => new { x.Name }).IsUnique().HasFilter("[IsDeleted] = 0");
});
}
}

View file

@ -0,0 +1,6 @@
namespace Sozsoft.Mcp.EntityFrameworkCore;
public static class McpDbProperties
{
public const string ConnectionStringName = "Mcp";
}

View file

@ -0,0 +1,20 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.Modularity;
namespace Sozsoft.Mcp.EntityFrameworkCore;
[DependsOn(
typeof(McpDomainModule),
typeof(AbpEntityFrameworkCoreModule)
)]
public class McpEntityFrameworkCoreModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAbpDbContext<McpDbContext>(options =>
{
options.AddDefaultRepositories(includeAllEntities: true);
});
}
}

View file

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\common.props" />
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<RootNamespace>Sozsoft.Mcp</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.EntityFrameworkCore" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Sozsoft.Mcp.Domain\Sozsoft.Mcp.Domain.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,101 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Sozsoft.Mcp.Dtos;
using Volo.Abp.AspNetCore.Mvc;
namespace Sozsoft.Mcp.Controllers;
/// <summary>
/// Uzak MCP istemcilerinin baglanti kesfi. Istemci /mcp'den 401 alir, <c>WWW-Authenticate</c>
/// basligindaki meta veri adresinden yetkilendirme sunucusunu ogrenir, kayit ucundan istemci
/// kimligini alir ve kullaniciyi platformun giris ekranina yonlendirir.
/// Adresler ve yanit bicimi RFC'lerle sabittir; bu yuzden ABP'nin konvansiyonel uclari yerine
/// acik rotali bir controller kullanilir.
/// </summary>
[AllowAnonymous]
[IgnoreAntiforgeryToken]
[ApiExplorerSettings(IgnoreApi = true)]
public class McpOAuthController(
IOptions<McpOAuthOptions> options,
IMcpOAuthAppService oAuthAppService) : AbpControllerBase
{
/// <summary>Korumali kaynak meta verisi (RFC 9728).</summary>
[HttpGet(McpConsts.ProtectedResourceMetadataPath)]
[HttpGet(McpConsts.ProtectedResourceMetadataPath + McpConsts.EndpointPath)]
public IActionResult GetProtectedResourceMetadata()
{
var value = options.Value;
return new JsonResult(new ProtectedResourceMetadata(
value.ResourceUrl,
[value.AuthorizationServer],
McpConsts.OAuthScopes,
["header"],
McpServerInfoDefaults.DisplayName));
}
/// <summary>Dinamik istemci kaydi (RFC 7591).</summary>
[HttpPost(McpConsts.RegistrationEndpointPath)]
public async Task<IActionResult> RegisterAsync([FromBody] ClientRegistrationRequest request)
{
var result = await oAuthAppService.RegisterClientAsync(new McpClientRegistrationRequestDto
{
ClientName = request.ClientName,
RedirectUris = request.RedirectUris ?? []
});
if (result.Error is not null)
{
return new JsonResult(new ClientRegistrationError(result.Error, result.ErrorDescription))
{
StatusCode = StatusCodes.Status400BadRequest
};
}
return new JsonResult(new ClientRegistrationResponse(
result.ClientId!,
result.ClientIdIssuedAt,
result.ClientName,
result.RedirectUris,
result.GrantTypes,
result.ResponseTypes,
result.TokenEndpointAuthMethod,
result.Scope))
{
StatusCode = StatusCodes.Status201Created
};
}
// Protokol sozlesmeleri snake_case alan adlariyla sabittir; ABP'nin camelCase JSON
// politikasina birakilmaz.
public sealed record ClientRegistrationRequest(
[property: JsonPropertyName("client_name")] string? ClientName,
[property: JsonPropertyName("redirect_uris")] List<string>? RedirectUris);
private sealed record ClientRegistrationResponse(
[property: JsonPropertyName("client_id")] string ClientId,
[property: JsonPropertyName("client_id_issued_at")] long ClientIdIssuedAt,
[property: JsonPropertyName("client_name")] string? ClientName,
[property: JsonPropertyName("redirect_uris")] IReadOnlyList<string> RedirectUris,
[property: JsonPropertyName("grant_types")] IReadOnlyList<string> GrantTypes,
[property: JsonPropertyName("response_types")] IReadOnlyList<string> ResponseTypes,
[property: JsonPropertyName("token_endpoint_auth_method")] string TokenEndpointAuthMethod,
[property: JsonPropertyName("scope")] string? Scope);
private sealed record ClientRegistrationError(
[property: JsonPropertyName("error")] string Error,
[property: JsonPropertyName("error_description")] string? ErrorDescription);
private sealed record ProtectedResourceMetadata(
[property: JsonPropertyName("resource")] string Resource,
[property: JsonPropertyName("authorization_servers")] IReadOnlyList<string> AuthorizationServers,
[property: JsonPropertyName("scopes_supported")] IReadOnlyList<string> ScopesSupported,
[property: JsonPropertyName("bearer_methods_supported")] IReadOnlyList<string> BearerMethodsSupported,
[property: JsonPropertyName("resource_name")] string ResourceName);
}

View file

@ -0,0 +1,37 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
using Volo.Abp.DependencyInjection;
namespace Sozsoft.Mcp;
/// <summary>
/// /mcp'den donen 401 yanitina korumali kaynak meta verisinin adresini ekler (RFC 9728 §5.1).
/// Uzak MCP istemcisi OAuth akisini bu basliktan baslatir; baslik yoksa baglanti formu
/// "kimlik dogrulama desteklenmiyor" diye durur. Kimlik dogrulamadan once calismalidir:
/// basligi yanit baslamadan hemen once yazar, 401'i hangi katman uretmis olursa olsun.
/// </summary>
public class McpAuthenticationChallengeMiddleware(IOptions<McpOAuthOptions> options)
: IMiddleware, ITransientDependency
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
if (context.Request.Path.StartsWithSegments(McpConsts.EndpointPath))
{
context.Response.OnStarting(() =>
{
if (context.Response.StatusCode == StatusCodes.Status401Unauthorized)
{
context.Response.Headers[HeaderNames.WWWAuthenticate] =
$"Bearer resource_metadata=\"{options.Value.ProtectedResourceMetadataUrl}\", " +
$"scope=\"{string.Join(' ', McpConsts.OAuthScopes)}\"";
}
return Task.CompletedTask;
});
}
await next(context);
}
}

View file

@ -0,0 +1,64 @@
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol.AspNetCore;
using Sozsoft.Mcp.Tools;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Modularity;
namespace Sozsoft.Mcp;
/// <summary>
/// /mcp ucunun sunum katmani. Araclar yalnizca uygulama sozlesmesini tanir; kimlik dogrulama,
/// kiraci cozumu, unit of work ve yetki kontrolu istegin gectigi ABP ardisik duzeninden gelir.
/// Ucun kendisi host tarafinda <c>MapMcp</c> ile baglanir.
/// </summary>
[DependsOn(
typeof(McpApplicationContractsModule),
typeof(AbpAspNetCoreMvcModule)
)]
public class McpHttpApiModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
Configure<McpOAuthOptions>(options =>
{
options.BaseUrl = (configuration["App:SelfUrl"] ?? string.Empty).TrimEnd('/');
options.AuthorizationServer = (configuration["AuthServer:Authority"] ?? string.Empty).TrimEnd('/');
});
context.Services
.AddMcpServer(options =>
{
options.ServerInfo = new() { Name = McpServerInfoDefaults.Name, Version = McpServerInfoDefaults.Version };
options.ServerInstructions = McpServerInfoDefaults.Instructions;
})
.WithHttpTransport(options =>
{
// Durumsuz kip: her istek kendi ABP kapsaminda kosar, boylece kimlik, kiraci
// cozumu ve unit of work istekle birlikte dogru kurulur. Durumlu kipte oturum
// kapsami initialize istegine baglanir ve sonraki cagrilar yanlis kiracinin
// baglaminda kalabilir; ayrica ters vekil arkasinda oturum yapisakligi gerekmez.
options.SessionMode = HttpServerSessionMode.Stateless;
})
.WithTools<McpSchemaTools>()
.WithTools<McpQueryTools>()
.WithTools<McpDataTools>()
.WithRequestFilters(filters => filters.AddListToolsFilter(next => async (requestContext, cancellationToken) =>
{
var result = await next(requestContext, cancellationToken);
// Arac listesi kiracinin sunucu kaydina ve kullanicinin uygulamadaki yetkilerine
// gore suzulur. Cagri aninda da ayni kural uygulanir
// (McpAppService.EnsureToolAllowedAsync): istemci listede olmayan bir araci da
// cagirabilir, suzme tek basina bir yetki siniri degildir.
var schemaAppService = requestContext.Services!.GetRequiredService<IMcpSchemaAppService>();
var available = (await schemaAppService.GetAvailableToolsAsync()).ToHashSet(System.StringComparer.Ordinal);
result.Tools = [.. result.Tools.Where(tool => available.Contains(tool.Name))];
return result;
}));
}
}

View file

@ -0,0 +1,22 @@
namespace Sozsoft.Mcp;
/// <summary>
/// /mcp ucunun OAuth adresleri. Degerler ortam yapilandirmasindan gelir (<c>App:SelfUrl</c>,
/// <c>AuthServer:Authority</c>); localhost, dev ve production ayni kodla calisir.
/// </summary>
public class McpOAuthOptions
{
/// <summary>API'nin disaridan erisilen kok adresi, sonunda egik cizgi olmadan.</summary>
public string BaseUrl { get; set; } = string.Empty;
/// <summary>Token'i veren yetkilendirme sunucusu (OpenIddict), sonunda egik cizgi olmadan.</summary>
public string AuthorizationServer { get; set; } = string.Empty;
/// <summary>Korumali kaynagin tanimlayicisi: istemcinin baglandigi /mcp adresi.</summary>
public string ResourceUrl => BaseUrl + McpConsts.EndpointPath;
/// <summary>
/// Kaynaga ozgu meta veri adresi (RFC 9728 §3.1: yol, well-known onekinin arkasina eklenir).
/// </summary>
public string ProtectedResourceMetadataUrl => BaseUrl + McpConsts.ProtectedResourceMetadataPath + McpConsts.EndpointPath;
}

View file

@ -0,0 +1,22 @@
namespace Sozsoft.Mcp;
/// <summary>
/// Istemciye <c>initialize</c> yanitinda bildirilen sunucu kimligi ve yonerge metni.
/// Yonerge modele hitap eder, son kullaniciya degil; bu yuzden dil anahtari uzerinden
/// gecmez — protokol metnidir, arayuz metni degil.
/// </summary>
internal static class McpServerInfoDefaults
{
public const string Name = "sozsoft-platform";
/// <summary>Connector ekraninda gorunen kaynak adi.</summary>
public const string DisplayName = "Sozsoft Platform";
public const string Version = "1.0.0";
public const string Instructions =
"Sozsoft Platform data access. Do not guess table or column names: first find the list with " +
"list_lists or search_schema, read its columns with describe_list, then query it with run_select " +
"using named @parameters. Never inline literal values into SQL. Columns marked isSensitive must " +
"not be selected. Call server_info first when a tool behaves unexpectedly.";
}

View file

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\common.props" />
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<RootNamespace>Sozsoft.Mcp</RootNamespace>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<!-- /mcp ucunun Streamable HTTP tasiyicisi. -->
<PackageReference Include="ModelContextProtocol.AspNetCore" Version="2.2.0" />
<PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<!-- Sunum katmani: araclar yalnizca uygulama sozlesmesini tanir, Domain'i gormez. -->
<ProjectReference Include="..\Sozsoft.Mcp.Application.Contracts\Sozsoft.Mcp.Application.Contracts.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,58 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
using ModelContextProtocol.Server;
using Sozsoft.Mcp.Dtos;
namespace Sozsoft.Mcp.Tools;
/// <summary>
/// Veri yazma araclari. Yazma bir ListForm ekraninin sozlesmesinden gecer; ekran yetkisi ve
/// tenant filtresi aynen uygulanir. Sunucu kaydinda <c>AllowWrite</c> kapaliysa bu araclar
/// arac listesinde hic gorunmez.
/// </summary>
[McpServerToolType]
public sealed class McpDataTools(IMcpDataAppService dataAppService)
{
[McpServerTool(Name = McpToolNames.InsertRow, Destructive = false, Idempotent = false)]
[Description("Bir listeye yeni kayit ekler. Alan adlari describe_list cikisindaki field degerleridir.")]
public async Task<McpWriteResultDto> InsertRowAsync(
[Description("Kaydin eklenecegi listenin kodu.")]
string listFormCode,
[Description("Alan adi -> deger eslemesi.")]
Dictionary<string, object?> values)
=> await dataAppService.InsertRowAsync(new McpRowRequestDto
{
ListFormCode = listFormCode,
Values = values ?? []
});
[McpServerTool(Name = McpToolNames.UpdateRow, Destructive = true, Idempotent = true)]
[Description("Bir listedeki kaydi anahtar degerine gore gunceller. Yalnizca verilen alanlar yazilir.")]
public async Task<McpWriteResultDto> UpdateRowAsync(
[Description("Kaydin bulundugu listenin kodu.")]
string listFormCode,
[Description("Guncellenecek kaydin anahtar alan degeri; describe_list cikisindaki keyFieldName alanina karsilik gelir.")]
List<object?> keys,
[Description("Alan adi -> yeni deger eslemesi.")]
Dictionary<string, object?> values)
=> await dataAppService.UpdateRowAsync(new McpRowRequestDto
{
ListFormCode = listFormCode,
Keys = keys ?? [],
Values = values ?? []
});
[McpServerTool(Name = McpToolNames.DeleteRow, Destructive = true, Idempotent = true)]
[Description("Bir listedeki kaydi anahtar degerine gore siler. Ekranin kendi silme komutu kullanilir; soft delete tasiyan tablolarda kayit pasife cekilir.")]
public async Task<McpWriteResultDto> DeleteRowAsync(
[Description("Kaydin bulundugu listenin kodu.")]
string listFormCode,
[Description("Silinecek kaydin anahtar alan degeri.")]
List<object?> keys)
=> await dataAppService.DeleteRowAsync(new McpRowRequestDto
{
ListFormCode = listFormCode,
Keys = keys ?? []
});
}

View file

@ -0,0 +1,88 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
using ModelContextProtocol.Server;
using Sozsoft.Mcp.Dtos;
namespace Sozsoft.Mcp.Tools;
/// <summary>Salt-okunur sorgu araclari.</summary>
[McpServerToolType]
public sealed class McpQueryTools(IMcpQueryAppService queryAppService)
{
[McpServerTool(Name = McpToolNames.QueryList, ReadOnly = true)]
[Description("Bir listenin verisini uygulamadaki ekranla ayni sorguyla okur; kullanicinin liste ve alan yetkileri, kiraci ve sube filtreleri gecerlidir. Veri okumak icin once bunu kullan. Alan adlari describe_list cikisindaki field degerleridir. Filtre/siralama/gruplama DevExtreme bicimindedir.")]
public async Task<McpListQueryResultDto> QueryListAsync(
[Description("Listenin kodu; list_lists cikisindaki listFormCode degeri.")]
string listFormCode,
[Description("Filtre, JSON metni. Ornek: [\"Status\",\"=\",\"Open\"] ya da [[\"Amount\",\">\",100],\"and\",[\"Name\",\"contains\",\"abc\"]]. Operatorler: =, <>, >, >=, <, <=, contains, notcontains, startswith, endswith.")]
string? filter = null,
[Description("Siralama, JSON metni. Ornek: [{\"selector\":\"CreationTime\",\"desc\":true}].")]
string? sort = null,
[Description("Gruplama, JSON metni. Ornek: [{\"selector\":\"CustomerId\",\"desc\":false,\"isExpanded\":false}]. Gruplu sorguda satirlar yerine grup anahtarlari ve ozetleri doner.")]
string? group = null,
[Description("Grup ozetleri, JSON metni. Ornek: [{\"selector\":\"Id\",\"summaryType\":\"count\"},{\"selector\":\"Amount\",\"summaryType\":\"sum\"}]. Tipler: count, sum, min, max, avg.")]
string? groupSummary = null,
[Description("Tum sonuc icin ozet, JSON metni. Ornek: [{\"selector\":\"Amount\",\"summaryType\":\"sum\"}].")]
string? totalSummary = null,
[Description("Atlanacak kayit sayisi (sayfalama).")]
int skip = 0,
[Description("Istenen kayit sayisi; sunucu kaydindaki tavandan buyuk olamaz.")]
int take = 0)
=> await queryAppService.QueryListAsync(new McpListQueryRequestDto
{
ListFormCode = listFormCode,
Filter = filter,
Sort = sort,
Group = group,
GroupSummary = groupSummary,
TotalSummary = totalSummary,
Skip = skip,
Take = take
});
[McpServerTool(Name = McpToolNames.RunSelect, ReadOnly = true)]
[Description("Yalnizca uygulamada SQL Query Manager yetkisi olan kullanicilar icin: tek bir parametreli SELECT ifadesi kosturur. Liste verisi icin query_list tercih edilir. Coklu ifade, DDL ve DML reddedilir; satir sayisi sunucu kaydindaki tavana kirpilir. Sorguya deger gomme, @ad parametresi kullan.")]
public async Task<McpQueryResultDto> RunSelectAsync(
[Description("Tek bir SELECT (ya da WITH ... SELECT) ifadesi. Degerler @ad bicimindeki parametrelerle verilir.")]
string sql,
[Description("Sorgudaki adli parametreler: anahtar parametre adi, deger ise ham JSON degeri.")]
Dictionary<string, object?>? parameters = null,
[Description("Istenen en fazla satir sayisi; sunucu kaydindaki tavandan buyuk olamaz.")]
int maxRows = 0)
=> await queryAppService.RunSelectAsync(new McpSelectRequestDto
{
Sql = sql,
Parameters = parameters ?? [],
MaxRows = maxRows
});
[McpServerTool(Name = McpToolNames.ListQueries, ReadOnly = true)]
[Description("Veri kaynagindaki kayitli nesneleri (view, stored procedure, function) ve cagrilabilir Custom Endpoint'leri dondurur. run_query bunlardan birini cagirir.")]
public async Task<List<McpSavedQueryDto>> ListQueriesAsync(
[Description("Nesne adi ya da aciklamasi icinde aranacak metin. Bos birakilabilir.")]
string? search = null)
=> await queryAppService.GetSavedQueriesAsync(search);
[McpServerTool(Name = McpToolNames.RunQuery, ReadOnly = true)]
[Description("Kayitli bir nesneyi ya da Custom Endpoint'i parametreleriyle cagirir. Nesne turu ve adi list_queries cikisindan alinir.")]
public async Task<McpQueryResultDto> RunQueryAsync(
[Description("Nesne turu: view, procedure, tableFunction, scalarFunction ya da customEndpoint.")]
string kind,
[Description("Nesnenin ya da Custom Endpoint'in adi.")]
string name,
[Description("Nesnenin semasi. Bos birakilirsa SQL Server'da dbo, PostgreSQL'de public kullanilir.")]
string? schemaName = null,
[Description("Adli parametreler: anahtar parametre adi, deger ise ham JSON degeri.")]
Dictionary<string, object?>? parameters = null,
[Description("Istenen en fazla satir sayisi; sunucu kaydindaki tavandan buyuk olamaz.")]
int maxRows = 0)
=> await queryAppService.RunSavedQueryAsync(new McpSavedQueryRequestDto
{
Kind = kind,
Name = name,
SchemaName = schemaName,
Parameters = parameters ?? [],
MaxRows = maxRows
});
}

View file

@ -0,0 +1,60 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
using ModelContextProtocol.Server;
using Sozsoft.Mcp.Dtos;
namespace Sozsoft.Mcp.Tools;
/// <summary>
/// Sema kesfi araclari. Tablo ve sutun bilgisi canli katalogdan degil, platformun ekran
/// tanimlarindan uretilen veri sozlugunden gelir; boylece modelin gordugu adlar kullanicinin
/// ekranda gordugu adlarla ayni olur.
/// </summary>
[McpServerToolType]
public sealed class McpSchemaTools(IMcpSchemaAppService schemaAppService)
{
[McpServerTool(Name = McpToolNames.ServerInfo, ReadOnly = true)]
[Description("Bu baglantinin kiracisini, acik araclari ve veri sozlugunun tazelik bilgisini dondurur. Baska bir arac beklendigi gibi calismiyorsa once bunu cagir.")]
public async Task<McpServerInfoResult> ServerInfoAsync()
{
var config = await schemaAppService.GetServerConfigAsync();
var schema = await schemaAppService.GetSchemaInfoAsync();
var availableTools = await schemaAppService.GetAvailableToolsAsync();
return new McpServerInfoResult(config, schema, availableTools);
}
[McpServerTool(Name = McpToolNames.ListLists, ReadOnly = true)]
[Description("Kiracinin listelerini (ekranlarini) ad, baslik, aciklama ve arkasindaki tablo ile dondurur. Arama metni verilmezse tumu doner.")]
public async Task<List<McpListSummaryDto>> ListListsAsync(
[Description("Liste adi, basligi, aciklamasi ya da tablo adi icinde aranacak metin. Bos birakilabilir.")]
string? search = null,
[Description("Donecek en fazla liste sayisi. Varsayilan 200.")]
int maxResults = 200)
=> await schemaAppService.GetListsAsync(search, maxResults);
[McpServerTool(Name = McpToolNames.DescribeList, ReadOnly = true)]
[Description("Tek bir listenin sutunlarini dondurur: alan adi, kullaniciya gorunen basligi, SQL tipi, lookup iliskisi ve sutunun hassas olup olmadigi.")]
public async Task<McpListDetailDto?> DescribeListAsync(
[Description("Listenin kodu; list_lists cikisindaki listFormCode degeri.")]
string listFormCode)
=> await schemaAppService.GetListAsync(listFormCode);
[McpServerTool(Name = McpToolNames.SearchSchema, ReadOnly = true)]
[Description("Liste ve sutun adlari ile basliklari uzerinde aksan ve buyuk/kucuk harf duyarsiz arama yapar; hangi listenin hangi sutununun arandigini bulmak icin kullanilir.")]
public async Task<List<McpSchemaMatchDto>> SearchSchemaAsync(
[Description("Aranacak metin. Turkce karakterler ve aksanlar onemsizdir.")]
string term,
[Description("Donecek en fazla eslesme sayisi. Varsayilan 50.")]
int maxResults = 50)
=> await schemaAppService.SearchAsync(term, maxResults);
}
/// <param name="Server">Istegin kiracisina dusen sunucu kaydi; yoksa null.</param>
/// <param name="Schema">Veri sozlugunun tazelik bilgisi.</param>
/// <param name="AvailableTools">Bu baglantida cagrilabilen araclar.</param>
public sealed record McpServerInfoResult(
McpServerConfigDto? Server,
McpSchemaInfoDto Schema,
IReadOnlyList<string> AvailableTools);

View file

@ -0,0 +1,8 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Version>0.1.0</Version>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<AbpProjectType>module</AbpProjectType>
</PropertyGroup>
</Project>

View file

@ -10,6 +10,6 @@ public class NotificationFilterRequestDto : PagedAndSortedResultRequestDto
/// <summary> /// <summary>
/// İstek; aktivite sayfasındaki liste ekranından mı geliyor yoksa bildirimleri kontrol etmek için mi /// İstek; aktivite sayfasındaki liste ekranından mı geliyor yoksa bildirimleri kontrol etmek için mi
/// </summary> /// </summary>
public bool IsListRequest { get; set; } = false; public bool IsListRequest { get; set; } = false;
} }

View file

@ -1,9 +0,0 @@
namespace Sozsoft.Notifications.Application;
public class NotificationRemoteServiceConsts
{
public const string RemoteServiceName = "Notifications";
public const string ModuleName = "notifications";
}

View file

@ -1,4 +1,4 @@
using Volo.Abp.Authorization.Permissions; using Volo.Abp.Authorization.Permissions;
namespace Sozsoft.Notifications.Permissions; namespace Sozsoft.Notifications.Permissions;

View file

@ -2,11 +2,11 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.EntityFrameworkCore;
using Sozsoft.Notifications.Domain; using Sozsoft.Notifications.Domain;
using Sozsoft.Notifications.Entities; using Sozsoft.Notifications.Entities;
using Sozsoft.Notifications.Enums; using Sozsoft.Notifications.Enums;
using Microsoft.AspNetCore.Authorization;
using Microsoft.EntityFrameworkCore;
using Volo.Abp; using Volo.Abp;
using Volo.Abp.Application.Services; using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Entities;

View file

@ -1,7 +1,7 @@
using Sozsoft.Notifications.Entities; using Riok.Mapperly.Abstractions;
using Sozsoft.Notifications.Domain; using Sozsoft.Notifications.Domain;
using Sozsoft.Notifications.Entities;
using Sozsoft.Notifications.NotificationRules; using Sozsoft.Notifications.NotificationRules;
using Riok.Mapperly.Abstractions;
using Volo.Abp.Mapperly; using Volo.Abp.Mapperly;
namespace Sozsoft.Notifications.Application; namespace Sozsoft.Notifications.Application;
@ -9,7 +9,7 @@ namespace Sozsoft.Notifications.Application;
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)] [Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
public partial class NotificationTypeToNotificationTypeDtoMapper : MapperBase<NotificationType, NotificationTypeDto> public partial class NotificationTypeToNotificationTypeDtoMapper : MapperBase<NotificationType, NotificationTypeDto>
{ {
public override partial NotificationTypeDto Map(NotificationType source); public override partial NotificationTypeDto Map(NotificationType source);
public override partial void Map(NotificationType source, NotificationTypeDto destination); public override partial void Map(NotificationType source, NotificationTypeDto destination);
@ -25,7 +25,7 @@ public override partial NotificationTypeDto Map(NotificationType source);
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)] [Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
public partial class NotificationRuleToNotificationRuleDtoMapper : MapperBase<NotificationRule, NotificationRuleDto> public partial class NotificationRuleToNotificationRuleDtoMapper : MapperBase<NotificationRule, NotificationRuleDto>
{ {
public override partial NotificationRuleDto Map(NotificationRule source); public override partial NotificationRuleDto Map(NotificationRule source);
public override partial void Map(NotificationRule source, NotificationRuleDto destination); public override partial void Map(NotificationRule source, NotificationRuleDto destination);

View file

@ -1,5 +1,5 @@
using Sozsoft.Notifications.Domain; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection; using Sozsoft.Notifications.Domain;
using Volo.Abp.Application; using Volo.Abp.Application;
using Volo.Abp.Mapperly; using Volo.Abp.Mapperly;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;

View file

@ -2,10 +2,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Sozsoft.Notifications.Domain;
using Sozsoft.Notifications.Entities;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Sozsoft.Notifications.Domain;
using Sozsoft.Notifications.Entities;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services; using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
@ -23,22 +23,16 @@ public class NotificationRuleAppService : CrudAppService<
INotificationRuleAppService INotificationRuleAppService
{ {
private readonly IRepository<NotificationType, Guid> repositoryType; private readonly IRepository<NotificationType, Guid> repositoryType;
private readonly IOrganizationUnitRepository repositoryOrganizationUnit;
private readonly IdentityUserManager userManager;
private readonly IIdentityUserRepository repositoryUser; private readonly IIdentityUserRepository repositoryUser;
public NotificationRuleAppService( public NotificationRuleAppService(
IRepository<NotificationType, Guid> repositoryType, IRepository<NotificationType, Guid> repositoryType,
IRepository<NotificationRule, Guid> repository, IRepository<NotificationRule, Guid> repository,
IdentityUserManager userManager, IIdentityUserRepository repositoryUser
IIdentityUserRepository repositoryUser,
IOrganizationUnitRepository repositoryOrganizationUnit
) : base(repository) ) : base(repository)
{ {
this.repositoryType = repositoryType; this.repositoryType = repositoryType;
this.userManager = userManager;
this.repositoryUser = repositoryUser; this.repositoryUser = repositoryUser;
this.repositoryOrganizationUnit = repositoryOrganizationUnit;
} }
public async Task<string[]> GetNotificationTypes() public async Task<string[]> GetNotificationTypes()

View file

@ -5,9 +5,9 @@ public static class NotificationChannels
public const string Sms = "Sms"; //SMS (ABP Sms + Posta Guvercini) public const string Sms = "Sms"; //SMS (ABP Sms + Posta Guvercini)
public const string Mail = "Mail"; //Email (ABP Emailing + Amazon SES) public const string Mail = "Mail"; //Email (ABP Emailing + Amazon SES)
public const string Rocket = "Rocket"; //Rocket.Chat (HTTP API) public const string Rocket = "Rocket"; //Rocket.Chat (HTTP API)
public const string Desktop = "Desktop"; public const string Desktop = "Desktop";
public const string UiActivity = "UiActivity"; //UI Activity (ABP UI Activity) public const string UiActivity = "UiActivity"; //UI Activity (ABP UI Activity)
public const string UiToast = "UiToast"; //UI Toast (ABP UI Toast) Ayarlar/Sistem/Bildirimler/Chrome açık olması gerekiyor. public const string UiToast = "UiToast"; //UI Toast (ABP UI Toast) Ayarlar/Sistem/Bildirimler/Chrome açık olması gerekiyor.
public const string WhatsApp = "WhatsApp"; //WhatsApp (HTTP API, template-based) public const string WhatsApp = "WhatsApp"; //WhatsApp (HTTP API, template-based)
// public const string Telegram = "Telegram"; public const string Telegram = "Telegram"; //Telegram (Bot API, chat based)
} }

View file

@ -1,7 +0,0 @@
namespace Sozsoft.Notifications.Domain;
public static class NotificationErrorCodes
{
//Add your business exception error codes here...
}

View file

@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using Volo.Abp.Domain.Entities.Auditing; using Volo.Abp.Domain.Entities.Auditing;
namespace Sozsoft.Notifications.Entities; namespace Sozsoft.Notifications.Entities;

View file

@ -7,8 +7,8 @@ namespace Sozsoft.Notifications.Entities;
public class NotificationRule : FullAuditedEntity<Guid> public class NotificationRule : FullAuditedEntity<Guid>
{ {
public required Guid NotificationTypeId { get; set; } public required Guid NotificationTypeId { get; set; }
public NotificationType NotificationType { get; set; } public NotificationType NotificationType { get; set; }
/// <summary> /// <summary>
/// Alıcı Tipi: User, Role, Organization, All, Custom /// Alıcı Tipi: User, Role, Organization, All, Custom
/// User ise RecipientId UserId olacak /// User ise RecipientId UserId olacak

View file

@ -40,7 +40,7 @@ public class NotificationIdentifierProvider : INotificationIdentifierProvider
NotificationChannels.UiActivity => user.Id.ToString(), NotificationChannels.UiActivity => user.Id.ToString(),
NotificationChannels.UiToast => user.Id.ToString(), NotificationChannels.UiToast => user.Id.ToString(),
NotificationChannels.WhatsApp => user.PhoneNumber, NotificationChannels.WhatsApp => user.PhoneNumber,
// NotificationChannels.Telegram => user.PhoneNumber, NotificationChannels.Telegram => user.GetProperty<string?>("TelegramChatId"),
_ => null, _ => null,
}; };

View file

@ -2,8 +2,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Sozsoft.Notifications.Entities;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Sozsoft.Notifications.Entities;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
using Volo.Abp.Identity; using Volo.Abp.Identity;
@ -192,9 +192,9 @@ public class NotificationManager : INotificationManager
} }
} }
var notificationType = await repositoryType.FirstOrDefaultAsync(a => a.Name == NotificationType) var notificationType = await repositoryType.FirstOrDefaultAsync(a => a.Name == NotificationType)
?? throw new Exception($"NotificationType '{NotificationType}' not found."); ?? throw new Exception($"NotificationType '{NotificationType}' not found.");
await repositoryNotification.InsertAsync(new Notification await repositoryNotification.InsertAsync(new Notification
{ {
NotificationTypeId = notificationType.Id, NotificationTypeId = notificationType.Id,

View file

@ -1,6 +1,6 @@
using Sozsoft.Notifications.Domain; using Microsoft.EntityFrameworkCore;
using Sozsoft.Notifications.Domain;
using Sozsoft.Notifications.Entities; using Sozsoft.Notifications.Entities;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Data; using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore;

View file

@ -1,5 +1,5 @@
using Sozsoft.Notifications.Domain; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection; using Sozsoft.Notifications.Domain;
using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;

View file

@ -1,10 +1,9 @@
using Volo.Abp.Localization; using Volo.Abp.Localization;
namespace Sozsoft.Sender.Localization namespace Sozsoft.Sender.Localization;
[LocalizationResourceName("SozsoftSender")]
public class ErpSenderResource
{ {
[LocalizationResourceName("SozsoftSender")]
public class ErpSenderResource
{
}
} }

Some files were not shown because too many files have changed in this diff Show more