Initial commit

This commit is contained in:
Popov_Grigorii
2026-07-15 17:17:03 +03:00
commit cc078a2c8d
94 changed files with 76773 additions and 0 deletions
@@ -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;
}
}
}