Compare commits
3 Commits
refactor/w
...
feat/signF
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c9cb3f6aa8 | ||
|
|
4b3c5907ae | ||
| cce240125d |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -365,9 +365,3 @@ FodyWeavers.xsd
|
||||
/EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/.vscode
|
||||
/EnvelopeGenerator.Tests.Application/Services/BugFixTests.cs
|
||||
/EnvelopeGenerator.Tests.Application/annotations.json
|
||||
/EnvelopeGenerator.Server/EnvelopeGenerator.Server/TekH - SoftHSM Test.md
|
||||
/EnvelopeGenerator.Server/EnvelopeGenerator.Server/tekh_softHSM_test.md
|
||||
/EnvelopeGenerator.Server/EnvelopeGenerator.Server/publish-output
|
||||
/EnvelopeGenerator.Server/EnvelopeGenerator.Server/tekh_softHSM_test.md
|
||||
/ignore/EnvelopeGenerator.Form
|
||||
/EnvelopeGenerator.Server/EnvelopeGenerator.Server/appsettings.Secrets.json
|
||||
|
||||
414
AGENTS.md
414
AGENTS.md
@@ -1,414 +0,0 @@
|
||||
# EnvelopeGenerator - Agent Guide
|
||||
|
||||
## Must Read First
|
||||
- **`COPILOT_CONTEXT.md`** - Architecture, coordinate systems, migration status
|
||||
- **`FORM_APPLICATION_CONTEXT.md`** - Legacy VB.NET features to migrate
|
||||
|
||||
---
|
||||
|
||||
## Documentation Catalog
|
||||
|
||||
This section explains every markdown file in the repo: what it contains, why it exists, and when you need it. Read this before deciding which file to open.
|
||||
|
||||
### Core Architecture Docs (Active — Always Relevant)
|
||||
|
||||
#### `AGENTS.md` ← you are here
|
||||
**What:** Authoritative agent/copilot onboarding guide. Covers project structure, route table, render mode rules, coordinate system, API gaps, and common mistakes.
|
||||
**Why it exists:** Single source of truth for agents so they don't re-derive architecture from code on every session.
|
||||
**When you need it:** Always. Read first on every session.
|
||||
**Maintenance:** Update whenever project structure, routes, render modes, or naming conventions change.
|
||||
|
||||
#### `COPILOT_CONTEXT.md`
|
||||
**What:** Deep-dive architecture reference. Covers the full Blazor Auto hybrid setup, authentication flows (JWT + cookies, per-envelope receiver tokens), service registration patterns, coordinate system proof, and an explicit "prefer Server/Server.Client over old WebUI/ReceiverUI" rule.
|
||||
**Why it exists:** Prevents agents from re-deriving or misinterpreting the hybrid SSR+WASM architecture. Also documents coordinate system evidence to prevent regressions (inches vs. points vs. pixels).
|
||||
**When you need it:**
|
||||
- Implementing any new page or service
|
||||
- Debugging auth/cookie issues
|
||||
- Any coordinate conversion question
|
||||
- Unsure which project a new file should go in
|
||||
**Maintenance:** Update when render mode assignments change, new auth schemes are added, or the coordinate system is touched.
|
||||
|
||||
#### `FORM_APPLICATION_CONTEXT.md`
|
||||
**What:** Comprehensive reverse-engineering of the legacy VB.NET WinForms app (`EnvelopeGenerator.Form`). Documents every form (frmMain, frmEnvelopeEditor, frmFieldEditor, etc.), their UI layout, toolbar actions, data models, coordinate system, and a feature-by-feature migration mapping table to `EnvelopeGenerator.Server`.
|
||||
**Why it exists:** The WinForms app is the functional specification for the sender-side web UI. It documents the full intended workflow that is being migrated to Blazor — especially the envelope creation, signature field placement, and dashboard features that are not yet implemented.
|
||||
**When you need it:**
|
||||
- Implementing any sender-side feature (`/sender`, `/sender/editor`, `/sender/envelope/{id}`)
|
||||
- Understanding what a specific toolbar button or form should do
|
||||
- Designing API endpoints for sender workflow
|
||||
- Checking coordinate system behavior (INCHES proof is here)
|
||||
**Maintenance:** Update the mapping table as features are implemented (change `❌ Not implemented` → `✅ Exists`). Do not rename `ReceiverUI` back — it was the old project name; current target is `EnvelopeGenerator.Server`.
|
||||
|
||||
#### `RECEIVER_PDF_VIEWER_CONTEXT.md`
|
||||
**What:** Detailed technical reference for the receiver-side PDF viewer (`EnvelopeReceiverPage.razor`). Documents the PDF.js integration, signature button rendering, JS interop API (`window.pdfViewer`), signature capture flow, HiDPI/zoom/thumbnail config, and the full component lifecycle.
|
||||
**Why it exists:** The receiver PDF viewer is the most complex component in the codebase. This doc prevents agents from misunderstanding the JS↔Blazor boundary, re-implementing already-solved problems (HiDPI, zoom, signature caching), or breaking the signing flow.
|
||||
**When you need it:**
|
||||
- Working on `EnvelopeReceiverPage.razor` or `pdf-viewer.js`
|
||||
- Adding new signature field rendering logic
|
||||
- Debugging signature rendering, zoom, or thumbnail issues
|
||||
- Implementing sender-side PDF.js overlay (signature field placement) — the receiver side is the reference implementation
|
||||
**Maintenance:** Update when `pdf-viewer.js` API changes, new JS interop functions are added, or the signing flow is modified.
|
||||
|
||||
#### `DATABASE_STRUCTURE_CONTEXT.md`
|
||||
**What:** Deep DB documentation for the envelope domain. Includes table/column snapshot, entity-to-table mapping, EF relationship/constraint map, stored procedure behavior + call sites, and trigger behavior + wiring path.
|
||||
**Why it exists:** DB is centralized and can evolve independently; this file preserves migration-critical data semantics when historical DB context is hard to recover.
|
||||
**When you need it:**
|
||||
- Planning or reviewing DB/schema changes
|
||||
- Debugging status transitions/history side effects
|
||||
- Tracing where `PRSIG_*` procedures or DB triggers affect app behavior
|
||||
- Reconciling Domain entities with physical SQL schema
|
||||
**Maintenance:** Refresh on each DB migration, trigger/procedure change, or significant Domain/EGDbContext model change.
|
||||
|
||||
---
|
||||
|
||||
### Deployment Docs
|
||||
|
||||
#### `EnvelopeGenerator.Server/EnvelopeGenerator.Server/README.md`
|
||||
**What:** IIS publish and deployment guide for `EnvelopeGenerator.Server`. Covers why self-contained publish is required (WASM assembly version strictness), the exact `dotnet publish` command, IIS Application Pool settings (`No Managed Code`), directory structure after publish, and common deployment errors.
|
||||
**Why it exists:** Blazor Auto (Server + WASM hybrid) has non-obvious deployment requirements that differ from a standard ASP.NET Core app. Forgetting self-contained publish or using the wrong app pool will break WASM loading in production.
|
||||
**When you need it:**
|
||||
- Deploying to IIS for the first time
|
||||
- Troubleshooting 500 errors on production after deploy
|
||||
- Setting up a new deployment environment
|
||||
**Maintenance:** Update if the publish target, IIS config, or runtime version changes.
|
||||
|
||||
---
|
||||
|
||||
## Markdown Audit Snapshot (2026-10-05)
|
||||
|
||||
The following table summarizes repository markdown files by practical status: completed, partially complete, or removable candidates.
|
||||
|
||||
| File | Type | Status | Missing / Gap | Removal Recommendation |
|
||||
|---|---|---|---|---|
|
||||
| `AGENTS.md` | Agent onboarding | Partially complete | Route/migration statements can drift from real code over time. | Keep |
|
||||
| `COPILOT_CONTEXT.md` | Architecture reference | Partially complete | Some route/render details may be out of sync with other docs; needs regular reconciliation. | Keep |
|
||||
| `FORM_APPLICATION_CONTEXT.md` | Legacy-to-modern feature spec | Partially complete | Large sections still marked not implemented / needs implementation. | Keep |
|
||||
| `RECEIVER_PDF_VIEWER_CONTEXT.md` | Receiver PDF viewer deep-dive | Partially complete | Long migration plan needs periodic sync with active implementation. | Keep |
|
||||
| `SENDER_SAVE_EDIT_MIGRATION_PLAN.md` | Sender workflow implementation plan | Partially complete | No explicit implementation progress markers per phase. | Keep (until migration closes) |
|
||||
| `SENDER_PDF_TOOLBAR_PLAN_EN.md` | Sender PDF toolbar plan | Incomplete | Explicit pending item remains (`Toolbar visual polish`). | Keep (until done) |
|
||||
| `SENDER_EDITOR_WASM_PDF_EDITOR_IMPLEMENTATION_PLAN_en.md` | Sender editor WASM migration plan | Incomplete | Plan exists but completion/progress tracking is minimal. | Keep (until done) |
|
||||
| `EnvelopeGenerator.Server/EnvelopeGenerator.Server/README.md` | Deployment guide | Complete (operational) | No critical gap identified. | Keep |
|
||||
| `envelope-generator-ui/README.md` | Angular scaffold README | Complete but generic | Template text only; low project-specific value. | Removable or replace with project-specific summary |
|
||||
| `EnvelopeGenerator.Web/wwwroot/lib/typed.js@2.1.0/README.md` | Third-party vendor README | Complete (vendor) | Not project workflow documentation. | Keep with vendor assets |
|
||||
| `EnvelopeGenerator.Web/wwwroot/lib/jquery-validation/LICENSE.md` | Third-party license | Complete (legal) | No gap. | Keep |
|
||||
| `EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/css/open-iconic/README.md` | Third-party vendor README | Complete (vendor) | Not project-specific guidance. | Keep with vendor assets |
|
||||
| `ignore/EnvelopeGenerator/EnvelopeGenerator.Web/wwwroot/README.md` | Legacy PSPDFKit note | Outdated | Mentions deprecated PSPDFKit-based flow; may mislead if reused. | Removable/archive |
|
||||
| `ignore/EnvelopeGenerator/EnvelopeGenerator.Web/wwwroot/lib/typed.js@2.1.0/README.md` | Duplicate vendor README | Redundant | Duplicate copy under `ignore/`. | Removable/archive |
|
||||
| `ignore/EnvelopeGenerator/EnvelopeGenerator.Web/wwwroot/lib/bootstrap-icons/README.md` | Vendor README in archive area | Secondary | Archive-only utility, not active docs. | Removable/archive |
|
||||
| `ignore/EnvelopeGenerator/EnvelopeGenerator.Web/wwwroot/lib/jquery-validation/LICENSE.md` | Duplicate license in archive area | Redundant | Duplicate copy under `ignore/`. | Removable/archive (if legal policy allows) |
|
||||
| `ignore/EnvelopeGenerator/EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/README.md` | Duplicate Angular scaffold README | Redundant | Generic template + duplicate in archive path. | Removable/archive |
|
||||
|
||||
### Documentation Audit TODO
|
||||
|
||||
- [ ] Validate this markdown audit table against the current codebase and update statuses (`complete`, `partial`, `incomplete`, `removable`) when implementation changes land.
|
||||
|
||||
---
|
||||
|
||||
## Active Architecture (Post-Migration)
|
||||
|
||||
**Frontend:** Blazor Auto (Server+WASM hybrid)
|
||||
- **EnvelopeGenerator.Server** (Server): `@rendermode InteractiveServer` - PDF viewers requiring DevExpress backend
|
||||
- **EnvelopeGenerator.Server.Client** (WASM): `@rendermode InteractiveWebAssembly` - Login, dashboards, business logic
|
||||
|
||||
**Backend + API Host:** `EnvelopeGenerator.Server` (ASP.NET Core 8.0, merged Blazor + Web API host)
|
||||
|
||||
**Proxy:** YARP in `EnvelopeGenerator.Server` is used for external integrations (for example AuthHub forwarding), not for routing app APIs to `EnvelopeGenerator.API`.
|
||||
|
||||
### Deprecated Projects - DO NOT USE
|
||||
- `EnvelopeGenerator.ReceiverUI` - Pure WASM (migrated to Server)
|
||||
- `EnvelopeGenerator.Web` - Razor Pages (replaced by Server)
|
||||
- **VB.NET projects** (`Form`, `Service`, `BBTests`) - Legacy, read-only for reference
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Run Active Host
|
||||
```powershell
|
||||
# Terminal - Blazor + Web API host
|
||||
cd EnvelopeGenerator.Server\EnvelopeGenerator.Server
|
||||
dotnet run
|
||||
```
|
||||
|
||||
**Critical:** `EnvelopeGenerator.Server` is the active runtime host for both UI and API controllers.
|
||||
|
||||
### Build
|
||||
```powershell
|
||||
dotnet build EnvelopeGenerator.sln
|
||||
```
|
||||
|
||||
## Project Boundaries
|
||||
|
||||
```
|
||||
EnvelopeGenerator.Domain/ # Entities (Envelope, Receiver, Document, etc.)
|
||||
EnvelopeGenerator.Application/ # MediatR CQRS (Commands, Queries, Handlers)
|
||||
EnvelopeGenerator.Infrastructure/ # EF Core, SQL executors, repositories
|
||||
EnvelopeGenerator.Server/
|
||||
EnvelopeGenerator.Server/ # Server-side Blazor + API controllers
|
||||
├─ Components/Pages/ # @rendermode InteractiveServer
|
||||
├─ Controllers/ # Active Web API endpoints
|
||||
EnvelopeGenerator.Server.Client/ # Client-side WASM components
|
||||
├─ Pages/ # @rendermode InteractiveWebAssembly
|
||||
├─ Services/ # HTTP API clients
|
||||
├─ Models/ # DTOs
|
||||
```
|
||||
|
||||
## Route Structure (Critical)
|
||||
|
||||
| Route | File Location | Render Mode | Purpose |
|
||||
|-------|--------------|-------------|---------|
|
||||
| `/` | `Server.Client/Pages/Index.razor` | WASM | Landing page |
|
||||
| `/sender/login` | `Server.Client/Pages/LoginSenderPage.razor` | WASM | Sender auth |
|
||||
| `/sender` | `Server/Components/Pages/EnvelopeSenderPage.razor` | **Server** | Sender dashboard |
|
||||
| `/sender/editor` | `Server/Components/Pages/EnvelopeSenderEditorPage.razor` | **Server** | Envelope editor + PDF viewer |
|
||||
| `/envelope/login/{key}` | `Server.Client/Pages/LoginReceiverPage.razor` | WASM | Receiver auth |
|
||||
| `/envelope/{key}` | `Server/Components/Pages/EnvelopeReceiverPage.razor` | **Server** | PDF viewer + signing |
|
||||
|
||||
**Rule:** PDF viewers MUST use `@rendermode InteractiveServer` (DevExpress backend requirement). Everything else uses WASM.
|
||||
|
||||
## Coordinate System (CRITICAL)
|
||||
|
||||
**Database stores INCHES** (GdPicture14 native). Origin: top-left, Y-axis down.
|
||||
|
||||
### Conversions
|
||||
```csharp
|
||||
// Database (INCHES) → PDF Points
|
||||
float points = inches * 72;
|
||||
|
||||
// Database (INCHES) → DevExpress DX
|
||||
float dx = inches * 100;
|
||||
|
||||
// PDF.js Pixels → Database (INCHES)
|
||||
float inches = (pixelX / canvasWidth) * pageWidthInches;
|
||||
```
|
||||
|
||||
**A4 Page:** 8.27" wide × 11.69" tall = 595pt × 842pt
|
||||
|
||||
**Signature Field Size:** 1.77" × 1.96" (FIXED, do not change)
|
||||
|
||||
**Evidence:** See `COPILOT_CONTEXT.md` lines 158-185, `EnvelopeGenerator.Form/frmFieldEditor.vb`
|
||||
|
||||
## API Architecture Quirks
|
||||
|
||||
### Monolithic Endpoint (Avoid for UI)
|
||||
`POST /api/EnvelopeReceiver` - Creates envelope+document+receivers+fields atomically.
|
||||
- **Use case:** External API consumers
|
||||
- **Not suitable for:** Step-by-step UI workflow (no draft support, no partial updates)
|
||||
|
||||
### Sender Workflow Endpoint
|
||||
```
|
||||
POST /api/Envelope # Create/update/save/send envelope workflow payload
|
||||
DELETE /api/Envelope/{id} # Delete with reason
|
||||
POST /api/Envelope/{id}/document # Upload PDF
|
||||
POST /api/Envelope/{id}/receivers # Add receiver
|
||||
POST /api/Envelope/{id}/signature-fields # Place signature field
|
||||
```
|
||||
|
||||
See `FORM_APPLICATION_CONTEXT.md` for detailed workflow requirements.
|
||||
|
||||
## Status Color Coding
|
||||
|
||||
Form app uses DevExpress `CustomDrawCell`. EnvelopeGenerator.Server needs CSS:
|
||||
|
||||
```css
|
||||
.envelope-row.status-partly-signed { background-color: #81C784; } /* GREEN_300 */
|
||||
.envelope-row.status-queued,
|
||||
.envelope-row.status-sent { background-color: #FFB74D; } /* ORANGE_300 */
|
||||
.envelope-row.status-completed { background-color: #81C784; }
|
||||
.envelope-row.status-deleted,
|
||||
.envelope-row.status-rejected { background-color: #E57373; } /* RED_300 */
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### YARP Proxy (`EnvelopeGenerator.Server/yarp.json`)
|
||||
Routes `/api/*`, `/swagger/*`, `/openapi/*`, `/scalar/*` → `https://localhost:8088`
|
||||
|
||||
### PDF.js Settings (`EnvelopeGenerator.Server/wwwroot/appsettings.json`)
|
||||
```json
|
||||
{
|
||||
"PdfViewerOptions": {
|
||||
"ThumbnailBaseScale": 0.75,
|
||||
"ThumbnailEnableHiDPI": true,
|
||||
"MainCanvasEnableHiDPI": true,
|
||||
"ZoomStepPercentage": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Server Host Config (`EnvelopeGenerator.Server/EnvelopeGenerator.Server/appsettings.json`)
|
||||
- `ConnectionStrings:Default` - SQL Server DB
|
||||
- `AllowedOrigins` - CORS (includes `http://localhost:5131`, `http://localhost:7192`)
|
||||
- `Cache:SignatureCacheExpiration` - Signature persistence timeout
|
||||
- `PSPDFKitLicenseKey` - **DEPRECATED** (use PDF.js instead)
|
||||
|
||||
## Migration Status
|
||||
|
||||
### Complete ✅
|
||||
- Receiver login/authentication
|
||||
- PDF viewing with PDF.js (HiDPI, zoom, thumbnails)
|
||||
- Signature capture (draw/type/image)
|
||||
- Signature caching (Redis/SQL)
|
||||
- Sender login
|
||||
|
||||
### Missing (High Priority) ❌
|
||||
- Sender dashboard (`/sender`) - Empty stub
|
||||
- Envelope editor (`/sender/envelope/{id}`)
|
||||
- Signature field placement tool (PDF.js + draggable overlays)
|
||||
- Granular API endpoints (draft, receivers, fields)
|
||||
- Master-detail grids for receivers/history
|
||||
|
||||
## Common Mistakes (DO NOT REPEAT)
|
||||
|
||||
| Mistake | Why Wrong |
|
||||
|---------|-----------|
|
||||
| Using iText7 in receiver pages | GPL license issue. Use PDF.js overlays. |
|
||||
| Using PSPDFKit | Removed from architecture. Use PDF.js + DevExpress. |
|
||||
| `@rendermode InteractiveWebAssembly` on PDF viewers | DevExpress DxPdfViewer requires server-side rendering. |
|
||||
| Hardcoded quality in PDF.js | Use `appsettings.json` `PdfViewerOptions`. |
|
||||
| Coordinates in points/pixels for DB | Database uses INCHES. Convert before save. |
|
||||
| `BottomMarginBand` for signatures | Repeats on every page. Use `DetailBand`. |
|
||||
|
||||
## Testing
|
||||
|
||||
**No automated tests exist yet.**
|
||||
|
||||
Manual testing workflow:
|
||||
1. Start EnvelopeGenerator.Server (`dotnet run` in `EnvelopeGenerator.Server\EnvelopeGenerator.Server`)
|
||||
2. Navigate to `https://localhost:5131` (or check console output for port)
|
||||
3. Test sender login at `/sender/login`
|
||||
4. Test receiver flow at `/envelope/login/{envelopeKey}`
|
||||
|
||||
## Technical Debt TODOs
|
||||
|
||||
- **CRITICAL (Licensing / pre-commercial release):** Remove all `SixLabors.ImageSharp` and `QRCoder-ImageSharp` package dependencies from production/runtime projects before commercial release. Replace remaining QR/image paths with permissive-license alternatives (no copyleft/share-source obligations), run repo-wide package audit (`*.csproj`), and add CI guardrails to block reintroduction.
|
||||
- Refactor `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeSenderPage.razor` to reduce complexity.
|
||||
- Move sender dashboard business/status logic into dedicated services and extension methods (for example effective status resolution, tab classification, receiver signed/rejected detection, and grid-layout filter sanitation).
|
||||
- Keep `EnvelopeSenderPage.razor` focused on UI composition/state orchestration; avoid embedding heavy domain logic directly in the Razor component.
|
||||
- Apply a modular page architecture across Server pages (starting with `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeSenderPage.razor`): split large pages into smaller subcomponents/partial modules, eliminate duplicated code paths, and centralize shared UI/domain logic for maintainability.
|
||||
- Perform a senior-level page performance review across sender/receiver flows before further UX changes: verify route transitions, component mount/unmount behavior, loading-state correctness, duplicate API calls, and perceived latency (first meaningful paint and return-navigation responsiveness). Treat loading logic defects as architecture/performance issues first, not styling problems.
|
||||
- Audit `EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/` for unnecessary request/payload remapping and remove redundant mappings where direct request DTO usage is safe; review client HTTP methods end-to-end for similar avoidable transformation layers.
|
||||
- Centralize dependency injection registrations into dedicated extension methods/modules (especially server + client service registrations in `Program.cs`) to reduce startup composition sprawl and improve maintainability.
|
||||
- Refactor `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers/PdfRenderController.cs` + matching client service contract to remove JSON/base64 `byte[]` transport for PDF rendering; prefer binary streaming (`application/octet-stream` or multipart), avoid `data:` URL bloat where possible, and validate end-to-end memory/latency behavior with large PDFs.
|
||||
- Establish a DevExpress toast-based notification architecture using `DxToastProvider` + `IToastNotificationService` (tab/page-scoped provider strategy, standardized `ToastOptions` factory, and severity-to-style mapping) so sender/receiver flows show consistent, non-duplicated, actionable feedback.
|
||||
- Eliminate manual property-by-property request→domain mapping drift (example: `CreateEnvelopeCommandHandler`) by introducing a standardized mapping policy (AutoMapper/profile conventions or reflection/source-generator based mapper) and enforcing it across envelope workflows; this needs to be solved in the shared company Core library level so all services get the same guardrails by default.
|
||||
- P1 security workstream: remove dynamic SQL string composition/interpolation from active save/create/update paths (`string.Format`, `$"...{input}..."`, `ToSqlParam`) and migrate to parameterized queries only; treat this as a potential SQL injection vulnerability (stability + security risk), execute repo-wide audit, and add CI guardrails to block reintroduction.
|
||||
- Alternative UX design (Server component): in `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeSenderPage.razor`, enforce deterministic row double-click behavior as exactly one action (preview OR edit), and keep row action buttons/tooltips aligned with the same rule to eliminate ambiguous navigation.
|
||||
- Implement SPA-style language switching so the UI culture updates immediately without a full page reload.
|
||||
- Implement sender-scoped caching for "recent receiver suggestions" used during envelope creation (wizard/editor flows): cache key should include sender identity, support short TTL + invalidation hooks after successful envelope receiver writes, and prevent cross-sender suggestion leakage.
|
||||
|
||||
## Database
|
||||
|
||||
**SQL Server** (DD_ECM)
|
||||
- Connection string in `EnvelopeGenerator.Server/EnvelopeGenerator.Server/appsettings.json`
|
||||
- EF Core migrations NOT used (manual SQL scripts)
|
||||
- Stored procedures: `PRSIG_*` prefix
|
||||
|
||||
**Key Tables:**
|
||||
- `TBSIG_ENVELOPE` - Envelope metadata
|
||||
- `TBSIG_ENVELOPE_RECEIVER` - Receiver assignments
|
||||
- `TBSIG_DOCUMENT_RECEIVER_ELEMENT` - Signature fields (X, Y in INCHES)
|
||||
- `TBSIG_RECEIVER` - Receiver registry
|
||||
- `TBSIG_ENVELOPE_DOCUMENT` - PDF binary data
|
||||
- `TBSIG_ENVELOPE_HISTORY` - Audit trail
|
||||
|
||||
### Data Structure (Domain + EGDbContext)
|
||||
|
||||
Use this as the canonical quick map before touching sender/receiver workflow code.
|
||||
|
||||
#### Core Aggregate Shape
|
||||
- `Envelope` (`TBSIG_ENVELOPE`) is the root aggregate for sending workflow.
|
||||
- `Envelope -> Documents` is `1:N` via `Document.EnvelopeId` (`TBSIG_ENVELOPE_DOCUMENT`).
|
||||
- `Envelope -> Histories` is `1:N` via `History.EnvelopeId` (`TBSIG_ENVELOPE_HISTORY`).
|
||||
- `Envelope <-> Receiver` is `N:N` through `EnvelopeReceiver` (`TBSIG_ENVELOPE_RECEIVER`, composite PK `{EnvelopeId, ReceiverId}`).
|
||||
- `Document -> DocReceiverElement` is `1:N` via `DocReceiverElement.DocumentId` (`TBSIG_DOCUMENT_RECEIVER_ELEMENT`).
|
||||
- `DocReceiverElement -> ElementAnnotation` is `1:N` via `ElementAnnotation.ElementId` (`TBSIG_DOCUMENT_RECEIVER_ELEMENT_ANNOTATION`).
|
||||
|
||||
#### Status and Tracking Entities
|
||||
- `DocumentStatus` (`TBSIG_DOCUMENT_STATUS`) links `EnvelopeId + ReceiverId` and stores receiver-level signing state.
|
||||
- `History` stores timeline events (`EnvelopeStatus`) with polymorphic `UserReference` lookup semantics.
|
||||
- `EnvelopeReceiverReadOnly` (`TBSIG_ENVELOPE_RECEIVER_READ_ONLY`) stores temporary read-only delegation with validity window (`DateValid`).
|
||||
|
||||
#### Important Constraints From `EGDbContext`
|
||||
- `Envelope.Uuid` has a unique index.
|
||||
- `Receiver.Signature` has a unique index.
|
||||
- `Receiver.EmailAddress` has a unique index.
|
||||
- `EnvelopeReceiver` uses composite primary key: `{ EnvelopeId, ReceiverId }`.
|
||||
- `History.UserReference` is mapped conditionally (`IsMigration == false`) to:
|
||||
- `Receiver.EmailAddress` (`History.Receiver`)
|
||||
- `User.Email` from UserManager (`History.Sender`)
|
||||
- `EnvelopeReceiverReadOnly.AddedWho` maps to `Receiver.EmailAddress` (principal key mapping, not receiver GUID).
|
||||
|
||||
#### Coordinate-Critical Fields
|
||||
- Signature placement is persisted in `DocReceiverElement` as `X`, `Y`, `Width`, `Height`, `Page`.
|
||||
- These coordinates are in INCHES (not PDF points, not pixels).
|
||||
|
||||
#### Cross-Module Tables in Same Context
|
||||
- `EGDbContext` also includes UserManager entities: `User`, `Group`, `Module`, `GroupOfUser`, `ModuleOfUser`, `UserRep`, and `ClientUser`.
|
||||
- Mail dispatcher entity `EmailOut` is also registered under `#if NET` and trigger-configurable.
|
||||
|
||||
#### Trigger-Aware Model Notes
|
||||
- Trigger names are injected from `DbTriggerParams` in appsettings and applied via `ToTable(tb => tb.HasTrigger(...))`.
|
||||
- Current trigger registration is explicit (`AddTrigger<T>()` calls), not attribute/reflection driven.
|
||||
- Do not remove trigger mappings casually; this project depends on DB-side automation/audit behavior.
|
||||
|
||||
## DevExpress
|
||||
|
||||
**License:** Commercial (v25.2.3)
|
||||
**Components Used:**
|
||||
- `DxGrid` - Master-detail grids
|
||||
- `DxPdfViewer` - Server-side PDF rendering
|
||||
- `DxPopup` - Modal dialogs
|
||||
- `DxToolbar` - Action bars
|
||||
- `DxFormLayout` - Forms
|
||||
|
||||
**Theme:** Blazing Berry (default)
|
||||
|
||||
## JavaScript Interop
|
||||
|
||||
**PDF Viewer:** `wwwroot/js/pdf-viewer.js`
|
||||
```javascript
|
||||
window.pdfViewer = {
|
||||
initialize(canvasId, pdfDataUrl, dotNetRef),
|
||||
renderPage(num),
|
||||
renderSignatureButtons(signatures, pageNum, dotNetRef),
|
||||
applySignature(signatureId, dataUrl, fullName, position, place),
|
||||
zoomIn(), zoomOut(), dispose()
|
||||
}
|
||||
```
|
||||
|
||||
**Signature Pad:** `wwwroot/js/receiver-signature.js`
|
||||
```javascript
|
||||
window.receiverSignature = {
|
||||
initializeDrawPad(canvasId, dotNetRef),
|
||||
getSignatureDataUrl(canvasId),
|
||||
clearPad(canvasId)
|
||||
}
|
||||
```
|
||||
|
||||
## Multi-Envelope Support
|
||||
|
||||
Receivers can login to **multiple envelopes simultaneously** via per-envelope cookies:
|
||||
```
|
||||
AuthTokenSignFLOWReceiver.{envelopeKey}
|
||||
```
|
||||
|
||||
Each envelope maintains independent authentication state.
|
||||
|
||||
## External Dependencies
|
||||
|
||||
**CDN:**
|
||||
- PDF.js 3.11.174: `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js`
|
||||
|
||||
**NuGet (Server.Client):**
|
||||
- `DevExpress.Blazor.*` 25.2.3
|
||||
- `SkiaSharp.*` 3.119.1 (WASM rendering)
|
||||
|
||||
**External Services:**
|
||||
- LDAP/AD authentication (optional)
|
||||
- GTX Messaging (SMS 2FA)
|
||||
- Email dispatcher (signFlow)
|
||||
|
||||
## Environment Variables
|
||||
|
||||
None required. All config in `appsettings.json`.
|
||||
|
||||
**Local dev ports:**
|
||||
- App host (UI + API): `https://localhost:5131` or `http://localhost:7192`
|
||||
@@ -1,351 +0,0 @@
|
||||
# EnvelopeGenerator — Current Workspace Context
|
||||
|
||||
## Purpose
|
||||
Digital document signing system for senders and receivers.
|
||||
|
||||
- Senders authenticate, view envelope lists, and manage envelope workflows.
|
||||
- Receivers authenticate per envelope, open PDFs, create signatures, and apply them in the viewer.
|
||||
- The active UI stack is `Blazor Auto` with server-side and WebAssembly render modes.
|
||||
- Primary UI/PDF libraries are `DevExpress` and `PDF.js`.
|
||||
|
||||
---
|
||||
|
||||
## Active Application Structure
|
||||
|
||||
### Main Host
|
||||
**Primary active application:** `EnvelopeGenerator.Server`
|
||||
|
||||
`EnvelopeGenerator.Server` is the current runtime host and contains:
|
||||
- Blazor server host
|
||||
- WebAssembly host integration
|
||||
- API controllers
|
||||
- authentication/authorization setup
|
||||
- Swagger/Scalar setup
|
||||
- YARP reverse proxy configuration
|
||||
- DevExpress server-side services
|
||||
- SQL Server distributed cache setup
|
||||
|
||||
### Client Project
|
||||
**Client UI project:** `EnvelopeGenerator.Server.Client`
|
||||
|
||||
This project contains:
|
||||
- WebAssembly-rendered pages
|
||||
- client-side services
|
||||
- client models and options
|
||||
- sender and receiver login flows
|
||||
|
||||
### Other Projects
|
||||
- `EnvelopeGenerator.Application` — MediatR/CQRS handlers and business logic
|
||||
- `EnvelopeGenerator.Domain` — domain models, constants, shared abstractions
|
||||
- `EnvelopeGenerator.Infrastructure` — EF Core and infrastructure services
|
||||
- `EnvelopeGenerator.PdfEditor` — PDF-related backend utilities
|
||||
- `EnvelopeGenerator.API` — deprecated project (no longer part of the active solution/runtime path)
|
||||
|
||||
### Legacy / Do Not Touch
|
||||
- `EnvelopeGenerator.Service`
|
||||
- `EnvelopeGenerator.Form`
|
||||
- `EnvelopeGenerator.BBTests`
|
||||
- `EnvelopeGenerator.CommonServices`
|
||||
|
||||
---
|
||||
|
||||
## Current Hosting Model
|
||||
|
||||
`EnvelopeGenerator.Server/Program.cs` currently configures:
|
||||
- `AddRazorComponents()` with both interactive server and interactive WebAssembly components
|
||||
- `AddControllers()` and `MapControllers()`
|
||||
- JWT authentication for sender and receiver flows
|
||||
- cookie authentication
|
||||
- authorization policies using `AuthScheme.Sender`, `AuthScheme.Receiver`, `AuthPolicy.Sender`, `AuthPolicy.Receiver`
|
||||
- `AddReverseProxy()` with `yarp.json`
|
||||
- Swagger / OpenAPI / Scalar
|
||||
- distributed SQL Server cache
|
||||
- DevExpress Blazor and DevExpress PDF Viewer server-side services
|
||||
- request localization middleware
|
||||
|
||||
This means the active app is a **merged UI + API host**.
|
||||
|
||||
---
|
||||
|
||||
## Reverse Proxy
|
||||
|
||||
**Config file:** `EnvelopeGenerator.Server/EnvelopeGenerator.Server/yarp.json`
|
||||
|
||||
Current YARP usage is focused on **AuthHub forwarding**, not a general `/api/* -> EnvelopeGenerator.API` proxy.
|
||||
|
||||
Configured routes forward:
|
||||
- `POST /api/auth` -> AuthHub `/api/auth/sign-flow`
|
||||
- `POST /api/Auth/envelope-receiver/{key}` -> AuthHub `/api/auth/envelope-receiver/{key}?cookie=true`
|
||||
|
||||
---
|
||||
|
||||
## Active Routes and Files
|
||||
|
||||
### WebAssembly Pages (`EnvelopeGenerator.Server.Client`)
|
||||
| Route | File | Render Mode | Purpose |
|
||||
|---|---|---|---|
|
||||
| `/` | `EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/IndexPage.razor` | WebAssembly | Landing page |
|
||||
| `/sender/login` | `EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/LoginSenderPage.razor` | WebAssembly | Sender login |
|
||||
| `/sender` | `EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/EnvelopeSenderPage.razor` | WebAssembly (`prerender: false`) | Sender dashboard |
|
||||
| `/envelope/login/{EnvelopeKey}` | `EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/LoginReceiverPage.razor` | WebAssembly | Receiver login |
|
||||
|
||||
### Server Pages (`EnvelopeGenerator.Server`)
|
||||
| Route | File | Render Mode | Purpose |
|
||||
|---|---|---|---|
|
||||
| `/envelope/{EnvelopeKey}` | `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverPage.razor` | InteractiveServer | Main receiver PDF viewer and signing page |
|
||||
| `/envelope/DxPdfViewer` | `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverPage_DxPdfViewer.razor` | InteractiveServer | DevExpress PDF Viewer test page |
|
||||
| `/envelope/{EnvelopeKey}/DxReportViewer` | `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverPage_DxReportViewer.razor` | InteractiveServer | DevExpress report-based PDF rendering |
|
||||
| `/envelope/Embed` | `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverPage_embed.razor` | InteractiveServer | Embedded browser PDF view test page |
|
||||
|
||||
---
|
||||
|
||||
## Current API Location
|
||||
|
||||
The active application exposes controllers from:
|
||||
`EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers`
|
||||
|
||||
Current controller set includes:
|
||||
- `AnnotationController`
|
||||
- `AuthController`
|
||||
- `CacheController`
|
||||
- `ConfigController`
|
||||
- `DocumentController`
|
||||
- `EmailTemplateController`
|
||||
- `EnvelopeController`
|
||||
- `EnvelopeReceiverController`
|
||||
- `EnvelopeTypeController`
|
||||
- `HistoryController`
|
||||
- `LocalizationController`
|
||||
- `ReadOnlyController`
|
||||
- `ReceiverController`
|
||||
- `SignatureController`
|
||||
- `TfaRegistrationController`
|
||||
|
||||
Do not assume API behavior lives in `EnvelopeGenerator.API`; the active merged host contains controller endpoints directly.
|
||||
|
||||
---
|
||||
|
||||
## Authentication Model
|
||||
|
||||
### Sender
|
||||
Client login page uses `EnvelopeGenerator.Server.Client/Services/AuthService.cs`.
|
||||
|
||||
Key sender endpoints:
|
||||
- `POST /api/auth?cookie=true` — login
|
||||
- `GET /api/auth/check` — current sender access check
|
||||
- `POST /api/auth/logout` — logout
|
||||
|
||||
### Receiver
|
||||
Receiver authentication is **per envelope**.
|
||||
|
||||
Key receiver endpoints used by client services:
|
||||
- `POST /api/Auth/envelope-receiver/{envelopeKey}` — submit access code
|
||||
- `GET /api/auth/check/envelope/{envelopeKey}` — check access
|
||||
- `POST /api/auth/logout/envelope/{envelopeKey}` — logout receiver for one envelope
|
||||
|
||||
Receiver cookie resolution in server auth uses an envelope-specific cookie name derived from:
|
||||
- `AuthTokenSignFLOWReceiver.{envelopeKey}` pattern
|
||||
|
||||
### Receiver Server-Side Authorization
|
||||
`EnvelopeReceiverPage.razor` does **not** rely on its own API access-check call for page authorization.
|
||||
|
||||
It uses:
|
||||
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Services/EnvelopeReceiverAuthorizationService.cs`
|
||||
|
||||
Behavior:
|
||||
- tries the current `HttpContext.User`
|
||||
- if needed, reads the per-envelope receiver cookie directly
|
||||
- validates the JWT with the receiver auth scheme
|
||||
- verifies the token subject matches the route envelope key
|
||||
|
||||
---
|
||||
|
||||
## Receiver Page Data Loading
|
||||
|
||||
Main server-side page data service:
|
||||
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Services/EnvelopeReceiverPageDataService.cs`
|
||||
|
||||
This service loads directly via MediatR and distributed cache:
|
||||
- document bytes
|
||||
- receiver envelope data
|
||||
- signature placeholders
|
||||
- cached signature data
|
||||
|
||||
For signature placeholders, the service:
|
||||
- reads document receiver elements
|
||||
- filters them for the authenticated receiver
|
||||
- converts coordinates to `UnitOfLength.Point` before UI use
|
||||
|
||||
---
|
||||
|
||||
## Receiver PDF Viewer
|
||||
|
||||
**Main file:** `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverPage.razor`
|
||||
|
||||
Current receiver viewer characteristics:
|
||||
- route: `/envelope/{EnvelopeKey}`
|
||||
- render mode: `InteractiveServer`
|
||||
- PDF rendering: `PDF.js`
|
||||
- toolbar: page navigation, zoom, thumbnail toggle, signature navigation, signature reset
|
||||
- signature popup: `DxPopup`
|
||||
- thumbnail sidebar: resizable and stored in `localStorage`
|
||||
|
||||
### JS Assets
|
||||
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/js/pdf-viewer.js`
|
||||
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/js/receiver-signature.js`
|
||||
|
||||
### CSS
|
||||
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/css/envelope-viewer.css`
|
||||
|
||||
### PDF.js CDN
|
||||
- `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js`
|
||||
- `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf_viewer.min.css`
|
||||
|
||||
---
|
||||
|
||||
## Signature Workflow
|
||||
|
||||
Receiver signatures are handled as a **viewer overlay workflow**.
|
||||
|
||||
### Current behavior
|
||||
1. Server-side authorization validates receiver access.
|
||||
2. The page loads document bytes, receiver data, signature placeholders, and cached signature state.
|
||||
3. If no cached signature exists, the signature popup opens automatically.
|
||||
4. Receiver creates signature using one of three tabs:
|
||||
- draw
|
||||
- text
|
||||
- image
|
||||
5. Required metadata:
|
||||
- full name
|
||||
- place
|
||||
6. Optional metadata:
|
||||
- position
|
||||
7. Clicking a signature placeholder applies the signature as a client-side overlay in the PDF viewer.
|
||||
|
||||
### Important note
|
||||
Although `itext` is referenced by the server project, the current receiver page signing flow is **not PDF stamping-based**. The active receiver UI uses client-side overlay behavior in the viewer.
|
||||
|
||||
### Signature DTO
|
||||
`EnvelopeGenerator.Server.Client/Models/SignatureCaptureDto.cs`
|
||||
|
||||
```csharp
|
||||
public sealed record SignatureCaptureDto {
|
||||
public required string DataUrl { get; init; }
|
||||
public required string FullName { get; init; }
|
||||
public string Position { get; init; } = "";
|
||||
public required string Place { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Signature Cache
|
||||
|
||||
### Active cache model
|
||||
The current receiver page cache flow is handled directly in the server project through:
|
||||
- `EnvelopeReceiverPageDataService`
|
||||
- `IDistributedCache`
|
||||
- SQL Server distributed cache configuration from `Program.cs`
|
||||
|
||||
### Cache key format
|
||||
Current server-side key prefix:
|
||||
- `envelope-generator.receiver-ui.signature:{receiverSignature}`
|
||||
|
||||
This is different from an envelope-key-only cache convention.
|
||||
|
||||
### Config
|
||||
`EnvelopeGenerator.Server/EnvelopeGenerator.Server/Options/CacheOptions.cs`
|
||||
- section name: `Cache`
|
||||
- option: `SignatureCacheExpiration`
|
||||
|
||||
### Related controller
|
||||
A cache API controller also exists in:
|
||||
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers/CacheController.cs`
|
||||
|
||||
---
|
||||
|
||||
## Sender Dashboard
|
||||
|
||||
**Main file:** `EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/EnvelopeSenderPage.razor`
|
||||
|
||||
Current behavior:
|
||||
- checks sender access through `AuthService.CheckSenderAccessAsync()`
|
||||
- redirects to `/sender/login` when unauthorized
|
||||
- loads envelope list through client `EnvelopeService`
|
||||
- separates envelopes into active/completed tabs
|
||||
- uses `DevExpress DxGrid`
|
||||
|
||||
The sender page is active, but create/edit/delete actions are still marked with TODO behavior in the UI page.
|
||||
|
||||
---
|
||||
|
||||
## Localization
|
||||
|
||||
Current server host localization setup in `Program.cs`:
|
||||
- supported cultures: `de-DE`, `en-US`
|
||||
- request localization middleware is enabled
|
||||
- `QueryStringRequestCultureProvider` is added
|
||||
- cookie-based localization services are registered via `AddCookieBasedLocalizer()`
|
||||
|
||||
Do not assume the old ReceiverUI-only `localStorage` culture approach is the current source of truth for the active host.
|
||||
|
||||
---
|
||||
|
||||
## Coordinate System
|
||||
|
||||
### Source data
|
||||
Database signature coordinates are still based on:
|
||||
- **unit:** inches
|
||||
- **origin:** top-left
|
||||
- **axes:** X right, Y down
|
||||
|
||||
### Relevant conversions
|
||||
- inches -> PDF points: `x_pt = x_inches * 72`
|
||||
- inches -> DevExpress DX units: `x_dx = x_inches * 100`
|
||||
|
||||
### Current receiver page behavior
|
||||
The server page data service converts signature placeholders to **points** before sending them into the viewer workflow.
|
||||
|
||||
### Unit systems to keep in mind
|
||||
| System | Unit | Origin | Y-axis |
|
||||
|---|---|---|---|
|
||||
| Database | Inches | Top-left | Down |
|
||||
| PDF.js display | Pixels | Top-left | Down |
|
||||
| PDF points | Points | Depends on PDF model | Depends on consumer |
|
||||
| DevExpress DX | 1/100 inch style coordinates | Top-left-oriented usage in this app | Down-oriented usage |
|
||||
|
||||
---
|
||||
|
||||
## Key Services and Files
|
||||
|
||||
### Client services
|
||||
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/AuthService.cs`
|
||||
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/EnvelopeService.cs`
|
||||
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/DocumentService.cs`
|
||||
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/SignatureCacheService.cs`
|
||||
|
||||
### Server services
|
||||
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Services/EnvelopeAuthService.cs`
|
||||
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Services/IEnvelopeAuthService.cs`
|
||||
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Services/EnvelopeReceiverAuthorizationService.cs`
|
||||
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Services/EnvelopeReceiverPageDataService.cs`
|
||||
|
||||
### Server config and host files
|
||||
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Program.cs`
|
||||
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/yarp.json`
|
||||
|
||||
---
|
||||
|
||||
## Working Rules for This Workspace
|
||||
|
||||
- Treat `EnvelopeGenerator.Server` as the active main application host.
|
||||
- Treat `EnvelopeGenerator.Server.Client` as the active client UI project.
|
||||
- Prefer current `Server` / `Server.Client` paths over old `WebUI` / `ReceiverUI` references.
|
||||
- Do not use `EnvelopeGenerator.Web` or `EnvelopeGenerator.ReceiverUI` as the primary implementation target unless explicitly asked.
|
||||
- Do not modify the legacy VB.NET projects unless explicitly requested.
|
||||
- For receiver PDF/signature work, prefer the current `PDF.js`-based flow in `EnvelopeReceiverPage.razor`.
|
||||
- For DevExpress PDF viewer issues, remember server-side services are registered in `EnvelopeGenerator.Server`.
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2026-06-29
|
||||
@@ -1,384 +0,0 @@
|
||||
# Database Structure Context
|
||||
|
||||
## Why this file exists
|
||||
|
||||
This is the deep-dive DB reference for migration work when schema can change and old DB knowledge may be lost.
|
||||
|
||||
- Scope: `EnvelopeGenerator.Domain` entities + `EnvelopeGenerator.Infrastructure/EGDbContext.cs` + live DB metadata snapshot from `DD_ECM`.
|
||||
- Goal: make schema transitions predictable (what exists, how it is linked, where logic executes).
|
||||
- Safety: all DB checks used read-only `SELECT` metadata queries; no data mutation.
|
||||
|
||||
## Snapshot metadata
|
||||
|
||||
- Source DB: `DD_ECM` on `SDD-VHP04-SQL19\DD_TESTING01`
|
||||
- Snapshot date: `2026-10-20`
|
||||
- App context: `EnvelopeGenerator.Infrastructure/EGDbContext.cs`
|
||||
|
||||
## Fast navigation
|
||||
|
||||
- High-level aggregate map: `AGENTS.md` -> `Database` section
|
||||
- EF model config source: `EnvelopeGenerator.Infrastructure/EGDbContext.cs`
|
||||
- Stored procedure wrappers:
|
||||
- `EnvelopeGenerator.Application/Common/SQL/EnvelopeCreateReadSQL.cs`
|
||||
- `EnvelopeGenerator.Application/Common/SQL/EnvelopeReceiverAddReadSQL.cs`
|
||||
- `EnvelopeGenerator.Application/Common/SQL/DocumentCreateReadSQL.cs`
|
||||
- Direct SQL execution in controller:
|
||||
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers/EnvelopeReceiverController.cs`
|
||||
|
||||
## Core data model (ER-level)
|
||||
|
||||
- `Envelope` (`TBSIG_ENVELOPE`) is root aggregate.
|
||||
- `Envelope (1) -> (N) Document` via `TBSIG_ENVELOPE_DOCUMENT.ENVELOPE_ID`.
|
||||
- `Envelope (1) -> (N) History` via `TBSIG_ENVELOPE_HISTORY.ENVELOPE_ID`.
|
||||
- `Envelope (N) <-> (N) Receiver` through `EnvelopeReceiver` (`TBSIG_ENVELOPE_RECEIVER`, composite PK `{ENVELOPE_ID, RECEIVER_ID}`).
|
||||
- `Document (1) -> (N) DocReceiverElement` via `TBSIG_DOCUMENT_RECEIVER_ELEMENT.DOCUMENT_ID`.
|
||||
- `DocReceiverElement (1) -> (N) ElementAnnotation` via `TBSIG_DOCUMENT_RECEIVER_ELEMENT_ANNOTATION.ELEMENT_ID`.
|
||||
- `DocumentStatus` links `Envelope + Receiver` state in `TBSIG_DOCUMENT_STATUS`.
|
||||
|
||||
## Entity <-> table mapping (code-level)
|
||||
|
||||
| Entity | Table | PK in code | PK in DB | Important relation notes |
|
||||
|---|---|---|---|---|
|
||||
| `Envelope` | `TBSIG_ENVELOPE` | `Id` | `GUID` | Has `Documents`, `Histories`, `EnvelopeReceivers`; unique index on `Uuid` configured in EF |
|
||||
| `EnvelopeReceiver` | `TBSIG_ENVELOPE_RECEIVER` | `{EnvelopeId, ReceiverId}` | `{ENVELOPE_ID, RECEIVER_ID}` | DB also has `GUID` identity (not modeled as entity PK) |
|
||||
| `Document` | `TBSIG_ENVELOPE_DOCUMENT` | `Id` | `GUID` | FK `ENVELOPE_ID` |
|
||||
| `DocReceiverElement` | `TBSIG_DOCUMENT_RECEIVER_ELEMENT` | `Id` | `GUID` | Coordinates persisted here (`POSITION_X/Y`, `WIDTH`, `HEIGHT`, `PAGE`) |
|
||||
| `ElementAnnotation` | `TBSIG_DOCUMENT_RECEIVER_ELEMENT_ANNOTATION` | `Id` | `GUID` | FK `ELEMENT_ID` |
|
||||
| `DocumentStatus` | `TBSIG_DOCUMENT_STATUS` | `Id` | `GUID` | FK `ENVELOPE_ID`, `RECEIVER_ID` |
|
||||
| `History` | `TBSIG_ENVELOPE_HISTORY` | `Id` | `GUID` | `USER_REFERENCE` maps to `Receiver.EmailAddress` and `User.Email` when `IsMigration == false` |
|
||||
| `Receiver` | `TBSIG_RECEIVER` | `Id` | `GUID` | Unique indexes in code: `Signature`, `EmailAddress` |
|
||||
| `EnvelopeReceiverReadOnly` | `TBSIG_ENVELOPE_RECEIVER_READ_ONLY` | `Id` | `GUID` | `AddedWho` is FK-like mapping to `Receiver.EmailAddress` |
|
||||
| `EnvelopeType` | `TBSIG_ENVELOPE_TYPE` | `Id` | `GUID` | Used as optional envelope defaults/template |
|
||||
| `EmailTemplate` | `TBSIG_EMAIL_TEMPLATE` | `Id` | `GUID` | Trigger-managed `CHANGED_WHEN` |
|
||||
| `EmailOut` (legacy/bridge) | `TBEMLP_EMAIL_OUT` | `Id` | `GUID` | Internal/obsolete domain class, still registered for DB access |
|
||||
|
||||
## Physical schema snapshot (table/column level)
|
||||
|
||||
Notes:
|
||||
- Format per column: `COLUMN_NAME TYPE NULLABLE`.
|
||||
- This section reflects live DB metadata at snapshot time.
|
||||
|
||||
### `TBSIG_ENVELOPE`
|
||||
|
||||
- `GUID int NOT NULL`
|
||||
- `USER_ID int NOT NULL`
|
||||
- `STATUS int NOT NULL`
|
||||
- `ENVELOPE_UUID nvarchar(36) NOT NULL`
|
||||
- `MESSAGE nvarchar(max) NOT NULL`
|
||||
- `EXPIRES_WHEN datetime NULL`
|
||||
- `EXPIRES_WARNING_WHEN datetime NULL`
|
||||
- `VERIFICATION_TYPE tinyint NOT NULL`
|
||||
- `TITLE nvarchar(128) NULL`
|
||||
- `CONTRACT_TYPE int NULL`
|
||||
- `LANGUAGE nvarchar(5) NULL`
|
||||
- `SEND_REMINDER_EMAILS bit NULL`
|
||||
- `FIRST_REMINDER_DAYS int NULL`
|
||||
- `REMINDER_INTERVAL_DAYS int NULL`
|
||||
- `ENVELOPE_TYPE int NULL`
|
||||
- `CERTIFICATION_TYPE int NULL`
|
||||
- `USE_ACCESS_CODE bit NULL`
|
||||
- `FINAL_EMAIL_TO_CREATOR int NULL`
|
||||
- `FINAL_EMAIL_TO_RECEIVERS int NULL`
|
||||
- `EXPIRES_WHEN_DAYS int NULL`
|
||||
- `EXPIRES_WARNING_WHEN_DAYS int NULL`
|
||||
- `TFA_ENABLED bit NOT NULL`
|
||||
- `DOC_RESULT varbinary(max) NULL`
|
||||
- `LANG_CODE varchar(5) NOT NULL`
|
||||
- `SOURCE varchar(10) NOT NULL`
|
||||
- `COMMENT varchar(128) NULL`
|
||||
- `ADDED_WHEN datetime NOT NULL`
|
||||
- `CHANGED_WHEN datetime NULL`
|
||||
|
||||
### `TBSIG_ENVELOPE_RECEIVER`
|
||||
|
||||
- `ENVELOPE_ID int NOT NULL`
|
||||
- `RECEIVER_ID int NOT NULL`
|
||||
- `SEQUENCE int NOT NULL`
|
||||
- `NAME nvarchar(128) NULL`
|
||||
- `JOB_TITLE nvarchar(128) NULL`
|
||||
- `COMPANY_NAME nvarchar(128) NULL`
|
||||
- `PRIVATE_MESSAGE nvarchar(max) NULL`
|
||||
- `ACCESS_CODE nvarchar(64) NULL`
|
||||
- `ADDED_WHEN datetime NOT NULL`
|
||||
- `CHANGED_WHEN datetime NULL`
|
||||
- `GUID bigint NOT NULL`
|
||||
- `PHONE_NUMBER varchar(20) NULL`
|
||||
- `PARENT_ENVELOPE_RECEIVER_ID int NOT NULL`
|
||||
|
||||
### `TBSIG_ENVELOPE_DOCUMENT`
|
||||
|
||||
- `GUID int NOT NULL`
|
||||
- `ENVELOPE_ID int NOT NULL`
|
||||
- `FILENAME nvarchar(256) NOT NULL`
|
||||
- `FILEPATH nvarchar(256) NOT NULL`
|
||||
- `ADDED_WHEN datetime NOT NULL`
|
||||
- `FILENAME_ORIGINAL nvarchar(256) NULL`
|
||||
- `BYTE_DATA varbinary(max) NULL`
|
||||
|
||||
### `TBSIG_DOCUMENT_RECEIVER_ELEMENT`
|
||||
|
||||
- `GUID int NOT NULL`
|
||||
- `DOCUMENT_ID int NOT NULL`
|
||||
- `RECEIVER_ID int NOT NULL`
|
||||
- `ELEMENT_TYPE int NOT NULL`
|
||||
- `POSITION_X float NOT NULL`
|
||||
- `POSITION_Y float NOT NULL`
|
||||
- `WIDTH float NOT NULL`
|
||||
- `HEIGHT float NOT NULL`
|
||||
- `PAGE int NOT NULL`
|
||||
- `REQUIRED bit NOT NULL`
|
||||
- `TOOLTIP nvarchar(512) NULL`
|
||||
- `READ_ONLY bit NOT NULL`
|
||||
- `ANNOTATION_INDEX int NOT NULL`
|
||||
- `ADDED_WHEN datetime NOT NULL`
|
||||
- `CHANGED_WHEN datetime NULL`
|
||||
- `CHANGED_WHO nvarchar(100) NULL`
|
||||
- `FULL_NAME nvarchar(100) NULL`
|
||||
- `POSITION nvarchar(100) NULL`
|
||||
- `PLACE nvarchar(100) NULL`
|
||||
- `INK varbinary(max) NULL`
|
||||
|
||||
### `TBSIG_DOCUMENT_RECEIVER_ELEMENT_ANNOTATION`
|
||||
|
||||
- `GUID bigint NOT NULL`
|
||||
- `ELEMENT_ID int NOT NULL`
|
||||
- `NAME nvarchar(100) NOT NULL`
|
||||
- `VALUE nvarchar(max) NOT NULL`
|
||||
- `ADDED_WHEN datetime NOT NULL`
|
||||
- `CHANGED_WHEN datetime NULL`
|
||||
- `CHANGED_WHO nvarchar(100) NULL`
|
||||
- `TYPE nvarchar(50) NOT NULL`
|
||||
- `POSITION_X float NULL`
|
||||
- `POSITION_Y float NULL`
|
||||
- `WIDTH float NULL`
|
||||
- `HEIGHT float NULL`
|
||||
|
||||
### `TBSIG_DOCUMENT_STATUS`
|
||||
|
||||
- `GUID int NOT NULL`
|
||||
- `ENVELOPE_ID int NOT NULL`
|
||||
- `RECEIVER_ID int NOT NULL`
|
||||
- `STATUS int NOT NULL`
|
||||
- `STATUS_CHANGED_WHEN datetime NULL`
|
||||
- `VALUE nvarchar(max) NULL`
|
||||
- `ADDED_WHEN datetime NOT NULL`
|
||||
- `CHANGED_WHEN datetime NULL`
|
||||
|
||||
### `TBSIG_ENVELOPE_HISTORY`
|
||||
|
||||
- `GUID bigint NOT NULL`
|
||||
- `ENVELOPE_ID int NOT NULL`
|
||||
- `USER_REFERENCE nvarchar(128) NOT NULL`
|
||||
- `STATUS int NOT NULL`
|
||||
- `ADDED_WHEN datetime NOT NULL`
|
||||
- `ACTION_DATE datetime NULL`
|
||||
- `COMMENT nvarchar(max) NULL`
|
||||
|
||||
### `TBSIG_RECEIVER`
|
||||
|
||||
- `GUID int NOT NULL`
|
||||
- `EMAIL_ADDRESS nvarchar(128) NOT NULL`
|
||||
- `SIGNATURE nvarchar(64) NOT NULL`
|
||||
- `ADDED_WHEN datetime NOT NULL`
|
||||
- `TOTP_SECRET_KEY nvarchar(max) NULL`
|
||||
- `TFA_REG_DEADLINE datetime NULL`
|
||||
- `CULTURE nvarchar(10) NULL`
|
||||
|
||||
### `TBSIG_ENVELOPE_RECEIVER_READ_ONLY`
|
||||
|
||||
- `GUID bigint NOT NULL`
|
||||
- `ENVELOPE_ID bigint NOT NULL`
|
||||
- `RECEIVER_MAIL nvarchar(250) NOT NULL`
|
||||
- `DATE_VALID date NOT NULL`
|
||||
- `ADDED_WHO varchar(100) NOT NULL`
|
||||
- `ADDED_WHEN datetime NULL`
|
||||
- `CHANGED_WHO varchar(100) NULL`
|
||||
- `CHANGED_WHEN datetime NULL`
|
||||
|
||||
### `TBSIG_ENVELOPE_TYPE`
|
||||
|
||||
- `GUID int NOT NULL`
|
||||
- `TITLE nvarchar(128) NOT NULL`
|
||||
- `LANGUAGE nvarchar(5) NOT NULL`
|
||||
- `EXPIRES_DAYS int NULL`
|
||||
- `CERTIFICATION_TYPE int NULL`
|
||||
- `USE_ACCESS_CODE bit NULL`
|
||||
- `FINAL_EMAIL_TO_CREATOR int NULL`
|
||||
- `FINAL_EMAIL_TO_RECEIVERS int NULL`
|
||||
- `ADDED_WHEN datetime NOT NULL`
|
||||
- `CHANGED_WHEN datetime NULL`
|
||||
- `EXPIRES_WARNING_DAYS int NULL`
|
||||
- `SEND_REMINDER_EMAILS bit NULL`
|
||||
- `FIRST_REMINDER_DAYS int NULL`
|
||||
- `REMINDER_INTERVAL_DAYS int NULL`
|
||||
- `CONTRACT_TYPE int NULL`
|
||||
|
||||
### `TBSIG_EMAIL_TEMPLATE`
|
||||
|
||||
- `GUID int NOT NULL`
|
||||
- `NAME nvarchar(64) NULL`
|
||||
- `BODY nvarchar(max) NULL`
|
||||
- `SUBJECT nvarchar(512) NULL`
|
||||
- `ADDED_WHEN datetime NOT NULL`
|
||||
- `CHANGED_WHEN datetime NULL`
|
||||
- `LANG_CODE varchar(5) NOT NULL`
|
||||
|
||||
### `TBEMLP_EMAIL_OUT`
|
||||
|
||||
- `GUID int NOT NULL`
|
||||
- `REMINDER_TYPE_ID int NOT NULL`
|
||||
- `SENDING_PROFILE int NOT NULL`
|
||||
- `REFERENCE_ID int NOT NULL`
|
||||
- `REFERENCE_STRING varchar(200) NULL`
|
||||
- `ENTITY_ID int NULL`
|
||||
- `WF_ID int NOT NULL`
|
||||
- `WF_REFERENCE varchar(200) NULL`
|
||||
- `EMAIL_ADRESS varchar(1000) NOT NULL`
|
||||
- `EMAIL_SUBJ varchar(500) NOT NULL`
|
||||
- `EMAIL_BODY varchar(max) NOT NULL`
|
||||
- `EMAIL_ATTMT1 varchar(512) NULL`
|
||||
- `EMAIL_SENT datetime NULL`
|
||||
- `COMMENT varchar(500) NULL`
|
||||
- `ADDED_WHO varchar(50) NOT NULL`
|
||||
- `ADDED_WHEN datetime NULL`
|
||||
- `CHANGED_WHO varchar(50) NULL`
|
||||
- `CHANGED_WHEN datetime NULL`
|
||||
- `ERROR_TIMESTAMP datetime NULL`
|
||||
- `ERROR_MSG varchar(900) NULL`
|
||||
- `ATTACHMENT1_BYTES varbinary(max) NULL`
|
||||
- `ATT1_RELATED_ID bigint NULL`
|
||||
- `ATT1_REL_TYPE varchar(20) NULL`
|
||||
|
||||
## Constraints and indexes (relevant)
|
||||
|
||||
From EF + DB metadata snapshot:
|
||||
|
||||
- `TBSIG_ENVELOPE`: PK `PK_TBSIG_ENVELOPE (GUID)`.
|
||||
- `TBSIG_ENVELOPE_RECEIVER`: composite PK `PK_TBSIG_ENVELOPE_RECEIVER (ENVELOPE_ID, RECEIVER_ID)`.
|
||||
- `TBSIG_DOCUMENT_RECEIVER_ELEMENT`: PK `GUID`, plus unique key `UQTBSIG_DOCUMENT_RECEIVER_ELEMENT_DocID_PosX_PosY (DOCUMENT_ID, POSITION_X, POSITION_Y)`.
|
||||
- `TBSIG_RECEIVER`: PK `GUID`, unique key `UQTBSIG_RECEIVER (EMAIL_ADDRESS)`.
|
||||
- `TBEMLP_EMAIL_OUT`: PK `GUID`, FK `FK_TBEMLP_EMAIL_OUT_SENDING_PROFILE`.
|
||||
|
||||
## Stored procedures: behavior and code call sites
|
||||
|
||||
### Active in .NET code path
|
||||
|
||||
1) `PRSIG_API_CREATE_ENVELOPE`
|
||||
- Purpose: creates envelope row (`STATUS=1002`, `SOURCE='API'`, defaults) and returns `ENVELOPE_UUID` via output.
|
||||
- DB writes: `TBSIG_ENVELOPE`.
|
||||
- Called by:
|
||||
- SQL template `EnvelopeGenerator.Application/Common/SQL/EnvelopeCreateReadSQL.cs`
|
||||
- executor `EnvelopeGenerator.Infrastructure/Executor/EnvelopeExecutor.cs`
|
||||
- used in `EnvelopeReceiverController.CreateAsync` and `CreateEnvelopeReceiverCommandHandler`.
|
||||
|
||||
2) `PRSIG_API_CREATE_RECEIVER`
|
||||
- Purpose: upserts `TBSIG_RECEIVER` by email, adds envelope assignment row with generated access code.
|
||||
- DB writes: `TBSIG_RECEIVER`, `TBSIG_ENVELOPE_RECEIVER`.
|
||||
- Called by:
|
||||
- SQL template `EnvelopeGenerator.Application/Common/SQL/EnvelopeReceiverAddReadSQL.cs`
|
||||
- executor `EnvelopeGenerator.Infrastructure/Executor/EnvelopeReceiverExecutor.cs`
|
||||
- used in `EnvelopeReceiverController.CreateAsync` and `CreateEnvelopeReceiverCommandHandler`.
|
||||
|
||||
3) `PRSIG_API_ADD_DOC`
|
||||
- Purpose: stores one document for envelope if absent; returns doc id.
|
||||
- DB writes: `TBSIG_ENVELOPE_DOCUMENT`.
|
||||
- Called by:
|
||||
- SQL template `EnvelopeGenerator.Application/Common/SQL/DocumentCreateReadSQL.cs`
|
||||
- executor `EnvelopeGenerator.Infrastructure/Executor/DocumentExecutor.cs`
|
||||
- used in `EnvelopeReceiverController.CreateAsync`.
|
||||
|
||||
4) `PRSIG_API_ADD_DOC_RECEIVER_ELEM`
|
||||
- Purpose: inserts signature element record if `(DOC_ID, RECEIVER_ID)` pair does not exist.
|
||||
- DB writes: `TBSIG_DOCUMENT_RECEIVER_ELEMENT`.
|
||||
- Called directly by:
|
||||
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers/EnvelopeReceiverController.cs`
|
||||
|
||||
5) `PRSIG_API_ADD_HISTORY_STATE`
|
||||
- Purpose: appends history row with status and user reference.
|
||||
- DB writes: `TBSIG_ENVELOPE_HISTORY`.
|
||||
- Called directly by:
|
||||
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers/EnvelopeReceiverController.cs`
|
||||
|
||||
### Legacy/other path references in repo
|
||||
|
||||
- `PRSIG_GET_ENVELOPES_FOR_USER` and `PRSIG_GET_CLOSED_ENVELOPES_FOR_USER` are called in legacy VB path:
|
||||
- `EnvelopeGenerator.CommonServices/Models/EnvelopeModel.vb`
|
||||
|
||||
- `PRSIG_API_WITHDRAW_ENVELOPE` exists in DB snapshot but no active `.cs` call site found in current server flow.
|
||||
|
||||
## Trigger behavior and where they are wired
|
||||
|
||||
## How trigger names are wired in app
|
||||
|
||||
- Config source: `EnvelopeGenerator.Server/EnvelopeGenerator.Server/appsettings.json` -> `DbTriggerParams`.
|
||||
- DI binding: `EnvelopeGenerator.Infrastructure/DependencyInjection.cs` (`AddDbTriggerParams`).
|
||||
- Model binding: `EnvelopeGenerator.Infrastructure/EGDbContext.cs` -> `AddTrigger<T>()` -> `ToTable(tb => tb.HasTrigger(triggerName))`.
|
||||
|
||||
### Trigger definitions observed in DB snapshot
|
||||
|
||||
1) `TBSIG_DOCUMENT_RECEIVER_ELEMENT_AFT_INS`
|
||||
- Fires on insert of `TBSIG_DOCUMENT_RECEIVER_ELEMENT`.
|
||||
- Normalizes/rewrites `POSITION_X`, `POSITION_Y` and sets `CHANGED_WHO='TBSIG_DOC_REC_EL_AFT_INS'`.
|
||||
|
||||
2) `TBSIG_DOCUMENT_RECEIVER_ELEMENT_AFT_UPD`
|
||||
- Fires on update of `TBSIG_DOCUMENT_RECEIVER_ELEMENT`.
|
||||
- Rewrites coordinates and `CHANGED_WHO='TBSIG_DOC_REC_EL_AFT_UPD'` except when `CHANGED_WHO='OFFSET_Y_POS'`.
|
||||
|
||||
3) `TBSIG_ENVELOPE_HISTORY_AFT_INS`
|
||||
- Fires on history insert.
|
||||
- Central state machine side-effects:
|
||||
- syncs `TBSIG_ENVELOPE.STATUS` for selected `100x` states,
|
||||
- on `1003` applies Y-offset update to receiver elements,
|
||||
- on `2005` calculates partial/fully signed and may insert follow-up `1006` history,
|
||||
- on `2007` (type-dependent reject path) writes rejection emails into `TBEMLP_EMAIL_OUT` and updates envelope rejected state,
|
||||
- on `3001` invitation-message counting can move envelope to `1004`.
|
||||
|
||||
4) `TBSIG_EMAIL_TEMPLATE_AFT_UPD`
|
||||
- Fires on update of `TBSIG_EMAIL_TEMPLATE`.
|
||||
- Sets `CHANGED_WHEN = GETDATE()`.
|
||||
|
||||
5) `TBSIG_ENVELOPE_RECEIVER_READ_ONLY_UPD`
|
||||
- Fires on update of `TBSIG_ENVELOPE_RECEIVER_READ_ONLY`.
|
||||
- Updates config timestamp row (`TBSIG_CONFIG.GUID = 1`).
|
||||
|
||||
6) `TBEMLP_EMAIL_OUT_AFT_UPD`
|
||||
- Fires on update of `TBEMLP_EMAIL_OUT`.
|
||||
- When `EMAIL_SENT` changes and `WF_ID in (3001..3005)`, writes corresponding history entries to `TBSIG_ENVELOPE_HISTORY`.
|
||||
|
||||
### Trigger mismatch alert (important)
|
||||
|
||||
- `DbTriggerParams` contains `TBEMLP_EMAIL_OUT_AFT_INS`, but this trigger was not found in the snapshot DB (`ExistsInDb = 0`).
|
||||
- `TBEMLP_EMAIL_OUT_AFT_UPD` exists and is active.
|
||||
|
||||
This should be treated as environment drift and validated before migrations.
|
||||
|
||||
## Known model-vs-DB drift risks
|
||||
|
||||
These are not necessarily bugs, but they are migration risk hotspots:
|
||||
|
||||
- `Receiver.EmailAddress` is `[StringLength(250)]` in entity, DB column is `nvarchar(128)`.
|
||||
- `EnvelopeReceiverReadOnly.AddedWho` entity expects `nvarchar(250)`, DB column is `varchar(100)`.
|
||||
- `EnvelopeReceiver` entity does not model DB columns `GUID` and `PARENT_ENVELOPE_RECEIVER_ID`.
|
||||
- `Envelope` entity does not explicitly model some DB columns (`VERIFICATION_TYPE`, `LANG_CODE`, `SOURCE`).
|
||||
- `EmailOut` entity has `[NotMapped] AddedWhen`, while DB has `ADDED_WHEN` column.
|
||||
|
||||
Before schema changes, reconcile these deltas intentionally (either mapping update or documented accepted divergence).
|
||||
|
||||
## Migration-safe operating rules
|
||||
|
||||
- Do not rely on implicit trigger behavior; document expected side effects in feature code touching history/status flows.
|
||||
- Any change to `TBSIG_ENVELOPE_HISTORY` semantics requires trigger impact review first.
|
||||
- Keep `DbTriggerParams` synchronized with actual DB trigger inventory per environment.
|
||||
- For stored procedure changes, update both:
|
||||
- SQL wrapper templates (`Application/Common/SQL/*`)
|
||||
- direct SQL callers in controllers/executors.
|
||||
|
||||
## Refresh checklist (read-only)
|
||||
|
||||
Run these metadata checks before/after DB migration:
|
||||
|
||||
1. Column inventory per table (`INFORMATION_SCHEMA.COLUMNS`).
|
||||
2. Constraint inventory (`INFORMATION_SCHEMA.TABLE_CONSTRAINTS` + `KEY_COLUMN_USAGE`).
|
||||
3. Trigger inventory (`sys.triggers`) and definitions (`OBJECT_DEFINITION`).
|
||||
4. Stored procedure inventory (`sys.procedures`) and definitions (`OBJECT_DEFINITION`).
|
||||
5. Repo call-site grep for `PRSIG_` names to detect dead or missing integration points.
|
||||
@@ -1,40 +0,0 @@
|
||||
using DigitalData.Core.Exceptions;
|
||||
using FluentValidation;
|
||||
using MediatR;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Behaviors;
|
||||
|
||||
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
|
||||
where TRequest : notnull
|
||||
{
|
||||
private readonly IEnumerable<IValidator<TRequest>> _validators;
|
||||
|
||||
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
|
||||
{
|
||||
_validators = validators;
|
||||
}
|
||||
|
||||
public async Task<TResponse> Handle(
|
||||
TRequest request,
|
||||
RequestHandlerDelegate<TResponse> next,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (_validators.Any())
|
||||
{
|
||||
var context = new ValidationContext<TRequest>(request);
|
||||
var failures = _validators
|
||||
.Select(v => v.Validate(context))
|
||||
.SelectMany(r => r.Errors)
|
||||
.Where(f => f is not null)
|
||||
.ToList();
|
||||
|
||||
if (failures.Count > 0)
|
||||
{
|
||||
var message = string.Join("; ", failures.Select(f => f.ErrorMessage).Distinct());
|
||||
throw new BadRequestException(message);
|
||||
}
|
||||
}
|
||||
|
||||
return await next();
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using MediatR;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Commands;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <typeparam name="TCommand"></typeparam>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
public class CreateCommandHandler<TCommand, TEntity> : IRequestHandler<TCommand, TEntity>
|
||||
where TCommand : class, IRequest<TEntity>
|
||||
where TEntity : class
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected readonly IRepository<TEntity> Repository;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="repository"></param>
|
||||
public CreateCommandHandler(IRepository<TEntity> repository)
|
||||
{
|
||||
Repository = repository;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="cancel"></param>
|
||||
/// <returns></returns>
|
||||
public Task<TEntity> Handle(TCommand request, CancellationToken cancel) => Repository.CreateAsync(request, cancel);
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using MediatR;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Commands;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <typeparam name="TUpdateDto"></typeparam>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
public abstract record UpdateCommand<TUpdateDto, TEntity> : IRequest where TUpdateDto : class where TEntity : class
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public TUpdateDto Update { get; init; } = null!;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public abstract Expression<Func<TEntity, bool>> BuildQueryExpression();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <typeparam name="TCommand"></typeparam>
|
||||
/// <typeparam name="TUpdateDto"></typeparam>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
public class UpdateCommandHandler<TCommand, TUpdateDto, TEntity> : IRequestHandler<TCommand>
|
||||
where TUpdateDto : class
|
||||
where TEntity : class
|
||||
where TCommand : UpdateCommand<TUpdateDto, TEntity>
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected readonly IRepository<TEntity> Repository;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="repository"></param>
|
||||
public UpdateCommandHandler(IRepository<TEntity> repository)
|
||||
{
|
||||
Repository = repository;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="cancel"></param>
|
||||
/// <returns></returns>
|
||||
public Task Handle(TCommand request, CancellationToken cancel)
|
||||
=> Repository.UpdateAsync(request.Update, request.BuildQueryExpression(), cancel);
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
namespace EnvelopeGenerator.Application.Common.Configurations;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class ESignParams
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public required string ApplicationName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string DefaultReason { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
namespace EnvelopeGenerator.Application.Common.Configurations;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class SignatureFieldParams
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public double Width { get; init; } = 1.77;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public double Height { get; init; } = 1.96;
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
namespace EnvelopeGenerator.Application.Common.Dto;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public record AnnotationCreateDto
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Obsolete("Not required for DevExpress")]
|
||||
public int ElementId { get; init; } = -1;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Obsolete("Not required for DevExpress")]
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Obsolete("Not required for DevExpress")]
|
||||
public string Value { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Obsolete("Not required for DevExpress")]
|
||||
public string Type { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Horizontal position of the signature field on the page.
|
||||
/// <br/><br/>
|
||||
/// <b>Unit:</b> INCHES (GdPicture14 native), origin at the <b>top-left</b> corner of the page, X increases to the right.
|
||||
/// <br/>
|
||||
/// <b>Conversion to DevExpress:</b> Multiply by 100 (DX uses 1/100 inch).
|
||||
/// Convert: <c>xDX = xInches * 100.0</c>
|
||||
/// <br/>
|
||||
/// <b>Conversion to PDF Points:</b> Multiply by 72 (PSPDFKit, iText7 use 1/72 inch).
|
||||
/// Convert: <c>xPt = xInches * 72.0</c>
|
||||
/// </summary>
|
||||
public double? X { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Vertical position of the signature field on the page.
|
||||
/// <br/><br/>
|
||||
/// <b>Unit:</b> INCHES (GdPicture14 native), origin at the <b>top-left</b> corner of the page, Y increases downward.
|
||||
/// <br/>
|
||||
/// <b>Conversion to DevExpress:</b> Multiply by 100 (DX uses 1/100 inch).
|
||||
/// Convert: <c>yDX = yInches * 100.0</c>
|
||||
/// <br/>
|
||||
/// <b>Conversion to PDF Points (top-left origin):</b> Multiply by 72.
|
||||
/// Convert: <c>yPt = yInches * 72.0</c>
|
||||
/// <br/>
|
||||
/// <b>Conversion to PDF Points (bottom-left origin - iText7):</b> Y-flip required.
|
||||
/// Convert: <c>yPt = (pageHeightInches - yInches - elemHeightInches) * 72.0</c>
|
||||
/// </summary>
|
||||
public double? Y { get; init; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Obsolete("Not required for DevExpress")]
|
||||
public double? Width { get; init; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Obsolete("Not required for DevExpress")]
|
||||
public double? Height { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Added to eliminate the need for SignatureDto in DevExpress
|
||||
/// </summary>
|
||||
public int? Page { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public record AnnotationDto : AnnotationCreateDto
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public long Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public DateTime AddedWhen { get; init; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public DateTime? ChangedWhen { get; init; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string? ChangedWho { get; init; }
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
using EnvelopeGenerator.Application.Common.Dto.Receiver;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using EnvelopeGenerator.Domain.Interfaces;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Dto;
|
||||
|
||||
// TODO: Add the “Reason” property and integrate with UI and the digital signature process.
|
||||
/// <summary>
|
||||
/// Data Transfer Object representing a positioned element assigned to a document receiver.
|
||||
/// </summary>
|
||||
public class DocReceiverElementDto : IDocReceiverElement, ISignatureInkPayload
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier of the element.
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier of the associated document.
|
||||
/// </summary>
|
||||
public int DocumentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public virtual DocumentDto? Document { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier of the receiver.
|
||||
/// </summary>
|
||||
public int ReceiverId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public virtual ReceiverDto? Receiver { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the element.
|
||||
/// </summary>
|
||||
public int ElementType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the X coordinate of the element.
|
||||
/// </summary>
|
||||
public double X { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Y coordinate of the element.
|
||||
/// </summary>
|
||||
public double Y { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the element.
|
||||
/// </summary>
|
||||
public double Width { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the height of the element.
|
||||
/// </summary>
|
||||
public double Height { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the page number where the element appears.
|
||||
/// </summary>
|
||||
public int Page { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the element is required.
|
||||
/// </summary>
|
||||
public bool Required { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the tooltip text for the element.
|
||||
/// </summary>
|
||||
public string? Tooltip { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the element is read-only.
|
||||
/// </summary>
|
||||
public bool ReadOnly { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the annotation index for ordering or reference.
|
||||
/// </summary>
|
||||
public int AnnotationIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timestamp when the element was added.
|
||||
/// </summary>
|
||||
public DateTime AddedWhen { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timestamp when the element was last changed, if applicable.
|
||||
/// </summary>
|
||||
public DateTime? ChangedWhen { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the top position of the element (in layout terms).
|
||||
/// </summary>
|
||||
public double Top => Y;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the left position of the element (in layout terms).
|
||||
/// </summary>
|
||||
public double Left => X;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public IEnumerable<AnnotationDto>? Annotations { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public SenderAppType SenderAppType { get; set; } = SenderAppType.LegacyFormApp;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string? FullName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string? Position { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string? Place { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public byte[]? Ink { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the stored signature bytes as an image payload with detected MIME type.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Thrown when signature image bytes do not exist.</exception>
|
||||
public (byte[] Bytes, string ContentType, string FileExtension) ToImage()
|
||||
{
|
||||
if (Ink is not { Length: > 0 })
|
||||
throw new InvalidOperationException("Signature image (Ink) does not exist.");
|
||||
|
||||
if (HasPrefix(Ink, 0x89, 0x50, 0x4E, 0x47))
|
||||
return (Ink, "image/png", ".png");
|
||||
|
||||
if (HasPrefix(Ink, 0xFF, 0xD8, 0xFF))
|
||||
return (Ink, "image/jpeg", ".jpg");
|
||||
|
||||
if (HasPrefix(Ink, 0x52, 0x49, 0x46, 0x46) &&
|
||||
Ink.Length >= 12 &&
|
||||
Ink[8] == 0x57 && Ink[9] == 0x45 && Ink[10] == 0x42 && Ink[11] == 0x50)
|
||||
return (Ink, "image/webp", ".webp");
|
||||
|
||||
return (Ink, "application/octet-stream", ".bin");
|
||||
}
|
||||
|
||||
private static bool HasPrefix(byte[] data, params byte[] prefix)
|
||||
{
|
||||
if (data.Length < prefix.Length)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < prefix.Length; i++)
|
||||
{
|
||||
if (data[i] != prefix[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
namespace EnvelopeGenerator.Application.Common.Dto;
|
||||
|
||||
/// <summary>
|
||||
/// Data Transfer Object representing document metadata and elements without binary payload.
|
||||
/// </summary>
|
||||
public record DocumentDetailsDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier of the document.
|
||||
/// </summary>
|
||||
public int Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the envelope ID to which the document belongs.
|
||||
/// </summary>
|
||||
public int EnvelopeId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the date and time when the document was added.
|
||||
/// </summary>
|
||||
public DateTime AddedWhen { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the collection of elements associated with the document for receiver interactions, if any.
|
||||
/// </summary>
|
||||
public IEnumerable<DocReceiverElementDto>? Elements { get; init; }
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
namespace EnvelopeGenerator.Application.Common.Dto;
|
||||
|
||||
/// <summary>
|
||||
/// Data Transfer Object representing a document within an envelope, including optional binary data and form elements.
|
||||
/// </summary>
|
||||
public record DocumentDto : DocumentDetailsDto
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public DocumentDto() { }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="byteData"></param>
|
||||
/// <param name="details"></param>
|
||||
public DocumentDto(byte[] byteData, DocumentDetailsDto details) : base(details)
|
||||
{
|
||||
ByteData = byteData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the binary data of the document, if available.
|
||||
/// </summary>
|
||||
public byte[]? ByteData { get; set; }
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace EnvelopeGenerator.Application.Common.Dto.Messaging;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class GtxMessagingResponse : Dictionary<string, object?> { }
|
||||
@@ -1,16 +0,0 @@
|
||||
namespace EnvelopeGenerator.Application.Common.Dto;
|
||||
|
||||
public sealed class PdfPageImageDto
|
||||
{
|
||||
public int PageNumber { get; init; }
|
||||
|
||||
public double WidthPt { get; init; }
|
||||
|
||||
public double HeightPt { get; init; }
|
||||
|
||||
public int WidthPx { get; init; }
|
||||
|
||||
public int HeightPx { get; init; }
|
||||
|
||||
public string ImageDataUrl { get; init; } = string.Empty;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace EnvelopeGenerator.Application.Common.Dto;
|
||||
|
||||
public sealed class PdfRenderRequestDto
|
||||
{
|
||||
public byte[]? PdfBytes { get; init; }
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
using EnvelopeGenerator.Application.Common.Enums;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Dto;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public sealed record PdfSignOptionsDto
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public long DocumentElementId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string Reason { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string Location { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string? Email { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string? Phone { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string ApplicationName { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public PdfSignatureCertificationLevel CertificationLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public PdfSignatureAppearanceDto? Appearance { get; set; }
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
namespace EnvelopeGenerator.Application.Common.Dto;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public enum PdfSignatureCoordinateUnit
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
Point = 0,
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
Inch = 1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public enum PdfSignatureCoordinateOrigin
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
BottomLeft = 0,
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
TopLeft = 1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="PageNumber"></param>
|
||||
/// <param name="Left"></param>
|
||||
/// <param name="Bottom"></param>
|
||||
/// <param name="Width"></param>
|
||||
/// <param name="Height"></param>
|
||||
/// <param name="ShowLabels"></param>
|
||||
/// <param name="ShowName"></param>
|
||||
/// <param name="ShowDate"></param>
|
||||
/// <param name="ShowReason"></param>
|
||||
/// <param name="ShowLocation"></param>
|
||||
/// <param name="ShowDistinguishedName"></param>
|
||||
/// <param name="DateTimeFormat"></param>
|
||||
/// <param name="CoordinateUnit"></param>
|
||||
/// <param name="CoordinateOrigin"></param>
|
||||
public sealed record PdfSignatureAppearanceDto(
|
||||
int PageNumber,
|
||||
double Left,
|
||||
double Bottom,
|
||||
double Width,
|
||||
double Height,
|
||||
bool ShowLabels = false,
|
||||
bool ShowName = false,
|
||||
bool ShowDate = false,
|
||||
bool ShowReason = false,
|
||||
bool ShowLocation = false,
|
||||
bool ShowDistinguishedName = false,
|
||||
string DateTimeFormat = "dd.MM.yyyy HH:mm:ss zzz",
|
||||
PdfSignatureCoordinateUnit CoordinateUnit = PdfSignatureCoordinateUnit.Point,
|
||||
PdfSignatureCoordinateOrigin CoordinateOrigin = PdfSignatureCoordinateOrigin.BottomLeft)
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="FamilyName"></param>
|
||||
/// <param name="Size"></param>
|
||||
/// <param name="IsBold"></param>
|
||||
/// <param name="IsItalic"></param>
|
||||
/// <param name="IsUnderline"></param>
|
||||
public sealed record FontDto(string FamilyName = "Helvetica", float Size = 10, bool IsBold = false, bool IsItalic = false, bool IsUnderline = false)
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public RgbaColorDto Color { get; init; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="R"></param>
|
||||
/// <param name="G"></param>
|
||||
/// <param name="B"></param>
|
||||
public sealed record RgbaColorDto(double R = 0, double G = 0, double B = 0);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public FontDto Font { get; init; } = new();
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
namespace EnvelopeGenerator.Application.Common.Dto;
|
||||
|
||||
public sealed record PdfSignaturePrincipalDto(
|
||||
string? CommonName,
|
||||
string? Organization,
|
||||
string? Country,
|
||||
string? Full);
|
||||
|
||||
public sealed record PdfSignatureCertificateInfoDto(
|
||||
PdfSignaturePrincipalDto IssuedTo,
|
||||
PdfSignaturePrincipalDto IssuedBy,
|
||||
string? SerialNumber,
|
||||
DateTimeOffset? ValidFrom,
|
||||
DateTimeOffset? ValidTo,
|
||||
bool SelfSigned,
|
||||
bool Expired,
|
||||
bool NotYetValid,
|
||||
bool IsSigner,
|
||||
int? Version,
|
||||
string? KeyAlgorithm,
|
||||
string? SignatureAlgorithm,
|
||||
string? Pem,
|
||||
int? KeySizeBits);
|
||||
|
||||
public sealed record PdfSignatureCheckItemDto(
|
||||
bool CoversWholeDoc,
|
||||
string? Reason,
|
||||
string? BusinessReason,
|
||||
string? Location,
|
||||
string? DeclaredName,
|
||||
string? ContactInfo,
|
||||
string? Email,
|
||||
string? Phone,
|
||||
string? DigestAlgorithm,
|
||||
string? SignatureAlgorithm,
|
||||
string? SignerName,
|
||||
string? Issuer,
|
||||
string? SerialNumber,
|
||||
DateTimeOffset? ValidFrom,
|
||||
DateTimeOffset? ValidTo,
|
||||
string ExpiryCheckedAgainst,
|
||||
IReadOnlyList<PdfSignatureCertificateInfoDto> Certificates,
|
||||
bool Unchanged,
|
||||
string? FieldName,
|
||||
bool IsCryptographicallyValid,
|
||||
string? SigningTime,
|
||||
string? Filter,
|
||||
string? SubFilter,
|
||||
string? ApplicationName,
|
||||
long? DocumentElementId,
|
||||
string? OriginalDocumentSha256,
|
||||
string? MessageDigestHex,
|
||||
string? Error);
|
||||
|
||||
public sealed record PdfSignatureCheckResultDto(
|
||||
bool Signed,
|
||||
IReadOnlyList<PdfSignatureCheckItemDto> Signatures);
|
||||
@@ -1,11 +0,0 @@
|
||||
using System.Dynamic;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Dto;
|
||||
|
||||
/// <summary>
|
||||
/// Represents PSPDFKit annotation data.
|
||||
/// </summary>
|
||||
/// <param name="Instant">Instant annotation data.</param>
|
||||
/// <param name="Structured">Structured annotation data.</param>
|
||||
[Obsolete("The PSPDFKit library is deprecated.")]
|
||||
public record PsPdfKitAnnotation(ExpandoObject Instant, IEnumerable<AnnotationCreateDto> Structured);
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace EnvelopeGenerator.Application.Common.Dto.Receiver;
|
||||
|
||||
public class RecentReceiverSuggestionDto
|
||||
{
|
||||
public required string FullName { get; init; }
|
||||
|
||||
public required string EmailAddress { get; init; }
|
||||
|
||||
public required DateTime LastUsedAt { get; init; }
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
namespace EnvelopeGenerator.Application.Common.Dto;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a captured signature with metadata created by the receiver in the signature popup.
|
||||
/// This model holds the signature image (as base64 data URL) along with signer information
|
||||
/// used for rendering applied signatures on the PDF canvas.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <b>Used in:</b> EnvelopeViewer.razor signature popup workflow
|
||||
/// <br/>
|
||||
/// <b>Creation:</b> User draws/types/uploads signature and fills required fields
|
||||
/// </remarks>
|
||||
public sealed record Signature
|
||||
{
|
||||
/// <summary>
|
||||
/// TBDD_DOCUMENT_RECEIVER_ELEMENT.ID - identifies the specific signature field on the PDF page.
|
||||
/// </summary>
|
||||
public required int Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Base64-encoded data URL of the signature image.
|
||||
/// <br/>
|
||||
/// <b>Format:</b> <c>data:image/png;base64,iVBORw0KG...</c>
|
||||
/// <br/>
|
||||
/// <b>Source:</b> Canvas.toDataURL() from signature pad (draw/text/image tabs)
|
||||
/// <br/>
|
||||
/// <b>Usage:</b> Set as <c>img.src</c> in applied signature overlay
|
||||
/// </summary>
|
||||
public required string DataUrl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Full name of the signer (first and last name).
|
||||
/// <br/>
|
||||
/// <b>Required:</b> Yes (validated in popup)
|
||||
/// <br/>
|
||||
/// <b>Example:</b> "Max Mustermann"
|
||||
/// </summary>
|
||||
public required string FullName { get; init; }
|
||||
|
||||
private readonly string? _position = null;
|
||||
|
||||
/// <summary>
|
||||
/// Job title or position of the signer.
|
||||
/// <br/>
|
||||
/// <b>Required:</b> No (optional field)
|
||||
/// <br/>
|
||||
/// <b>Example:</b> "Geschäftsführer" or empty string
|
||||
/// </summary>
|
||||
public string? Position
|
||||
{
|
||||
get => _position;
|
||||
init => _position = string.IsNullOrWhiteSpace(value) ? null : value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Location/place where the signature was created.
|
||||
/// <br/>
|
||||
/// <b>Required:</b> Yes (validated in popup)
|
||||
/// <br/>
|
||||
/// <b>Display:</b> Shown with current date in German format (dd.MM.yyyy)
|
||||
/// <br/>
|
||||
/// <b>Example:</b> "Berlin" ? rendered as "Berlin, 26.01.2025"
|
||||
/// </summary>
|
||||
public required string Place { get; init; }
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
namespace EnvelopeGenerator.Application.Common.Dto;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a user's UI preferences stored in the distributed cache (dbo.TBDD_CACHE).
|
||||
/// </summary>
|
||||
public sealed record UserPreferencesDto
|
||||
{
|
||||
/// <summary>
|
||||
/// The active DevExpress theme name.
|
||||
/// Allowed values: "blazing-berry", "fluent", "purple"
|
||||
/// </summary>
|
||||
public string ThemeName { get; set; } = "blazing-berry";
|
||||
|
||||
/// <summary>
|
||||
/// Whether dark mode is active.
|
||||
/// Default: false (light mode).
|
||||
/// </summary>
|
||||
public bool IsDarkMode { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Persisted DxGrid layout states, keyed by a stable grid identifier.
|
||||
/// Example keys: "sender.active-envelopes", "sender.completed-envelopes"
|
||||
/// Values are opaque JSON strings produced by DxGrid.SaveLayoutAsync().
|
||||
/// </summary>
|
||||
public Dictionary<string, string> GridLayouts { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The authenticated user's display name ("{GivenName} {Surname}").
|
||||
/// Populated by the server on GET responses from JWT claims.
|
||||
/// NOT persisted in cache — injected at read time by UserPreferencesController.
|
||||
/// Ignored on PUT requests.
|
||||
/// </summary>
|
||||
public string? DisplayName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static UserPreferencesDto Default { get; } = new UserPreferencesDto();
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
namespace EnvelopeGenerator.Application.Common.Enums;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public enum PdfSignatureCertificationLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// All changes are allowed
|
||||
/// </summary>
|
||||
NoCertification = 0,
|
||||
|
||||
/// <summary>
|
||||
/// No changes are allowed
|
||||
/// </summary>
|
||||
NoChangesAllowed,
|
||||
|
||||
/// <summary>
|
||||
/// Allows users to fill in forms and sign the document.
|
||||
/// </summary>
|
||||
FillForms,
|
||||
|
||||
/// <summary>
|
||||
/// Allows users to fill in forms, create, modify and delete annotations, and sign the document.
|
||||
/// </summary>
|
||||
FillFormsAndAnnotate
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
namespace EnvelopeGenerator.Application.Common;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public enum EnvelopeFlag
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
EnvelopeOrReceiverNonexists,
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
NonDecodableEnvelopeReceiverId,
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
WrongEnvelopeReceiverId,
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
AccessCodeNull
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
using AutoMapper;
|
||||
using EnvelopeGenerator.Domain.Interfaces.Auditing;
|
||||
using System;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for applying auditing timestamps during AutoMapper mappings.
|
||||
/// </summary>
|
||||
public static class AutoMapperAuditingExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Maps <see cref="IHasAddedWhen.AddedWhen"/> to the current UTC time.
|
||||
/// </summary>
|
||||
public static IMappingExpression<TSource, TDestination> MapAddedWhen<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
|
||||
where TDestination : IHasAddedWhen
|
||||
=> expression.ForMember(dest => dest.AddedWhen, opt => opt.MapFrom(_ => DateTime.Now));
|
||||
|
||||
/// <summary>
|
||||
/// Maps <see cref="IHasChangedWhen.ChangedWhen"/> to the current UTC time.
|
||||
/// </summary>
|
||||
public static IMappingExpression<TSource, TDestination> MapChangedWhen<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
|
||||
where TDestination : IHasChangedWhen
|
||||
=> expression.ForMember(dest => dest.ChangedWhen, opt => opt.MapFrom(_ => DateTime.Now));
|
||||
|
||||
/// <summary>
|
||||
/// Converts a base64 data URL string to a byte array.
|
||||
/// Handles data URLs in the format: "data:image/png;base64,iVBORw0KG..."
|
||||
/// </summary>
|
||||
/// <param name="dataUrl">The base64 data URL string from Canvas.toDataURL()</param>
|
||||
/// <returns>The decoded byte array, or null if the input is null or empty</returns>
|
||||
public static byte[]? MapDataUrlToRequiredBytes(this string dataUrl)
|
||||
{
|
||||
// Remove data URL prefix (e.g., "data:image/png;base64,")
|
||||
var base64Index = dataUrl.IndexOf(',', StringComparison.Ordinal);
|
||||
if (base64Index == -1)
|
||||
throw new ArgumentException("Invalid data URL format. Unable to extract base64 data.", nameof(dataUrl));
|
||||
|
||||
var base64Data = dataUrl[(base64Index + 1)..];
|
||||
return Convert.FromBase64String(base64Data);
|
||||
}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using System.Text;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Extensions;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class DecodingExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Validates whether a given string is a correctly formatted Base-64 encoded string.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method checks the string for proper Base-64 formatting, which includes validating
|
||||
/// the length of the string (must be divisible by 4). It also checks each character to ensure
|
||||
/// it belongs to the Base-64 character set (A-Z, a-z, 0-9, '+', '/', and '=' for padding).
|
||||
/// The method ensures that padding characters ('=') only appear at the end of the string and
|
||||
/// are in a valid configuration (either one '=' at the end if the string's length % 4 is 3,
|
||||
/// or two '==' if the length % 4 is 2).
|
||||
/// </remarks>
|
||||
/// <param name="input">The Base-64 encoded string to validate.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the string is a valid Base-64 encoded string; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// string testString = "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnk=";
|
||||
/// bool isValid = IsValidBase64String(testString);
|
||||
/// Console.WriteLine(isValid); // Output: true
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static bool IsBase64String(this string input)
|
||||
{
|
||||
// Check if the string is null or empty
|
||||
if (string.IsNullOrEmpty(input))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Replace valid base-64 padding
|
||||
input = input.Trim();
|
||||
int mod4 = input.Length % 4;
|
||||
if (mod4 > 0)
|
||||
{
|
||||
// Base-64 string lengths should be divisible by 4
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check each character to ensure it is valid base-64
|
||||
foreach (char c in input)
|
||||
{
|
||||
if (!char.IsLetterOrDigit(c) && c != '+' && c != '/' && c != '=')
|
||||
{
|
||||
// Invalid character detected
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure no invalid padding scenarios exist
|
||||
if (input.EndsWith("==") && input.Length % 4 == 0 ||
|
||||
input.EndsWith("=") && input.Length % 4 == 3)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return input.IndexOf('=') == -1; // No padding allowed except at the end
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="encodedKey"></param>
|
||||
/// <param name="decodedKeys"></param>
|
||||
/// <returns></returns>
|
||||
public static bool TryDecode(this string encodedKey, out string[] decodedKeys)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] bytes = Convert.FromBase64String(encodedKey);
|
||||
string decodedString = Encoding.UTF8.GetString(bytes);
|
||||
decodedKeys = decodedString.Split(new string[] { "::" }, StringSplitOptions.None);
|
||||
return true;
|
||||
}
|
||||
catch(ArgumentNullException) { }
|
||||
catch (FormatException) { }
|
||||
catch(ArgumentException) { }
|
||||
|
||||
decodedKeys = Array.Empty<string>();
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="decodedKeys"></param>
|
||||
/// <returns></returns>
|
||||
public static EncodeType GetEncodeType(this string[] decodedKeys) => decodedKeys.Length switch
|
||||
{
|
||||
2 => EncodeType.EnvelopeReceiver,
|
||||
3 => long.TryParse(decodedKeys[1], out var _) ? EncodeType.EnvelopeReceiverReadOnly : EncodeType.Undefined,
|
||||
_ => EncodeType.Undefined,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="decodedKeys"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
public static (string? EnvelopeUuid, string? ReceiverSignature) ParseEnvelopeReceiverId(this string[] decodedKeys)
|
||||
=> decodedKeys.GetEncodeType() == EncodeType.EnvelopeReceiver
|
||||
? (EnvelopeUuid: decodedKeys[0], ReceiverSignature: decodedKeys[1])
|
||||
: throw new InvalidOperationException("Attempted to convert a decoded other than type EnvelopeReceiver to EnvelopeReceiver.");
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="decodedKeys"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
public static long ParseReadOnlyId(this string[] decodedKeys)
|
||||
=> decodedKeys.GetEncodeType() == EncodeType.EnvelopeReceiverReadOnly
|
||||
? long.Parse(decodedKeys[1])
|
||||
: throw new InvalidOperationException("Attempted to convert a decoded other than type EnvelopeReceiver to EnvelopeReceiver. ");
|
||||
|
||||
/// <summary>
|
||||
/// Decodes the envelope receiver ID and extracts the envelope UUID and receiver signature.
|
||||
/// </summary>
|
||||
/// <param name="envelopeReceiverId">The base64 encoded string containing the envelope UUID and receiver signature.</param>
|
||||
/// <returns>A tuple containing the envelope UUID and receiver signature.</returns>
|
||||
public static (string? EnvelopeUuid, string? ReceiverSignature) DecodeEnvelopeReceiverId(this string envelopeReceiverId)
|
||||
{
|
||||
if (!envelopeReceiverId.IsBase64String())
|
||||
{
|
||||
return (null, null);
|
||||
}
|
||||
byte[] bytes = Convert.FromBase64String(envelopeReceiverId);
|
||||
string decodedString = Encoding.UTF8.GetString(bytes);
|
||||
string[] parts = decodedString.Split(new string[] { "::" }, StringSplitOptions.None);
|
||||
|
||||
if (parts.Length > 1)
|
||||
return (EnvelopeUuid: parts[0], ReceiverSignature: parts[1]);
|
||||
else
|
||||
return (string.Empty, string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="envelopeReceiverReadOnlyId"></param>
|
||||
/// <returns></returns>
|
||||
public static long? DecodeEnvelopeReceiverReadOnlyId(this string envelopeReceiverReadOnlyId)
|
||||
{
|
||||
if (!envelopeReceiverReadOnlyId.IsBase64String())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
byte[] bytes = Convert.FromBase64String(envelopeReceiverReadOnlyId);
|
||||
string decodedString = Encoding.UTF8.GetString(bytes);
|
||||
string[] parts = decodedString.Split(new string[] { "::" }, StringSplitOptions.None);
|
||||
|
||||
if (parts.Length > 2)
|
||||
return long.TryParse(parts[1], out long readOnlyId) ? readOnlyId : null;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the envelope UUID from the decoded envelope receiver ID.
|
||||
/// </summary>
|
||||
/// <param name="envelopeReceiverId">The base64 encoded string to decode.</param>
|
||||
/// <returns>The envelope UUID.</returns>
|
||||
public static string? GetEnvelopeUuid(this string envelopeReceiverId) => envelopeReceiverId.DecodeEnvelopeReceiverId().EnvelopeUuid;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the receiver signature from the decoded envelope receiver ID.
|
||||
/// </summary>
|
||||
/// <param name="envelopeReceiverId">The base64 encoded string to decode.</param>
|
||||
/// <returns>The receiver signature.</returns>
|
||||
public static string? GetReceiverSignature(this string envelopeReceiverId) => envelopeReceiverId.DecodeEnvelopeReceiverId().ReceiverSignature;
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
using System.Text;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for decoding and extracting information from an envelope receiver ID.
|
||||
/// </summary>
|
||||
public static class EncodingExtensions
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="readOnlyId"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToEnvelopeKey(this long readOnlyId)
|
||||
{
|
||||
//The random number is used as a salt to increase security but it is not saved in the database.
|
||||
string combinedString = $"{Random.Shared.Next()}::{readOnlyId}::{Random.Shared.Next()}";
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(combinedString);
|
||||
string base64String = Convert.ToBase64String(bytes);
|
||||
|
||||
return base64String;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToEnvelopeKey(this (string envelopeUuid, string receiverSignature) input)
|
||||
{
|
||||
string combinedString = $"{input.envelopeUuid}::{input.receiverSignature}";
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(combinedString);
|
||||
string base64String = Convert.ToBase64String(bytes);
|
||||
|
||||
return base64String;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Extensions;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class JsonExtensions
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToJson(this object obj, JsonSerializerOptions? options = null)
|
||||
=> JsonSerializer.Serialize(obj, options);
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
using DigitalData.Core.Exceptions;
|
||||
using EnvelopeGenerator.Application.Common.Extensions;
|
||||
using EnvelopeGenerator.Application.Common.Query;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using EnvelopeGenerator.Domain.Interfaces;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Extensions;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class QueryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <param name="root"></param>
|
||||
/// <param name="query"></param>
|
||||
/// <param name="notnull"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="BadRequestException"></exception>
|
||||
public static IQueryable<TEntity> Where<TEntity>(this IQueryable<TEntity> root, EnvelopeQueryBase query, bool notnull = true)
|
||||
where TEntity : IHasEnvelope
|
||||
{
|
||||
if (query.Id is not null)
|
||||
root = root.Where(e => e.Envelope!.Id == query.Id);
|
||||
else if (query.Uuid is not null)
|
||||
root = root.Where(e => e.Envelope!.Uuid == query.Uuid);
|
||||
else if (notnull)
|
||||
throw new BadRequestException(
|
||||
"Either Envelope Id or Envelope Uuid must be provided in the query."
|
||||
);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="root"></param>
|
||||
/// <param name="query"></param>
|
||||
/// <param name="notnull"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="BadRequestException"></exception>
|
||||
public static IQueryable<Envelope> Where(this IQueryable<Envelope> root, EnvelopeQueryBase query, bool notnull = true)
|
||||
{
|
||||
if (query.Id is not null)
|
||||
root = root.Where(e => e.Id == query.Id);
|
||||
else if (query.Uuid is not null)
|
||||
root = root.Where(e => e.Uuid == query.Uuid);
|
||||
else if (notnull)
|
||||
throw new BadRequestException(
|
||||
"Either Envelope Id or Envelope Uuid must be provided in the query."
|
||||
);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <param name="root"></param>
|
||||
/// <param name="query"></param>
|
||||
/// <param name="notnull"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="BadRequestException"></exception>
|
||||
public static IQueryable<TEntity> Where<TEntity>(this IQueryable<TEntity> root, ReceiverQueryBase query, bool notnull = true)
|
||||
where TEntity : IHasReceiver
|
||||
{
|
||||
if (query.Id is not null)
|
||||
root = root.Where(e => e.Receiver!.Id == query.Id);
|
||||
else if (query.EmailAddress is not null)
|
||||
root = root.Where(e => e.Receiver!.EmailAddress == query.EmailAddress);
|
||||
else if (query.Signature is not null)
|
||||
root = root.Where(e => e.Receiver!.Signature == query.Signature);
|
||||
else if (notnull)
|
||||
throw new BadRequestException(
|
||||
"Receiver must have at least one identifier (Id, EmailAddress, or Signature)."
|
||||
);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="root"></param>
|
||||
/// <param name="query"></param>
|
||||
/// <param name="notnull"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="BadRequestException"></exception>
|
||||
public static IQueryable<Receiver> Where(this IQueryable<Receiver> root, ReceiverQueryBase query, bool notnull = true)
|
||||
{
|
||||
if (query.Id is not null)
|
||||
root = root.Where(e => e.Id == query.Id);
|
||||
else if (query.EmailAddress is not null)
|
||||
root = root.Where(e => e.EmailAddress == query.EmailAddress);
|
||||
else if (query.Signature is not null)
|
||||
root = root.Where(e => e.Signature == query.Signature);
|
||||
else if (notnull)
|
||||
throw new BadRequestException(
|
||||
"Receiver must have at least one identifier (Id, EmailAddress, or Signature)."
|
||||
);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <typeparam name="TEnvelopeQuery"></typeparam>
|
||||
/// <typeparam name="TReceiverQuery"></typeparam>
|
||||
/// <param name="root"></param>
|
||||
/// <param name="query"></param>
|
||||
/// <param name="notnull"></param>
|
||||
/// <returns></returns>
|
||||
public static IQueryable<TEntity> Where<TEntity, TEnvelopeQuery, TReceiverQuery>(this IQueryable<TEntity> root, EnvelopeReceiverQueryBase<TEnvelopeQuery, TReceiverQuery> query, bool notnull = true)
|
||||
where TEntity : IHasEnvelope, IHasReceiver
|
||||
where TEnvelopeQuery : EnvelopeQueryBase, new()
|
||||
where TReceiverQuery : ReceiverQueryBase, new()
|
||||
=> root.Where(query.Envelope, notnull).Where(query.Receiver, notnull);
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using OtpNet;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Extensions;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class StringExtension
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="totp"></param>
|
||||
/// <param name="secret"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsValidTotp(this string totp, string secret)
|
||||
{
|
||||
var secret_bytes = Base32Encoding.ToBytes(secret);
|
||||
var secret_totp = new Totp(secret_bytes);
|
||||
return secret_totp.VerifyTotp(totp, out _, VerificationWindow.RfcSpecifiedNetworkDelay);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="seperator"></param>
|
||||
/// <param name="values"></param>
|
||||
/// <returns></returns>
|
||||
public static string Join(this IEnumerable<string> values, string seperator) => string.Join(seperator, values);
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
using DigitalData.Core.Exceptions;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for tasks
|
||||
/// </summary>
|
||||
[Obsolete("Implement Mediator behaviors in the Osolete .NET project.")]
|
||||
public static class TaskExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Awaits the specified task and ensures that the result is not <c>null</c>.
|
||||
/// If the result is <c>null</c>, the exception created by factory-method is thrown.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the result.</typeparam>
|
||||
/// <typeparam name="TException">The type of the exception.</typeparam>
|
||||
/// <param name="task">The task to await.</param>
|
||||
/// <param name="factory">Exception provider</param>
|
||||
/// <returns>The awaited result if not <c>null</c>.</returns>
|
||||
/// <exception>Thrown if the result is <c>null</c>.</exception>
|
||||
[Obsolete("Implement Mediator behaviors in the Osolete .NET project.")]
|
||||
public static async Task<T> ThrowIfNull<T, TException>(this Task<T?> task, Func<TException> factory) where TException : Exception
|
||||
{
|
||||
var result = await task;
|
||||
return result ?? throw factory();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Awaits the specified task and ensures that the result is not <c>empty</c>.
|
||||
/// If the result contains no elements, the exception created by factory-method is thrown.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The element type of the collection.</typeparam>
|
||||
/// <typeparam name="TException">The type of the exception.</typeparam>
|
||||
/// <param name="task">The task to await.</param>
|
||||
/// <param name="factory">Exception provider</param>
|
||||
/// <returns>The awaited collection if it is not <c>null</c> or empty.</returns>
|
||||
/// <exception cref="NotFoundException">Thrown if the result is <c>null</c> or empty.</exception>
|
||||
[Obsolete("Implement Mediator behaviors in the Osolete .NET project.")]
|
||||
public static async Task<IEnumerable<T>> ThrowIfEmpty<T, TException>(this Task<IEnumerable<T>> task, Func<TException> factory) where TException : Exception
|
||||
{
|
||||
var result = await task;
|
||||
return result?.Any() ?? false ? result : throw factory();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="I"></typeparam>
|
||||
/// <param name="task"></param>
|
||||
/// <param name="act"></param>
|
||||
/// <returns></returns>
|
||||
[Obsolete("Implement Mediator behaviors in the Osolete .NET project.")]
|
||||
public static async Task<I> Then<T, I>(this Task<T> task, Func<T, I> act)
|
||||
{
|
||||
var res = await task;
|
||||
return act(res);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="task"></param>
|
||||
/// <returns></returns>
|
||||
[Obsolete("Implement Mediator behaviors in the Osolete .NET project.")]
|
||||
public static Task<T?> FirstOrDefaultAsync<T>(this Task<IEnumerable<T>> task) => task.Then(t => t.FirstOrDefault());
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="TException"></typeparam>
|
||||
/// <param name="task"></param>
|
||||
/// <param name="factory"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<T> FirstAsync<T, TException>(this Task<IEnumerable<T>> task, Func<TException> factory)
|
||||
where TException : Exception
|
||||
=> task.Then(t => t.FirstOrDefault() ?? throw factory());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static NotFoundException NotFound() => new();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Obsolete("Implement Mediator behaviors in the Osolete .NET project.")]
|
||||
public static BadRequestException BadRequest() => new();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Obsolete("Implement Mediator behaviors in the Osolete .NET project.")]
|
||||
public static ForbiddenException Forbidden() => new();
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
using Ganss.Xss;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using System.Text.Encodings.Web;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Extensions;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class XSSExtensions
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="encoder"></param>
|
||||
/// <returns></returns>
|
||||
public static string? TryEncode(this string? value, UrlEncoder encoder) => value is null ? value : encoder.Encode(value);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="encoder"></param>
|
||||
/// <returns></returns>
|
||||
public static string? TryEncode(this LocalizedString? value, UrlEncoder encoder) => value is null ? null : encoder.Encode(value);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="html"></param>
|
||||
/// <param name="sanitizer"></param>
|
||||
/// <returns></returns>
|
||||
public static string? TrySanitize(this string? html, HtmlSanitizer sanitizer) => html is null ? html : sanitizer.Sanitize(html);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="html"></param>
|
||||
/// <param name="sanitizer"></param>
|
||||
/// <returns></returns>
|
||||
public static string? TrySanitize(this LocalizedString? html, HtmlSanitizer sanitizer) => html is null ? null : sanitizer.Sanitize(html);
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
using DigitalData.Core.Exceptions;
|
||||
using EnvelopeGenerator.Application.Common.Dto;
|
||||
using EnvelopeGenerator.Domain.Interfaces;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Doc;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public interface IPdfPageImageRenderer
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="pdfBytes"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task<IReadOnlyList<PdfPageImageDto>> RenderPagesAsImagesAsync(byte[] pdfBytes, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="element"></param>
|
||||
/// <returns></returns>
|
||||
Stream ComposeSignaturePreviewPng(ISignatureInkPayload element);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="bytes"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsSupportedImageFormat(byte[] bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class PdfPageImageRendererExtensions
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="renderer"></param>
|
||||
/// <param name="inkBytes"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="BadRequestException"></exception>
|
||||
public static byte[] EnsureSupportedImageBytes(this IPdfPageImageRenderer renderer, byte[] inkBytes)
|
||||
{
|
||||
if (!renderer.IsSupportedImageFormat(inkBytes))
|
||||
throw new BadRequestException("Only PNG and JPEG signature formats are supported.");
|
||||
|
||||
return inkBytes;
|
||||
}
|
||||
|
||||
public static byte[] ComposeSignaturePreviewPngBytes(this IPdfPageImageRenderer renderer, ISignatureInkPayload element)
|
||||
{
|
||||
using var stream = renderer.ComposeSignaturePreviewPng(element);
|
||||
|
||||
if (stream is MemoryStream memoryStream)
|
||||
return memoryStream.ToArray();
|
||||
|
||||
using var copy = new MemoryStream();
|
||||
stream.CopyTo(copy);
|
||||
return copy.ToArray();
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public interface IRequestContext
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
string? Origin { get; }
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using DigitalData.Core.Abstraction.Application;
|
||||
using EnvelopeGenerator.Application.Common.Dto;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Obsolete("Use MediatR")]
|
||||
public interface IEnvelopeDocumentService : IBasicCRUDService<DocumentDto, Document, int>
|
||||
{
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using EnvelopeGenerator.Application.Common.Dto;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Defines PDF digital-signing operations for the application boundary.
|
||||
/// </summary>
|
||||
public interface IPdfDigitalSignatureService
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a digital signature for the provided PDF bytes and returns
|
||||
/// a new signed PDF document.
|
||||
/// </summary>
|
||||
/// <param name="inputPdf">Source PDF content stream.</param>
|
||||
/// <param name="outputPdf">Destination stream for signed PDF output.</param>
|
||||
/// <param name="options">Optional signing options that map to PDF signature metadata fields.</param>
|
||||
/// <param name="image">Optional image stream for signature appearance.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for cooperative cancellation.</param>
|
||||
Task SignPdfAsync(Stream inputPdf, Stream outputPdf, PdfSignOptionsDto? options = null, Stream? image = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the provided PDF contains signature field(s) and whether
|
||||
/// those signatures are cryptographically valid.
|
||||
/// </summary>
|
||||
/// <param name="inputPdf">PDF content stream to inspect.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for cooperative cancellation.</param>
|
||||
/// <returns>A structured result containing signature presence and per-signature details.</returns>
|
||||
Task<PdfSignatureCheckResultDto> CheckPdfSignatureAsync(Stream inputPdf, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using EnvelopeGenerator.Application.Common.Dto;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Manages per-user UI preferences (theme, dark mode, grid layouts).
|
||||
/// Preferences are stored in the distributed cache (dbo.TBDD_CACHE) as JSON.
|
||||
/// Cache key schema: "signflow.user-preferences:{username}"
|
||||
/// </summary>
|
||||
public interface IUserPreferencesService
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves preferences for the given user.
|
||||
/// Always returns a valid object — never throws, never returns null.
|
||||
/// Returns defaults if no entry exists in cache.
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task<UserPreferencesDto> GetOrCreateAsync(string id, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Persists the given preferences for the user in the distributed cache.
|
||||
/// Failures are non-fatal and silently ignored.
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="preferences">The preferences to persist.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task SaveAsync(string id, UserPreferencesDto preferences, CancellationToken ct = default);
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
using EnvelopeGenerator.Application.Common.Extensions;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Services.Notification;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public record DocumentReceivedNotification : EnvelopeNotificationBase
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public required string DocumentTitle { get; init; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public required string NameReceiver { get; init; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public required string NameSender { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// [SIGNATURE_TYPE]
|
||||
/// </summary>
|
||||
public required bool IsReadAndConfirm { private get; init; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string SignatureType => IsReadAndConfirm ? "Signieren" : "Lesen und bestätigen";
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public required bool AdvancedSignature { private get; init; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string DocumentProcess => AdvancedSignature ? " und elektronisch unterschreiben" : string.Empty;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public required Uri Origin { private get; init; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public required string EnvelopeUuid { private get; init; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public required string ReceiverSignature { private get; init; }
|
||||
|
||||
private Uri EnvelopeURL => new(Origin, $"Envelope/{(EnvelopeUuid, ReceiverSignature).ToEnvelopeKey()}");
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string LinkToDocument => EnvelopeURL.AbsoluteUri;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string LinkToDocumentText
|
||||
{
|
||||
get
|
||||
{
|
||||
var urlText = EnvelopeURL.ToString();
|
||||
return $"{urlText[..Math.Min(urlText.Length, 40)]}..";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public required string Message { get; init; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string NamePortal { get; set; } = "signFLOW Portal";
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override EmailTemplateType GetTemplateType()
|
||||
{
|
||||
return EmailTemplateType.DocumentReceived;
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Services.Notification;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public record DocumentSignedNotification : EnvelopeNotificationBase
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public required string DocumentTitle { get; init; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public required string NameReceiver { get; init; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string NamePortal { get; init; } = "signFLOW Portal";
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public required bool IsReadAndConfirm { private get; init; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string ReceiverAction => IsReadAndConfirm ? "bestätigt" : "unterschrieben";
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string FinalStatus => IsReadAndConfirm ? "Lesebestätigung" : "Signatur";
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string FinalAction => IsReadAndConfirm ? "Empfänger bestätigt" : "Vertragspartner unterzeichnet";
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public override EmailTemplateType GetTemplateType() => EmailTemplateType.DocumentSigned;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Umschlag abgeschlossen: '[DOCUMENT_TITLE]'
|
||||
|
||||
---
|
||||
|
||||
Guten Tag [NAME_RECEIVER],<br /> <br /> Der Vorgang <B><I>'[DOCUMENT_TITLE]'</I></B> wurde erfolgreich abgeschlossen.<br /> <br /> Sie erhalten das Dokument mit einem detaillierten Ergebnisbericht als Anhang zu dieser Email.<br /> <br /> Mit freundlichen Grüßen<br /> <br /> [NAME_PORTAL]
|
||||
|
||||
*/
|
||||
@@ -1,19 +0,0 @@
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Services.Notification;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public abstract record EnvelopeNotificationBase
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public abstract EmailTemplateType GetTemplateType();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public IReadOnlyList<(string FileName, byte[] Content)>? Attachments { get; init; }
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Services.Notification;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public interface IEnvelopeNotificationService
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnvelopeNotificationBase"></typeparam>
|
||||
/// <param name="receiverEmail"></param>
|
||||
/// <param name="notification"></param>
|
||||
/// <param name="cancel"></param>
|
||||
/// <returns></returns>
|
||||
public Task Notify<TEnvelopeNotificationBase>(string receiverEmail, TEnvelopeNotificationBase notification, CancellationToken cancel = default)
|
||||
where TEnvelopeNotificationBase : EnvelopeNotificationBase;
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
using AutoMapper;
|
||||
using EnvelopeGenerator.Application.Common.Dto.History;
|
||||
using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiver;
|
||||
using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiverReadOnly;
|
||||
using EnvelopeGenerator.Application.Common.Dto.Messaging;
|
||||
using EnvelopeGenerator.Application.Common.Dto.Receiver;
|
||||
using EnvelopeGenerator.Application.Common.Extensions;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
using EnvelopeGenerator.Application.Common.Dto;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the AutoMapper profile configuration for mapping between
|
||||
/// domain entities and data transfer objects (DTOs) used within the EnvelopeGenerator application.
|
||||
/// </summary>
|
||||
public class MappingProfile : Profile
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MappingProfile"/> class.
|
||||
/// Configures the mappings between entities and DTOs used throughout the application.
|
||||
/// </summary>
|
||||
public MappingProfile()
|
||||
{
|
||||
// Entity to DTO mappings
|
||||
CreateMap<Config, ConfigDto>();
|
||||
CreateMap<DocReceiverElement, DocReceiverElementDto>();
|
||||
CreateMap<DocumentStatus, DocumentStatusDto>();
|
||||
CreateMap<EmailTemplate, EmailTemplateDto>();
|
||||
CreateMap<Envelope, EnvelopeDto>()
|
||||
.ForMember(dest => dest.EnvelopeTypeTitle,
|
||||
opt => opt.MapFrom(src => src.Type != null ? src.Type.Title : null));
|
||||
CreateMap<Document, DocumentDto>();
|
||||
CreateMap<Document, DocumentDetailsDto>();
|
||||
CreateMap<History, HistoryDto>().ForMember(dest => dest.ActionDate, opt => opt.MapFrom(src => src.ChangedWhen));
|
||||
CreateMap<History, HistoryCreateDto>().ForMember(dest => dest.ActionDate, opt => opt.MapFrom(src => src.ChangedWhen));
|
||||
CreateMap<EnvelopeReceiver, EnvelopeReceiverDto>();
|
||||
CreateMap<EnvelopeReceiver, EnvelopeReceiverSecretDto>();
|
||||
CreateMap<EnvelopeType, EnvelopeTypeDto>();
|
||||
CreateMap<Receiver, ReceiverDto>();
|
||||
CreateMap<EnvelopeReceiverReadOnly, EnvelopeReceiverReadOnlyDto>();
|
||||
CreateMap<ElementAnnotation, AnnotationDto>();
|
||||
|
||||
// DTO to Entity mappings
|
||||
CreateMap<ConfigDto, Config>();
|
||||
CreateMap<DocReceiverElementDto, DocReceiverElement>();
|
||||
CreateMap<Signature, DocReceiverElement>()
|
||||
.ForMember(dest => dest.Ink, opt => opt.MapFrom(src => src.DataUrl.MapDataUrlToRequiredBytes()))
|
||||
.MapChangedWhen();
|
||||
CreateMap<DocumentStatusDto, DocumentStatus>();
|
||||
CreateMap<EmailTemplateDto, EmailTemplate>();
|
||||
CreateMap<EnvelopeDto, Envelope>();
|
||||
CreateMap<DocumentDto, Document>();
|
||||
CreateMap<HistoryDto, History>().ForMember(dest => dest.ChangedWhen, opt => opt.MapFrom(src => src.ActionDate));
|
||||
CreateMap<HistoryCreateDto, History>().ForMember(dest => dest.ChangedWhen, opt => opt.MapFrom(src => src.ActionDate));
|
||||
CreateMap<EnvelopeReceiverDto, EnvelopeReceiver>();
|
||||
CreateMap<EnvelopeTypeDto, EnvelopeType>();
|
||||
CreateMap<ReceiverDto, Receiver>().ForMember(rcv => rcv.EnvelopeReceivers, rcvReadDto => rcvReadDto.Ignore());
|
||||
CreateMap<EnvelopeReceiverReadOnlyCreateDto, EnvelopeReceiverReadOnly>();
|
||||
CreateMap<EnvelopeReceiverReadOnlyUpdateDto, EnvelopeReceiverReadOnly>();
|
||||
CreateMap<AnnotationCreateDto, ElementAnnotation>()
|
||||
.MapAddedWhen();
|
||||
|
||||
// Messaging mappings
|
||||
// for GTX messaging
|
||||
CreateMap<GtxMessagingResponse, SmsResponse>()
|
||||
.ConstructUsing(gtxRes => gtxRes.Ok()
|
||||
? new SmsResponse() { Ok = true }
|
||||
: new SmsResponse() { Ok = false, Errors = gtxRes });
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
using EnvelopeGenerator.Application.Common.Dto;
|
||||
using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiver;
|
||||
using EnvelopeGenerator.Application.Common.Extensions;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using MediatR;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Notifications.DocSigned;
|
||||
|
||||
/// <summary>
|
||||
/// Notification raised when a document is signed by a receiver.
|
||||
/// </summary>
|
||||
[Obsolete("This notification is deprecated. Use Signature.Commands.SignCommand instead.")]
|
||||
public record DocSignedNotification : INotification, ISendMailNotification
|
||||
{
|
||||
/// <summary>
|
||||
/// The envelope receiver information.
|
||||
/// </summary>
|
||||
public required EnvelopeReceiverDto EnvelopeReceiver { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The PSPDFKit annotation data.
|
||||
/// </summary>
|
||||
[Obsolete("The PSPDFKit library is deprecated.")]
|
||||
public PsPdfKitAnnotation? PsPdfKitAnnotation { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the email template type.
|
||||
/// </summary>
|
||||
public EmailTemplateType TemplateType => EmailTemplateType.DocumentSigned;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the email address of the receiver.
|
||||
/// </summary>
|
||||
public string EmailAddress => EnvelopeReceiver.Receiver?.EmailAddress
|
||||
?? throw new InvalidOperationException($"Receiver is null." +
|
||||
$"DocSignedNotification:\n{this.ToJson(Format.Json.ForDiagnostics)}");
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using EnvelopeGenerator.Application.Common.Dto;
|
||||
using MediatR;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Notifications.DocSigned.Handlers;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Obsolete("The PSPDFKit library is deprecated.")]
|
||||
public class AnnotationHandler : INotificationHandler<DocSignedNotification>
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
private readonly IRepository<ElementAnnotation> _repo;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="repository"></param>
|
||||
public AnnotationHandler(IRepository<ElementAnnotation> repository)
|
||||
{
|
||||
_repo = repository;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="notification"></param>
|
||||
/// <param name="cancel"></param>
|
||||
/// <returns></returns>
|
||||
public async Task Handle(DocSignedNotification notification, CancellationToken cancel)
|
||||
{
|
||||
if (notification.PsPdfKitAnnotation is PsPdfKitAnnotation annot)
|
||||
await _repo.CreateAsync(annot.Structured, cancel);
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
using EnvelopeGenerator.Application.DocStatus.Commands;
|
||||
using EnvelopeGenerator.Application.Common.Dto;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using MediatR;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Notifications.DocSigned.Handlers;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Obsolete("This notification is deprecated. Use Signature.Commands.SignCommand instead.")]
|
||||
public class DocStatusHandler : INotificationHandler<DocSignedNotification>
|
||||
{
|
||||
private const string BlankAnnotationJson = "{}";
|
||||
|
||||
private readonly ISender _sender;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
public DocStatusHandler(ISender sender)
|
||||
{
|
||||
_sender = sender;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="notification"></param>
|
||||
/// <param name="cancel"></param>
|
||||
/// <returns></returns>
|
||||
[Obsolete("This notification is deprecated. Use Signature.Commands.SignCommand instead.")]
|
||||
public Task Handle(DocSignedNotification notification, CancellationToken cancel) => _sender.Send(new CreateDocStatusCommand()
|
||||
{
|
||||
EnvelopeId = notification.EnvelopeReceiver.EnvelopeId,
|
||||
ReceiverId = notification.EnvelopeReceiver.ReceiverId,
|
||||
Value = notification.PsPdfKitAnnotation is PsPdfKitAnnotation annot
|
||||
? JsonSerializer.Serialize(annot.Instant, Format.Json.ForAnnotations)
|
||||
: BlankAnnotationJson
|
||||
}, cancel);
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
using EnvelopeGenerator.Application.Common.Extensions;
|
||||
using EnvelopeGenerator.Application.Histories.Commands;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using MediatR;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Notifications.DocSigned.Handlers;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class HistoryHandler : INotificationHandler<DocSignedNotification>
|
||||
{
|
||||
private readonly ISender _sender;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
public HistoryHandler(ISender sender)
|
||||
{
|
||||
_sender = sender;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="notification"></param>
|
||||
/// <param name="cancel"></param>
|
||||
/// <returns></returns>
|
||||
public async Task Handle(DocSignedNotification notification, CancellationToken cancel)
|
||||
{
|
||||
if (notification.EnvelopeReceiver.Receiver is null)
|
||||
throw new InvalidOperationException($"Receiver information is missing in the notification. DocSignedNotification:\n {notification.ToJson(Format.Json.ForDiagnostics)}");
|
||||
|
||||
await _sender.Send(new CreateHistoryCommand()
|
||||
{
|
||||
EnvelopeId = notification.EnvelopeReceiver.EnvelopeId,
|
||||
UserReference = notification.EnvelopeReceiver.Receiver.EmailAddress,
|
||||
Status = EnvelopeStatus.DocumentSigned,
|
||||
}, cancel);
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using DigitalData.EmailProfilerDispatcher.Abstraction.Entities;
|
||||
using EnvelopeGenerator.Application.Common.Configurations;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using Microsoft.Extensions.Options;
|
||||
using EnvelopeGenerator.Domain.Interfaces;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Notifications.DocSigned.Handlers;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class SendSignedMailHandler : SendMailHandler<DocSignedNotification>
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="tempRepo"></param>
|
||||
/// <param name="emailOutRepo"></param>
|
||||
/// <param name="mailParamsOptions"></param>
|
||||
/// <param name="dispatcherParamsOptions"></param>
|
||||
public SendSignedMailHandler(IRepository<EmailTemplate> tempRepo, IRepository<EmailOut> emailOutRepo, IOptions<MailParams> mailParamsOptions, IOptions<DispatcherParams> dispatcherParamsOptions) : base(tempRepo, emailOutRepo, mailParamsOptions, dispatcherParamsOptions)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="notification"></param>
|
||||
/// <param name="emailOut"></param>
|
||||
protected override void ConfigureEmailOut(DocSignedNotification notification, EmailOut emailOut)
|
||||
{
|
||||
emailOut.ReferenceString = notification.EmailAddress;
|
||||
emailOut.ReferenceId = notification.EnvelopeReceiver.ReceiverId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <param name="notification"></param>
|
||||
/// <returns></returns>
|
||||
protected override Dictionary<string, string> CreatePlaceHolders(DocSignedNotification notification)
|
||||
{
|
||||
var placeHolders = new Dictionary<string, string>()
|
||||
{
|
||||
{ "[NAME_RECEIVER]", notification.EnvelopeReceiver.Name ?? string.Empty },
|
||||
{ "[DOCUMENT_TITLE]", notification.EnvelopeReceiver.Envelope?.Title ?? string.Empty },
|
||||
};
|
||||
|
||||
if (notification.EnvelopeReceiver.Envelope.IsReadAndConfirm())
|
||||
{
|
||||
placeHolders["[SIGNATURE_TYPE]"] = "Lesen und bestätigen";
|
||||
placeHolders["[DOCUMENT_PROCESS]"] = string.Empty;
|
||||
placeHolders["[FINAL_STATUS]"] = "Lesebestätigung";
|
||||
placeHolders["[FINAL_ACTION]"] = "Empfänger bestätigt";
|
||||
placeHolders["[REJECTED_BY_OTHERS]"] = "anderen Empfänger abgelehnt!";
|
||||
placeHolders["[RECEIVER_ACTION]"] = "bestätigt";
|
||||
}
|
||||
else
|
||||
{
|
||||
placeHolders["[SIGNATURE_TYPE]"] = "Signieren";
|
||||
placeHolders["[DOCUMENT_PROCESS]"] = " und elektronisch unterschreiben";
|
||||
placeHolders["[FINAL_STATUS]"] = "Signatur";
|
||||
placeHolders["[FINAL_ACTION]"] = "Vertragspartner unterzeichnet";
|
||||
placeHolders["[REJECTED_BY_OTHERS]"] = "anderen Vertragspartner abgelehnt! Ihre notwendige Unterzeichnung wurde verworfen.";
|
||||
placeHolders["[RECEIVER_ACTION]"] = "unterschrieben";
|
||||
}
|
||||
|
||||
return placeHolders;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using MediatR;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Notifications.EnvelopeDeleted;
|
||||
|
||||
public record EnvelopeDeletedNotification : INotification, ISendMailNotification
|
||||
{
|
||||
public required Envelope Envelope { get; init; }
|
||||
|
||||
public required EnvelopeReceiver EnvelopeReceiver { get; init; }
|
||||
|
||||
public required string Reason { get; init; }
|
||||
|
||||
public required string SenderReference { get; init; }
|
||||
|
||||
public EmailTemplateType TemplateType => EmailTemplateType.DocumentDeleted;
|
||||
|
||||
public string EmailAddress => EnvelopeReceiver.Receiver?.EmailAddress
|
||||
?? throw new InvalidOperationException("Receiver email is missing for EnvelopeDeletedNotification.");
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using DigitalData.EmailProfilerDispatcher.Abstraction.Entities;
|
||||
using EnvelopeGenerator.Application.Common.Configurations;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Notifications.EnvelopeDeleted.Handlers;
|
||||
|
||||
public class SendDeletedMailHandler : SendMailHandler<EnvelopeDeletedNotification>
|
||||
{
|
||||
public SendDeletedMailHandler(
|
||||
IRepository<EmailTemplate> tempRepo,
|
||||
IRepository<EmailOut> emailOutRepo,
|
||||
IOptions<MailParams> mailParamsOptions,
|
||||
IOptions<DispatcherParams> dispatcherParamsOptions)
|
||||
: base(tempRepo, emailOutRepo, mailParamsOptions, dispatcherParamsOptions)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void ConfigureEmailOut(EnvelopeDeletedNotification notification, EmailOut emailOut)
|
||||
{
|
||||
emailOut.ReferenceString = notification.EmailAddress;
|
||||
emailOut.ReferenceId = notification.EnvelopeReceiver.ReceiverId;
|
||||
emailOut.WfId = (int)EnvelopeStatus.MessageDeletionSent;
|
||||
}
|
||||
|
||||
protected override Dictionary<string, string> CreatePlaceHolders(EnvelopeDeletedNotification notification)
|
||||
{
|
||||
var senderName = notification.Envelope.User?.Email;
|
||||
if (string.IsNullOrWhiteSpace(senderName))
|
||||
senderName = notification.SenderReference;
|
||||
|
||||
return new Dictionary<string, string>
|
||||
{
|
||||
["[NAME_RECEIVER]"] = notification.EnvelopeReceiver.Name ?? string.Empty,
|
||||
["[NAME_SENDER]"] = senderName ?? string.Empty,
|
||||
["[DOCUMENT_TITLE]"] = notification.Envelope.Title ?? string.Empty,
|
||||
["[REASON]"] = notification.Reason
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using MediatR;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Notifications.RemoveSignature.Handlers;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class RemoveAnnotationHandler : INotificationHandler<RemoveSignatureNotification>
|
||||
{
|
||||
private readonly IRepository<ElementAnnotation> _repo;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="repository"></param>
|
||||
public RemoveAnnotationHandler(IRepository<ElementAnnotation> repository)
|
||||
{
|
||||
_repo = repository;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="notification"></param>
|
||||
/// <param name="cancel"></param>
|
||||
/// <returns></returns>
|
||||
public Task Handle(RemoveSignatureNotification notification, CancellationToken cancel)
|
||||
{
|
||||
notification.ThrowIfHasNoFilter();
|
||||
return _repo.DeleteAsync(annots =>
|
||||
{
|
||||
// envelope ID filter
|
||||
if (notification.EnvelopeId is int envelopeId)
|
||||
annots = annots.Where(annot => annot.Element!.Document.EnvelopeId == envelopeId);
|
||||
|
||||
// envelope UUID filter
|
||||
if (notification.EnvelopeUuid is string envelopeUuid)
|
||||
annots = annots.Where(annot => annot.Element!.Document.Envelope!.Uuid == envelopeUuid);
|
||||
|
||||
// receiver ID
|
||||
if (notification.ReceiverId is int receiverId)
|
||||
annots = annots.Where(annot => annot.Element!.ReceiverId == receiverId);
|
||||
|
||||
// receiver signature
|
||||
if (notification.ReceiverSignature is string receiverSignature)
|
||||
annots = annots.Where(annot => annot.Element!.Receiver!.Signature == receiverSignature);
|
||||
|
||||
return annots;
|
||||
}, cancel);
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
using AngleSharp.Html;
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using MediatR;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Notifications.RemoveSignature.Handlers;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class RemoveDocStatusHandler : INotificationHandler<RemoveSignatureNotification>
|
||||
{
|
||||
private readonly IRepository<Domain.Entities.DocumentStatus> _repo;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="repository"></param>
|
||||
public RemoveDocStatusHandler(IRepository<Domain.Entities.DocumentStatus> repository)
|
||||
{
|
||||
_repo = repository;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="notification"></param>
|
||||
/// <param name="cancel"></param>
|
||||
/// <returns></returns>
|
||||
public Task Handle(RemoveSignatureNotification notification, CancellationToken cancel)
|
||||
{
|
||||
notification.ThrowIfHasNoFilter();
|
||||
return _repo.DeleteAsync(statuses =>
|
||||
{
|
||||
// envelope ID filter
|
||||
if (notification.EnvelopeId is int envelopeId)
|
||||
statuses = statuses.Where(status => status.EnvelopeId == envelopeId);
|
||||
|
||||
// envelope UUID filter
|
||||
if (notification.EnvelopeUuid is string envelopeUuid)
|
||||
statuses = statuses.Where(status => status.Envelope!.Uuid == envelopeUuid);
|
||||
|
||||
// receiver Id filter
|
||||
if (notification.ReceiverId is int receiverId)
|
||||
statuses = statuses.Where(status => status.ReceiverId == receiverId);
|
||||
|
||||
// receiver signature filter
|
||||
if (notification.ReceiverSignature is string receiverSignature)
|
||||
statuses = statuses.Where(status => status.Receiver!.Signature == receiverSignature);
|
||||
|
||||
return statuses;
|
||||
}, cancel);
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using EnvelopeGenerator.Application.Common.Extensions;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using MediatR;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Notifications.RemoveSignature.Handlers;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class RemoveHistoryHandler : INotificationHandler<RemoveSignatureNotification>
|
||||
{
|
||||
private readonly IRepository<Domain.Entities.History> _repo;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="repository"></param>
|
||||
public RemoveHistoryHandler(IRepository<Domain.Entities.History> repository)
|
||||
{
|
||||
_repo = repository;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="notification"></param>
|
||||
/// <param name="cancel"></param>
|
||||
/// <returns></returns>
|
||||
public Task Handle(RemoveSignatureNotification notification, CancellationToken cancel)
|
||||
{
|
||||
notification.ThrowIfHasNoFilter();
|
||||
return _repo.DeleteAsync(hists =>
|
||||
{
|
||||
hists = hists.Where(hist => hist.Status == EnvelopeStatus.DocumentSigned);
|
||||
|
||||
// envelope ID filter
|
||||
if (notification.EnvelopeId is int envelopeId)
|
||||
hists = hists.Where(hist => hist.EnvelopeId == envelopeId);
|
||||
|
||||
// envelope UUID filter
|
||||
if (notification.EnvelopeUuid is string envelopeUuid)
|
||||
hists = hists.Where(hist => hist.Envelope!.Uuid == envelopeUuid);
|
||||
|
||||
// receiver ID filter
|
||||
if (notification.ReceiverId is int receiverId)
|
||||
hists = hists.Where(hist => hist.Receiver!.Id == receiverId);
|
||||
|
||||
// receiver signature filter
|
||||
if (notification.ReceiverSignature is string receiverSignature)
|
||||
hists = hists.Where(hist => hist.Receiver!.Signature == receiverSignature);
|
||||
|
||||
return hists;
|
||||
}, cancel);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
using MediatR;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Notifications.RemoveSignature;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="EnvelopeId"></param>
|
||||
/// <param name="ReceiverId"></param>
|
||||
/// <param name="EnvelopeUuid"></param>
|
||||
/// <param name="ReceiverSignature"></param>
|
||||
public record RemoveSignatureNotification(
|
||||
int? EnvelopeId = null,
|
||||
int? ReceiverId = null,
|
||||
string? EnvelopeUuid = null,
|
||||
string? ReceiverSignature = null
|
||||
) : INotification
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool HasFilter =>
|
||||
EnvelopeId is not null
|
||||
|| ReceiverId is not null
|
||||
|| EnvelopeUuid is not null
|
||||
|| ReceiverSignature is not null;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
public void ThrowIfHasNoFilter()
|
||||
{
|
||||
if (!HasFilter)
|
||||
throw new InvalidOperationException("At least one filter parameter must be provided.");
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using DigitalData.EmailProfilerDispatcher.Abstraction.Entities;
|
||||
using EnvelopeGenerator.Application.Common.Configurations;
|
||||
using EnvelopeGenerator.Application.Common.Extensions;
|
||||
using EnvelopeGenerator.Application.Common.Notifications.EnvelopeDeleted;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Notifications;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public interface ISendMailNotification : INotification
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public EmailTemplateType TemplateType { get; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string EmailAddress { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public abstract class SendMailHandler<TNotification> : INotificationHandler<TNotification>
|
||||
where TNotification : ISendMailNotification
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected readonly IRepository<EmailTemplate> TempRepo;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected readonly IRepository<EmailOut> EmailOutRepo;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected abstract Dictionary<string, string> CreatePlaceHolders(TNotification notification);
|
||||
|
||||
/// <summary>
|
||||
///{ "[MESSAGE]", notification.Message },<br/>
|
||||
///{ "[DOCUMENT_ACCESS_CODE]", notification.ReceiverAccessCode },<br/>
|
||||
///{ "[REASON]", pReason }<br/>
|
||||
///{ "[NAME_SENDER]", notification.Envelope.User?.FullName},<br/>
|
||||
///{ "[NAME_PORTAL]", DispatcherParams. },<br/>
|
||||
///{ "[SIGNATURE_TYPE]", "signieren" },<br/>
|
||||
///{ "[LINK_TO_DOCUMENT]", notification.SignatureLink },<br/>
|
||||
///{ "[LINK_TO_DOCUMENT_TEXT]", $"{notification.SignatureLink.Truncate(40)}.." },
|
||||
/// </summary>
|
||||
protected readonly MailParams MailParams;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected readonly DispatcherParams DispatcherParams;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="notification"></param>
|
||||
/// <param name="emailOut"></param>
|
||||
protected abstract void ConfigureEmailOut(TNotification notification, EmailOut emailOut);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="tempRepo"></param>
|
||||
/// <param name="emailOutRepo"></param>
|
||||
/// <param name="mailParamsOptions"></param>
|
||||
/// <param name="dispatcherParamsOptions"></param>
|
||||
protected SendMailHandler(IRepository<EmailTemplate> tempRepo, IRepository<EmailOut> emailOutRepo, IOptions<MailParams> mailParamsOptions, IOptions<DispatcherParams> dispatcherParamsOptions)
|
||||
{
|
||||
TempRepo = tempRepo;
|
||||
EmailOutRepo = emailOutRepo;
|
||||
MailParams = mailParamsOptions.Value;
|
||||
DispatcherParams = dispatcherParamsOptions.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="notification"></param>
|
||||
/// <param name="cancel"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public virtual async Task Handle(TNotification notification, CancellationToken cancel)
|
||||
{
|
||||
// do not send email if the envelope is deleted and the status is less than or equal to EnvelopeSaved
|
||||
if (notification is EnvelopeDeletedNotification delNot && delNot.Envelope.Status <= EnvelopeStatus.EnvelopeSaved)
|
||||
return;
|
||||
|
||||
var placeHolders = CreatePlaceHolders(notification);
|
||||
|
||||
var temp = await TempRepo
|
||||
.Where(x => x.Name == notification.TemplateType.ToString())
|
||||
.SingleOrDefaultAsync(cancel)
|
||||
?? throw new InvalidOperationException($"Receiver information is missing in the notification." +
|
||||
$"{typeof(TNotification)}:\n {notification.ToJson(Format.Json.ForDiagnostics)}");
|
||||
|
||||
temp.Subject = ReplacePlaceHolders(temp.Subject, placeHolders, MailParams.Placeholders);
|
||||
|
||||
temp.Body = ReplacePlaceHolders(temp.Body, placeHolders, MailParams.Placeholders);
|
||||
|
||||
var emailOut = new EmailOut
|
||||
{
|
||||
EmailAddress = notification.EmailAddress,
|
||||
EmailBody = temp.Body,
|
||||
EmailSubj = temp.Subject,
|
||||
AddedWhen = DateTime.Now,
|
||||
AddedWho = DispatcherParams.AddedWho,
|
||||
SendingProfile = DispatcherParams.SendingProfile,
|
||||
ReminderTypeId = DispatcherParams.ReminderTypeId,
|
||||
EmailAttmt1 = DispatcherParams.EmailAttmt1,
|
||||
WfId = (int)EnvelopeStatus.MessageConfirmationSent,
|
||||
|
||||
};
|
||||
ConfigureEmailOut(notification, emailOut);
|
||||
await EmailOutRepo.CreateAsync(emailOut, cancel);
|
||||
}
|
||||
|
||||
private static string ReplacePlaceHolders(string text, params Dictionary<string, string>[] placeHoldersList)
|
||||
{
|
||||
foreach (var placeHolders in placeHoldersList)
|
||||
foreach (var ph in placeHolders)
|
||||
text = text.Replace(ph.Key, ph.Value);
|
||||
return text;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
namespace EnvelopeGenerator.Application.Common.Query;
|
||||
|
||||
/// <summary>
|
||||
/// Repräsentiert eine Abfrage für Umschläge.
|
||||
/// </summary>
|
||||
public record EnvelopeQueryBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Die eindeutige Kennung des Umschlags.
|
||||
/// </summary>
|
||||
public virtual int? Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Die universell eindeutige Kennung des Umschlags.
|
||||
/// </summary>
|
||||
public virtual string? Uuid { get; set; }
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
using DigitalData.Core.Exceptions;
|
||||
using EnvelopeGenerator.Application.Common.Extensions;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Query;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public record EnvelopeReceiverQueryBase : EnvelopeReceiverQueryBase<EnvelopeQueryBase, ReceiverQueryBase>;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnvelopeQuery"></typeparam>
|
||||
/// <typeparam name="TReceiverQuery"></typeparam>
|
||||
public record EnvelopeReceiverQueryBase<TEnvelopeQuery, TReceiverQuery>
|
||||
where TEnvelopeQuery : EnvelopeQueryBase, new()
|
||||
where TReceiverQuery : ReceiverQueryBase, new()
|
||||
{
|
||||
private string? _key;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public virtual string? Key
|
||||
{
|
||||
get => _key;
|
||||
set
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
_key = null;
|
||||
return;
|
||||
}
|
||||
|
||||
(string? EnvelopeUuid, string? ReceiverSignature) = value.DecodeEnvelopeReceiverId();
|
||||
if (string.IsNullOrEmpty(EnvelopeUuid) || string.IsNullOrEmpty(ReceiverSignature))
|
||||
throw new BadRequestException("Der EnvelopeReceiverKey muss ein gültiger Base64-kodierter String sein, der die EnvelopeUuid und die ReceiverSignature enthält.");
|
||||
|
||||
Envelope = new TEnvelopeQuery()
|
||||
{
|
||||
Uuid = EnvelopeUuid
|
||||
};
|
||||
Receiver = new TReceiverQuery()
|
||||
{
|
||||
Signature = ReceiverSignature
|
||||
};
|
||||
_key = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Repräsentiert eine Abfrage für Umschläge.
|
||||
/// </summary>
|
||||
public virtual TEnvelopeQuery Envelope { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Stellt eine Abfrage dar, um die Details eines Empfängers zu lesen.
|
||||
/// um spezifische Informationen über einen Empfänger abzurufen.
|
||||
/// </summary>
|
||||
public virtual TReceiverQuery Receiver { get; set; } = new();
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using AutoMapper;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Query;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class MappingProfile : Profile
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public MappingProfile()
|
||||
{
|
||||
CreateMap<EnvelopeQueryBase, Envelope>();
|
||||
CreateMap<ReceiverQueryBase, Receiver>();
|
||||
CreateMap<EnvelopeReceiverQueryBase, EnvelopeReceiver>();
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Query;
|
||||
|
||||
/// <summary>
|
||||
/// Stellt eine Abfrage dar, um die Details eines Empfängers zu lesen.
|
||||
/// um spezifische Informationen über einen Empfänger abzurufen.
|
||||
/// </summary>
|
||||
public record ReceiverQueryBase
|
||||
{
|
||||
/// <summary>
|
||||
/// ID des Empfängers
|
||||
/// </summary>
|
||||
public virtual int? Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// E-Mail Adresse des Empfängers
|
||||
/// </summary>
|
||||
public virtual string? EmailAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Eindeutige Signatur des Empfängers
|
||||
/// </summary>
|
||||
public virtual string? Signature { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether any of the specified query criteria have a value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property returns <c>true</c> if at least one of the fields
|
||||
/// <see cref="Id"/>, <see cref="EmailAddress"/>, or <see cref="Signature"/> is not null.
|
||||
/// <para>Usage example: The query can be executed only if at least one criterion is specified.</para>
|
||||
/// </remarks>
|
||||
[NotMapped]
|
||||
public virtual bool HasAnyCriteria => Id is not null || EmailAddress is not null || Signature is not null;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace EnvelopeGenerator.Application.Common.Configurations;
|
||||
namespace EnvelopeGenerator.Application.Configurations;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace EnvelopeGenerator.Application.Configurations;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class DbTriggerParams : Dictionary<string, ICollection<string>>
|
||||
{
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace EnvelopeGenerator.Application.Common.Configurations;
|
||||
namespace EnvelopeGenerator.Application.Configurations;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -23,5 +23,5 @@ public class DispatcherParams
|
||||
/// <summary>
|
||||
/// Default value is string.Empty
|
||||
/// </summary>
|
||||
public string? EmailAttmt1 { get; init; } = null;
|
||||
public string EmailAttmt1 { get; init; } = string.Empty;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using DigitalData.Core.Client.Interface;
|
||||
namespace EnvelopeGenerator.Application.Common.Configurations;
|
||||
namespace EnvelopeGenerator.Application.Configurations;
|
||||
|
||||
/// <summary>
|
||||
/// https://www.gtx-messaging.com/en/api-docs/sms-rest-api/
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace EnvelopeGenerator.Application.Common.Configurations;
|
||||
namespace EnvelopeGenerator.Application.Configurations;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -1,7 +1,7 @@
|
||||
using OtpNet;
|
||||
using System.Globalization;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Configurations;
|
||||
namespace EnvelopeGenerator.Application.Configurations;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -1,7 +1,7 @@
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Repositories;
|
||||
namespace EnvelopeGenerator.Application.Contracts.Repositories;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -1,11 +1,11 @@
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Repositories;
|
||||
namespace EnvelopeGenerator.Application.Contracts.Repositories;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Obsolete("Use IRepository")]
|
||||
public interface IDocumentReceiverElementRepository : ICRUDRepository<DocReceiverElement, int>
|
||||
public interface IDocumentReceiverElementRepository : ICRUDRepository<DocumentReceiverElement, int>
|
||||
{
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Repositories;
|
||||
namespace EnvelopeGenerator.Application.Contracts.Repositories;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -1,8 +1,8 @@
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using static EnvelopeGenerator.Domain.Constants;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Repositories;
|
||||
namespace EnvelopeGenerator.Application.Contracts.Repositories;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -0,0 +1,12 @@
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Contracts.Repositories;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Obsolete("Use Read-method returning IReadQuery<TEntity> instead.")]
|
||||
public interface IEnvelopeCertificateRepository : ICRUDRepository<EnvelopeCertificate, int>
|
||||
{
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Repositories;
|
||||
namespace EnvelopeGenerator.Application.Contracts.Repositories;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Obsolete("Use IRepository")]
|
||||
public interface IEnvelopeDocumentRepository : ICRUDRepository<Document, int>
|
||||
public interface IEnvelopeDocumentRepository : ICRUDRepository<EnvelopeDocument, int>
|
||||
{
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using EnvelopeGenerator.Domain;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Repositories;
|
||||
namespace EnvelopeGenerator.Application.Contracts.Repositories;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Obsolete("Use IRepository")]
|
||||
public interface IEnvelopeHistoryRepository : ICRUDRepository<History, long>
|
||||
public interface IEnvelopeHistoryRepository : ICRUDRepository<EnvelopeHistory, long>
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
@@ -17,7 +17,7 @@ public interface IEnvelopeHistoryRepository : ICRUDRepository<History, long>
|
||||
/// <param name="userReference"></param>
|
||||
/// <param name="status"></param>
|
||||
/// <returns></returns>
|
||||
Task<int> CountAsync(int? envelopeId = null, string? userReference = null, EnvelopeStatus? status = null);
|
||||
Task<int> CountAsync(int? envelopeId = null, string? userReference = null, Constants.EnvelopeStatus? status = null);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -28,5 +28,5 @@ public interface IEnvelopeHistoryRepository : ICRUDRepository<History, long>
|
||||
/// <param name="withSender"></param>
|
||||
/// <param name="withReceiver"></param>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<History>> ReadAsync(int? envelopeId = null, string? userReference = null, EnvelopeStatus? status = null, bool withSender = false, bool withReceiver = false);
|
||||
Task<IEnumerable<EnvelopeHistory>> ReadAsync(int? envelopeId = null, string? userReference = null, Constants.EnvelopeStatus? status = null, bool withSender = false, bool withReceiver = false);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Repositories;
|
||||
namespace EnvelopeGenerator.Application.Contracts.Repositories;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -1,9 +1,7 @@
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using EnvelopeGenerator.Application.Envelopes.Queries;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Repositories;
|
||||
namespace EnvelopeGenerator.Application.Contracts.Repositories;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -85,7 +83,7 @@ public interface IEnvelopeReceiverRepository : ICRUDRepository<EnvelopeReceiver,
|
||||
/// <param name="max_status"></param>
|
||||
/// <param name="ignore_statuses"></param>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<EnvelopeReceiver>> ReadByUsernameAsync(string username, EnvelopeStatus? min_status = null, EnvelopeStatus? max_status = null, params EnvelopeStatus[] ignore_statuses);
|
||||
Task<IEnumerable<EnvelopeReceiver>> ReadByUsernameAsync(string username, int? min_status = null, int? max_status = null, params int[] ignore_statuses);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -1,8 +1,7 @@
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Repositories;
|
||||
namespace EnvelopeGenerator.Application.Contracts.Repositories;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -39,5 +38,5 @@ public interface IEnvelopeRepository : ICRUDRepository<Envelope, int>
|
||||
/// <param name="max_status"></param>
|
||||
/// <param name="ignore_statuses"></param>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<Envelope>> ReadByUserAsync(int userId, EnvelopeStatus? min_status = null, EnvelopeStatus? max_status = null, params EnvelopeStatus[] ignore_statuses);
|
||||
Task<IEnumerable<Envelope>> ReadByUserAsync(int userId, int? min_status = null, int? max_status = null, params int[] ignore_statuses);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Repositories;
|
||||
namespace EnvelopeGenerator.Application.Contracts.Repositories;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -1,7 +1,7 @@
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Repositories;
|
||||
namespace EnvelopeGenerator.Application.Contracts.Repositories;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -1,6 +1,6 @@
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.SQLExecutor;
|
||||
namespace EnvelopeGenerator.Application.Contracts.SQLExecutor;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -14,5 +14,5 @@ public interface IDocumentExecutor
|
||||
/// <param name="envelope_uuid"></param>
|
||||
/// <param name="cancellation"></param>
|
||||
/// <returns></returns>
|
||||
Task<Document> CreateDocumentAsync(string base64, string envelope_uuid, CancellationToken cancellation = default);
|
||||
Task<EnvelopeDocument> CreateDocumentAsync(string base64, string envelope_uuid, CancellationToken cancellation = default);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using Dapper;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.SQLExecutor;
|
||||
namespace EnvelopeGenerator.Application.Contracts.SQLExecutor;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -1,6 +1,6 @@
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.SQLExecutor;
|
||||
namespace EnvelopeGenerator.Application.Contracts.SQLExecutor;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.SQLExecutor;
|
||||
namespace EnvelopeGenerator.Application.Contracts.SQLExecutor;
|
||||
|
||||
/// <summary>
|
||||
/// Provides methods for executing common queries on a given entity type.
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.SQLExecutor;
|
||||
namespace EnvelopeGenerator.Application.Contracts.SQLExecutor;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a raw SQL query contract.
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.SQLExecutor;
|
||||
namespace EnvelopeGenerator.Application.Contracts.SQLExecutor;
|
||||
|
||||
/// <summary>
|
||||
/// Defines methods for executing raw SQL queries or custom SQL query classes and returning query executors for further operations.
|
||||
@@ -1,6 +1,6 @@
|
||||
using Dapper;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.SQLExecutor;
|
||||
namespace EnvelopeGenerator.Application.Contracts.SQLExecutor;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -1,6 +1,6 @@
|
||||
using OtpNet;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||
namespace EnvelopeGenerator.Application.Contracts.Services;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -1,9 +1,9 @@
|
||||
using DigitalData.Core.Abstraction.Application;
|
||||
using DigitalData.Core.Abstraction.Application.DTO;
|
||||
using EnvelopeGenerator.Application.Common.Dto;
|
||||
using EnvelopeGenerator.Application.DTOs;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||
namespace EnvelopeGenerator.Application.Contracts.Services;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -1,13 +1,13 @@
|
||||
using DigitalData.Core.Abstraction.Application;
|
||||
using EnvelopeGenerator.Application.Common.Dto;
|
||||
using EnvelopeGenerator.Application.DTOs;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||
namespace EnvelopeGenerator.Application.Contracts.Services;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Obsolete("Use MediatR")]
|
||||
public interface IDocumentReceiverElementService : IBasicCRUDService<DocReceiverElementDto, DocReceiverElement, int>
|
||||
public interface IDocumentReceiverElementService : IBasicCRUDService<DocumentReceiverElementDto, DocumentReceiverElement, int>
|
||||
{
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using DigitalData.Core.Abstraction.Application;
|
||||
using EnvelopeGenerator.Application.Common.Dto;
|
||||
using EnvelopeGenerator.Application.DTOs;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||
namespace EnvelopeGenerator.Application.Contracts.Services;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -1,10 +1,10 @@
|
||||
using DigitalData.Core.Abstraction.Application;
|
||||
using DigitalData.Core.Abstraction.Application.DTO;
|
||||
using EnvelopeGenerator.Application.Common.Dto;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using EnvelopeGenerator.Application.DTOs;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using static EnvelopeGenerator.Domain.Constants;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||
namespace EnvelopeGenerator.Application.Contracts.Services;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -0,0 +1,13 @@
|
||||
using DigitalData.Core.Abstraction.Application;
|
||||
using EnvelopeGenerator.Application.DTOs;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Contracts.Services;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Obsolete("Use MediatR")]
|
||||
public interface IEnvelopeCertificateService : IBasicCRUDService<EnvelopeCertificateDto, EnvelopeCertificate, int>
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using DigitalData.Core.Abstraction.Application;
|
||||
using EnvelopeGenerator.Application.DTOs;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Contracts.Services;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Obsolete("Use MediatR")]
|
||||
public interface IEnvelopeDocumentService : IBasicCRUDService<EnvelopeDocumentDto, EnvelopeDocument, int>
|
||||
{
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
using DigitalData.Core.Abstraction.Application;
|
||||
using DigitalData.Core.Abstraction.Application.DTO;
|
||||
using EnvelopeGenerator.Application.Common.Dto.History;
|
||||
using EnvelopeGenerator.Application.Common.Dto.Receiver;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using EnvelopeGenerator.Application.DTOs.EnvelopeHistory;
|
||||
using EnvelopeGenerator.Application.DTOs.Receiver;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using static EnvelopeGenerator.Domain.Constants;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||
namespace EnvelopeGenerator.Application.Contracts.Services;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Obsolete("Use MediatR")]
|
||||
public interface IEnvelopeHistoryService : ICRUDService<HistoryCreateDto, HistoryDto, History, long>
|
||||
public interface IEnvelopeHistoryService : ICRUDService<EnvelopeHistoryCreateDto, EnvelopeHistoryDto, EnvelopeHistory, long>
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
@@ -56,7 +56,7 @@ public interface IEnvelopeHistoryService : ICRUDService<HistoryCreateDto, Histor
|
||||
/// <param name="withSender"></param>
|
||||
/// <param name="withReceiver"></param>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<HistoryDto>> ReadAsync(int? envelopeId = null, string? userReference = null, ReferenceType? referenceType = null, EnvelopeStatus? status = null, bool withSender = false, bool withReceiver = false);
|
||||
Task<IEnumerable<EnvelopeHistoryDto>> ReadAsync(int? envelopeId = null, string? userReference = null, ReferenceType? referenceType = null, EnvelopeStatus? status = null, bool withSender = false, bool withReceiver = false);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -64,14 +64,14 @@ public interface IEnvelopeHistoryService : ICRUDService<HistoryCreateDto, Histor
|
||||
/// <param name="envelopeId"></param>
|
||||
/// <param name="userReference"></param>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<HistoryDto>> ReadRejectedAsync(int envelopeId, string? userReference = null);
|
||||
Task<IEnumerable<EnvelopeHistoryDto>> ReadRejectedAsync(int envelopeId, string? userReference = null);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="envelopeId"></param>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<ReceiverDto>> ReadRejectingReceivers(int envelopeId);
|
||||
Task<IEnumerable<ReceiverReadDto>> ReadRejectingReceivers(int envelopeId);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -1,10 +1,10 @@
|
||||
using DigitalData.Core.Abstraction.Application.DTO;
|
||||
using DigitalData.EmailProfilerDispatcher.Abstraction.Contracts;
|
||||
using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiver;
|
||||
using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiverReadOnly;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using EnvelopeGenerator.Application.DTOs.EnvelopeReceiver;
|
||||
using EnvelopeGenerator.Application.DTOs.EnvelopeReceiverReadOnly;
|
||||
using EnvelopeGenerator.Domain;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||
namespace EnvelopeGenerator.Application.Contracts.Services;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -19,7 +19,7 @@ public interface IEnvelopeMailService : IEmailOutService
|
||||
/// <param name="tempType"></param>
|
||||
/// <param name="optionalPlaceholders"></param>
|
||||
/// <returns></returns>
|
||||
Task<DataResult<int>> SendAsync(EnvelopeReceiverDto envelopeReceiverDto, EmailTemplateType tempType, Dictionary<string, object>? optionalPlaceholders = null);
|
||||
Task<DataResult<int>> SendAsync(EnvelopeReceiverDto envelopeReceiverDto, Constants.EmailTemplateType tempType, Dictionary<string, object>? optionalPlaceholders = null);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -35,4 +35,11 @@ public interface IEnvelopeMailService : IEmailOutService
|
||||
/// <param name="envelopeReceiverDto"></param>
|
||||
/// <returns></returns>
|
||||
Task<DataResult<int>> SendAccessCodeAsync(EnvelopeReceiverDto envelopeReceiverDto);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="envelopeReceiverDto"></param>
|
||||
/// <returns></returns>
|
||||
Task<DataResult<int>> SendTFAQrCodeAsync(EnvelopeReceiverDto envelopeReceiverDto);
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
using DigitalData.Core.Abstraction.Application;
|
||||
using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiverReadOnly;
|
||||
using EnvelopeGenerator.Application.DTOs.EnvelopeReceiverReadOnly;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||
namespace EnvelopeGenerator.Application.Contracts.Services;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -1,16 +1,13 @@
|
||||
using CommandDotNet;
|
||||
using DigitalData.Core.Abstraction.Application;
|
||||
using DigitalData.Core.Abstraction.Application.DTO;
|
||||
using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiver;
|
||||
using EnvelopeGenerator.Application.Common.Dto.Messaging;
|
||||
using EnvelopeGenerator.Application.DTOs.EnvelopeReceiver;
|
||||
using EnvelopeGenerator.Application.DTOs.Messaging;
|
||||
using EnvelopeGenerator.Application.Envelopes;
|
||||
using EnvelopeGenerator.Application.Envelopes.Queries;
|
||||
using EnvelopeGenerator.Application.Receivers.Queries;
|
||||
using EnvelopeGenerator.Domain;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using EnvelopeGenerator.Application.Receivers.Queries.Read;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||
namespace EnvelopeGenerator.Application.Contracts.Services;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -123,7 +120,7 @@ public interface IEnvelopeReceiverService : IBasicCRUDService<EnvelopeReceiverDt
|
||||
/// <param name="receiverQuery"></param>
|
||||
/// <param name="ignore_statuses"></param>
|
||||
/// <returns></returns>
|
||||
Task<DataResult<IEnumerable<EnvelopeReceiverDto>>> ReadByUsernameAsync(string username, EnvelopeStatus? min_status = null, EnvelopeStatus? max_status = null, ReadEnvelopeQuery? envelopeQuery = null, ReadReceiverQuery? receiverQuery = null, params EnvelopeStatus[] ignore_statuses);
|
||||
Task<DataResult<IEnumerable<EnvelopeReceiverDto>>> ReadByUsernameAsync(string username, int? min_status = null, int? max_status = null, EnvelopeQuery? envelopeQuery = null, ReadReceiverQuery? receiverQuery = null, params int[] ignore_statuses);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -1,10 +1,9 @@
|
||||
using DigitalData.Core.Abstraction.Application;
|
||||
using DigitalData.Core.Abstraction.Application.DTO;
|
||||
using EnvelopeGenerator.Application.Common.Dto;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using EnvelopeGenerator.Application.DTOs;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||
namespace EnvelopeGenerator.Application.Contracts.Services;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -41,5 +40,5 @@ public interface IEnvelopeService : IBasicCRUDService<EnvelopeDto, Envelope, int
|
||||
/// <param name="max_status"></param>
|
||||
/// <param name="ignore_statuses"></param>
|
||||
/// <returns></returns>
|
||||
Task<DataResult<IEnumerable<EnvelopeDto>>> ReadByUserAsync(int userId, EnvelopeStatus? min_status = null, EnvelopeStatus? max_status = null, params EnvelopeStatus[] ignore_statuses);
|
||||
Task<DataResult<IEnumerable<EnvelopeDto>>> ReadByUserAsync(int userId, int? min_status = null, int? max_status = null, params int[] ignore_statuses);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiver;
|
||||
using EnvelopeGenerator.Application.Common.Dto.Messaging;
|
||||
using EnvelopeGenerator.Application.DTOs.EnvelopeReceiver;
|
||||
using EnvelopeGenerator.Application.DTOs.Messaging;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||
namespace EnvelopeGenerator.Application.Contracts.Services;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -1,8 +1,8 @@
|
||||
using DigitalData.Core.Abstraction.Application;
|
||||
using EnvelopeGenerator.Application.Common.Dto;
|
||||
using EnvelopeGenerator.Application.DTOs;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||
namespace EnvelopeGenerator.Application.Contracts.Services;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -1,16 +1,15 @@
|
||||
using DigitalData.Core.Abstraction.Application;
|
||||
using DigitalData.Core.Abstraction.Application.DTO;
|
||||
using EnvelopeGenerator.Application.Common.Dto.Receiver;
|
||||
using EnvelopeGenerator.Application.Receivers.Commands;
|
||||
using EnvelopeGenerator.Application.DTOs.Receiver;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||
namespace EnvelopeGenerator.Application.Contracts.Services;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Obsolete("Use MediatR")]
|
||||
public interface IReceiverService : ICRUDService<CreateReceiverCommand, ReceiverDto, Receiver, int>
|
||||
public interface IReceiverService : ICRUDService<ReceiverCreateDto, ReceiverReadDto, Receiver, int>
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
@@ -18,7 +17,7 @@ public interface IReceiverService : ICRUDService<CreateReceiverCommand, Receiver
|
||||
/// <param name="emailAddress"></param>
|
||||
/// <param name="signature"></param>
|
||||
/// <returns></returns>
|
||||
Task<DataResult<ReceiverDto>> ReadByAsync(string? emailAddress = null, string? signature = null);
|
||||
Task<DataResult<ReceiverReadDto>> ReadByAsync(string? emailAddress = null, string? signature = null);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
@@ -1,6 +1,6 @@
|
||||
using EnvelopeGenerator.Application.Common.Dto.Messaging;
|
||||
using EnvelopeGenerator.Application.DTOs.Messaging;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||
namespace EnvelopeGenerator.Application.Contracts.Services;
|
||||
|
||||
//TODO: move to DigitalData.Core
|
||||
/// <summary>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user