aw-spec-cli 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/.agents/skills/aw-spec-addcomponent/SKILL.md +129 -0
- package/assets/.agents/skills/aw-spec-agent-context-update/SKILL.md +31 -0
- package/assets/.agents/skills/aw-spec-analyze/SKILL.md +296 -0
- package/assets/.agents/skills/aw-spec-checklist/SKILL.md +374 -0
- package/assets/.agents/skills/aw-spec-clarify/SKILL.md +320 -0
- package/assets/.agents/skills/aw-spec-constitution/SKILL.md +194 -0
- package/assets/.agents/skills/aw-spec-fast-implement/SKILL.md +268 -0
- package/assets/.agents/skills/aw-spec-foundation/SKILL.md +358 -0
- package/assets/.agents/skills/aw-spec-implement/SKILL.md +279 -0
- package/assets/.agents/skills/aw-spec-minimal-implementation/SKILL.md +209 -0
- package/assets/.agents/skills/aw-spec-plan/SKILL.md +221 -0
- package/assets/.agents/skills/aw-spec-select-domain/SKILL.md +73 -0
- package/assets/.agents/skills/aw-spec-specify/SKILL.md +382 -0
- package/assets/.agents/skills/aw-spec-tasks/SKILL.md +259 -0
- package/assets/.agents/skills/aw-spec-taskstoissues/SKILL.md +107 -0
- package/assets/.aw-spec/domain.json +3 -0
- package/assets/.aw-spec/extensions/.registry +19 -0
- package/assets/.aw-spec/extensions/agent-context/README.md +57 -0
- package/assets/.aw-spec/extensions/agent-context/agent-context-config.yml +4 -0
- package/assets/.aw-spec/extensions/agent-context/commands/aw-spec.agent-context.update.md +26 -0
- package/assets/.aw-spec/extensions/agent-context/extension.yml +34 -0
- package/assets/.aw-spec/extensions/agent-context/scripts/bash/update-agent-context.sh +201 -0
- package/assets/.aw-spec/extensions/agent-context/scripts/powershell/update-agent-context.ps1 +237 -0
- package/assets/.aw-spec/extensions.yml +23 -0
- package/assets/.aw-spec/feature.json +3 -0
- package/assets/.aw-spec/init-options.json +9 -0
- package/assets/.aw-spec/integration.json +15 -0
- package/assets/.aw-spec/integrations/aw-spec.manifest.json +17 -0
- package/assets/.aw-spec/integrations/claude.manifest.json +17 -0
- package/assets/.aw-spec/memory/constitution.md +50 -0
- package/assets/.aw-spec/scripts/bash/check-prerequisites.sh +189 -0
- package/assets/.aw-spec/scripts/bash/common.sh +582 -0
- package/assets/.aw-spec/scripts/bash/create-new-feature.sh +299 -0
- package/assets/.aw-spec/scripts/bash/setup-plan.sh +84 -0
- package/assets/.aw-spec/scripts/bash/setup-tasks.sh +91 -0
- package/assets/.aw-spec/templates/checklist-template.md +40 -0
- package/assets/.aw-spec/templates/constitution-template.md +57 -0
- package/assets/.aw-spec/templates/plan-template.md +113 -0
- package/assets/.aw-spec/templates/spec-template.md +131 -0
- package/assets/.aw-spec/templates/tasks-template.md +252 -0
- package/assets/.aw-spec/workflows/aw-spec/workflow.yml +77 -0
- package/assets/.aw-spec/workflows/workflow-registry.json +13 -0
- package/assets/.claude/skills/aw-spec-addcomponent/SKILL.md +129 -0
- package/assets/.claude/skills/aw-spec-agent-context-update/SKILL.md +31 -0
- package/assets/.claude/skills/aw-spec-analyze/SKILL.md +296 -0
- package/assets/.claude/skills/aw-spec-checklist/SKILL.md +374 -0
- package/assets/.claude/skills/aw-spec-clarify/SKILL.md +320 -0
- package/assets/.claude/skills/aw-spec-constitution/SKILL.md +194 -0
- package/assets/.claude/skills/aw-spec-fast-implement/SKILL.md +268 -0
- package/assets/.claude/skills/aw-spec-foundation/SKILL.md +358 -0
- package/assets/.claude/skills/aw-spec-implement/SKILL.md +279 -0
- package/assets/.claude/skills/aw-spec-minimal-implementation/SKILL.md +209 -0
- package/assets/.claude/skills/aw-spec-plan/SKILL.md +221 -0
- package/assets/.claude/skills/aw-spec-select-domain/SKILL.md +73 -0
- package/assets/.claude/skills/aw-spec-specify/SKILL.md +382 -0
- package/assets/.claude/skills/aw-spec-tasks/SKILL.md +259 -0
- package/assets/.claude/skills/aw-spec-taskstoissues/SKILL.md +107 -0
- package/assets/.kilocode/rules/specify-rules.md +4 -0
- package/assets/.kilocode/workflows/aw-spec-addcomponent.md +129 -0
- package/assets/.kilocode/workflows/aw-spec-agent-context.update.md +31 -0
- package/assets/.kilocode/workflows/aw-spec-analyze.md +296 -0
- package/assets/.kilocode/workflows/aw-spec-checklist.md +374 -0
- package/assets/.kilocode/workflows/aw-spec-clarify.md +320 -0
- package/assets/.kilocode/workflows/aw-spec-constitution.md +194 -0
- package/assets/.kilocode/workflows/aw-spec-fast-implement.md +263 -0
- package/assets/.kilocode/workflows/aw-spec-foundation.md +358 -0
- package/assets/.kilocode/workflows/aw-spec-implement.md +279 -0
- package/assets/.kilocode/workflows/aw-spec-minimal-implementation.md +204 -0
- package/assets/.kilocode/workflows/aw-spec-plan.md +221 -0
- package/assets/.kilocode/workflows/aw-spec-select-domain.md +73 -0
- package/assets/.kilocode/workflows/aw-spec-specify.md +382 -0
- package/assets/.kilocode/workflows/aw-spec-tasks.md +259 -0
- package/assets/.kilocode/workflows/aw-spec-taskstoissues.md +107 -0
- package/assets/docs/README.md +106 -0
- package/assets/docs/VerticalSlice/PACKAGING.md +185 -0
- package/assets/docs/VerticalSlice/VerticalSlice.pack.txt +2833 -0
- package/assets/docs/VerticalSlice/pack-foundation.ps1 +145 -0
- package/assets/docs/VerticalSlice/pack-foundation.sh +196 -0
- package/assets/docs/VerticalSlice/unpack-foundation.ps1 +126 -0
- package/assets/docs/VerticalSlice/unpack-foundation.sh +217 -0
- package/assets/docs/architecture/clean-architecture.md +71 -0
- package/assets/docs/architecture/cqrs.md +141 -0
- package/assets/docs/architecture/eventing.md +83 -0
- package/assets/docs/architecture/folder-structure.md +61 -0
- package/assets/docs/architecture/modular-monolith.md +47 -0
- package/assets/docs/architecture/persistence.md +269 -0
- package/assets/docs/architecture/testing.md +55 -0
- package/assets/docs/architecture/vertical-slices.md +201 -0
- package/assets/docs/command.txt +295 -0
- package/assets/docs/components/Email/README.md +112 -0
- package/assets/docs/components/Email/integration.md +260 -0
- package/assets/docs/components/Jobs/README.md +113 -0
- package/assets/docs/components/Jobs/integration.md +245 -0
- package/assets/docs/components/QrGenerator/README.md +78 -0
- package/assets/docs/components/QrGenerator/integration.md +185 -0
- package/assets/docs/components/README.md +57 -0
- package/assets/docs/domain/domain-driven-design.md +212 -0
- package/assets/docs/domainspecs/healthcare/ai-rules.domain.md +34 -0
- package/assets/docs/domainspecs/healthcare/api-versioning.domain.md +17 -0
- package/assets/docs/domainspecs/healthcare/auditing.domain.md +29 -0
- package/assets/docs/domainspecs/healthcare/authorization.domain.md +26 -0
- package/assets/docs/domainspecs/healthcare/clinical-terms.md +20 -0
- package/assets/docs/domainspecs/healthcare/coding-standards.domain.md +26 -0
- package/assets/docs/domainspecs/healthcare/comandrulemap.md +119 -0
- package/assets/docs/domainspecs/healthcare/consent.md +19 -0
- package/assets/docs/domainspecs/healthcare/cqrs.domain.md +12 -0
- package/assets/docs/domainspecs/healthcare/data-classification.domain.md +28 -0
- package/assets/docs/domainspecs/healthcare/dependency-policy.domain.md +11 -0
- package/assets/docs/domainspecs/healthcare/dicom.md +15 -0
- package/assets/docs/domainspecs/healthcare/domain-driven-design.domain.md +28 -0
- package/assets/docs/domainspecs/healthcare/domain-requirements.md +55 -0
- package/assets/docs/domainspecs/healthcare/eventing.domain.md +18 -0
- package/assets/docs/domainspecs/healthcare/fhir.md +36 -0
- package/assets/docs/domainspecs/healthcare/folder-structure.domain.md +23 -0
- package/assets/docs/domainspecs/healthcare/hipaa.md +34 -0
- package/assets/docs/domainspecs/healthcare/hl7.md +15 -0
- package/assets/docs/domainspecs/healthcare/integration-terms.md +21 -0
- package/assets/docs/domainspecs/healthcare/persistence.domain.md +20 -0
- package/assets/docs/domainspecs/healthcare/terminology.md +30 -0
- package/assets/docs/domainspecs/healthcare/testing.domain.md +15 -0
- package/assets/docs/domainspecs/healthcare/vertical-slices.domain.md +18 -0
- package/assets/docs/domainspecs/healthcare/zero-trust.domain.md +12 -0
- package/assets/docs/domainspecs/pharma/ai-rules.domain.md +32 -0
- package/assets/docs/domainspecs/pharma/api-versioning.domain.md +20 -0
- package/assets/docs/domainspecs/pharma/atc.md +22 -0
- package/assets/docs/domainspecs/pharma/auditing.domain.md +26 -0
- package/assets/docs/domainspecs/pharma/authorization.domain.md +25 -0
- package/assets/docs/domainspecs/pharma/coding-standards.domain.md +28 -0
- package/assets/docs/domainspecs/pharma/comandrulemap.md +136 -0
- package/assets/docs/domainspecs/pharma/cqrs.domain.md +16 -0
- package/assets/docs/domainspecs/pharma/data-classification.domain.md +24 -0
- package/assets/docs/domainspecs/pharma/data-provenance.md +21 -0
- package/assets/docs/domainspecs/pharma/dependency-policy.domain.md +11 -0
- package/assets/docs/domainspecs/pharma/domain-driven-design.domain.md +36 -0
- package/assets/docs/domainspecs/pharma/domain-requirements.md +99 -0
- package/assets/docs/domainspecs/pharma/eventing.domain.md +18 -0
- package/assets/docs/domainspecs/pharma/fhir.md +31 -0
- package/assets/docs/domainspecs/pharma/folder-structure.domain.md +24 -0
- package/assets/docs/domainspecs/pharma/hl7.md +17 -0
- package/assets/docs/domainspecs/pharma/idmp.md +26 -0
- package/assets/docs/domainspecs/pharma/integration-terms.md +21 -0
- package/assets/docs/domainspecs/pharma/ndc.md +28 -0
- package/assets/docs/domainspecs/pharma/persistence.domain.md +23 -0
- package/assets/docs/domainspecs/pharma/pharma-terms.md +27 -0
- package/assets/docs/domainspecs/pharma/product-lifecycle.domain.md +30 -0
- package/assets/docs/domainspecs/pharma/regulatory-compliance.md +19 -0
- package/assets/docs/domainspecs/pharma/terminology.md +32 -0
- package/assets/docs/domainspecs/pharma/testing.domain.md +19 -0
- package/assets/docs/domainspecs/pharma/vertical-slices.domain.md +17 -0
- package/assets/docs/domainspecs/pharma/zero-trust.domain.md +12 -0
- package/assets/docs/glossary/ai-rules.md +59 -0
- package/assets/docs/governance/api-versioning.md +99 -0
- package/assets/docs/governance/coding-standards.md +87 -0
- package/assets/docs/governance/dependency-policy.md +40 -0
- package/assets/docs/interop/ihe-conformance.md +31 -0
- package/assets/docs/pharma-constitution.my-ref.md +86 -0
- package/assets/docs/requirement-spec.txt +15 -0
- package/assets/docs/security/auditing.md +117 -0
- package/assets/docs/security/authorization-matrix.md +14 -0
- package/assets/docs/security/authorization.md +87 -0
- package/assets/docs/security/data-classification.md +38 -0
- package/assets/docs/security/zero-trust.md +76 -0
- package/assets/docs/technology-constitution.md +172 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +3 -2
- package/dist/commands/init.js.map +1 -1
- package/dist/utils/copy-assets.d.ts +8 -1
- package/dist/utils/copy-assets.d.ts.map +1 -1
- package/dist/utils/copy-assets.js +1 -0
- package/dist/utils/copy-assets.js.map +1 -1
- package/dist/utils/paths.js +1 -1
- package/dist/utils/paths.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Email Component (`AppWeaver.Messaging.Email`)
|
|
2
|
+
|
|
3
|
+
> Pure technology (domain-agnostic). Reference sheet for the Email messaging component.
|
|
4
|
+
> To wire it into a project, follow the agent runbook in [integration.md](./integration.md).
|
|
5
|
+
> Commands: `/aw-spec-plan`, `/aw-spec-tasks`, `/aw-spec-implement`.
|
|
6
|
+
|
|
7
|
+
## What it is
|
|
8
|
+
|
|
9
|
+
A provider-agnostic email-sending component. Application code depends on a single abstraction,
|
|
10
|
+
`IEmailService`; the transport (Microsoft Graph, SMTP, SendGrid, OAuth2-SMTP, or a local File
|
|
11
|
+
sink) is chosen once during DI registration via `AddEmailMessaging(...)`.
|
|
12
|
+
|
|
13
|
+
**Use when:** a feature must send transactional or notification email (user-created alerts,
|
|
14
|
+
confirmations, admin notices, password flows, attachments).
|
|
15
|
+
|
|
16
|
+
## Public contract (`AppWeaver.Messaging.Email`)
|
|
17
|
+
|
|
18
|
+
```csharp
|
|
19
|
+
namespace AppWeaver.Messaging.Email;
|
|
20
|
+
|
|
21
|
+
public interface IEmailService
|
|
22
|
+
{
|
|
23
|
+
// Convenience overload — single recipient.
|
|
24
|
+
Task SendAsync(string from, string to, string subject, string body,
|
|
25
|
+
bool isHtml = true, CancellationToken cancellationToken = default);
|
|
26
|
+
|
|
27
|
+
// Full message — multi-recipient, Cc/Bcc, attachments.
|
|
28
|
+
Task SendAsync(EmailMessage message, CancellationToken cancellationToken = default);
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```csharp
|
|
33
|
+
namespace AppWeaver.Messaging.Email.Entities;
|
|
34
|
+
|
|
35
|
+
public class EmailMessage
|
|
36
|
+
{
|
|
37
|
+
public string From { get; set; } // falls back to provider DefaultFrom if empty
|
|
38
|
+
public List<string> To { get; set; } = new();
|
|
39
|
+
public List<string> Cc { get; set; } = new();
|
|
40
|
+
public List<string> Bcc { get; set; } = new();
|
|
41
|
+
public string Subject { get; set; }
|
|
42
|
+
public string Body { get; set; }
|
|
43
|
+
public bool IsHtml { get; set; } = true;
|
|
44
|
+
public List<EmailAttachment> Attachments { get; set; } = new();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public class EmailAttachment
|
|
48
|
+
{
|
|
49
|
+
public string FileName { get; set; }
|
|
50
|
+
public byte[] Content { get; set; }
|
|
51
|
+
public string ContentType { get; set; } = "application/octet-stream";
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Packages
|
|
56
|
+
|
|
57
|
+
| Package | Version | Role | Builder method | Provider key |
|
|
58
|
+
|---|---|---|---|---|
|
|
59
|
+
| `AppWeaver.Messaging.Email` | `0.1.3` | **Required.** Abstraction + `AddEmailMessaging` + facade. | — | — |
|
|
60
|
+
| `AppWeaver.Messaging.Email.MsGraph` | `0.1.2` | Microsoft 365 / Graph API (recommended for prod). | `UseMsGraph` | `MsGraph` |
|
|
61
|
+
| `AppWeaver.Messaging.Email.Smtp` | `0.1.3` | Classic SMTP (host/port/user/password). | `UseSmtp` | `Smtp` |
|
|
62
|
+
| `AppWeaver.Messaging.Email.SendGrid` | `0.1.2` | SendGrid API key. | `UseSendGrid` | `SendGrid` |
|
|
63
|
+
| `AppWeaver.Messaging.Email.OAuth2` | `0.1.3` | OAuth2-authenticated SMTP (O365). | `UseOAuth2` | `OAuth2` |
|
|
64
|
+
| `AppWeaver.Messaging.Email.File` | `0.1.2` | Writes email to a local `.txt` file — local dev / tests. | `UseFile` | `File` |
|
|
65
|
+
|
|
66
|
+
Install the abstraction **plus exactly one provider** for the environment you are wiring.
|
|
67
|
+
|
|
68
|
+
## Provider option shapes
|
|
69
|
+
|
|
70
|
+
| Provider | Options type | Key fields | Bound config section |
|
|
71
|
+
|---|---|---|---|
|
|
72
|
+
| MsGraph | `MsGraphEmailOptions` | `TenantId`, `ClientId`, `ClientSecret`, `DefaultFrom`, `Scopes` | `Messaging.Email.MsGraph` |
|
|
73
|
+
| Smtp | `SmtpEmailOptions` | `Host`, `Port`, `Username`, `Password`, `EnableSsl`, `DefaultFrom` | `Messaging.Email.Smtp` |
|
|
74
|
+
| SendGrid | `SendGridEmailOptions` | `ApiKey` *(required)*, `DefaultFrom` | `Messaging.Email.SendGrid` |
|
|
75
|
+
| OAuth2 | `OAuth2EmailOptions` | `TenantId`, `ClientId`, `ClientSecret`, `Host`, `Port`, `Scopes`, `DefaultFrom` | `Messaging.Email.OAuth2` |
|
|
76
|
+
| File | `FileEmailOptions` | `DefaultFrom` | `Messaging.Email.Smtp` *(quirk — pass options inline)* |
|
|
77
|
+
|
|
78
|
+
## Registration entry point
|
|
79
|
+
|
|
80
|
+
```csharp
|
|
81
|
+
// In AppWeaver.Messaging.Email.Extensions
|
|
82
|
+
IComponentBuilder<IEmailService> AddEmailMessaging(
|
|
83
|
+
this IServiceCollection services,
|
|
84
|
+
Action<IComponentBuilder<IEmailService>> configureBuilder, // required
|
|
85
|
+
Action<MessagingEmailConfiguration>? configureOptions = null,// set Provider here
|
|
86
|
+
IConfiguration? configuration = null); // OR bind from config
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`MessagingEmailConfiguration` exposes a single `[Required] string Provider`. The facade
|
|
90
|
+
(`DefaultImplementation`) resolves the keyed provider whose key equals `Provider`.
|
|
91
|
+
|
|
92
|
+
## Source map (framework)
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
framework/Messaging.Email/
|
|
96
|
+
Integration.md ← original MsGraph tutorial (reference)
|
|
97
|
+
src/
|
|
98
|
+
AppWeaver.Messaging.Email/ ← IEmailService, EmailMessage, AddEmailMessaging, facade
|
|
99
|
+
AppWeaver.Messaging.Email.MsGraph/ ← UseMsGraph + MsGraphEmailService
|
|
100
|
+
AppWeaver.Messaging.Email.Smtp/ ← UseSmtp + SmtpEmailService
|
|
101
|
+
AppWeaver.Messaging.Email.SendGrid/ ← UseSendGrid + SendGridEmailService
|
|
102
|
+
AppWeaver.Messaging.Email.OAuth2/ ← UseOAuth2 + OAuth2EmailService
|
|
103
|
+
AppWeaver.Messaging.Email.File/ ← UseFile + FileEmailService
|
|
104
|
+
test/
|
|
105
|
+
MsGraphTest/Program.cs ← MsGraph end-to-end sample
|
|
106
|
+
TestApp/Program.cs ← OAuth2 + File multi-provider sample
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Related
|
|
110
|
+
|
|
111
|
+
- [integration.md](./integration.md) · [[components-readme]] · [[cqrs]] ·
|
|
112
|
+
[[clean-architecture]] · [[dependency-policy]]
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# Email Component — Integration Workflow (agent playbook)
|
|
2
|
+
|
|
3
|
+
> Pure technology (domain-agnostic). This file is an **agent runbook**: read it top-to-bottom and
|
|
4
|
+
> execute the workflow in order. It drives an interactive integration of `IEmailService` into a
|
|
5
|
+
> project, asking the user which transport implementation(s) to wire. Read [README.md](./README.md)
|
|
6
|
+
> first for the contract. Replace `<SampleProjectName>` with the project name.
|
|
7
|
+
> Commands: `/aw-spec-implement`.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Agent workflow — execute in order
|
|
12
|
+
|
|
13
|
+
1. **Load context.** Read [README.md](./README.md) (contract + provider matrix) and this file.
|
|
14
|
+
2. **Locate target projects** in the solution (see [[folder-structure]]):
|
|
15
|
+
`…Application` (injects `IEmailService`), `…Infrastructure` (does the registration),
|
|
16
|
+
`…Service` (holds `appsettings.json`). If the layout differs, map these roles before continuing.
|
|
17
|
+
3. **Present the implementation catalog (below) as a selection list and ASK the user which
|
|
18
|
+
implementation(s) to integrate.** Use an interactive multi-select prompt (`AskUserQuestion`,
|
|
19
|
+
`multiSelect: true`). The list options are the rows of the *Implementation catalog*.
|
|
20
|
+
- The user MAY select **more than one** (e.g., `MsGraph` for production + `File` for local dev).
|
|
21
|
+
- If more than one is selected, **ask a follow-up single-select**: *which one is the default
|
|
22
|
+
(active) provider?* — this becomes `options.Provider`.
|
|
23
|
+
- If the user selects exactly one, that one is the default.
|
|
24
|
+
4. **Apply “Common steps” once** (§Common steps) — abstraction package + registration scaffold.
|
|
25
|
+
5. **For each selected implementation, apply its block** under §Implementation blocks — add its
|
|
26
|
+
package, its `Use…` call inside the `AddEmailMessaging` builder, and its config section.
|
|
27
|
+
6. **Set the default provider:** `configureOptions: options => options.Provider = "<default key>"`.
|
|
28
|
+
All selected implementations stay registered; the active one is switchable later via config.
|
|
29
|
+
7. **(Optional) Wire a usage example** in the relevant feature handler (§Usage).
|
|
30
|
+
8. **Verify** (§Verification) and report what was added.
|
|
31
|
+
|
|
32
|
+
> **Guardrail — always set `options.Provider`.** When `Provider` is null the facade falls back to a
|
|
33
|
+
> **hardcoded `"SendGrid"`** key; if SendGrid is not registered, resolution throws at runtime. Never
|
|
34
|
+
> rely on the implicit fallback.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Implementation catalog (selection list)
|
|
39
|
+
|
|
40
|
+
Present these as the multi-select options. `Key` is the provider key and the `Use…` method suffix.
|
|
41
|
+
|
|
42
|
+
| Key | Implementation | Best for | Package (v) | Required settings |
|
|
43
|
+
|---|---|---|---|---|
|
|
44
|
+
| `MsGraph` | Microsoft 365 / Graph API | Production on an O365 tenant | `AppWeaver.Messaging.Email.MsGraph` (0.1.2) | `TenantId`, `ClientId`, `ClientSecret`, `DefaultFrom` |
|
|
45
|
+
| `Smtp` | Classic SMTP | Any SMTP server | `AppWeaver.Messaging.Email.Smtp` (0.1.3) | `Host`, `Port`, `Username`, `Password`, `EnableSsl` |
|
|
46
|
+
| `SendGrid` | SendGrid API | SendGrid accounts | `AppWeaver.Messaging.Email.SendGrid` (0.1.2) | `ApiKey` *(required)*, `DefaultFrom` |
|
|
47
|
+
| `OAuth2` | OAuth2-authenticated SMTP | O365 SMTP with OAuth2 | `AppWeaver.Messaging.Email.OAuth2` (0.1.3) | `TenantId`, `ClientId`, `ClientSecret`, `Host`, `Port` |
|
|
48
|
+
| `File` | Writes email to a local `.txt` | Local dev / tests (no real send) | `AppWeaver.Messaging.Email.File` (0.1.2) | `DefaultFrom` |
|
|
49
|
+
|
|
50
|
+
> Recommended pairing: **`MsGraph` (default) + `File`** so developers can run locally without
|
|
51
|
+
> sending real mail, while production uses Graph.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Common steps (apply once, regardless of selection)
|
|
56
|
+
|
|
57
|
+
### C1 — Abstraction package version
|
|
58
|
+
|
|
59
|
+
**File:** `Directory.Packages.props` (versions live here only — see [[dependency-policy]])
|
|
60
|
+
|
|
61
|
+
```xml
|
|
62
|
+
<ItemGroup>
|
|
63
|
+
<!-- AppWeaver — Email (abstraction) -->
|
|
64
|
+
<PackageVersion Include="AppWeaver.Messaging.Email" Version="0.1.3" />
|
|
65
|
+
<!-- + one <PackageVersion> per selected implementation — see each block below -->
|
|
66
|
+
</ItemGroup>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### C2 — Project references
|
|
70
|
+
|
|
71
|
+
**File:** `src/<SampleProjectName>.Application/<SampleProjectName>.Application.csproj`
|
|
72
|
+
|
|
73
|
+
```xml
|
|
74
|
+
<ItemGroup>
|
|
75
|
+
<PackageReference Include="AppWeaver.Messaging.Email" /> <!-- no Version= -->
|
|
76
|
+
</ItemGroup>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**File:** `src/<SampleProjectName>.Infrastructure/<SampleProjectName>.Infrastructure.csproj`
|
|
80
|
+
(add the abstraction here too, plus each selected implementation’s package — see its block).
|
|
81
|
+
|
|
82
|
+
```xml
|
|
83
|
+
<ItemGroup>
|
|
84
|
+
<PackageReference Include="AppWeaver.Messaging.Email" />
|
|
85
|
+
</ItemGroup>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### C3 — Registration scaffold
|
|
89
|
+
|
|
90
|
+
**File:** `src/<SampleProjectName>.Infrastructure/Extensions/InfrastructureServiceRegistration.cs`
|
|
91
|
+
|
|
92
|
+
Add the base call; each selected implementation contributes one `builder.Use…(…)` line (C4).
|
|
93
|
+
|
|
94
|
+
```csharp
|
|
95
|
+
using AppWeaver.Messaging.Email.Extensions;
|
|
96
|
+
// + one using per selected implementation (see its block)
|
|
97
|
+
|
|
98
|
+
services.AddEmailMessaging(
|
|
99
|
+
configureBuilder: builder =>
|
|
100
|
+
{
|
|
101
|
+
// ⬇ one Use…() call per selected implementation is inserted here
|
|
102
|
+
},
|
|
103
|
+
configureOptions: options => options.Provider = "<default key>"); // step 6
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### C4 — Base appsettings block
|
|
107
|
+
|
|
108
|
+
**File:** `src/<SampleProjectName>.Service/appsettings.json`
|
|
109
|
+
|
|
110
|
+
```jsonc
|
|
111
|
+
{
|
|
112
|
+
"Messaging": {
|
|
113
|
+
"Email": {
|
|
114
|
+
"DefaultFrom": "no-reply@your-domain.com",
|
|
115
|
+
"AdminEmail": "admin@your-domain.com"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Implementation blocks
|
|
124
|
+
|
|
125
|
+
For each implementation the user selected, do all three sub-steps: **package**, **registration
|
|
126
|
+
line**, **config**. Secrets MUST be supplied via user-secrets / env / key vault — never committed.
|
|
127
|
+
|
|
128
|
+
### MsGraph
|
|
129
|
+
```xml
|
|
130
|
+
<!-- Directory.Packages.props --> <PackageVersion Include="AppWeaver.Messaging.Email.MsGraph" Version="0.1.2" />
|
|
131
|
+
<!-- Infrastructure.csproj --> <PackageReference Include="AppWeaver.Messaging.Email.MsGraph" />
|
|
132
|
+
```
|
|
133
|
+
```csharp
|
|
134
|
+
using AppWeaver.Messaging.Email.MsGraph.Extensions;
|
|
135
|
+
// inside AddEmailMessaging builder:
|
|
136
|
+
builder.UseMsGraph(o =>
|
|
137
|
+
{
|
|
138
|
+
o.TenantId = configuration["Messaging.Email.MsGraph:TenantId"];
|
|
139
|
+
o.ClientId = configuration["Messaging.Email.MsGraph:ClientId"];
|
|
140
|
+
o.ClientSecret = configuration["Messaging.Email.MsGraph:ClientSecret"]; // user-secrets
|
|
141
|
+
o.DefaultFrom = configuration["Messaging:Email:DefaultFrom"];
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
```jsonc
|
|
145
|
+
// appsettings.json — secret set out-of-band:
|
|
146
|
+
// dotnet user-secrets set "Messaging.Email.MsGraph:ClientSecret" "<secret>"
|
|
147
|
+
"Messaging.Email.MsGraph": { "TenantId": "<guid>", "ClientId": "<guid>", "DefaultFrom": "no-reply@your-domain.com" }
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Smtp
|
|
151
|
+
```xml
|
|
152
|
+
<PackageVersion Include="AppWeaver.Messaging.Email.Smtp" Version="0.1.3" />
|
|
153
|
+
<PackageReference Include="AppWeaver.Messaging.Email.Smtp" />
|
|
154
|
+
```
|
|
155
|
+
```csharp
|
|
156
|
+
using AppWeaver.Messaging.Email.Smtp.Extensions;
|
|
157
|
+
builder.UseSmtp(o =>
|
|
158
|
+
{
|
|
159
|
+
o.Host = configuration["Messaging.Email.Smtp:Host"]!;
|
|
160
|
+
o.Port = int.Parse(configuration["Messaging.Email.Smtp:Port"] ?? "587");
|
|
161
|
+
o.Username = configuration["Messaging.Email.Smtp:Username"]!;
|
|
162
|
+
o.Password = configuration["Messaging.Email.Smtp:Password"]!; // user-secrets
|
|
163
|
+
o.EnableSsl = true;
|
|
164
|
+
o.DefaultFrom = configuration["Messaging:Email:DefaultFrom"];
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
```jsonc
|
|
168
|
+
"Messaging.Email.Smtp": { "Host": "smtp.example.com", "Port": 587, "Username": "<user>", "EnableSsl": true }
|
|
169
|
+
```
|
|
170
|
+
> Note: the `SendAsync(from,to,…)` convenience overload of the SMTP service writes to a file; the
|
|
171
|
+
> `EmailMessage` overload performs the real SMTP send. Use `SendAsync(EmailMessage, ct)` for delivery.
|
|
172
|
+
|
|
173
|
+
### SendGrid
|
|
174
|
+
```xml
|
|
175
|
+
<PackageVersion Include="AppWeaver.Messaging.Email.SendGrid" Version="0.1.2" />
|
|
176
|
+
<PackageReference Include="AppWeaver.Messaging.Email.SendGrid" />
|
|
177
|
+
```
|
|
178
|
+
```csharp
|
|
179
|
+
using AppWeaver.Messaging.Email.SendGrid.Extensions;
|
|
180
|
+
builder.UseSendGrid(o =>
|
|
181
|
+
{
|
|
182
|
+
o.ApiKey = configuration["Messaging.Email.SendGrid:ApiKey"]!; // [Required] — user-secrets
|
|
183
|
+
o.DefaultFrom = configuration["Messaging:Email:DefaultFrom"];
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
> `ApiKey` is `[Required]` and validates on startup — a missing key fails app boot, not the first send.
|
|
187
|
+
|
|
188
|
+
### OAuth2
|
|
189
|
+
```xml
|
|
190
|
+
<PackageVersion Include="AppWeaver.Messaging.Email.OAuth2" Version="0.1.3" />
|
|
191
|
+
<PackageReference Include="AppWeaver.Messaging.Email.OAuth2" />
|
|
192
|
+
```
|
|
193
|
+
```csharp
|
|
194
|
+
using AppWeaver.Messaging.Email.OAuth2.Extensions;
|
|
195
|
+
builder.UseOAuth2(o =>
|
|
196
|
+
{
|
|
197
|
+
o.TenantId = configuration["Messaging.Email.OAuth2:TenantId"];
|
|
198
|
+
o.ClientId = configuration["Messaging.Email.OAuth2:ClientId"];
|
|
199
|
+
o.ClientSecret = configuration["Messaging.Email.OAuth2:ClientSecret"]; // user-secrets
|
|
200
|
+
o.Host = "smtp.office365.com"; o.Port = 587;
|
|
201
|
+
o.DefaultFrom = configuration["Messaging:Email:DefaultFrom"];
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### File
|
|
206
|
+
```xml
|
|
207
|
+
<PackageVersion Include="AppWeaver.Messaging.Email.File" Version="0.1.2" />
|
|
208
|
+
<PackageReference Include="AppWeaver.Messaging.Email.File" />
|
|
209
|
+
```
|
|
210
|
+
```csharp
|
|
211
|
+
using AppWeaver.Messaging.Email.File.Extensions;
|
|
212
|
+
builder.UseFile(o => o.DefaultFrom = configuration["Messaging:Email:DefaultFrom"]);
|
|
213
|
+
```
|
|
214
|
+
> Pass `DefaultFrom` **inline** as shown. The File provider’s config binder reads the
|
|
215
|
+
> `Messaging.Email.Smtp` section (a known quirk), so do not rely on a `Messaging.Email.File` config
|
|
216
|
+
> section. Output is written to `Emails_<yyyy-MM-dd>.txt` in `AppContext.BaseDirectory`.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Usage (inject the abstraction in a feature handler)
|
|
221
|
+
|
|
222
|
+
Email is a side effect of a business operation — call it from the Application command handler that
|
|
223
|
+
owns the operation (see [[cqrs]], [[vertical-slices]]). Inject **`IEmailService`**, never a provider.
|
|
224
|
+
|
|
225
|
+
```csharp
|
|
226
|
+
using AppWeaver.Messaging.Email;
|
|
227
|
+
|
|
228
|
+
public class CreateUserCommandHandler(IEmailService _email, IConfiguration _config /* … */)
|
|
229
|
+
: ICommandHandler<CreateUserCommand, Result<UserDto>>
|
|
230
|
+
{
|
|
231
|
+
public async ValueTask<Result<UserDto>> Handle(CreateUserCommand req, CancellationToken ct)
|
|
232
|
+
{
|
|
233
|
+
// … persist aggregate …
|
|
234
|
+
var from = _config["Messaging:Email:DefaultFrom"]!;
|
|
235
|
+
var to = _config["Messaging:Email:AdminEmail"]!;
|
|
236
|
+
await _email.SendAsync(from, to, $"New user: {req.Name}", "<p>Created.</p>", ct: ct);
|
|
237
|
+
return Result<UserDto>.Ok(/* dto */);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Multi-recipient / attachments — use the `EmailMessage` overload (To/Cc/Bcc + `EmailAttachment`,
|
|
243
|
+
see [README.md](./README.md)).
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Verification
|
|
248
|
+
|
|
249
|
+
1. **Builds** — `dotnet build` succeeds; no `Version=` on package references.
|
|
250
|
+
2. **Startup validates** — app boots; options `ValidateOnStart()` passes; `options.Provider` equals
|
|
251
|
+
one of the selected keys.
|
|
252
|
+
3. **Resolves** — `IEmailService` resolves from DI.
|
|
253
|
+
4. **Sends** — a send succeeds; for `File`, confirm `Emails_<yyyy-MM-dd>.txt` appears in the app
|
|
254
|
+
base directory.
|
|
255
|
+
5. **No secrets in diff** — `git diff` shows no secret / API key / password literals.
|
|
256
|
+
|
|
257
|
+
## Related
|
|
258
|
+
|
|
259
|
+
- [README.md](./README.md) · [[components-readme]] · [[cqrs]] · [[clean-architecture]] ·
|
|
260
|
+
[[vertical-slices]] · [[dependency-policy]] · [[ai-rules]]
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Jobs Component (`AppWeaver.Jobs`)
|
|
2
|
+
|
|
3
|
+
> Pure technology (domain-agnostic). Reference sheet for the background-job component.
|
|
4
|
+
> To wire it into a project, follow the agent runbook in [integration.md](./integration.md).
|
|
5
|
+
> Commands: `/aw-spec-plan`, `/aw-spec-tasks`, `/aw-spec-implement`.
|
|
6
|
+
|
|
7
|
+
## What it is
|
|
8
|
+
|
|
9
|
+
A plug-and-play background-job framework. Application code depends only on the `IJobScheduler`
|
|
10
|
+
and `IJobProgress` abstractions; the engine (Quartz.NET, optionally with EF Core status
|
|
11
|
+
persistence) is a single registration decision. Supports **fire-and-forget**, **delayed**, and
|
|
12
|
+
**recurring (CRON)** jobs with progress tracking, status polling, and cooperative cancellation.
|
|
13
|
+
|
|
14
|
+
**Use when:** work must run outside the request thread — exports, batch processing, archival,
|
|
15
|
+
report generation, scheduled maintenance, sending bulk notifications.
|
|
16
|
+
|
|
17
|
+
## Public contract (`AppWeaver.Jobs.Abstractions`)
|
|
18
|
+
|
|
19
|
+
```csharp
|
|
20
|
+
public interface IJob { } // marker — implement on every job class
|
|
21
|
+
|
|
22
|
+
public interface IJobScheduler
|
|
23
|
+
{
|
|
24
|
+
Task<string> EnqueueAsync<TJob>(Expression<Func<TJob, Task>> jobMethod,
|
|
25
|
+
string? description = null, string? userId = null) where TJob : class, IJob;
|
|
26
|
+
|
|
27
|
+
Task<string> ScheduleAsync<TJob>(Expression<Func<TJob, Task>> jobMethod, TimeSpan delay,
|
|
28
|
+
string? description = null, string? userId = null) where TJob : class, IJob;
|
|
29
|
+
|
|
30
|
+
Task AddOrUpdateRecurringAsync<TJob>(string jobId,
|
|
31
|
+
Expression<Func<TJob, Task>> jobMethod, string cronExpression) where TJob : class, IJob;
|
|
32
|
+
|
|
33
|
+
Task RemoveRecurringAsync(string jobId);
|
|
34
|
+
Task<JobStatus?> GetStatusAsync(string jobId);
|
|
35
|
+
Task<IEnumerable<JobStatus>> GetStatusesAsync(string userId, DateTime? from = null, DateTime? to = null);
|
|
36
|
+
Task CancelAsync(string jobId);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public interface IJobProgress
|
|
40
|
+
{
|
|
41
|
+
Task ReportProgressAsync(int percent, string? message = null, object? progressDetails = null);
|
|
42
|
+
Task ReportResultUrlAsync(string url);
|
|
43
|
+
CancellationToken CancellationToken { get; } // check inside loops for cooperative cancel
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`JobStatus` carries: `JobId`, `State`, `CreatedAt`, `StartedAt`, `CompletedAt`, `ProgressPercent`,
|
|
48
|
+
`ProgressMessage`, `ResultUrl`, `ErrorMessage`, `Description`, `UserId`, `ProgressDetails`.
|
|
49
|
+
`JobState` = `Pending → Running → Completed | Failed | Cancelled`.
|
|
50
|
+
|
|
51
|
+
## Packages
|
|
52
|
+
|
|
53
|
+
| Package | Version | Role | Builder method |
|
|
54
|
+
|---|---|---|---|
|
|
55
|
+
| `AppWeaver.Jobs` | `0.1.1` | **Required.** Abstractions + `AddAppWeaverJobs` + facade. | — |
|
|
56
|
+
| `AppWeaver.Jobs.Quartz` | `0.1.0` | **Recommended engine.** Quartz.NET scheduler; in-memory or persistent store. | `UseQuartzJobScheduler` |
|
|
57
|
+
| `AppWeaver.Jobs.Persistence.EfCore` | `0.1.0` | EF Core persistence of `JobStatus`. | `UseEfCoreJobs` + `UseJobDatabase` / `UseAppWeaverDataStore` |
|
|
58
|
+
| `AppWeaver.Jobs.InMemory` | `0.1.0` | ⚠️ **Work in progress — not functional. Do not use.** | — |
|
|
59
|
+
|
|
60
|
+
**Recommended stack:** `AppWeaver.Jobs` + `AppWeaver.Jobs.Quartz` + `AppWeaver.Jobs.Persistence.EfCore`.
|
|
61
|
+
|
|
62
|
+
## Registration shapes
|
|
63
|
+
|
|
64
|
+
```csharp
|
|
65
|
+
// In AppWeaver.Jobs.Extensions
|
|
66
|
+
IComponentBuilder<IJobScheduler> AddAppWeaverJobs(
|
|
67
|
+
this IServiceCollection services,
|
|
68
|
+
Action<IComponentBuilder<IJobScheduler>> configureBuilder, // required
|
|
69
|
+
Action<JobsConfiguration>? configureOptions = null, // set Provider = "Quartz"
|
|
70
|
+
IConfiguration? configuration = null); // OR bind "AppWeaver:Jobs"
|
|
71
|
+
|
|
72
|
+
// In AppWeaver.Jobs.Quartz.Extensions
|
|
73
|
+
builder.UseQuartzJobScheduler(opts => { opts.MaxConcurrency = 10; opts.ConfigureQuartz = q => { … }; });
|
|
74
|
+
|
|
75
|
+
// In AppWeaver.Jobs.Persistence.EfCore.Extensions (choose ONE store wiring)
|
|
76
|
+
builder.UseEfCoreJobs().UseJobDatabase(opts => opts.UseSqlite("Data Source=jobs.db")); // standalone
|
|
77
|
+
builder.UseEfCoreJobs().UseAppWeaverDataStore(); // shared DataStore
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`JobsConfiguration.Provider` (config section `AppWeaver:Jobs`) selects the engine key; for Quartz
|
|
81
|
+
it is `"Quartz"`. `UseQuartzJobScheduler` registers `IJobProgress` (`QuartzJobProgress`) and a
|
|
82
|
+
Quartz hosted service automatically.
|
|
83
|
+
|
|
84
|
+
## Persistence modes (EF Core)
|
|
85
|
+
|
|
86
|
+
| Mode | Wiring | DbContext | Migration |
|
|
87
|
+
|---|---|---|---|
|
|
88
|
+
| **Standalone** | `.UseJobDatabase(opts => opts.UseXxx(conn))` | dedicated `JobStatusDbContext` | own migration / `EnsureCreated` |
|
|
89
|
+
| **Shared DataStore** | `.UseAppWeaverDataStore()` (after `AddDataStore().UseEfCoreDataStore()`) | shares DataStore connection | Shared: in `SharedComponentDbContext` migration; Isolated: own migration |
|
|
90
|
+
|
|
91
|
+
The Quartz **persistent store** (for distributed/durable scheduling) is configured separately via
|
|
92
|
+
`opts.ConfigureQuartz = q => q.UsePersistentStore(...)` and needs the Quartz DDL applied to the DB.
|
|
93
|
+
With no `ConfigureQuartz`, Quartz uses an in-memory store (single instance, lost on restart).
|
|
94
|
+
|
|
95
|
+
## Source map (framework)
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
framework/Jobs/
|
|
99
|
+
README.md ← full component manual (authoritative deep-dive)
|
|
100
|
+
src/
|
|
101
|
+
AppWeaver.Jobs/ ← IJob, IJobScheduler, IJobProgress, JobStatus, AddAppWeaverJobs
|
|
102
|
+
AppWeaver.Jobs.Quartz/ ← UseQuartzJobScheduler + QuartzJobScheduler/Wrapper/Progress
|
|
103
|
+
AppWeaver.Jobs.Persistence.EfCore/ ← UseEfCoreJobs/UseJobDatabase/UseAppWeaverDataStore
|
|
104
|
+
AppWeaver.Jobs.InMemory/ ← ⚠️ WIP, not functional
|
|
105
|
+
tests/
|
|
106
|
+
AppWeaver.Jobs.Tests/ ← DefaultJobSchedulerTests
|
|
107
|
+
TestApp/ ← Quartz + EF Core end-to-end scenarios (A–O)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Related
|
|
111
|
+
|
|
112
|
+
- [integration.md](./integration.md) · [[components-readme]] · [[cqrs]] ·
|
|
113
|
+
[[persistence]] · [[clean-architecture]] · [[dependency-policy]]
|