Compare commits

...

87 commits
1.2.0 ... 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
1060 changed files with 95372 additions and 49841 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/

718
README.md
View file

@ -59,7 +59,7 @@ React/C# kodu yazmadan, yalnızca konfigürasyon ile mümkündür.
| **Arka plan işleri** | Hangfire + ABP Background Worker; zamanlanmış SQL, mail kuyruğu, yedekleme, oturum temizliği. |
| **Gerçek zamanlı** | SignalR tabanlı messenger ve video oda (WebRTC/TURN) altyapısı. |
| **Intranet & Forum** | Duyuru, anket, sosyal duvar, etkinlik; kategori/konu/gönderi tabanlı forum. |
| **Public web sitesi** | Ana sayfa, hakkımızda, hizmetler, ürün, ödeme, blog, demo ve iletişim içerikleri (tasarımcı ekranlarıyla düzenlenebilir). |
| **Public web sitesi** | Menüsü ve sayfaları veritabanından yönetilen ziyaretçi sitesi: sayfalar Custom Component, menü `Pub` modülü (bkz. §10.4). Ürün/ödeme ve demo akışları fiziksel sayfalardır. |
| **PWA** | Service worker ile offline kabuk, kontrollü sürüm güncellemesi ve changelog bildirimi. |
---
@ -68,7 +68,7 @@ React/C# kodu yazmadan, yalnızca konfigürasyon ile mümkündür.
```
┌──────────────────────────────────────────────────────────────────────┐
│ UI — React 18 + Vite + TypeScript + DevExtreme + Tailwind (PWA) │
│ UI — React 19 + Vite + TypeScript + DevExtreme + Tailwind (PWA) │
│ ├─ DynamicRouter (route kayıtlarından React Router üretimi) │
│ ├─ ListForm runtime (Grid / Pivot / Tree / Chart / Gantt / ...) │
│ ├─ Form runtime (DevExtreme Form, metadata ile alan üretimi)│
@ -128,10 +128,10 @@ DevExtreme bileşenleri bu metadata'ya göre kendini kurar.
| Bileşen | Sürüm / Notlar |
| --- | --- |
| React | 18.3 |
| React | 19.2 |
| Build | Vite 8 + TypeScript 5.9 |
| UI kütüphaneleri | DevExtreme 25.2 (React), Tailwind CSS 3.4, kendi `components/ui` tasarım sistemi |
| Routing | react-router-dom 6 (dinamik route üretimi) |
| UI kütüphaneleri | DevExtreme 26.1 (React), Tailwind CSS 3.4, kendi `components/ui` tasarım sistemi |
| Routing | react-router-dom 7 (dinamik route üretimi) |
| State | easy-peasy (global store) + @tanstack/react-query |
| Form | Formik + Yup, DevExtreme Form |
| Kod editörü | Monaco Editor |
@ -178,8 +178,15 @@ sozsoft-platform/
├── configs/
│ ├── docker/ # Yerel geliştirme için veri katmanı compose dosyaları ve scriptler
│ ├── deployment/ # Dev/Prod compose dosyaları, nginx/redis/turn konfigürasyonu, deploy scriptleri
│ ├── seeds/ # Runtime'da düzenlenebilen seed'ler; CDN ile aynı kapsam düzeni
│ │ # host/{crud,custom,wizard,sql,postgres}
│ │ # tenants/{tenantId}/{crud,custom,wizard,sql,postgres}
│ │ # {sql|postgres}/{object,execute}
│ └── ai/ # AI entegrasyonu (n8n akış tanımları)
├── .github/instructions/ # ai.instructions.md (bağlayıcı platform kuralları), list.instructions.md
├── .github/instructions/ # ai.instructions.md (bağlayıcı platform kuralları)
│ # lowcode.instructions.md (artefakt üretim referansı: şemalar, örnekler)
│ # dotnet.instructions.md (api/ için bağlayıcı .NET/ABP kod standardı)
│ # list.instructions.md (modül/liste ekleme prosedürü)
├── CLAUDE.md # Claude Code için operasyon kuralları
└── README.md
```
@ -233,18 +240,52 @@ dotnet run --project api/src/Sozsoft.Platform.DbMigrator
DbMigrator; şema migration'larını uygular ve ardından menü, route, permission, dil,
host ayarları, ülke/şehir/ilçe verileri ile seed edilmiş ListForm ekranlarını yükler
(`api/src/Sozsoft.Platform.DbMigrator/Seeds/`).
(kodla versiyonlanan sabit seed'ler `api/src/Sozsoft.Platform.DbMigrator/Migrations/`,
runtime'da düzenlenebilen seed'ler `configs/seeds/` altındadır — bkz. `App:SeedsPath`).
`configs/seeds` altında en üst seviye kapsamdır (CDN klasörleriyle aynı mantık): host
veritabanı için `host/`, her tenant için `tenants/{tenantId}/`. Kapsamın altında içerik
türüne göre `crud/`, `wizard/`, `data/`, `sql/`, `postgres/` klasörleri bulunur:
```
configs/seeds/
host/
crud/ wizard/ data/
sql/ { object/ execute/ }
postgres/ { object/ execute/ }
tenants/
{tenantId}/
crud/ wizard/ data/
sql/ { object/ execute/ }
postgres/ { object/ execute/ }
```
Aktif veritabanı sağlayıcısına göre `sql/` (SQL Server) ya da `postgres/` okunur. Her
`.sql` dosyası amacını belirten bir klasörün altındadır; klasör köklerinde dosya bulunmaz:
| Klasör | Ne zaman çalışır |
| --- | --- |
| `{sql\|postgres}/object` | Seed sırasında nesneyi oluşturur/günceller. SqlTableDesigner ve SqlQueryManager, deploy ettiği script'i çalıştığı kapsamın bu klasörüne yazar (host bağlamı`host/`, tenant seçiliyken → `tenants/{tenantId}/`). |
| `{sql\|postgres}/execute` | Nesneyi oluşturur; ayrıca tüm migration ve seed'ler bittikten sonra `AfterAllMigrationsSqlExecutor` dosya adından türettiği procedure'ü çalıştırır (dosya adı = procedure adı, `schema.Procedure.sql` de olur). |
| `wizard` | ListForm Wizard'ın ürettiği `.json` konfigürasyonları; `WizardDataSeeder` kendi kapsamındaki dosyaları okur. |
| `crud` | CrudEndpoint konfigürasyonları; `CrudDataSeeder` kendi kapsamındaki dosyaları `ICrudEndpointSeedApplier` üzerinden uygular. Politika **damga karşılaştırmalıdır**: eksik endpoint her zaman eklenir, mevcut bir kayıt yalnızca dosyanın `GeneratedAt` damgası kaydın `LastModificationTime`/`CreationTime` değerinden **yeniyse** güncellenir ya da silinir; aksi halde kayıt korunur ve log'a drift uyarısı düşer. Böylece CRUD Endpoint Manager'dan yapılan bir revizyon depodan gelen eski bir dosya yüzünden migrate sırasında geri alınmaz. Damgası olmayan (eski biçimde yazılmış) dosya hiçbir zaman yeni sayılmaz. |
| `data` | Ekranlardan girilen listelerin veri aynası (`{ListFormCode}.json`). `ListForm.SeedFilePath` dolu olan ekranda `SeedSyncInsert/Update/Delete` bayraklarından işaretli olan işlemler dosyaya yansır (`ListFormSeedDataSynchronizer`); migrate/seed sırasında bütün contributor'lar bittikten sonra `ListFormSeedDataApplier` dosyayı geri uygular: anahtarı veritabanında **olmayan** satırları ekler, var olanlara dokunmaz. Aynı uygulayıcı wizard import'unun kapanışında da (`CompleteImport`) yalnızca o import'ta yazılan dosyalar için, `overwriteExisting`ık olarak çalışır — orada var olan satır dosyadaki değerlerle güncellenir. |
Klasör kökünde kalmış eski `.sql` dosyaları geriye dönük uyumluluk için hâlâ işlenir,
fakat `SqlDataSeeder` bunları uyarı ile loglar — ilgili klasöre taşınmaları beklenir.
### 5.4. Frontend
```powershell
cd ui
npm install
npm start # component metadata üretir + vite dev server (http://localhost:3000)
npm start # vite dev server (http://localhost:3000)
```
`npm start`, `vite` öncesinde `scripts/generate-component-metadata.mjs` çalıştırır; bu script
Visual Designer ve Custom Component altyapısının kullandığı bileşen kataloğunu günceller.
Visual Designer ve Custom Component altyapısının kullandığı bileşen kataloğu
`src/components/visualDesigner/generated/componentProps.json` dosyasında tutulur ve repoya
dahildir. `src/components/ui` altındaki bileşen prop'ları değiştiğinde bu dosya elle
güncellenmelidir.
### 5.5. İlk giriş
@ -331,6 +372,8 @@ Ekran davranışı büyük ölçüde JSON kolonlarında saklanır. Öne çıkanl
| `ColumnOptionJson`, `PagerOptionJson`, `SelectionJson`, `StateStoringJson` | Sütun, sayfalama, seçim ve durum saklama ayarları. |
| `Insert/Update/DeleteCommand` (+ `Before` / `After`) | CRUD komutları ve öncesi/sonrası çalışacak SQL kancaları. |
| `Insert/Update/DeleteServiceAddress` | Varsayılan olarak `list-form-data/*`; özel endpoint'e yönlendirilebilir. |
| `SeedFilePath` | Doluysa seed sırasında listenin verisi kapsam klasöründeki bu JSON dosyasından geri yüklenir (`data/{ListFormCode}.json`). |
| `SeedSyncInsert` / `SeedSyncUpdate` / `SeedSyncDelete` | Ekranda yapılan hangi işlemin seed dosyasına yansıyacağı; kapalıysa o işlem dosyaya dokunmaz. |
| `SubFormsJson` | Ana-detay ilişkisi (ParentFieldName → ChildFieldName eşlemesi). |
| `WidgetsJson` | Ekranın üstünde gösterilecek KPI kartları. |
| `WorkflowJson` | İş akışı bağlaması. |
@ -345,11 +388,17 @@ Ekran davranışı büyük ölçüde JSON kolonlarında saklanır. Öne çıkanl
Aynı `ListForm` tanımı, aynı veri hattı üzerinden birden fazla görünümle sunulabilir:
`Grid` · `Pivot` · `Tree` · `Chart` · `Gantt` · `Scheduler` · `CardView` · `TodoBoard` · `Widget Group`
`Grid` · `Pivot` · `Tree` · `Chart` · `Gantt` · `Scheduler` · `CardView` · `TodoBoard (Kanban)` ·
`Widget Group`
Kullanıcı görünümler arasında geçiş yapabilir; grid durumu (sütun sırası, genişlik, filtre,
gruplama) kullanıcı bazlı saklanır ve sıfırlanabilir.
**TodoBoard artık bir Kanban panosudur:** kolonlar `TodoOptionJson` içindeki `statusExpr`
değerlerinden üretilir, kartlar kolonlar arasında sürüklenerek durum değiştirir ve panodan yeni
kolon (durum) eklenebilir. Kart içeriği `titleExpr`, `descriptionExpr`, `dueDateExpr`, `tagExpr`
ve atanan kullanıcı alanlarından kurulur.
### 7.4. Alan (field) davranışı
- **Editör tipleri:** `dxTextBox`, `dxTextArea`, `dxNumberBox`, `dxDateBox`, `dxDateRangeBox`,
@ -360,9 +409,67 @@ gruplama) kullanıcı bazlı saklanır ve sıfırlanabilir.
- **Doğrulama:** `ValidationRuleJson` ile required/range/pattern/custom kuralları.
- **Editör script:** `EditorScript` alanı ile alan değişiminde çalışan JS (örn. tarih farkından
gün hesaplama, miktar × birim fiyat toplamı). Çalıştırma `utils/editorScriptRuntime.ts` üzerinden yapılır.
- **Editör seçenekleri:** `EditorOptions` alanı DevExtreme editörüne geçirilen JSON'dur
(format, maske, placeholder, yükseklik, buton görünürlüğü, platform editörlerinin tipli
ayarları). Elle JSON yazmak zorunlu değildir; bkz. 7.4.1.
- **Koşullu biçimlendirme:** `ColumnStylingJson`, `ColumnCssClass`/`ColumnCssValue`.
- **Alan bazlı yetki:** `PermissionJson` ile sütun düzeyinde okuma/yazma/dışa aktarma kontrolü.
#### 7.4.1. Script Builder ve Editor Options Builder
`EditorScript` ve `EditorOptions` alanları hem görsel olarak hem de seeder kodundan üretilir;
**iki tarafın çıktısı birebir aynı olmak zorundadır.**
**Editor Script Builder** (`json-row-operations/editor-script/`)
- Kural sözlüğü `scriptRecipes.ts` içindedir; her tarif (recipe) tek satırlık runtime çağrısı üretir.
Gruplar: hesap (`calc`), veri (`data`), görünüm (`view`), bildirim, API.
- Üretilen script'in ilk satırındaki `// @builder {...}` başlığı kuralları saklar. Dialog script'i
regex ile çözmeye çalışmaz, bu başlıktan geri okur; başlık bozulursa/elle düzenlenirse script
"manuel" sayılır ve kural editörü kapanır.
- Kural tetikleyicileri: `change` (varsayılan), `open` (form açılışında — varsayılan değer üretmek
için, script başına `// @runOnOpen` direktifi eklenir), `both`.
- Koşullar `and`/`or` ile birleşir: `equals`, `notEquals`, `contains`, `empty`, `notEmpty`,
`greaterThan`, `lessThan`, `isTrue`, `isFalse`, `always`.
- Dialog'un kendisi ortaktır (`components/scriptBuilder/ScriptBuilderDialog.tsx` + IntelliSense);
ListForm lehçesi `formScriptDialect.ts`, Visual Designer lehçesi `designerScriptDialect.ts`
ile aynı sözleşmeye bağlanır.
**Seeder tarafı (C#)** — `Sozsoft.Platform.Domain.Shared/Editors/`
`scriptRecipes.ts` ve editorOptions sözlüğünün C# portudur. Seeder ile basılan script/JSON,
dialogda açılıp görsel olarak düzenlenebilir kalır. Bir tarif TypeScript tarafında değişirse
buradaki karşılığı da güncellenmelidir.
| Tip | Kullanım |
| --- | --- |
| `EditorScript` | `Multiply`, `Subtract`, `Percent`, `Sum`, `Formula`, `Today`, `Days`, `Hours`, `Copy`, `SetValue`, `Clear`, `ReadOnly`, `Notify`, `Ask`, `OpenUrl`, `ApiToField`, `Custom``EditorScript.Build(rules)` |
| `EditorScriptRule` | `.When(...)` / `.WhenAny(...)` koşulları, `.OnChange()` / `.OnOpen()` / `.OnOpenAndChange()` tetikleyicisi; `string`'e örtük dönüşür |
| `EditorScriptCondition` | `Is`, `IsNot`, `Contains`, `IsEmpty`, `IsNotEmpty`, `GreaterThan`, `LessThan`, `IsTrue`, `IsFalse`, `Always` |
| `EditorOptions` | Hazır başlangıçlar: `Disabled()`, `ReadOnly()`, `ShowClearButton()`, `Multiline(h)`, `Number(precision)`, `Percent()`, `Date()`, `DateTime()`, `Time(interval)`, `Phone()`, `Slider()`, `ImageUpload()`, `Html(h)` |
| `EditorOptionsBuilder` | Akıcı ekleme: `.Placeholder()`, `.MaxLength()`, `.Height()`, `.Width()`, `.Format()`, `.Mask()`, `.Flag()`, `.Text()`, `.Number()`, `.Json()`; `string`'e örtük dönüşür |
```csharp
EditorOptions = EditorOptions.Number(4).ShowClearButton(),
EditorOptions = EditorOptions.Multiline(60).Placeholder("Açıklama"),
EditorScript = EditorScript.Build(
EditorScript.Multiply("Quantity", "UnitPrice", "Total"),
EditorScript.Percent("Total", "VatRate", "VatAmount", mode: EditorScriptPercentMode.Add)
.When(EditorScriptCondition.IsNotEmpty("VatRate"))),
```
**Editor Options Builder** (`json-row-operations/editor-options/`)
- UI tamamen `optionSpecs.ts` veri sözlüğünden üretilir; yeni bir ayar eklemek için oraya tek
satır yazmak yeterlidir. Her ayar `path` (örn. `format.precision`), tip (`boolean` üç durumlu /
`number` / `text` / `select` / `size` / `stringList` / `json`), grup ve hangi editörlerde
anlamlı olduğu bilgisini taşır.
- `platform: true` işaretli ayarlar backend'in tipli DTO'ya (`GridBoxOptionsDto`,
`TagBoxOptionsDto`, `ImageUploadOptionsDto`) deserialize ettiği alanlardır; yanlış tipte
yazılırsa sessizce yok sayılır.
- `presets.ts` hazır kalıplar sunar (HTML editör tam araç çubuğu, telefon maskesi, tarih/saat…);
kalıp mevcut JSON ile **birleştirilir**, diğer ayarları silmez.
### 7.5. İş akışı ve onay
`FormTabWorkflow` ve `views/admin/listForm/workflow/WorkflowDesigner.tsx` üzerinden görsel
@ -373,15 +480,67 @@ butonlar gösterilebilir.
### 7.6. Yeni ekran ekleme akışı (Wizard)
`/admin/listform/wizard` altındaki 11 adımlı sihirbaz, aşağıdaki yedi artefaktı tek akışta üretir:
`/admin/listform/wizard` altındaki sihirbaz, aşağıdaki artefaktları tek akışta üretir:
1. **ListForm** kaydı (kod, ad, başlık, veri kaynağı, select komutu, anahtar alan)
2. **ListFormField** kümesi (sütunlar, editörler, lookup'lar, doğrulamalar)
3. **Route** kaydı (`key`, `path`, `componentPath`, `routeType`, `authority`)
4. **Menu** kaydı (`ParentCode`, `Code`, `DisplayName`, `Url`, `Icon`, `RequiredPermissionName`, `Order`)
5. **Permission** kayıtları (`.Default`, `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note`)
6. Gerekiyorsa **ayar/entegrasyon** bağımlılıkları
7. **Doğrulama ve geri alma** notları
6. **Dil anahtarları** (menü, başlık, açıklama; EN + TR)
7. Gerekiyorsa **ayar/entegrasyon** bağımlılıkları
8. **Seed dosyası**`configs/seeds/{host|tenants/{tenantId}}/wizard/{Ad}.json` (wizard cevapları + üretilen `ListForm`/`Fields` sözleşmesi)
9. **Doğrulama ve geri alma** notları
**Adım bileşenleri.** Adımlar numaralı dosya adları yerine işlevleriyle adlandırılır ve
`React.lazy` ile yüklenir; aynı anda yalnızca görünen adım indirilir, sihirbazın açılış maliyeti
adım sayısından bağımsızdır.
| Adım | Dosya | İçerik |
| --- | --- | --- |
| Menü ve kimlik | `WizardStepMenu.tsx` | Wizard adı, ListForm kodu, menü ağacı/üst menü, ikon, sıra, izin grubu, dil metinleri |
| Veri ayarları | `WizardStepDataSettings.tsx` | Veri kaynağı, select komutu tipi, anahtar alan, CRUD endpoint diyaloğu |
| Alanlar | `WizardStepFields.tsx` | Sütun grupları, editör tipleri, lookup, doğrulama |
| Alt formlar | `WizardStepSubForms.tsx` | Anadetay ilişki eşlemesi |
| Widget'lar | `WizardStepWidgets.tsx` | KPI kartları |
| İş akışı | `WizardStepWorkflow.tsx` | Onay/koşul grafiği |
| Düzen adımları | `WizardStepTodoLayout` · `WizardStepTreeLayout` · `WizardStepGanttLayout` · `WizardStepSchedulerLayout` | Yalnızca ilgili görünüm açıksa görünür |
| Bileşen | `WizardStepComponent.tsx` · `WizardStepCustomComponent.tsx` | Custom yolunda bağlanacak bileşen |
| Yayınlama | `WizardStepDeploy.tsx` | Özet, doğrulama ve seed dosyasının üretimi |
Menü ağacı yardımcıları (`menuTree.ts`) adım bileşenlerinden ayrı bir modüldedir; böylece Wizard
ve `SqlTableDesignerDialog` bu fonksiyonları kullanırken adım bileşenlerini pakete çekmez.
**Seed dosyası ekranın kendisini taşır.** Dosya `Wizard` bölümünün yanında ekranın
`GridOptionsEditDto` sözleşmesini (`ListForm`) ve alanların `ColumnFormatEditDto` sözleşmesini
(`Fields`) tutar; `WizardDataSeeder` ekranı bu iki bölümden kurar. Böylece Wizard ile ListForm
detayı aynı veri yapısını konuşur ve tasarımcıdaki değişiklikler wizard'a geri yazılabilir.
`Wizard` bölümü yalnızca ekrandan üretilemeyen bilgileri (menü, yetki, dil metinleri, bileşen
yolu, alan başlıklarının dil karşılıkları) taşır; `ListForm`/`Fields` içinde karşılığı olan alanlar
dosyaya yazılmaz, okuma sırasında geri doldurulur. Kalanlar dosyada `Menu`, `Language` ve `Groups`
başlıkları altında toplanır (`WizardSeedFileDto.ToSeedJson` / `FromSeedJson`). Dosya adı `{Ad}.json`'dır (zaman damgası yok);
uygulama sırası `Wizard.SeededAt` damgasından gelir.
**İki yol: `ComponentKind`.** `ListFormWizardDto.ComponentKind` sihirbazın yol ayrımıdır ve seed
dosyasında ilk alandır. Varsayılan `List` olduğu için bu alanı taşımayan eski seed dosyaları
eskisi gibi çalışır.
| `ComponentKind` | Üretilen | Menü URL'i | Yetkiler |
| --- | --- | --- | --- |
| `List` | ListForm + ListFormField + Route + Menu + Permission + dil anahtarları | `/admin/list/{ListFormCode}` | `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
| `Custom` | Menü + Permission + dil anahtarları; ekran seçilen Custom Component'tir | Component'in `RoutePath` değeri | Yalnızca `.Create`, `.Update`, `.Delete` (Export/Import/Note ListForm'a özgüdür) |
**Menüsüz wizard (`CreateMenu = false`).** Menü (ve gerekiyorsa üst menü) kaydı hiç üretilmez;
ListForm, permission ve dil anahtarları her iki durumda da üretilir. Menüsüz wizard'lar başka bir
ekranda SubGrid/parça olarak kullanılmak üzere tanımlanır. Alan varsayılanı `true` olduğundan bu
alanı taşımayan eski seed dosyaları etkilenmez.
**İzin grubu seçimi.** Menü adımında izin grubu bir seçim listesinden gelir
(`GetWizardPermissionGroups`). Görünen adlar, grup adıyla aynı olan dil anahtarının metinlerinden
okunur; seçim yapıldığında `PermissionGroupEn/Tr` forma doldurulur. Yeni bir grup
tanımlanırsa grup adıyla aynı dil anahtarına bu metinler yazılır; mevcut grup seçilip alanlar boş
bırakılırsa sunucu veritabanındaki değeri geri yazar. Böylece seed dosyası her durumda iki dilli
görünen adı taşır.
Dinamik liste ekranlarının route deseni `/admin/list/{ListFormCode}` biçimindedir; form
görünümleri `/admin/form/{listFormCode}/:id`, grafik `/admin/chart/{listFormCode}`, pivot
@ -391,13 +550,183 @@ Var olan bir ekranı düzenlemek için `/admin/listform/edit/{listFormCode}` —
yapılandırması sekmeli bir editörde yönetilir (Veritabanı, Sütunlar, Filtreler, Düzenleme,
Yetkiler, Alt Form, Pivot, Tree, Gantt, Scheduler, Todo, Widget, Workflow, Durum, Grafik sekmeleri).
**Wizard File Manager** (`/admin/listform/wizard` liste ekranı) üretilmiş seed dosyalarını yönetir:
liste/kart görünümü arasında geçiş (tercih diğer listelerle aynı yerde, `admin.lists.states`
altında saklanır), arama, düzenleme (dosyanın yerine geçen yeni bir çalıştırma), silme, veritabanı
migrate tetikleme, dışa aktarma ve içe aktarma. Liste, wizard'ın ilk deploy damgasına
(`Wizard.SeededAt`) göre **tersine** sıralıdır — en son üretilen ekran en üstte durur; damgası
okunamayan dosyalar sona düşer. Butonlar `App.Listforms.Wizard.*` yetkileriyle gizlenir; asıl
kontrol `ListFormWizardAppService` üzerindedir.
### 7.7. İçe/dışa aktarma
**Veri (ekran içi)**
- **Dışa aktarma:** xlsx, csv, pdf (grid durumuna ve görünür sütunlara saygı duyar).
- **İçe aktarma:** `components/importManager` altındaki dosya yükleme → önizleme → ilerleme →
sonuç akışı; sonuçlar `ListFormImportLog` üzerinde satır bazında raporlanır. Şablon dosyası
ekran tanımından üretilir.
**Ekran tanımı (wizard seed paketi)**
Bir ekranın tanımı, bağımlılıklarıyla birlikte tek bir zip olarak taşınır. Zip içindeki yollar seed
kapsam klasörü (`host` / `tenants/{tenantId}`) kökü baz alınarak yazılır; arşiv başka bir ortamda
aynı klasör düzenine doğrudan açılabilir.
Toplama **özyinelemelidir** (`WizardExportCollector`): alt formların wizard dosyaları kuyruğa
eklenir ve onların bağımlılıkları da aynı kurallarla toplanır. Böylece zip tek başına çalışır.
| Yol | İçerik |
| --- | --- |
| `wizard/{dosya}.json` | Wizard tanımının kendisi **ve** `SubFormsDto[].Code` ile bağlı alt form ekranlarının wizard dosyaları (özyinelemeli) |
| `data/App.DeveloperKit.CustomComponents.json` | Custom yolunda bağlı component ve bağımlılıkları — dosyanın yalnızca bu satırlara süzülmüş kopyası |
| `data/{ListFormCode}.json` | Ekranın `SeedFilePath` değeri doluysa kendi veri aynası |
| `crud/{entity}.json` | Component'lerin/nesnenin kullandığı CRUD endpoint tanımları |
| `{sql\|postgres}/{object\|execute}/{nesne}.sql` | `SelectCommand`'a karşılık gelen SQL nesnesi, lookup sorgularının (`LookupDto.LookupQuery`) `FROM`/`JOIN` ile işaret ettiği nesneler ve Custom yolunda CRUD endpoint'lerinin dayandığı tablolar |
Script dosyaları sabit bir sağlayıcı/klasör kombinasyonundan **tahmin edilmez**: kapsam kökündeki
script klasörleri (`sql/object`, `postgres/execute` …) diskten taranır ve nesne adıyla eşleşen her
dosya arşive girer. Yeni bir sağlayıcı ya da script klasörü eklendiğinde toplayıcı değişmez.
Script bulunamaması **uyarı üretmez**: her tablo seed ile yönetilmez — ABP'nin kendi tabloları
(`AbpUsers`, `AbpRoles` …) EF migration'larıyla kurulur ve seed script'leri yoktur. "Bu tablo
hedefte var mı" sorusunun tek doğru cevabı hedef veritabanındadır; kararı import analizi verir,
export varsayımda bulunmaz.
Çözülemeyen bir **kayıtlı referans** (seed dosyasında adı geçen ama karşılığı bulunmayan alt form,
`SeedFilePath` veri aynası, `DataSources[].SeedFile` CRUD dosyası, custom component satırı)
indirmeyi engellemez ama sessiz de kalmaz: `ExportAnalysis` ucu indirmeden önce arşivin tam
içeriğini ve eksikleri döndürür, Wizard File Manager eksik varsa önce bu listeyi gösterir.
**İçe aktarma dört adımlı bir sihirbazdır** (`WizardImportDialog`, `Steps` bileşeni):
| Adım | İçerik | Geçiş koşulu |
| --- | --- | --- |
| **Dosyalar** | Zip girdileri ve durumları; çakışanlar Monaco `DiffEditor` ile çözülür | Çözülmemiş çakışma kalmamalı |
| **Kontroller** | Analiz bulguları ve zip'te atlanan girdiler. Engelleyiciler iki başlıkta ayrılır — **arşiv eksik ya da bozuk** (burada giderilemez, kaynak ortamda export tekrarlanmalı; düzeltme düğmesi yoktur) ve **ortamda giderilebilir**. Tabloya dokunan bulgularda mevcut `SqlTableDesignerDialog`ılır — şema farkında **Tabloyu düzenle** (düzenleme modu), eksik tabloda **Tabloyu oluştur** (oluşturma modu, ad ön dolu); deploy sonrası analiz kendiliğinden yenilenir (önceki oturum bırakılır) | Engelleyici (`Error`) bulgu kalmamalı |
| **Yapılacaklar** | Onaylandığında ne olacağının tam listesi: yazılacak dosyalar, çalışacak SQL script'leri, eklenecek kolonlar (cümleleriyle), uygulanacak CRUD ve veri dosyaları, kurulacak ekranlar. Sıra sunucudaki kapanış sırasıyla aynıdır | Onay |
| **Uygulama** | Dosya dosya yazım, kapanış adımlarının sonucu | — |
Geriye dönüş her zaman serbesttir; ileri atlama bağımlılık koşullarına bağlıdır. Adım başlıklarına
tıklanarak da gezinilebilir.
İçe aktarma **toplu (batch) bir işlemdir**: engelleme oturum genelindedir, dosya bazlı değildir.
Bir tek engelleyici bulgu tüm zip'i durdurur ve o dosyanın işaretini kaldırarak aşılamaz — yarım
kurulum üretmemenin tek güvenli yolu budur. Karşılığında, tabloya dair her engel diyalogdan
çıkmadan giderilebilir (tablo tasarımcısı → deploy → analiz yenilenir). Tabloyla ilgisi olmayan
engeller (eksik alt form, eksik custom component bağımlılığı, bozuk JSON) zip'in kendisinin eksik
olduğunu gösterir; bunlar kaynakta düzeltilip yeniden export edilmelidir.
Adımların altında yatan üç sunucu işlemi şunlardır:
1. **Analiz** (`AnalyzeImport`) — zip, seed kökü altındaki `.imports/{importId}/staged` klasörüne
ılır (nokta ile başladığı için seeder taramalarına girmez) ve her dosya hedefteki karşılığıyla
karşılaştırılır: `New` (hedefte yok), `Identical` (aynı, sorulmaz), `Conflict` (farklı).
Çakışan dosyalar Monaco `DiffEditor` ile karşılaştırılıp birleştirilir.
`data/*.json` girdilerinde karşılaştırma **satır bazlıdır**: hedef dosyadan yalnızca gelen
satırların anahtarları süzülür, dosya düzeyindeki `GeneratedAt`/`Order` karşılaştırmaya girmez.
Böylece aktarılan kaydın dışındaki satırlar fark olarak görünmez; gelen satırların hiçbiri
hedefte yoksa dosya var olsa bile durum `New` (ekleme), varsa ve içerik aynıysa `Identical`
(hiç aktarılmaz), farklıysa `Conflict` (güncelleme) olur.
2. **Uygulama** (`ApplyImport`) — dosyalar tek tek yazılır, ilerleme satır satır gösterilir. Yazılan
her dosyanın önceki hâli `.imports/{importId}/backup` altına alındığı için hata durumunda
`RollbackImport` ile toplu işlem geri alınabilir.
3. **Kapanış** (`CompleteImport`) — oturum kapatılmadan önce zip'in getirdiği dosyalar
**bağımlılık sırasıyla** veritabanına uygulanır. Sıra bilinçli ve sabittir:
**(a)** `{sql|postgres}/{object,execute}/*.sql` scriptleri çalıştırılır (`object` önce) — tablo
yoksa burada oluşur, aksi halde sonraki iki adım eksik tablo yüzünden baştan düşerdi;
**(b)** hedefte eksik olan kolonlar eklenir (aşağıya bakın);
**(c)** `crud/*.json` dosyaları `ICrudEndpointSeedApplier` ile uygulanır;
**(d)** `data/*.json` dosyaları
`IListFormSeedDataApplier.ApplyFilesAsync(..., overwriteExisting: true)` ile veritabanına
uygulanır: tabloda olmayan satır eklenir, var olan satır dosyadaki değerlerle **güncellenir**
(migrate/seed akışından farkı budur — orada dosya bayat olabileceği için canlı kayda
dokunulmaz). Kayıt daha önce **soft delete** edilmişse silinme izleri de temizlenir
(`IsDeleted`, `DeletionTime`, `DeleterId`); aksi halde satır tabloda durur ama ekranın
varsayılan süzgeci (`"IsDeleted" = 'false'`) onu gizlemeye devam ederdi. Hangi kolonların
sıfırlanacağı ekranın `DeleteCommand`'ından okunur, hard delete yapan ekranda hiçbir şey
eklenmez. Hedefle **zaten aynı** olan (`Identical`) veri dosyaları da uygulanır: yazacak bir şey
yoktur ama satırlar veritabanında eksik ya da silinmiş olabilir. Kullanıcının "mevcudu koru"
dediği (farklı olup yazılmayan) dosyaya dokunulmaz. Anahtarı olmayan satır güncellenemez,
atlanır; eklenen/güncellenen/atlanan satır
sayısı dialogda raporlanır. Bu adım `RollbackImport`
kapsamında **değildir** — geri alma bu adımdan önce yapılır.
5. **Ekran tanımları** (`wizard/*.json`) — son adım: ekranın tablosu, CRUD ucu ve lookup verileri o
noktada hazırdır. Kurulum wizard ekranının kullandığı yolun aynısından geçer
(`DeployWizardAsync`), dosya ekranın üretilmiş `ListForm`/`Fields` sözleşmesini taşıdığı için
bunlar wizard cevaplarından **yeniden türetilmez** — tasarımcı üzerindeki her değişiklik olduğu
gibi kurulur. **Hedefte zaten var olan ekrana dokunulmaz.** Her ekran kendi işleminde kurulur:
bir dosyadaki hata yalnızca o ekranın kayıtlarını geri alır. İmport bittiğinde ekran çalışır
durumdadır, migrate beklemez; istemci ABP config'ini yeniler, böylece menü anında görünür.
**Analiz doğrulama da yapar** (`WizardImportValidator`). Zip'in hedef ortamda yarım bir kurulum
bırakıp bırakmayacağı dosyalar yazılmadan **önce** denetlenir; bulgular `Error`/`Warning` olarak
raporlanır ve en az bir `Error` varken import başlatılamaz — sunucu oturuma `blocked` işaret
dosyası bırakır, dolayısıyla `ApplyImport` ucu doğrudan çağrılsa bile reddeder.
| Kural | Ağırlık |
| --- | --- |
| Bozuk JSON (`wizard`, `crud`, `data`) | Error |
| Wizard bölümü ya da adı olmayan wizard dosyası; `ListForm` bölümü eksik List wizard'ı | Error |
| Ekranın tablosu (`SelectCommand`, `Table`/`View`) hedefte **yok** ve zip onu oluşturan `.sql` dosyasını **taşımıyor** | Error |
| `crud/{Entity}.json`'un tablosu hedefte yok ve zip'te oluşturan script yok | Error |
| `data/{code}.json`'un ekranı ne zip'te ne hedefte var | Error |
| Custom component'in `Dependencies` ya da `DataSources[].SeedFile` bağımlılığı ne zip'te ne hedefte var | Error |
| Custom wizard'da bileşen adı / rota yolu boş | Error |
| Alt form (`SubFormsDto[].Code`) ne zip'te ne hedefte var | Error |
| Nesne varlığı sorgulanamadı (bağlantı/izin) ve script de yok | Warning |
| Zip yalnızca karşı sağlayıcının (`sql` ↔ `postgres`) scriptlerini taşıyor | Warning |
| Ekranın `SeedFilePath` veri dosyası ne zip'te ne hedefte var; boş CRUD/veri dosyası | Warning |
| Script'in kurduğu tablo hedefte **var** ama bir kolonu eksik | Warning — kapanışta eklenir |
| Eksik kolon `NOT NULL`, varsayılanı yok ve tabloda kayıt var | Error (otomatik eklenemez) |
| Var olan kolonun uzunluk / kesinlik / ondalık / nullability değeri script'ten farklı | Error |
| Hedefte script'te olmayan fazladan kolon var | Warning (dokunulmaz) |
Eksik tablo ölçütü "veritabanında var" değil, "veritabanında var **ya da** zip onu oluşturan
scripti taşıyor"dur — kapanış scriptleri veri satırlarından önce çalıştırdığı için zip'te scripti
olan tablo eksik sayılmaz.
#### Şema karşılaştırması — tablo var ama yapısı farklıysa
Üretilen tablo script'leri `IF OBJECT_ID(...) IS NULL BEGIN CREATE TABLE ... END` kalıbıyla sarılıdır:
hedefte tablo varsa `CREATE` bloğu **tamamen atlanır**. Dolayısıyla eski şemalı bir tabloya yeni bir
kolon kendiliğinden gelmez; ekran o kolonu bir alan olarak tanımladığı için açılışta "geçersiz kolon"
hatası verir. Bu sessiz uyuşmazlığı analiz adımı yakalar (`SqlCreateTableParser` +
`WizardImportValidator`): zip'in getirdiği `CREATE TABLE` bildirimi, hedef tablonun
`INFORMATION_SCHEMA.COLUMNS` üzerinden okunan canlı şemasıyla karşılaştırılır.
Ayrım **kayıp riskine** göre yapılır:
- **Eksik kolon eklenir.** Kolon eklemek kayıpsız bir işlemdir; analiz "şu kolon eklenecek" diye
uyarır, çalıştırılacak cümleyi gösterir ve kapanışta (SQL script'lerinden sonra, CRUD ve veriden
önce) uygular. Tek istisna: `NOT NULL` olup varsayılan değeri olmayan bir kolon dolu bir tabloya
eklenemez — kolona hangi değerin yazılacağı bir iş kararıdır, bu durumda import durdurulur ve
cümle kullanıcıya bırakılır. Tablonun boş olup olmadığı çalışma anında sorgulanır; boş tabloya
böyle bir kolon güvenle eklenir.
- **Var olan kolon değiştirilmez.** Uzunluk/kesinlik/ondalık/nullability farkında import durur.
Daraltma veri keser, tip dönüşümü başarısız olabilir; doğru işlem veriye bakmayı gerektirir.
Fark somut olarak bildirilir ("uzunluk hedefte 200, script'te 300"), düzeltmeyi kullanıcı yapar.
- **Hiçbir kolon düşürülmez, hiçbir tablo `DROP` edilmez.** Hedefteki fazla kolon yalnızca
bilgilendirme olarak listelenir.
Karşılaştırma **tip adına bakmaz**. Sağlayıcı kataloğu kendi kanonik adını raporlar (PostgreSQL'de
`VARCHAR``character varying`, `TIMESTAMP``timestamp without time zone`); script'teki yazılışla
metin olarak karşılaştırmak her `VARCHAR` kolonunda sahte fark üretirdi. Yalnızca her iki
sağlayıcıda da aynı anlamı taşıyan ölçüler karşılaştırılır: uzunluk, kesinlik/ondalık, nullability.
Script bir tablo kurmuyorsa (procedure, view) ya da `CREATE TABLE` çözülemiyorsa karşılaştırma
sessizce atlanır — tahmin yürütülmez.
`data/*.json` dosyaları kapsamdaki bütün satırları taşıdığı için üzerine yazılmaz: satırlar anahtar
alan üzerinden birleştirilir (gelen satır varsa günceller, yoksa eklenir, hedefteki diğer satırlar
korunur). Diff ekranında düzenlenen içerik de aynı sözleşmeyi taşıdığı için elle birleştirme bu
davranışı bozmaz.
Güvenlik sınırları: yalnızca `wizard`, `crud`, `data`, `sql`, `postgres` kök klasörleri
(SQL sağlayıcı klasörlerinin altında yalnızca `object` ve `execute`) kabul edilir; dosya başına
5 MB, arşiv başına 50 MB, en çok 500 girdi. Kabul edilmeyen girdiler analiz sonucunda uyarı
olarak listelenir. Yetkiler: `App.Listforms.Wizard.Export` / `App.Listforms.Wizard.Import`.
---
## 8. Developer Kit
@ -406,18 +735,137 @@ Teknik kullanıcılar için `/admin/developerkit` altında toplanan araçlar:
| Araç | Route | Ne işe yarar |
| --- | --- | --- |
| **SQL Query Manager** | `/admin/sql-manager` | Nesne gezgini, Monaco tabanlı SQL editörü, sonuç grid'i, nesne özellikleri ve **tablo tasarımcısı**. |
| **Custom Endpoint** | `/admin/developerkit/endpoints` | SQL veya servis tanımından REST endpoint üretimi; GET/POST/PUT/DELETE için ayrı yetki kapıları (`App.DeveloperKit.CustomEndpoints.*`) ve kullanıcı/rol/global erişim kuralları. |
| **CRUD Endpoint** | `/admin/developerkit/entities` | Tablo tanımından tam CRUD endpoint kümesi üretimi. |
| **Dynamic Service** | `/admin/developerkit/dynamic-services` | C# kodunu tarayıcıda yazıp Roslyn ile derleme (`TestCompile`), yayınlama (`Publish`) ve çalışan uygulamaya controller olarak kaydetme. Yetkiler: Create/Edit/Delete/Manage/TestCompile/Publish/ViewCode. |
| **Custom Component** | `/admin/developerkit/components` | React bileşenini veritabanında saklama; `@babel/standalone` ile tarayıcıda derleyip route'a bağlama. |
| **Visual Designer** | Component editörü içinde | Sürükle-bırak kanvas ile bileşen üretimi ve kod üretimi (`visualDesigner/codeGenerator.ts`). |
| **SQL Query Manager** | `/admin/sqlQueryManager` | Nesne gezgini, Monaco tabanlı SQL editörü, sonuç grid'i, **tablo tasarımcısı**, **view tasarımcısı** ve **CRUD endpoint yönetimi**. |
| **Custom Endpoint** | `/admin/list/App.DeveloperKit.CustomEndpoints` | SQL veya servis tanımından REST endpoint üretimi; GET/POST/PUT/DELETE için ayrı yetki kapıları (`App.DeveloperKit.CustomEndpoints.*`) ve kullanıcı/rol/global erişim kuralları. |
| **CRUD Endpoint** | SQL Query Manager içinde | Tablo tanımından tam CRUD endpoint kümesi üretimi. Ayrı ekranı yoktur; bkz. aşağıdaki not. |
| **Dynamic Service** | `/admin/list/App.DeveloperKit.DynamicServices` | Liste bir ListForm ekranıdır (KPI şeridi, Swagger butonu, kart/ızgara görünümü); kod yazma listeden açılan `DynamicServiceEditor` diyalogundadır: Monaco editörü, Roslyn ile derleme (`TestCompile`), yayınlama (`Publish`) ve çalışan uygulamaya controller olarak kaydetme. Yetkiler: Create/Edit/Delete/Manage/TestCompile/Publish/ViewCode. |
| **Custom Component** | `/admin/list/App.DeveloperKit.CustomComponents` | React bileşenini veritabanında saklama; `@babel/standalone` ile tarayıcıda derleyip route'a bağlama. Liste bir ListForm ekranıdır (KPI şeridi, ızgara/kart görünümü, grid'in kendi ekleme/düzenleme formu); satırdaki **Design** butonu `/admin/developerkit/components/design/{id}` tasarımcısını, **Open URL** butonu bileşenin `RoutePath`'ini açar. Kayıtlar `configs/seeds/host/data/App.DeveloperKit.CustomComponents.json` dosyasına senkronlanır. Yetkiler `App.DeveloperKit.CustomComponents{,.Create,.Update,.Delete,.Export,.Import}`. |
| **Visual Designer** | `/admin/developerkit/components/design/:id` | Custom Component listesindeki **Design** butonundan açılır; sürükle-bırak kanvas ile bileşen üretimi ve kod üretimi (`visualDesigner/codeGenerator.ts`). |
| **ListForm** | `/admin/list/App.Listforms.Listform` | Var olan ekranların ham tanım listesi; buradan `/admin/listform/edit/{kod}` sekmeli editörüne geçilir. |
| **Wizard Manager** | `/admin/listform/wizardManager` | Üretilmiş wizard seed dosyalarının listesi/kartı, düzenleme, silme, DB migrate, export/import. Yetki `App.Listforms.Wizard{,.Create,.Update,.Delete,.Export,.Import}`. |
> Bu ekranların artefakt şemaları, enum değerleri ve çalışan örnekleri
> [`.github/instructions/lowcode.instructions.md`](.github/instructions/lowcode.instructions.md)
> dosyasındadır.
### 8.1. SQL View Designer
`SqlViewDesignerDialog` (+ `sqlViewDesigner/`), SSMS Query Designer düzeninde görsel view
oluşturma aracıdır. Üç panel:
- **Diagram** (`DiagramPane.tsx`) — `ViewSource` kutuları (x/y konumlu, daraltılabilir, yalnızca
seçili kolonları gösterebilir) ve aralarındaki JOIN okları. Kaynak bir tablo/view olabileceği
gibi `CROSS/OUTER APPLY` ya da türetilmiş bir alt sorgu da olabilir; alt sorgunun gövdesi
olduğu gibi saklanır. JOIN türleri: `INNER`, `LEFT`, `RIGHT`, `FULL`, `CROSS`; koşul
operatörleri `=`, `<>`, `>`, `>=`, `<`, `<=`.
- **Criteria grid** (`CriteriaGrid.tsx`) — Column / Alias / Output / Group By / Sort / Filter /
Or… satırları. Bir satır ya `sourceId + columnName` referansıdır ya da serbest bir SQL
ifadesidir. Group By sütunu `GroupBy`, `Where` (satır çıktıya girmez, yalnızca filtre taşır) ve
`SUM`/`COUNT`/`COUNT_DISTINCT`/`AVG`/`MIN`/`MAX` değerlerini alır. Filtre hücreleri SSMS'teki
gibi serbest yüklemdir (`> 100`, `LIKE '%abc%'`, `IS NULL`).
- **T-SQL önizleme** — model → SQL üretimi (`generateViewSql`).
Tasarımcı tek yönlü çalışır: **model → T-SQL**. Mevcut bir view açılırken `parseViewSql` ile geri
okunmaya çalışılır; üretilen kanonik şekle uymayan bir tanım gelirse dialog **ham SQL moduna**
düşer ve kullanıcı Query Editor'dan devam eder (`onOpenInEditor`). PostgreSQL bağlantılarında
`isPostgreSql` bayrağı ile söz dizimi buna göre üretilir.
> **CRUD Endpoint, SQL Query Manager ile birleştirildi.** Ayrı `/admin/developerkit/endpoints`
> ekranı ve menüsü kaldırıldı. Endpoint üretme, aktif/pasif etme, test etme ve silme işlemleri
> artık iki yerden yapılır: SQL Query Manager nesne gezgininde tablo satırının **CRUD Endpoints**
> aksiyonu (ve seçili tablolar için toolbar'daki toplu üretim butonu), ayrıca ListForm
> sihirbazının **Adım 2 Veri Kaynağı** ekranındaki aynı diyalog. Ayrıca **tablo
> tasarımcısında deploy öncesi bir adım** vardır: endpoint üretilip üretilmeyeceği ve
> hangi operasyonların (GetList/GetById/Create/Update/Delete) aktif başlayacağı seçilir
> ("tümünü seç" destekli); seçilmeyenler pasif olarak kaydedilir, sonradan diyalogdan
> açılabilir. Hepsi `CrudEndpointDialog` bileşenini kullanır. Yetki `App.SqlQueryManager` altındaki
> `App.SqlQueryManager.CrudEndpoints`'tir; izin verilmemişse butonlar görünmez ve
> `crud-endpoint-generate` uçlarının tamamı (base CRUD metotları dahil) reddedilir.
> Üretilen endpoint'ler `configs/seeds/{host|tenants/{tenantId}}/crud/{EntityName}.json` olarak
> seed dosyasına yazılır; `CrudDataSeeder` aynı dosyaları okuyup endpoint'leri geri yükler.
> **Tablo tasarımı varsayılanı:** Yeni tablo oluştururken tenant ve tam denetim (audit) kolonları
> varsayılan olarak eklenir: `Id`, `TenantId`, `CreationTime`, `CreatorId`, `LastModificationTime`,
> `LastModifierId`, `IsDeleted`, `DeletionTime`, `DeleterId`. Kullanıcııkça istemedikçe bu
> kolonlar çıkarılmaz.
### 8.2. Custom Component ve Visual Designer
**Saklanan tanım.** Bir custom component `Name`, `RoutePath`, `Code` (JSX), `Props`,
`Dependencies` ve `DataSources` alanlarından oluşur. Görsel tasarımcı dokümanı iki yerde tutulur:
`Props.visualDesigner` içinde (nodes, canvas, lifecycle, dataSources) ve üretilen kodun ilk
satırındaki `/*__SOZSOFT_VISUAL_DESIGNER__…__*/` yorumunda. Böylece yalnızca kod elinizdeyse bile
tasarımcı dokümanı geri okunabilir. `sourceMode` alanı `visual` ya da `code` olur; kod moduna
geçilen bir bileşen kanvasa geri dönmez.
**Seed senkronizasyonu.** Bileşenlerin seed kaynağı Custom Components ekranının veri dosyasıdır:
`configs/seeds/{host|tenants/{tenantId}}/data/App.DeveloperKit.CustomComponents.json`. Bileşene
yazan **iki yol** da bu dosyaya işlenir (`ListFormSeedDataSynchronizer`): listeden yapılan
ekleme/güncelleme/silme ve Visual Designer'ın kaydı (`CustomComponentAppService`). Veritabanı
sıfırlandığında `ListFormSeedDataApplier` satırları geri yazar; `Code`, `Props` ve `DataSources`
kolonları bu yüzden ekranda gizli alan olarak tanımlıdır. `DataSources` sütunu tasarımcı
dokümanından türetilir (`CustomComponentDataSourceResolver`): Data sekmesindeki her endpoint
`method + path` ile CRUD endpoint kataloğunda aranır, eşleşenler `EntityName` +
`crud/{EntityName}.json` referansını taşır, elle yazılmış olanlar listede kalır ama bu alanları
boş gelir. Wizard export'u bu referansları izleyerek bağlı `crud/*.json` dosyalarını zip'e ekler.
**Toolbox.** Kanvasa bırakılabilecekler beş aileye ayrılır:
| Aile | İçerik |
| --- | --- |
| `layout` | `PageContainer`, `FlexRow` (kolon sayısı, ilk kolon genişliği, hizalama, gap, wrap), `Spacer` |
| `data` | `Form` — ASP.NET'in Form + FormView karşılığı; dört CRUD endpoint'ini sahiplenen kapsayıcı |
| `platform` | `ListView`, `DataGridView`, `TreeView`, `GanttView`, `TodoBoard`, `CardView`, `SchedulerView`, `PivotView`, `ChartView` — hepsi `listFormCode` ile bir ListForm ekranını gömer |
| `html` / `ui` | Ham HTML etiketleri ve `components/ui` tasarım sistemi bileşenleri (sözleşmeleri metadata'dan okunur) |
| `custom` | Başka custom component'ler (bağımlılık olarak kaydedilir) |
**Form bileşeni.** `selectEndpoint` / `insertEndpoint` / `updateEndpoint` / `deleteEndpoint`,
`keyFieldName`, `collectionPath`, `keySource` + `keyParamName`, `previewKeyValue`, `autoLoad`,
`showToolbar`, `columnCount`, `gap` özellikleriyle yapılandırılır. İçine bırakılan her bileşen
Select sonucunun bir kolonuna bağlanır ve Save/Delete üzerinden geri yazar.
**Veri bağlama.** `DesignerBinding` bir kaynağa (`sourceId`) ve yola (`path`) bağlanır; `labelPath`
/ `valuePath` seçim bileşenleri içindir. `columns` ile ekranda görünmeyen ek sütunlar da taşınır
ve script'ten `refs.<ad>.getColumn('sutun')` ile okunup başka bir bileşenin değerine ya da Form
kaydına yazılır. Filtreler CrudEndpoint `GetList` sözleşmesine göre üretilir: `eq` çıplak query
parametresi (`?RoleId=…`), diğerleri son ek taşır (`?Name.contains=…`). Operatörler: `eq`, `ne`,
`contains`, `startswith`, `endswith`, `gt`, `gte`, `lt`, `lte`, `in`, `isnull`, `notnull`.
**Yetki modeli.** İki katmanlıdır:
- **Node görünürlüğü**`designerPermission` prop'u dolu olan bir düğüm, yetki verilmemişse
render edilmez.
- **Form komutları** — her komut (`selectPermission`, `insertPermission`, `updatePermission`,
`deletePermission`) ya serbesttir (boş) ya da bir yetkiye bağlıdır. `Otomatik` modda temel,
Wizard'ın bu bileşen için ürettiği okuma yetkisidir (menü kaydının korunduğu yetkinin aynısı) ve
komutlara `''` / `.Create` / `.Update` / `.Delete` son ekleri eklenir. `Özel` modda yetki adı
elle yazılır.
**Script.** Tasarımcı, ListForm Editor Script ile aynı ortak dialog'u kullanır
(`designerScriptDialect.ts` + `designerScriptRecipes.ts`); tarif grupları bileşen erişimi, API
çağrıları, form ve olay/sayfa başlıkları altında toplanır.
**Kanvas doğruluğu.** Tasarım zamanında görülen, canlıda görülendir; bunu iki mekanizma sağlar.
(1) *Yerleşim sınıflarının taşınması*: kanvas her düğümü seçim/sürükleme için bir sarmalayıcıya
alır ve üst öğe grid/flex olduğunda asıl öğe o sarmalayıcı olur. `splitLayoutClasses` düğümün
yerleşim sınıflarını sarmalayıcıya geçirir — `col-span`/`row-span`/`order`/`self`/`flex-1`/`basis`,
sabit ve kesirli `w-*`, kenar boşlukları taşınır; konum sınıfları (`absolute`, `inset-*`, `z-*`,
`w-full`) her ikisinde de kalır. Görünürlük sınıfları da sarmalayıcıya kopyalanır (`hidden lg:flex`
→ sarmalayıcıda `hidden lg:block`): aksi hâlde mobilde gizlenmesi gereken masaüstü şeridi yine bir
grid hücresi işgal eder ve komşularını yanlış hücreye iter. `fixed` kanvasta akışa çevrilir, yoksa
düğüm tasarım yüzeyinden taşardı. (2) *Cihaz çerçevesi*: araç çubuğundan masaüstü/tablet/mobil seçildiğinde kanvas kendi
belgesinde çizilir (`CanvasFrame`), böylece `md:`/`lg:` kuralları tarayıcı penceresine değil seçilen
genişliğe göre çözülür. Cihaz modunda tasarım yüzeyi kenar boşluğunu bırakır: kanvas o cihazın ekranıdır. `Responsive`
seçimi aynı sayfada, çerçevesiz ve boşluklu çizer. (3) *Gezinme kapalıdır*: kanvastaki bir bağlantıya
tıklamak seçim yapar, sayfayı değiştirmez — tıklama yakalama evresinde durdurulur, `react-router`'ın
`Link`'i de `defaultPrevented` görünce gezinmez.
**Diğer notlar.** `PlatformIcon` tasarımcı dokümanındaki ikon adını çözer; `selectComponents.ts`
`Select.componentAs` için saklanan adı (`ReactSelect`, `CreatableSelect`, `AsyncSelect`) gerçek
bileşene çevirir — kanvas ve üretilen bileşenin aynı adı aynı şekilde yorumlamasını sağlayan tek
nokta budur. Yeni bileşen `configs/seeds/host/data/App.DeveloperKit.CustomComponents.json`
içindeki bir satır olarak başlar.
**Dinamik servis yaşam döngüsü:** `DynamicServiceCompiler` kodu derler →
`DynamicAssemblyRegistrationService` assembly'yi tenant bağlamıyla kaydeder →
`ActionDescriptorChangeProvider` MVC'ye route tablosunun değiştiğini bildirir →
@ -442,14 +890,30 @@ Route.Authority ──┘ └► Permission
- Frontend'de `dynamicRoutesContext` route'ları çeker, `dynamicRouteLoader` bunları fiziksel
view'lara veya runtime derlenmiş Custom Component'lere eşler, `DynamicRouter` React Router
ağacını kurar; her korumalı route `ProtectedRoute` + `PermissionGuard` ile sarılır.
- Aynı yolu bir fiziksel route ile bir Custom Component paylaşıyorsa **Custom Component kazanır**;
karşılığı olan dosyası bulunmayan rota kaydı hiç yüklenmez. Public site menüsü bu modelin
dışındadır: `Pub` modülü altında tutulur ve uygulama konfigürasyonuyla taşınır (bkz. §10.4).
**Yetki kodları** `PlatformConsts.AppCodes` içinde merkezî olarak tanımlıdır ve
`PermissionsData.json` ile seed edilir. Ana gruplar: `App.Saas`, `App.Branches`, `App.Intranet.*`,
`App.Definitions.*`, `App.Restrictions.*`, `App.Languages.*`, `App.Listforms.*`,
`App.Notifications.*`, `App.BackgroundWorkers.*`, `App.Menus.*`, `App.DeveloperKit.*`,
`App.SqlQueryManager.*`,
`App.Orders.*`, `App.BlogManagement.*`, `App.IdentityManagement.*`, `App.Reports.*`,
`App.Administration`, `App.Setting`, `App.Setup.Migrate`.
Aksiyon yetkisi olan alt gruplar (UI tarafındaki karşılıkları `constants/permission.constant.ts`
içindedir; oradaki kontroller yalnızca butonları gizler, asıl kontrol AppService'lerdedir):
| Grup | Alt yetkiler |
| --- | --- |
| `App.Listforms.Wizard` | `.Create`, `.Update`, `.Delete`, `.Export`, `.Import` |
| `App.DeveloperKit.CustomComponents` | `.Create`, `.Update`, `.Delete`, `.Export`, `.Import` |
| `App.DeveloperKit.CustomEndpoints` | `.Get`, `.Post`, `.Put`, `.Remove` (dispatcher üzerinden çağrı kapısı; endpoint bazlı User/Role/Global kuralları bunun üstünde denetlenir) |
| `App.DeveloperKit.DynamicServices` | `.Create`, `.Edit`, `.Delete`, `.Manage`, `.TestCompile`, `.Publish`, `.ViewCode` |
| `App.SqlQueryManager` | `.CrudEndpoints` |
| `App.Setup.Migrate` | Migration + seed tetikleme (host tarafı) |
> **Kural:** Yetki sözleşmesi olmayan menü/route önerilmez ve eklenmez.
---
@ -487,8 +951,8 @@ Route.Authority ──┘ └► Permission
| Dosya yöneticisi | `/admin/files` | Klasör/dosya işlemleri. |
| Aktivite ve denetim kaydı | `/admin/activityLog`, `/admin/changeLog` | Kullanıcı hareketleri ve ABP audit log detayı. |
| Bildirimler | `/admin/profile/notification-settings`, bildirim ekranları | Kanal tercihleri ve bildirim oluşturma. |
| Forum | `/admin/forum`, `/admin/forumManagement` | Forum kullanımı ve yönetimi. |
| Intranet | Intranet dashboard ve widget'ları | Duyuru, anket, sosyal duvar, etkinlik. |
| Forum | `/admin/forum` | Forum kullanımı; yönetim `App.ForumManagement` altındaki ListForm ekranlarıyla yapılır. |
| Intranet | Intranet dashboard ve widget'ları | Duyuru, anket, sosyal duvar, etkinlik. Hangi widget'ın hangi kolonda, hangi sırada ve hangi yetkiyle yayınlandığı `Adm_T_IntranetWidget` kaydından gelir; kayıtlar `configs/seeds/{kapsam}/data/App.DeveloperKit.IntranetWidgets.json` ile senkron tutulur. |
| Video oda | `/admin/videoroom/*` | Oda listesi, detay, planlama, katılımcı ve ekran paylaşımı. |
| Raporlar | `/admin/reports/:report/view|design/...` | DevExpress rapor görüntüleyici ve tasarımcı. |
| AI asistanı | `/admin/ai` | n8n webhook üzerinden sohbet arayüzü. |
@ -496,9 +960,101 @@ Route.Authority ──┘ └► Permission
### 10.4. Public site
`/home` · `/about` · `/services` · `/products` · `/checkout` · `/payment` · `/success` ·
`/blog` · `/blog/:id` · `/demo` · `/contact` — içerikler veritabanından yönetilir;
`/about/designer` ve `/services/designer` sayfa tasarımcılarıdır.
`/home` · `/about` · `/contact` (Custom Component) · `/products` · `/checkout` · `/payment` ·
`/success` · `/blog` · `/blog/:id` · `/demo` (fiziksel sayfalar).
Ziyaretçiye açık kısım iki parçadan oluşur ve ikisi de veritabanında yaşar:
**Menü.** Public menüler `Menu` tablosunda `ModuleId = "Pub"` modülü altında tutulur ve Menü
Yönetimi ekranından girilir (sözleşme: `PlatformConsts.PublicSite`). `App.Public.Header` kökü hem
üst navigasyonu hem de alt bilgideki hızlı bağlantıları besler — ikisi de `PublicNav` düğümüdür,
menü bir kez eklenir iki yerde birden görünür; alt kaydı olan bir menü üstte açılır liste olur.
Alt bilgide `cta` kayıtları (Giriş) yer almaz: oradaki düğüm `source="navigation"` ile çizilir. `Url` sayfa yolu, `Icon`
react-icons adı, `Order` sıra, `IsDisabled` yayın anahtarı, `DisplayName` dil anahtarıdır;
`CssClass` içinde `cta` geçen kayıt düz bağlantı yerine vurgulu buton olarak çizilir. Menü ucu
`[Authorize]` olduğu için menüler uygulama konfigürasyonunun `publicMenus` alanıyla taşınır
(`PlatformApplicationConfigurationContributor`), istemcide `usePublicMenu` okur. `Pub` modülü
yönetim navigasyonuna girmez (`abpConfig.model.ts` menüyü kurarken bu modülü eler; Menü Yönetimi
ekranı elemez, menüler oradan düzenlenir). Seed: `MenusData.json``Modules` + `Menus`.
**Sayfa.** Her public sayfa bir Custom Component'tir: `RoutePath` `/admin/` ile başlamadığı sürece
bileşen otomatik olarak public route olur ve `PublicLayout` altında yayınlanır. Tasarım yalnızca
DeveloperKit > Custom Components (Visual Designer) üzerinden değişir. Aynı yolu bir fiziksel route
ile paylaşan custom component kazanır; karşılığı olan dosyası bulunmayan rota kaydı ise hiç
yüklenmez (`hasPhysicalComponent`) — seeder yalnızca eklediği için eski kayıtlar veritabanında
kalsa bile ekran patlamaz. Seed: `configs/seeds/host/data/App.DeveloperKit.CustomComponents.json`;
bileşenin ayrı dosyası yoktur, her bileşen bu dosyada bir satırdır.
Runtime kapsamı sınırlıdır: bileşen kodundaki `import` satırları derlemede temizlenir,
kullanılabilenler `React`, `ui` kit, `PlatformViewHost`, `apiService`, `axios`, `DOMPurify`,
`translate`, `checkPermission`, `getCulture` ve **katalog düğümleri**
(`components/componentEditor/runtimeNodes.ts`). **`react-icons` ve `react-router` yoktur** — ikon
`<PlatformIcon name="FaUsers" size={40} className="..." />` ile ada göre çizilir (menü ikonlarıyla
aynı sözlük), gezinme `<a href>` ile yazılır. Kullanıcıya görünen metinler dil anahtarı üzerinden
gelir.
**Tailwind sınıf yüzeyi.** Bileşenin kodu veritabanında yaşar; derleme sırasında taranan tek
kaynak seed dosyasıdır (`configs/seeds/*/data/App.DeveloperKit.CustomComponents.json`). Bu dosya
depo kökünde, `ui/` dışındadır — derleme bağlamı yalnızca `ui/` klasörü olduğunda (docker, ayrı
checkout) glob boş döner ve seed'e özgü sınıflar (`md:col-span-4`, `bg-gray-950/80`, `opacity-20`)
CSS'e hiç girmez: uygulama yerelde doğru, sunucuda stilsiz görünür. Bu yüzden aynı sınıflar var
olan `ui/safelist.txt` dosyasına da yazılır. Dosyayı zaten `twSafelistGenerator` eklentisi üretiyor
(renk/ölçü listesi); eklenti artık seed dosyalarındaki `className` değerlerini de toplayıp
`# --- custom component classes ---` işaretinin altına ekliyor. `safelist.txt` `ui/` içinde ve
depoda olduğu için her ortamda taranır. Seed kökü görünmüyorsa işaretin altındaki liste olduğu gibi
korunur — eksik bir bağlam listeyi silip sorunu büyütmez. Ayrı bir betik ya da ikinci bir safelist
dosyası yoktur; liste her `npm run build` / `npm start` ile tazelenir.
**Katalog düğümleri.** Kanvasın doğrudan çizdiği, derlenen bileşen listesinde yer almayan
bileşenler tek bir sözlükte durur (`RUNTIME_NODE_COMPONENTS`): kanvas onu `VisualCanvas` içinde,
üretilen kod ise runtime kapsamı üzerinden kullanır — iki taraf aynı kaynaktan beslendiği için
tasarım ve çalışma zamanı ayrışamaz. Sözlükte `PlatformIcon` ve public site parçaları
(`components/publicSite`) bulunur. Yeni bir düğüm eklerken: bileşen `components/publicSite` (ya da
uygun klasör) → `runtimeNodes.ts` sözlüğü → `catalog.ts` tanımı (`kind: 'custom'`) → `LanguagesData.json`
ıklama anahtarı.
Elle yazılmış bir bileşen Visual Designer'da **kod modunda**ılır: tasarımcı yalnızca kendi
ürettiği kodu düğüm ağacına çevirir (`isDesignerGeneratedCode`), aksi hâlde tanımadığı JSX'i
düşürüp kaydederken kodu yok ederdi.
Public bileşenlerin tamamı`/home`, `/about`, `/contact` sayfaları ile üst ve alt bilgi
(`PublicHeader`, `PublicFooter`; rotaları yok) — **tasarımcı belgesi** olarak tutulur: `Props` alanındaki
`visualDesigner` düğüm ağacı kaynaktır (`sourceMode: "visual"`), `Code` ondan üretilir
(`generateDesignerCode`) ve runtime da bileşeni bu belgeden derler. Yani hepsi kanvasta görünür,
sürükle-bırak ve özellik paneliyle düzenlenir. Belgeler `div`/`h1`-`h5`/`p`/`span`/`img`/`a`
HTML düğümleri ve `PlatformIcon` ikon düğümüyle kurulmuştur; `::` ile başlayan metinler üretilen
kodda `translate()` çağrısına döner. `RoutePath` boş bırakılabilir; rotası olmayan bir bileşen
yalnızca başka bir ekranın içinde çizilir (alt bilgi gibi).
Üst ve alt bilgi de birer Custom Component'tir (`PublicHeader`, `PublicFooter`; rota yok):
`PublicLayout` ikisini de `renderComponent` ile çizer ve geriye yalnızca rota değişiminde başa
sarma ile demo penceresi kalır. Bileşen kayıtlı değilse o parça çizilmez.
Üst bilginin davranışı (menü listesi, aktif bağlantı, kaydırmada değişen görünüm, mobil panel,
tema/dil/demo kontrolleri) `components/publicSite` altındaki katalog düğümlerinde yaşar; yerleşim
ve sınıflar tasarımcıdadır: `PublicHeaderBar` (kaydırıldığında `scrolledClassName`'e geçen kabuk),
`PublicNav` (`source`: `navigation`/`actions`/`all`, `variant`: `horizontal`/`vertical`),
`PublicMobileMenu` (hamburger + içeriği tasarlanan panel), `PublicLogo`, `PublicThemeToggle`,
`PublicLanguageSelector`, `PublicDemoButton`.
Rotası olmayan bileşende `RoutePath` **NULL** yazılır; `Sas_H_CustomComponent`'in `RoutePath`
tekil indeksi yalnızca dolu değerleri kapsar (`[IsDeleted] = 0 AND [RoutePath] IS NOT NULL`), aksi
hâlde ikinci rotasız bileşen birincisiyle çakışırdı.
`/home`, `/about`, `/contact` bu yolla çalışır (`PublicHomePage`, `PublicAboutPage`,
`PublicContactPage`); üst ve alt bilgi rotasızdır (`PublicHeader`, `PublicFooter`).
Veriden beslenen public sayfalar (`/products`, `/checkout`, `/payment`, `/success`, `/blog`,
`/blog/:id`, `/demo`) fiziksel kalır; içerik sayfaları Custom Component'tir. Blog listesi ve detayı
`PublicAppService`'in `post-list` / `post-by-slug` uçlarından beslenir, yönetimi
`App.BlogManagement.*` ekranlarındadır.
Eski statik içerik sayfaları ve sayfa içi tasarım modu (`?design=1`) kaldırılmıştır:
`views/public/Home|About|Contact|Services`, `views/public/designer/`, bunlara özel istemci
servisleri; sunucuda `Home`/`About`/`Contact`/`Service` entity'leri, `PublicAppService` sayfa
Get/Save uçları ve DTO'ları, `TenantData.json` + `TenantDataSeeder`,
`App.{Home,About,Services,Contact}` yetkileri ve `.Design` alt yetkileri, `/admin/public/*/designer`
rota ve menüleri.
---
@ -676,21 +1232,48 @@ konfigürasyonu ile üretilir.
- Tailwind tabanlı tasarım sistemi (`components/ui`) + DevExtreme temaları; açık/koyu mod ve
tema yapılandırıcı (`ThemeConfigurator`).
- **Varsayılan mod koyudur.** `proxy/theme/theme.config.ts`: `mode` ve `navMode` `dark`, DevExtreme
stili `dx.material.blue.dark.compact`. Kullanıcı tercihi store üzerinden saklanır; bu yalnızca
ilk açılış varsayılanıdır.
- Dil metinleri veritabanından gelir (`Sozsoft.Languages`); dil seçici header'dadır.
Tenant bazlı çeviri geçersiz kılma desteklenir.
- Layout seçenekleri: dikey menü, yatay menü, yığılmış yan menü, mobil navigasyon.
### 18.4. PWA ve sürüm yönetimi
- Precache **yalnızca uygulama kabuğunu** kapsar (index.html + entry JS/CSS ≈ 2.8 MB); DevExtreme
temaları, lazy chunk'lar, font ve görseller runtime cache ile saklanır. Sebebi:
`dist` klasörü ~55 MB olduğundan tam precache, yavaş bağlantılarda service worker kurulumunun
timeout'a düşmesine yol açıyordu.
- `registerType: 'prompt'` — yeni sürüm indirilir, kullanıcı onayıyla devreye alınır
(`views/version/swRegistration.ts`, `useAppVersionNotice.tsx`).
- `version.json` her zaman ağdan tazelenir (NetworkFirst); changelog `/admin/changeLog`
ekranında gösterilir.
- `scripts/generate-version.js` build sırasında sürüm bilgisini üretir.
- Kurulum iki kademelidir: **kabuk** (index.html + entry JS/CSS + eager vendor chunk'ları)
workbox precache manifest'inden gelir ve zorunludur; **bileşenler** (tüm lazy chunk'lar,
build'de üretilen `dist/warmup.json`) güncelleme penceresinde aynı ilerleme çubuğunda arka
planda indirilir. İkinci kademe best-effort'tur (inemeyen dosya kurulumu düşürmez, süre
bütçesi aşılırsa kalanlar runtime cache'e kalır); böylece deploy sonrası ilk gezinmede chunk
beklenmez ama yavaş bağlantıda kurulum timeout'a düşmez. DevExtreme temaları (~33 MB), font
ve görseller runtime cache'tedir.
- `registerType: 'prompt'` — yeni sürüm indirilir, indirme bitince otomatik devreye alınır
(`views/version/swRegistration.ts`, `sw.ts`, `AppUpdateDialog.tsx`).
- Isıtma yalnızca kuruluma bağlı değildir: uygulama açılışta `ENSURE_WARMUP` mesajı yollar,
service worker eksik kalan bileşenleri kaldığı yerden tamamlar.
- **Sürüm bilgisi backend'dedir.** `Sas_H_ChangeLog` tablosu tek kaynaktır; istemci sürüm
numarasını ve son sürümün notlarını `application-configuration` yanıtındaki
`extraProperties.changeLogs` alanından okur (`PlatformApplicationConfigurationContributor`),
tam listeyi ise `/api/app/change-log` endpoint'inden alır. Ayrı bir `version.json` isteği
yoktur. Changelog `/admin/changeLog` ekranında gösterilir; sürüm numarası son görülenden
farklıysa kullanıcı giriş sonrası oraya yönlendirilir (tetikleyici bundle değil, sürümdür —
yalnızca yeni bir git tag'i eklenmiş olsa da çalışır).
- **Sürüm notu akışı — DbMigrator gerektirmez:** `configs/change-logs/generate-change-logs.sh`
deploy sırasında (`deploy/app.sh`) git tag'lerinden `configs/change-logs/change-logs.json`
üretir, dosya API konteynerine `/etc/api/change-logs` altına salt okunur mount edilir ve
`ChangeLogStartupService` uygulama açılışında tabloyu bu dosyayla eşitler. Dosya imaja
**gömülmez**: api build context'i içindeyken her yeni tag Dockerfile'daki `COPY . .`
katmanını geçersiz kılıp imajı gereksiz yere yeniden derletiyordu; artık kod değişmediyse
imaj cache'ten gelir. Yeni sürüm için tag atıp deploy etmek yeterlidir. Notlar
**annotated** tag mesajından okunur (`git tag -a 1.2.0 -m "..."`), her satır bir madde.
- **Tablo git tag listesinin aynasıdır:** yeni tag eklenir, mesajı düzeltilen tag güncellenir,
git'ten silinen tag tablodan da (hard delete ile) kaldırılır. Bu yüzden `Sas_H_ChangeLog`
elle düzenlenmemelidir — ilk deploy'da geri alınır. Dosya okunamaz veya boşsa senkronizasyon
hiçbir şey yapmaz, tablo boşaltılmaz.
- Deploy algılaması service worker'ın kendi `registration.update()` kontrolüne dayanır:
herhangi bir chunk değiştiğinde entry hash'i, dolayısıyla index.html ve `sw.js` içindeki
precache manifest'i de değişir.
---
@ -751,6 +1334,11 @@ Script sırası (`configs/deployment/scripts`):
8-dev-build.sh / 9-dev-deploy.sh / 10-dev-migrator-true.sh → Dev ortamı
```
Migrator servisi `SEED` ortam değişkeni ile çalışır ve compose dosyasında `SEED=${SEED:-true}`
olarak tanımlıdır: değişken verilmezse seed **çalışır**. Seed dosyaları imaja gömülmez; depodaki
`configs/seeds` klasöründen mount edilir (`App:SeedsPath`), böylece bir seed değiştiğinde imaj
build etmek gerekmez.
Ayrıntılı kurulum notları: [`configs/deployment/README.md`](configs/deployment/README.md),
[`Readme_Production.md`](configs/deployment/Readme_Production.md),
[`Readme_Devops.md`](configs/deployment/Readme_Devops.md).
@ -790,25 +1378,66 @@ dotnet build
dotnet run --project src/Sozsoft.Platform.HttpApi.Host
dotnet format --include .\modules\Sozsoft.Notifications\ --folder
# Kod standardı kapısı (ölü kod / stil analizörleri)
dotnet build Sozsoft.Platform.sln --no-incremental `
-p:EnforceCodeStyleInBuild=true -p:GenerateDocumentationFile=true
dotnet format Sozsoft.Platform.sln --diagnostics IDE0005,IDE0161 --severity warn
# Migration (EntityFrameworkCore projesi içinde)
dotnet ef migrations add <Ad>
dotnet ef database update
dotnet ef migrations remove
```
.NET/ABP kod standardı (hedef sürümler, nullable politikası, ölü kod kuralları, modern C#
kullanımı, ABP katman sınırları) bağlayıcı olarak
[`.github/instructions/dotnet.instructions.md`](.github/instructions/dotnet.instructions.md)
dosyasındadı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.
Yeni ABP modülü eklemek için:
`abp new Sozsoft.<Modul> -t module --no-ui -m none --database-provider ef`
### 21.4. Seed dosyaları
Yeni bir modülün nasıl kurulacağını öğrenmek için başvurulacak dosyalar:
İki ayrı seed yüzeyi vardır ve karıştırılmamalıdır.
- `api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs`
- `.../Seeds/ListFormSeeder_Administration.cs`
- `.../Seeds/MenusData.json`
- `.../Seeds/PermissionsData.json`
- `.../Seeds/HostData.json`
- `.../Seeds/LanguagesData.json`
**1. Depo içi (kod ile taşınan) seed'ler** — `api/src/Sozsoft.Platform.DbMigrator/Migrations/`.
Platformun kendi ekranları, menüleri, yetkileri ve dil metinleri buradadır; değişiklikleri
derlenip yayınlanır. Yeni bir modülün nasıl kurulacağını öğrenmek için başvurulacak dosyalar:
- `ListFormSeeder_Saas.cs`, `ListFormSeeder_Administration.cs` — ListForm + alan tanımları
- `MenusData.json``Routes`, `Modules`, `Menus`
- `PermissionsData.json` — yetki grupları ve tanımları
- `HostData.json` — ayar/entegrasyon değerleri
- `LanguagesData.json` — dil anahtarları (EN/TR)
- `WizardDataSeeder.cs`, `CrudDataSeeder.cs` — aşağıdaki
runtime seed dosyalarını okuyup uygulayan seeder'lar
- `ListFormSeedDataApplier`, `CrudEndpointSeedApplier` (`Domain/Data/`) — uygulama kuralının tek
yeri; hem migrate/seed akışı hem wizard import'unun kapanışı aynı servisleri çağırır
**2. Runtime'da üretilen seed'ler** — `configs/seeds/`. Wizard, Custom Components ekranı/Visual
Designer, CRUD Endpoint Manager ve `SeedFilePath` tanımlı ListForm ekranları çalışma zamanında
burayı yazar; veritabanı silinip yeniden oluşturulduğunda aynı dosyalar
geri yüklenir. Kapsam klasörü CDN düzeniyle aynıdır ve `SeedPathResolver` üzerinden çözülür:
```
configs/seeds/
├── host/ # host kapsamı
│ ├── wizard/ # {Ad}.json → WizardDataSeeder
│ ├── crud/ # {EntityName}.json → CrudEndpointSeedApplier
│ ├── data/ # {ListFormCode}.json → ListFormSeedDataApplier
│ ├── sql/{object,execute}/ # .sql (SQL Server)
│ └── postgres/{object,execute}/
├── tenants/{tenantId}/… # aynı düzen, tenant kapsamı
└── .imports/{importId}/ # wizard import staging + backup (seeder taramalarına girmez)
```
> Bu dosyalar elle de düzenlenebilir; ilgili ekrandan tekrar kaydedildiğinde yeniden üretilirler.
> Wizard dosyası `EditFileName` ile güncellenirse sunucu önce eski dosyayı ve ürettiği kayıtları
> siler, sonra yenisini üretir. Wizard'ın ürettiği ekran **ListForm detayından** düzenlendiğinde
> de aynı dosya güncellenir (`WizardSeedSynchronizer`): `ListForm`/`Fields` bölümleri ve
> `Wizard` bölümündeki karşılıkları veritabanındaki güncel haliyle değiştirilir.
---
@ -836,8 +1465,11 @@ Yeni bir modülün nasıl kurulacağını öğrenmek için başvurulacak dosyala
- `Telegram` bildirim kanalı altyapıda tanımlıdır ancak UI'da kapalıdır.
- Depodaki `appsettings*.json` ve deployment dokümanları geliştirme değerleri/örnek kimlik
bilgileri içerir; üretim öncesi tümü değiştirilmelidir.
- `.github/instructions/ai.instructions.md` içinde stack ".NET 9 + ABP 9" olarak yazılıdır;
kod tabanı **.NET 10 + ABP 10** üzerindedir. Kural bölümleri geçerliliğini korur.
- SQL View Designer tek yönlüdür (model → T-SQL). Elle yazılmış ya da tasarımcının kanonik
şekline uymayan bir view tanımı geri okunamaz; dialog ham SQL moduna düşer.
- `api/test/Sozsoft.Platform.EntityFrameworkCore.Tests` şu an SQLite şema oluşturmada
(`nvarchar(max)` → `SQLite Error 1`) kırıktır; bu kırıklık kod standardından önce de vardı ve
ayrı bir iş olarak ele alınmalıdır.
- Rolyetki eşleşmeleri veritabanında müşteri ortamına göre değişir; buradaki yetki kodları
tanımın kendisidir, atama değil.

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,5 +1,5 @@
namespace Sozsoft.Languages.Languages
{
namespace Sozsoft.Languages.Languages;
public static class LanguageCodes
{
public const string Ar = "ar";
@ -21,25 +21,3 @@
public const string Zh = "zh-Hans";
}
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 = "繁體中文";
}
}

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;

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;

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

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;

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,13 +30,15 @@ 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);

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
{

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;
}
@ -955,38 +957,26 @@ FROM (
}
/// <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);
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";
return SeedPathResolver.GetScopePath(
_configuration,
_currentTenant.Id,
providerDirectoryName,
SeedPathResolver.ObjectFolder);
}
}

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

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

View file

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

View file

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

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