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.
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
Core stack:
- Backend: C# .NET 9 + ABP 9.x
- Frontend: React 18 + DevExtreme
- Backend: C# .NET 10 (C# 14) + ABP 10.x — kod standardı: `dotnet.instructions.md`
- Frontend: React 19 + DevExtreme
- Database: SQL Server (dynamic datasource support)
- Cache: Redis
- Background jobs: Hangfire + ABP Background Workers
@ -41,11 +52,25 @@ System nature:
3. Every proposal must include tenant and permission design.
4. Never bypass platform authorization patterns.
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:
- 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.
- Never say "cannot be done": the answer is either an in-frame build or an approval question.
---
@ -116,7 +141,7 @@ Capabilities:
- Query
- WebService
- 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
- Grid state save/load/reset
- User filter save/apply/delete flows
@ -146,6 +171,34 @@ Supported editor types:
- dxTextArea
- 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
@ -172,6 +225,8 @@ Supported component families:
- TreeList / Tree view
- SubForm tabs (List, Tree, Gantt, Scheduler, Form, Chart)
- 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:
@ -212,6 +267,54 @@ If user requests custom code explicitly:
- Provide a warning that low-code path is preferred.
- 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
@ -342,7 +445,8 @@ When configuration cannot satisfy requirement:
1. Try SQL-based design
2. Try Custom Endpoint
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)
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
- api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json
- api/src/Sozsoft.Platform.DbMigrator/Seeds/PermissionsData.json
- api/src/Sozsoft.Platform.DbMigrator/Seeds/HostData.json
- api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json
**A. Repository seeds (shipped with code)** — `api/src/Sozsoft.Platform.DbMigrator/Migrations/`.
Platform-owned screens, menus, permissions and language texts. Changing them requires a build and
a deploy. AI must learn and teach implementation flow primarily from these assets:
- 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.
@ -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.
### 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)
@ -701,7 +864,131 @@ Rules:
- Permission contract must be created/bound for both parent and child menu items.
- 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:

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
- **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.
@ -24,7 +28,7 @@ This document summarizes the rules, standards, and step-by-step instructions for
## Special File Handling Rules
- **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:**
- 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
- **{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}.
### 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/seeds/**/data/**
*.bak
**/node_modules
**/.DS_Store
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>
<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>
</Project>

View file

@ -1,6 +1,6 @@
using Volo.Abp.Application;
using Volo.Abp.Modularity;
using Volo.Abp.Authorization;
using Volo.Abp.Modularity;
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;

View file

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

View file

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

View file

@ -1,14 +1,14 @@
using Sozsoft.Languages.Entities;
using Sozsoft.Languages.Localization;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using StackExchange.Redis;
using System;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Dynamic.Core;
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.Application.Dtos;
using Volo.Abp.Application.Services;

View file

@ -1,6 +1,6 @@
using Riok.Mapperly.Abstractions;
using Sozsoft.Languages.Entities;
using Volo.Abp.Localization;
using Riok.Mapperly.Abstractions;
using Volo.Abp.Mapperly;
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
{
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";
}
namespace Sozsoft.Languages.Languages;
public static class LanguageNames
{
public const string Ar = "العربية";
public const string Cs = "Čeština";
public const string De = "Deutsch";
public const string En = "English";
public const string Es = "Español";
public const string Fi = "Finnish";
public const string Fr = "Français";
public const string Hi = "Hindi";
public const string Hr = "Croatian";
public const string Hu = "Magyar";
public const string It = "Italiano";
public const string Pt = "Português";
public const string Ru = "Русский";
public const string Sk = "Slovak";
public const string Sl = "Slovenščina";
public const string Tr = "Türkçe";
public const string Zh = "繁體中文";
}
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";
}

View file

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

View file

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

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.TextTemplating;

View file

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

View file

@ -2,9 +2,9 @@
using Amazon;
using Amazon.SQS;
using Amazon.SQS.Model;
using Sozsoft.Sender.Mail.AmazonSes;
using Sozsoft.MailQueue.Domain.Entities;
using Microsoft.Extensions.Configuration;
using Sozsoft.MailQueue.Domain.Entities;
using Sozsoft.Sender.Mail.AmazonSes;
using Volo.Abp.Domain.Repositories;
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.EntityFrameworkCore;

View file

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

View file

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

View file

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

View file

@ -1,16 +1,16 @@
using System.Reflection;
using Sozsoft.Sender;
using Sozsoft.MailQueue.Domain.Shared;
using Sozsoft.MailQueue.EntityFrameworkCore;
using Microsoft.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection;
using Sozsoft.MailQueue.Domain.Shared;
using Sozsoft.MailQueue.EntityFrameworkCore;
using Sozsoft.Sender;
using Volo.Abp.Application;
using Volo.Abp.Authorization;
using Volo.Abp.Mapperly;
using Volo.Abp.Domain;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.Localization;
using Volo.Abp.Localization.ExceptionHandling;
using Volo.Abp.Mapperly;
using Volo.Abp.Modularity;
using Volo.Abp.TextTemplating.Razor;
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;

