Compare commits

...

114 commits
1.1.10 ... main

Author SHA1 Message Date
Sedat ÖZTÜRK
6742393271 Public sayfalar Custom componente taşındı 2026-09-08 18:00:23 +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
Sedat ÖZTÜRK
783c1fe1d3 Claude güncellemesi SqlQueryManager, Notification, FileManager 2026-08-11 22:55:20 +03:00
Sedat ÖZTÜRK
a5ee6de819 Claude güncellemesi AiBot güncellemesi 2026-08-11 16:45:08 +03:00
Sedat ÖZTÜRK
de7e6c1e21 Claude güncellemesi Intranet güncellemesi 2026-08-11 16:18:17 +03:00
Sedat ÖZTÜRK
5217887bb8 Claude güncellemesi Video Player güncellemesi 2026-08-11 15:44:56 +03:00
Sedat ÖZTÜRK
1fe4abaeb6 Claude güncellemesi Workflow Reset güncellemesi 2026-08-11 14:43:51 +03:00
Sedat ÖZTÜRK
db24baed91 Claude güncellemesi WizardManager güncellemesi 2026-08-11 14:09:58 +03:00
Sedat ÖZTÜRK
5626c27a90 Claude güncellemesi MenuManager güncellemesi 2026-08-11 13:29:41 +03:00
Sedat ÖZTÜRK
3ecb4e2102 Claude güncellemesi Notification güncellemesi 2026-08-11 13:00:43 +03:00
Sedat ÖZTÜRK
42d502f812 Claude güncellemesi MessengerHub güncellemesi 2026-08-11 11:56:56 +03:00
Sedat ÖZTÜRK
bbfa1cee0d Claude güncellemesi Public sayfaları güncellemesi 2026-08-10 16:53:24 +03:00
Sedat ÖZTÜRK
a42776e9c9 Claude güncellemesi ScriptBuilderDialog güncellemesi 2026-08-10 12:08:08 +03:00
Sedat ÖZTÜRK
2368ee3825 Claude güncellemesi Login.Result düzeltmeleri 2026-08-09 12:48:21 +03:00
Sedat ÖZTÜRK
b9550acda2 Calude güncellemesi FileManager optimizasyon 2026-08-09 12:08:57 +03:00
Sedat ÖZTÜRK
a01ce1533a Claude güncellemesi Setup ve Migration 2026-08-09 11:52:28 +03:00
Sedat ÖZTÜRK
56b74b85a5 Claude güncellemesi FormTab güncellemeri optimizasyon 2026-08-09 02:33:32 +03:00
Sedat ÖZTÜRK
0d27742f8c Claude güncellemesi Editor Script Builder güncellemesi 2026-08-09 02:06:14 +03:00
Sedat ÖZTÜRK
a1f5e5e539 Claude güncellemesi Editor Options Builder Modal güncellemeleri 2026-08-09 01:14:57 +03:00
Sedat ÖZTÜRK
38e3ef5797 Claude güncellemesi SqlDataSource Event geliştirmesi 2026-08-09 00:38:31 +03:00
Sedat ÖZTÜRK
93e337b9b2 Claude vite optimize ve check new version 2026-08-07 14:59:47 +03:00
Sedat ÖZTÜRK
81960df31b Claude güncellemesi SqlDataSource komponenti ve Localization 2026-08-07 12:32:42 +03:00
Sedat ÖZTÜRK
0224f4b3e1 Calude güncellemesi Grid, Tree, Rapor ve diğerleri 2026-08-07 01:54:50 +03:00
Sedat ÖZTÜRK
ac4ccd76d6 Claude güncellemesi ile Custom Component 2026-08-07 00:19:34 +03:00
Sedat ÖZTÜRK
24eb249f9f Custom component güncellemeleri 2026-08-06 16:17:59 +03:00
Sedat ÖZTÜRK
9a76afae24 Custom Component veritabanı yokken çağrısı 2026-08-06 12:16:10 +03:00
Sedat Öztürk
25ae424f42 Custom Component düzenlemesi 2026-08-05 23:51:43 +03:00
Sedat ÖZTÜRK
9cc77fb3a1 CardView lookup çağrısı düzeltildi. 2026-08-03 16:01:50 +03:00
Sedat Öztürk
f39a3e5764 List için select Endoint çağrı hatası düzeltildi. 2026-08-01 09:01:40 +03:00
1130 changed files with 125896 additions and 70267 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

9
.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/

3124
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

@ -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;

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 //SQL olan keyvalue'lari cikar ve sadece querylerde foreach yap
// QUERY -> qryDepo_Cikis_CekiListesi_Detayli // QUERY -> qryDepo_Cikis_CekiListesi_Detayli
// dict.add(item.Value, params[item.Key + "SQL"].Value) // dict.add(item.Value, params[item.Key + "SQL"].Value)
// QUERY2 -> qryDepo_Cikis_CekiListesi_Sub // QUERY2 -> qryDepo_Cikis_CekiListesi_Sub
// dict.add(item.Value, params[item.Key + "SQL"].Value) // dict.add(item.Value, params[item.Key + "SQL"].Value)
//Bunlari yeni dict'e ekle //Bunlari yeni dict'e ekle
//dict //dict
//qryDepo_Cikis_CekiListesi_Detayli -> Ksp_Depo_Cikis_CekiListesi_Detayli 252993 //qryDepo_Cikis_CekiListesi_Detayli -> Ksp_Depo_Cikis_CekiListesi_Detayli 252993
//qryDepo_Cikis_CekiListesi_Sub -> KSp_Depo_Cikis_CekiListesi_Sub 252993 //qryDepo_Cikis_CekiListesi_Sub -> KSp_Depo_Cikis_CekiListesi_Sub 252993
// */ // */
// //dict -> key (name), value (sql) // //dict -> key (name), value (sql)
// var queries = model.Filtreler // var queries = model.Filtreler
// .Where(a => a.Key.StartsWith("QUERY")) // .Where(a => a.Key.StartsWith("QUERY"))
// .Where(a => !a.Key.EndsWith("SQL")) // .Where(a => !a.Key.EndsWith("SQL"))
// .Where(a => !a.Value.IsNullOrWhiteSpace()) // .Where(a => !a.Value.IsNullOrWhiteSpace())
// .ToDictionary(x => x.Value, x => model.Filtreler.GetValueOrDefault(x.Key + "SQL")); // .ToDictionary(x => x.Value, x => model.Filtreler.GetValueOrDefault(x.Key + "SQL"));
// return MsAccessReportManager.GenerateAccessReport( // return MsAccessReportManager.GenerateAccessReport(
// model.DbAdi, // model.DbAdi,
// model.Index, // model.Index,
// model.RaporAdi, // model.RaporAdi,
// model.ExportDosyaTipi, // model.ExportDosyaTipi,
// queries, // queries,
// attachmentFolder); // attachmentFolder);
// } // }
// else // else
// { // {
// return MsAccessReportManager.GenerateAccessReport( // return MsAccessReportManager.GenerateAccessReport(
// model.DbAdi, // model.DbAdi,
// model.Index, // model.Index,
// model.RaporAdi, // model.RaporAdi,
// string.Join(" AND ", model.Filtreler.Select(a => $"{a.Key}={a.Value}")), // string.Join(" AND ", model.Filtreler.Select(a => $"{a.Key}={a.Value}")),
// model.ExportDosyaTipi, // model.ExportDosyaTipi,
// attachmentFolder); // 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;
@ -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

