Initial commit
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
@page
|
||||
@model ListmonkIntegration.Pages.Campaigns.IndexModel
|
||||
@{
|
||||
ViewData["Title"] = "Управление рассылками";
|
||||
}
|
||||
|
||||
<h1> Управление рассылками</h1>
|
||||
<hr />
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="mb-4">
|
||||
<a asp-page="/Templates/Create" class="btn btn-primary btn-lg">
|
||||
Создать шаблон письма
|
||||
</a>
|
||||
<a asp-page="/Campaigns/Send" class="btn btn-warning btn-lg">
|
||||
Отправить письмо
|
||||
</a>
|
||||
<a asp-page="/Subscribers/Upload" class="btn btn-success btn-lg">
|
||||
Загрузить подписчиков
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<h5>Функционал:</h5>
|
||||
<ul>
|
||||
<li>Загрузка Excel файлов с подписчиками</li>
|
||||
<li>Создание шаблонов писем</li>
|
||||
<li>Просмотр существующих кампаний</li>
|
||||
<li>Запуск рассылок</li>
|
||||
<li>Просмотр статистики</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<a asp-page="/Index" class="btn btn-secondary">← На главную</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (TempData["SuccessMessage"] != null)
|
||||
{
|
||||
<div class="alert alert-success mt-3">
|
||||
@TempData["SuccessMessage"]
|
||||
</div>
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace ListmonkIntegration.Pages.Campaigns
|
||||
{
|
||||
public class IndexModel : PageModel
|
||||
{
|
||||
public void OnGet()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
@page
|
||||
@model ListmonkIntegration.Pages.Campaigns.SendModel
|
||||
@{
|
||||
ViewData["Title"] = "Отправка письма";
|
||||
}
|
||||
|
||||
<h1>Отправка письма</h1>
|
||||
<hr />
|
||||
|
||||
@if (!string.IsNullOrEmpty(Model.ErrorMessage))
|
||||
{
|
||||
<div class="alert alert-danger">@Model.ErrorMessage</div>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrEmpty(Model.SuccessMessage))
|
||||
{
|
||||
<div class="alert alert-success">@Model.SuccessMessage</div>
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<form method="post">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Название кампании</label>
|
||||
<input type="text" asp-for="CampaignName" class="form-control" required placeholder="Например: Рассылка 08.07.2026" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Выберите шаблон письма</label>
|
||||
<select asp-for="SelectedTemplateId" class="form-select" asp-items="Model.Templates" id="templateSelect" required>
|
||||
<option value="">-- Выберите шаблон --</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Список получателей</label>
|
||||
<select asp-for="SelectedListId" class="form-select" asp-items="Model.Lists" required>
|
||||
<option value="">-- Выберите список --</option>
|
||||
</select>
|
||||
<div class="form-text">
|
||||
<span id="selectedListInfo"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Предпросмотр шаблона</label>
|
||||
<div id="templatePreview" class="border p-3 bg-light" style="min-height: 200px;">
|
||||
<p class="text-muted">Выберите шаблон для предпросмотра</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<button type="submit" class="btn btn-primary btn-lg">Отправить</button>
|
||||
<a asp-page="/Campaigns/Index" class="btn btn-secondary">Отмена</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
document.getElementById('templateSelect').addEventListener('change', async function() {
|
||||
const templateId = this.value;
|
||||
const previewDiv = document.getElementById('templatePreview');
|
||||
|
||||
if (templateId) {
|
||||
previewDiv.innerHTML = '<p class="text-muted">Загрузка...</p>';
|
||||
try {
|
||||
const response = await fetch(`/api/templates/${templateId}/preview`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
previewDiv.innerHTML = data.body || '<p class="text-muted">Шаблон пуст</p>';
|
||||
} else {
|
||||
previewDiv.innerHTML = '<p class="text-danger">Ошибка загрузки шаблона</p>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
previewDiv.innerHTML = '<p class="text-danger">Ошибка загрузки шаблона</p>';
|
||||
}
|
||||
} else {
|
||||
previewDiv.innerHTML = '<p class="text-muted">Выберите шаблон для предпросмотра</p>';
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelector('select[asp-for="SelectedListId"]').addEventListener('change', function() {
|
||||
const select = this;
|
||||
const infoSpan = document.getElementById('selectedListInfo');
|
||||
const selectedOption = select.options[select.selectedIndex];
|
||||
if (selectedOption.value) {
|
||||
infoSpan.textContent = `Выбран список: ${selectedOption.text}`;
|
||||
} else {
|
||||
infoSpan.textContent = '';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using ListmonkIntegration.Services;
|
||||
|
||||
namespace ListmonkIntegration.Pages.Campaigns
|
||||
{
|
||||
public class SendModel : PageModel
|
||||
{
|
||||
private readonly IListmonkService _listmonkService;
|
||||
private readonly ILogger<SendModel> _logger;
|
||||
|
||||
[BindProperty]
|
||||
public string CampaignName { get; set; } = string.Empty;
|
||||
|
||||
[BindProperty]
|
||||
public int? SelectedTemplateId { get; set; }
|
||||
|
||||
[BindProperty]
|
||||
public int? SelectedListId { get; set; }
|
||||
|
||||
public SelectList Templates { get; set; } = new SelectList(Enumerable.Empty<SelectListItem>());
|
||||
public SelectList Lists { get; set; } = new SelectList(Enumerable.Empty<SelectListItem>());
|
||||
|
||||
public string? ErrorMessage { get; set; }
|
||||
public string? SuccessMessage { get; set; }
|
||||
|
||||
public SendModel(IListmonkService listmonkService, ILogger<SendModel> logger)
|
||||
{
|
||||
_listmonkService = listmonkService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task OnGetAsync()
|
||||
{
|
||||
await LoadDataAsync();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync()
|
||||
{
|
||||
if (!SelectedTemplateId.HasValue || !SelectedListId.HasValue || string.IsNullOrWhiteSpace(CampaignName))
|
||||
{
|
||||
ErrorMessage = "Çàïîëíèòå âñå ïîëÿ";
|
||||
await LoadDataAsync();
|
||||
return Page();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var templates = await _listmonkService.GetTemplatesAsync();
|
||||
var template = templates.FirstOrDefault(t => t.Id == SelectedTemplateId.Value);
|
||||
|
||||
if (template == null)
|
||||
{
|
||||
ErrorMessage = "Øàáëîí íå íàéäåí";
|
||||
await LoadDataAsync();
|
||||
return Page();
|
||||
}
|
||||
|
||||
var campaignId = await _listmonkService.CreateCampaignAsync(
|
||||
CampaignName,
|
||||
SelectedListId.Value,
|
||||
SelectedTemplateId.Value,
|
||||
template.Subject
|
||||
);
|
||||
|
||||
var started = await _listmonkService.StartCampaignAsync(campaignId);
|
||||
|
||||
if (started)
|
||||
{
|
||||
SuccessMessage = $"Êàìïàíèÿ \"{CampaignName}\" óñïåøíî ñîçäàíà è çàïóùåíà!";
|
||||
_logger.LogInformation($"Campaign {campaignId} started successfully");
|
||||
}
|
||||
else
|
||||
{
|
||||
ErrorMessage = $"Êàìïàíèÿ ñîçäàíà (ID: {campaignId}), íî íå óäàëîñü çàïóñòèòü";
|
||||
}
|
||||
|
||||
await LoadDataAsync();
|
||||
return Page();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error sending campaign");
|
||||
ErrorMessage = $"Îøèáêà: {ex.Message}";
|
||||
await LoadDataAsync();
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadDataAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var templates = await _listmonkService.GetTemplatesAsync();
|
||||
Templates = new SelectList(templates, "Id", "Name");
|
||||
|
||||
var lists = await _listmonkService.GetListsAsync();
|
||||
Lists = new SelectList(lists, "Id", "Name");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error loading data");
|
||||
ErrorMessage = $"Îøèáêà çàãðóçêè äàííûõ: {ex.Message}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
@page
|
||||
@model ErrorModel
|
||||
@{
|
||||
ViewData["Title"] = "Error";
|
||||
}
|
||||
|
||||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
|
||||
@if (Model.ShowRequestId)
|
||||
{
|
||||
<p>
|
||||
<strong>Request ID:</strong> <code>@Model.RequestId</code>
|
||||
</p>
|
||||
}
|
||||
|
||||
<h3>Development Mode</h3>
|
||||
<p>
|
||||
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
|
||||
</p>
|
||||
<p>
|
||||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||
It can result in displaying sensitive information from exceptions to end users.
|
||||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||
and restarting the app.
|
||||
</p>
|
||||
@@ -0,0 +1,28 @@
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace ListmonkIntegration.Pages
|
||||
{
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
[IgnoreAntiforgeryToken]
|
||||
public class ErrorModel : PageModel
|
||||
{
|
||||
public string? RequestId { get; set; }
|
||||
|
||||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
|
||||
private readonly ILogger<ErrorModel> _logger;
|
||||
|
||||
public ErrorModel(ILogger<ErrorModel> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void OnGet()
|
||||
{
|
||||
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
@page
|
||||
@model IndexModel
|
||||
@{
|
||||
ViewData["Title"] = "Главная";
|
||||
}
|
||||
|
||||
<div class="text-center">
|
||||
<h1 class="display-4">Система управления рассылками</h1>
|
||||
<p>Интеграция с Listmonk для автоматизации email-рассылок</p>
|
||||
</div>
|
||||
|
||||
<div class="row mt-5">
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title">Статус подключения</h5>
|
||||
@if (Model.IsConnected)
|
||||
{
|
||||
<p class="text-success"><strong>Подключено к Listmonk</strong></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="text-danger"><strong>Нет подключения</strong></p>
|
||||
}
|
||||
<p class="card-text">Адрес: @Model.ListmonkUrl</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title">Загрузка данных</h5>
|
||||
<p class="card-text">Загрузите Excel файл со списком получателей</p>
|
||||
<a asp-page="/Subscribers/Upload" class="btn btn-primary">Загрузить файл</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title">Рассылки</h5>
|
||||
<p class="card-text">Создание шаблонов писем. </p>
|
||||
<a asp-page="/Campaigns/Index" class="btn btn-success">Управление</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,33 @@
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using ListmonkIntegration.Services;
|
||||
using ListmonkIntegration.Models;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace ListmonkIntegration.Pages
|
||||
{
|
||||
public class IndexModel : PageModel
|
||||
{
|
||||
private readonly ILogger<IndexModel> _logger;
|
||||
private readonly IListmonkService _listmonkService;
|
||||
private readonly ListmonkSettings _settings;
|
||||
|
||||
public bool IsConnected { get; private set; }
|
||||
public string ListmonkUrl { get; private set; } = string.Empty;
|
||||
|
||||
public IndexModel(
|
||||
ILogger<IndexModel> logger,
|
||||
IListmonkService listmonkService,
|
||||
IOptions<ListmonkSettings> settings)
|
||||
{
|
||||
_logger = logger;
|
||||
_listmonkService = listmonkService;
|
||||
_settings = settings.Value;
|
||||
}
|
||||
|
||||
public async Task OnGetAsync()
|
||||
{
|
||||
ListmonkUrl = _settings.BaseUrl;
|
||||
IsConnected = await _listmonkService.TestConnectionAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
@page
|
||||
@model PrivacyModel
|
||||
@{
|
||||
ViewData["Title"] = "Privacy Policy";
|
||||
}
|
||||
<h1>@ViewData["Title"]</h1>
|
||||
|
||||
<p>Use this page to detail your site's privacy policy.</p>
|
||||
@@ -0,0 +1,20 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace ListmonkIntegration.Pages
|
||||
{
|
||||
public class PrivacyModel : PageModel
|
||||
{
|
||||
private readonly ILogger<PrivacyModel> _logger;
|
||||
|
||||
public PrivacyModel(ILogger<PrivacyModel> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void OnGet()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@ViewData["Title"] - ListmonkIntegration</title>
|
||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
|
||||
<link rel="stylesheet" href="~/ListmonkIntegration.styles.css" asp-append-version="true" />
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" asp-area="" asp-page="/Index">ListmonkIntegration</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
|
||||
aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
|
||||
<ul class="navbar-nav flex-grow-1">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
<div class="container">
|
||||
<main role="main" class="pb-3">
|
||||
@RenderBody()
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer class="border-top footer text-muted">
|
||||
<div class="container">
|
||||
© 2026 - ListmonkIntegration - <a asp-area="" asp-page="/Privacy">Privacy</a>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="~/lib/jquery/dist/jquery.min.js"></script>
|
||||
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="~/js/site.js" asp-append-version="true"></script>
|
||||
|
||||
@await RenderSectionAsync("Scripts", required: false)
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,48 @@
|
||||
/* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
|
||||
for details on configuring this project to bundle and minify static web assets. */
|
||||
|
||||
a.navbar-brand {
|
||||
white-space: normal;
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0077cc;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #1b6ec2;
|
||||
border-color: #1861ac;
|
||||
}
|
||||
|
||||
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
|
||||
color: #fff;
|
||||
background-color: #1b6ec2;
|
||||
border-color: #1861ac;
|
||||
}
|
||||
|
||||
.border-top {
|
||||
border-top: 1px solid #e5e5e5;
|
||||
}
|
||||
.border-bottom {
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.box-shadow {
|
||||
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
|
||||
}
|
||||
|
||||
button.accept-policy {
|
||||
font-size: 1rem;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
line-height: 60px;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
|
||||
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
|
||||
@@ -0,0 +1,73 @@
|
||||
@page
|
||||
@model ListmonkIntegration.Pages.Subscribers.UploadModel
|
||||
@{
|
||||
ViewData["Title"] = "Загрузка подписчиков";
|
||||
}
|
||||
|
||||
<h1>Загрузка списка подписчиков</h1>
|
||||
<hr />
|
||||
|
||||
@if (!string.IsNullOrEmpty(Model.Message))
|
||||
{
|
||||
<div class="alert @(Model.IsSuccess ? "alert-success" : "alert-danger")">
|
||||
@Model.Message
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Выберите Excel файл (.xlsx)</label>
|
||||
<input type="file" name="excelFile" class="form-control" accept=".xlsx" required />
|
||||
<div class="form-text">
|
||||
Файл должен содержать столбцы: Email, FullName и другие атрибуты
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Название списка в Listmonk</label>
|
||||
<input type="text" name="listName" class="form-control" value="Рассылка @DateTime.Now.ToString("dd.MM.yyyy")" required />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<button type="submit" class="btn btn-primary">Загрузить и обработать</button>
|
||||
<a asp-page="/Index" class="btn btn-secondary">Отмена</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (Model.Subscribers != null && Model.Subscribers.Any())
|
||||
{
|
||||
<h3 class="mt-4">Загружено подписчиков: @Model.Subscribers.Count</h3>
|
||||
|
||||
@if (Model.ColumnHeaders != null && Model.ColumnHeaders.Any())
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
@foreach (var header in Model.ColumnHeaders)
|
||||
{
|
||||
<th>@header</th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var sub in Model.Subscribers)
|
||||
{
|
||||
<tr>
|
||||
<td>@sub.Email</td>
|
||||
<td>@sub.FullName</td>
|
||||
@foreach (var attr in sub.Attributes)
|
||||
{
|
||||
<td>@attr.Value</td>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using ListmonkIntegration.Services;
|
||||
using ListmonkIntegration.Models;
|
||||
using ClosedXML.Excel;
|
||||
|
||||
namespace ListmonkIntegration.Pages.Subscribers
|
||||
{
|
||||
public class UploadModel : PageModel
|
||||
{
|
||||
private readonly IListmonkService _listmonkService;
|
||||
private readonly ILogger<UploadModel> _logger;
|
||||
|
||||
[BindProperty]
|
||||
public IFormFile? excelFile { get; set; }
|
||||
|
||||
public List<Subscriber> Subscribers { get; set; } = new List<Subscriber>();
|
||||
public List<string> ColumnHeaders { get; set; } = new List<string>();
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public bool IsSuccess { get; set; }
|
||||
|
||||
public UploadModel(IListmonkService listmonkService, ILogger<UploadModel> logger)
|
||||
{
|
||||
_listmonkService = listmonkService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void OnGet()
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync(string listName)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("=== Upload started ===");
|
||||
|
||||
if (excelFile == null || excelFile.Length == 0)
|
||||
{
|
||||
Message = "Ôàéë íå âûáðàí";
|
||||
IsSuccess = false;
|
||||
return Page();
|
||||
}
|
||||
|
||||
_logger.LogInformation($"File received: {excelFile.FileName}, Size: {excelFile.Length} bytes");
|
||||
|
||||
// Îãðàíè÷åíèå ðàçìåðà ôàéëà (10 MB)
|
||||
if (excelFile.Length > 10 * 1024 * 1024)
|
||||
{
|
||||
Message = "Ôàéë ñëèøêîì áîëüøîé (ìàêñèìóì 10 MB)";
|
||||
IsSuccess = false;
|
||||
return Page();
|
||||
}
|
||||
|
||||
// ×òåíèå Excel ôàéëà
|
||||
_logger.LogInformation("Reading Excel file...");
|
||||
Subscribers = ReadExcelFile(excelFile);
|
||||
_logger.LogInformation($"Read {Subscribers.Count} subscribers");
|
||||
|
||||
if (!Subscribers.Any())
|
||||
{
|
||||
Message = "Íå íàéäåíî ïîäïèñ÷èêîâ â ôàéëå";
|
||||
IsSuccess = false;
|
||||
return Page();
|
||||
}
|
||||
|
||||
// Ïðîâåðÿåì ïîäêëþ÷åíèå
|
||||
_logger.LogInformation("Testing Listmonk connection...");
|
||||
if (!await _listmonkService.TestConnectionAsync())
|
||||
{
|
||||
Message = "Íåò ïîäêëþ÷åíèÿ ê Listmonk";
|
||||
IsSuccess = false;
|
||||
return Page();
|
||||
}
|
||||
|
||||
// Ñîçäàåì ñïèñîê
|
||||
_logger.LogInformation($"Creating list: {listName}");
|
||||
int listId = await _listmonkService.CreateListAsync(listName);
|
||||
|
||||
// Èìïîðòèðóåì
|
||||
_logger.LogInformation($"Importing to list {listId}...");
|
||||
await _listmonkService.ImportSubscribersAsync(new List<int> { listId }, Subscribers);
|
||||
|
||||
Message = $"Óñïåøíî çàãðóæåíî {Subscribers.Count} ïîäïèñ÷èêîâ â ñïèñîê \"{listName}\"";
|
||||
IsSuccess = true;
|
||||
_logger.LogInformation("=== Upload completed successfully ===");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Message = $"Îøèáêà: {ex.Message}";
|
||||
IsSuccess = false;
|
||||
_logger.LogError(ex, "=== Upload failed ===");
|
||||
}
|
||||
|
||||
return Page();
|
||||
}
|
||||
|
||||
private List<Subscriber> ReadExcelFile(IFormFile file)
|
||||
{
|
||||
var subscribers = new List<Subscriber>();
|
||||
|
||||
try
|
||||
{
|
||||
// Ñîçäà¸ì âðåìåííûé ôàéë ñ ïðàâèëüíûì ðàñøèðåíèåì .xlsx
|
||||
var tempFilePath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx");
|
||||
_logger.LogInformation($"Temp file: {tempFilePath}");
|
||||
|
||||
try
|
||||
{
|
||||
// Ñîõðàíÿåì çàãðóæåííûé ôàéë âî âðåìåííûé ôàéë
|
||||
using (var stream = new FileStream(tempFilePath, FileMode.Create))
|
||||
{
|
||||
file.CopyTo(stream);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Opening workbook...");
|
||||
using (var workbook = new XLWorkbook(tempFilePath))
|
||||
{
|
||||
var worksheet = workbook.Worksheet(1);
|
||||
var firstRow = worksheet.FirstRowUsed();
|
||||
|
||||
if (firstRow == null)
|
||||
{
|
||||
_logger.LogWarning("Empty file");
|
||||
return subscribers;
|
||||
}
|
||||
|
||||
// Çàãîëîâêè
|
||||
var headers = new List<string>();
|
||||
foreach (var cell in firstRow.Cells())
|
||||
{
|
||||
var header = cell.Value.ToString().Trim();
|
||||
headers.Add(header);
|
||||
_logger.LogInformation($"Header: {header}");
|
||||
}
|
||||
|
||||
ColumnHeaders = headers;
|
||||
_logger.LogInformation($"Total headers: {headers.Count}");
|
||||
|
||||
if (headers.Count < 2)
|
||||
{
|
||||
_logger.LogError("Need at least 2 columns");
|
||||
return subscribers;
|
||||
}
|
||||
|
||||
// Äàííûå
|
||||
var lastRow = worksheet.LastRowUsed();
|
||||
_logger.LogInformation($"Last row: {lastRow?.RowNumber() ?? 0}");
|
||||
|
||||
for (int row = 2; row <= lastRow.RowNumber(); row++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var email = worksheet.Cell(row, 1).GetValue<string>().Trim();
|
||||
if (string.IsNullOrWhiteSpace(email))
|
||||
{
|
||||
_logger.LogWarning($"Row {row}: empty email, skipping");
|
||||
continue;
|
||||
}
|
||||
|
||||
var name = worksheet.Cell(row, 2).GetValue<string>().Trim();
|
||||
|
||||
var attributes = new Dictionary<string, string>();
|
||||
for (int col = 3; col <= headers.Count; col++)
|
||||
{
|
||||
var value = worksheet.Cell(row, col).GetValue<string>().Trim();
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
attributes[headers[col - 1]] = value;
|
||||
}
|
||||
}
|
||||
|
||||
subscribers.Add(new Subscriber
|
||||
{
|
||||
Email = email,
|
||||
FullName = name,
|
||||
Attributes = attributes
|
||||
});
|
||||
|
||||
_logger.LogInformation($"Row {row}: Added {email}");
|
||||
}
|
||||
catch (Exception rowEx)
|
||||
{
|
||||
_logger.LogWarning(rowEx, $"Skipping row {row}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Óäàëÿåì âðåìåííûé ôàéë
|
||||
if (System.IO.File.Exists(tempFilePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
System.IO.File.Delete(tempFilePath);
|
||||
_logger.LogInformation($"Deleted temp file: {tempFilePath}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, $"Failed to delete temp file: {tempFilePath}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error reading Excel");
|
||||
throw;
|
||||
}
|
||||
|
||||
return subscribers;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
@page
|
||||
@model ListmonkIntegration.Pages.Templates.CreateModel
|
||||
@{
|
||||
ViewData["Title"] = "Создание шаблона письма";
|
||||
}
|
||||
|
||||
<h1>Создание шаблона письма</h1>
|
||||
<hr />
|
||||
|
||||
@if (TempData["SuccessMessage"] != null)
|
||||
{
|
||||
<div class="alert alert-success">
|
||||
@TempData["SuccessMessage"]
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (TempData["ErrorMessage"] != null)
|
||||
{
|
||||
<div class="alert alert-danger">
|
||||
@TempData["ErrorMessage"]
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<form method="post">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Название шаблона</label>
|
||||
<input type="text" asp-for="TemplateName" class="form-control" required placeholder="Например: Приветственное письмо" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Тема письма</label>
|
||||
<input type="text" asp-for="Subject" class="form-control" required placeholder="Например: Добро пожаловать, {{ .Subscriber.Name }}!" />
|
||||
<div class="form-text">Можно использовать переменные: {{ .Subscriber.Name }}, {{ .Subscriber.Email }}, {{ .Subscriber.Attribs.ИмяАтрибута }}</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Выберите список рассылки (для просмотра атрибутов)</label>
|
||||
<select asp-for="SelectedListId" class="form-select" asp-items="Model.Lists" id="listSelect">
|
||||
<option value="">-- Выберите список --</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Текст письма</label>
|
||||
<textarea asp-for="Body" id="editor" class="form-control" rows="15" placeholder="Введите текст письма здесь..."></textarea>
|
||||
<div class="form-text">
|
||||
Используйте редактор для форматирования текста. HTML код генерируется автоматически.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<button type="submit" class="btn btn-primary">Создать шаблон</button>
|
||||
<a asp-page="/Campaigns/Index" class="btn btn-secondary">Отмена</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<!-- Кнопки для вставки переменных -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5>Быстрая вставка переменных</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-2">
|
||||
<label class="form-label small text-muted">Стандартные поля:</label>
|
||||
<div class="btn-group-vertical w-100">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary mb-1" onclick="insertVariable('{{ .Subscriber.Name }}')">
|
||||
Имя (Name)
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary mb-1" onclick="insertVariable('{{ .Subscriber.Email }}')">
|
||||
Email
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div id="attributesButtons">
|
||||
<label class="form-label small text-muted">Атрибуты из списка:</label>
|
||||
<p class="text-muted small">Выберите список для загрузки атрибутов</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Примеры использования -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>Пример использования</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<small class="text-muted">
|
||||
Переменные подставляются автоматически при отправке письма каждому получателю.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script src="https://cdn.tiny.cloud/1/k6rorn68v4xqaesxvaroqlkyhfmncfoq8bxgxv3nd39szqrc/tinymce/8/tinymce.min.js" referrerpolicy="origin" crossorigin="anonymous"></script>
|
||||
<script>
|
||||
console.log('Script started');
|
||||
|
||||
// Глобальная переменная для хранения ссылки на редактор
|
||||
window.tinymceEditor = null;
|
||||
|
||||
// Инициализация TinyMCE
|
||||
tinymce.init({
|
||||
selector: '#editor', // ИСПРАВЛЕНО: конкретный ID вместо всех textarea
|
||||
api_key: 'k6rorn68v4xqaesxvaroqlkyhfmncfoq8bxgxv3nd39szqrc',
|
||||
height: 400,
|
||||
plugins: 'lists link table code bold italic underline',
|
||||
toolbar: 'bold italic underline | h1 h2 h3 | bullist numlist | link unlink | code',
|
||||
menubar: false,
|
||||
statusbar: true,
|
||||
branding: false,
|
||||
content_style: 'body { font-family: Arial, sans-serif; font-size: 14px; }',
|
||||
|
||||
// ИСПРАВЛЕНО: добавлена setup функция для сохранения редактора
|
||||
setup: function(editor) {
|
||||
window.tinymceEditor = editor;
|
||||
console.log('TinyMCE editor saved to window.tinymceEditor');
|
||||
|
||||
editor.on('change', function() {
|
||||
tinymce.triggerSave();
|
||||
});
|
||||
},
|
||||
|
||||
init_instance_callback: function(editor) {
|
||||
console.log('TinyMCE initialized successfully');
|
||||
}
|
||||
});
|
||||
|
||||
// Глобальная функция для вставки переменной
|
||||
window.insertVariable = function(variable) {
|
||||
console.log('insertVariable called with:', variable);
|
||||
|
||||
try {
|
||||
// Проверяем, существует ли редактор
|
||||
if (window.tinymceEditor && !window.tinymceEditor.destroyed) {
|
||||
// Вставляем в TinyMCE
|
||||
window.tinymceEditor.insertContent(variable);
|
||||
window.tinymceEditor.focus();
|
||||
console.log('✓ Inserted into TinyMCE editor');
|
||||
} else if (window.tinymce && window.tinymce.editors.length > 0) {
|
||||
// Альтернативный способ - берём первый редактор
|
||||
var editor = window.tinymce.editors[0];
|
||||
editor.insertContent(variable);
|
||||
editor.focus();
|
||||
console.log('✓ Inserted via tinymce.editors');
|
||||
} else {
|
||||
// Если TinyMCE не инициализирован, вставляем в textarea
|
||||
var textarea = document.querySelector('textarea[name="Body"]');
|
||||
if (textarea) {
|
||||
var start = textarea.selectionStart || 0;
|
||||
var end = textarea.selectionEnd || 0;
|
||||
var text = textarea.value;
|
||||
textarea.value = text.substring(0, start) + variable + text.substring(end);
|
||||
textarea.focus();
|
||||
console.log('✓ Inserted into textarea (TinyMCE not ready)');
|
||||
} else {
|
||||
console.error('✗ Textarea not found');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('✗ Error inserting variable:', error);
|
||||
}
|
||||
};
|
||||
|
||||
console.log('Script loaded, waiting for DOM...');
|
||||
|
||||
// Загрузка атрибутов при выборе списка
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('DOM loaded');
|
||||
|
||||
const listSelect = document.getElementById('listSelect');
|
||||
if (listSelect) {
|
||||
listSelect.addEventListener('change', async function() {
|
||||
const listId = this.value;
|
||||
const attributesDiv = document.getElementById('attributesButtons');
|
||||
console.log('List selected, ID:', listId);
|
||||
|
||||
if (listId) {
|
||||
attributesDiv.innerHTML = '<label class="form-label small text-muted">Атрибуты из списка:</label><div class="text-center"><div class="spinner-border spinner-border-sm" role="status"><span class="visually-hidden">Загрузка...</span></div></div>';
|
||||
try {
|
||||
const response = await fetch(`/api/lists/${listId}/attributes`);
|
||||
console.log('API response status:', response.status);
|
||||
if (response.ok) {
|
||||
const attributes = await response.json();
|
||||
console.log('Attributes loaded:', attributes);
|
||||
displayAttributeButtons(attributes);
|
||||
} else {
|
||||
attributesDiv.innerHTML = '<label class="form-label small text-muted">Атрибуты из списка:</label><p class="text-danger small">Ошибка загрузки атрибутов.</p>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading attributes:', error);
|
||||
attributesDiv.innerHTML = '<label class="form-label small text-muted">Атрибуты из списка:</label><p class="text-danger small">Ошибка загрузки атрибутов.</p>';
|
||||
}
|
||||
} else {
|
||||
attributesDiv.innerHTML = '<label class="form-label small text-muted">Атрибуты из списка:</label><p class="text-muted small">Выберите список для загрузки атрибутов</p>';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Отображение кнопок для атрибутов
|
||||
window.displayAttributeButtons = function(attributes) {
|
||||
console.log('displayAttributeButtons called with:', attributes);
|
||||
const attributesDiv = document.getElementById('attributesButtons');
|
||||
|
||||
if (attributes && attributes.length > 0) {
|
||||
let html = '<label class="form-label small text-muted">Атрибуты из списка:</label><div class="d-flex flex-wrap gap-1">';
|
||||
attributes.forEach(attr => {
|
||||
const variable = `{{ .Subscriber.Attribs.${attr} }}`;
|
||||
console.log('Creating button for attr:', attr, 'variable:', variable);
|
||||
// Экранируем специальные символы для onclick
|
||||
const escapedVar = variable.replace(/'/g, "\\'").replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
|
||||
html += `<button type="button" class="btn btn-sm btn-outline-secondary m-1" onclick="insertVariable('${escapedVar}')">
|
||||
${attr}
|
||||
</button>`;
|
||||
});
|
||||
html += '</div>';
|
||||
attributesDiv.innerHTML = html;
|
||||
console.log('Buttons created');
|
||||
} else {
|
||||
attributesDiv.innerHTML = '<label class="form-label small text-muted">Атрибуты из списка:</label><p class="text-muted small">У выбранного списка нет пользовательских атрибутов.</p>';
|
||||
}
|
||||
};
|
||||
|
||||
console.log('All scripts initialized');
|
||||
</script>
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using ListmonkIntegration.Services;
|
||||
using ListmonkIntegration.Models;
|
||||
|
||||
namespace ListmonkIntegration.Pages.Templates
|
||||
{
|
||||
public class CreateModel : PageModel
|
||||
{
|
||||
private readonly IListmonkService _listmonkService;
|
||||
private readonly ILogger<CreateModel> _logger;
|
||||
|
||||
[BindProperty]
|
||||
public string TemplateName { get; set; } = string.Empty;
|
||||
|
||||
[BindProperty]
|
||||
public string Subject { get; set; } = string.Empty;
|
||||
|
||||
[BindProperty]
|
||||
public string Body { get; set; } = string.Empty;
|
||||
|
||||
[BindProperty]
|
||||
public int? SelectedListId { get; set; }
|
||||
|
||||
public SelectList Lists { get; set; } = new SelectList(Enumerable.Empty<SelectListItem>());
|
||||
|
||||
public string? DebugInfo { get; set; }
|
||||
|
||||
public CreateModel(IListmonkService listmonkService, ILogger<CreateModel> logger)
|
||||
{
|
||||
_listmonkService = listmonkService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task OnGetAsync()
|
||||
{
|
||||
_logger.LogInformation("OnGetAsync called, loading lists...");
|
||||
await LoadListsAsync();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(TemplateName) || string.IsNullOrWhiteSpace(Subject) || string.IsNullOrWhiteSpace(Body))
|
||||
{
|
||||
TempData["ErrorMessage"] = "Çàïîëíèòå âñå îáÿçàòåëüíûå ïîëÿ";
|
||||
await LoadListsAsync();
|
||||
return Page();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogInformation($"Creating template: {TemplateName}");
|
||||
|
||||
var templateId = await _listmonkService.CreateTemplateAsync(
|
||||
TemplateName,
|
||||
Subject,
|
||||
Body
|
||||
);
|
||||
|
||||
_logger.LogInformation($"Template created successfully with ID: {templateId}");
|
||||
TempData["SuccessMessage"] = $"Øàáëîí \"{TemplateName}\" óñïåøíî ñîçäàí!";
|
||||
|
||||
return RedirectToPage("/Campaigns/Index");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error creating template");
|
||||
TempData["ErrorMessage"] = $"Îøèáêà ïðè ñîçäàíèè øàáëîíà: {ex.Message}";
|
||||
await LoadListsAsync();
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadListsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Calling GetListsAsync...");
|
||||
var lists = await _listmonkService.GetListsAsync();
|
||||
_logger.LogInformation($"Got {lists.Count} lists from Listmonk");
|
||||
|
||||
foreach (var list in lists)
|
||||
{
|
||||
_logger.LogInformation($" List: ID={list.Id}, Name={list.Name}, Subscribers={list.SubscriberCount}");
|
||||
}
|
||||
|
||||
Lists = new SelectList(lists, "Id", "Name");
|
||||
DebugInfo = $"Çàãðóæåíî ñïèñêîâ: {lists.Count}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error loading lists");
|
||||
DebugInfo = $"Îøèáêà çàãðóçêè ñïèñêîâ: {ex.Message}";
|
||||
Lists = new SelectList(Enumerable.Empty<SelectListItem>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
@using ListmonkIntegration
|
||||
@namespace ListmonkIntegration.Pages
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@@ -0,0 +1,3 @@
|
||||
@{
|
||||
Layout = "_Layout";
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
@page
|
||||
@model ListmonkIntegration.Pages.api.ListsModel
|
||||
@{
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using ListmonkIntegration.Services;
|
||||
|
||||
namespace ListmonkIntegration.Pages.api
|
||||
{
|
||||
[IgnoreAntiforgeryToken]
|
||||
public class ListsModel : PageModel
|
||||
{
|
||||
private readonly IListmonkService _listmonkService;
|
||||
private readonly ILogger<ListsModel> _logger;
|
||||
|
||||
public ListsModel(IListmonkService listmonkService, ILogger<ListsModel> logger)
|
||||
{
|
||||
_listmonkService = listmonkService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnGetAttributesAsync(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation($"API: Getting attributes for list {id}");
|
||||
var attributes = await _listmonkService.GetListAttributesAsync(id);
|
||||
_logger.LogInformation($"API: Found {attributes.Count} attributes");
|
||||
return new JsonResult(attributes);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting attributes");
|
||||
return new JsonResult(new List<string>()) { StatusCode = 500 };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user