View file

@ -2,11 +2,11 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.EntityFrameworkCore;
using Sozsoft.Notifications.Domain;
using Sozsoft.Notifications.Entities;
using Sozsoft.Notifications.Enums;
using Microsoft.AspNetCore.Authorization;
using Microsoft.EntityFrameworkCore;
using Volo.Abp;
using Volo.Abp.Application.Services;
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.Entities;
using Sozsoft.Notifications.NotificationRules;
using Riok.Mapperly.Abstractions;
using Volo.Abp.Mapperly;
namespace Sozsoft.Notifications.Application;
@ -9,7 +9,7 @@ namespace Sozsoft.Notifications.Application;
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
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);
@ -25,7 +25,7 @@ public override partial NotificationTypeDto Map(NotificationType source);
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
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);
@ -41,10 +41,15 @@ public override partial NotificationRuleDto Map(NotificationRule source);
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
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))]
public override partial NotificationDto Map(Notification source);
[MapProperty("NotificationType.Name", nameof(NotificationDto.NotificationType))]
[MapperIgnoreTarget(nameof(NotificationDto.CreatorFullname))]
[MapperIgnoreTarget(nameof(NotificationDto.TenantId))]
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.Mapperly;
using Volo.Abp.Modularity;

View file

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

View file

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

View file

@ -2,8 +2,8 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Sozsoft.Notifications.Entities;
using Microsoft.EntityFrameworkCore;
using Sozsoft.Notifications.Entities;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
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 Microsoft.EntityFrameworkCore;
using Volo.Abp.Data;
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.Modularity;

View file

@ -1,10 +1,9 @@
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.MultiTenancy;

View file

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

View file

