refactor: replace internal UnitOfWork with NotificationUnitOfWork and NotificationBaseRepository
- Consolidate unit of work implementation with NotificationUnitOfWork. - Refactor repositories to use NotificationBaseRepository for consistency. - Simplify request handlers by removing IUnitOfWork dependency. - Update related tests and service registration.
This commit is contained in:
@@ -1,6 +1,47 @@
|
||||
@HrynCo.NotificationService.Api_HostAddress = http://localhost:5188
|
||||
@host = http://localhost:5188
|
||||
|
||||
GET {{HrynCo.NotificationService.Api_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
### Create a new email template
|
||||
POST {{host}}/api/v1/email-templates
|
||||
Content-Type: application/json
|
||||
|
||||
###
|
||||
{
|
||||
"ServiceName": "StoreMate-Prod",
|
||||
"Key": "ShareInvite",
|
||||
"LanguageCode": "uk",
|
||||
"Subject": "Вас запрошено",
|
||||
"HtmlBody": "<html><body><div style=\"font-family: Arial, sans-serif; color: #1f2937;\"><p>Вітаємо, \u007b\u007bRecipientName\u007d\u007d.</p><h1>Вас запрошено</h1><p>\u007b\u007bInviterName\u007d\u007d запросив вас приєднатися до \u007b\u007bAppName\u007d\u007d, щоб ви могли безпечно співпрацювати.</p><p><a href=\"\u007b\u007bInviteLink\u007d\u007d\">Відкрити запрошення</a></p><p>Запрошення дійсне до <strong>\u007b\u007bValidUntil\u007d\u007d</strong>.</p></div></body></html>",
|
||||
"TextBody": "Вітаємо, \u007b\u007bRecipientName\u007d\u007d.\n\n\u007b\u007bInviterName\u007d\u007d запросив вас приєднатися до \u007b\u007bAppName\u007d\u007d, щоб ви могли безпечно співпрацювати.\n\nВідкрийте запрошення: \u007b\u007bInviteLink\u007d\u007d\nДійсне до: \u007b\u007bValidUntil\u007d\u007d",
|
||||
"Variables": [
|
||||
{ "Name": "RecipientName", "Required": false },
|
||||
{ "Name": "InviterName", "Required": false },
|
||||
{ "Name": "AppName", "Required": false },
|
||||
{ "Name": "InviteLink", "Required": false },
|
||||
{ "Name": "ValidUntil", "Required": false }
|
||||
]
|
||||
}
|
||||
|
||||
### Get the created template
|
||||
GET {{host}}/api/v1/email-templates/StoreMate-Prod/ShareInvite/uk
|
||||
|
||||
### List all templates for the service
|
||||
GET {{host}}/api/v1/email-templates?serviceName=StoreMate-Prod
|
||||
|
||||
### Update the template
|
||||
PUT {{host}}/api/v1/email-templates/StoreMate-Prod/ShareInvite/uk
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"Subject": "Вас запрошено",
|
||||
"HtmlBody": "<html><body><p>Вітаємо, \u007b\u007bRecipientName\u007d\u007d.</p><p>\u007b\u007bInviterName\u007d\u007d запросив вас приєднатися до \u007b\u007bAppName\u007d\u007d.</p><p><a href=\"\u007b\u007bInviteLink\u007d\u007d\">Відкрити запрошення</a></p><p>Дійсне до <strong>\u007b\u007bValidUntil\u007d\u007d</strong>.</p></body></html>",
|
||||
"TextBody": "Вітаємо, \u007b\u007bRecipientName\u007d\u007d.\n\n\u007b\u007bInviterName\u007d\u007d запросив вас приєднатися до \u007b\u007bAppName\u007d\u007d.\n\nВідкрийте запрошення: \u007b\u007bInviteLink\u007d\u007d\nДійсне до: \u007b\u007bValidUntil\u007d\u007d",
|
||||
"Variables": [
|
||||
{ "Name": "RecipientName", "Required": false },
|
||||
{ "Name": "InviterName", "Required": false },
|
||||
{ "Name": "AppName", "Required": false },
|
||||
{ "Name": "InviteLink", "Required": false },
|
||||
{ "Name": "ValidUntil", "Required": false }
|
||||
]
|
||||
}
|
||||
|
||||
### Delete the template
|
||||
DELETE {{host}}/api/v1/email-templates/StoreMate-Prod/ShareInvite/uk
|
||||
|
||||
@@ -21,74 +21,93 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-5">
|
||||
<label asp-for="ServiceName" class="form-label fw-semibold">Service Name</label>
|
||||
<input asp-for="ServiceName" class="form-control" readonly="@(!Model.IsNew)" />
|
||||
<span asp-validation-for="ServiceName" class="text-danger small"></span>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<label asp-for="Key" class="form-label fw-semibold">Key</label>
|
||||
<input asp-for="Key" class="form-control" readonly="@(!Model.IsNew)" />
|
||||
<span asp-validation-for="Key" class="text-danger small"></span>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label asp-for="LanguageCode" class="form-label fw-semibold">Language</label>
|
||||
<input asp-for="LanguageCode" class="form-control" readonly="@(!Model.IsNew)" />
|
||||
<span asp-validation-for="LanguageCode" class="text-danger small"></span>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="nav nav-tabs template-editor-tabs mb-3" id="templateEditorTabs" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="edit-tab" data-bs-toggle="tab" data-bs-target="#edit-pane" type="button" role="tab" aria-controls="edit-pane" aria-selected="true">
|
||||
Edit
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="preview-tab" data-bs-toggle="tab" data-bs-target="#preview-pane" type="button" role="tab" aria-controls="preview-pane" aria-selected="false">
|
||||
Preview
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Subject" class="form-label fw-semibold">Subject</label>
|
||||
<input asp-for="Subject" class="form-control" />
|
||||
<span asp-validation-for="Subject" class="text-danger small"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="HtmlBody" class="form-label fw-semibold">HTML Body</label>
|
||||
<textarea asp-for="HtmlBody" class="form-control font-monospace" rows="10"></textarea>
|
||||
<span asp-validation-for="HtmlBody" class="text-danger small"></span>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="TextBody" class="form-label fw-semibold">Text Body</label>
|
||||
<textarea asp-for="TextBody" class="form-control font-monospace" rows="5"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="VariablesJson" class="form-label fw-semibold">Variables (JSON)</label>
|
||||
<textarea asp-for="VariablesJson" class="form-control font-monospace" rows="4"
|
||||
placeholder='[{"name":"UserName","required":true}]'></textarea>
|
||||
<span asp-validation-for="VariablesJson" class="text-danger small"></span>
|
||||
<div class="form-text">JSON array of <code>{"name":"...", "required":true|false}</code></div>
|
||||
</div>
|
||||
|
||||
<div class="template-preview-panel mb-3">
|
||||
<div class="template-preview-panel-header">
|
||||
<div>
|
||||
<div class="template-preview-title">Preview</div>
|
||||
<div class="template-preview-subtitle">Rendered with sample values from the variable list.</div>
|
||||
</div>
|
||||
<span id="previewStatus" class="badge text-bg-secondary">Ready</span>
|
||||
</div>
|
||||
|
||||
<div class="template-preview-grid">
|
||||
<div class="template-preview-source">
|
||||
<div class="template-preview-section-title">Sample values</div>
|
||||
<div id="previewVariables" class="template-preview-variables"></div>
|
||||
<div class="form-text mt-2">Change these values to see the rendered output update immediately.</div>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade show active" id="edit-pane" role="tabpanel" aria-labelledby="edit-tab" tabindex="0">
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-5">
|
||||
<label asp-for="ServiceName" class="form-label fw-semibold">Service Name</label>
|
||||
<input asp-for="ServiceName" class="form-control" readonly="@(!Model.IsNew)" />
|
||||
<span asp-validation-for="ServiceName" class="text-danger small"></span>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<label asp-for="Key" class="form-label fw-semibold">Key</label>
|
||||
<input asp-for="Key" class="form-control" readonly="@(!Model.IsNew)" />
|
||||
<span asp-validation-for="Key" class="text-danger small"></span>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label asp-for="LanguageCode" class="form-label fw-semibold">Language</label>
|
||||
<input asp-for="LanguageCode" class="form-control" readonly="@(!Model.IsNew)" />
|
||||
<span asp-validation-for="LanguageCode" class="text-danger small"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="template-preview-output">
|
||||
<div class="template-preview-section-title">Rendered subject</div>
|
||||
<div id="previewSubject" class="template-preview-subject"></div>
|
||||
<div class="mb-3">
|
||||
<label asp-for="Subject" class="form-label fw-semibold">Subject</label>
|
||||
<input asp-for="Subject" class="form-control" />
|
||||
<span asp-validation-for="Subject" class="text-danger small"></span>
|
||||
</div>
|
||||
|
||||
<div class="template-preview-section-title mt-3">Rendered HTML</div>
|
||||
<iframe id="previewHtmlFrame" class="template-preview-frame" title="Email HTML preview"></iframe>
|
||||
<div class="mb-3">
|
||||
<label asp-for="HtmlBody" class="form-label fw-semibold">HTML Body</label>
|
||||
<textarea asp-for="HtmlBody" class="form-control font-monospace" rows="10"></textarea>
|
||||
<span asp-validation-for="HtmlBody" class="text-danger small"></span>
|
||||
</div>
|
||||
|
||||
<div class="template-preview-section-title mt-3">Rendered text</div>
|
||||
<pre id="previewText" class="template-preview-text mb-0"></pre>
|
||||
<div class="mb-3">
|
||||
<label asp-for="TextBody" class="form-label fw-semibold">Text Body</label>
|
||||
<textarea asp-for="TextBody" class="form-control font-monospace" rows="5"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="VariablesJson" class="form-label fw-semibold">Variables (JSON)</label>
|
||||
<textarea asp-for="VariablesJson" class="form-control font-monospace" rows="4"
|
||||
placeholder='[{"name":"UserName","required":true}]'></textarea>
|
||||
<span asp-validation-for="VariablesJson" class="text-danger small"></span>
|
||||
<div class="form-text">JSON array of <code>{"name":"...", "required":true|false}</code></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="preview-pane" role="tabpanel" aria-labelledby="preview-tab" tabindex="0">
|
||||
<div class="template-preview-panel mb-3">
|
||||
<div class="template-preview-panel-header">
|
||||
<div>
|
||||
<div class="template-preview-title">Preview</div>
|
||||
<div class="template-preview-subtitle">Rendered with sample values from the variable list.</div>
|
||||
</div>
|
||||
<span id="previewStatus" class="badge text-bg-secondary">Ready</span>
|
||||
</div>
|
||||
|
||||
<div class="template-preview-body">
|
||||
<div class="template-preview-source template-preview-section-block">
|
||||
<div class="template-preview-section-title">Sample values</div>
|
||||
<div id="previewVariables" class="template-preview-variables"></div>
|
||||
<div class="form-text mt-2">Change these values to see the rendered output update immediately.</div>
|
||||
</div>
|
||||
|
||||
<div class="template-preview-output">
|
||||
<div class="template-preview-section-title">Rendered subject</div>
|
||||
<div id="previewSubject" class="template-preview-subject"></div>
|
||||
|
||||
<div class="template-preview-section-title mt-3">Rendered HTML</div>
|
||||
<iframe id="previewHtmlFrame" class="template-preview-frame" title="Email HTML preview"></iframe>
|
||||
|
||||
<div class="template-preview-section-title mt-3">Rendered text</div>
|
||||
<pre id="previewText" class="template-preview-text mb-0"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -114,6 +133,7 @@
|
||||
const previewText = document.getElementById('previewText');
|
||||
const previewFrame = document.getElementById('previewHtmlFrame');
|
||||
const previewStatus = document.getElementById('previewStatus');
|
||||
const previewTab = document.getElementById('preview-tab');
|
||||
|
||||
if (!subjectField || !htmlField || !textField || !variablesField || !previewVariablesHost || !previewSubject || !previewText || !previewFrame || !previewStatus) {
|
||||
return;
|
||||
@@ -267,6 +287,9 @@
|
||||
subjectField.addEventListener('input', updatePreview);
|
||||
htmlField.addEventListener('input', updatePreview);
|
||||
textField.addEventListener('input', updatePreview);
|
||||
if (previewTab) {
|
||||
previewTab.addEventListener('shown.bs.tab', updatePreview);
|
||||
}
|
||||
|
||||
renderVariableInputs();
|
||||
})();
|
||||
|
||||
@@ -118,9 +118,10 @@ body {
|
||||
.empty-state .bi { font-size: 2.5rem; opacity: .35; display: block; margin-bottom: .75rem; }
|
||||
.empty-state p { font-size: .9rem; margin-bottom: 0; }
|
||||
|
||||
/* ── Editor wrapper — constrains width ───────────────── */
|
||||
/* ── Editor wrapper ──────────────────────────────────── */
|
||||
.editor-wrapper {
|
||||
max-width: 860px;
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
/* ── Editor card ──────────────────────────────────────── */
|
||||
@@ -154,6 +155,26 @@ body {
|
||||
border-radius: 0 0 .5rem .5rem !important;
|
||||
}
|
||||
|
||||
/* ── Editor tabs ─────────────────────────────────────── */
|
||||
.template-editor-tabs {
|
||||
border-bottom-color: #dce3eb;
|
||||
}
|
||||
|
||||
.template-editor-tabs .nav-link {
|
||||
color: #526072;
|
||||
font-weight: 600;
|
||||
border-radius: .5rem .5rem 0 0;
|
||||
}
|
||||
|
||||
.template-editor-tabs .nav-link.active {
|
||||
color: #0d6efd;
|
||||
}
|
||||
|
||||
/* ── Tab content ──────────────────────────────────────── */
|
||||
.tab-content {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* ── Email template preview ───────────────────────────── */
|
||||
.template-preview-panel {
|
||||
border: 1px solid #dce3eb;
|
||||
@@ -185,9 +206,9 @@ body {
|
||||
margin-top: .15rem;
|
||||
}
|
||||
|
||||
.template-preview-grid {
|
||||
.template-preview-body {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(240px, 300px) minmax(0, 1fr);
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: 1rem;
|
||||
padding: 1rem 1.25rem 1.25rem;
|
||||
}
|
||||
@@ -197,6 +218,13 @@ body {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.template-preview-section-block {
|
||||
padding: 1rem;
|
||||
border: 1px solid #e5ebf2;
|
||||
border-radius: .65rem;
|
||||
background: #fafcff;
|
||||
}
|
||||
|
||||
.template-preview-section-title {
|
||||
font-size: .7rem;
|
||||
font-weight: 700;
|
||||
@@ -210,6 +238,7 @@ body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .75rem;
|
||||
max-width: 420px;
|
||||
}
|
||||
|
||||
.template-preview-variables .form-control-sm {
|
||||
@@ -248,7 +277,7 @@ body {
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.template-preview-grid {
|
||||
.template-preview-body {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user