@ -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);
@ -41,10 +41,15 @@ public override partial NotificationRuleDto Map(NotificationRule source);
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)] [Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
public partial class NotificationToNotificationDtoMapper : MapperBase<Notification, NotificationDto> public partial class NotificationToNotificationDtoMapper : MapperBase<Notification, NotificationDto>
{ {
[MapperIgnoreTarget(nameof(NotificationDto.CreatorFullname))] // NotificationType hedefte string. Kaynaktaki navigation entity'si doğrudan
// eşlenirse ToString() devreye girip "[ENTITY: NotificationType] Id = ..."
// yazısı UI'a düşüyor; bu yüzden açıkça Name alanından beslenir.
[MapProperty("NotificationType.Name", nameof(NotificationDto.NotificationType))]
[MapperIgnoreTarget(nameof(NotificationDto.CreatorFullname))]
[MapperIgnoreTarget(nameof(NotificationDto.TenantId))] [MapperIgnoreTarget(nameof(NotificationDto.TenantId))]
public override partial NotificationDto Map(Notification source); public override partial NotificationDto Map(Notification source);
[MapProperty("NotificationType.Name", nameof(NotificationDto.NotificationType))]
[MapperIgnoreTarget(nameof(NotificationDto.CreatorFullname))] [MapperIgnoreTarget(nameof(NotificationDto.CreatorFullname))]
[MapperIgnoreTarget(nameof(NotificationDto.TenantId))] [MapperIgnoreTarget(nameof(NotificationDto.TenantId))]
public override partial void Map(Notification source, NotificationDto destination); public override partial void Map(Notification source, NotificationDto 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

@ -9,5 +9,5 @@ public static class NotificationChannels
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

@ -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;

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
{
}
} }

View file

@ -1,4 +1,4 @@
using Volo.Abp.BackgroundJobs; using Volo.Abp.BackgroundJobs;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;

View file

@ -1,4 +1,4 @@
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
namespace Sozsoft.Sender.Mail; namespace Sozsoft.Sender.Mail;

View file

@ -1,5 +1,4 @@
using System.Net.Http.Json; using System.Net.Http.Json;
using Microsoft.Extensions.Logging;
using Volo.Abp.Settings; using Volo.Abp.Settings;
namespace Sozsoft.Sender.Rocket; namespace Sozsoft.Sender.Rocket;
@ -12,16 +11,13 @@ public interface IRocketHttpClient
public sealed class RocketHttpClient : IRocketHttpClient, IDisposable public sealed class RocketHttpClient : IRocketHttpClient, IDisposable
{ {
private readonly HttpClient httpClient; private readonly HttpClient httpClient;
private readonly ILogger<RocketHttpClient> logger;
private readonly ISettingProvider settingProvider; private readonly ISettingProvider settingProvider;
public RocketHttpClient( public RocketHttpClient(
HttpClient httpClient, HttpClient httpClient,
ILogger<RocketHttpClient> logger,
ISettingProvider settingProvider) ISettingProvider settingProvider)
{ {
this.httpClient = httpClient; this.httpClient = httpClient;
this.logger = logger;
this.settingProvider = settingProvider; this.settingProvider = settingProvider;
} }

View file

@ -1,5 +1,4 @@
using Microsoft.Extensions.Logging; using Volo.Abp.Settings;
using Volo.Abp.Settings;
namespace Sozsoft.Sender.Sms.PostaGuvercini; namespace Sozsoft.Sender.Sms.PostaGuvercini;
@ -11,16 +10,13 @@ public interface IPostaGuverciniHttpClient
public sealed class PostaGuverciniHttpClient : IPostaGuverciniHttpClient, IDisposable public sealed class PostaGuverciniHttpClient : IPostaGuverciniHttpClient, IDisposable
{ {
private readonly HttpClient httpClient; private readonly HttpClient httpClient;
private readonly ILogger<PostaGuverciniHttpClient> logger;
private readonly ISettingProvider settingProvider; private readonly ISettingProvider settingProvider;
public PostaGuverciniHttpClient( public PostaGuverciniHttpClient(
HttpClient httpClient, HttpClient httpClient,
ILogger<PostaGuverciniHttpClient> logger,
ISettingProvider settingProvider) ISettingProvider settingProvider)
{ {
this.httpClient = httpClient; this.httpClient = httpClient;
this.logger = logger;
this.settingProvider = settingProvider; this.settingProvider = settingProvider;
} }

View file

@ -1,11 +1,12 @@
using Sozsoft.Sender.Localization; using Microsoft.Extensions.DependencyInjection;
using Sozsoft.Sender.Localization;
using Sozsoft.Sender.Mail; using Sozsoft.Sender.Mail;
using Sozsoft.Sender.Mail.AmazonSes; using Sozsoft.Sender.Mail.AmazonSes;
using Sozsoft.Sender.Rocket; using Sozsoft.Sender.Rocket;
using Sozsoft.Sender.Sms; using Sozsoft.Sender.Sms;
using Sozsoft.Sender.Sms.PostaGuvercini; using Sozsoft.Sender.Sms.PostaGuvercini;
using Sozsoft.Sender.Telegram;
using Sozsoft.Sender.WhatsApp; using Sozsoft.Sender.WhatsApp;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.BackgroundJobs; using Volo.Abp.BackgroundJobs;
using Volo.Abp.Emailing; using Volo.Abp.Emailing;
using Volo.Abp.Localization; using Volo.Abp.Localization;
@ -36,6 +37,7 @@ public class ErpSenderModule : AbpModule
context.Services.AddScoped<ISozsoftSmsSender>(sp => sp.GetRequiredService<PostaGuverciniSmsSender>()); context.Services.AddScoped<ISozsoftSmsSender>(sp => sp.GetRequiredService<PostaGuverciniSmsSender>());
context.Services.AddScoped<ISozsoftRocketSender>(sp => sp.GetRequiredService<RocketSender>()); context.Services.AddScoped<ISozsoftRocketSender>(sp => sp.GetRequiredService<RocketSender>());
context.Services.AddScoped<ISozsoftWhatsAppSender>(sp => sp.GetRequiredService<SozsoftWhatsAppSender>()); context.Services.AddScoped<ISozsoftWhatsAppSender>(sp => sp.GetRequiredService<SozsoftWhatsAppSender>());
context.Services.AddScoped<ISozsoftTelegramSender>(sp => sp.GetRequiredService<SozsoftTelegramSender>());
context.Services.AddHttpClient<PostaGuverciniHttpClient>(); context.Services.AddHttpClient<PostaGuverciniHttpClient>();
context.Services.AddScoped<IPostaGuverciniHttpClient>(sp => sp.GetRequiredService<PostaGuverciniHttpClient>()); context.Services.AddScoped<IPostaGuverciniHttpClient>(sp => sp.GetRequiredService<PostaGuverciniHttpClient>());
@ -46,6 +48,9 @@ public class ErpSenderModule : AbpModule
context.Services.AddHttpClient<WhatsAppHttpClient>(); context.Services.AddHttpClient<WhatsAppHttpClient>();
context.Services.AddScoped<IWhatsAppHttpClient>(sp => sp.GetRequiredService<WhatsAppHttpClient>()); context.Services.AddScoped<IWhatsAppHttpClient>(sp => sp.GetRequiredService<WhatsAppHttpClient>());
context.Services.AddHttpClient<TelegramHttpClient>();
context.Services.AddScoped<ITelegramHttpClient>(sp => sp.GetRequiredService<TelegramHttpClient>());
} }
} }

View file

@ -0,0 +1,44 @@
using Microsoft.Extensions.Logging;
using Volo.Abp.DependencyInjection;
namespace Sozsoft.Sender.Telegram;
public interface ISozsoftTelegramSender : ITransientDependency
{
Task<TelegramSendResult> SendAsync(string ChatId, string Message);
}
public class SozsoftTelegramSender : ISozsoftTelegramSender
{
private readonly ITelegramHttpClient httpClient;
private readonly ILogger<SozsoftTelegramSender> logger;
public SozsoftTelegramSender(
ITelegramHttpClient httpClient,
ILogger<SozsoftTelegramSender> logger)
{
this.httpClient = httpClient;
this.logger = logger;
}
public async Task<TelegramSendResult> SendAsync(string ChatId, string Message)
{
logger.LogInformation("Sending Telegram message. {ChatId}, {Message}", ChatId, Message);
var result = await httpClient.SendMessageAsync(ChatId, Message);
var success = result?.ok == true;
if (success)
{
logger.LogInformation("Telegram sent successfully. {ChatId}, {Message}", ChatId, Message);
}
else
{
logger.LogWarning("Failed to send Telegram message. {ChatId}, {Message}, {ErrorCode}, {Description}",
ChatId, Message, result?.error_code, result?.description);
}
return new TelegramSendResult
{
Success = success,
};
}
}

View file

@ -0,0 +1,44 @@
using System.Net.Http.Json;
using Volo.Abp.Settings;
namespace Sozsoft.Sender.Telegram;
public interface ITelegramHttpClient
{
Task<TelegramSendResultDto> SendMessageAsync(string ChatId, string Message);
}
public sealed class TelegramHttpClient : ITelegramHttpClient, IDisposable
{
private readonly HttpClient httpClient;
private readonly ISettingProvider settingProvider;
public TelegramHttpClient(
HttpClient httpClient,
ISettingProvider settingProvider)
{
this.httpClient = httpClient;
this.settingProvider = settingProvider;
}
public async Task<TelegramSendResultDto> SendMessageAsync(string ChatId, string Message)
{
var url = await settingProvider.GetOrNullAsync(TelegramSettingNames.Url);
var botToken = await settingProvider.GetOrNullAsync(TelegramSettingNames.BotToken);
var parseMode = await settingProvider.GetOrNullAsync(TelegramSettingNames.ParseMode);
var response = await httpClient.PostAsJsonAsync($"{url.TrimEnd('/')}/bot{botToken}/sendMessage",
new TelegramPostMessageInput
{
chat_id = ChatId,
text = Message,
parse_mode = parseMode.IsNullOrWhiteSpace() ? null : parseMode,
disable_web_page_preview = true
});
// Bot API hatayı 4xx gövdesinde açıklar; durum kodunu fırlatmak açıklamayı kaybettirir.
return await response.Content.ReadFromJsonAsync<TelegramSendResultDto>();
}
public void Dispose() => httpClient?.Dispose();
}

View file

@ -0,0 +1,9 @@
namespace Sozsoft.Sender.Telegram;
public class TelegramPostMessageInput
{
public string chat_id { get; set; }
public string text { get; set; }
public string parse_mode { get; set; }
public bool disable_web_page_preview { get; set; }
}

View file

@ -0,0 +1,6 @@
namespace Sozsoft.Sender.Telegram;
public class TelegramSendResult
{
public bool Success { get; set; }
}

View file

@ -0,0 +1,23 @@
namespace Sozsoft.Sender.Telegram;
public class TelegramSendResultDto
{
public bool ok { get; set; }
public int? error_code { get; set; }
public string description { get; set; }
public Message result { get; set; }
public class Message
{
public long message_id { get; set; }
public int date { get; set; }
public Chat chat { get; set; }
public class Chat
{
public long id { get; set; }
public string type { get; set; }
public string username { get; set; }
}
}
}

View file

@ -0,0 +1,9 @@
namespace Sozsoft.Sender.Telegram;
public static class TelegramSettingNames
{
public const string Default = "App.Sender.Telegram.";
public const string Url = Default + "Url"; // https://api.telegram.org
public const string BotToken = Default + "BotToken"; // BotFather token: 123456:ABC-DEF...
public const string ParseMode = Default + "ParseMode"; // HTML | Markdown | MarkdownV2 | (bos)
}

View file

@ -1,6 +1,5 @@
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Net.Http.Json; using System.Net.Http.Json;
using Microsoft.Extensions.Logging;
using Volo.Abp.Settings; using Volo.Abp.Settings;
namespace Sozsoft.Sender.WhatsApp; namespace Sozsoft.Sender.WhatsApp;
@ -13,16 +12,13 @@ public interface IWhatsAppHttpClient
public sealed class WhatsAppHttpClient : IWhatsAppHttpClient, IDisposable public sealed class WhatsAppHttpClient : IWhatsAppHttpClient, IDisposable
{ {
private readonly HttpClient httpClient; private readonly HttpClient httpClient;
private readonly ILogger<WhatsAppHttpClient> logger;
private readonly ISettingProvider settingProvider; private readonly ISettingProvider settingProvider;
public WhatsAppHttpClient( public WhatsAppHttpClient(
HttpClient httpClient, HttpClient httpClient,
ILogger<WhatsAppHttpClient> logger,
ISettingProvider settingProvider) ISettingProvider settingProvider)
{ {
this.httpClient = httpClient; this.httpClient = httpClient;
this.logger = logger;
this.settingProvider = settingProvider; this.settingProvider = settingProvider;
} }

View file

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

View file

@ -1,9 +0,0 @@
namespace Sozsoft.Settings;
public class SettingsRemoteServiceConsts
{
public const string RemoteServiceName = "Settings";
public const string ModuleName = "settings";
}

View file

@ -2,7 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Sozsoft.Languages;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Application.Services; using Volo.Abp.Application.Services;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
@ -15,20 +14,17 @@ namespace Sozsoft.Settings;
public class SettingUiAppService : ApplicationService, ISettingUiAppService public class SettingUiAppService : ApplicationService, ISettingUiAppService
{ {
//private readonly IRepository<SettingDefinition, Guid> repositorySettingDefinition; //private readonly IRepository<SettingDefinition, Guid> repositorySettingDefinition;
private readonly ILanguageKeyIntegrationService languageKeyIntegrationService;
private readonly ISettingDefinitionManager settingDefinitionManager; private readonly ISettingDefinitionManager settingDefinitionManager;
private readonly ISettingManager settingManager; private readonly ISettingManager settingManager;
private readonly ErpSettingDefinitionManager ErpSettingDefinitionManager; private readonly ErpSettingDefinitionManager ErpSettingDefinitionManager;
private readonly ICurrentTenant currentTenant; private readonly ICurrentTenant currentTenant;
public SettingUiAppService( public SettingUiAppService(
ILanguageKeyIntegrationService languageKeyIntegrationService,
ISettingDefinitionManager settingDefinitionManager, ISettingDefinitionManager settingDefinitionManager,
ISettingManager settingManager, ISettingManager settingManager,
ErpSettingDefinitionManager ErpSettingDefinitionManager, ErpSettingDefinitionManager ErpSettingDefinitionManager,
ICurrentTenant currentTenant) ICurrentTenant currentTenant)
{ {
this.languageKeyIntegrationService = languageKeyIntegrationService;
this.settingDefinitionManager = settingDefinitionManager; this.settingDefinitionManager = settingDefinitionManager;
this.settingManager = settingManager; this.settingManager = settingManager;
this.ErpSettingDefinitionManager = ErpSettingDefinitionManager; this.ErpSettingDefinitionManager = ErpSettingDefinitionManager;

View file

@ -1,5 +1,5 @@
using Riok.Mapperly.Abstractions;
using Sozsoft.Settings.Entities; using Sozsoft.Settings.Entities;
using Riok.Mapperly.Abstractions;
using Volo.Abp.Mapperly; using Volo.Abp.Mapperly;
namespace Sozsoft.Settings; namespace Sozsoft.Settings;
@ -7,7 +7,7 @@ namespace Sozsoft.Settings;
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)] [Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
public partial class SettingDefinitionToSettingDefinitionDtoMapper : TwoWayMapperBase<SettingDefinition, SettingDefinitionDto> public partial class SettingDefinitionToSettingDefinitionDtoMapper : TwoWayMapperBase<SettingDefinition, SettingDefinitionDto>
{ {
public override partial SettingDefinitionDto Map(SettingDefinition source); public override partial SettingDefinitionDto Map(SettingDefinition source);
public override partial void Map(SettingDefinition source, SettingDefinitionDto destination); public override partial void Map(SettingDefinition source, SettingDefinitionDto destination);

View file

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

View file

@ -2,9 +2,9 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Sozsoft.Settings.Localization;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
using Sozsoft.Settings.Localization;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Services; using Volo.Abp.Domain.Services;
using Volo.Abp.Settings; using Volo.Abp.Settings;

View file

@ -1,4 +1,4 @@
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Abp.SettingManagement; using Volo.Abp.SettingManagement;
using Volo.Abp.Settings; using Volo.Abp.Settings;

View file

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

View file

@ -1,9 +1,9 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
using Sozsoft.Settings.Entities;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.ChangeTracking;
using Sozsoft.Settings.Entities;
using Volo.Abp; using Volo.Abp;
using Volo.Abp.EntityFrameworkCore.Modeling; using Volo.Abp.EntityFrameworkCore.Modeling;
using Volo.Abp.SettingManagement.EntityFrameworkCore; using Volo.Abp.SettingManagement.EntityFrameworkCore;

View file

@ -1,4 +1,4 @@
namespace Sozsoft.SqlQueryManager.Application.Contracts; namespace Sozsoft.SqlQueryManager.Application.Contracts;
public class DatabaseTableDto public class DatabaseTableDto
{ {

View file

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.Application.Services; using Volo.Abp.Application.Services;
@ -30,29 +30,16 @@ public interface ISqlObjectManagerAppService : IApplicationService
Task<string> GetNativeObjectDefinitionAsync(string dataSourceCode, string schemaName, string objectName); Task<string> GetNativeObjectDefinitionAsync(string dataSourceCode, string schemaName, string objectName);
/// <summary> /// <summary>
/// Saves the T-SQL script to Seeds/SqlData/{fileName}.sql in the DbMigrator project. /// Saves the T-SQL script to {SeedsRoot}/{host|tenants/{tenantId}}/{sql|postgres}/object/{fileName}.sql
/// (repo: configs/seeds). The scope folder follows the current tenant: host context writes to host/.
/// Called automatically after a successful SqlTableDesigner deploy so the script can be re-seeded. /// Called automatically after a successful SqlTableDesigner deploy so the script can be re-seeded.
/// </summary> /// </summary>
Task SaveTableScriptAsync(SaveTableScriptDto input); Task SaveTableScriptAsync(SaveTableScriptDto input);
/// <summary> /// <summary>
/// Deletes matching SQL seed files from Seeds/SqlData when objects are dropped from the UI. /// Deletes matching SQL seed files from the current scope's object folder when objects are
/// dropped from the UI.
/// Non-existing files are ignored. /// Non-existing files are ignored.
/// </summary> /// </summary>
Task DeleteSqlDataFilesAsync(DeleteSqlDataFilesDto input); Task DeleteSqlDataFilesAsync(DeleteSqlDataFilesDto input);
/// <summary>
/// Lists .sql files currently available under DbMigrator Seeds/SqlData.
/// </summary>
Task<List<SqlDataFileDto>> GetSqlDataFilesAsync(string dataDirectoryName = "SqlData", string relativePath = "");
/// <summary>
/// Reads a .sql seed file content from the selected data directory.
/// </summary>
Task<string> GetSqlDataFileContentAsync(string dataDirectoryName = "SqlData", string relativePath = "");
/// <summary>
/// Moves a SQL seed file between the selected data directory root and HostData.
/// </summary>
Task MoveSqlDataFileAsync(MoveSqlDataFileDto input);
} }

View file

@ -1,12 +0,0 @@
using System;
namespace Sozsoft.SqlQueryManager.Application.Contracts;
public class SqlDataFileDto
{
public string FileName { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string RelativePath { get; set; } = string.Empty;
public bool IsDirectory { get; set; }
public DateTime CreatedAt { get; set; }
}

View file

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace Sozsoft.SqlQueryManager.Application.Contracts; namespace Sozsoft.SqlQueryManager.Application.Contracts;
@ -20,7 +20,8 @@ public class SqlQueryExecutionResultDto
} }
/// <summary> /// <summary>
/// Input for saving a T-SQL script file to DbMigrator Seeds/SqlData. /// Input for saving a T-SQL script file to the seeds folder
/// ({App:SeedsPath}/{host|tenants/{tenantId}}/{sql|postgres}/object).
/// </summary> /// </summary>
public class SaveTableScriptDto public class SaveTableScriptDto
{ {
@ -36,7 +37,8 @@ public class SaveTableScriptDto
} }
/// <summary> /// <summary>
/// Input for deleting seed files from DbMigrator Seeds/SqlData. /// Input for deleting seed files from the seeds folder
/// ({App:SeedsPath}/{host|tenants/{tenantId}}/{sql|postgres}/object).
/// </summary> /// </summary>
public class DeleteSqlDataFilesDto public class DeleteSqlDataFilesDto
{ {
@ -46,10 +48,3 @@ public class DeleteSqlDataFilesDto
/// </summary> /// </summary>
public List<string> FileNames { get; set; } = new(); public List<string> FileNames { get; set; } = new();
} }
public class MoveSqlDataFileDto
{
public string DataDirectoryName { get; set; } = string.Empty;
public string SourceRelativePath { get; set; } = string.Empty;
public string TargetRelativePath { get; set; } = string.Empty;
}

View file

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace Sozsoft.SqlQueryManager.Application.Contracts; namespace Sozsoft.SqlQueryManager.Application.Contracts;

View file

@ -1,4 +1,4 @@
using Volo.Abp.Application; using Volo.Abp.Application;
using Volo.Abp.Authorization; using Volo.Abp.Authorization;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;

View file

@ -1,6 +1,4 @@
using System.Collections.Generic; namespace Sozsoft.SqlQueryManager.Application.Contracts;
namespace Sozsoft.SqlQueryManager.Application.Contracts;
public class SqlTemplateDto public class SqlTemplateDto
{ {
@ -9,10 +7,3 @@ public class SqlTemplateDto
public string Description { get; set; } public string Description { get; set; }
public string Template { get; set; } public string Template { get; set; }
} }
public class GetTemplateInput
{
public string TemplateType { get; set; }
public string ObjectName { get; set; }
public string SchemaName { get; set; }
}

View file

@ -2,19 +2,21 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Sozsoft.SqlQueryManager.Application.Contracts;
using Sozsoft.SqlQueryManager.Domain.Services;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Volo.Abp.Application.Services; using Sozsoft.Platform.Data.Seeds;
using Volo.Abp.MultiTenancy;
using System.Text.RegularExpressions;
using Sozsoft.Platform.Enums; using Sozsoft.Platform.Enums;
using Sozsoft.Platform.Queries; using Sozsoft.Platform.Queries;
using Sozsoft.Settings;
using Sozsoft.SqlQueryManager.Application.Contracts;
using Sozsoft.SqlQueryManager.Domain.Services;
using Volo.Abp.Application.Services;
using Volo.Abp.MultiTenancy;
namespace Sozsoft.SqlQueryManager.Application; namespace Sozsoft.SqlQueryManager.Application;
@ -48,7 +50,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
private readonly IDataSourceManager _dataSourceManager; private readonly IDataSourceManager _dataSourceManager;
private readonly ICurrentTenant _currentTenant; private readonly ICurrentTenant _currentTenant;
private readonly IHttpContextAccessor _httpContextAccessor; private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IHostEnvironment _hostEnvironment; private readonly IConfiguration _configuration;
private readonly ILogger<SqlObjectManagerAppService> _logger; private readonly ILogger<SqlObjectManagerAppService> _logger;
public SqlObjectManagerAppService( public SqlObjectManagerAppService(
@ -57,7 +59,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
IDataSourceManager dataSourceManager, IDataSourceManager dataSourceManager,
ICurrentTenant currentTenant, ICurrentTenant currentTenant,
IHttpContextAccessor httpContextAccessor, IHttpContextAccessor httpContextAccessor,
IHostEnvironment hostEnvironment, IConfiguration configuration,
ILogger<SqlObjectManagerAppService> logger) ILogger<SqlObjectManagerAppService> logger)
{ {
_sqlExecutorService = sqlExecutorService; _sqlExecutorService = sqlExecutorService;
@ -65,7 +67,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
_dataSourceManager = dataSourceManager; _dataSourceManager = dataSourceManager;
_currentTenant = currentTenant; _currentTenant = currentTenant;
_httpContextAccessor = httpContextAccessor; _httpContextAccessor = httpContextAccessor;
_hostEnvironment = hostEnvironment; _configuration = configuration;
_logger = logger; _logger = logger;
} }
@ -954,191 +956,27 @@ FROM (
return Task.CompletedTask; return Task.CompletedTask;
} }
[HttpGet("api/app/sql-object-manager/sql-data-files")]
public Task<List<SqlDataFileDto>> GetSqlDataFilesAsync(
[FromQuery] string dataDirectoryName = "SqlData",
[FromQuery] string relativePath = "")
{
ValidateTenantAccess();
try
{
var rootPath = ResolveSqlDataOutputPath(dataDirectoryName);
var outputPath = ResolveSqlDataChildPath(rootPath, relativePath);
if (!Directory.Exists(outputPath))
return Task.FromResult(new List<SqlDataFileDto>());
var directories = Directory.GetDirectories(outputPath, "*", SearchOption.TopDirectoryOnly)
.Where(d => string.Equals(Path.GetFileName(d), "HostData", StringComparison.OrdinalIgnoreCase))
.Select(d => new SqlDataFileDto
{
FileName = Path.GetFileName(d)!,
Name = Path.GetFileName(d)!,
RelativePath = BuildSqlDataRelativePath(relativePath, Path.GetFileName(d)!),
IsDirectory = true,
CreatedAt = Directory.GetCreationTime(d)
});
var files = Directory.GetFiles(outputPath, "*.sql", SearchOption.TopDirectoryOnly)
.Select(f => new SqlDataFileDto
{
FileName = Path.GetFileName(f)!,
Name = Path.GetFileName(f)!,
RelativePath = BuildSqlDataRelativePath(relativePath, Path.GetFileName(f)!),
IsDirectory = false,
CreatedAt = File.GetCreationTime(f)
})
.Where(x => !string.IsNullOrWhiteSpace(x.Name));
var entries = directories
.Concat(files)
.OrderByDescending(x => x.IsDirectory)
.ThenBy(x => x.Name, StringComparer.OrdinalIgnoreCase)
.ToList();
return Task.FromResult(entries);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to list SQL seed files: {Message}", ex.Message);
return Task.FromResult(new List<SqlDataFileDto>());
}
}
[HttpGet("api/app/sql-object-manager/sql-data-file-content")]
public async Task<string> GetSqlDataFileContentAsync(
[FromQuery] string dataDirectoryName = "SqlData",
[FromQuery] string relativePath = "")
{
ValidateTenantAccess();
var rootPath = ResolveSqlDataOutputPath(dataDirectoryName);
var filePath = ResolveSqlDataChildPath(rootPath, relativePath);
if (!File.Exists(filePath))
throw new Volo.Abp.UserFriendlyException("SQL seed file was not found.");
if (!string.Equals(Path.GetExtension(filePath), ".sql", StringComparison.OrdinalIgnoreCase))
throw new Volo.Abp.UserFriendlyException("Only .sql files can be previewed.");
return await File.ReadAllTextAsync(filePath);
}
[HttpPost("api/app/sql-object-manager/move-sql-data-file")]
public Task MoveSqlDataFileAsync(MoveSqlDataFileDto input)
{
ValidateTenantAccess();
if (input == null)
throw new Volo.Abp.UserFriendlyException("Invalid move request.");
var rootPath = ResolveSqlDataOutputPath(input.DataDirectoryName);
var sourcePath = ResolveSqlDataChildPath(rootPath, input.SourceRelativePath);
var targetPath = ResolveSqlDataChildPath(rootPath, input.TargetRelativePath);
if (!File.Exists(sourcePath))
throw new Volo.Abp.UserFriendlyException("Source file was not found.");
if (!string.Equals(Path.GetExtension(sourcePath), ".sql", StringComparison.OrdinalIgnoreCase) ||
!string.Equals(Path.GetExtension(targetPath), ".sql", StringComparison.OrdinalIgnoreCase))
{
throw new Volo.Abp.UserFriendlyException("Only .sql files can be moved.");
}
Directory.CreateDirectory(Path.GetDirectoryName(targetPath)!);
if (File.Exists(targetPath))
throw new Volo.Abp.UserFriendlyException("A file with the same name already exists in the target folder.");
File.Move(sourcePath, targetPath);
_logger.LogInformation("SQL seed file moved from {SourcePath} to {TargetPath}", sourcePath, targetPath);
return Task.CompletedTask;
}
/// <summary>
/// Script dosyalarinin yazilacagi klasor. SqlDataSeeder ile ayni cozumleyiciyi kullanir
/// (App:SeedsPath -> configs/seeds), aksi halde deploy edilen nesne seed olarak geri okunmaz.
/// <para>
/// Yol, islemin yapildigi veritabani kapsamini izler: host (varsayilan) baglaminda
/// <c>host/{sql|postgres}/object</c>, bir tenant secilmisken
/// <c>tenants/{tenantId}/{sql|postgres}/object</c>. Boylece nesne, seed sirasinda
/// olusturuldugu veritabaninda yeniden olusturulur.
/// </para>
/// </summary>
private string ResolveSqlDataOutputPath() private string ResolveSqlDataOutputPath()
{ {
return ResolveSqlDataOutputPath("SqlData"); var providerDirectoryName = SeedPathResolver.GetProviderFolderName(
SettingsConsts.DefaultDatabaseProvider == SettingsConsts.DatabaseProvider.PostgreSql);
return SeedPathResolver.GetScopePath(
_configuration,
_currentTenant.Id,
providerDirectoryName,
SeedPathResolver.ObjectFolder);
} }
private string ResolveSqlDataOutputPath(string dataDirectoryName)
{
const string dbMigratorName = "Sozsoft.Platform.DbMigrator";
var safeDirectoryName = NormalizeSqlDataDirectoryName(dataDirectoryName);
var dir = new DirectoryInfo(_hostEnvironment.ContentRootPath);
while (dir != null)
{
var candidate = Path.Combine(dir.FullName, "src", dbMigratorName, "Seeds");
if (Directory.Exists(candidate))
return Path.Combine(candidate, safeDirectoryName);
candidate = Path.Combine(dir.FullName, dbMigratorName, "Seeds");
if (Directory.Exists(candidate))
return Path.Combine(candidate, safeDirectoryName);
dir = dir.Parent;
}
return Path.Combine(_hostEnvironment.ContentRootPath, "Seeds", safeDirectoryName);
}
private static string NormalizeSqlDataDirectoryName(string dataDirectoryName)
{
return string.Equals(dataDirectoryName, "PostgresData", StringComparison.OrdinalIgnoreCase)
? "PostgresData"
: "SqlData";
}
private static string ResolveSqlDataChildPath(string rootPath, string relativePath)
{
var normalized = NormalizeSqlDataRelativePath(relativePath);
var fullPath = Path.GetFullPath(Path.Combine(rootPath, normalized));
var fullRoot = Path.GetFullPath(rootPath);
var fullRootWithSeparator = fullRoot.EndsWith(Path.DirectorySeparatorChar)
? fullRoot
: fullRoot + Path.DirectorySeparatorChar;
if (!string.Equals(fullPath, fullRoot, StringComparison.OrdinalIgnoreCase) &&
!fullPath.StartsWith(fullRootWithSeparator, StringComparison.OrdinalIgnoreCase))
{
throw new Volo.Abp.UserFriendlyException("Invalid path.");
}
return fullPath;
}
private static string NormalizeSqlDataRelativePath(string relativePath)
{
if (string.IsNullOrWhiteSpace(relativePath))
return string.Empty;
var normalized = relativePath.Replace('\\', '/').Trim('/');
var parts = normalized.Split('/', StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 0)
return string.Empty;
if (parts.Any(p => p == "." || p == ".." || p.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0))
throw new Volo.Abp.UserFriendlyException("Invalid path.");
var isRootFile = parts.Length == 1 && parts[0].EndsWith(".sql", StringComparison.OrdinalIgnoreCase);
var isHostDataFolder = parts.Length == 1 && string.Equals(parts[0], "HostData", StringComparison.OrdinalIgnoreCase);
var isHostDataFile = parts.Length == 2 &&
string.Equals(parts[0], "HostData", StringComparison.OrdinalIgnoreCase) &&
parts[1].EndsWith(".sql", StringComparison.OrdinalIgnoreCase);
if (!isRootFile && !isHostDataFolder && !isHostDataFile)
throw new Volo.Abp.UserFriendlyException("Invalid path.");
return Path.Combine(parts);
}
private static string BuildSqlDataRelativePath(string parentRelativePath, string name)
{
if (string.IsNullOrWhiteSpace(parentRelativePath))
return name;
return $"{parentRelativePath.Trim('/', '\\')}/{name}";
}
} }

View file

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

View file

@ -1,4 +1,4 @@
using Volo.Abp.Localization; using Volo.Abp.Localization;
namespace Sozsoft.SqlQueryManager.Domain.Shared; namespace Sozsoft.SqlQueryManager.Domain.Shared;

View file

@ -1,4 +1,4 @@
namespace Sozsoft.SqlQueryManager.Domain.Shared; namespace Sozsoft.SqlQueryManager.Domain.Shared;
// SqlObjectType, SqlFunctionType and SqlQueryStatus enums removed: // SqlObjectType, SqlFunctionType and SqlQueryStatus enums removed:
// The module no longer persists SQL objects and therefore does not need these enums. // The module no longer persists SQL objects and therefore does not need these enums.

View file

@ -1,4 +1,4 @@
using Sozsoft.SqlQueryManager.Domain.Shared; using Sozsoft.SqlQueryManager.Domain.Shared;
using Volo.Abp.Domain; using Volo.Abp.Domain;
using Volo.Abp.Localization; using Volo.Abp.Localization;
using Volo.Abp.Localization.ExceptionHandling; using Volo.Abp.Localization.ExceptionHandling;

View file

@ -1,11 +0,0 @@
namespace Sozsoft.SqlQueryManager.Domain.Shared;
public static class SqlQueryManagerErrorCodes
{
public const string InvalidSqlQuery = "SqlQueryManager:InvalidSqlQuery";
public const string DataSourceNotFound = "SqlQueryManager:DataSourceNotFound";
public const string ExecutionFailed = "SqlQueryManager:ExecutionFailed";
public const string PermissionDenied = "SqlQueryManager:PermissionDenied";
public const string InvalidObjectName = "SqlQueryManager:InvalidObjectName";
public const string ObjectAlreadyExists = "SqlQueryManager:ObjectAlreadyExists";
}

View file

@ -1,4 +1,4 @@
using Volo.Abp.Reflection; using Volo.Abp.Reflection;
namespace Sozsoft.SqlQueryManager.Domain.Shared; namespace Sozsoft.SqlQueryManager.Domain.Shared;

View file

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace Sozsoft.SqlQueryManager.Domain.Services; namespace Sozsoft.SqlQueryManager.Domain.Services;

View file

@ -1,13 +1,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Sozsoft.Platform.DynamicData; using Sozsoft.Platform.DynamicData;
using Sozsoft.Platform.Enums; using Sozsoft.Platform.Enums;
using Sozsoft.Platform.Queries; using Sozsoft.Platform.Queries;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp; using Volo.Abp;
using Volo.Abp.Domain.Services; using Volo.Abp.Domain.Services;

View file

@ -1,5 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
namespace Sozsoft.SqlQueryManager.Domain.Services; namespace Sozsoft.SqlQueryManager.Domain.Services;

View file

@ -1,5 +1,4 @@
using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Domain;
using Volo.Abp.Domain;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
namespace Sozsoft.SqlQueryManager; namespace Sozsoft.SqlQueryManager;

View file

@ -1,4 +1,4 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Volo.Abp.Data; using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore;

View file

@ -1,4 +1,4 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;

View file

@ -1,4 +1,4 @@
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
namespace Sozsoft.Platform.AuditLogs; namespace Sozsoft.Platform.AuditLogs;

View file

@ -0,0 +1,13 @@
using System;
using Volo.Abp.Application.Dtos;
namespace Sozsoft.Platform.ChangeLogs;
public class ChangeLogDto : EntityDto<Guid>
{
public string Version { get; set; }
public DateTime ReleaseDate { get; set; }
/// <summary>Sürüm notu maddeleri (entity'de satır satır saklanır).</summary>
public string[] ChangeLog { get; set; }
}

View file

@ -7,21 +7,31 @@ namespace Sozsoft.Platform.DeveloperKit;
public class CustomComponentDto : FullAuditedEntityDto<Guid> public class CustomComponentDto : FullAuditedEntityDto<Guid>
{ {
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
public string RoutePath { get; set; } = string.Empty; /// <summary>Rotasi olmayan bilesende (ust bilgi, alt bilgi) bostur.</summary>
public string? RoutePath { get; set; }
public string Code { get; set; } = string.Empty; public string Code { get; set; } = string.Empty;
public string? Props { get; set; } public string? Props { get; set; }
public string? Description { get; set; } public string? Description { get; set; }
public bool IsActive { get; set; } = true; public bool IsActive { get; set; } = true;
public string? Dependencies { get; set; } // JSON string of component names public string? Dependencies { get; set; } // JSON string of component names
/// <summary>
/// Data sekmesinde kullanilan API'ler; JSON dizisi. Props icindeki designer dokumanindan
/// kaydetme sirasinda sunucu tarafinda uretilir, istemciden gonderilmez.
/// </summary>
public string? DataSources { get; set; }
} }
public class CreateUpdateCustomComponentDto public class CreateUpdateCustomComponentDto
{ {
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
[Required] /// <summary>
/// Bilesenin kendi rotasi. Bos birakilabilir: rotasi olmayan bir bilesen (ornegin
/// baska bir ekranin icinde cizilen alt bilgi) hicbir adrese baglanmaz.
/// </summary>
[StringLength(512)] [StringLength(512)]
[RegularExpression(@"^/.*", ErrorMessage = "RoutePath must start with '/'.")] [RegularExpression(@"^(/.*)?$", ErrorMessage = "RoutePath must start with '/'.")]
public string RoutePath { get; set; } = string.Empty; public string? RoutePath { get; set; }
public string Code { get; set; } = string.Empty; public string Code { get; set; } = string.Empty;
public string? Props { get; set; } public string? Props { get; set; }
public string? Description { get; set; } public string? Description { get; set; }

View file

@ -12,9 +12,17 @@ public interface ICrudEndpointAppService : ICrudAppService<
PagedAndSortedResultRequestDto, PagedAndSortedResultRequestDto,
CreateUpdateCrudEndpointDto> CreateUpdateCrudEndpointDto>
{ {
Task<List<CrudEndpointDto>> GetActiveEndpointsAsync();
Task<List<CrudEndpointDto>> GetEndpointsByEntityAsync(string entityName); Task<List<CrudEndpointDto>> GetEndpointsByEntityAsync(string entityName);
Task<CrudEndpointDto> ToggleAsync(Guid id); Task<CrudEndpointDto> ToggleAsync(Guid id);
Task<PagedResultDto<CrudEndpointDto>> GenerateCrudEndpointsAsync(string entityName); /// <param name="operationTypes">
/// Aktif baslatilacak operasyonlarin virgulle ayrilmis listesi
/// (GetList, GetById, Create, Update, Delete). Bos birakilirsa hepsi aktif olur.
/// </param>
/// <param name="objectType">
/// Veritabani nesnesinin turu (table, view, procedure, function). Tablo disindaki
/// nesneler icin yalnizca okuma endpoint'leri (GetList, GetById) uretilir.
/// </param>
Task<PagedResultDto<CrudEndpointDto>> GenerateCrudEndpointsAsync(string entityName, string operationTypes = null, string objectType = null);
Task DeleteByEntityAsync(string entityName);
} }

View file

@ -1,6 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services; using Volo.Abp.Application.Services;
@ -12,7 +10,4 @@ public interface ICustomComponentAppService : ICrudAppService<
PagedAndSortedResultRequestDto, PagedAndSortedResultRequestDto,
CreateUpdateCustomComponentDto> CreateUpdateCustomComponentDto>
{ {
Task<List<CustomComponentDto>> GetActiveComponentsAsync();
} }

View file

@ -1,7 +1,7 @@
#nullable enable #nullable enable
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Content; using Volo.Abp.Content;
namespace Sozsoft.Platform.FileManagement; namespace Sozsoft.Platform.FileManagement;

View file

@ -44,34 +44,6 @@ public class ForumCategoryDto : FullAuditedEntityDto<Guid>
public Guid? LastPostUserId { get; set; } public Guid? LastPostUserId { get; set; }
} }
public class CreateForumCategoryDto
{
public Guid? TenantId { get; set; }
[Required]
[StringLength(100)]
public string Name { get; set; }
[Required]
[StringLength(100)]
public string Slug { get; set; }
[Required]
[StringLength(500)]
public string Description { get; set; }
[StringLength(10)]
public string Icon { get; set; }
public int DisplayOrder { get; set; }
public bool IsActive { get; set; } = true;
public bool IsLocked { get; set; } = false;
}
public class UpdateForumCategoryDto : CreateForumCategoryDto
{
}
public class GetCategoriesInput : PagedAndSortedResultRequestDto public class GetCategoriesInput : PagedAndSortedResultRequestDto
{ {
public bool? IsActive { get; set; } public bool? IsActive { get; set; }

View file

@ -14,9 +14,6 @@ public interface IForumAppService : IApplicationService
Task<PagedResultDto<ForumCategoryDto>> GetCategoriesAsync(GetCategoriesInput input); Task<PagedResultDto<ForumCategoryDto>> GetCategoriesAsync(GetCategoriesInput input);
Task<ForumCategoryDto> GetCategoryAsync(Guid id); Task<ForumCategoryDto> GetCategoryAsync(Guid id);
Task<ForumCategoryDto> GetCategoryBySlugAsync(string slug); Task<ForumCategoryDto> GetCategoryBySlugAsync(string slug);
Task<ForumCategoryDto> CreateCategoryAsync(CreateForumCategoryDto input);
Task<ForumCategoryDto> UpdateCategoryAsync(Guid id, UpdateForumCategoryDto input);
Task DeleteCategoryAsync(Guid id);
// Topics // Topics
Task<PagedResultDto<ForumTopicDto>> GetTopicsAsync(GetTopicsInput input); Task<PagedResultDto<ForumTopicDto>> GetTopicsAsync(GetTopicsInput input);

View file

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Sozsoft.Platform.Identity.Dto; namespace Sozsoft.Platform.Identity.Dto;

View file

@ -1,4 +1,4 @@
using System; using System;
using Volo.Abp.Content; using Volo.Abp.Content;
namespace Sozsoft.Platform.Identity.Dto; namespace Sozsoft.Platform.Identity.Dto;

View file

@ -6,8 +6,8 @@ namespace Sozsoft.Platform.Identity.Dto;
public class UserClaimModel : ExtensibleObject public class UserClaimModel : ExtensibleObject
{ {
public Guid Id { get; set; } public Guid Id { get; set; }
public Guid UserId { get; set; } public Guid UserId { get; set; }
public string ClaimType { get; set; } public string ClaimType { get; set; }
public string ClaimValue { get; set; } public string ClaimValue { get; set; }
} }

View file

@ -38,6 +38,7 @@ public class UserInfoViewModel : ExtensibleObject
public bool ShouldChangePasswordOnNextLogin { get; set; } public bool ShouldChangePasswordOnNextLogin { get; set; }
public string Avatar { get; set; } public string Avatar { get; set; }
public string RocketUsername { get; set; } public string RocketUsername { get; set; }
public string TelegramChatId { get; set; }
public DateTimeOffset? CreationTime { get; set; } public DateTimeOffset? CreationTime { get; set; }
public DateTimeOffset? LastModificationTime { get; set; } public DateTimeOffset? LastModificationTime { get; set; }
public string WorkHour { get; set; } public string WorkHour { get; set; }

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