@ -1,5 +1,4 @@
using System.Net.Http.Json;
using Microsoft.Extensions.Logging;
using Volo.Abp.Settings;
namespace Sozsoft.Sender.Rocket;
@ -12,16 +11,13 @@ public interface IRocketHttpClient
public sealed class RocketHttpClient : IRocketHttpClient, IDisposable
{
private readonly HttpClient httpClient;
private readonly ILogger<RocketHttpClient> logger;
private readonly ISettingProvider settingProvider;
public RocketHttpClient(
HttpClient httpClient,
ILogger<RocketHttpClient> logger,
ISettingProvider settingProvider)
{
this.httpClient = httpClient;
this.logger = logger;
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;
@ -11,16 +10,13 @@ public interface IPostaGuverciniHttpClient
public sealed class PostaGuverciniHttpClient : IPostaGuverciniHttpClient, IDisposable
{
private readonly HttpClient httpClient;
private readonly ILogger<PostaGuverciniHttpClient> logger;
private readonly ISettingProvider settingProvider;
public PostaGuverciniHttpClient(
HttpClient httpClient,
ILogger<PostaGuverciniHttpClient> logger,
ISettingProvider settingProvider)
{
this.httpClient = httpClient;
this.logger = logger;
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.AmazonSes;
using Sozsoft.Sender.Rocket;
using Sozsoft.Sender.Sms;
using Sozsoft.Sender.Sms.PostaGuvercini;
using Sozsoft.Sender.Telegram;
using Sozsoft.Sender.WhatsApp;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.BackgroundJobs;
using Volo.Abp.Emailing;
using Volo.Abp.Localization;
@ -36,6 +37,7 @@ public class ErpSenderModule : AbpModule
context.Services.AddScoped<ISozsoftSmsSender>(sp => sp.GetRequiredService<PostaGuverciniSmsSender>());
context.Services.AddScoped<ISozsoftRocketSender>(sp => sp.GetRequiredService<RocketSender>());
context.Services.AddScoped<ISozsoftWhatsAppSender>(sp => sp.GetRequiredService<SozsoftWhatsAppSender>());
context.Services.AddScoped<ISozsoftTelegramSender>(sp => sp.GetRequiredService<SozsoftTelegramSender>());
context.Services.AddHttpClient<PostaGuverciniHttpClient>();
context.Services.AddScoped<IPostaGuverciniHttpClient>(sp => sp.GetRequiredService<PostaGuverciniHttpClient>());
@ -46,6 +48,9 @@ public class ErpSenderModule : AbpModule
context.Services.AddHttpClient<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.Json;
using Microsoft.Extensions.Logging;
using Volo.Abp.Settings;
namespace Sozsoft.Sender.WhatsApp;
@ -13,16 +12,13 @@ public interface IWhatsAppHttpClient
public sealed class WhatsAppHttpClient : IWhatsAppHttpClient, IDisposable
{
private readonly HttpClient httpClient;
private readonly ILogger<WhatsAppHttpClient> logger;
private readonly ISettingProvider settingProvider;
public WhatsAppHttpClient(
HttpClient httpClient,
ILogger<WhatsAppHttpClient> logger,
ISettingProvider settingProvider)
{
this.httpClient = httpClient;
this.logger = logger;
this.settingProvider = settingProvider;
}

View file

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

View file

@ -1,5 +1,5 @@
using Riok.Mapperly.Abstractions;
using Sozsoft.Settings.Entities;
using Riok.Mapperly.Abstractions;
using Volo.Abp.Mapperly;
namespace Sozsoft.Settings;
@ -7,7 +7,7 @@ namespace Sozsoft.Settings;
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
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);

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.Linq;
using System.Threading.Tasks;
using Sozsoft.Settings.Localization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Localization;
using Sozsoft.Settings.Localization;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Services;
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.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.EntityFrameworkCore;
using Volo.Abp.SettingManagement.EntityFrameworkCore;

View file

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

View file

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

View file

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
@ -30,29 +30,16 @@ public interface ISqlObjectManagerAppService : IApplicationService
Task<string> GetNativeObjectDefinitionAsync(string dataSourceCode, string schemaName, string objectName);
/// <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.
/// </summary>
Task SaveTableScriptAsync(SaveTableScriptDto input);
/// <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.
/// </summary>
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;
@ -20,7 +20,8 @@ public class SqlQueryExecutionResultDto
}
/// <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>
public class SaveTableScriptDto
{
@ -36,7 +37,8 @@ public class SaveTableScriptDto
}
/// <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>
public class DeleteSqlDataFilesDto
{
@ -46,10 +48,3 @@ public class DeleteSqlDataFilesDto
/// </summary>
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;

View file

@ -1,4 +1,4 @@
using Volo.Abp.Application;
using Volo.Abp.Application;
using Volo.Abp.Authorization;
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
{
@ -9,10 +7,3 @@ public class SqlTemplateDto
public string Description { 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.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Sozsoft.SqlQueryManager.Application.Contracts;
using Sozsoft.SqlQueryManager.Domain.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Volo.Abp.Application.Services;
using Volo.Abp.MultiTenancy;
using System.Text.RegularExpressions;
using Sozsoft.Platform.Data.Seeds;
using Sozsoft.Platform.Enums;
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;
@ -48,7 +50,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
private readonly IDataSourceManager _dataSourceManager;
private readonly ICurrentTenant _currentTenant;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IHostEnvironment _hostEnvironment;
private readonly IConfiguration _configuration;
private readonly ILogger<SqlObjectManagerAppService> _logger;
public SqlObjectManagerAppService(
@ -57,7 +59,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
IDataSourceManager dataSourceManager,
ICurrentTenant currentTenant,
IHttpContextAccessor httpContextAccessor,
IHostEnvironment hostEnvironment,
IConfiguration configuration,
ILogger<SqlObjectManagerAppService> logger)
{
_sqlExecutorService = sqlExecutorService;
@ -65,7 +67,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
_dataSourceManager = dataSourceManager;
_currentTenant = currentTenant;
_httpContextAccessor = httpContextAccessor;
_hostEnvironment = hostEnvironment;
_configuration = configuration;
_logger = logger;
}
@ -954,191 +956,27 @@ FROM (
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()
{
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;

View file

@ -1,4 +1,4 @@
namespace Sozsoft.SqlQueryManager.Domain.Shared;
namespace Sozsoft.SqlQueryManager.Domain.Shared;
// SqlObjectType, SqlFunctionType and SqlQueryStatus enums removed:
// 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.Localization;
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;

View file

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

View file

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

View file

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Text;
using System.Collections.Generic;
using Volo.Abp.DependencyInjection;
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;
namespace Sozsoft.SqlQueryManager;

View file

@ -1,4 +1,4 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Data;
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.Modularity;

View file

@ -1,4 +1,4 @@
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Dtos;
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 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? Props { get; set; }
public string? Description { get; set; }
public bool IsActive { get; set; } = true;
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 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)]
[RegularExpression(@"^/.*", ErrorMessage = "RoutePath must start with '/'.")]
public string RoutePath { get; set; } = string.Empty;
[RegularExpression(@"^(/.*)?$", ErrorMessage = "RoutePath must start with '/'.")]
public string? RoutePath { get; set; }
public string Code { get; set; } = string.Empty;
public string? Props { get; set; }
public string? Description { get; set; }

View file

@ -12,9 +12,17 @@ public interface ICrudEndpointAppService : ICrudAppService<
PagedAndSortedResultRequestDto,
CreateUpdateCrudEndpointDto>
{
Task<List<CrudEndpointDto>> GetActiveEndpointsAsync();
Task<List<CrudEndpointDto>> GetEndpointsByEntityAsync(string entityName);
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.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
@ -12,7 +10,4 @@ public interface ICustomComponentAppService : ICrudAppService<
PagedAndSortedResultRequestDto,
CreateUpdateCustomComponentDto>
{
Task<List<CustomComponentDto>> GetActiveComponentsAsync();
}

View file

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

View file

@ -44,34 +44,6 @@ public class ForumCategoryDto : FullAuditedEntityDto<Guid>
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 bool? IsActive { get; set; }

View file

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

View file

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

View file

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

View file

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

View file

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

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