Compare commits
No commits in common. "main" and "1.1.10" have entirely different histories.
1130 changed files with 71945 additions and 127574 deletions
309
.github/instructions/ai.instructions.md
vendored
309
.github/instructions/ai.instructions.md
vendored
|
|
@ -14,25 +14,14 @@ Primary principle:
|
|||
|
||||
- Configuration first, code last.
|
||||
|
||||
Companion documents (this file wins on conflict):
|
||||
|
||||
| Document | Scope |
|
||||
| --- | --- |
|
||||
| `lowcode.instructions.md` | **Authoring reference**: exact schemas, enum values and worked examples for producing wizard screens, editor scripts, custom components, SQL objects and endpoints. Read it before writing any artifact. |
|
||||
| `lowcode-reference.instructions.md` | **Capability inventory**: field-by-field contract of every ListForm/ListFormField JSON column, chart and pivot options, SQL command hooks, Visual Designer toolbox, the scheduled-mail module and (§12) every ready-made screen under the Saas and Administration menus. Sweep its §0 checklist before producing a screen or component; a request whose answer already exists as a platform screen (§12.5) is configured there, not rebuilt. |
|
||||
| `dotnet.instructions.md` | Binding code standard for everything under `api/` |
|
||||
| `list.instructions.md` | Step-by-step procedure for adding a module / list through seeders |
|
||||
| `../../README.md` | Technical map: what exists, where it lives, how it is wired |
|
||||
| `../../CLAUDE.md` | Operating rules and standing defaults for AI agents in this repo |
|
||||
|
||||
---
|
||||
|
||||
## 2. Platform Scope
|
||||
|
||||
Core stack:
|
||||
|
||||
- Backend: C# .NET 10 (C# 14) + ABP 10.x — kod standardı: `dotnet.instructions.md`
|
||||
- Frontend: React 19 + DevExtreme
|
||||
- Backend: C# .NET 9 + ABP 9.x
|
||||
- Frontend: React 18 + DevExtreme
|
||||
- Database: SQL Server (dynamic datasource support)
|
||||
- Cache: Redis
|
||||
- Background jobs: Hangfire + ABP Background Workers
|
||||
|
|
@ -52,25 +41,11 @@ System nature:
|
|||
3. Every proposal must include tenant and permission design.
|
||||
4. Never bypass platform authorization patterns.
|
||||
5. Never hardcode secrets, tenant ids, or connection strings.
|
||||
6. **Stay inside the framework; ask before stepping out.** The platform may extend itself through
|
||||
its own mechanisms (seed artifacts; adding a missing event, prop, or small backward-compatible
|
||||
capability to an existing UI component — when Developer Kit tooling builds ~70% of the
|
||||
requested component, the rest is completed with such small additions, never with a new
|
||||
component; backward-compatible additions to script recipes / editor options / tokens).
|
||||
Anything beyond that — a new React component or page, a new toolbox family, core engine changes (ListForm
|
||||
runtime, seeders, code generator, auth, DataSourceManager, dispatcher/compiler), new ABP
|
||||
entity/module/migration, platform table schema changes, new NuGet/npm dependencies, `Routes`
|
||||
in `MenusData.json`, new external integrations — is **out of frame**: finish everything that
|
||||
fits inside the frame first, then ask the user with a single question (why configuration is
|
||||
insufficient, the smallest change that works, files touched, rollback) and proceed **only with
|
||||
explicit approval**. Approval is per request and does not carry over. Full lists:
|
||||
`lowcode.instructions.md` §0.8.
|
||||
|
||||
Exception policy:
|
||||
|
||||
- Code-level React or backend development can be considered only if user explicitly requests code implementation and configuration path is insufficient.
|
||||
- If exception is required, AI must explain why configuration-based options are not enough.
|
||||
- Never say "cannot be done": the answer is either an in-frame build or an approval question.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -141,7 +116,7 @@ Capabilities:
|
|||
- Query
|
||||
- WebService
|
||||
- Cascading lookup parent-child behavior
|
||||
- Dynamic validation, editor options, editor scripts (see 7.1)
|
||||
- Dynamic validation, editor options, editor scripts
|
||||
- Conditional formatting and style injection
|
||||
- Grid state save/load/reset
|
||||
- User filter save/apply/delete flows
|
||||
|
|
@ -171,34 +146,6 @@ Supported editor types:
|
|||
- dxTextArea
|
||||
- dxTextBox
|
||||
|
||||
### 7.1 Editor Script and Editor Options (builder-backed)
|
||||
|
||||
`EditorScript` and `EditorOptions` are produced from a shared vocabulary, in two places that
|
||||
must stay identical:
|
||||
|
||||
| Surface | Location |
|
||||
| --- | --- |
|
||||
| Visual builder (UI) | `ui/src/views/admin/listForm/edit/json-row-operations/editor-script/scriptRecipes.ts` and `.../editor-options/optionSpecs.ts` + `presets.ts` |
|
||||
| Seeder API (C#) | `api/src/Sozsoft.Platform.Domain.Shared/Editors/` — `EditorScript`, `EditorScriptRule`, `EditorScriptCondition`, `EditorOptions`, `EditorOptionsBuilder` |
|
||||
|
||||
Rules AI must follow:
|
||||
|
||||
- Prefer a recipe over hand-written JS. Recipes cover arithmetic (`Multiply`, `Subtract`,
|
||||
`Percent`, `Sum`, `Formula`), dates (`Today`, `Days`, `Hours`), data movement (`Copy`,
|
||||
`SetValue`, `Clear`, `ApiToField`), and view/notify actions (`ReadOnly`, `Notify`, `Ask`,
|
||||
`OpenUrl`). Free-form JS is `Custom` and is the last option.
|
||||
- Generated script carries a `// @builder {...}` header on the first line that stores the rules,
|
||||
plus `// @runOnOpen` when any rule runs on form open. **Never edit the header by hand and never
|
||||
emit a script that does not match what the builder would emit** — the dialog then treats the
|
||||
script as manually edited and closes the rule editor.
|
||||
- Rule triggers: `change` (default), `open` (default-value generation), `both`. Conditions join
|
||||
with `and` / `or`.
|
||||
- When a recipe changes on one side, change its port on the other side in the same task.
|
||||
- In seeders write `EditorOptions = EditorOptions.Number(2).ShowClearButton()` style expressions
|
||||
instead of raw JSON strings. Options marked `platform: true` in `optionSpecs.ts` deserialize
|
||||
into typed DTOs (`GridBoxOptionsDto`, `TagBoxOptionsDto`, `ImageUploadOptionsDto`); wrong types
|
||||
are silently ignored.
|
||||
|
||||
---
|
||||
|
||||
## 8. Dynamic Form System
|
||||
|
|
@ -225,8 +172,6 @@ Supported component families:
|
|||
- TreeList / Tree view
|
||||
- SubForm tabs (List, Tree, Gantt, Scheduler, Form, Chart)
|
||||
- Widget Group (dashboard KPI cards)
|
||||
- TodoBoard — a Kanban board: columns come from `statusExpr` values, cards change status by drag
|
||||
and drop, new columns (statuses) can be added from the board
|
||||
|
||||
Runtime UI capabilities:
|
||||
|
||||
|
|
@ -267,54 +212,6 @@ If user requests custom code explicitly:
|
|||
- Provide a warning that low-code path is preferred.
|
||||
- Offer configuration-first alternative first.
|
||||
|
||||
### 10.1 Custom Component path (still not hand-written React)
|
||||
|
||||
When a screen genuinely cannot be expressed as a ListForm, the next step is **not** a physical
|
||||
React page. It is a Custom Component built in the Visual Designer and attached to a menu through
|
||||
the Wizard's `Custom` path.
|
||||
|
||||
- The component is stored in the database (`Name`, `RoutePath`, `Code`, `Props`, `Dependencies`,
|
||||
`DataSources`) and compiled in the browser with `@babel/standalone`.
|
||||
- The designer document lives in `Props.visualDesigner` **and** in the
|
||||
`/*__SOZSOFT_VISUAL_DESIGNER__…__*/` header of the generated code. `sourceMode` is `visual` or
|
||||
`code`; switching to `code` is one-way — the canvas cannot be restored afterwards, so do not
|
||||
propose it casually.
|
||||
- Toolbox families: `layout` (`PageContainer`, `FlexRow`, `Spacer`), `data` (`Form` — owns the
|
||||
four CRUD endpoints and acts as a container), `platform` (`ListView`, `DataGridView`,
|
||||
`TreeView`, `GanttView`, `TodoBoard`, `CardView`, `SchedulerView`, `PivotView`, `ChartView` —
|
||||
each embeds a ListForm screen through `listFormCode`), `html`/`ui`, and other custom
|
||||
components as `custom`.
|
||||
- **Reuse before drawing:** if the requirement is a list/tree/chart that a ListForm already
|
||||
serves, drop the matching `platform` node with its `listFormCode` instead of rebuilding it out
|
||||
of primitives.
|
||||
- Data binding goes through CRUD endpoints. Filter operators map to the CrudEndpoint `GetList`
|
||||
contract: `eq` is a bare query parameter, every other operator carries its name as a suffix
|
||||
(`?Name.contains=…`).
|
||||
- Permissions are two-layered and mandatory: node-level `designerPermission` for visibility, and
|
||||
per-command Form permissions (`selectPermission`, `insertPermission`, `updatePermission`,
|
||||
`deletePermission`). In `Otomatik` mode the base is the read permission the Wizard created for
|
||||
the component and the commands get `''` / `.Create` / `.Update` / `.Delete` suffixes.
|
||||
- List screen permissions: `App.DeveloperKit.CustomComponents{,.Create,.Update,.Delete,.Export,.Import}`.
|
||||
- Every save/delete is mirrored into `configs/seeds/{scope}/data/App.DeveloperKit.CustomComponents.json`; the endpoints it uses are
|
||||
referenced through `crud/{Entity}.json`. A component that is not seed-backed is not done.
|
||||
|
||||
### 10.2 SQL View Designer
|
||||
|
||||
For shaping data before a screen binds to it, prefer a designed view over an inline query:
|
||||
`SqlViewDesignerDialog` produces T-SQL from a diagram (sources, joins, `CROSS/OUTER APPLY`,
|
||||
derived subqueries) plus an SSMS-style criteria grid (Column / Alias / Output / Group By / Sort /
|
||||
Filter / Or…). It is **one-way** — model to T-SQL. A view that does not parse back into the
|
||||
canonical shape drops the dialog to raw SQL mode, so hand-editing a designed view costs the
|
||||
designer. Aggregations available in the Group By column: `GroupBy`, `Where`, `SUM`, `COUNT`,
|
||||
`COUNT_DISTINCT`, `AVG`, `MIN`, `MAX`.
|
||||
|
||||
Persistence: only the **table designer's** deploy writes a seed file automatically
|
||||
(`{sql|postgres}/object/{TableName}.sql`, full CREATE script, via `save-table-script`). View
|
||||
Designer deploys and any DDL run from the SQL editor (views, procedures, functions) reach the
|
||||
database only — their seed files under `{sql|postgres}/object/` must be written explicitly.
|
||||
Full mechanics and the SP/function production patterns: `lowcode.instructions.md` §8.1–§8.2.1
|
||||
and §9.5.
|
||||
|
||||
---
|
||||
|
||||
## 11. Integrations
|
||||
|
|
@ -445,8 +342,7 @@ When configuration cannot satisfy requirement:
|
|||
1. Try SQL-based design
|
||||
2. Try Custom Endpoint
|
||||
3. Try Dynamic Service
|
||||
4. Propose minimal code change with explicit justification — this step is out of frame:
|
||||
**ask and wait for approval before touching code** (rule 3.6).
|
||||
4. Propose minimal code change with explicit justification
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -644,39 +540,13 @@ When in doubt, AI must choose the path that preserves:
|
|||
|
||||
## 31. Seeder-Driven Low-Code Development Guide (Authoritative)
|
||||
|
||||
There are **two seed surfaces**. AI must not mix them.
|
||||
AI must learn and teach implementation flow primarily from these seed assets:
|
||||
|
||||
**A. Repository seeds (shipped with code)** — `api/src/Sozsoft.Platform.DbMigrator/Migrations/`.
|
||||
Platform-owned screens, menus, permissions and language texts. Changing them requires a build and
|
||||
a deploy. AI must learn and teach implementation flow primarily from these assets:
|
||||
|
||||
- ListFormSeeder_Saas.cs / ListFormSeeder_Administration.cs / ListFormSeeder_{Modul}.cs
|
||||
- MenusData.json
|
||||
- PermissionsData.json
|
||||
- HostData.json
|
||||
- LanguagesData.json
|
||||
- WizardDataSeeder.cs / CrudDataSeeder.cs — the seeders that read
|
||||
surface B below
|
||||
- ListFormSeedDataApplier, CrudEndpointSeedApplier (Domain/Data/) — the single home of the apply
|
||||
rules; both the migrate/seed flow and the wizard import's completion call the same services.
|
||||
`data/` rows are inserted-only on migrate (a possibly stale file must not overwrite live data);
|
||||
`crud/` entries are timestamp-guarded — an existing record is updated or deleted only when the
|
||||
file's `GeneratedAt` is newer than the record's `LastModificationTime`/`CreationTime`. **Migrate
|
||||
adds; it does not overwrite.** Follow this rule in any new seeder.
|
||||
|
||||
**B. Runtime seeds (written by the running application)** — `configs/seeds/`, resolved through
|
||||
`SeedPathResolver`. The Wizard, the Custom Components screen / Visual Designer, the CRUD
|
||||
Endpoint Manager and every ListForm screen with a `SeedFilePath` write here; the
|
||||
seeders above restore them when the database is recreated. Scope folders mirror the CDN layout:
|
||||
|
||||
```
|
||||
configs/seeds/host/{wizard,data,crud,sql/{object,execute},postgres/{object,execute}}
|
||||
configs/seeds/tenants/{tenantId}/… same layout, tenant scope
|
||||
configs/seeds/.imports/{importId}/… wizard import staging + backup (not scanned)
|
||||
```
|
||||
|
||||
Rule: anything produced at runtime must land in surface B. **A feature that disappears when the
|
||||
database is recreated is not delivered.**
|
||||
- api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs
|
||||
- api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json
|
||||
- api/src/Sozsoft.Platform.DbMigrator/Seeds/PermissionsData.json
|
||||
- api/src/Sozsoft.Platform.DbMigrator/Seeds/HostData.json
|
||||
- api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json
|
||||
|
||||
If user asks "how to add a new module/screen", AI must answer with this exact operational sequence.
|
||||
|
||||
|
|
@ -789,39 +659,6 @@ When user asks for a screen/module, AI must answer in this order:
|
|||
|
||||
AI should produce practical, copy-adaptable artifact definitions and avoid abstract-only explanations.
|
||||
|
||||
### 31.9 Screen brief, discovery questions and image-based requests
|
||||
|
||||
A request arrives either as prose or as a **screenshot / mockup / photo** of the desired screen.
|
||||
Both enter the same pipeline — and for a **new screen/component the pipeline starts with
|
||||
questions, not files**:
|
||||
|
||||
1. **Run the discovery round** (`lowcode.instructions.md` §0.6). Ask in batches (max 4 questions
|
||||
per round, each with 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)
|
||||
|
|
@ -864,131 +701,7 @@ Rules:
|
|||
- Permission contract must be created/bound for both parent and child menu items.
|
||||
- Route/menu consistency remains mandatory (`Url` and screen contract must match).
|
||||
|
||||
### 32.3 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
|
||||
### 32.3 AI enforcement requirement
|
||||
|
||||
AI must proactively apply these defaults in:
|
||||
|
||||
|
|
|
|||
150
.github/instructions/dotnet.instructions.md
vendored
150
.github/instructions/dotnet.instructions.md
vendored
|
|
@ -1,150 +0,0 @@
|
|||
# .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,10 +7,6 @@ This document summarizes the rules, standards, and step-by-step instructions for
|
|||
---
|
||||
|
||||
|
||||
> **Bu doküman depo içi seeder'larla (DbMigrator/Migrations) modül eklemeyi anlatır.**
|
||||
> Çalışma zamanında Wizard / Custom Components / CRUD Endpoint Manager ile üretim yapacaksan
|
||||
> `lowcode.instructions.md` esastır; oradaki seed dosyaları `configs/seeds` altına düşer.
|
||||
|
||||
## General Principles
|
||||
- **Configuration First:** Always prefer platform configuration (menus, permissions, forms, localization) over custom code.
|
||||
- **Modularization:** Each module (e.g., {Modul}) must have its own seeder, permission group, and localization entries.
|
||||
|
|
@ -28,7 +24,7 @@ This document summarizes the rules, standards, and step-by-step instructions for
|
|||
## Special File Handling Rules
|
||||
|
||||
- **MenusData.json:**
|
||||
- Never modify the `Routes` section directly. Only `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.
|
||||
- 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.
|
||||
|
||||
- **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.
|
||||
|
|
@ -39,7 +35,7 @@ This document summarizes the rules, standards, and step-by-step instructions for
|
|||
|
||||
|
||||
### 1. MenusData.json
|
||||
- **{Modul} menu** added as a top-level menu with `ModuleId: {Modul}`.
|
||||
- **{Modul} menu** added as a top-level menu with `ShortName: {Modul}`.
|
||||
- **{Liste}** added as a child menu under {Modul}.
|
||||
|
||||
### 2. PermissionsData.json
|
||||
|
|
|
|||
1393
.github/instructions/lowcode-reference.instructions.md
vendored
1393
.github/instructions/lowcode-reference.instructions.md
vendored
File diff suppressed because it is too large
Load diff
3746
.github/instructions/lowcode.instructions.md
vendored
3746
.github/instructions/lowcode.instructions.md
vendored
File diff suppressed because it is too large
Load diff
11
.gitignore
vendored
11
.gitignore
vendored
|
|
@ -1,14 +1,5 @@
|
|||
# Konteyner veri klasorleri (mssql/postgres/redis vb.); seed veri dosyalari haric tutulur.
|
||||
configs/**/data/**
|
||||
!configs/seeds/**/data/**
|
||||
*.bak
|
||||
**/node_modules
|
||||
**/.DS_Store
|
||||
logs/
|
||||
|
||||
# Deploy sırasında git tag'lerinden üretilir (configs/change-logs/generate-change-logs.sh)
|
||||
# ve API konteynerine mount edilir. Takip edilirse deploy'daki `git pull` yerel
|
||||
# değişiklikle çakışır ve dosya eskir.
|
||||
configs/change-logs/change-logs.json
|
||||
# Wizard import staging klasoru (gecici; oturum bitince silinir)
|
||||
configs/seeds/.imports/
|
||||
logs/
|
||||
|
|
@ -1,9 +1,5 @@
|
|||
<Project>
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<NoWarn>$(NoWarn);CS0162;CS8321;CS8618;CS8632;CS1591;CS1573;CS1572;CS1570;CS1574</NoWarn>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<AnalysisLevel>latest</AnalysisLevel>
|
||||
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>$(NoWarn);CS0162;CS8321;CS8618;CS8632</NoWarn>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
using Volo.Abp.Application;
|
||||
using Volo.Abp.Authorization;
|
||||
using Volo.Abp.Modularity;
|
||||
using Volo.Abp.Authorization;
|
||||
|
||||
namespace Sozsoft.Languages;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
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;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
using System;
|
||||
using Sozsoft.Languages.Entities;
|
||||
using Sozsoft.Languages.Localization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Threading.Tasks;
|
||||
using Sozsoft.Languages.Entities;
|
||||
using Sozsoft.Languages.Localization;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Volo.Abp.Application.Services;
|
||||
using Volo.Abp.Domain.Entities;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
using System;
|
||||
using Sozsoft.Languages.Entities;
|
||||
using Sozsoft.Languages.Localization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Threading.Tasks;
|
||||
using Sozsoft.Languages.Entities;
|
||||
using Sozsoft.Languages.Localization;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Volo.Abp.Application.Services;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
using System;
|
||||
using Sozsoft.Languages.Entities;
|
||||
using Sozsoft.Languages.Localization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using StackExchange.Redis;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Linq.Dynamic.Core;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Sozsoft.Languages.Entities;
|
||||
using Sozsoft.Languages.Localization;
|
||||
using StackExchange.Redis;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Volo.Abp.Application.Services;
|
||||
|
|
@ -136,7 +136,7 @@ public class LanguageTextAppService : CrudAppService<
|
|||
var db = redis.GetDatabase();
|
||||
await db.ExecuteAsync("FLUSHALL");
|
||||
}
|
||||
|
||||
|
||||
protected virtual IQueryable<LanguageKey> ApplySorting(IQueryable<LanguageKey> query, LanguageTextTranslatedRequestDto input)
|
||||
{
|
||||
//Try to sort query if available
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
using Riok.Mapperly.Abstractions;
|
||||
using Sozsoft.Languages.Entities;
|
||||
using Volo.Abp.Localization;
|
||||
using Riok.Mapperly.Abstractions;
|
||||
using Volo.Abp.Mapperly;
|
||||
|
||||
namespace Sozsoft.Languages;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
namespace Sozsoft.Languages;
|
||||
|
||||
public static class LanguagesErrorCodes
|
||||
{
|
||||
//Add your business exception error codes here...
|
||||
}
|
||||
|
||||
|
|
@ -1,23 +1,45 @@
|
|||
namespace Sozsoft.Languages.Languages;
|
||||
|
||||
public static class LanguageCodes
|
||||
namespace Sozsoft.Languages.Languages
|
||||
{
|
||||
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 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 const string Ar = "العربية";
|
||||
public const string Cs = "Čeština";
|
||||
public const string De = "Deutsch";
|
||||
public const string En = "English";
|
||||
public const string Es = "Español";
|
||||
public const string Fi = "Finnish";
|
||||
public const string Fr = "Français";
|
||||
public const string Hi = "Hindi";
|
||||
public const string Hr = "Croatian";
|
||||
public const string Hu = "Magyar";
|
||||
public const string It = "Italiano";
|
||||
public const string Pt = "Português";
|
||||
public const string Ru = "Русский";
|
||||
public const string Sk = "Slovak";
|
||||
public const string Sl = "Slovenščina";
|
||||
public const string Tr = "Türkçe";
|
||||
public const string Zh = "繁體中文";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
using System.Threading.Tasks;
|
||||
using Sozsoft.Languages.Entities;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.Caching;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Domain.Entities.Events.Distributed;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
using Volo.Abp.EventBus.Distributed;
|
||||
|
||||
namespace Sozsoft.Languages;
|
||||
|
|
@ -12,11 +15,14 @@ public class LanguageTextCacheItemInvalidatorDistributed :
|
|||
IDistributedEventHandler<EntityDeletedEto<LanguageTextEto>>,
|
||||
ITransientDependency
|
||||
{
|
||||
private readonly IRepository<LanguageText, Guid> languageTextRepository;
|
||||
private readonly IDistributedCache<LanguageTextCacheItem> cache;
|
||||
|
||||
public LanguageTextCacheItemInvalidatorDistributed(
|
||||
IRepository<LanguageText, Guid> languageTextRepository,
|
||||
IDistributedCache<LanguageTextCacheItem> cache)
|
||||
{
|
||||
this.languageTextRepository = languageTextRepository;
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
using System;
|
||||
using Sozsoft.Languages.Entities;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Sozsoft.Languages.Entities;
|
||||
using Volo.Abp.Caching;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Volo.Abp.Localization;
|
||||
|
||||
namespace Sozsoft.Languages.Localization;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Sozsoft.Languages.Entities;
|
||||
using Sozsoft.Languages.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.EntityFrameworkCore;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Sozsoft.Languages.Entities;
|
||||
using Sozsoft.Languages.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.EntityFrameworkCore.Modeling;
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
namespace Sozsoft.MailQueue.Application;
|
||||
namespace Sozsoft.MailQueue.Application;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
using Microsoft.Extensions.Configuration;
|
||||
using Sozsoft.MailQueue.FileGeneration;
|
||||
using Sozsoft.MailQueue.FileGeneration;
|
||||
using Sozsoft.MailQueue.MailGeneration.Models;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Guids;
|
||||
using Volo.Abp.TextTemplating;
|
||||
|
|
@ -141,53 +141,53 @@ public class AttachmentGenerator : IAttachmentGenerator, ITransientDependency
|
|||
|
||||
return null;
|
||||
|
||||
// if (model.Filtreler.ContainsKey("QUERY") && model.Filtreler.ContainsKey("QUERYSQL"))
|
||||
// {
|
||||
// /*
|
||||
//Parametrelerden asagidaki sekilde gelen queryleri en alttaki formata donusturuyoruz
|
||||
//params
|
||||
//0 =>
|
||||
// QUERY -> qryDepo_Cikis_CekiListesi_Detayli
|
||||
// QUERYSQL -> Ksp_Depo_Cikis_CekiListesi_Detayli 252993
|
||||
// QUERY2 -> qryDepo_Cikis_CekiListesi_Sub
|
||||
// QUERY2SQL -> KSp_Depo_Cikis_CekiListesi_Sub 252993
|
||||
// if (model.Filtreler.ContainsKey("QUERY") && model.Filtreler.ContainsKey("QUERYSQL"))
|
||||
// {
|
||||
// /*
|
||||
//Parametrelerden asagidaki sekilde gelen queryleri en alttaki formata donusturuyoruz
|
||||
//params
|
||||
//0 =>
|
||||
// QUERY -> qryDepo_Cikis_CekiListesi_Detayli
|
||||
// QUERYSQL -> Ksp_Depo_Cikis_CekiListesi_Detayli 252993
|
||||
// QUERY2 -> qryDepo_Cikis_CekiListesi_Sub
|
||||
// QUERY2SQL -> KSp_Depo_Cikis_CekiListesi_Sub 252993
|
||||
|
||||
//SQL olan keyvalue'lari cikar ve sadece querylerde foreach yap
|
||||
// QUERY -> qryDepo_Cikis_CekiListesi_Detayli
|
||||
// dict.add(item.Value, params[item.Key + "SQL"].Value)
|
||||
// QUERY2 -> qryDepo_Cikis_CekiListesi_Sub
|
||||
// dict.add(item.Value, params[item.Key + "SQL"].Value)
|
||||
|
||||
//SQL olan keyvalue'lari cikar ve sadece querylerde foreach yap
|
||||
// QUERY -> qryDepo_Cikis_CekiListesi_Detayli
|
||||
// dict.add(item.Value, params[item.Key + "SQL"].Value)
|
||||
// QUERY2 -> qryDepo_Cikis_CekiListesi_Sub
|
||||
// dict.add(item.Value, params[item.Key + "SQL"].Value)
|
||||
//Bunlari yeni dict'e ekle
|
||||
//dict
|
||||
//qryDepo_Cikis_CekiListesi_Detayli -> Ksp_Depo_Cikis_CekiListesi_Detayli 252993
|
||||
//qryDepo_Cikis_CekiListesi_Sub -> KSp_Depo_Cikis_CekiListesi_Sub 252993
|
||||
// */
|
||||
// //dict -> key (name), value (sql)
|
||||
// var queries = model.Filtreler
|
||||
// .Where(a => a.Key.StartsWith("QUERY"))
|
||||
// .Where(a => !a.Key.EndsWith("SQL"))
|
||||
// .Where(a => !a.Value.IsNullOrWhiteSpace())
|
||||
// .ToDictionary(x => x.Value, x => model.Filtreler.GetValueOrDefault(x.Key + "SQL"));
|
||||
|
||||
//Bunlari yeni dict'e ekle
|
||||
//dict
|
||||
//qryDepo_Cikis_CekiListesi_Detayli -> Ksp_Depo_Cikis_CekiListesi_Detayli 252993
|
||||
//qryDepo_Cikis_CekiListesi_Sub -> KSp_Depo_Cikis_CekiListesi_Sub 252993
|
||||
// */
|
||||
// //dict -> key (name), value (sql)
|
||||
// var queries = model.Filtreler
|
||||
// .Where(a => a.Key.StartsWith("QUERY"))
|
||||
// .Where(a => !a.Key.EndsWith("SQL"))
|
||||
// .Where(a => !a.Value.IsNullOrWhiteSpace())
|
||||
// .ToDictionary(x => x.Value, x => model.Filtreler.GetValueOrDefault(x.Key + "SQL"));
|
||||
|
||||
// return MsAccessReportManager.GenerateAccessReport(
|
||||
// model.DbAdi,
|
||||
// model.Index,
|
||||
// model.RaporAdi,
|
||||
// model.ExportDosyaTipi,
|
||||
// queries,
|
||||
// attachmentFolder);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// return MsAccessReportManager.GenerateAccessReport(
|
||||
// model.DbAdi,
|
||||
// model.Index,
|
||||
// model.RaporAdi,
|
||||
// string.Join(" AND ", model.Filtreler.Select(a => $"{a.Key}={a.Value}")),
|
||||
// model.ExportDosyaTipi,
|
||||
// attachmentFolder);
|
||||
// }
|
||||
// return MsAccessReportManager.GenerateAccessReport(
|
||||
// model.DbAdi,
|
||||
// model.Index,
|
||||
// model.RaporAdi,
|
||||
// model.ExportDosyaTipi,
|
||||
// queries,
|
||||
// attachmentFolder);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// return MsAccessReportManager.GenerateAccessReport(
|
||||
// model.DbAdi,
|
||||
// model.Index,
|
||||
// model.RaporAdi,
|
||||
// string.Join(" AND ", model.Filtreler.Select(a => $"{a.Key}={a.Value}")),
|
||||
// model.ExportDosyaTipi,
|
||||
// attachmentFolder);
|
||||
// }
|
||||
}
|
||||
|
||||
private async Task<string> ConvertFileToBase64Async(string filePath)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using Microsoft.Extensions.Configuration;
|
||||
using Sozsoft.MailQueue.MailGeneration.Models;
|
||||
using Sozsoft.MailQueue.MailGeneration.Models;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.TextTemplating;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
using System.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sozsoft.MailQueue.Domain.Entities;
|
||||
using Sozsoft.MailQueue.EntityFrameworkCore;
|
||||
using Sozsoft.MailQueue.MailGeneration.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
using Volo.Abp.TextTemplating;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Sozsoft.Sender.Mail;
|
||||
using Sozsoft.MailQueue.Domain.MailGeneration;
|
||||
using Sozsoft.MailQueue.Domain.Shared;
|
||||
using Sozsoft.Sender.Mail;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Volo.Abp.BackgroundWorkers;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
using Volo.Abp.Guids;
|
||||
|
|
@ -182,7 +182,7 @@ public class MailQueueWorker : BackgroundWorkerBase
|
|||
}
|
||||
queue.SendStatus = result.Success;
|
||||
queue.SendTime = Clock.Now;
|
||||
await Repository.UpdateAsync(queue, cancellationToken: cancellationToken);
|
||||
await Repository.UpdateAsync(queue);
|
||||
|
||||
// Attachmentlari silebiliriz
|
||||
try
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
using Amazon;
|
||||
using Amazon.SQS;
|
||||
using Amazon.SQS.Model;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Sozsoft.MailQueue.Domain.Entities;
|
||||
using Sozsoft.Sender.Mail.AmazonSes;
|
||||
using Sozsoft.MailQueue.Domain.Entities;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
using Volo.Abp.Domain.Services;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Sozsoft.MailQueue.Domain;
|
||||
using Sozsoft.MailQueue.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.EntityFrameworkCore;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Sozsoft.MailQueue.Domain;
|
||||
using Sozsoft.MailQueue.Domain;
|
||||
using Sozsoft.MailQueue.Domain.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.EntityFrameworkCore.Modeling;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
using System.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sozsoft.MailQueue.Domain.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using ClosedXML.Excel;
|
||||
using DocumentFormat.OpenXml.Spreadsheet;
|
||||
using Sozsoft.MailQueue.MailGeneration.Models;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
|
|
@ -25,7 +26,7 @@ public class XLSFileGenerator : IFileGenerator, ITransientDependency
|
|||
using var stream = new MemoryStream();
|
||||
workbook.SaveAs(stream);
|
||||
stream.Close();
|
||||
|
||||
|
||||
return await Task.FromResult(stream.ToArray());
|
||||
}
|
||||
|
||||
|
|
@ -79,11 +80,8 @@ public class XLSFileGenerator : IFileGenerator, ITransientDependency
|
|||
var col = table.TabloSutunlari[j];
|
||||
var cell = sheet.Cell(i + 2, j + 1);
|
||||
var value = row.GetValueOrDefault(col.SutunAdi);
|
||||
if (!Enum.TryParse(col.VeriTipi, out XLDataType dataType))
|
||||
{
|
||||
dataType = XLDataType.Text;
|
||||
}
|
||||
|
||||
var dataType = XLDataType.Text;
|
||||
Enum.TryParse(col.VeriTipi, out dataType);
|
||||
switch (dataType)
|
||||
{
|
||||
case XLDataType.Number:
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
using System.Reflection;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Sozsoft.Sender;
|
||||
using Sozsoft.MailQueue.Domain.Shared;
|
||||
using Sozsoft.MailQueue.EntityFrameworkCore;
|
||||
using Sozsoft.Sender;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Volo.Abp.Application;
|
||||
using Volo.Abp.Authorization;
|
||||
using Volo.Abp.Mapperly;
|
||||
using Volo.Abp.Domain;
|
||||
using Volo.Abp.EntityFrameworkCore;
|
||||
using Volo.Abp.Localization;
|
||||
using Volo.Abp.Localization.ExceptionHandling;
|
||||
using Volo.Abp.Mapperly;
|
||||
using Volo.Abp.Modularity;
|
||||
using Volo.Abp.TextTemplating.Razor;
|
||||
using Volo.Abp.VirtualFileSystem;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,6 @@ public class NotificationFilterRequestDto : PagedAndSortedResultRequestDto
|
|||
/// <summary>
|
||||
/// İstek; aktivite sayfasındaki liste ekranından mı geliyor yoksa bildirimleri kontrol etmek için mi
|
||||
/// </summary>
|
||||
public bool IsListRequest { get; set; } = false;
|
||||
public bool IsListRequest { get; set; } = false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
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;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sozsoft.Notifications.Domain;
|
||||
using Sozsoft.Notifications.Entities;
|
||||
using Sozsoft.Notifications.Enums;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.Application.Services;
|
||||
using Volo.Abp.Domain.Entities;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
using Riok.Mapperly.Abstractions;
|
||||
using Sozsoft.Notifications.Domain;
|
||||
using Sozsoft.Notifications.Entities;
|
||||
using Sozsoft.Notifications.Domain;
|
||||
using Sozsoft.Notifications.NotificationRules;
|
||||
using Riok.Mapperly.Abstractions;
|
||||
using Volo.Abp.Mapperly;
|
||||
|
||||
namespace Sozsoft.Notifications.Application;
|
||||
|
|
@ -9,7 +9,7 @@ namespace Sozsoft.Notifications.Application;
|
|||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
||||
public partial class NotificationTypeToNotificationTypeDtoMapper : MapperBase<NotificationType, NotificationTypeDto>
|
||||
{
|
||||
public override partial NotificationTypeDto Map(NotificationType source);
|
||||
public override partial NotificationTypeDto Map(NotificationType source);
|
||||
|
||||
public override partial void Map(NotificationType source, NotificationTypeDto destination);
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ public partial class NotificationTypeToNotificationTypeDtoMapper : MapperBase<No
|
|||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
||||
public partial class NotificationRuleToNotificationRuleDtoMapper : MapperBase<NotificationRule, NotificationRuleDto>
|
||||
{
|
||||
public override partial NotificationRuleDto Map(NotificationRule source);
|
||||
public override partial NotificationRuleDto Map(NotificationRule source);
|
||||
|
||||
public override partial void Map(NotificationRule source, NotificationRuleDto destination);
|
||||
|
||||
|
|
@ -41,15 +41,10 @@ public partial class NotificationRuleToNotificationRuleDtoMapper : MapperBase<No
|
|||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
||||
public partial class NotificationToNotificationDtoMapper : MapperBase<Notification, NotificationDto>
|
||||
{
|
||||
// NotificationType hedefte string. Kaynaktaki navigation entity'si doğrudan
|
||||
// eşlenirse ToString() devreye girip "[ENTITY: NotificationType] Id = ..."
|
||||
// yazısı UI'a düşüyor; bu yüzden açıkça Name alanından beslenir.
|
||||
[MapProperty("NotificationType.Name", nameof(NotificationDto.NotificationType))]
|
||||
[MapperIgnoreTarget(nameof(NotificationDto.CreatorFullname))]
|
||||
[MapperIgnoreTarget(nameof(NotificationDto.CreatorFullname))]
|
||||
[MapperIgnoreTarget(nameof(NotificationDto.TenantId))]
|
||||
public override partial NotificationDto Map(Notification source);
|
||||
|
||||
[MapProperty("NotificationType.Name", nameof(NotificationDto.NotificationType))]
|
||||
[MapperIgnoreTarget(nameof(NotificationDto.CreatorFullname))]
|
||||
[MapperIgnoreTarget(nameof(NotificationDto.TenantId))]
|
||||
public override partial void Map(Notification source, NotificationDto destination);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Sozsoft.Notifications.Domain;
|
||||
using Sozsoft.Notifications.Domain;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Volo.Abp.Application;
|
||||
using Volo.Abp.Mapperly;
|
||||
using Volo.Abp.Modularity;
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sozsoft.Notifications.Domain;
|
||||
using Sozsoft.Notifications.Entities;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Volo.Abp.Application.Services;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
|
|
@ -23,16 +23,22 @@ public class NotificationRuleAppService : CrudAppService<
|
|||
INotificationRuleAppService
|
||||
{
|
||||
private readonly IRepository<NotificationType, Guid> repositoryType;
|
||||
private readonly IOrganizationUnitRepository repositoryOrganizationUnit;
|
||||
private readonly IdentityUserManager userManager;
|
||||
private readonly IIdentityUserRepository repositoryUser;
|
||||
|
||||
public NotificationRuleAppService(
|
||||
IRepository<NotificationType, Guid> repositoryType,
|
||||
IRepository<NotificationRule, Guid> repository,
|
||||
IIdentityUserRepository repositoryUser
|
||||
IdentityUserManager userManager,
|
||||
IIdentityUserRepository repositoryUser,
|
||||
IOrganizationUnitRepository repositoryOrganizationUnit
|
||||
) : base(repository)
|
||||
{
|
||||
this.repositoryType = repositoryType;
|
||||
this.userManager = userManager;
|
||||
this.repositoryUser = repositoryUser;
|
||||
this.repositoryOrganizationUnit = repositoryOrganizationUnit;
|
||||
}
|
||||
|
||||
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 Mail = "Mail"; //Email (ABP Emailing + Amazon SES)
|
||||
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 UiToast = "UiToast"; //UI Toast (ABP UI Toast) Ayarlar/Sistem/Bildirimler/Chrome açık olması gerekiyor.
|
||||
public const string WhatsApp = "WhatsApp"; //WhatsApp (HTTP API, template-based)
|
||||
public const string Telegram = "Telegram"; //Telegram (Bot API, chat based)
|
||||
// public const string Telegram = "Telegram";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
namespace Sozsoft.Notifications.Domain;
|
||||
|
||||
public static class NotificationErrorCodes
|
||||
{
|
||||
//Add your business exception error codes here...
|
||||
}
|
||||
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Volo.Abp.Domain.Entities.Auditing;
|
||||
|
||||
namespace Sozsoft.Notifications.Entities;
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ namespace Sozsoft.Notifications.Entities;
|
|||
public class NotificationRule : FullAuditedEntity<Guid>
|
||||
{
|
||||
public required Guid NotificationTypeId { get; set; }
|
||||
public NotificationType NotificationType { get; set; }
|
||||
|
||||
public NotificationType NotificationType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Alıcı Tipi: User, Role, Organization, All, Custom
|
||||
/// User ise RecipientId UserId olacak
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ public class NotificationIdentifierProvider : INotificationIdentifierProvider
|
|||
NotificationChannels.UiActivity => user.Id.ToString(),
|
||||
NotificationChannels.UiToast => user.Id.ToString(),
|
||||
NotificationChannels.WhatsApp => user.PhoneNumber,
|
||||
NotificationChannels.Telegram => user.GetProperty<string?>("TelegramChatId"),
|
||||
// NotificationChannels.Telegram => user.PhoneNumber,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Sozsoft.Notifications.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
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.");
|
||||
|
||||
|
||||
await repositoryNotification.InsertAsync(new Notification
|
||||
{
|
||||
NotificationTypeId = notificationType.Id,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Sozsoft.Notifications.Domain;
|
||||
using Sozsoft.Notifications.Domain;
|
||||
using Sozsoft.Notifications.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.EntityFrameworkCore;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Sozsoft.Notifications.Domain;
|
||||
using Sozsoft.Notifications.Domain;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Volo.Abp.EntityFrameworkCore;
|
||||
using Volo.Abp.Modularity;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
using Volo.Abp.Localization;
|
||||
|
||||
namespace Sozsoft.Sender.Localization;
|
||||
|
||||
[LocalizationResourceName("SozsoftSender")]
|
||||
public class ErpSenderResource
|
||||
namespace Sozsoft.Sender.Localization
|
||||
{
|
||||
[LocalizationResourceName("SozsoftSender")]
|
||||
public class ErpSenderResource
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Volo.Abp.BackgroundJobs;
|
||||
using Volo.Abp.BackgroundJobs;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.MultiTenancy;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Volo.Abp.MultiTenancy;
|
||||
using Volo.Abp.MultiTenancy;
|
||||
|
||||
namespace Sozsoft.Sender.Mail;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Net.Http.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Volo.Abp.Settings;
|
||||
|
||||
namespace Sozsoft.Sender.Rocket;
|
||||
|
|
@ -11,13 +12,16 @@ public interface IRocketHttpClient
|
|||
public sealed class RocketHttpClient : IRocketHttpClient, IDisposable
|
||||
{
|
||||
private readonly HttpClient httpClient;
|
||||
private readonly ILogger<RocketHttpClient> logger;
|
||||
private readonly ISettingProvider settingProvider;
|
||||
|
||||
public RocketHttpClient(
|
||||
HttpClient httpClient,
|
||||
ILogger<RocketHttpClient> logger,
|
||||
ISettingProvider settingProvider)
|
||||
{
|
||||
this.httpClient = httpClient;
|
||||
this.logger = logger;
|
||||
this.settingProvider = settingProvider;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using Volo.Abp.Settings;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Volo.Abp.Settings;
|
||||
|
||||
namespace Sozsoft.Sender.Sms.PostaGuvercini;
|
||||
|
||||
|
|
@ -10,13 +11,16 @@ public interface IPostaGuverciniHttpClient
|
|||
public sealed class PostaGuverciniHttpClient : IPostaGuverciniHttpClient, IDisposable
|
||||
{
|
||||
private readonly HttpClient httpClient;
|
||||
private readonly ILogger<PostaGuverciniHttpClient> logger;
|
||||
private readonly ISettingProvider settingProvider;
|
||||
|
||||
public PostaGuverciniHttpClient(
|
||||
HttpClient httpClient,
|
||||
ILogger<PostaGuverciniHttpClient> logger,
|
||||
ISettingProvider settingProvider)
|
||||
{
|
||||
this.httpClient = httpClient;
|
||||
this.logger = logger;
|
||||
this.settingProvider = settingProvider;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Sozsoft.Sender.Localization;
|
||||
using Sozsoft.Sender.Localization;
|
||||
using Sozsoft.Sender.Mail;
|
||||
using Sozsoft.Sender.Mail.AmazonSes;
|
||||
using Sozsoft.Sender.Rocket;
|
||||
using Sozsoft.Sender.Sms;
|
||||
using Sozsoft.Sender.Sms.PostaGuvercini;
|
||||
using Sozsoft.Sender.Telegram;
|
||||
using Sozsoft.Sender.WhatsApp;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Volo.Abp.BackgroundJobs;
|
||||
using Volo.Abp.Emailing;
|
||||
using Volo.Abp.Localization;
|
||||
|
|
@ -37,7 +36,6 @@ public class ErpSenderModule : AbpModule
|
|||
context.Services.AddScoped<ISozsoftSmsSender>(sp => sp.GetRequiredService<PostaGuverciniSmsSender>());
|
||||
context.Services.AddScoped<ISozsoftRocketSender>(sp => sp.GetRequiredService<RocketSender>());
|
||||
context.Services.AddScoped<ISozsoftWhatsAppSender>(sp => sp.GetRequiredService<SozsoftWhatsAppSender>());
|
||||
context.Services.AddScoped<ISozsoftTelegramSender>(sp => sp.GetRequiredService<SozsoftTelegramSender>());
|
||||
|
||||
context.Services.AddHttpClient<PostaGuverciniHttpClient>();
|
||||
context.Services.AddScoped<IPostaGuverciniHttpClient>(sp => sp.GetRequiredService<PostaGuverciniHttpClient>());
|
||||
|
|
@ -48,9 +46,6 @@ public class ErpSenderModule : AbpModule
|
|||
context.Services.AddHttpClient<WhatsAppHttpClient>();
|
||||
context.Services.AddScoped<IWhatsAppHttpClient>(sp => sp.GetRequiredService<WhatsAppHttpClient>());
|
||||
|
||||
context.Services.AddHttpClient<TelegramHttpClient>();
|
||||
context.Services.AddScoped<ITelegramHttpClient>(sp => sp.GetRequiredService<TelegramHttpClient>());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
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();
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
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; }
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
namespace Sozsoft.Sender.Telegram;
|
||||
|
||||
public class TelegramSendResult
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
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,5 +1,6 @@
|
|||
using System.Net.Http.Headers;
|
||||
using System.Net.Http.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Volo.Abp.Settings;
|
||||
|
||||
namespace Sozsoft.Sender.WhatsApp;
|
||||
|
|
@ -12,13 +13,16 @@ public interface IWhatsAppHttpClient
|
|||
public sealed class WhatsAppHttpClient : IWhatsAppHttpClient, IDisposable
|
||||
{
|
||||
private readonly HttpClient httpClient;
|
||||
private readonly ILogger<WhatsAppHttpClient> logger;
|
||||
private readonly ISettingProvider settingProvider;
|
||||
|
||||
public WhatsAppHttpClient(
|
||||
HttpClient httpClient,
|
||||
ILogger<WhatsAppHttpClient> logger,
|
||||
ISettingProvider settingProvider)
|
||||
{
|
||||
this.httpClient = httpClient;
|
||||
this.logger = logger;
|
||||
this.settingProvider = settingProvider;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Volo.Abp.Authorization.Permissions;
|
||||
using Volo.Abp.Authorization.Permissions;
|
||||
|
||||
namespace Sozsoft.Settings.Permissions;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
namespace Sozsoft.Settings;
|
||||
|
||||
public class SettingsRemoteServiceConsts
|
||||
{
|
||||
public const string RemoteServiceName = "Settings";
|
||||
|
||||
public const string ModuleName = "settings";
|
||||
}
|
||||
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Sozsoft.Languages;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Volo.Abp.Application.Services;
|
||||
using Volo.Abp.MultiTenancy;
|
||||
|
|
@ -14,17 +15,20 @@ namespace Sozsoft.Settings;
|
|||
public class SettingUiAppService : ApplicationService, ISettingUiAppService
|
||||
{
|
||||
//private readonly IRepository<SettingDefinition, Guid> repositorySettingDefinition;
|
||||
private readonly ILanguageKeyIntegrationService languageKeyIntegrationService;
|
||||
private readonly ISettingDefinitionManager settingDefinitionManager;
|
||||
private readonly ISettingManager settingManager;
|
||||
private readonly ErpSettingDefinitionManager ErpSettingDefinitionManager;
|
||||
private readonly ICurrentTenant currentTenant;
|
||||
|
||||
public SettingUiAppService(
|
||||
ILanguageKeyIntegrationService languageKeyIntegrationService,
|
||||
ISettingDefinitionManager settingDefinitionManager,
|
||||
ISettingManager settingManager,
|
||||
ErpSettingDefinitionManager ErpSettingDefinitionManager,
|
||||
ICurrentTenant currentTenant)
|
||||
{
|
||||
this.languageKeyIntegrationService = languageKeyIntegrationService;
|
||||
this.settingDefinitionManager = settingDefinitionManager;
|
||||
this.settingManager = settingManager;
|
||||
this.ErpSettingDefinitionManager = ErpSettingDefinitionManager;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using Riok.Mapperly.Abstractions;
|
||||
using Sozsoft.Settings.Entities;
|
||||
using Riok.Mapperly.Abstractions;
|
||||
using Volo.Abp.Mapperly;
|
||||
|
||||
namespace Sozsoft.Settings;
|
||||
|
|
@ -7,7 +7,7 @@ namespace Sozsoft.Settings;
|
|||
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
|
||||
public partial class SettingDefinitionToSettingDefinitionDtoMapper : TwoWayMapperBase<SettingDefinition, SettingDefinitionDto>
|
||||
{
|
||||
public override partial SettingDefinitionDto Map(SettingDefinition source);
|
||||
public override partial SettingDefinitionDto Map(SettingDefinition source);
|
||||
|
||||
public override partial void Map(SettingDefinition source, SettingDefinitionDto destination);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
namespace Sozsoft.Settings;
|
||||
|
||||
public static class SettingsErrorCodes
|
||||
{
|
||||
//Add your business exception error codes here...
|
||||
}
|
||||
|
||||
|
|
@ -2,9 +2,9 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Sozsoft.Settings.Localization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Sozsoft.Settings.Localization;
|
||||
using Volo.Abp.Domain.Repositories;
|
||||
using Volo.Abp.Domain.Services;
|
||||
using Volo.Abp.Settings;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Volo.Abp.Modularity;
|
||||
using Volo.Abp.Modularity;
|
||||
using Volo.Abp.SettingManagement;
|
||||
using Volo.Abp.Settings;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Sozsoft.Settings.Entities;
|
||||
using Sozsoft.Settings.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.EntityFrameworkCore;
|
||||
using Volo.Abp.SettingManagement.EntityFrameworkCore;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Sozsoft.Settings.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.ChangeTracking;
|
||||
using Sozsoft.Settings.Entities;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.EntityFrameworkCore.Modeling;
|
||||
using Volo.Abp.SettingManagement.EntityFrameworkCore;
|
||||
|
|
@ -15,7 +15,7 @@ public static class SettingsDbContextModelCreatingExtensions
|
|||
public static void ConfigureSettings(this ModelBuilder builder)
|
||||
{
|
||||
Check.NotNull(builder, nameof(builder));
|
||||
|
||||
|
||||
builder.ConfigureSettingManagement();
|
||||
|
||||
builder.Entity<SettingDefinition>(b =>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
namespace Sozsoft.SqlQueryManager.Application.Contracts;
|
||||
namespace Sozsoft.SqlQueryManager.Application.Contracts;
|
||||
|
||||
public class DatabaseTableDto
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.Application.Services;
|
||||
|
||||
|
|
@ -30,16 +30,29 @@ public interface ISqlObjectManagerAppService : IApplicationService
|
|||
Task<string> GetNativeObjectDefinitionAsync(string dataSourceCode, string schemaName, string objectName);
|
||||
|
||||
/// <summary>
|
||||
/// 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/.
|
||||
/// Saves the T-SQL script to Seeds/SqlData/{fileName}.sql in the DbMigrator project.
|
||||
/// Called automatically after a successful SqlTableDesigner deploy so the script can be re-seeded.
|
||||
/// </summary>
|
||||
Task SaveTableScriptAsync(SaveTableScriptDto input);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes matching SQL seed files from the current scope's object folder when objects are
|
||||
/// dropped from the UI.
|
||||
/// Deletes matching SQL seed files from Seeds/SqlData when objects are dropped from the UI.
|
||||
/// Non-existing files are ignored.
|
||||
/// </summary>
|
||||
Task DeleteSqlDataFilesAsync(DeleteSqlDataFilesDto input);
|
||||
|
||||
/// <summary>
|
||||
/// Lists .sql files currently available under DbMigrator Seeds/SqlData.
|
||||
/// </summary>
|
||||
Task<List<SqlDataFileDto>> GetSqlDataFilesAsync(string dataDirectoryName = "SqlData", string relativePath = "");
|
||||
|
||||
/// <summary>
|
||||
/// Reads a .sql seed file content from the selected data directory.
|
||||
/// </summary>
|
||||
Task<string> GetSqlDataFileContentAsync(string dataDirectoryName = "SqlData", string relativePath = "");
|
||||
|
||||
/// <summary>
|
||||
/// Moves a SQL seed file between the selected data directory root and HostData.
|
||||
/// </summary>
|
||||
Task MoveSqlDataFileAsync(MoveSqlDataFileDto input);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace Sozsoft.SqlQueryManager.Application.Contracts;
|
||||
|
||||
public class SqlDataFileDto
|
||||
{
|
||||
public string FileName { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string RelativePath { get; set; } = string.Empty;
|
||||
public bool IsDirectory { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Sozsoft.SqlQueryManager.Application.Contracts;
|
||||
|
||||
|
|
@ -20,8 +20,7 @@ public class SqlQueryExecutionResultDto
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Input for saving a T-SQL script file to the seeds folder
|
||||
/// ({App:SeedsPath}/{host|tenants/{tenantId}}/{sql|postgres}/object).
|
||||
/// Input for saving a T-SQL script file to DbMigrator Seeds/SqlData.
|
||||
/// </summary>
|
||||
public class SaveTableScriptDto
|
||||
{
|
||||
|
|
@ -37,8 +36,7 @@ public class SaveTableScriptDto
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Input for deleting seed files from the seeds folder
|
||||
/// ({App:SeedsPath}/{host|tenants/{tenantId}}/{sql|postgres}/object).
|
||||
/// Input for deleting seed files from DbMigrator Seeds/SqlData.
|
||||
/// </summary>
|
||||
public class DeleteSqlDataFilesDto
|
||||
{
|
||||
|
|
@ -48,3 +46,10 @@ public class DeleteSqlDataFilesDto
|
|||
/// </summary>
|
||||
public List<string> FileNames { get; set; } = new();
|
||||
}
|
||||
|
||||
public class MoveSqlDataFileDto
|
||||
{
|
||||
public string DataDirectoryName { get; set; } = string.Empty;
|
||||
public string SourceRelativePath { get; set; } = string.Empty;
|
||||
public string TargetRelativePath { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Sozsoft.SqlQueryManager.Application.Contracts;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Volo.Abp.Application;
|
||||
using Volo.Abp.Application;
|
||||
using Volo.Abp.Authorization;
|
||||
using Volo.Abp.Modularity;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
namespace Sozsoft.SqlQueryManager.Application.Contracts;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Sozsoft.SqlQueryManager.Application.Contracts;
|
||||
|
||||
public class SqlTemplateDto
|
||||
{
|
||||
|
|
@ -7,3 +9,10 @@ public class SqlTemplateDto
|
|||
public string Description { get; set; }
|
||||
public string Template { get; set; }
|
||||
}
|
||||
|
||||
public class GetTemplateInput
|
||||
{
|
||||
public string TemplateType { get; set; }
|
||||
public string ObjectName { get; set; }
|
||||
public string SchemaName { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,21 +2,19 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Sozsoft.SqlQueryManager.Application.Contracts;
|
||||
using Sozsoft.SqlQueryManager.Domain.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Sozsoft.Platform.Data.Seeds;
|
||||
using Sozsoft.Platform.Enums;
|
||||
using Sozsoft.Platform.Queries;
|
||||
using Sozsoft.Settings;
|
||||
using Sozsoft.SqlQueryManager.Application.Contracts;
|
||||
using Sozsoft.SqlQueryManager.Domain.Services;
|
||||
using Volo.Abp.Application.Services;
|
||||
using Volo.Abp.MultiTenancy;
|
||||
using System.Text.RegularExpressions;
|
||||
using Sozsoft.Platform.Enums;
|
||||
using Sozsoft.Platform.Queries;
|
||||
|
||||
namespace Sozsoft.SqlQueryManager.Application;
|
||||
|
||||
|
|
@ -50,7 +48,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
|
|||
private readonly IDataSourceManager _dataSourceManager;
|
||||
private readonly ICurrentTenant _currentTenant;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly IHostEnvironment _hostEnvironment;
|
||||
private readonly ILogger<SqlObjectManagerAppService> _logger;
|
||||
|
||||
public SqlObjectManagerAppService(
|
||||
|
|
@ -59,7 +57,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
|
|||
IDataSourceManager dataSourceManager,
|
||||
ICurrentTenant currentTenant,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
IConfiguration configuration,
|
||||
IHostEnvironment hostEnvironment,
|
||||
ILogger<SqlObjectManagerAppService> logger)
|
||||
{
|
||||
_sqlExecutorService = sqlExecutorService;
|
||||
|
|
@ -67,7 +65,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
|
|||
_dataSourceManager = dataSourceManager;
|
||||
_currentTenant = currentTenant;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_configuration = configuration;
|
||||
_hostEnvironment = hostEnvironment;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
|
@ -956,27 +954,191 @@ FROM (
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Script dosyalarinin yazilacagi klasor. SqlDataSeeder ile ayni cozumleyiciyi kullanir
|
||||
/// (App:SeedsPath -> configs/seeds), aksi halde deploy edilen nesne seed olarak geri okunmaz.
|
||||
/// <para>
|
||||
/// Yol, islemin yapildigi veritabani kapsamini izler: host (varsayilan) baglaminda
|
||||
/// <c>host/{sql|postgres}/object</c>, bir tenant secilmisken
|
||||
/// <c>tenants/{tenantId}/{sql|postgres}/object</c>. Boylece nesne, seed sirasinda
|
||||
/// olusturuldugu veritabaninda yeniden olusturulur.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
private string ResolveSqlDataOutputPath()
|
||||
[HttpGet("api/app/sql-object-manager/sql-data-files")]
|
||||
public Task<List<SqlDataFileDto>> GetSqlDataFilesAsync(
|
||||
[FromQuery] string dataDirectoryName = "SqlData",
|
||||
[FromQuery] string relativePath = "")
|
||||
{
|
||||
var providerDirectoryName = SeedPathResolver.GetProviderFolderName(
|
||||
SettingsConsts.DefaultDatabaseProvider == SettingsConsts.DatabaseProvider.PostgreSql);
|
||||
ValidateTenantAccess();
|
||||
|
||||
return SeedPathResolver.GetScopePath(
|
||||
_configuration,
|
||||
_currentTenant.Id,
|
||||
providerDirectoryName,
|
||||
SeedPathResolver.ObjectFolder);
|
||||
try
|
||||
{
|
||||
var rootPath = ResolveSqlDataOutputPath(dataDirectoryName);
|
||||
var outputPath = ResolveSqlDataChildPath(rootPath, relativePath);
|
||||
if (!Directory.Exists(outputPath))
|
||||
return Task.FromResult(new List<SqlDataFileDto>());
|
||||
|
||||
var directories = Directory.GetDirectories(outputPath, "*", SearchOption.TopDirectoryOnly)
|
||||
.Where(d => string.Equals(Path.GetFileName(d), "HostData", StringComparison.OrdinalIgnoreCase))
|
||||
.Select(d => new SqlDataFileDto
|
||||
{
|
||||
FileName = Path.GetFileName(d)!,
|
||||
Name = Path.GetFileName(d)!,
|
||||
RelativePath = BuildSqlDataRelativePath(relativePath, Path.GetFileName(d)!),
|
||||
IsDirectory = true,
|
||||
CreatedAt = Directory.GetCreationTime(d)
|
||||
});
|
||||
|
||||
var files = Directory.GetFiles(outputPath, "*.sql", SearchOption.TopDirectoryOnly)
|
||||
.Select(f => new SqlDataFileDto
|
||||
{
|
||||
FileName = Path.GetFileName(f)!,
|
||||
Name = Path.GetFileName(f)!,
|
||||
RelativePath = BuildSqlDataRelativePath(relativePath, Path.GetFileName(f)!),
|
||||
IsDirectory = false,
|
||||
CreatedAt = File.GetCreationTime(f)
|
||||
})
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x.Name));
|
||||
|
||||
var entries = directories
|
||||
.Concat(files)
|
||||
.OrderByDescending(x => x.IsDirectory)
|
||||
.ThenBy(x => x.Name, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
|
||||
return Task.FromResult(entries);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to list SQL seed files: {Message}", ex.Message);
|
||||
return Task.FromResult(new List<SqlDataFileDto>());
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("api/app/sql-object-manager/sql-data-file-content")]
|
||||
public async Task<string> GetSqlDataFileContentAsync(
|
||||
[FromQuery] string dataDirectoryName = "SqlData",
|
||||
[FromQuery] string relativePath = "")
|
||||
{
|
||||
ValidateTenantAccess();
|
||||
|
||||
var rootPath = ResolveSqlDataOutputPath(dataDirectoryName);
|
||||
var filePath = ResolveSqlDataChildPath(rootPath, relativePath);
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
throw new Volo.Abp.UserFriendlyException("SQL seed file was not found.");
|
||||
|
||||
if (!string.Equals(Path.GetExtension(filePath), ".sql", StringComparison.OrdinalIgnoreCase))
|
||||
throw new Volo.Abp.UserFriendlyException("Only .sql files can be previewed.");
|
||||
|
||||
return await File.ReadAllTextAsync(filePath);
|
||||
}
|
||||
|
||||
[HttpPost("api/app/sql-object-manager/move-sql-data-file")]
|
||||
public Task MoveSqlDataFileAsync(MoveSqlDataFileDto input)
|
||||
{
|
||||
ValidateTenantAccess();
|
||||
|
||||
if (input == null)
|
||||
throw new Volo.Abp.UserFriendlyException("Invalid move request.");
|
||||
|
||||
var rootPath = ResolveSqlDataOutputPath(input.DataDirectoryName);
|
||||
var sourcePath = ResolveSqlDataChildPath(rootPath, input.SourceRelativePath);
|
||||
var targetPath = ResolveSqlDataChildPath(rootPath, input.TargetRelativePath);
|
||||
|
||||
if (!File.Exists(sourcePath))
|
||||
throw new Volo.Abp.UserFriendlyException("Source file was not found.");
|
||||
|
||||
if (!string.Equals(Path.GetExtension(sourcePath), ".sql", StringComparison.OrdinalIgnoreCase) ||
|
||||
!string.Equals(Path.GetExtension(targetPath), ".sql", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new Volo.Abp.UserFriendlyException("Only .sql files can be moved.");
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(targetPath)!);
|
||||
|
||||
if (File.Exists(targetPath))
|
||||
throw new Volo.Abp.UserFriendlyException("A file with the same name already exists in the target folder.");
|
||||
|
||||
File.Move(sourcePath, targetPath);
|
||||
_logger.LogInformation("SQL seed file moved from {SourcePath} to {TargetPath}", sourcePath, targetPath);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private string ResolveSqlDataOutputPath()
|
||||
{
|
||||
return ResolveSqlDataOutputPath("SqlData");
|
||||
}
|
||||
|
||||
private string ResolveSqlDataOutputPath(string dataDirectoryName)
|
||||
{
|
||||
const string dbMigratorName = "Sozsoft.Platform.DbMigrator";
|
||||
var safeDirectoryName = NormalizeSqlDataDirectoryName(dataDirectoryName);
|
||||
var dir = new DirectoryInfo(_hostEnvironment.ContentRootPath);
|
||||
|
||||
while (dir != null)
|
||||
{
|
||||
var candidate = Path.Combine(dir.FullName, "src", dbMigratorName, "Seeds");
|
||||
if (Directory.Exists(candidate))
|
||||
return Path.Combine(candidate, safeDirectoryName);
|
||||
|
||||
candidate = Path.Combine(dir.FullName, dbMigratorName, "Seeds");
|
||||
if (Directory.Exists(candidate))
|
||||
return Path.Combine(candidate, safeDirectoryName);
|
||||
|
||||
dir = dir.Parent;
|
||||
}
|
||||
|
||||
return Path.Combine(_hostEnvironment.ContentRootPath, "Seeds", safeDirectoryName);
|
||||
}
|
||||
|
||||
private static string NormalizeSqlDataDirectoryName(string dataDirectoryName)
|
||||
{
|
||||
return string.Equals(dataDirectoryName, "PostgresData", StringComparison.OrdinalIgnoreCase)
|
||||
? "PostgresData"
|
||||
: "SqlData";
|
||||
}
|
||||
|
||||
private static string ResolveSqlDataChildPath(string rootPath, string relativePath)
|
||||
{
|
||||
var normalized = NormalizeSqlDataRelativePath(relativePath);
|
||||
var fullPath = Path.GetFullPath(Path.Combine(rootPath, normalized));
|
||||
var fullRoot = Path.GetFullPath(rootPath);
|
||||
var fullRootWithSeparator = fullRoot.EndsWith(Path.DirectorySeparatorChar)
|
||||
? fullRoot
|
||||
: fullRoot + Path.DirectorySeparatorChar;
|
||||
|
||||
if (!string.Equals(fullPath, fullRoot, StringComparison.OrdinalIgnoreCase) &&
|
||||
!fullPath.StartsWith(fullRootWithSeparator, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new Volo.Abp.UserFriendlyException("Invalid path.");
|
||||
}
|
||||
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
private static string NormalizeSqlDataRelativePath(string relativePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(relativePath))
|
||||
return string.Empty;
|
||||
|
||||
var normalized = relativePath.Replace('\\', '/').Trim('/');
|
||||
var parts = normalized.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (parts.Length == 0)
|
||||
return string.Empty;
|
||||
|
||||
if (parts.Any(p => p == "." || p == ".." || p.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0))
|
||||
throw new Volo.Abp.UserFriendlyException("Invalid path.");
|
||||
|
||||
var isRootFile = parts.Length == 1 && parts[0].EndsWith(".sql", StringComparison.OrdinalIgnoreCase);
|
||||
var isHostDataFolder = parts.Length == 1 && string.Equals(parts[0], "HostData", StringComparison.OrdinalIgnoreCase);
|
||||
var isHostDataFile = parts.Length == 2 &&
|
||||
string.Equals(parts[0], "HostData", StringComparison.OrdinalIgnoreCase) &&
|
||||
parts[1].EndsWith(".sql", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (!isRootFile && !isHostDataFolder && !isHostDataFile)
|
||||
throw new Volo.Abp.UserFriendlyException("Invalid path.");
|
||||
|
||||
return Path.Combine(parts);
|
||||
}
|
||||
|
||||
private static string BuildSqlDataRelativePath(string parentRelativePath, string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(parentRelativePath))
|
||||
return name;
|
||||
|
||||
return $"{parentRelativePath.Trim('/', '\\')}/{name}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
namespace Sozsoft.SqlQueryManager.Domain.Shared;
|
||||
namespace Sozsoft.SqlQueryManager.Domain.Shared;
|
||||
|
||||
// SqlObjectType, SqlFunctionType and SqlQueryStatus enums removed:
|
||||
// The module no longer persists SQL objects and therefore does not need these enums.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Sozsoft.SqlQueryManager.Domain.Shared;
|
||||
using Sozsoft.SqlQueryManager.Domain.Shared;
|
||||
using Volo.Abp.Domain;
|
||||
using Volo.Abp.Localization;
|
||||
using Volo.Abp.Localization.ExceptionHandling;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
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;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Sozsoft.SqlQueryManager.Domain.Services;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Sozsoft.Platform.DynamicData;
|
||||
using Sozsoft.Platform.Enums;
|
||||
using Sozsoft.Platform.Queries;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.Domain.Services;
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ public class SqlExecutorService : DomainService, ISqlExecutorService
|
|||
{
|
||||
// Get DataSource to determine database type
|
||||
var dataSource = await _dataSourceManager.GetDataSourceAsync(
|
||||
CurrentTenant.IsAvailable,
|
||||
CurrentTenant.IsAvailable,
|
||||
dataSourceCode);
|
||||
|
||||
if (dataSource == null)
|
||||
|
|
@ -53,7 +53,7 @@ public class SqlExecutorService : DomainService, ISqlExecutorService
|
|||
}
|
||||
|
||||
var repository = _serviceProvider.GetKeyedService<IDynamicDataRepository>(repositoryKey);
|
||||
|
||||
|
||||
if (repository == null)
|
||||
{
|
||||
throw new BusinessException("SqlQueryManager:RepositoryNotFound")
|
||||
|
|
@ -64,8 +64,8 @@ public class SqlExecutorService : DomainService, ISqlExecutorService
|
|||
}
|
||||
|
||||
public async Task<SqlExecutionResult> ExecuteQueryAsync(
|
||||
string sql,
|
||||
string dataSourceCode,
|
||||
string sql,
|
||||
string dataSourceCode,
|
||||
Dictionary<string, object> parameters = null)
|
||||
{
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
|
|
@ -74,7 +74,7 @@ public class SqlExecutorService : DomainService, ISqlExecutorService
|
|||
try
|
||||
{
|
||||
var connectionString = await _dataSourceManager.GetConnectionStringAsync(
|
||||
CurrentTenant.IsAvailable,
|
||||
CurrentTenant.IsAvailable,
|
||||
dataSourceCode);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(connectionString))
|
||||
|
|
@ -107,8 +107,8 @@ public class SqlExecutorService : DomainService, ISqlExecutorService
|
|||
}
|
||||
|
||||
public async Task<SqlExecutionResult> ExecuteNonQueryAsync(
|
||||
string sql,
|
||||
string dataSourceCode,
|
||||
string sql,
|
||||
string dataSourceCode,
|
||||
Dictionary<string, object> parameters = null)
|
||||
{
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
|
|
@ -117,7 +117,7 @@ public class SqlExecutorService : DomainService, ISqlExecutorService
|
|||
try
|
||||
{
|
||||
var connectionString = await _dataSourceManager.GetConnectionStringAsync(
|
||||
CurrentTenant.IsAvailable,
|
||||
CurrentTenant.IsAvailable,
|
||||
dataSourceCode);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(connectionString))
|
||||
|
|
@ -149,8 +149,8 @@ public class SqlExecutorService : DomainService, ISqlExecutorService
|
|||
}
|
||||
|
||||
public async Task<SqlExecutionResult> ExecuteScalarAsync<T>(
|
||||
string sql,
|
||||
string dataSourceCode,
|
||||
string sql,
|
||||
string dataSourceCode,
|
||||
Dictionary<string, object> parameters = null)
|
||||
{
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
|
|
@ -159,7 +159,7 @@ public class SqlExecutorService : DomainService, ISqlExecutorService
|
|||
try
|
||||
{
|
||||
var connectionString = await _dataSourceManager.GetConnectionStringAsync(
|
||||
CurrentTenant.IsAvailable,
|
||||
CurrentTenant.IsAvailable,
|
||||
dataSourceCode);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(connectionString))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
|
||||
namespace Sozsoft.SqlQueryManager.Domain.Services;
|
||||
|
|
@ -47,7 +48,7 @@ GO";
|
|||
public string GetViewTemplate(string viewName, string schemaName = "dbo", bool withSchemaBinding = false)
|
||||
{
|
||||
var schemaBindingClause = withSchemaBinding ? " WITH SCHEMABINDING" : "";
|
||||
|
||||
|
||||
return $@"-- =============================================
|
||||
-- Author: <Author Name>
|
||||
-- Create date: <Create Date>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using Volo.Abp.Domain;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Volo.Abp.Domain;
|
||||
using Volo.Abp.Modularity;
|
||||
|
||||
namespace Sozsoft.SqlQueryManager;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.EntityFrameworkCore;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Volo.Abp.EntityFrameworkCore;
|
||||
using Volo.Abp.Modularity;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Volo.Abp.Application.Dtos;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
|
||||
namespace Sozsoft.Platform.AuditLogs;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
using System;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
|
||||
namespace Sozsoft.Platform.ChangeLogs;
|
||||
|
||||
public class ChangeLogDto : EntityDto<Guid>
|
||||
{
|
||||
public string Version { get; set; }
|
||||
public DateTime ReleaseDate { get; set; }
|
||||
|
||||
/// <summary>Sürüm notu maddeleri (entity'de satır satır saklanır).</summary>
|
||||
public string[] ChangeLog { get; set; }
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ public class CrudEndpointDto : FullAuditedEntityDto<Guid>
|
|||
public string OperationType { get; set; } = string.Empty;
|
||||
public string CsharpCode { get; set; } = string.Empty;
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
|
||||
// Navigation property for display purposes
|
||||
public string? EntityDisplayName { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,31 +7,21 @@ namespace Sozsoft.Platform.DeveloperKit;
|
|||
public class CustomComponentDto : FullAuditedEntityDto<Guid>
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
/// <summary>Rotasi olmayan bilesende (ust bilgi, alt bilgi) bostur.</summary>
|
||||
public string? RoutePath { get; set; }
|
||||
public string RoutePath { get; set; } = string.Empty;
|
||||
public string Code { get; set; } = string.Empty;
|
||||
public string? Props { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
public string? Dependencies { get; set; } // JSON string of component names
|
||||
|
||||
/// <summary>
|
||||
/// Data sekmesinde kullanilan API'ler; JSON dizisi. Props icindeki designer dokumanindan
|
||||
/// kaydetme sirasinda sunucu tarafinda uretilir, istemciden gonderilmez.
|
||||
/// </summary>
|
||||
public string? DataSources { get; set; }
|
||||
}
|
||||
|
||||
public class CreateUpdateCustomComponentDto
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// Bilesenin kendi rotasi. Bos birakilabilir: rotasi olmayan bir bilesen (ornegin
|
||||
/// baska bir ekranin icinde cizilen alt bilgi) hicbir adrese baglanmaz.
|
||||
/// </summary>
|
||||
[Required]
|
||||
[StringLength(512)]
|
||||
[RegularExpression(@"^(/.*)?$", ErrorMessage = "RoutePath must start with '/'.")]
|
||||
public string? RoutePath { get; set; }
|
||||
[RegularExpression(@"^/.*", ErrorMessage = "RoutePath must start with '/'.")]
|
||||
public string RoutePath { get; set; } = string.Empty;
|
||||
public string Code { get; set; } = string.Empty;
|
||||
public string? Props { get; set; }
|
||||
public string? Description { get; set; }
|
||||
|
|
|
|||
|
|
@ -12,17 +12,9 @@ public interface ICrudEndpointAppService : ICrudAppService<
|
|||
PagedAndSortedResultRequestDto,
|
||||
CreateUpdateCrudEndpointDto>
|
||||
{
|
||||
Task<List<CrudEndpointDto>> GetActiveEndpointsAsync();
|
||||
Task<List<CrudEndpointDto>> GetEndpointsByEntityAsync(string entityName);
|
||||
Task<CrudEndpointDto> ToggleAsync(Guid id);
|
||||
/// <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);
|
||||
Task<PagedResultDto<CrudEndpointDto>> GenerateCrudEndpointsAsync(string entityName);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Volo.Abp.Application.Dtos;
|
||||
using Volo.Abp.Application.Services;
|
||||
|
||||
|
|
@ -10,4 +12,7 @@ public interface ICustomComponentAppService : ICrudAppService<
|
|||
PagedAndSortedResultRequestDto,
|
||||
CreateUpdateCustomComponentDto>
|
||||
{
|
||||
Task<List<CustomComponentDto>> GetActiveComponentsAsync();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#nullable enable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Collections.Generic;
|
||||
using Volo.Abp.Content;
|
||||
|
||||
namespace Sozsoft.Platform.FileManagement;
|
||||
|
|
@ -11,7 +11,7 @@ public class CreateFolderDto
|
|||
[Required]
|
||||
[StringLength(255)]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
|
||||
public string? ParentId { get; set; }
|
||||
|
||||
public string? TenantId { get; set; }
|
||||
|
|
@ -37,11 +37,11 @@ public class UploadFileDto
|
|||
{
|
||||
[Required]
|
||||
public string FileName { get; set; } = string.Empty;
|
||||
|
||||
|
||||
public string? ParentId { get; set; }
|
||||
|
||||
public string? TenantId { get; set; }
|
||||
|
||||
|
||||
// NoteModal pattern - Files array
|
||||
public IRemoteStreamContent[]? Files { get; set; }
|
||||
}
|
||||
|
|
@ -50,7 +50,7 @@ public class SearchFilesDto
|
|||
{
|
||||
[Required]
|
||||
public string Query { get; set; } = string.Empty;
|
||||
|
||||
|
||||
public string? ParentId { get; set; }
|
||||
|
||||
public string? TenantId { get; set; }
|
||||
|
|
@ -64,11 +64,11 @@ public class BulkDeleteDto
|
|||
public string? TenantId { get; set; }
|
||||
}
|
||||
|
||||
public class CopyItemsDto
|
||||
public class CopyItemsDto
|
||||
{
|
||||
[Required]
|
||||
public List<string> ItemIds { get; set; } = new();
|
||||
|
||||
|
||||
public string? TargetFolderId { get; set; }
|
||||
|
||||
public string? TenantId { get; set; }
|
||||
|
|
@ -78,7 +78,7 @@ public class MoveItemsDto
|
|||
{
|
||||
[Required]
|
||||
public List<string> ItemIds { get; set; } = new();
|
||||
|
||||
|
||||
public string? TargetFolderId { get; set; }
|
||||
|
||||
public string? TenantId { get; set; }
|
||||
|
|
|
|||
|
|
@ -44,6 +44,34 @@ public class ForumCategoryDto : FullAuditedEntityDto<Guid>
|
|||
public Guid? LastPostUserId { get; set; }
|
||||
}
|
||||
|
||||
public class CreateForumCategoryDto
|
||||
{
|
||||
public Guid? TenantId { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(100)]
|
||||
public string Name { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(100)]
|
||||
public string Slug { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(500)]
|
||||
public string Description { get; set; }
|
||||
|
||||
[StringLength(10)]
|
||||
public string Icon { get; set; }
|
||||
|
||||
public int DisplayOrder { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
public bool IsLocked { get; set; } = false;
|
||||
}
|
||||
|
||||
public class UpdateForumCategoryDto : CreateForumCategoryDto
|
||||
{
|
||||
}
|
||||
|
||||
public class GetCategoriesInput : PagedAndSortedResultRequestDto
|
||||
{
|
||||
public bool? IsActive { get; set; }
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ public interface IForumAppService : IApplicationService
|
|||
Task<PagedResultDto<ForumCategoryDto>> GetCategoriesAsync(GetCategoriesInput input);
|
||||
Task<ForumCategoryDto> GetCategoryAsync(Guid id);
|
||||
Task<ForumCategoryDto> GetCategoryBySlugAsync(string slug);
|
||||
Task<ForumCategoryDto> CreateCategoryAsync(CreateForumCategoryDto input);
|
||||
Task<ForumCategoryDto> UpdateCategoryAsync(Guid id, UpdateForumCategoryDto input);
|
||||
Task DeleteCategoryAsync(Guid id);
|
||||
|
||||
// Topics
|
||||
Task<PagedResultDto<ForumTopicDto>> GetTopicsAsync(GetTopicsInput input);
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ namespace Sozsoft.Platform.Identity.Dto;
|
|||
|
||||
public class AssignedBranchViewModel
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string Name { 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