Compare commits
73 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6742393271 | ||
|
|
ad00e75911 | ||
|
|
9b9ef9c100 | ||
|
|
59ec8950a3 | ||
|
|
54e3390cbf | ||
|
|
76091c0518 | ||
|
|
a20d47cab8 | ||
|
|
3ec9a94860 | ||
|
|
ed143c01d0 | ||
|
|
1423865a8c | ||
|
|
c33926d6e5 | ||
|
|
8be8fef972 | ||
|
|
4bf6b4c257 | ||
|
|
fd8d2ef73a | ||
|
|
6f756f33d0 | ||
|
|
63653dd974 | ||
|
|
43e3aeebd0 | ||
|
|
2e4e089e4a | ||
|
|
e80fc2337c | ||
|
|
3b93cd3895 | ||
|
|
a0491f9db8 | ||
|
|
a1a76b1a95 | ||
|
|
3db82df964 | ||
|
|
7c9934f56e | ||
|
|
9e99475a3e | ||
|
|
e237cacc2a | ||
|
|
47d9a20998 | ||
|
|
7a0839a3f6 | ||
|
|
0941b9143b | ||
|
|
3622764923 | ||
|
|
4d7aaef901 | ||
|
|
78bc20b645 | ||
|
|
c4de55d459 | ||
|
|
7ec77c551f | ||
|
|
462dd20edb | ||
|
|
1fc7111da2 | ||
|
|
a3667539d6 | ||
|
|
a670d2e966 | ||
|
|
91de623a85 | ||
|
|
ae7ddf6c7f | ||
|
|
9981d5b19a | ||
|
|
422e32489f | ||
|
|
e4cecc8a62 | ||
|
|
9de6b1d05c | ||
|
|
ec6b35d6d5 | ||
|
|
b1dac82d56 | ||
|
|
0d0834efaf | ||
|
|
9e362f6671 | ||
|
|
f744d18f83 | ||
|
|
b13f0b6a57 | ||
|
|
baef565dd4 | ||
|
|
90a66562e7 | ||
|
|
4c09de9ad0 | ||
|
|
7186cb0ab0 | ||
|
|
870c98690c | ||
|
|
4003dc1845 | ||
|
|
61234dec3f | ||
|
|
690da79b87 | ||
|
|
6da6654ec4 | ||
|
|
7659dbdaa1 | ||
|
|
003e74101f | ||
|
|
ece4ee9783 | ||
|
|
eb77285e59 | ||
|
|
010594ac27 | ||
|
|
3aa74318c2 | ||
|
|
3152cd681b | ||
|
|
7870ebb7bc | ||
|
|
a0121f8e11 | ||
|
|
f5ba73f875 | ||
|
|
9a2d88b93b | ||
|
|
c993b77b5d | ||
|
|
39ad5cab12 | ||
|
|
b517b7346e |
879 changed files with 67443 additions and 32361 deletions
309
.github/instructions/ai.instructions.md
vendored
309
.github/instructions/ai.instructions.md
vendored
|
|
@ -14,14 +14,25 @@ Primary principle:
|
||||||
|
|
||||||
- Configuration first, code last.
|
- Configuration first, code last.
|
||||||
|
|
||||||
|
Companion documents (this file wins on conflict):
|
||||||
|
|
||||||
|
| Document | Scope |
|
||||||
|
| --- | --- |
|
||||||
|
| `lowcode.instructions.md` | **Authoring reference**: exact schemas, enum values and worked examples for producing wizard screens, editor scripts, custom components, SQL objects and endpoints. Read it before writing any artifact. |
|
||||||
|
| `lowcode-reference.instructions.md` | **Capability inventory**: field-by-field contract of every ListForm/ListFormField JSON column, chart and pivot options, SQL command hooks, Visual Designer toolbox, the scheduled-mail module and (§12) every ready-made screen under the Saas and Administration menus. Sweep its §0 checklist before producing a screen or component; a request whose answer already exists as a platform screen (§12.5) is configured there, not rebuilt. |
|
||||||
|
| `dotnet.instructions.md` | Binding code standard for everything under `api/` |
|
||||||
|
| `list.instructions.md` | Step-by-step procedure for adding a module / list through seeders |
|
||||||
|
| `../../README.md` | Technical map: what exists, where it lives, how it is wired |
|
||||||
|
| `../../CLAUDE.md` | Operating rules and standing defaults for AI agents in this repo |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Platform Scope
|
## 2. Platform Scope
|
||||||
|
|
||||||
Core stack:
|
Core stack:
|
||||||
|
|
||||||
- Backend: C# .NET 9 + ABP 9.x
|
- Backend: C# .NET 10 (C# 14) + ABP 10.x — kod standardı: `dotnet.instructions.md`
|
||||||
- Frontend: React 18 + DevExtreme
|
- Frontend: React 19 + DevExtreme
|
||||||
- Database: SQL Server (dynamic datasource support)
|
- Database: SQL Server (dynamic datasource support)
|
||||||
- Cache: Redis
|
- Cache: Redis
|
||||||
- Background jobs: Hangfire + ABP Background Workers
|
- Background jobs: Hangfire + ABP Background Workers
|
||||||
|
|
@ -41,11 +52,25 @@ System nature:
|
||||||
3. Every proposal must include tenant and permission design.
|
3. Every proposal must include tenant and permission design.
|
||||||
4. Never bypass platform authorization patterns.
|
4. Never bypass platform authorization patterns.
|
||||||
5. Never hardcode secrets, tenant ids, or connection strings.
|
5. Never hardcode secrets, tenant ids, or connection strings.
|
||||||
|
6. **Stay inside the framework; ask before stepping out.** The platform may extend itself through
|
||||||
|
its own mechanisms (seed artifacts; adding a missing event, prop, or small backward-compatible
|
||||||
|
capability to an existing UI component — when Developer Kit tooling builds ~70% of the
|
||||||
|
requested component, the rest is completed with such small additions, never with a new
|
||||||
|
component; backward-compatible additions to script recipes / editor options / tokens).
|
||||||
|
Anything beyond that — a new React component or page, a new toolbox family, core engine changes (ListForm
|
||||||
|
runtime, seeders, code generator, auth, DataSourceManager, dispatcher/compiler), new ABP
|
||||||
|
entity/module/migration, platform table schema changes, new NuGet/npm dependencies, `Routes`
|
||||||
|
in `MenusData.json`, new external integrations — is **out of frame**: finish everything that
|
||||||
|
fits inside the frame first, then ask the user with a single question (why configuration is
|
||||||
|
insufficient, the smallest change that works, files touched, rollback) and proceed **only with
|
||||||
|
explicit approval**. Approval is per request and does not carry over. Full lists:
|
||||||
|
`lowcode.instructions.md` §0.8.
|
||||||
|
|
||||||
Exception policy:
|
Exception policy:
|
||||||
|
|
||||||
- Code-level React or backend development can be considered only if user explicitly requests code implementation and configuration path is insufficient.
|
- Code-level React or backend development can be considered only if user explicitly requests code implementation and configuration path is insufficient.
|
||||||
- If exception is required, AI must explain why configuration-based options are not enough.
|
- If exception is required, AI must explain why configuration-based options are not enough.
|
||||||
|
- Never say "cannot be done": the answer is either an in-frame build or an approval question.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -116,7 +141,7 @@ Capabilities:
|
||||||
- Query
|
- Query
|
||||||
- WebService
|
- WebService
|
||||||
- Cascading lookup parent-child behavior
|
- Cascading lookup parent-child behavior
|
||||||
- Dynamic validation, editor options, editor scripts
|
- Dynamic validation, editor options, editor scripts (see 7.1)
|
||||||
- Conditional formatting and style injection
|
- Conditional formatting and style injection
|
||||||
- Grid state save/load/reset
|
- Grid state save/load/reset
|
||||||
- User filter save/apply/delete flows
|
- User filter save/apply/delete flows
|
||||||
|
|
@ -146,6 +171,34 @@ Supported editor types:
|
||||||
- dxTextArea
|
- dxTextArea
|
||||||
- dxTextBox
|
- dxTextBox
|
||||||
|
|
||||||
|
### 7.1 Editor Script and Editor Options (builder-backed)
|
||||||
|
|
||||||
|
`EditorScript` and `EditorOptions` are produced from a shared vocabulary, in two places that
|
||||||
|
must stay identical:
|
||||||
|
|
||||||
|
| Surface | Location |
|
||||||
|
| --- | --- |
|
||||||
|
| Visual builder (UI) | `ui/src/views/admin/listForm/edit/json-row-operations/editor-script/scriptRecipes.ts` and `.../editor-options/optionSpecs.ts` + `presets.ts` |
|
||||||
|
| Seeder API (C#) | `api/src/Sozsoft.Platform.Domain.Shared/Editors/` — `EditorScript`, `EditorScriptRule`, `EditorScriptCondition`, `EditorOptions`, `EditorOptionsBuilder` |
|
||||||
|
|
||||||
|
Rules AI must follow:
|
||||||
|
|
||||||
|
- Prefer a recipe over hand-written JS. Recipes cover arithmetic (`Multiply`, `Subtract`,
|
||||||
|
`Percent`, `Sum`, `Formula`), dates (`Today`, `Days`, `Hours`), data movement (`Copy`,
|
||||||
|
`SetValue`, `Clear`, `ApiToField`), and view/notify actions (`ReadOnly`, `Notify`, `Ask`,
|
||||||
|
`OpenUrl`). Free-form JS is `Custom` and is the last option.
|
||||||
|
- Generated script carries a `// @builder {...}` header on the first line that stores the rules,
|
||||||
|
plus `// @runOnOpen` when any rule runs on form open. **Never edit the header by hand and never
|
||||||
|
emit a script that does not match what the builder would emit** — the dialog then treats the
|
||||||
|
script as manually edited and closes the rule editor.
|
||||||
|
- Rule triggers: `change` (default), `open` (default-value generation), `both`. Conditions join
|
||||||
|
with `and` / `or`.
|
||||||
|
- When a recipe changes on one side, change its port on the other side in the same task.
|
||||||
|
- In seeders write `EditorOptions = EditorOptions.Number(2).ShowClearButton()` style expressions
|
||||||
|
instead of raw JSON strings. Options marked `platform: true` in `optionSpecs.ts` deserialize
|
||||||
|
into typed DTOs (`GridBoxOptionsDto`, `TagBoxOptionsDto`, `ImageUploadOptionsDto`); wrong types
|
||||||
|
are silently ignored.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. Dynamic Form System
|
## 8. Dynamic Form System
|
||||||
|
|
@ -172,6 +225,8 @@ Supported component families:
|
||||||
- TreeList / Tree view
|
- TreeList / Tree view
|
||||||
- SubForm tabs (List, Tree, Gantt, Scheduler, Form, Chart)
|
- SubForm tabs (List, Tree, Gantt, Scheduler, Form, Chart)
|
||||||
- Widget Group (dashboard KPI cards)
|
- Widget Group (dashboard KPI cards)
|
||||||
|
- TodoBoard — a Kanban board: columns come from `statusExpr` values, cards change status by drag
|
||||||
|
and drop, new columns (statuses) can be added from the board
|
||||||
|
|
||||||
Runtime UI capabilities:
|
Runtime UI capabilities:
|
||||||
|
|
||||||
|
|
@ -212,6 +267,54 @@ If user requests custom code explicitly:
|
||||||
- Provide a warning that low-code path is preferred.
|
- Provide a warning that low-code path is preferred.
|
||||||
- Offer configuration-first alternative first.
|
- Offer configuration-first alternative first.
|
||||||
|
|
||||||
|
### 10.1 Custom Component path (still not hand-written React)
|
||||||
|
|
||||||
|
When a screen genuinely cannot be expressed as a ListForm, the next step is **not** a physical
|
||||||
|
React page. It is a Custom Component built in the Visual Designer and attached to a menu through
|
||||||
|
the Wizard's `Custom` path.
|
||||||
|
|
||||||
|
- The component is stored in the database (`Name`, `RoutePath`, `Code`, `Props`, `Dependencies`,
|
||||||
|
`DataSources`) and compiled in the browser with `@babel/standalone`.
|
||||||
|
- The designer document lives in `Props.visualDesigner` **and** in the
|
||||||
|
`/*__SOZSOFT_VISUAL_DESIGNER__…__*/` header of the generated code. `sourceMode` is `visual` or
|
||||||
|
`code`; switching to `code` is one-way — the canvas cannot be restored afterwards, so do not
|
||||||
|
propose it casually.
|
||||||
|
- Toolbox families: `layout` (`PageContainer`, `FlexRow`, `Spacer`), `data` (`Form` — owns the
|
||||||
|
four CRUD endpoints and acts as a container), `platform` (`ListView`, `DataGridView`,
|
||||||
|
`TreeView`, `GanttView`, `TodoBoard`, `CardView`, `SchedulerView`, `PivotView`, `ChartView` —
|
||||||
|
each embeds a ListForm screen through `listFormCode`), `html`/`ui`, and other custom
|
||||||
|
components as `custom`.
|
||||||
|
- **Reuse before drawing:** if the requirement is a list/tree/chart that a ListForm already
|
||||||
|
serves, drop the matching `platform` node with its `listFormCode` instead of rebuilding it out
|
||||||
|
of primitives.
|
||||||
|
- Data binding goes through CRUD endpoints. Filter operators map to the CrudEndpoint `GetList`
|
||||||
|
contract: `eq` is a bare query parameter, every other operator carries its name as a suffix
|
||||||
|
(`?Name.contains=…`).
|
||||||
|
- Permissions are two-layered and mandatory: node-level `designerPermission` for visibility, and
|
||||||
|
per-command Form permissions (`selectPermission`, `insertPermission`, `updatePermission`,
|
||||||
|
`deletePermission`). In `Otomatik` mode the base is the read permission the Wizard created for
|
||||||
|
the component and the commands get `''` / `.Create` / `.Update` / `.Delete` suffixes.
|
||||||
|
- List screen permissions: `App.DeveloperKit.CustomComponents{,.Create,.Update,.Delete,.Export,.Import}`.
|
||||||
|
- Every save/delete is mirrored into `configs/seeds/{scope}/data/App.DeveloperKit.CustomComponents.json`; the endpoints it uses are
|
||||||
|
referenced through `crud/{Entity}.json`. A component that is not seed-backed is not done.
|
||||||
|
|
||||||
|
### 10.2 SQL View Designer
|
||||||
|
|
||||||
|
For shaping data before a screen binds to it, prefer a designed view over an inline query:
|
||||||
|
`SqlViewDesignerDialog` produces T-SQL from a diagram (sources, joins, `CROSS/OUTER APPLY`,
|
||||||
|
derived subqueries) plus an SSMS-style criteria grid (Column / Alias / Output / Group By / Sort /
|
||||||
|
Filter / Or…). It is **one-way** — model to T-SQL. A view that does not parse back into the
|
||||||
|
canonical shape drops the dialog to raw SQL mode, so hand-editing a designed view costs the
|
||||||
|
designer. Aggregations available in the Group By column: `GroupBy`, `Where`, `SUM`, `COUNT`,
|
||||||
|
`COUNT_DISTINCT`, `AVG`, `MIN`, `MAX`.
|
||||||
|
|
||||||
|
Persistence: only the **table designer's** deploy writes a seed file automatically
|
||||||
|
(`{sql|postgres}/object/{TableName}.sql`, full CREATE script, via `save-table-script`). View
|
||||||
|
Designer deploys and any DDL run from the SQL editor (views, procedures, functions) reach the
|
||||||
|
database only — their seed files under `{sql|postgres}/object/` must be written explicitly.
|
||||||
|
Full mechanics and the SP/function production patterns: `lowcode.instructions.md` §8.1–§8.2.1
|
||||||
|
and §9.5.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 11. Integrations
|
## 11. Integrations
|
||||||
|
|
@ -342,7 +445,8 @@ When configuration cannot satisfy requirement:
|
||||||
1. Try SQL-based design
|
1. Try SQL-based design
|
||||||
2. Try Custom Endpoint
|
2. Try Custom Endpoint
|
||||||
3. Try Dynamic Service
|
3. Try Dynamic Service
|
||||||
4. Propose minimal code change with explicit justification
|
4. Propose minimal code change with explicit justification — this step is out of frame:
|
||||||
|
**ask and wait for approval before touching code** (rule 3.6).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -540,13 +644,39 @@ When in doubt, AI must choose the path that preserves:
|
||||||
|
|
||||||
## 31. Seeder-Driven Low-Code Development Guide (Authoritative)
|
## 31. Seeder-Driven Low-Code Development Guide (Authoritative)
|
||||||
|
|
||||||
AI must learn and teach implementation flow primarily from these seed assets:
|
There are **two seed surfaces**. AI must not mix them.
|
||||||
|
|
||||||
- api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Saas.cs
|
**A. Repository seeds (shipped with code)** — `api/src/Sozsoft.Platform.DbMigrator/Migrations/`.
|
||||||
- api/src/Sozsoft.Platform.DbMigrator/Migrations/MenusData.json
|
Platform-owned screens, menus, permissions and language texts. Changing them requires a build and
|
||||||
- api/src/Sozsoft.Platform.DbMigrator/Migrations/PermissionsData.json
|
a deploy. AI must learn and teach implementation flow primarily from these assets:
|
||||||
- api/src/Sozsoft.Platform.DbMigrator/Migrations/HostData.json
|
|
||||||
- api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json
|
- ListFormSeeder_Saas.cs / ListFormSeeder_Administration.cs / ListFormSeeder_{Modul}.cs
|
||||||
|
- MenusData.json
|
||||||
|
- PermissionsData.json
|
||||||
|
- HostData.json
|
||||||
|
- LanguagesData.json
|
||||||
|
- WizardDataSeeder.cs / CrudDataSeeder.cs — the seeders that read
|
||||||
|
surface B below
|
||||||
|
- ListFormSeedDataApplier, CrudEndpointSeedApplier (Domain/Data/) — the single home of the apply
|
||||||
|
rules; both the migrate/seed flow and the wizard import's completion call the same services.
|
||||||
|
`data/` rows are inserted-only on migrate (a possibly stale file must not overwrite live data);
|
||||||
|
`crud/` entries are timestamp-guarded — an existing record is updated or deleted only when the
|
||||||
|
file's `GeneratedAt` is newer than the record's `LastModificationTime`/`CreationTime`. **Migrate
|
||||||
|
adds; it does not overwrite.** Follow this rule in any new seeder.
|
||||||
|
|
||||||
|
**B. Runtime seeds (written by the running application)** — `configs/seeds/`, resolved through
|
||||||
|
`SeedPathResolver`. The Wizard, the Custom Components screen / Visual Designer, the CRUD
|
||||||
|
Endpoint Manager and every ListForm screen with a `SeedFilePath` write here; the
|
||||||
|
seeders above restore them when the database is recreated. Scope folders mirror the CDN layout:
|
||||||
|
|
||||||
|
```
|
||||||
|
configs/seeds/host/{wizard,data,crud,sql/{object,execute},postgres/{object,execute}}
|
||||||
|
configs/seeds/tenants/{tenantId}/… same layout, tenant scope
|
||||||
|
configs/seeds/.imports/{importId}/… wizard import staging + backup (not scanned)
|
||||||
|
```
|
||||||
|
|
||||||
|
Rule: anything produced at runtime must land in surface B. **A feature that disappears when the
|
||||||
|
database is recreated is not delivered.**
|
||||||
|
|
||||||
If user asks "how to add a new module/screen", AI must answer with this exact operational sequence.
|
If user asks "how to add a new module/screen", AI must answer with this exact operational sequence.
|
||||||
|
|
||||||
|
|
@ -659,6 +789,39 @@ When user asks for a screen/module, AI must answer in this order:
|
||||||
|
|
||||||
AI should produce practical, copy-adaptable artifact definitions and avoid abstract-only explanations.
|
AI should produce practical, copy-adaptable artifact definitions and avoid abstract-only explanations.
|
||||||
|
|
||||||
|
### 31.9 Screen brief, discovery questions and image-based requests
|
||||||
|
|
||||||
|
A request arrives either as prose or as a **screenshot / mockup / photo** of the desired screen.
|
||||||
|
Both enter the same pipeline — and for a **new screen/component the pipeline starts with
|
||||||
|
questions, not files**:
|
||||||
|
|
||||||
|
1. **Run the discovery round** (`lowcode.instructions.md` §0.6). Ask in batches (max 4 questions
|
||||||
|
per round, each with 2–4 options and a recommended default), max three rounds. Round 1 always
|
||||||
|
covers: screen kind (List/Wizard vs Custom Component vs single-record form), data source,
|
||||||
|
owning module, and tenancy scope. Round 2 uses the path-specific question set (§0.6.3 for
|
||||||
|
List, §0.6.4 for Custom). Never ask what the repo already answers — search first, confirm
|
||||||
|
findings instead of re-asking. If the user says "you decide / don't ask", skip the round and
|
||||||
|
build on defaults.
|
||||||
|
2. Turn the answers into a single screen brief — purpose, data source, columns and editors,
|
||||||
|
layouts, actions, sub-screens, widgets, approval flow, menu, permissions, tenancy, language
|
||||||
|
texts. For an image, read it first (page regions → main region type → toolbar → columns →
|
||||||
|
filters → form controls → row actions); the image answers most of round 2, so ask only what
|
||||||
|
an image cannot show (§0.7.4).
|
||||||
|
3. **Get the brief confirmed before writing any seed file.** Mark defaulted lines `(varsayılan)`
|
||||||
|
so the user sees what they are approving.
|
||||||
|
4. Pick the tool: grid-shaped work → SQL Query Manager + Wizard; free layout / dashboard /
|
||||||
|
single-record custom form → Custom Component. **When in doubt, recommend the Wizard.**
|
||||||
|
5. Produce the seed files in dependency order, then list them and restate the defaulted choices.
|
||||||
|
|
||||||
|
An image is a reference, not a pixel contract: build the closest thing the platform's own
|
||||||
|
components give, and state at the end what had no equivalent. Three things are never assumed,
|
||||||
|
always asked: which table is written to, who approves (when there is an approval flow), and the
|
||||||
|
formula of any calculation/business rule. This question-first rule applies only to producing a
|
||||||
|
**new** screen/component; fixes, field additions, debugging and explanations proceed directly.
|
||||||
|
|
||||||
|
The concrete question sets, brief template, the image-element → artifact mapping and the
|
||||||
|
visual-control → `EditorType` mapping are in `lowcode.instructions.md` §0.6–§0.7.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 32. Mandatory Default Behaviors (Do Not Ask Repeatedly)
|
## 32. Mandatory Default Behaviors (Do Not Ask Repeatedly)
|
||||||
|
|
@ -701,7 +864,131 @@ Rules:
|
||||||
- Permission contract must be created/bound for both parent and child menu items.
|
- Permission contract must be created/bound for both parent and child menu items.
|
||||||
- Route/menu consistency remains mandatory (`Url` and screen contract must match).
|
- Route/menu consistency remains mandatory (`Url` and screen contract must match).
|
||||||
|
|
||||||
### 32.3 AI enforcement requirement
|
### 32.3 Wizard component kind and menu creation
|
||||||
|
|
||||||
|
`ListFormWizardDto.ComponentKind` is the wizard's fork and the first field in the seed file.
|
||||||
|
Default is `List`, so older seed files that lack it keep working.
|
||||||
|
|
||||||
|
| Kind | Produces | Menu URL | Child permissions |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `List` | ListForm + ListFormFields + Route + Menu + Permissions + language keys | `/admin/list/{ListFormCode}` | `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note` |
|
||||||
|
| `Custom` | Menu + Permissions + language keys; the screen is the selected Custom Component | the component's `RoutePath` | `.Create`, `.Update`, `.Delete` only |
|
||||||
|
|
||||||
|
`Export` / `Import` / `Note` exist only in the ListForm ecosystem. Never propose them on the
|
||||||
|
`Custom` path.
|
||||||
|
|
||||||
|
`CreateMenu = false` (menu-less wizard): no menu record — and no parent menu record — is created;
|
||||||
|
ListForm, permissions and language keys are still produced. Use it when the screen is meant to be
|
||||||
|
embedded elsewhere as a SubGrid/part. Default is `true`.
|
||||||
|
|
||||||
|
### 32.4 Permission group selection
|
||||||
|
|
||||||
|
The wizard's permission group comes from a select list backed by `GetWizardPermissionGroups`.
|
||||||
|
Display names are read from the language key whose name equals the group name.
|
||||||
|
|
||||||
|
- Selecting an existing group fills `PermissionGroupEn/Tr` (seed file: `Language.PermissionGroupEn/Tr`);
|
||||||
|
if left blank the server writes the database values back, so the seed file always carries both
|
||||||
|
languages.
|
||||||
|
- Defining a new group writes those texts to the language key with the same name as the group.
|
||||||
|
- AI must never propose a permission group without EN + TR display names.
|
||||||
|
|
||||||
|
### 32.5 Wizard export/import is a package, not a file
|
||||||
|
|
||||||
|
Exporting a wizard produces a zip whose paths are relative to the scope folder
|
||||||
|
(`host` / `tenants/{tenantId}`) and which carries every dependency:
|
||||||
|
|
||||||
|
The closure is **recursive** (`WizardExportCollector`): a sub-form's wizard file is queued and its
|
||||||
|
own dependencies are collected under the same rules, so the archive stands on its own.
|
||||||
|
|
||||||
|
- `wizard/{file}.json` — the screen itself plus the wizard files of the sub-forms it references
|
||||||
|
through `SubFormsDto[].Code`, recursively
|
||||||
|
- `data/App.DeveloperKit.CustomComponents.json` — `Custom` path; the file is filtered down to the
|
||||||
|
exported component and its dependency chain, never exported whole
|
||||||
|
- `data/{ListFormCode}.json` — the screen's own data mirror when `SeedFilePath` is set
|
||||||
|
- `crud/{entity}.json` — endpoints those components use
|
||||||
|
- `{sql|postgres}/{object|execute}/{object}.sql` — the object behind `SelectCommand`, the objects
|
||||||
|
a lookup query (`LookupDto.LookupQuery`) references through `FROM`/`JOIN`, and on the `Custom`
|
||||||
|
path the tables the CRUD endpoints sit on
|
||||||
|
|
||||||
|
Script files are never guessed from a fixed provider/folder combination: the scope's script folders
|
||||||
|
are scanned from disk, and every file matching the object name is added. A missing script is **not**
|
||||||
|
a warning — not every table is seed-managed (ABP's own tables come from EF migrations); whether the
|
||||||
|
target has it is decided by the import analysis against the real database, never assumed here.
|
||||||
|
|
||||||
|
An unresolved **recorded reference** (a sub-form, a `SeedFilePath` mirror, a `DataSources[].SeedFile`,
|
||||||
|
a component row named in a seed file but with no counterpart) does not block the download but is
|
||||||
|
never silent: `ExportAnalysis` returns the full archive contents plus what is missing, and the File
|
||||||
|
Manager shows that list before downloading.
|
||||||
|
|
||||||
|
The import screen is a four-step wizard (`Steps`): **Files** (resolve conflicts) → **Checks**
|
||||||
|
(findings; a schema finding opens the existing `SqlTableDesignerDialog`, and the analysis re-runs
|
||||||
|
after a deploy) → **Plan** (the full list of what confirming will do, in the server's completion
|
||||||
|
order) → **Apply**. Going back is always allowed; going forward is gated — unresolved conflicts
|
||||||
|
block Checks, a blocking finding blocks Plan.
|
||||||
|
|
||||||
|
Server-side it is two phases: `AnalyzeImport` (unzip to `.imports/{id}/staged`, classify each entry
|
||||||
|
as `New` / `Identical` / `Conflict`) then `ApplyImport` (file-by-file write, previous content backed
|
||||||
|
up so `RollbackImport` can undo the whole batch), closed by `CompleteImport`.
|
||||||
|
|
||||||
|
`AnalyzeImport` also **validates** before anything is written (`WizardImportValidator`). With at
|
||||||
|
least one `Error` finding the session is marked `blocked` on the server and `ApplyImport` refuses,
|
||||||
|
so the check cannot be bypassed by calling the endpoint directly. Errors: malformed JSON; a wizard
|
||||||
|
file with no `Wizard`/`ListForm` section; a screen's or a `crud/{Entity}.json`'s table missing in
|
||||||
|
the target **and** absent from the archive's scripts; a `data/{code}.json` whose ListForm exists
|
||||||
|
neither in the archive nor in the target; an unresolvable custom-component `Dependencies` /
|
||||||
|
`DataSources[].SeedFile`; an empty component name or route on the `Custom` path; a sub-form
|
||||||
|
reference with no counterpart. Warnings: object existence could not be queried, provider mismatch
|
||||||
|
(`sql` ↔ `postgres`), a missing `SeedFilePath` data file, an empty file.
|
||||||
|
|
||||||
|
The analysis also **compares the table schema**. Generated table scripts are wrapped in
|
||||||
|
`IF OBJECT_ID(...) IS NULL BEGIN CREATE TABLE ... END`, so when the target already has the table the
|
||||||
|
`CREATE` block is skipped entirely and a new column never arrives from the script itself — the screen
|
||||||
|
would then fail with an invalid-column error. The incoming `CREATE TABLE` declaration (`SqlCreateTableParser`) is
|
||||||
|
compared against the live `INFORMATION_SCHEMA.COLUMNS` shape. The split follows the risk of loss:
|
||||||
|
**a missing column is added** — adding is lossless, so it is reported as a Warning with the statement
|
||||||
|
it will run (`WizardImportIssueDto.Statement`) and applied at completion; the one exception is a
|
||||||
|
`NOT NULL` column with no default on a non-empty table, which the database itself would reject and
|
||||||
|
where the value to write is a business decision — that blocks. **An existing column is never
|
||||||
|
changed**: a length / precision / scale / nullability difference is an Error and stops the import,
|
||||||
|
because narrowing truncates and a type conversion can fail. An extra column in the target is a
|
||||||
|
Warning and is never dropped; no path emits `DROP TABLE`, `DROP COLUMN` or `ALTER COLUMN`. The
|
||||||
|
comparison lives in one place (`SqlTableSchemaComparer`) and both the analysis and the completion
|
||||||
|
use it. Type **names** are deliberately not compared: the provider catalog reports its own
|
||||||
|
canonical name (`VARCHAR` → `character varying` on PostgreSQL), so a textual comparison would raise
|
||||||
|
a false difference on every `VARCHAR` column.
|
||||||
|
|
||||||
|
A `data/*.json` entry is compared and merged **by row, not by file**: the diff shows only the
|
||||||
|
rows whose keys the archive carries (file-level `GeneratedAt`/`Order` are excluded), so unrelated
|
||||||
|
records never surface as a difference — no matching row means `New` (insert), a matching identical
|
||||||
|
row means `Identical` (nothing is transferred), a matching different row means `Conflict` (update).
|
||||||
|
The file itself is never overwritten: rows are merged into the target by key (incoming row wins,
|
||||||
|
target-only rows are kept). `CompleteImport` then applies exactly the
|
||||||
|
`data/` files it wrote to the database through `IListFormSeedDataApplier` with
|
||||||
|
`overwriteExisting: true` — a missing row is inserted, an existing one is updated from the file
|
||||||
|
(unlike migrate/seed, where a possibly stale file must not overwrite live data), and a row that was
|
||||||
|
soft deleted is revived by clearing the columns the screen's `DeleteCommand` sets
|
||||||
|
(`IsDeleted`/`DeletionTime`/`DeleterId`), otherwise the default filter would keep hiding it — and
|
||||||
|
reports the inserted/updated/skipped counts. Data files that were already identical to the target
|
||||||
|
are applied too: there was nothing to write, but the rows may be missing or soft deleted in the
|
||||||
|
database. A file the user chose to keep (different, not written) is left alone. This step is not
|
||||||
|
covered by `RollbackImport`, which only restores files and must be run before it.
|
||||||
|
|
||||||
|
`CompleteImport` runs in **dependency order and the order is fixed**: (a) the `.sql` scripts under
|
||||||
|
`{sql|postgres}/{object,execute}` (`object` first) — a missing table is created here, otherwise the
|
||||||
|
later steps would fail on it; (b) columns missing from existing tables are added; (c) `crud/*.json`
|
||||||
|
through `ICrudEndpointSeedApplier`; (d) the `data/*.json` rows; (e) `wizard/*.json` — the screens
|
||||||
|
are installed through the same path the wizard screen uses (`DeployWizardAsync`), reusing the file's
|
||||||
|
`ListForm`/`Fields` contract as-is instead of rebuilding it from the wizard answers. A screen that
|
||||||
|
already exists is left untouched, and each screen is installed in its own unit of work. No migrate
|
||||||
|
run is needed: the screen works as soon as the import finishes.
|
||||||
|
|
||||||
|
Limits: allowed root folders `wizard`, `crud`, `data`, `sql`, `postgres` (and only `object` /
|
||||||
|
`execute` under the SQL provider folders); 5 MB per entry, 50 MB per archive, 500 entries.
|
||||||
|
Permissions: `App.Listforms.Wizard.Export` / `.Import`.
|
||||||
|
|
||||||
|
AI must not propose moving a screen between environments by copying a single json file.
|
||||||
|
|
||||||
|
### 32.6 AI enforcement requirement
|
||||||
|
|
||||||
AI must proactively apply these defaults in:
|
AI must proactively apply these defaults in:
|
||||||
|
|
||||||
|
|
|
||||||
150
.github/instructions/dotnet.instructions.md
vendored
Normal file
150
.github/instructions/dotnet.instructions.md
vendored
Normal 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.
|
||||||
8
.github/instructions/list.instructions.md
vendored
8
.github/instructions/list.instructions.md
vendored
|
|
@ -7,6 +7,10 @@ This document summarizes the rules, standards, and step-by-step instructions for
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
> **Bu doküman depo içi seeder'larla (DbMigrator/Migrations) modül eklemeyi anlatır.**
|
||||||
|
> Çalışma zamanında Wizard / Custom Components / CRUD Endpoint Manager ile üretim yapacaksan
|
||||||
|
> `lowcode.instructions.md` esastır; oradaki seed dosyaları `configs/seeds` altına düşer.
|
||||||
|
|
||||||
## General Principles
|
## General Principles
|
||||||
- **Configuration First:** Always prefer platform configuration (menus, permissions, forms, localization) over custom code.
|
- **Configuration First:** Always prefer platform configuration (menus, permissions, forms, localization) over custom code.
|
||||||
- **Modularization:** Each module (e.g., {Modul}) must have its own seeder, permission group, and localization entries.
|
- **Modularization:** Each module (e.g., {Modul}) must have its own seeder, permission group, and localization entries.
|
||||||
|
|
@ -24,7 +28,7 @@ This document summarizes the rules, standards, and step-by-step instructions for
|
||||||
## Special File Handling Rules
|
## Special File Handling Rules
|
||||||
|
|
||||||
- **MenusData.json:**
|
- **MenusData.json:**
|
||||||
- Never modify the `Routes` section directly. Only `MenuGroups` and `Menus` sections can be changed for menu additions or updates. All menu additions must use the platform's configuration mechanisms or be added to `MenuGroups` and `Menus` as required by platform design.
|
- Never modify the `Routes` section directly. Only `Modules` and `Menus` sections can be changed for menu additions or updates. All menu additions must use the platform's configuration mechanisms or be added to `Modules` and `Menus` as required by platform design.
|
||||||
|
|
||||||
- **LanguagesData.json:**
|
- **LanguagesData.json:**
|
||||||
- When adding a new key, always check if the key already exists. Only add the key if it does not exist to prevent duplicates.
|
- When adding a new key, always check if the key already exists. Only add the key if it does not exist to prevent duplicates.
|
||||||
|
|
@ -35,7 +39,7 @@ This document summarizes the rules, standards, and step-by-step instructions for
|
||||||
|
|
||||||
|
|
||||||
### 1. MenusData.json
|
### 1. MenusData.json
|
||||||
- **{Modul} menu** added as a top-level menu with `ShortName: {Modul}`.
|
- **{Modul} menu** added as a top-level menu with `ModuleId: {Modul}`.
|
||||||
- **{Liste}** added as a child menu under {Modul}.
|
- **{Liste}** added as a child menu under {Modul}.
|
||||||
|
|
||||||
### 2. PermissionsData.json
|
### 2. PermissionsData.json
|
||||||
|
|
|
||||||
1393
.github/instructions/lowcode-reference.instructions.md
vendored
Normal file
1393
.github/instructions/lowcode-reference.instructions.md
vendored
Normal file
File diff suppressed because it is too large
Load diff
3746
.github/instructions/lowcode.instructions.md
vendored
Normal file
3746
.github/instructions/lowcode.instructions.md
vendored
Normal file
File diff suppressed because it is too large
Load diff
8
.gitignore
vendored
8
.gitignore
vendored
|
|
@ -1,10 +1,14 @@
|
||||||
|
# Konteyner veri klasorleri (mssql/postgres/redis vb.); seed veri dosyalari haric tutulur.
|
||||||
configs/**/data/**
|
configs/**/data/**
|
||||||
|
!configs/seeds/**/data/**
|
||||||
*.bak
|
*.bak
|
||||||
**/node_modules
|
**/node_modules
|
||||||
**/.DS_Store
|
**/.DS_Store
|
||||||
logs/
|
logs/
|
||||||
|
|
||||||
# Deploy sırasında git tag'lerinden üretilir (configs/release-notes/generate-release-notes.sh)
|
# 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
|
# ve API konteynerine mount edilir. Takip edilirse deploy'daki `git pull` yerel
|
||||||
# değişiklikle çakışır ve dosya eskir.
|
# değişiklikle çakışır ve dosya eskir.
|
||||||
configs/release-notes/release-notes.json
|
configs/change-logs/change-logs.json
|
||||||
|
# Wizard import staging klasoru (gecici; oturum bitince silinir)
|
||||||
|
configs/seeds/.imports/
|
||||||
|
|
|
||||||
682
README.md
682
README.md
|
|
@ -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. |
|
| **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ı. |
|
| **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. |
|
| **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. |
|
| **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) │
|
│ ├─ DynamicRouter (route kayıtlarından React Router üretimi) │
|
||||||
│ ├─ ListForm runtime (Grid / Pivot / Tree / Chart / Gantt / ...) │
|
│ ├─ ListForm runtime (Grid / Pivot / Tree / Chart / Gantt / ...) │
|
||||||
│ ├─ Form runtime (DevExtreme Form, metadata ile alan üretimi)│
|
│ ├─ 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 |
|
| Bileşen | Sürüm / Notlar |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| React | 18.3 |
|
| React | 19.2 |
|
||||||
| Build | Vite 8 + TypeScript 5.9 |
|
| Build | Vite 8 + TypeScript 5.9 |
|
||||||
| UI kütüphaneleri | DevExtreme 25.2 (React), Tailwind CSS 3.4, kendi `components/ui` tasarım sistemi |
|
| UI kütüphaneleri | DevExtreme 26.1 (React), Tailwind CSS 3.4, kendi `components/ui` tasarım sistemi |
|
||||||
| Routing | react-router-dom 6 (dinamik route üretimi) |
|
| Routing | react-router-dom 7 (dinamik route üretimi) |
|
||||||
| State | easy-peasy (global store) + @tanstack/react-query |
|
| State | easy-peasy (global store) + @tanstack/react-query |
|
||||||
| Form | Formik + Yup, DevExtreme Form |
|
| Form | Formik + Yup, DevExtreme Form |
|
||||||
| Kod editörü | Monaco Editor |
|
| Kod editörü | Monaco Editor |
|
||||||
|
|
@ -178,8 +178,15 @@ sozsoft-platform/
|
||||||
├── configs/
|
├── configs/
|
||||||
│ ├── docker/ # Yerel geliştirme için veri katmanı compose dosyaları ve scriptler
|
│ ├── docker/ # Yerel geliştirme için veri katmanı compose dosyaları ve scriptler
|
||||||
│ ├── deployment/ # Dev/Prod compose dosyaları, nginx/redis/turn konfigürasyonu, deploy scriptleri
|
│ ├── 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ı)
|
│ └── 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ı
|
├── CLAUDE.md # Claude Code için operasyon kuralları
|
||||||
└── README.md
|
└── README.md
|
||||||
```
|
```
|
||||||
|
|
@ -233,7 +240,39 @@ dotnet run --project api/src/Sozsoft.Platform.DbMigrator
|
||||||
|
|
||||||
DbMigrator; şema migration'larını uygular ve ardından menü, route, permission, dil,
|
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
|
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` açı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
|
### 5.4. Frontend
|
||||||
|
|
||||||
|
|
@ -333,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ı. |
|
| `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/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. |
|
| `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). |
|
| `SubFormsJson` | Ana-detay ilişkisi (ParentFieldName → ChildFieldName eşlemesi). |
|
||||||
| `WidgetsJson` | Ekranın üstünde gösterilecek KPI kartları. |
|
| `WidgetsJson` | Ekranın üstünde gösterilecek KPI kartları. |
|
||||||
| `WorkflowJson` | İş akışı bağlaması. |
|
| `WorkflowJson` | İş akışı bağlaması. |
|
||||||
|
|
@ -347,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:
|
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,
|
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.
|
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ışı
|
### 7.4. Alan (field) davranışı
|
||||||
|
|
||||||
- **Editör tipleri:** `dxTextBox`, `dxTextArea`, `dxNumberBox`, `dxDateBox`, `dxDateRangeBox`,
|
- **Editör tipleri:** `dxTextBox`, `dxTextArea`, `dxNumberBox`, `dxDateBox`, `dxDateRangeBox`,
|
||||||
|
|
@ -362,9 +409,67 @@ gruplama) kullanıcı bazlı saklanır ve sıfırlanabilir.
|
||||||
- **Doğrulama:** `ValidationRuleJson` ile required/range/pattern/custom kuralları.
|
- **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
|
- **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.
|
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`.
|
- **Koşullu biçimlendirme:** `ColumnStylingJson`, `ColumnCssClass`/`ColumnCssValue`.
|
||||||
- **Alan bazlı yetki:** `PermissionJson` ile sütun düzeyinde okuma/yazma/dışa aktarma kontrolü.
|
- **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
|
### 7.5. İş akışı ve onay
|
||||||
|
|
||||||
`FormTabWorkflow` ve `views/admin/listForm/workflow/WorkflowDesigner.tsx` üzerinden görsel
|
`FormTabWorkflow` ve `views/admin/listForm/workflow/WorkflowDesigner.tsx` üzerinden görsel
|
||||||
|
|
@ -375,15 +480,67 @@ butonlar gösterilebilir.
|
||||||
|
|
||||||
### 7.6. Yeni ekran ekleme akışı (Wizard)
|
### 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)
|
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)
|
2. **ListFormField** kümesi (sütunlar, editörler, lookup'lar, doğrulamalar)
|
||||||
3. **Route** kaydı (`key`, `path`, `componentPath`, `routeType`, `authority`)
|
3. **Route** kaydı (`key`, `path`, `componentPath`, `routeType`, `authority`)
|
||||||
4. **Menu** kaydı (`ParentCode`, `Code`, `DisplayName`, `Url`, `Icon`, `RequiredPermissionName`, `Order`)
|
4. **Menu** kaydı (`ParentCode`, `Code`, `DisplayName`, `Url`, `Icon`, `RequiredPermissionName`, `Order`)
|
||||||
5. **Permission** kayıtları (`.Default`, `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note`)
|
5. **Permission** kayıtları (`.Default`, `.Create`, `.Update`, `.Delete`, `.Export`, `.Import`, `.Note`)
|
||||||
6. Gerekiyorsa **ayar/entegrasyon** bağımlılıkları
|
6. **Dil anahtarları** (menü, başlık, açıklama; EN + TR)
|
||||||
7. **Doğrulama ve geri alma** notları
|
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` | Ana–detay 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
|
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
|
görünümleri `/admin/form/{listFormCode}/:id`, grafik `/admin/chart/{listFormCode}`, pivot
|
||||||
|
|
@ -393,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,
|
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).
|
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
|
### 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).
|
- **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 →
|
- **İç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ı
|
sonuç akışı; sonuçlar `ListFormImportLog` üzerinde satır bazında raporlanır. Şablon dosyası
|
||||||
ekran tanımından üretilir.
|
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` açı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
|
||||||
|
açı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
|
## 8. Developer Kit
|
||||||
|
|
@ -408,18 +735,137 @@ Teknik kullanıcılar için `/admin/developerkit` altında toplanan araçlar:
|
||||||
|
|
||||||
| Araç | Route | Ne işe yarar |
|
| 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ı**. |
|
| **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/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ı. |
|
| **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** | `/admin/developerkit/entities` | Tablo tanımından tam CRUD endpoint kümesi üretimi. |
|
| **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/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. |
|
| **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/developerkit/components` | React bileşenini veritabanında saklama; `@babel/standalone` ile tarayıcıda derleyip route'a bağlama. |
|
| **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** | Component editörü içinde | Sürükle-bırak kanvas ile bileşen üretimi ve kod üretimi (`visualDesigner/codeGenerator.ts`). |
|
| **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ı
|
> **Tablo tasarımı varsayılanı:** Yeni tablo oluştururken tenant ve tam denetim (audit) kolonları
|
||||||
> varsayılan olarak eklenir: `Id`, `TenantId`, `CreationTime`, `CreatorId`, `LastModificationTime`,
|
> varsayılan olarak eklenir: `Id`, `TenantId`, `CreationTime`, `CreatorId`, `LastModificationTime`,
|
||||||
> `LastModifierId`, `IsDeleted`, `DeletionTime`, `DeleterId`. Kullanıcı açıkça istemedikçe bu
|
> `LastModifierId`, `IsDeleted`, `DeletionTime`, `DeleterId`. Kullanıcı açıkça istemedikçe bu
|
||||||
> kolonlar çıkarılmaz.
|
> 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 →
|
**Dinamik servis yaşam döngüsü:** `DynamicServiceCompiler` kodu derler →
|
||||||
`DynamicAssemblyRegistrationService` assembly'yi tenant bağlamıyla kaydeder →
|
`DynamicAssemblyRegistrationService` assembly'yi tenant bağlamıyla kaydeder →
|
||||||
`ActionDescriptorChangeProvider` MVC'ye route tablosunun değiştiğini bildirir →
|
`ActionDescriptorChangeProvider` MVC'ye route tablosunun değiştiğini bildirir →
|
||||||
|
|
@ -444,14 +890,30 @@ Route.Authority ──┘ └► Permission
|
||||||
- Frontend'de `dynamicRoutesContext` route'ları çeker, `dynamicRouteLoader` bunları fiziksel
|
- Frontend'de `dynamicRoutesContext` route'ları çeker, `dynamicRouteLoader` bunları fiziksel
|
||||||
view'lara veya runtime derlenmiş Custom Component'lere eşler, `DynamicRouter` React Router
|
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.
|
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
|
**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.*`,
|
`PermissionsData.json` ile seed edilir. Ana gruplar: `App.Saas`, `App.Branches`, `App.Intranet.*`,
|
||||||
`App.Definitions.*`, `App.Restrictions.*`, `App.Languages.*`, `App.Listforms.*`,
|
`App.Definitions.*`, `App.Restrictions.*`, `App.Languages.*`, `App.Listforms.*`,
|
||||||
`App.Notifications.*`, `App.BackgroundWorkers.*`, `App.Menus.*`, `App.DeveloperKit.*`,
|
`App.Notifications.*`, `App.BackgroundWorkers.*`, `App.Menus.*`, `App.DeveloperKit.*`,
|
||||||
|
`App.SqlQueryManager.*`,
|
||||||
`App.Orders.*`, `App.BlogManagement.*`, `App.IdentityManagement.*`, `App.Reports.*`,
|
`App.Orders.*`, `App.BlogManagement.*`, `App.IdentityManagement.*`, `App.Reports.*`,
|
||||||
`App.Administration`, `App.Setting`, `App.Setup.Migrate`.
|
`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.
|
> **Kural:** Yetki sözleşmesi olmayan menü/route önerilmez ve eklenmez.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -489,8 +951,8 @@ Route.Authority ──┘ └► Permission
|
||||||
| Dosya yöneticisi | `/admin/files` | Klasör/dosya işlemleri. |
|
| 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ı. |
|
| 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. |
|
| Bildirimler | `/admin/profile/notification-settings`, bildirim ekranları | Kanal tercihleri ve bildirim oluşturma. |
|
||||||
| Forum | `/admin/forum`, `/admin/forumManagement` | Forum kullanımı ve yönetimi. |
|
| 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. |
|
| 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ı. |
|
| 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ı. |
|
| 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ü. |
|
| AI asistanı | `/admin/ai` | n8n webhook üzerinden sohbet arayüzü. |
|
||||||
|
|
@ -498,9 +960,101 @@ Route.Authority ──┘ └► Permission
|
||||||
|
|
||||||
### 10.4. Public site
|
### 10.4. Public site
|
||||||
|
|
||||||
`/home` · `/about` · `/services` · `/products` · `/checkout` · `/payment` · `/success` ·
|
`/home` · `/about` · `/contact` (Custom Component) · `/products` · `/checkout` · `/payment` ·
|
||||||
`/blog` · `/blog/:id` · `/demo` · `/contact` — içerikler veritabanından yönetilir;
|
`/success` · `/blog` · `/blog/:id` · `/demo` (fiziksel sayfalar).
|
||||||
`/about/designer` ve `/services/designer` sayfa tasarımcılarıdır.
|
|
||||||
|
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`
|
||||||
|
açıklama anahtarı.
|
||||||
|
|
||||||
|
Elle yazılmış bir bileşen Visual Designer'da **kod modunda** açı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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -678,6 +1232,9 @@ konfigürasyonu ile üretilir.
|
||||||
|
|
||||||
- Tailwind tabanlı tasarım sistemi (`components/ui`) + DevExtreme temaları; açık/koyu mod ve
|
- Tailwind tabanlı tasarım sistemi (`components/ui`) + DevExtreme temaları; açık/koyu mod ve
|
||||||
tema yapılandırıcı (`ThemeConfigurator`).
|
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.
|
- Dil metinleri veritabanından gelir (`Sozsoft.Languages`); dil seçici header'dadır.
|
||||||
Tenant bazlı çeviri geçersiz kılma desteklenir.
|
Tenant bazlı çeviri geçersiz kılma desteklenir.
|
||||||
- Layout seçenekleri: dikey menü, yatay menü, yığılmış yan menü, mobil navigasyon.
|
- Layout seçenekleri: dikey menü, yatay menü, yığılmış yan menü, mobil navigasyon.
|
||||||
|
|
@ -695,23 +1252,23 @@ konfigürasyonu ile üretilir.
|
||||||
(`views/version/swRegistration.ts`, `sw.ts`, `AppUpdateDialog.tsx`).
|
(`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,
|
- 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.
|
service worker eksik kalan bileşenleri kaldığı yerden tamamlar.
|
||||||
- **Sürüm bilgisi backend'dedir.** `Sas_H_ReleaseNote` tablosu tek kaynaktır; istemci sürüm
|
- **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
|
numarasını ve son sürümün notlarını `application-configuration` yanıtındaki
|
||||||
`extraProperties.changeLogs` alanından okur (`PlatformApplicationConfigurationContributor`),
|
`extraProperties.changeLogs` alanından okur (`PlatformApplicationConfigurationContributor`),
|
||||||
tam listeyi ise `/api/app/release-note` endpoint'inden alır. Ayrı bir `version.json` isteği
|
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
|
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 —
|
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).
|
yalnızca yeni bir git tag'i eklenmiş olsa da çalışır).
|
||||||
- **Sürüm notu akışı — DbMigrator gerektirmez:** `configs/release-notes/generate-release-notes.sh`
|
- **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/release-notes/release-notes.json`
|
deploy sırasında (`deploy/app.sh`) git tag'lerinden `configs/change-logs/change-logs.json`
|
||||||
üretir, dosya API konteynerine `/etc/api/release-notes` altına salt okunur mount edilir ve
|
üretir, dosya API konteynerine `/etc/api/change-logs` altına salt okunur mount edilir ve
|
||||||
`ReleaseNoteStartupService` uygulama açılışında tabloyu bu dosyayla eşitler. Dosya imaja
|
`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 . .`
|
**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
|
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
|
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.
|
**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,
|
- **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_ReleaseNote`
|
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
|
elle düzenlenmemelidir — ilk deploy'da geri alınır. Dosya okunamaz veya boşsa senkronizasyon
|
||||||
hiçbir şey yapmaz, tablo boşaltılmaz.
|
hiçbir şey yapmaz, tablo boşaltılmaz.
|
||||||
- Deploy algılaması service worker'ın kendi `registration.update()` kontrolüne dayanır:
|
- Deploy algılaması service worker'ın kendi `registration.update()` kontrolüne dayanır:
|
||||||
|
|
@ -777,6 +1334,11 @@ Script sırası (`configs/deployment/scripts`):
|
||||||
8-dev-build.sh / 9-dev-deploy.sh / 10-dev-migrator-true.sh → Dev ortamı
|
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),
|
Ayrıntılı kurulum notları: [`configs/deployment/README.md`](configs/deployment/README.md),
|
||||||
[`Readme_Production.md`](configs/deployment/Readme_Production.md),
|
[`Readme_Production.md`](configs/deployment/Readme_Production.md),
|
||||||
[`Readme_Devops.md`](configs/deployment/Readme_Devops.md).
|
[`Readme_Devops.md`](configs/deployment/Readme_Devops.md).
|
||||||
|
|
@ -816,25 +1378,66 @@ dotnet build
|
||||||
dotnet run --project src/Sozsoft.Platform.HttpApi.Host
|
dotnet run --project src/Sozsoft.Platform.HttpApi.Host
|
||||||
dotnet format --include .\modules\Sozsoft.Notifications\ --folder
|
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)
|
# Migration (EntityFrameworkCore projesi içinde)
|
||||||
dotnet ef migrations add <Ad>
|
dotnet ef migrations add <Ad>
|
||||||
dotnet ef database update
|
dotnet ef database update
|
||||||
dotnet ef migrations remove
|
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:
|
Yeni ABP modülü eklemek için:
|
||||||
`abp new Sozsoft.<Modul> -t module --no-ui -m none --database-provider ef`
|
`abp new Sozsoft.<Modul> -t module --no-ui -m none --database-provider ef`
|
||||||
|
|
||||||
### 21.4. Seed dosyaları
|
### 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`
|
**1. Depo içi (kod ile taşınan) seed'ler** — `api/src/Sozsoft.Platform.DbMigrator/Migrations/`.
|
||||||
- `.../Migrations/ListFormSeeder_Administration.cs`
|
Platformun kendi ekranları, menüleri, yetkileri ve dil metinleri buradadır; değişiklikleri
|
||||||
- `.../Migrations/MenusData.json`
|
derlenip yayınlanır. Yeni bir modülün nasıl kurulacağını öğrenmek için başvurulacak dosyalar:
|
||||||
- `.../Migrations/PermissionsData.json`
|
|
||||||
- `.../Migrations/HostData.json`
|
- `ListFormSeeder_Saas.cs`, `ListFormSeeder_Administration.cs` — ListForm + alan tanımları
|
||||||
- `.../Migrations/LanguagesData.json`
|
- `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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -862,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.
|
- `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
|
- Depodaki `appsettings*.json` ve deployment dokümanları geliştirme değerleri/örnek kimlik
|
||||||
bilgileri içerir; üretim öncesi tümü değiştirilmelidir.
|
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;
|
- SQL View Designer tek yönlüdür (model → T-SQL). Elle yazılmış ya da tasarımcının kanonik
|
||||||
kod tabanı **.NET 10 + ABP 10** üzerindedir. Kural bölümleri geçerliliğini korur.
|
ş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.
|
||||||
- Rol–yetki eşleşmeleri veritabanında müşteri ortamına göre değişir; buradaki yetki kodları
|
- Rol–yetki eşleşmeleri veritabanında müşteri ortamına göre değişir; buradaki yetki kodları
|
||||||
tanımın kendisidir, atama değil.
|
tanımın kendisidir, atama değil.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<NoWarn>$(NoWarn);CS0162;CS8321;CS8618;CS8632</NoWarn>
|
<NoWarn>$(NoWarn);CS0162;CS8321;CS8618;CS8632;CS1591;CS1573;CS1572;CS1570;CS1574</NoWarn>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<AnalysisLevel>latest</AnalysisLevel>
|
||||||
|
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using Volo.Abp.Application;
|
using Volo.Abp.Application;
|
||||||
using Volo.Abp.Modularity;
|
|
||||||
using Volo.Abp.Authorization;
|
using Volo.Abp.Authorization;
|
||||||
|
using Volo.Abp.Modularity;
|
||||||
|
|
||||||
namespace Sozsoft.Languages;
|
namespace Sozsoft.Languages;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
namespace Sozsoft.Languages;
|
|
||||||
|
|
||||||
public class LanguagesRemoteServiceConsts
|
|
||||||
{
|
|
||||||
public const string RemoteServiceName = "Languages";
|
|
||||||
|
|
||||||
public const string ModuleName = "languages";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using Volo.Abp.Authorization.Permissions;
|
using Volo.Abp.Authorization.Permissions;
|
||||||
|
|
||||||
namespace Sozsoft.Languages.Permissions;
|
namespace Sozsoft.Languages.Permissions;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
using Sozsoft.Languages.Entities;
|
using System;
|
||||||
using Sozsoft.Languages.Localization;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Sozsoft.Languages.Entities;
|
||||||
|
using Sozsoft.Languages.Localization;
|
||||||
using Volo.Abp.Application.Dtos;
|
using Volo.Abp.Application.Dtos;
|
||||||
using Volo.Abp.Application.Services;
|
using Volo.Abp.Application.Services;
|
||||||
using Volo.Abp.Domain.Entities;
|
using Volo.Abp.Domain.Entities;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
using Sozsoft.Languages.Entities;
|
using System;
|
||||||
using Sozsoft.Languages.Localization;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Sozsoft.Languages.Entities;
|
||||||
|
using Sozsoft.Languages.Localization;
|
||||||
using Volo.Abp.Application.Dtos;
|
using Volo.Abp.Application.Dtos;
|
||||||
using Volo.Abp.Application.Services;
|
using Volo.Abp.Application.Services;
|
||||||
using Volo.Abp.Domain.Repositories;
|
using Volo.Abp.Domain.Repositories;
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
using Sozsoft.Languages.Entities;
|
using System;
|
||||||
using Sozsoft.Languages.Localization;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using StackExchange.Redis;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Dynamic.Core;
|
using System.Linq.Dynamic.Core;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Sozsoft.Languages.Entities;
|
||||||
|
using Sozsoft.Languages.Localization;
|
||||||
|
using StackExchange.Redis;
|
||||||
using Volo.Abp;
|
using Volo.Abp;
|
||||||
using Volo.Abp.Application.Dtos;
|
using Volo.Abp.Application.Dtos;
|
||||||
using Volo.Abp.Application.Services;
|
using Volo.Abp.Application.Services;
|
||||||
|
|
@ -136,7 +136,7 @@ public class LanguageTextAppService : CrudAppService<
|
||||||
var db = redis.GetDatabase();
|
var db = redis.GetDatabase();
|
||||||
await db.ExecuteAsync("FLUSHALL");
|
await db.ExecuteAsync("FLUSHALL");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual IQueryable<LanguageKey> ApplySorting(IQueryable<LanguageKey> query, LanguageTextTranslatedRequestDto input)
|
protected virtual IQueryable<LanguageKey> ApplySorting(IQueryable<LanguageKey> query, LanguageTextTranslatedRequestDto input)
|
||||||
{
|
{
|
||||||
//Try to sort query if available
|
//Try to sort query if available
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
using Riok.Mapperly.Abstractions;
|
||||||
using Sozsoft.Languages.Entities;
|
using Sozsoft.Languages.Entities;
|
||||||
using Volo.Abp.Localization;
|
using Volo.Abp.Localization;
|
||||||
using Riok.Mapperly.Abstractions;
|
|
||||||
using Volo.Abp.Mapperly;
|
using Volo.Abp.Mapperly;
|
||||||
|
|
||||||
namespace Sozsoft.Languages;
|
namespace Sozsoft.Languages;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
namespace Sozsoft.Languages;
|
|
||||||
|
|
||||||
public static class LanguagesErrorCodes
|
|
||||||
{
|
|
||||||
//Add your business exception error codes here...
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,45 +1,23 @@
|
||||||
namespace Sozsoft.Languages.Languages
|
namespace Sozsoft.Languages.Languages;
|
||||||
{
|
|
||||||
public static class LanguageCodes
|
|
||||||
{
|
|
||||||
public const string Ar = "ar";
|
|
||||||
public const string Cs = "cs";
|
|
||||||
public const string De = "de-DE";
|
|
||||||
public const string En = "en";
|
|
||||||
public const string Es = "es";
|
|
||||||
public const string Fi = "fi";
|
|
||||||
public const string Fr = "fr";
|
|
||||||
public const string Hi = "hi";
|
|
||||||
public const string Hr = "hr";
|
|
||||||
public const string Hu = "hu";
|
|
||||||
public const string It = "it";
|
|
||||||
public const string Pt = "pt-BR";
|
|
||||||
public const string Ru = "ru";
|
|
||||||
public const string Sk = "sk";
|
|
||||||
public const string Sl = "sl";
|
|
||||||
public const string Tr = "tr";
|
|
||||||
public const string Zh = "zh-Hans";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class LanguageNames
|
public static class LanguageCodes
|
||||||
{
|
{
|
||||||
public const string Ar = "العربية";
|
public const string Ar = "ar";
|
||||||
public const string Cs = "Čeština";
|
public const string Cs = "cs";
|
||||||
public const string De = "Deutsch";
|
public const string De = "de-DE";
|
||||||
public const string En = "English";
|
public const string En = "en";
|
||||||
public const string Es = "Español";
|
public const string Es = "es";
|
||||||
public const string Fi = "Finnish";
|
public const string Fi = "fi";
|
||||||
public const string Fr = "Français";
|
public const string Fr = "fr";
|
||||||
public const string Hi = "Hindi";
|
public const string Hi = "hi";
|
||||||
public const string Hr = "Croatian";
|
public const string Hr = "hr";
|
||||||
public const string Hu = "Magyar";
|
public const string Hu = "hu";
|
||||||
public const string It = "Italiano";
|
public const string It = "it";
|
||||||
public const string Pt = "Português";
|
public const string Pt = "pt-BR";
|
||||||
public const string Ru = "Русский";
|
public const string Ru = "ru";
|
||||||
public const string Sk = "Slovak";
|
public const string Sk = "sk";
|
||||||
public const string Sl = "Slovenščina";
|
public const string Sl = "sl";
|
||||||
public const string Tr = "Türkçe";
|
public const string Tr = "tr";
|
||||||
public const string Zh = "繁體中文";
|
public const string Zh = "zh-Hans";
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
using Sozsoft.Languages.Entities;
|
using System.Threading.Tasks;
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Volo.Abp.Caching;
|
using Volo.Abp.Caching;
|
||||||
using Volo.Abp.DependencyInjection;
|
using Volo.Abp.DependencyInjection;
|
||||||
using Volo.Abp.Domain.Entities.Events.Distributed;
|
using Volo.Abp.Domain.Entities.Events.Distributed;
|
||||||
using Volo.Abp.Domain.Repositories;
|
|
||||||
using Volo.Abp.EventBus.Distributed;
|
using Volo.Abp.EventBus.Distributed;
|
||||||
|
|
||||||
namespace Sozsoft.Languages;
|
namespace Sozsoft.Languages;
|
||||||
|
|
@ -15,14 +12,11 @@ public class LanguageTextCacheItemInvalidatorDistributed :
|
||||||
IDistributedEventHandler<EntityDeletedEto<LanguageTextEto>>,
|
IDistributedEventHandler<EntityDeletedEto<LanguageTextEto>>,
|
||||||
ITransientDependency
|
ITransientDependency
|
||||||
{
|
{
|
||||||
private readonly IRepository<LanguageText, Guid> languageTextRepository;
|
|
||||||
private readonly IDistributedCache<LanguageTextCacheItem> cache;
|
private readonly IDistributedCache<LanguageTextCacheItem> cache;
|
||||||
|
|
||||||
public LanguageTextCacheItemInvalidatorDistributed(
|
public LanguageTextCacheItemInvalidatorDistributed(
|
||||||
IRepository<LanguageText, Guid> languageTextRepository,
|
|
||||||
IDistributedCache<LanguageTextCacheItem> cache)
|
IDistributedCache<LanguageTextCacheItem> cache)
|
||||||
{
|
{
|
||||||
this.languageTextRepository = languageTextRepository;
|
|
||||||
this.cache = cache;
|
this.cache = cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
using Sozsoft.Languages.Entities;
|
using System;
|
||||||
using Microsoft.Extensions.Localization;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
using Sozsoft.Languages.Entities;
|
||||||
using Volo.Abp.Caching;
|
using Volo.Abp.Caching;
|
||||||
using Volo.Abp.DependencyInjection;
|
using Volo.Abp.DependencyInjection;
|
||||||
using Volo.Abp.Domain.Repositories;
|
using Volo.Abp.Domain.Repositories;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using Microsoft.Extensions.Localization;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
using Volo.Abp.Localization;
|
using Volo.Abp.Localization;
|
||||||
|
|
||||||
namespace Sozsoft.Languages.Localization;
|
namespace Sozsoft.Languages.Localization;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
using Sozsoft.Languages.Entities;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Sozsoft.Languages.Entities;
|
||||||
using Volo.Abp.Data;
|
using Volo.Abp.Data;
|
||||||
using Volo.Abp.EntityFrameworkCore;
|
using Volo.Abp.EntityFrameworkCore;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
using Sozsoft.Languages.Entities;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Sozsoft.Languages.Entities;
|
||||||
using Volo.Abp;
|
using Volo.Abp;
|
||||||
using Volo.Abp.EntityFrameworkCore.Modeling;
|
using Volo.Abp.EntityFrameworkCore.Modeling;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
namespace Sozsoft.MailQueue.Application;
|
namespace Sozsoft.MailQueue.Application;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using Sozsoft.MailQueue.FileGeneration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Sozsoft.MailQueue.FileGeneration;
|
||||||
using Sozsoft.MailQueue.MailGeneration.Models;
|
using Sozsoft.MailQueue.MailGeneration.Models;
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Volo.Abp.DependencyInjection;
|
using Volo.Abp.DependencyInjection;
|
||||||
using Volo.Abp.Guids;
|
using Volo.Abp.Guids;
|
||||||
using Volo.Abp.TextTemplating;
|
using Volo.Abp.TextTemplating;
|
||||||
|
|
@ -141,53 +141,53 @@ public class AttachmentGenerator : IAttachmentGenerator, ITransientDependency
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
// if (model.Filtreler.ContainsKey("QUERY") && model.Filtreler.ContainsKey("QUERYSQL"))
|
// if (model.Filtreler.ContainsKey("QUERY") && model.Filtreler.ContainsKey("QUERYSQL"))
|
||||||
// {
|
// {
|
||||||
// /*
|
// /*
|
||||||
//Parametrelerden asagidaki sekilde gelen queryleri en alttaki formata donusturuyoruz
|
//Parametrelerden asagidaki sekilde gelen queryleri en alttaki formata donusturuyoruz
|
||||||
//params
|
//params
|
||||||
//0 =>
|
//0 =>
|
||||||
// QUERY -> qryDepo_Cikis_CekiListesi_Detayli
|
// QUERY -> qryDepo_Cikis_CekiListesi_Detayli
|
||||||
// QUERYSQL -> Ksp_Depo_Cikis_CekiListesi_Detayli 252993
|
// QUERYSQL -> Ksp_Depo_Cikis_CekiListesi_Detayli 252993
|
||||||
// QUERY2 -> qryDepo_Cikis_CekiListesi_Sub
|
// QUERY2 -> qryDepo_Cikis_CekiListesi_Sub
|
||||||
// QUERY2SQL -> KSp_Depo_Cikis_CekiListesi_Sub 252993
|
// QUERY2SQL -> KSp_Depo_Cikis_CekiListesi_Sub 252993
|
||||||
|
|
||||||
//SQL olan keyvalue'lari cikar ve sadece querylerde foreach yap
|
|
||||||
// QUERY -> qryDepo_Cikis_CekiListesi_Detayli
|
|
||||||
// dict.add(item.Value, params[item.Key + "SQL"].Value)
|
|
||||||
// QUERY2 -> qryDepo_Cikis_CekiListesi_Sub
|
|
||||||
// dict.add(item.Value, params[item.Key + "SQL"].Value)
|
|
||||||
|
|
||||||
//Bunlari yeni dict'e ekle
|
//SQL olan keyvalue'lari cikar ve sadece querylerde foreach yap
|
||||||
//dict
|
// QUERY -> qryDepo_Cikis_CekiListesi_Detayli
|
||||||
//qryDepo_Cikis_CekiListesi_Detayli -> Ksp_Depo_Cikis_CekiListesi_Detayli 252993
|
// dict.add(item.Value, params[item.Key + "SQL"].Value)
|
||||||
//qryDepo_Cikis_CekiListesi_Sub -> KSp_Depo_Cikis_CekiListesi_Sub 252993
|
// QUERY2 -> qryDepo_Cikis_CekiListesi_Sub
|
||||||
// */
|
// dict.add(item.Value, params[item.Key + "SQL"].Value)
|
||||||
// //dict -> key (name), value (sql)
|
|
||||||
// var queries = model.Filtreler
|
|
||||||
// .Where(a => a.Key.StartsWith("QUERY"))
|
|
||||||
// .Where(a => !a.Key.EndsWith("SQL"))
|
|
||||||
// .Where(a => !a.Value.IsNullOrWhiteSpace())
|
|
||||||
// .ToDictionary(x => x.Value, x => model.Filtreler.GetValueOrDefault(x.Key + "SQL"));
|
|
||||||
|
|
||||||
// return MsAccessReportManager.GenerateAccessReport(
|
//Bunlari yeni dict'e ekle
|
||||||
// model.DbAdi,
|
//dict
|
||||||
// model.Index,
|
//qryDepo_Cikis_CekiListesi_Detayli -> Ksp_Depo_Cikis_CekiListesi_Detayli 252993
|
||||||
// model.RaporAdi,
|
//qryDepo_Cikis_CekiListesi_Sub -> KSp_Depo_Cikis_CekiListesi_Sub 252993
|
||||||
// model.ExportDosyaTipi,
|
// */
|
||||||
// queries,
|
// //dict -> key (name), value (sql)
|
||||||
// attachmentFolder);
|
// var queries = model.Filtreler
|
||||||
// }
|
// .Where(a => a.Key.StartsWith("QUERY"))
|
||||||
// else
|
// .Where(a => !a.Key.EndsWith("SQL"))
|
||||||
// {
|
// .Where(a => !a.Value.IsNullOrWhiteSpace())
|
||||||
// return MsAccessReportManager.GenerateAccessReport(
|
// .ToDictionary(x => x.Value, x => model.Filtreler.GetValueOrDefault(x.Key + "SQL"));
|
||||||
// model.DbAdi,
|
|
||||||
// model.Index,
|
// return MsAccessReportManager.GenerateAccessReport(
|
||||||
// model.RaporAdi,
|
// model.DbAdi,
|
||||||
// string.Join(" AND ", model.Filtreler.Select(a => $"{a.Key}={a.Value}")),
|
// model.Index,
|
||||||
// model.ExportDosyaTipi,
|
// model.RaporAdi,
|
||||||
// attachmentFolder);
|
// model.ExportDosyaTipi,
|
||||||
// }
|
// queries,
|
||||||
|
// attachmentFolder);
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// return MsAccessReportManager.GenerateAccessReport(
|
||||||
|
// model.DbAdi,
|
||||||
|
// model.Index,
|
||||||
|
// model.RaporAdi,
|
||||||
|
// string.Join(" AND ", model.Filtreler.Select(a => $"{a.Key}={a.Value}")),
|
||||||
|
// model.ExportDosyaTipi,
|
||||||
|
// attachmentFolder);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> ConvertFileToBase64Async(string filePath)
|
private async Task<string> ConvertFileToBase64Async(string filePath)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
using Sozsoft.MailQueue.MailGeneration.Models;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Sozsoft.MailQueue.MailGeneration.Models;
|
||||||
using Volo.Abp.DependencyInjection;
|
using Volo.Abp.DependencyInjection;
|
||||||
using Volo.Abp.TextTemplating;
|
using Volo.Abp.TextTemplating;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
using System.Data;
|
using System.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Sozsoft.MailQueue.Domain.Entities;
|
using Sozsoft.MailQueue.Domain.Entities;
|
||||||
using Sozsoft.MailQueue.EntityFrameworkCore;
|
using Sozsoft.MailQueue.EntityFrameworkCore;
|
||||||
using Sozsoft.MailQueue.MailGeneration.Models;
|
using Sozsoft.MailQueue.MailGeneration.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Volo.Abp.DependencyInjection;
|
using Volo.Abp.DependencyInjection;
|
||||||
using Volo.Abp.Domain.Repositories;
|
using Volo.Abp.Domain.Repositories;
|
||||||
using Volo.Abp.TextTemplating;
|
using Volo.Abp.TextTemplating;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
using Sozsoft.Sender.Mail;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Sozsoft.MailQueue.Domain.MailGeneration;
|
using Sozsoft.MailQueue.Domain.MailGeneration;
|
||||||
using Sozsoft.MailQueue.Domain.Shared;
|
using Sozsoft.MailQueue.Domain.Shared;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Sozsoft.Sender.Mail;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Volo.Abp.BackgroundWorkers;
|
using Volo.Abp.BackgroundWorkers;
|
||||||
using Volo.Abp.Domain.Repositories;
|
using Volo.Abp.Domain.Repositories;
|
||||||
using Volo.Abp.Guids;
|
using Volo.Abp.Guids;
|
||||||
|
|
@ -182,7 +182,7 @@ public class MailQueueWorker : BackgroundWorkerBase
|
||||||
}
|
}
|
||||||
queue.SendStatus = result.Success;
|
queue.SendStatus = result.Success;
|
||||||
queue.SendTime = Clock.Now;
|
queue.SendTime = Clock.Now;
|
||||||
await Repository.UpdateAsync(queue);
|
await Repository.UpdateAsync(queue, cancellationToken: cancellationToken);
|
||||||
|
|
||||||
// Attachmentlari silebiliriz
|
// Attachmentlari silebiliriz
|
||||||
try
|
try
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
using Amazon;
|
using Amazon;
|
||||||
using Amazon.SQS;
|
using Amazon.SQS;
|
||||||
using Amazon.SQS.Model;
|
using Amazon.SQS.Model;
|
||||||
using Sozsoft.Sender.Mail.AmazonSes;
|
|
||||||
using Sozsoft.MailQueue.Domain.Entities;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Sozsoft.MailQueue.Domain.Entities;
|
||||||
|
using Sozsoft.Sender.Mail.AmazonSes;
|
||||||
using Volo.Abp.Domain.Repositories;
|
using Volo.Abp.Domain.Repositories;
|
||||||
using Volo.Abp.Domain.Services;
|
using Volo.Abp.Domain.Services;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
using Sozsoft.MailQueue.Domain;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Sozsoft.MailQueue.Domain;
|
||||||
using Volo.Abp.Data;
|
using Volo.Abp.Data;
|
||||||
using Volo.Abp.EntityFrameworkCore;
|
using Volo.Abp.EntityFrameworkCore;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using Sozsoft.MailQueue.Domain;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Sozsoft.MailQueue.Domain;
|
||||||
using Sozsoft.MailQueue.Domain.Entities;
|
using Sozsoft.MailQueue.Domain.Entities;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Volo.Abp;
|
using Volo.Abp;
|
||||||
using Volo.Abp.EntityFrameworkCore.Modeling;
|
using Volo.Abp.EntityFrameworkCore.Modeling;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using Sozsoft.MailQueue.Domain.Entities;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Sozsoft.MailQueue.Domain.Entities;
|
||||||
using Volo.Abp.DependencyInjection;
|
using Volo.Abp.DependencyInjection;
|
||||||
using Volo.Abp.Domain.Repositories;
|
using Volo.Abp.Domain.Repositories;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using ClosedXML.Excel;
|
using ClosedXML.Excel;
|
||||||
using DocumentFormat.OpenXml.Spreadsheet;
|
|
||||||
using Sozsoft.MailQueue.MailGeneration.Models;
|
using Sozsoft.MailQueue.MailGeneration.Models;
|
||||||
using Volo.Abp.DependencyInjection;
|
using Volo.Abp.DependencyInjection;
|
||||||
|
|
||||||
|
|
@ -26,7 +25,7 @@ public class XLSFileGenerator : IFileGenerator, ITransientDependency
|
||||||
using var stream = new MemoryStream();
|
using var stream = new MemoryStream();
|
||||||
workbook.SaveAs(stream);
|
workbook.SaveAs(stream);
|
||||||
stream.Close();
|
stream.Close();
|
||||||
|
|
||||||
return await Task.FromResult(stream.ToArray());
|
return await Task.FromResult(stream.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,8 +79,11 @@ public class XLSFileGenerator : IFileGenerator, ITransientDependency
|
||||||
var col = table.TabloSutunlari[j];
|
var col = table.TabloSutunlari[j];
|
||||||
var cell = sheet.Cell(i + 2, j + 1);
|
var cell = sheet.Cell(i + 2, j + 1);
|
||||||
var value = row.GetValueOrDefault(col.SutunAdi);
|
var value = row.GetValueOrDefault(col.SutunAdi);
|
||||||
var dataType = XLDataType.Text;
|
if (!Enum.TryParse(col.VeriTipi, out XLDataType dataType))
|
||||||
Enum.TryParse(col.VeriTipi, out dataType);
|
{
|
||||||
|
dataType = XLDataType.Text;
|
||||||
|
}
|
||||||
|
|
||||||
switch (dataType)
|
switch (dataType)
|
||||||
{
|
{
|
||||||
case XLDataType.Number:
|
case XLDataType.Number:
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Sozsoft.Sender;
|
|
||||||
using Sozsoft.MailQueue.Domain.Shared;
|
|
||||||
using Sozsoft.MailQueue.EntityFrameworkCore;
|
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Sozsoft.MailQueue.Domain.Shared;
|
||||||
|
using Sozsoft.MailQueue.EntityFrameworkCore;
|
||||||
|
using Sozsoft.Sender;
|
||||||
using Volo.Abp.Application;
|
using Volo.Abp.Application;
|
||||||
using Volo.Abp.Authorization;
|
using Volo.Abp.Authorization;
|
||||||
using Volo.Abp.Mapperly;
|
|
||||||
using Volo.Abp.Domain;
|
using Volo.Abp.Domain;
|
||||||
using Volo.Abp.EntityFrameworkCore;
|
using Volo.Abp.EntityFrameworkCore;
|
||||||
using Volo.Abp.Localization;
|
using Volo.Abp.Localization;
|
||||||
using Volo.Abp.Localization.ExceptionHandling;
|
using Volo.Abp.Localization.ExceptionHandling;
|
||||||
|
using Volo.Abp.Mapperly;
|
||||||
using Volo.Abp.Modularity;
|
using Volo.Abp.Modularity;
|
||||||
using Volo.Abp.TextTemplating.Razor;
|
using Volo.Abp.TextTemplating.Razor;
|
||||||
using Volo.Abp.VirtualFileSystem;
|
using Volo.Abp.VirtualFileSystem;
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,6 @@ public class NotificationFilterRequestDto : PagedAndSortedResultRequestDto
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// İstek; aktivite sayfasındaki liste ekranından mı geliyor yoksa bildirimleri kontrol etmek için mi
|
/// İstek; aktivite sayfasındaki liste ekranından mı geliyor yoksa bildirimleri kontrol etmek için mi
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsListRequest { get; set; } = false;
|
public bool IsListRequest { get; set; } = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
namespace Sozsoft.Notifications.Application;
|
|
||||||
|
|
||||||
public class NotificationRemoteServiceConsts
|
|
||||||
{
|
|
||||||
public const string RemoteServiceName = "Notifications";
|
|
||||||
|
|
||||||
public const string ModuleName = "notifications";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using Volo.Abp.Authorization.Permissions;
|
using Volo.Abp.Authorization.Permissions;
|
||||||
|
|
||||||
namespace Sozsoft.Notifications.Permissions;
|
namespace Sozsoft.Notifications.Permissions;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Sozsoft.Notifications.Domain;
|
using Sozsoft.Notifications.Domain;
|
||||||
using Sozsoft.Notifications.Entities;
|
using Sozsoft.Notifications.Entities;
|
||||||
using Sozsoft.Notifications.Enums;
|
using Sozsoft.Notifications.Enums;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Volo.Abp;
|
using Volo.Abp;
|
||||||
using Volo.Abp.Application.Services;
|
using Volo.Abp.Application.Services;
|
||||||
using Volo.Abp.Domain.Entities;
|
using Volo.Abp.Domain.Entities;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
using Sozsoft.Notifications.Entities;
|
using Riok.Mapperly.Abstractions;
|
||||||
using Sozsoft.Notifications.Domain;
|
using Sozsoft.Notifications.Domain;
|
||||||
|
using Sozsoft.Notifications.Entities;
|
||||||
using Sozsoft.Notifications.NotificationRules;
|
using Sozsoft.Notifications.NotificationRules;
|
||||||
using Riok.Mapperly.Abstractions;
|
|
||||||
using Volo.Abp.Mapperly;
|
using Volo.Abp.Mapperly;
|
||||||
|
|
||||||
namespace Sozsoft.Notifications.Application;
|
namespace Sozsoft.Notifications.Application;
|
||||||
|
|
@ -9,7 +9,7 @@ namespace Sozsoft.Notifications.Application;
|
||||||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
||||||
public partial class NotificationTypeToNotificationTypeDtoMapper : MapperBase<NotificationType, NotificationTypeDto>
|
public partial class NotificationTypeToNotificationTypeDtoMapper : MapperBase<NotificationType, NotificationTypeDto>
|
||||||
{
|
{
|
||||||
public override partial NotificationTypeDto Map(NotificationType source);
|
public override partial NotificationTypeDto Map(NotificationType source);
|
||||||
|
|
||||||
public override partial void Map(NotificationType source, NotificationTypeDto destination);
|
public override partial void Map(NotificationType source, NotificationTypeDto destination);
|
||||||
|
|
||||||
|
|
@ -25,7 +25,7 @@ public override partial NotificationTypeDto Map(NotificationType source);
|
||||||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
||||||
public partial class NotificationRuleToNotificationRuleDtoMapper : MapperBase<NotificationRule, NotificationRuleDto>
|
public partial class NotificationRuleToNotificationRuleDtoMapper : MapperBase<NotificationRule, NotificationRuleDto>
|
||||||
{
|
{
|
||||||
public override partial NotificationRuleDto Map(NotificationRule source);
|
public override partial NotificationRuleDto Map(NotificationRule source);
|
||||||
|
|
||||||
public override partial void Map(NotificationRule source, NotificationRuleDto destination);
|
public override partial void Map(NotificationRule source, NotificationRuleDto destination);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
using Sozsoft.Notifications.Domain;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Sozsoft.Notifications.Domain;
|
||||||
using Volo.Abp.Application;
|
using Volo.Abp.Application;
|
||||||
using Volo.Abp.Mapperly;
|
using Volo.Abp.Mapperly;
|
||||||
using Volo.Abp.Modularity;
|
using Volo.Abp.Modularity;
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Sozsoft.Notifications.Domain;
|
|
||||||
using Sozsoft.Notifications.Entities;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Sozsoft.Notifications.Domain;
|
||||||
|
using Sozsoft.Notifications.Entities;
|
||||||
using Volo.Abp.Application.Dtos;
|
using Volo.Abp.Application.Dtos;
|
||||||
using Volo.Abp.Application.Services;
|
using Volo.Abp.Application.Services;
|
||||||
using Volo.Abp.Domain.Repositories;
|
using Volo.Abp.Domain.Repositories;
|
||||||
|
|
@ -23,22 +23,16 @@ public class NotificationRuleAppService : CrudAppService<
|
||||||
INotificationRuleAppService
|
INotificationRuleAppService
|
||||||
{
|
{
|
||||||
private readonly IRepository<NotificationType, Guid> repositoryType;
|
private readonly IRepository<NotificationType, Guid> repositoryType;
|
||||||
private readonly IOrganizationUnitRepository repositoryOrganizationUnit;
|
|
||||||
private readonly IdentityUserManager userManager;
|
|
||||||
private readonly IIdentityUserRepository repositoryUser;
|
private readonly IIdentityUserRepository repositoryUser;
|
||||||
|
|
||||||
public NotificationRuleAppService(
|
public NotificationRuleAppService(
|
||||||
IRepository<NotificationType, Guid> repositoryType,
|
IRepository<NotificationType, Guid> repositoryType,
|
||||||
IRepository<NotificationRule, Guid> repository,
|
IRepository<NotificationRule, Guid> repository,
|
||||||
IdentityUserManager userManager,
|
IIdentityUserRepository repositoryUser
|
||||||
IIdentityUserRepository repositoryUser,
|
|
||||||
IOrganizationUnitRepository repositoryOrganizationUnit
|
|
||||||
) : base(repository)
|
) : base(repository)
|
||||||
{
|
{
|
||||||
this.repositoryType = repositoryType;
|
this.repositoryType = repositoryType;
|
||||||
this.userManager = userManager;
|
|
||||||
this.repositoryUser = repositoryUser;
|
this.repositoryUser = repositoryUser;
|
||||||
this.repositoryOrganizationUnit = repositoryOrganizationUnit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string[]> GetNotificationTypes()
|
public async Task<string[]> GetNotificationTypes()
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ public static class NotificationChannels
|
||||||
public const string Sms = "Sms"; //SMS (ABP Sms + Posta Guvercini)
|
public const string Sms = "Sms"; //SMS (ABP Sms + Posta Guvercini)
|
||||||
public const string Mail = "Mail"; //Email (ABP Emailing + Amazon SES)
|
public const string Mail = "Mail"; //Email (ABP Emailing + Amazon SES)
|
||||||
public const string Rocket = "Rocket"; //Rocket.Chat (HTTP API)
|
public const string Rocket = "Rocket"; //Rocket.Chat (HTTP API)
|
||||||
public const string Desktop = "Desktop";
|
public const string Desktop = "Desktop";
|
||||||
public const string UiActivity = "UiActivity"; //UI Activity (ABP UI Activity)
|
public const string UiActivity = "UiActivity"; //UI Activity (ABP UI Activity)
|
||||||
public const string UiToast = "UiToast"; //UI Toast (ABP UI Toast) Ayarlar/Sistem/Bildirimler/Chrome açık olması gerekiyor.
|
public const string UiToast = "UiToast"; //UI Toast (ABP UI Toast) Ayarlar/Sistem/Bildirimler/Chrome açık olması gerekiyor.
|
||||||
public const string WhatsApp = "WhatsApp"; //WhatsApp (HTTP API, template-based)
|
public const string WhatsApp = "WhatsApp"; //WhatsApp (HTTP API, template-based)
|
||||||
// public const string Telegram = "Telegram";
|
public const string Telegram = "Telegram"; //Telegram (Bot API, chat based)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
namespace Sozsoft.Notifications.Domain;
|
|
||||||
|
|
||||||
public static class NotificationErrorCodes
|
|
||||||
{
|
|
||||||
//Add your business exception error codes here...
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using Volo.Abp.Domain.Entities.Auditing;
|
using Volo.Abp.Domain.Entities.Auditing;
|
||||||
|
|
||||||
namespace Sozsoft.Notifications.Entities;
|
namespace Sozsoft.Notifications.Entities;
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ namespace Sozsoft.Notifications.Entities;
|
||||||
public class NotificationRule : FullAuditedEntity<Guid>
|
public class NotificationRule : FullAuditedEntity<Guid>
|
||||||
{
|
{
|
||||||
public required Guid NotificationTypeId { get; set; }
|
public required Guid NotificationTypeId { get; set; }
|
||||||
public NotificationType NotificationType { get; set; }
|
public NotificationType NotificationType { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Alıcı Tipi: User, Role, Organization, All, Custom
|
/// Alıcı Tipi: User, Role, Organization, All, Custom
|
||||||
/// User ise RecipientId UserId olacak
|
/// User ise RecipientId UserId olacak
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ public class NotificationIdentifierProvider : INotificationIdentifierProvider
|
||||||
NotificationChannels.UiActivity => user.Id.ToString(),
|
NotificationChannels.UiActivity => user.Id.ToString(),
|
||||||
NotificationChannels.UiToast => user.Id.ToString(),
|
NotificationChannels.UiToast => user.Id.ToString(),
|
||||||
NotificationChannels.WhatsApp => user.PhoneNumber,
|
NotificationChannels.WhatsApp => user.PhoneNumber,
|
||||||
// NotificationChannels.Telegram => user.PhoneNumber,
|
NotificationChannels.Telegram => user.GetProperty<string?>("TelegramChatId"),
|
||||||
_ => null,
|
_ => null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Sozsoft.Notifications.Entities;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Sozsoft.Notifications.Entities;
|
||||||
using Volo.Abp.DependencyInjection;
|
using Volo.Abp.DependencyInjection;
|
||||||
using Volo.Abp.Domain.Repositories;
|
using Volo.Abp.Domain.Repositories;
|
||||||
using Volo.Abp.Identity;
|
using Volo.Abp.Identity;
|
||||||
|
|
@ -192,9 +192,9 @@ public class NotificationManager : INotificationManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var notificationType = await repositoryType.FirstOrDefaultAsync(a => a.Name == NotificationType)
|
var notificationType = await repositoryType.FirstOrDefaultAsync(a => a.Name == NotificationType)
|
||||||
?? throw new Exception($"NotificationType '{NotificationType}' not found.");
|
?? throw new Exception($"NotificationType '{NotificationType}' not found.");
|
||||||
|
|
||||||
await repositoryNotification.InsertAsync(new Notification
|
await repositoryNotification.InsertAsync(new Notification
|
||||||
{
|
{
|
||||||
NotificationTypeId = notificationType.Id,
|
NotificationTypeId = notificationType.Id,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using Sozsoft.Notifications.Domain;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Sozsoft.Notifications.Domain;
|
||||||
using Sozsoft.Notifications.Entities;
|
using Sozsoft.Notifications.Entities;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Volo.Abp.Data;
|
using Volo.Abp.Data;
|
||||||
using Volo.Abp.EntityFrameworkCore;
|
using Volo.Abp.EntityFrameworkCore;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
using Sozsoft.Notifications.Domain;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Sozsoft.Notifications.Domain;
|
||||||
using Volo.Abp.EntityFrameworkCore;
|
using Volo.Abp.EntityFrameworkCore;
|
||||||
using Volo.Abp.Modularity;
|
using Volo.Abp.Modularity;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
using Volo.Abp.Localization;
|
using Volo.Abp.Localization;
|
||||||
|
|
||||||
namespace Sozsoft.Sender.Localization
|
namespace Sozsoft.Sender.Localization;
|
||||||
|
|
||||||
|
[LocalizationResourceName("SozsoftSender")]
|
||||||
|
public class ErpSenderResource
|
||||||
{
|
{
|
||||||
[LocalizationResourceName("SozsoftSender")]
|
|
||||||
public class ErpSenderResource
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using Volo.Abp.BackgroundJobs;
|
using Volo.Abp.BackgroundJobs;
|
||||||
using Volo.Abp.DependencyInjection;
|
using Volo.Abp.DependencyInjection;
|
||||||
using Volo.Abp.MultiTenancy;
|
using Volo.Abp.MultiTenancy;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using Volo.Abp.MultiTenancy;
|
using Volo.Abp.MultiTenancy;
|
||||||
|
|
||||||
namespace Sozsoft.Sender.Mail;
|
namespace Sozsoft.Sender.Mail;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Volo.Abp.Settings;
|
using Volo.Abp.Settings;
|
||||||
|
|
||||||
namespace Sozsoft.Sender.Rocket;
|
namespace Sozsoft.Sender.Rocket;
|
||||||
|
|
@ -12,16 +11,13 @@ public interface IRocketHttpClient
|
||||||
public sealed class RocketHttpClient : IRocketHttpClient, IDisposable
|
public sealed class RocketHttpClient : IRocketHttpClient, IDisposable
|
||||||
{
|
{
|
||||||
private readonly HttpClient httpClient;
|
private readonly HttpClient httpClient;
|
||||||
private readonly ILogger<RocketHttpClient> logger;
|
|
||||||
private readonly ISettingProvider settingProvider;
|
private readonly ISettingProvider settingProvider;
|
||||||
|
|
||||||
public RocketHttpClient(
|
public RocketHttpClient(
|
||||||
HttpClient httpClient,
|
HttpClient httpClient,
|
||||||
ILogger<RocketHttpClient> logger,
|
|
||||||
ISettingProvider settingProvider)
|
ISettingProvider settingProvider)
|
||||||
{
|
{
|
||||||
this.httpClient = httpClient;
|
this.httpClient = httpClient;
|
||||||
this.logger = logger;
|
|
||||||
this.settingProvider = settingProvider;
|
this.settingProvider = settingProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using Microsoft.Extensions.Logging;
|
using Volo.Abp.Settings;
|
||||||
using Volo.Abp.Settings;
|
|
||||||
|
|
||||||
namespace Sozsoft.Sender.Sms.PostaGuvercini;
|
namespace Sozsoft.Sender.Sms.PostaGuvercini;
|
||||||
|
|
||||||
|
|
@ -11,16 +10,13 @@ public interface IPostaGuverciniHttpClient
|
||||||
public sealed class PostaGuverciniHttpClient : IPostaGuverciniHttpClient, IDisposable
|
public sealed class PostaGuverciniHttpClient : IPostaGuverciniHttpClient, IDisposable
|
||||||
{
|
{
|
||||||
private readonly HttpClient httpClient;
|
private readonly HttpClient httpClient;
|
||||||
private readonly ILogger<PostaGuverciniHttpClient> logger;
|
|
||||||
private readonly ISettingProvider settingProvider;
|
private readonly ISettingProvider settingProvider;
|
||||||
|
|
||||||
public PostaGuverciniHttpClient(
|
public PostaGuverciniHttpClient(
|
||||||
HttpClient httpClient,
|
HttpClient httpClient,
|
||||||
ILogger<PostaGuverciniHttpClient> logger,
|
|
||||||
ISettingProvider settingProvider)
|
ISettingProvider settingProvider)
|
||||||
{
|
{
|
||||||
this.httpClient = httpClient;
|
this.httpClient = httpClient;
|
||||||
this.logger = logger;
|
|
||||||
this.settingProvider = settingProvider;
|
this.settingProvider = settingProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
using Sozsoft.Sender.Localization;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Sozsoft.Sender.Localization;
|
||||||
using Sozsoft.Sender.Mail;
|
using Sozsoft.Sender.Mail;
|
||||||
using Sozsoft.Sender.Mail.AmazonSes;
|
using Sozsoft.Sender.Mail.AmazonSes;
|
||||||
using Sozsoft.Sender.Rocket;
|
using Sozsoft.Sender.Rocket;
|
||||||
using Sozsoft.Sender.Sms;
|
using Sozsoft.Sender.Sms;
|
||||||
using Sozsoft.Sender.Sms.PostaGuvercini;
|
using Sozsoft.Sender.Sms.PostaGuvercini;
|
||||||
|
using Sozsoft.Sender.Telegram;
|
||||||
using Sozsoft.Sender.WhatsApp;
|
using Sozsoft.Sender.WhatsApp;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Volo.Abp.BackgroundJobs;
|
using Volo.Abp.BackgroundJobs;
|
||||||
using Volo.Abp.Emailing;
|
using Volo.Abp.Emailing;
|
||||||
using Volo.Abp.Localization;
|
using Volo.Abp.Localization;
|
||||||
|
|
@ -36,6 +37,7 @@ public class ErpSenderModule : AbpModule
|
||||||
context.Services.AddScoped<ISozsoftSmsSender>(sp => sp.GetRequiredService<PostaGuverciniSmsSender>());
|
context.Services.AddScoped<ISozsoftSmsSender>(sp => sp.GetRequiredService<PostaGuverciniSmsSender>());
|
||||||
context.Services.AddScoped<ISozsoftRocketSender>(sp => sp.GetRequiredService<RocketSender>());
|
context.Services.AddScoped<ISozsoftRocketSender>(sp => sp.GetRequiredService<RocketSender>());
|
||||||
context.Services.AddScoped<ISozsoftWhatsAppSender>(sp => sp.GetRequiredService<SozsoftWhatsAppSender>());
|
context.Services.AddScoped<ISozsoftWhatsAppSender>(sp => sp.GetRequiredService<SozsoftWhatsAppSender>());
|
||||||
|
context.Services.AddScoped<ISozsoftTelegramSender>(sp => sp.GetRequiredService<SozsoftTelegramSender>());
|
||||||
|
|
||||||
context.Services.AddHttpClient<PostaGuverciniHttpClient>();
|
context.Services.AddHttpClient<PostaGuverciniHttpClient>();
|
||||||
context.Services.AddScoped<IPostaGuverciniHttpClient>(sp => sp.GetRequiredService<PostaGuverciniHttpClient>());
|
context.Services.AddScoped<IPostaGuverciniHttpClient>(sp => sp.GetRequiredService<PostaGuverciniHttpClient>());
|
||||||
|
|
@ -46,6 +48,9 @@ public class ErpSenderModule : AbpModule
|
||||||
context.Services.AddHttpClient<WhatsAppHttpClient>();
|
context.Services.AddHttpClient<WhatsAppHttpClient>();
|
||||||
context.Services.AddScoped<IWhatsAppHttpClient>(sp => sp.GetRequiredService<WhatsAppHttpClient>());
|
context.Services.AddScoped<IWhatsAppHttpClient>(sp => sp.GetRequiredService<WhatsAppHttpClient>());
|
||||||
|
|
||||||
|
context.Services.AddHttpClient<TelegramHttpClient>();
|
||||||
|
context.Services.AddScoped<ITelegramHttpClient>(sp => sp.GetRequiredService<TelegramHttpClient>());
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
44
api/modules/Sozsoft.Sender/Telegram/SozsoftTelegramSender.cs
Normal file
44
api/modules/Sozsoft.Sender/Telegram/SozsoftTelegramSender.cs
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
44
api/modules/Sozsoft.Sender/Telegram/TelegramHttpClient.cs
Normal file
44
api/modules/Sozsoft.Sender/Telegram/TelegramHttpClient.cs
Normal 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();
|
||||||
|
}
|
||||||
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Sozsoft.Sender.Telegram;
|
||||||
|
|
||||||
|
public class TelegramSendResult
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
}
|
||||||
23
api/modules/Sozsoft.Sender/Telegram/TelegramSendResultDto.cs
Normal file
23
api/modules/Sozsoft.Sender/Telegram/TelegramSendResultDto.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Volo.Abp.Settings;
|
using Volo.Abp.Settings;
|
||||||
|
|
||||||
namespace Sozsoft.Sender.WhatsApp;
|
namespace Sozsoft.Sender.WhatsApp;
|
||||||
|
|
@ -13,16 +12,13 @@ public interface IWhatsAppHttpClient
|
||||||
public sealed class WhatsAppHttpClient : IWhatsAppHttpClient, IDisposable
|
public sealed class WhatsAppHttpClient : IWhatsAppHttpClient, IDisposable
|
||||||
{
|
{
|
||||||
private readonly HttpClient httpClient;
|
private readonly HttpClient httpClient;
|
||||||
private readonly ILogger<WhatsAppHttpClient> logger;
|
|
||||||
private readonly ISettingProvider settingProvider;
|
private readonly ISettingProvider settingProvider;
|
||||||
|
|
||||||
public WhatsAppHttpClient(
|
public WhatsAppHttpClient(
|
||||||
HttpClient httpClient,
|
HttpClient httpClient,
|
||||||
ILogger<WhatsAppHttpClient> logger,
|
|
||||||
ISettingProvider settingProvider)
|
ISettingProvider settingProvider)
|
||||||
{
|
{
|
||||||
this.httpClient = httpClient;
|
this.httpClient = httpClient;
|
||||||
this.logger = logger;
|
|
||||||
this.settingProvider = settingProvider;
|
this.settingProvider = settingProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using Volo.Abp.Authorization.Permissions;
|
using Volo.Abp.Authorization.Permissions;
|
||||||
|
|
||||||
namespace Sozsoft.Settings.Permissions;
|
namespace Sozsoft.Settings.Permissions;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
namespace Sozsoft.Settings;
|
|
||||||
|
|
||||||
public class SettingsRemoteServiceConsts
|
|
||||||
{
|
|
||||||
public const string RemoteServiceName = "Settings";
|
|
||||||
|
|
||||||
public const string ModuleName = "settings";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Sozsoft.Languages;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Volo.Abp.Application.Services;
|
using Volo.Abp.Application.Services;
|
||||||
using Volo.Abp.MultiTenancy;
|
using Volo.Abp.MultiTenancy;
|
||||||
|
|
@ -15,20 +14,17 @@ namespace Sozsoft.Settings;
|
||||||
public class SettingUiAppService : ApplicationService, ISettingUiAppService
|
public class SettingUiAppService : ApplicationService, ISettingUiAppService
|
||||||
{
|
{
|
||||||
//private readonly IRepository<SettingDefinition, Guid> repositorySettingDefinition;
|
//private readonly IRepository<SettingDefinition, Guid> repositorySettingDefinition;
|
||||||
private readonly ILanguageKeyIntegrationService languageKeyIntegrationService;
|
|
||||||
private readonly ISettingDefinitionManager settingDefinitionManager;
|
private readonly ISettingDefinitionManager settingDefinitionManager;
|
||||||
private readonly ISettingManager settingManager;
|
private readonly ISettingManager settingManager;
|
||||||
private readonly ErpSettingDefinitionManager ErpSettingDefinitionManager;
|
private readonly ErpSettingDefinitionManager ErpSettingDefinitionManager;
|
||||||
private readonly ICurrentTenant currentTenant;
|
private readonly ICurrentTenant currentTenant;
|
||||||
|
|
||||||
public SettingUiAppService(
|
public SettingUiAppService(
|
||||||
ILanguageKeyIntegrationService languageKeyIntegrationService,
|
|
||||||
ISettingDefinitionManager settingDefinitionManager,
|
ISettingDefinitionManager settingDefinitionManager,
|
||||||
ISettingManager settingManager,
|
ISettingManager settingManager,
|
||||||
ErpSettingDefinitionManager ErpSettingDefinitionManager,
|
ErpSettingDefinitionManager ErpSettingDefinitionManager,
|
||||||
ICurrentTenant currentTenant)
|
ICurrentTenant currentTenant)
|
||||||
{
|
{
|
||||||
this.languageKeyIntegrationService = languageKeyIntegrationService;
|
|
||||||
this.settingDefinitionManager = settingDefinitionManager;
|
this.settingDefinitionManager = settingDefinitionManager;
|
||||||
this.settingManager = settingManager;
|
this.settingManager = settingManager;
|
||||||
this.ErpSettingDefinitionManager = ErpSettingDefinitionManager;
|
this.ErpSettingDefinitionManager = ErpSettingDefinitionManager;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
using Riok.Mapperly.Abstractions;
|
||||||
using Sozsoft.Settings.Entities;
|
using Sozsoft.Settings.Entities;
|
||||||
using Riok.Mapperly.Abstractions;
|
|
||||||
using Volo.Abp.Mapperly;
|
using Volo.Abp.Mapperly;
|
||||||
|
|
||||||
namespace Sozsoft.Settings;
|
namespace Sozsoft.Settings;
|
||||||
|
|
@ -7,7 +7,7 @@ namespace Sozsoft.Settings;
|
||||||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
||||||
public partial class SettingDefinitionToSettingDefinitionDtoMapper : TwoWayMapperBase<SettingDefinition, SettingDefinitionDto>
|
public partial class SettingDefinitionToSettingDefinitionDtoMapper : TwoWayMapperBase<SettingDefinition, SettingDefinitionDto>
|
||||||
{
|
{
|
||||||
public override partial SettingDefinitionDto Map(SettingDefinition source);
|
public override partial SettingDefinitionDto Map(SettingDefinition source);
|
||||||
|
|
||||||
public override partial void Map(SettingDefinition source, SettingDefinitionDto destination);
|
public override partial void Map(SettingDefinition source, SettingDefinitionDto destination);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
namespace Sozsoft.Settings;
|
|
||||||
|
|
||||||
public static class SettingsErrorCodes
|
|
||||||
{
|
|
||||||
//Add your business exception error codes here...
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Sozsoft.Settings.Localization;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
|
using Sozsoft.Settings.Localization;
|
||||||
using Volo.Abp.Domain.Repositories;
|
using Volo.Abp.Domain.Repositories;
|
||||||
using Volo.Abp.Domain.Services;
|
using Volo.Abp.Domain.Services;
|
||||||
using Volo.Abp.Settings;
|
using Volo.Abp.Settings;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using Volo.Abp.Modularity;
|
using Volo.Abp.Modularity;
|
||||||
using Volo.Abp.SettingManagement;
|
using Volo.Abp.SettingManagement;
|
||||||
using Volo.Abp.Settings;
|
using Volo.Abp.Settings;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
using Sozsoft.Settings.Entities;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Sozsoft.Settings.Entities;
|
||||||
using Volo.Abp.Data;
|
using Volo.Abp.Data;
|
||||||
using Volo.Abp.EntityFrameworkCore;
|
using Volo.Abp.EntityFrameworkCore;
|
||||||
using Volo.Abp.SettingManagement.EntityFrameworkCore;
|
using Volo.Abp.SettingManagement.EntityFrameworkCore;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Sozsoft.Settings.Entities;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.ChangeTracking;
|
using Microsoft.EntityFrameworkCore.ChangeTracking;
|
||||||
|
using Sozsoft.Settings.Entities;
|
||||||
using Volo.Abp;
|
using Volo.Abp;
|
||||||
using Volo.Abp.EntityFrameworkCore.Modeling;
|
using Volo.Abp.EntityFrameworkCore.Modeling;
|
||||||
using Volo.Abp.SettingManagement.EntityFrameworkCore;
|
using Volo.Abp.SettingManagement.EntityFrameworkCore;
|
||||||
|
|
@ -15,7 +15,7 @@ public static class SettingsDbContextModelCreatingExtensions
|
||||||
public static void ConfigureSettings(this ModelBuilder builder)
|
public static void ConfigureSettings(this ModelBuilder builder)
|
||||||
{
|
{
|
||||||
Check.NotNull(builder, nameof(builder));
|
Check.NotNull(builder, nameof(builder));
|
||||||
|
|
||||||
builder.ConfigureSettingManagement();
|
builder.ConfigureSettingManagement();
|
||||||
|
|
||||||
builder.Entity<SettingDefinition>(b =>
|
builder.Entity<SettingDefinition>(b =>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
namespace Sozsoft.SqlQueryManager.Application.Contracts;
|
namespace Sozsoft.SqlQueryManager.Application.Contracts;
|
||||||
|
|
||||||
public class DatabaseTableDto
|
public class DatabaseTableDto
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Volo.Abp.Application.Services;
|
using Volo.Abp.Application.Services;
|
||||||
|
|
||||||
|
|
@ -30,13 +30,15 @@ public interface ISqlObjectManagerAppService : IApplicationService
|
||||||
Task<string> GetNativeObjectDefinitionAsync(string dataSourceCode, string schemaName, string objectName);
|
Task<string> GetNativeObjectDefinitionAsync(string dataSourceCode, string schemaName, string objectName);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Saves the T-SQL script to Seeds/SqlData/{fileName}.sql in the DbMigrator project.
|
/// Saves the T-SQL script to {SeedsRoot}/{host|tenants/{tenantId}}/{sql|postgres}/object/{fileName}.sql
|
||||||
|
/// (repo: configs/seeds). The scope folder follows the current tenant: host context writes to host/.
|
||||||
/// Called automatically after a successful SqlTableDesigner deploy so the script can be re-seeded.
|
/// Called automatically after a successful SqlTableDesigner deploy so the script can be re-seeded.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task SaveTableScriptAsync(SaveTableScriptDto input);
|
Task SaveTableScriptAsync(SaveTableScriptDto input);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes matching SQL seed files from Seeds/SqlData when objects are dropped from the UI.
|
/// Deletes matching SQL seed files from the current scope's object folder when objects are
|
||||||
|
/// dropped from the UI.
|
||||||
/// Non-existing files are ignored.
|
/// Non-existing files are ignored.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task DeleteSqlDataFilesAsync(DeleteSqlDataFilesDto input);
|
Task DeleteSqlDataFilesAsync(DeleteSqlDataFilesDto input);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Sozsoft.SqlQueryManager.Application.Contracts;
|
namespace Sozsoft.SqlQueryManager.Application.Contracts;
|
||||||
|
|
||||||
|
|
@ -20,7 +20,8 @@ public class SqlQueryExecutionResultDto
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Input for saving a T-SQL script file to DbMigrator Seeds/SqlData.
|
/// Input for saving a T-SQL script file to the seeds folder
|
||||||
|
/// ({App:SeedsPath}/{host|tenants/{tenantId}}/{sql|postgres}/object).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SaveTableScriptDto
|
public class SaveTableScriptDto
|
||||||
{
|
{
|
||||||
|
|
@ -36,7 +37,8 @@ public class SaveTableScriptDto
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Input for deleting seed files from DbMigrator Seeds/SqlData.
|
/// Input for deleting seed files from the seeds folder
|
||||||
|
/// ({App:SeedsPath}/{host|tenants/{tenantId}}/{sql|postgres}/object).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DeleteSqlDataFilesDto
|
public class DeleteSqlDataFilesDto
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Sozsoft.SqlQueryManager.Application.Contracts;
|
namespace Sozsoft.SqlQueryManager.Application.Contracts;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using Volo.Abp.Application;
|
using Volo.Abp.Application;
|
||||||
using Volo.Abp.Authorization;
|
using Volo.Abp.Authorization;
|
||||||
using Volo.Abp.Modularity;
|
using Volo.Abp.Modularity;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using System.Collections.Generic;
|
namespace Sozsoft.SqlQueryManager.Application.Contracts;
|
||||||
|
|
||||||
namespace Sozsoft.SqlQueryManager.Application.Contracts;
|
|
||||||
|
|
||||||
public class SqlTemplateDto
|
public class SqlTemplateDto
|
||||||
{
|
{
|
||||||
|
|
@ -9,10 +7,3 @@ public class SqlTemplateDto
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public string Template { get; set; }
|
public string Template { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GetTemplateInput
|
|
||||||
{
|
|
||||||
public string TemplateType { get; set; }
|
|
||||||
public string ObjectName { get; set; }
|
|
||||||
public string SchemaName { get; set; }
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,21 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Sozsoft.SqlQueryManager.Application.Contracts;
|
|
||||||
using Sozsoft.SqlQueryManager.Domain.Services;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Volo.Abp.Application.Services;
|
using Sozsoft.Platform.Data.Seeds;
|
||||||
using Volo.Abp.MultiTenancy;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Sozsoft.Platform.Enums;
|
using Sozsoft.Platform.Enums;
|
||||||
using Sozsoft.Platform.Queries;
|
using Sozsoft.Platform.Queries;
|
||||||
|
using Sozsoft.Settings;
|
||||||
|
using Sozsoft.SqlQueryManager.Application.Contracts;
|
||||||
|
using Sozsoft.SqlQueryManager.Domain.Services;
|
||||||
|
using Volo.Abp.Application.Services;
|
||||||
|
using Volo.Abp.MultiTenancy;
|
||||||
|
|
||||||
namespace Sozsoft.SqlQueryManager.Application;
|
namespace Sozsoft.SqlQueryManager.Application;
|
||||||
|
|
||||||
|
|
@ -48,7 +50,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
|
||||||
private readonly IDataSourceManager _dataSourceManager;
|
private readonly IDataSourceManager _dataSourceManager;
|
||||||
private readonly ICurrentTenant _currentTenant;
|
private readonly ICurrentTenant _currentTenant;
|
||||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||||
private readonly IHostEnvironment _hostEnvironment;
|
private readonly IConfiguration _configuration;
|
||||||
private readonly ILogger<SqlObjectManagerAppService> _logger;
|
private readonly ILogger<SqlObjectManagerAppService> _logger;
|
||||||
|
|
||||||
public SqlObjectManagerAppService(
|
public SqlObjectManagerAppService(
|
||||||
|
|
@ -57,7 +59,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
|
||||||
IDataSourceManager dataSourceManager,
|
IDataSourceManager dataSourceManager,
|
||||||
ICurrentTenant currentTenant,
|
ICurrentTenant currentTenant,
|
||||||
IHttpContextAccessor httpContextAccessor,
|
IHttpContextAccessor httpContextAccessor,
|
||||||
IHostEnvironment hostEnvironment,
|
IConfiguration configuration,
|
||||||
ILogger<SqlObjectManagerAppService> logger)
|
ILogger<SqlObjectManagerAppService> logger)
|
||||||
{
|
{
|
||||||
_sqlExecutorService = sqlExecutorService;
|
_sqlExecutorService = sqlExecutorService;
|
||||||
|
|
@ -65,7 +67,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
|
||||||
_dataSourceManager = dataSourceManager;
|
_dataSourceManager = dataSourceManager;
|
||||||
_currentTenant = currentTenant;
|
_currentTenant = currentTenant;
|
||||||
_httpContextAccessor = httpContextAccessor;
|
_httpContextAccessor = httpContextAccessor;
|
||||||
_hostEnvironment = hostEnvironment;
|
_configuration = configuration;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -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()
|
private string ResolveSqlDataOutputPath()
|
||||||
{
|
{
|
||||||
return ResolveSqlDataOutputPath("SqlData");
|
var providerDirectoryName = SeedPathResolver.GetProviderFolderName(
|
||||||
}
|
SettingsConsts.DefaultDatabaseProvider == SettingsConsts.DatabaseProvider.PostgreSql);
|
||||||
|
|
||||||
private string ResolveSqlDataOutputPath(string dataDirectoryName)
|
return SeedPathResolver.GetScopePath(
|
||||||
{
|
_configuration,
|
||||||
const string dbMigratorName = "Sozsoft.Platform.DbMigrator";
|
_currentTenant.Id,
|
||||||
var safeDirectoryName = NormalizeSqlDataDirectoryName(dataDirectoryName);
|
providerDirectoryName,
|
||||||
var dir = new DirectoryInfo(_hostEnvironment.ContentRootPath);
|
SeedPathResolver.ObjectFolder);
|
||||||
|
|
||||||
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";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
namespace Sozsoft.SqlQueryManager.Application;
|
namespace Sozsoft.SqlQueryManager.Application;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using Volo.Abp.Localization;
|
using Volo.Abp.Localization;
|
||||||
|
|
||||||
namespace Sozsoft.SqlQueryManager.Domain.Shared;
|
namespace Sozsoft.SqlQueryManager.Domain.Shared;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
namespace Sozsoft.SqlQueryManager.Domain.Shared;
|
namespace Sozsoft.SqlQueryManager.Domain.Shared;
|
||||||
|
|
||||||
// SqlObjectType, SqlFunctionType and SqlQueryStatus enums removed:
|
// SqlObjectType, SqlFunctionType and SqlQueryStatus enums removed:
|
||||||
// The module no longer persists SQL objects and therefore does not need these enums.
|
// The module no longer persists SQL objects and therefore does not need these enums.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using Sozsoft.SqlQueryManager.Domain.Shared;
|
using Sozsoft.SqlQueryManager.Domain.Shared;
|
||||||
using Volo.Abp.Domain;
|
using Volo.Abp.Domain;
|
||||||
using Volo.Abp.Localization;
|
using Volo.Abp.Localization;
|
||||||
using Volo.Abp.Localization.ExceptionHandling;
|
using Volo.Abp.Localization.ExceptionHandling;
|
||||||
|
|
|
||||||
|
|
@ -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";
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using Volo.Abp.Reflection;
|
using Volo.Abp.Reflection;
|
||||||
|
|
||||||
namespace Sozsoft.SqlQueryManager.Domain.Shared;
|
namespace Sozsoft.SqlQueryManager.Domain.Shared;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Sozsoft.SqlQueryManager.Domain.Services;
|
namespace Sozsoft.SqlQueryManager.Domain.Services;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Sozsoft.Platform.DynamicData;
|
using Sozsoft.Platform.DynamicData;
|
||||||
using Sozsoft.Platform.Enums;
|
using Sozsoft.Platform.Enums;
|
||||||
using Sozsoft.Platform.Queries;
|
using Sozsoft.Platform.Queries;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Volo.Abp;
|
using Volo.Abp;
|
||||||
using Volo.Abp.Domain.Services;
|
using Volo.Abp.Domain.Services;
|
||||||
|
|
||||||
|
|
@ -30,7 +30,7 @@ public class SqlExecutorService : DomainService, ISqlExecutorService
|
||||||
{
|
{
|
||||||
// Get DataSource to determine database type
|
// Get DataSource to determine database type
|
||||||
var dataSource = await _dataSourceManager.GetDataSourceAsync(
|
var dataSource = await _dataSourceManager.GetDataSourceAsync(
|
||||||
CurrentTenant.IsAvailable,
|
CurrentTenant.IsAvailable,
|
||||||
dataSourceCode);
|
dataSourceCode);
|
||||||
|
|
||||||
if (dataSource == null)
|
if (dataSource == null)
|
||||||
|
|
@ -53,7 +53,7 @@ public class SqlExecutorService : DomainService, ISqlExecutorService
|
||||||
}
|
}
|
||||||
|
|
||||||
var repository = _serviceProvider.GetKeyedService<IDynamicDataRepository>(repositoryKey);
|
var repository = _serviceProvider.GetKeyedService<IDynamicDataRepository>(repositoryKey);
|
||||||
|
|
||||||
if (repository == null)
|
if (repository == null)
|
||||||
{
|
{
|
||||||
throw new BusinessException("SqlQueryManager:RepositoryNotFound")
|
throw new BusinessException("SqlQueryManager:RepositoryNotFound")
|
||||||
|
|
@ -64,8 +64,8 @@ public class SqlExecutorService : DomainService, ISqlExecutorService
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SqlExecutionResult> ExecuteQueryAsync(
|
public async Task<SqlExecutionResult> ExecuteQueryAsync(
|
||||||
string sql,
|
string sql,
|
||||||
string dataSourceCode,
|
string dataSourceCode,
|
||||||
Dictionary<string, object> parameters = null)
|
Dictionary<string, object> parameters = null)
|
||||||
{
|
{
|
||||||
var stopwatch = Stopwatch.StartNew();
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
|
@ -74,7 +74,7 @@ public class SqlExecutorService : DomainService, ISqlExecutorService
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var connectionString = await _dataSourceManager.GetConnectionStringAsync(
|
var connectionString = await _dataSourceManager.GetConnectionStringAsync(
|
||||||
CurrentTenant.IsAvailable,
|
CurrentTenant.IsAvailable,
|
||||||
dataSourceCode);
|
dataSourceCode);
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(connectionString))
|
if (string.IsNullOrWhiteSpace(connectionString))
|
||||||
|
|
@ -107,8 +107,8 @@ public class SqlExecutorService : DomainService, ISqlExecutorService
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SqlExecutionResult> ExecuteNonQueryAsync(
|
public async Task<SqlExecutionResult> ExecuteNonQueryAsync(
|
||||||
string sql,
|
string sql,
|
||||||
string dataSourceCode,
|
string dataSourceCode,
|
||||||
Dictionary<string, object> parameters = null)
|
Dictionary<string, object> parameters = null)
|
||||||
{
|
{
|
||||||
var stopwatch = Stopwatch.StartNew();
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
|
@ -117,7 +117,7 @@ public class SqlExecutorService : DomainService, ISqlExecutorService
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var connectionString = await _dataSourceManager.GetConnectionStringAsync(
|
var connectionString = await _dataSourceManager.GetConnectionStringAsync(
|
||||||
CurrentTenant.IsAvailable,
|
CurrentTenant.IsAvailable,
|
||||||
dataSourceCode);
|
dataSourceCode);
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(connectionString))
|
if (string.IsNullOrWhiteSpace(connectionString))
|
||||||
|
|
@ -149,8 +149,8 @@ public class SqlExecutorService : DomainService, ISqlExecutorService
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SqlExecutionResult> ExecuteScalarAsync<T>(
|
public async Task<SqlExecutionResult> ExecuteScalarAsync<T>(
|
||||||
string sql,
|
string sql,
|
||||||
string dataSourceCode,
|
string dataSourceCode,
|
||||||
Dictionary<string, object> parameters = null)
|
Dictionary<string, object> parameters = null)
|
||||||
{
|
{
|
||||||
var stopwatch = Stopwatch.StartNew();
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
|
@ -159,7 +159,7 @@ public class SqlExecutorService : DomainService, ISqlExecutorService
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var connectionString = await _dataSourceManager.GetConnectionStringAsync(
|
var connectionString = await _dataSourceManager.GetConnectionStringAsync(
|
||||||
CurrentTenant.IsAvailable,
|
CurrentTenant.IsAvailable,
|
||||||
dataSourceCode);
|
dataSourceCode);
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(connectionString))
|
if (string.IsNullOrWhiteSpace(connectionString))
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
|
||||||
using Volo.Abp.DependencyInjection;
|
using Volo.Abp.DependencyInjection;
|
||||||
|
|
||||||
namespace Sozsoft.SqlQueryManager.Domain.Services;
|
namespace Sozsoft.SqlQueryManager.Domain.Services;
|
||||||
|
|
@ -48,7 +47,7 @@ GO";
|
||||||
public string GetViewTemplate(string viewName, string schemaName = "dbo", bool withSchemaBinding = false)
|
public string GetViewTemplate(string viewName, string schemaName = "dbo", bool withSchemaBinding = false)
|
||||||
{
|
{
|
||||||
var schemaBindingClause = withSchemaBinding ? " WITH SCHEMABINDING" : "";
|
var schemaBindingClause = withSchemaBinding ? " WITH SCHEMABINDING" : "";
|
||||||
|
|
||||||
return $@"-- =============================================
|
return $@"-- =============================================
|
||||||
-- Author: <Author Name>
|
-- Author: <Author Name>
|
||||||
-- Create date: <Create Date>
|
-- Create date: <Create Date>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Volo.Abp.Domain;
|
||||||
using Volo.Abp.Domain;
|
|
||||||
using Volo.Abp.Modularity;
|
using Volo.Abp.Modularity;
|
||||||
|
|
||||||
namespace Sozsoft.SqlQueryManager;
|
namespace Sozsoft.SqlQueryManager;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Volo.Abp.Data;
|
using Volo.Abp.Data;
|
||||||
using Volo.Abp.EntityFrameworkCore;
|
using Volo.Abp.EntityFrameworkCore;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Volo.Abp.EntityFrameworkCore;
|
using Volo.Abp.EntityFrameworkCore;
|
||||||
using Volo.Abp.Modularity;
|
using Volo.Abp.Modularity;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using Volo.Abp.Application.Dtos;
|
using Volo.Abp.Application.Dtos;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.AuditLogs;
|
namespace Sozsoft.Platform.AuditLogs;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using Volo.Abp.Application.Dtos;
|
using Volo.Abp.Application.Dtos;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.ReleaseNotes;
|
namespace Sozsoft.Platform.ChangeLogs;
|
||||||
|
|
||||||
public class ReleaseNoteDto : EntityDto<Guid>
|
public class ChangeLogDto : EntityDto<Guid>
|
||||||
{
|
{
|
||||||
public string Version { get; set; }
|
public string Version { get; set; }
|
||||||
public DateTime ReleaseDate { get; set; }
|
public DateTime ReleaseDate { get; set; }
|
||||||
|
|
@ -11,7 +11,7 @@ public class CrudEndpointDto : FullAuditedEntityDto<Guid>
|
||||||
public string OperationType { get; set; } = string.Empty;
|
public string OperationType { get; set; } = string.Empty;
|
||||||
public string CsharpCode { get; set; } = string.Empty;
|
public string CsharpCode { get; set; } = string.Empty;
|
||||||
public bool IsActive { get; set; } = true;
|
public bool IsActive { get; set; } = true;
|
||||||
|
|
||||||
// Navigation property for display purposes
|
// Navigation property for display purposes
|
||||||
public string? EntityDisplayName { get; set; }
|
public string? EntityDisplayName { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,21 +7,31 @@ namespace Sozsoft.Platform.DeveloperKit;
|
||||||
public class CustomComponentDto : FullAuditedEntityDto<Guid>
|
public class CustomComponentDto : FullAuditedEntityDto<Guid>
|
||||||
{
|
{
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
public string RoutePath { get; set; } = string.Empty;
|
/// <summary>Rotasi olmayan bilesende (ust bilgi, alt bilgi) bostur.</summary>
|
||||||
|
public string? RoutePath { get; set; }
|
||||||
public string Code { get; set; } = string.Empty;
|
public string Code { get; set; } = string.Empty;
|
||||||
public string? Props { get; set; }
|
public string? Props { get; set; }
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
public bool IsActive { get; set; } = true;
|
public bool IsActive { get; set; } = true;
|
||||||
public string? Dependencies { get; set; } // JSON string of component names
|
public string? Dependencies { get; set; } // JSON string of component names
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Data sekmesinde kullanilan API'ler; JSON dizisi. Props icindeki designer dokumanindan
|
||||||
|
/// kaydetme sirasinda sunucu tarafinda uretilir, istemciden gonderilmez.
|
||||||
|
/// </summary>
|
||||||
|
public string? DataSources { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CreateUpdateCustomComponentDto
|
public class CreateUpdateCustomComponentDto
|
||||||
{
|
{
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
[Required]
|
/// <summary>
|
||||||
|
/// Bilesenin kendi rotasi. Bos birakilabilir: rotasi olmayan bir bilesen (ornegin
|
||||||
|
/// baska bir ekranin icinde cizilen alt bilgi) hicbir adrese baglanmaz.
|
||||||
|
/// </summary>
|
||||||
[StringLength(512)]
|
[StringLength(512)]
|
||||||
[RegularExpression(@"^/.*", ErrorMessage = "RoutePath must start with '/'.")]
|
[RegularExpression(@"^(/.*)?$", ErrorMessage = "RoutePath must start with '/'.")]
|
||||||
public string RoutePath { get; set; } = string.Empty;
|
public string? RoutePath { get; set; }
|
||||||
public string Code { get; set; } = string.Empty;
|
public string Code { get; set; } = string.Empty;
|
||||||
public string? Props { get; set; }
|
public string? Props { get; set; }
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,17 @@ public interface ICrudEndpointAppService : ICrudAppService<
|
||||||
PagedAndSortedResultRequestDto,
|
PagedAndSortedResultRequestDto,
|
||||||
CreateUpdateCrudEndpointDto>
|
CreateUpdateCrudEndpointDto>
|
||||||
{
|
{
|
||||||
Task<List<CrudEndpointDto>> GetActiveEndpointsAsync();
|
|
||||||
Task<List<CrudEndpointDto>> GetEndpointsByEntityAsync(string entityName);
|
Task<List<CrudEndpointDto>> GetEndpointsByEntityAsync(string entityName);
|
||||||
Task<CrudEndpointDto> ToggleAsync(Guid id);
|
Task<CrudEndpointDto> ToggleAsync(Guid id);
|
||||||
Task<PagedResultDto<CrudEndpointDto>> GenerateCrudEndpointsAsync(string entityName);
|
/// <param name="operationTypes">
|
||||||
|
/// Aktif baslatilacak operasyonlarin virgulle ayrilmis listesi
|
||||||
|
/// (GetList, GetById, Create, Update, Delete). Bos birakilirsa hepsi aktif olur.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="objectType">
|
||||||
|
/// Veritabani nesnesinin turu (table, view, procedure, function). Tablo disindaki
|
||||||
|
/// nesneler icin yalnizca okuma endpoint'leri (GetList, GetById) uretilir.
|
||||||
|
/// </param>
|
||||||
|
Task<PagedResultDto<CrudEndpointDto>> GenerateCrudEndpointsAsync(string entityName, string operationTypes = null, string objectType = null);
|
||||||
|
Task DeleteByEntityAsync(string entityName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Volo.Abp.Application.Dtos;
|
using Volo.Abp.Application.Dtos;
|
||||||
using Volo.Abp.Application.Services;
|
using Volo.Abp.Application.Services;
|
||||||
|
|
||||||
|
|
@ -12,7 +10,4 @@ public interface ICustomComponentAppService : ICrudAppService<
|
||||||
PagedAndSortedResultRequestDto,
|
PagedAndSortedResultRequestDto,
|
||||||
CreateUpdateCustomComponentDto>
|
CreateUpdateCustomComponentDto>
|
||||||
{
|
{
|
||||||
Task<List<CustomComponentDto>> GetActiveComponentsAsync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
using Volo.Abp.Content;
|
using Volo.Abp.Content;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.FileManagement;
|
namespace Sozsoft.Platform.FileManagement;
|
||||||
|
|
@ -11,7 +11,7 @@ public class CreateFolderDto
|
||||||
[Required]
|
[Required]
|
||||||
[StringLength(255)]
|
[StringLength(255)]
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
public string? ParentId { get; set; }
|
public string? ParentId { get; set; }
|
||||||
|
|
||||||
public string? TenantId { get; set; }
|
public string? TenantId { get; set; }
|
||||||
|
|
@ -37,11 +37,11 @@ public class UploadFileDto
|
||||||
{
|
{
|
||||||
[Required]
|
[Required]
|
||||||
public string FileName { get; set; } = string.Empty;
|
public string FileName { get; set; } = string.Empty;
|
||||||
|
|
||||||
public string? ParentId { get; set; }
|
public string? ParentId { get; set; }
|
||||||
|
|
||||||
public string? TenantId { get; set; }
|
public string? TenantId { get; set; }
|
||||||
|
|
||||||
// NoteModal pattern - Files array
|
// NoteModal pattern - Files array
|
||||||
public IRemoteStreamContent[]? Files { get; set; }
|
public IRemoteStreamContent[]? Files { get; set; }
|
||||||
}
|
}
|
||||||
|
|
@ -50,7 +50,7 @@ public class SearchFilesDto
|
||||||
{
|
{
|
||||||
[Required]
|
[Required]
|
||||||
public string Query { get; set; } = string.Empty;
|
public string Query { get; set; } = string.Empty;
|
||||||
|
|
||||||
public string? ParentId { get; set; }
|
public string? ParentId { get; set; }
|
||||||
|
|
||||||
public string? TenantId { get; set; }
|
public string? TenantId { get; set; }
|
||||||
|
|
@ -64,11 +64,11 @@ public class BulkDeleteDto
|
||||||
public string? TenantId { get; set; }
|
public string? TenantId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CopyItemsDto
|
public class CopyItemsDto
|
||||||
{
|
{
|
||||||
[Required]
|
[Required]
|
||||||
public List<string> ItemIds { get; set; } = new();
|
public List<string> ItemIds { get; set; } = new();
|
||||||
|
|
||||||
public string? TargetFolderId { get; set; }
|
public string? TargetFolderId { get; set; }
|
||||||
|
|
||||||
public string? TenantId { get; set; }
|
public string? TenantId { get; set; }
|
||||||
|
|
@ -78,7 +78,7 @@ public class MoveItemsDto
|
||||||
{
|
{
|
||||||
[Required]
|
[Required]
|
||||||
public List<string> ItemIds { get; set; } = new();
|
public List<string> ItemIds { get; set; } = new();
|
||||||
|
|
||||||
public string? TargetFolderId { get; set; }
|
public string? TargetFolderId { get; set; }
|
||||||
|
|
||||||
public string? TenantId { get; set; }
|
public string? TenantId { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -44,34 +44,6 @@ public class ForumCategoryDto : FullAuditedEntityDto<Guid>
|
||||||
public Guid? LastPostUserId { get; set; }
|
public Guid? LastPostUserId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CreateForumCategoryDto
|
|
||||||
{
|
|
||||||
public Guid? TenantId { get; set; }
|
|
||||||
|
|
||||||
[Required]
|
|
||||||
[StringLength(100)]
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
[Required]
|
|
||||||
[StringLength(100)]
|
|
||||||
public string Slug { get; set; }
|
|
||||||
|
|
||||||
[Required]
|
|
||||||
[StringLength(500)]
|
|
||||||
public string Description { get; set; }
|
|
||||||
|
|
||||||
[StringLength(10)]
|
|
||||||
public string Icon { get; set; }
|
|
||||||
|
|
||||||
public int DisplayOrder { get; set; }
|
|
||||||
public bool IsActive { get; set; } = true;
|
|
||||||
public bool IsLocked { get; set; } = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class UpdateForumCategoryDto : CreateForumCategoryDto
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public class GetCategoriesInput : PagedAndSortedResultRequestDto
|
public class GetCategoriesInput : PagedAndSortedResultRequestDto
|
||||||
{
|
{
|
||||||
public bool? IsActive { get; set; }
|
public bool? IsActive { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,6 @@ public interface IForumAppService : IApplicationService
|
||||||
Task<PagedResultDto<ForumCategoryDto>> GetCategoriesAsync(GetCategoriesInput input);
|
Task<PagedResultDto<ForumCategoryDto>> GetCategoriesAsync(GetCategoriesInput input);
|
||||||
Task<ForumCategoryDto> GetCategoryAsync(Guid id);
|
Task<ForumCategoryDto> GetCategoryAsync(Guid id);
|
||||||
Task<ForumCategoryDto> GetCategoryBySlugAsync(string slug);
|
Task<ForumCategoryDto> GetCategoryBySlugAsync(string slug);
|
||||||
Task<ForumCategoryDto> CreateCategoryAsync(CreateForumCategoryDto input);
|
|
||||||
Task<ForumCategoryDto> UpdateCategoryAsync(Guid id, UpdateForumCategoryDto input);
|
|
||||||
Task DeleteCategoryAsync(Guid id);
|
|
||||||
|
|
||||||
// Topics
|
// Topics
|
||||||
Task<PagedResultDto<ForumTopicDto>> GetTopicsAsync(GetTopicsInput input);
|
Task<PagedResultDto<ForumTopicDto>> GetTopicsAsync(GetTopicsInput input);
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ namespace Sozsoft.Platform.Identity.Dto;
|
||||||
|
|
||||||
public class AssignedBranchViewModel
|
public class AssignedBranchViewModel
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
public bool IsAssigned { get; set; }
|
public bool IsAssigned { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@ namespace Sozsoft.Platform.Identity.Dto;
|
||||||
|
|
||||||
public class AssignedClaimViewModel
|
public class AssignedClaimViewModel
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
public string ClaimType { get; set; }
|
public string ClaimType { get; set; }
|
||||||
|
|
||||||
public string ClaimValue { get; set; }
|
public string ClaimValue { get; set; }
|
||||||
|
|
||||||
public bool IsAssigned { get; set; }
|
public bool IsAssigned { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue