Merge branch 'dev' into fix/trim-subsystem-operation-time-data

This commit is contained in:
on.nemtina 2023-07-17 16:03:13 +05:00
commit 68d2675d9a
44 changed files with 34461 additions and 50 deletions

View File

@ -13,5 +13,15 @@
/// </summary> /// </summary>
public string Caption { get; set; } = null!; public string Caption { get; set; } = null!;
/// <summary>
/// Порядок
/// </summary>
public int Order { get; set; }
/// <summary>
/// Является ли контактом
/// </summary>
public bool IsContact { get; set; }
} }
} }

View File

@ -0,0 +1,17 @@
namespace AsbCloudApp.Data;
/// <summary>
/// DTO категории уведомления
/// </summary>
public class NotificationCategoryDto : IId
{
/// <summary>
/// Id категории
/// </summary>
public int Id { get; set; }
/// <summary>
/// Название категории
/// </summary>
public string Name { get; set; } = null!;
}

View File

@ -0,0 +1,75 @@
using System;
namespace AsbCloudApp.Data;
/// <summary>
/// DTO уведомления
/// </summary>
public class NotificationDto : IId
{
/// <summary>
/// Id уведомления
/// </summary>
public int Id { get; set; }
/// <summary>
/// Id получателя уведомления
/// </summary>
public int IdUser { get; set; }
/// <summary>
/// Id категории уведомления
/// </summary>
public int IdNotificationCategory { get; set; }
/// <summary>
/// Заголовок уведомления
/// </summary>
public string Title { get; set; } = null!;
/// <summary>
/// Сообщение уведомления
/// </summary>
public string Message { get; set; } = null!;
/// <summary>
/// Дата отправки уведомления
/// </summary>
public DateTime? SentDate { get; set; }
/// <summary>
/// Дата прочтения уведомления
/// </summary>
public DateTime? ReadDate { get; set; }
/// <summary>
/// Состояние уведомления
/// 0 - Зарегистрировано,
/// 1 - Отправлено,
/// 2 - Прочитано
/// </summary>
public int IdState
{
get
{
if (SentDate is not null && ReadDate is not null)
return 2;
if (SentDate is not null)
return 1;
return 0;
}
}
/// <summary>
/// Id типа доставки уведомления
/// 0 - SignalR
/// </summary>
public int IdTransportType { get; set; }
/// <summary>
/// DTO категории уведомления
/// </summary>
public NotificationCategoryDto NotificationCategory { get; set; } = null!;
}

View File

@ -62,8 +62,11 @@ namespace AsbCloudApp.Data.User
/// <summary> /// <summary>
/// Id состояния пользователя /// Id состояния пользователя
/// 0 - не активен,
/// 1 - активен,
/// 2 - заблокирован
/// </summary> /// </summary>
public short? IdState { get; set; } public short IdState { get; set; }
/// <summary> /// <summary>
/// DTO компании /// DTO компании

View File

@ -0,0 +1,34 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
namespace AsbCloudApp.Repositories;
/// <summary>
/// Репозиторий для уведомлений
/// </summary>
public interface INotificationRepository : ICrudRepository<NotificationDto>
{
/// <summary>
/// Обновление изменённых уведомлений
/// </summary>
/// <param name="notifications"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<int> UpdateRangeAsync(IEnumerable<NotificationDto> notifications,
CancellationToken cancellationToken);
/// <summary>
/// Получение уведомлений по параметрам
/// </summary>
/// <param name="idUser"></param>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<PaginationContainer<NotificationDto>> GetNotificationsAsync(int idUser,
NotificationRequest request,
CancellationToken cancellationToken);
}

View File

@ -0,0 +1,17 @@
namespace AsbCloudApp.Requests;
/// <summary>
/// Параметры запроса для получения уведомлений
/// </summary>
public class NotificationRequest : RequestBase
{
/// <summary>
/// Получение отправленных/не отправленных уведомлений
/// </summary>
public bool? IsSent { get; set; } = false;
/// <summary>
/// Id типа доставки уведомления
/// </summary>
public int? IdTransportType { get; set; }
}

View File

@ -19,15 +19,15 @@ namespace AsbCloudApp.Requests
public DateTime? LtDate { get; set; } public DateTime? LtDate { get; set; }
/// <summary> /// <summary>
/// фильтр по максимальной глубине скважины /// фильтр. Больше или равно глубины скважины на начало операции.
/// </summary>
public double? LeDepth { get; set; }
/// <summary>
/// фильтр по минимальной глубине скважины
/// </summary> /// </summary>
public double? GeDepth { get; set; } public double? GeDepth { get; set; }
/// <summary>
/// фильтр. Меньше или равно глубины скважины на конец операции.
/// </summary>
public double? LeDepth { get; set; }
/// <summary> /// <summary>
/// фильтр по списку id категорий операции /// фильтр по списку id категорий операции
/// </summary> /// </summary>

View File

@ -22,9 +22,10 @@ namespace AsbCloudApp.Services
/// <summary> /// <summary>
/// Получение типов контаков /// Получение типов контаков
/// </summary> /// </summary>
/// <param name="idWell">ключ скважины</param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<IEnumerable<CompanyTypeDto>> GetTypesAsync(CancellationToken token); Task<IEnumerable<CompanyTypeDto>> GetTypesAsync(int idWell, CancellationToken token);
/// <summary> /// <summary>
/// Обновление контактов по ключу скважины, типу контакта и ключам пользователей /// Обновление контактов по ключу скважины, типу контакта и ключам пользователей

View File

@ -0,0 +1,35 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
namespace AsbCloudApp.Services.Notifications;
/// <summary>
/// Интерфейс для отправителя уведомлений
/// </summary>
public interface INotificationTransportService
{
/// <summary>
/// Id типа доставки уведомления
/// </summary>
int IdTransportType { get; }
/// <summary>
/// Отправка одного уведомлений
/// </summary>
/// <param name="notification"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task SendAsync(NotificationDto notification,
CancellationToken cancellationToken);
/// <summary>
/// Отправка нескольких уведомлений
/// </summary>
/// <param name="notifications"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task SendRangeAsync(IEnumerable<NotificationDto> notifications,
CancellationToken cancellationToken);
}

View File

@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
namespace AsbCloudApp.Services.Notifications;
/// <summary>
/// Сервис для работы с уведомлениями
/// </summary>
public class NotificationService
{
private readonly ICrudRepository<NotificationCategoryDto> notificationCategoryRepository;
private readonly INotificationRepository notificationRepository;
private readonly IEnumerable<INotificationTransportService> notificationTransportServices;
/// <summary>
/// Сервис для работы с уведомлениями
/// </summary>
/// <param name="notificationCategoryRepository"></param>
/// <param name="notificationRepository"></param>
/// <param name="notificationTransportServices"></param>
public NotificationService(ICrudRepository<NotificationCategoryDto> notificationCategoryRepository,
INotificationRepository notificationRepository,
IEnumerable<INotificationTransportService> notificationTransportServices)
{
this.notificationCategoryRepository = notificationCategoryRepository;
this.notificationRepository = notificationRepository;
this.notificationTransportServices = notificationTransportServices;
}
/// <summary>
/// Отправка нового уведомления
/// </summary>
/// <param name="idUser"></param>
/// <param name="idNotificationCategory"></param>
/// <param name="title"></param>
/// <param name="message"></param>
/// <param name="idTransportType"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task NotifyAsync(int idUser,
int idNotificationCategory,
string title,
string message,
int idTransportType,
CancellationToken cancellationToken)
{
var notificationCategory = await notificationCategoryRepository
.GetOrDefaultAsync(idNotificationCategory, cancellationToken)
?? throw new ArgumentInvalidException("Категория уведомления не найдена", nameof(idNotificationCategory));
var notification = new NotificationDto()
{
IdUser = idUser,
IdNotificationCategory = idNotificationCategory,
Title = title,
Message = message,
IdTransportType = idTransportType
};
notification.Id = await notificationRepository.InsertAsync(notification,
cancellationToken);
notification.NotificationCategory = notificationCategory;
var notificationTransportService = GetNotificationTransportService(idTransportType);
await notificationTransportService.SendAsync(notification, cancellationToken);
await notificationRepository.UpdateAsync(notification,
cancellationToken);
}
/// <summary>
/// Обновление уведомления
/// </summary>
/// <param name="idNotification"></param>
/// <param name="isRead"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task UpdateNotificationAsync(int idNotification,
bool isRead,
CancellationToken cancellationToken)
{
var notification = await notificationRepository.GetOrDefaultAsync(idNotification,
cancellationToken)
?? throw new ArgumentInvalidException("Уведомление не найдено", nameof(idNotification));
if (isRead)
{
if (notification.SentDate == null)
throw new ArgumentInvalidException("Уведомление не может быть прочитано", nameof(isRead));
notification.SentDate = DateTime.UtcNow;
}
await notificationRepository.UpdateAsync(notification,
cancellationToken);
}
/// <summary>
/// Отправка уведомлений, которые не были отправлены
/// </summary>
/// <param name="idUser"></param>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task ResendNotificationAsync(int idUser,
NotificationRequest request,
CancellationToken cancellationToken)
{
if (!request.IdTransportType.HasValue)
throw new ArgumentInvalidException("Id типа доставки уведомления должен иметь значение",
nameof(request.IdTransportType));
var result = await notificationRepository.GetNotificationsAsync(idUser,
request,
cancellationToken);
var notificationTransportService = GetNotificationTransportService(request.IdTransportType.Value);
await notificationTransportService.SendRangeAsync(result.Items,
cancellationToken);
await notificationRepository.UpdateRangeAsync(result.Items,
cancellationToken);
}
private INotificationTransportService GetNotificationTransportService(int idTransportType)
{
var notificationTransportService = notificationTransportServices
.FirstOrDefault(s => s.IdTransportType == idTransportType)
?? throw new ArgumentInvalidException("Доставщик уведомлений не найден", nameof(idTransportType));
return notificationTransportService;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,72 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
#nullable disable
namespace AsbCloudDb.Migrations
{
public partial class Update_IdState_For_User : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.UpdateData(
table: "t_user",
keyColumn: "state",
keyValue: null,
column: "state",
value: (short)1);
migrationBuilder.AlterColumn<short>(
name: "state",
table: "t_user",
type: "smallint",
nullable: false,
defaultValue: (short)0,
comment: "состояние:\n0 - не активен, \n1 - активен, \n2 - заблокирован",
oldClrType: typeof(short),
oldType: "smallint",
oldNullable: true,
oldComment: "состояние:\n100 - удален");
migrationBuilder.AlterColumn<int>(
name: "id_category",
table: "t_help_page",
type: "integer",
nullable: false,
comment: "Id категории файла",
oldClrType: typeof(int),
oldType: "integer",
oldComment: "id категории файла");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<short>(
name: "state",
table: "t_user",
type: "smallint",
nullable: true,
comment: "состояние:\n100 - удален",
oldClrType: typeof(short),
oldType: "smallint",
oldComment: "состояние:\n0 - не активен, \n1 - активен, \n2 - заблокирован");
migrationBuilder.AlterColumn<int>(
name: "id_category",
table: "t_help_page",
type: "integer",
nullable: false,
comment: "id категории файла",
oldClrType: typeof(int),
oldType: "integer",
oldComment: "Id категории файла");
migrationBuilder.UpdateData(
table: "t_user",
keyColumn: "id",
keyValue: 1,
column: "state",
value: null);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,85 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace AsbCloudDb.Migrations
{
public partial class Add_Notification : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "t_notification_category",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
name = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_t_notification_category", x => x.id);
},
comment: "Категории уведомлений");
migrationBuilder.CreateTable(
name: "t_notification",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
id_user = table.Column<int>(type: "integer", nullable: false, comment: "Id получателя"),
id_notification_category = table.Column<int>(type: "integer", nullable: false, comment: "Id категории уведомления"),
title = table.Column<string>(type: "text", nullable: false, comment: "Заголовок уведомления"),
message = table.Column<string>(type: "text", nullable: false, comment: "Сообщение уведомления"),
time_to_life = table.Column<TimeSpan>(type: "interval", nullable: false, comment: "Время жизни уведомления"),
sent_date = table.Column<DateTime>(type: "timestamp with time zone", nullable: true, comment: "Дата отправки уведомления"),
notification_state = table.Column<string>(type: "text", nullable: false, comment: "Состояние уведомления"),
notification_transport = table.Column<string>(type: "text", nullable: false, comment: "Метод доставки уведомления")
},
constraints: table =>
{
table.PrimaryKey("PK_t_notification", x => x.id);
table.ForeignKey(
name: "FK_t_notification_t_notification_category_id_notification_cate~",
column: x => x.id_notification_category,
principalTable: "t_notification_category",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_t_notification_t_user_id_user",
column: x => x.id_user,
principalTable: "t_user",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
},
comment: "Уведомления");
migrationBuilder.InsertData(
table: "t_notification_category",
columns: new[] { "id", "name" },
values: new object[] { 1, "Системные уведомления" });
migrationBuilder.CreateIndex(
name: "IX_t_notification_id_notification_category",
table: "t_notification",
column: "id_notification_category");
migrationBuilder.CreateIndex(
name: "IX_t_notification_id_user",
table: "t_notification",
column: "id_user");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "t_notification");
migrationBuilder.DropTable(
name: "t_notification_category");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,47 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace AsbCloudDb.Migrations
{
public partial class Add_Order_For_CompanyType : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "order",
table: "t_company_type",
type: "integer",
nullable: false,
defaultValue: 0);
migrationBuilder.UpdateData(
table: "t_company_type",
keyColumn: "id",
keyValue: 1,
column: "order",
value: 1);
migrationBuilder.UpdateData(
table: "t_company_type",
keyColumn: "id",
keyValue: 2,
column: "order",
value: 2);
migrationBuilder.UpdateData(
table: "t_company_type",
keyColumn: "id",
keyValue: 5,
column: "order",
value: 3);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "order",
table: "t_company_type");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,95 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace AsbCloudDb.Migrations
{
public partial class Update_Notification : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "notification_state",
table: "t_notification");
migrationBuilder.DropColumn(
name: "notification_transport",
table: "t_notification");
migrationBuilder.DropColumn(
name: "time_to_life",
table: "t_notification");
migrationBuilder.AddColumn<int>(
name: "id_transport_type",
table: "t_notification",
type: "integer",
nullable: false,
defaultValue: 0,
comment: "Id типа доставки уведомления");
migrationBuilder.AddColumn<DateTime>(
name: "read_date",
table: "t_notification",
type: "timestamp with time zone",
nullable: true,
comment: "Дата прочтения уведомления");
migrationBuilder.AlterColumn<int>(
name: "id_category",
table: "t_help_page",
type: "integer",
nullable: false,
comment: "Id категории файла",
oldClrType: typeof(int),
oldType: "integer",
oldComment: "id категории файла");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "id_transport_type",
table: "t_notification");
migrationBuilder.DropColumn(
name: "read_date",
table: "t_notification");
migrationBuilder.AddColumn<string>(
name: "notification_state",
table: "t_notification",
type: "text",
nullable: false,
defaultValue: "",
comment: "Состояние уведомления");
migrationBuilder.AddColumn<string>(
name: "notification_transport",
table: "t_notification",
type: "text",
nullable: false,
defaultValue: "",
comment: "Метод доставки уведомления");
migrationBuilder.AddColumn<TimeSpan>(
name: "time_to_life",
table: "t_notification",
type: "interval",
nullable: false,
defaultValue: new TimeSpan(0, 0, 0, 0, 0),
comment: "Время жизни уведомления");
migrationBuilder.AlterColumn<int>(
name: "id_category",
table: "t_help_page",
type: "integer",
nullable: false,
comment: "id категории файла",
oldClrType: typeof(int),
oldType: "integer",
oldComment: "Id категории файла");
}
}
}

View File

@ -122,6 +122,10 @@ namespace AsbCloudDb.Migrations
b.Property<bool>("IsContact") b.Property<bool>("IsContact")
.HasColumnType("boolean"); .HasColumnType("boolean");
b.Property<int>("Order")
.HasColumnType("integer")
.HasColumnName("order");
b.HasKey("Id"); b.HasKey("Id");
b.ToTable("t_company_type"); b.ToTable("t_company_type");
@ -131,19 +135,22 @@ namespace AsbCloudDb.Migrations
{ {
Id = 1, Id = 1,
Caption = "Недрапользователь", Caption = "Недрапользователь",
IsContact = false IsContact = false,
Order = 1
}, },
new new
{ {
Id = 2, Id = 2,
Caption = "Буровой подрядчик", Caption = "Буровой подрядчик",
IsContact = false IsContact = false,
Order = 2
}, },
new new
{ {
Id = 3, Id = 3,
Caption = "Сервис автоматизации бурения", Caption = "Сервис автоматизации бурения",
IsContact = false IsContact = false,
Order = 0
}); });
}); });
@ -989,7 +996,7 @@ namespace AsbCloudDb.Migrations
b.Property<int>("IdCategory") b.Property<int>("IdCategory")
.HasColumnType("integer") .HasColumnType("integer")
.HasColumnName("id_category") .HasColumnName("id_category")
.HasComment("id категории файла"); .HasComment("Id категории файла");
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
@ -1152,6 +1159,91 @@ namespace AsbCloudDb.Migrations
}); });
}); });
modelBuilder.Entity("AsbCloudDb.Model.Notification", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("IdNotificationCategory")
.HasColumnType("integer")
.HasColumnName("id_notification_category")
.HasComment("Id категории уведомления");
b.Property<int>("IdTransportType")
.HasColumnType("integer")
.HasColumnName("id_transport_type")
.HasComment("Id типа доставки уведомления");
b.Property<int>("IdUser")
.HasColumnType("integer")
.HasColumnName("id_user")
.HasComment("Id получателя");
b.Property<string>("Message")
.IsRequired()
.HasColumnType("text")
.HasColumnName("message")
.HasComment("Сообщение уведомления");
b.Property<DateTime?>("ReadDate")
.HasColumnType("timestamp with time zone")
.HasColumnName("read_date")
.HasComment("Дата прочтения уведомления");
b.Property<DateTime?>("SentDate")
.HasColumnType("timestamp with time zone")
.HasColumnName("sent_date")
.HasComment("Дата отправки уведомления");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("text")
.HasColumnName("title")
.HasComment("Заголовок уведомления");
b.HasKey("Id");
b.HasIndex("IdNotificationCategory");
b.HasIndex("IdUser");
b.ToTable("t_notification");
b.HasComment("Уведомления");
});
modelBuilder.Entity("AsbCloudDb.Model.NotificationCategory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text")
.HasColumnName("name");
b.HasKey("Id");
b.ToTable("t_notification_category");
b.HasComment("Категории уведомлений");
b.HasData(
new
{
Id = 1,
Name = "Системные уведомления"
});
});
modelBuilder.Entity("AsbCloudDb.Model.OperationValue", b => modelBuilder.Entity("AsbCloudDb.Model.OperationValue", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@ -4761,10 +4853,10 @@ namespace AsbCloudDb.Migrations
.HasColumnType("integer") .HasColumnType("integer")
.HasColumnName("id_company"); .HasColumnName("id_company");
b.Property<short?>("IdState") b.Property<short>("IdState")
.HasColumnType("smallint") .HasColumnType("smallint")
.HasColumnName("state") .HasColumnName("state")
.HasComment("состояние:\n100 - удален"); .HasComment("состояние:\n0 - не активен, \n1 - активен, \n2 - заблокирован");
b.Property<string>("Login") b.Property<string>("Login")
.IsRequired() .IsRequired()
@ -4826,6 +4918,7 @@ namespace AsbCloudDb.Migrations
Id = 1, Id = 1,
Email = "", Email = "",
IdCompany = 1, IdCompany = 1,
IdState = (short)1,
Login = "dev", Login = "dev",
Name = "Разработчик", Name = "Разработчик",
PasswordHash = "Vlcj|4fa529103dde7ff72cfe76185f344d4aa87931f8e1b2044e8a7739947c3d18923464eaad93843e4f809c5e126d013072" PasswordHash = "Vlcj|4fa529103dde7ff72cfe76185f344d4aa87931f8e1b2044e8a7739947c3d18923464eaad93843e4f809c5e126d013072"
@ -7559,6 +7652,25 @@ namespace AsbCloudDb.Migrations
b.Navigation("Well"); b.Navigation("Well");
}); });
modelBuilder.Entity("AsbCloudDb.Model.Notification", b =>
{
b.HasOne("AsbCloudDb.Model.NotificationCategory", "NotificationCategory")
.WithMany("Notifications")
.HasForeignKey("IdNotificationCategory")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("AsbCloudDb.Model.User", "User")
.WithMany()
.HasForeignKey("IdUser")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("NotificationCategory");
b.Navigation("User");
});
modelBuilder.Entity("AsbCloudDb.Model.OperationValue", b => modelBuilder.Entity("AsbCloudDb.Model.OperationValue", b =>
{ {
b.HasOne("AsbCloudDb.Model.WellOperationCategory", "OperationCategory") b.HasOne("AsbCloudDb.Model.WellOperationCategory", "OperationCategory")
@ -8151,6 +8263,11 @@ namespace AsbCloudDb.Migrations
b.Navigation("Measures"); b.Navigation("Measures");
}); });
modelBuilder.Entity("AsbCloudDb.Model.NotificationCategory", b =>
{
b.Navigation("Notifications");
});
modelBuilder.Entity("AsbCloudDb.Model.Permission", b => modelBuilder.Entity("AsbCloudDb.Model.Permission", b =>
{ {
b.Navigation("RelationUserRolePermissions"); b.Navigation("RelationUserRolePermissions");

View File

@ -74,8 +74,9 @@ namespace AsbCloudDb.Model
public static int ReferenceCount => referenceCount; public static int ReferenceCount => referenceCount;
public DbSet<Faq> Faqs => Set<Faq>(); public DbSet<Faq> Faqs => Set<Faq>();
public DbSet<HelpPage> HelpPages => Set<HelpPage>(); public DbSet<HelpPage> HelpPages => Set<HelpPage>();
public DbSet<Notification> Notifications => Set<Notification>();
public DbSet<NotificationCategory> NotificationCategories => Set<NotificationCategory>();
public AsbCloudDbContext() : base() public AsbCloudDbContext() : base()
{ {

View File

@ -16,6 +16,9 @@ namespace AsbCloudDb.Model
public string Caption { get; set; } = null!; public string Caption { get; set; } = null!;
public bool IsContact { get; set; } public bool IsContact { get; set; }
[Column("order")]
public int Order { get; set; }
[InverseProperty(nameof(Company.CompanyType))] [InverseProperty(nameof(Company.CompanyType))]
public virtual ICollection<Company> Companies { get; set; } = null!; public virtual ICollection<Company> Companies { get; set; } = null!;
} }

View File

@ -24,7 +24,8 @@ namespace AsbCloudDb.Model.DefaultData
{ typeof(WellType), new EntityFillerWellType()}, { typeof(WellType), new EntityFillerWellType()},
{ typeof(MeasureCategory), new EntityFillerMeasureCategory()}, { typeof(MeasureCategory), new EntityFillerMeasureCategory()},
{ typeof(CompanyType), new EntityFillerCompanyType()}, { typeof(CompanyType), new EntityFillerCompanyType()},
{ typeof(AsbCloudDb.Model.Subsystems.Subsystem), new EntityFillerSubsystem() }, { typeof(Subsystems.Subsystem), new EntityFillerSubsystem() },
{ typeof(NotificationCategory), new EntityNotificationCategory()},
}; };
return fillers; return fillers;
} }

View File

@ -3,9 +3,9 @@
internal class EntityFillerCompanyType : EntityFiller<CompanyType> internal class EntityFillerCompanyType : EntityFiller<CompanyType>
{ {
public override CompanyType[] GetData() => new CompanyType[] { public override CompanyType[] GetData() => new CompanyType[] {
new (){ Id = 1, Caption = "Недрапользователь", }, new (){ Id = 1, Caption = "Недрапользователь", Order = 1 },
new (){ Id = 2, Caption = "Буровой подрядчик", }, new (){ Id = 2, Caption = "Буровой подрядчик", Order = 2 },
new (){ Id = 3, Caption = "Сервис автоматизации бурения", }, new (){ Id = 3, Caption = "Сервис автоматизации бурения", Order = 0 }
}; };
} }
} }

View File

@ -9,6 +9,7 @@
Login = "dev", Login = "dev",
PasswordHash = "Vlcj|4fa529103dde7ff72cfe76185f344d4aa87931f8e1b2044e8a7739947c3d18923464eaad93843e4f809c5e126d013072", PasswordHash = "Vlcj|4fa529103dde7ff72cfe76185f344d4aa87931f8e1b2044e8a7739947c3d18923464eaad93843e4f809c5e126d013072",
Name = "Разработчик", Name = "Разработчик",
IdState = 1,
}, },
}; };
} }

View File

@ -0,0 +1,9 @@
namespace AsbCloudDb.Model.DefaultData;
public class EntityNotificationCategory : EntityFiller<NotificationCategory>
{
public override NotificationCategory[] GetData() => new NotificationCategory[]
{
new() { Id = 1, Name = "Системные уведомления" }
};
}

View File

@ -68,7 +68,8 @@ namespace AsbCloudDb.Model
DbSet<Record60> Record60 { get; } DbSet<Record60> Record60 { get; }
DbSet<Record61> Record61 { get; } DbSet<Record61> Record61 { get; }
DbSet<HelpPage> HelpPages { get; } DbSet<HelpPage> HelpPages { get; }
DbSet<Notification> Notifications { get; }
DbSet<NotificationCategory> NotificationCategories { get; }
DatabaseFacade Database { get; } DatabaseFacade Database { get; }
Task<int> RefreshMaterializedViewAsync(string mwName, CancellationToken token); Task<int> RefreshMaterializedViewAsync(string mwName, CancellationToken token);

View File

@ -0,0 +1,41 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
namespace AsbCloudDb.Model;
[Table("t_notification"), Comment("Уведомления")]
public class Notification : IId
{
[Key]
[Column("id")]
public int Id { get; set; }
[Column("id_user"), Comment("Id получателя")]
public int IdUser { get; set; }
[Column("id_notification_category"), Comment("Id категории уведомления")]
public int IdNotificationCategory { get; set; }
[Column("title"), Comment("Заголовок уведомления")]
public string Title { get; set; } = null!;
[Column("message"), Comment("Сообщение уведомления")]
public string Message { get; set; } = null!;
[Column("sent_date"), Comment("Дата отправки уведомления")]
public DateTime? SentDate { get; set; }
[Column("read_date"), Comment("Дата прочтения уведомления")]
public DateTime? ReadDate { get; set; }
[Column("id_transport_type"), Comment("Id типа доставки уведомления")]
public int IdTransportType { get; set; }
[ForeignKey(nameof(IdNotificationCategory))]
public virtual NotificationCategory NotificationCategory { get; set; } = null!;
[ForeignKey(nameof(IdUser))]
public virtual User User { get; set; } = null!;
}

View File

@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
namespace AsbCloudDb.Model;
[Table("t_notification_category"), Comment("Категории уведомлений")]
public class NotificationCategory : IId
{
[Key]
[Column("id")]
public int Id { get; set; }
[Column("name")]
public string Name { get; set; } = null!;
[InverseProperty(nameof(Notification.NotificationCategory))]
public virtual ICollection<Notification> Notifications { get; set; } = null!;
}

View File

@ -8,6 +8,8 @@ namespace AsbCloudDb.Model
[Table("t_user"), Comment("Пользователи облака")] [Table("t_user"), Comment("Пользователи облака")]
public partial class User : IId public partial class User : IId
{ {
public const int ActiveStateId = 1;
[Key] [Key]
[Column("id")] [Column("id")]
public int Id { get; set; } public int Id { get; set; }
@ -23,8 +25,8 @@ namespace AsbCloudDb.Model
[StringLength(255)] [StringLength(255)]
public string PasswordHash { get; set; } = null!; public string PasswordHash { get; set; } = null!;
[Column("state"), Comment("состояние:\n100 - удален")] [Column("state"), Comment("состояние:\n0 - не активен, \n1 - активен, \n2 - заблокирован")]
public short? IdState { get; set; } public short IdState { get; set; }
[Column("name"), Comment("имя")] [Column("name"), Comment("имя")]
[StringLength(255)] [StringLength(255)]

View File

@ -23,6 +23,7 @@ using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using System; using System;
using AsbCloudApp.Services.Notifications;
namespace AsbCloudInfrastructure namespace AsbCloudInfrastructure
{ {
@ -82,6 +83,11 @@ namespace AsbCloudInfrastructure
TypeAdapterConfig.GlobalSettings.Default.Config TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<WellFinalDocumentDto, WellFinalDocument>(); .ForType<WellFinalDocumentDto, WellFinalDocument>();
TypeAdapterConfig.GlobalSettings.Default.Config
.ForType<NotificationDto, Notification>()
.Ignore(dst => dst.NotificationCategory,
dst => dst.User);
} }
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration) public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
@ -143,6 +149,11 @@ namespace AsbCloudInfrastructure
services.AddTransient<IGtrRepository, GtrWitsRepository>(); services.AddTransient<IGtrRepository, GtrWitsRepository>();
services.AddTransient<NotificationService>();
services.AddTransient<INotificationRepository, NotificationRepository>();
services.AddTransient<ICrudRepository<NotificationCategoryDto>, CrudCacheRepositoryBase<NotificationCategoryDto,
NotificationCategory>>();
// admin crud services: // admin crud services:
services.AddTransient<ICrudRepository<TelemetryDto>, CrudCacheRepositoryBase<TelemetryDto, Telemetry>>(s => services.AddTransient<ICrudRepository<TelemetryDto>, CrudCacheRepositoryBase<TelemetryDto, Telemetry>>(s =>
new CrudCacheRepositoryBase<TelemetryDto, Telemetry>( new CrudCacheRepositoryBase<TelemetryDto, Telemetry>(

View File

@ -0,0 +1,105 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudDb;
using AsbCloudDb.Model;
using Mapster;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
namespace AsbCloudInfrastructure.Repository;
public class NotificationRepository : CrudCacheRepositoryBase<NotificationDto, Notification>, INotificationRepository
{
private static IQueryable<Notification> MakeQueryNotification(DbSet<Notification> dbSet)
=> dbSet.Include(n => n.NotificationCategory)
.AsNoTracking();
public NotificationRepository(IAsbCloudDbContext dbContext, IMemoryCache memoryCache)
: base(dbContext, memoryCache, MakeQueryNotification)
{
}
public async Task<int> UpdateRangeAsync(IEnumerable<NotificationDto> notifications, CancellationToken cancellationToken)
{
if (!notifications.Any())
return 0;
var ids = notifications.Select(d => d.Id).ToArray();
var existingEntities = await dbSet
.Where(d => ids.Contains(d.Id))
.AsNoTracking()
.Select(d => d.Id)
.ToArrayAsync(cancellationToken);
if (ids.Length > existingEntities.Length)
return ICrudRepository<SetpointsRequestDto>.ErrorIdNotFound;
var entities = notifications.Select(Convert);
dbContext.Notifications.UpdateRange(entities);
var result = await dbContext.SaveChangesAsync(cancellationToken);
DropCache();
return result;
}
public async Task<PaginationContainer<NotificationDto>> GetNotificationsAsync(int idUser,
NotificationRequest request,
CancellationToken cancellationToken)
{
var skip = request.Skip ?? 0;
var take = request.Take ?? 10;
var query = BuildQuery(idUser, request);
var result = new PaginationContainer<NotificationDto>()
{
Skip = skip,
Take = take,
Count = await query.CountAsync(cancellationToken),
};
if (result.Count < skip)
return result;
result.Items = await query
.SortBy(request.SortFields)
.Skip(skip)
.Take(take)
.AsNoTracking()
.Select(x => x.Adapt<NotificationDto>())
.ToListAsync(cancellationToken);
return result;
}
private IQueryable<Notification> BuildQuery(int idUser,
NotificationRequest request)
{
var query = dbContext.Notifications
.Include(x => x.NotificationCategory)
.Where(n => n.IdUser == idUser);
if (request.IsSent.HasValue)
{
if(request.IsSent.Value)
query = query.Where(n => n.SentDate != null);
else
query = query.Where(n => n.SentDate == null);
}
if (request.IdTransportType.HasValue)
query = query.Where(n => n.IdTransportType == request.IdTransportType);
return query;
}
}

View File

@ -50,9 +50,12 @@ namespace AsbCloudInfrastructure.Services
return entities; return entities;
} }
public async Task<IEnumerable<CompanyTypeDto>> GetTypesAsync(CancellationToken token) public async Task<IEnumerable<CompanyTypeDto>> GetTypesAsync(int idWell, CancellationToken token)
{ {
var query = db.CompaniesTypes.Where(t => t.IsContact); var query = db.CompaniesTypes
.Where(t => t.IsContact)
.Where(t => t.Companies.Any(c => c.Users.Any() && c.RelationCompaniesWells.Any(w => w.IdWell == idWell)))
.OrderBy(t => t.Order);
var entities = await query.AsNoTracking() var entities = await query.AsNoTracking()
.ToArrayAsync(token) .ToArrayAsync(token)

View File

@ -92,6 +92,6 @@ public class HelpPageController : ControllerBase
cancellationToken); cancellationToken);
memoryStream.Position = 0; memoryStream.Position = 0;
return File(memoryStream, "application/octet-stream", file.fileName); return File(memoryStream, "application/pdf", file.fileName);
} }
} }

View File

@ -0,0 +1,148 @@
using System.ComponentModel.DataAnnotations;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services.Notifications;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace AsbCloudWebApi.Controllers;
/// <summary>
/// Уведомления
/// </summary>
[ApiController]
[Authorize]
[Route("api/notification")]
public class NotificationController : ControllerBase
{
private readonly NotificationService notificationService;
private readonly INotificationRepository notificationRepository;
public NotificationController(NotificationService notificationService,
INotificationRepository notificationRepository)
{
this.notificationService = notificationService;
this.notificationRepository = notificationRepository;
}
/// <summary>
/// Отправка уведомления
/// </summary>
/// <param name="idUser">Id пользователя</param>
/// <param name="idNotificationCategory">Id категории уведомления. Допустимое значение параметра: 1</param>
/// <param name="title">Заголовок уведомления</param>
/// <param name="message">Сообщение уведомления</param>
/// <param name="idNotificationTransport">Id типа доставки уведомления. Допустимое значение: 0</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[HttpPost]
[Route("send")]
public async Task<IActionResult> SendAsync([Required] int idUser,
[Required]
[Range(minimum: 1, maximum: 1, ErrorMessage = "Id категории уведомления недоступно. Допустимые: 1")]
int idNotificationCategory,
[Required] string title,
[Required] string message,
[Required]
[Range(minimum: 0, maximum: 0, ErrorMessage = "Id способа отправки уведомления недоступно. Допустимые: 0")]
int idNotificationTransport,
CancellationToken cancellationToken)
{
await notificationService.NotifyAsync(idUser,
idNotificationCategory,
title,
message,
idNotificationTransport,
cancellationToken);
return Ok();
}
/// <summary>
/// Обновление уведомления
/// </summary>
/// <param name="idNotification">Id уведомления</param>
/// <param name="isRead">Прочитано ли уведомление</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[HttpPut]
[Route("update")]
public async Task<IActionResult> UpdateAsync([Required] int idNotification,
[Required] bool isRead,
CancellationToken cancellationToken)
{
await notificationService.UpdateNotificationAsync(idNotification,
isRead,
cancellationToken);
return Ok();
}
/// <summary>
/// Получение уведомления по Id
/// </summary>
/// <param name="idNotification">Id уведомления</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[HttpGet]
[Route("get/{idNotification}")]
[ProducesResponseType(typeof(NotificationDto), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> GetAsync([Required] int idNotification,
CancellationToken cancellationToken)
{
var notification = await notificationRepository.GetOrDefaultAsync(idNotification, cancellationToken);
if (notification is null)
{
return BadRequest(ArgumentInvalidException.MakeValidationError(nameof(idNotification),
"Уведомление не найдено"));
}
return Ok(notification);
}
/// <summary>
/// Получение списка уведомлений
/// </summary>
/// <param name="request">Параметры запроса</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[HttpGet]
[Route("getList")]
[ProducesResponseType(typeof(PaginationContainer<NotificationDto>), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> GetListAsync([FromQuery] NotificationRequest request,
CancellationToken cancellationToken)
{
int? idUser = User.GetUserId();
if (!idUser.HasValue)
return Forbid();
var result = await notificationRepository.GetNotificationsAsync(idUser.Value,
request,
cancellationToken);
return Ok(result);
}
/// <summary>
/// Удаление уведомления
/// </summary>
/// <param name="idNotification">Id уведомления</param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[HttpDelete]
[Route("delete")]
public async Task<IActionResult> DeleteAsync([Required] int idNotification,
CancellationToken cancellationToken)
{
await notificationRepository.DeleteAsync(idNotification,
cancellationToken);
return Ok();
}
}

View File

@ -29,13 +29,14 @@ namespace AsbCloudWebApi.Controllers
/// <summary> /// <summary>
/// получение списка типов контактов /// получение списка типов контактов
/// </summary> /// </summary>
/// <param name="idWell">ключ скважины</param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
[HttpGet("api/contacts/types")] [HttpGet("api/well/{idWell}/contacts/types")]
[ProducesResponseType(typeof(IEnumerable<CompanyTypeDto>), (int)System.Net.HttpStatusCode.OK)] [ProducesResponseType(typeof(IEnumerable<CompanyTypeDto>), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> GetTypesAsync(CancellationToken token) public async Task<IActionResult> GetTypesAsync(int idWell, CancellationToken token)
{ {
var result = await wellContactsRepository.GetTypesAsync(token).ConfigureAwait(false); var result = await wellContactsRepository.GetTypesAsync(idWell, token).ConfigureAwait(false);
return Ok(result); return Ok(result);
} }

View File

@ -1,6 +1,9 @@
using AsbCloudApp.Data.GTR; using AsbCloudApp.Data.GTR;
using AsbCloudApp.Repositories;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services; using AsbCloudInfrastructure.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
@ -9,6 +12,9 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsbCloudApp.Services.Notifications;
using AsbCloudWebApi.SignalR.Services;
using Microsoft.OpenApi.Any;
namespace AsbCloudWebApi namespace AsbCloudWebApi
{ {
@ -18,8 +24,10 @@ namespace AsbCloudWebApi
{ {
services.AddSwaggerGen(c => services.AddSwaggerGen(c =>
{ {
c.MapType<TimeSpan>(() => new OpenApiSchema { Type = "string", Example = new OpenApiString("0.00:00:00") });
c.MapType<DateOnly>(() => new OpenApiSchema { Type = "string", Format = "date" }); c.MapType<DateOnly>(() => new OpenApiSchema { Type = "string", Format = "date" });
c.MapType<JsonValue>(() => new OpenApiSchema { c.MapType<JsonValue>(() => new OpenApiSchema
{
AnyOf = new OpenApiSchema[] AnyOf = new OpenApiSchema[]
{ {
new OpenApiSchema {Type = "string", Format = "string" }, new OpenApiSchema {Type = "string", Format = "string" },
@ -98,10 +106,39 @@ namespace AsbCloudWebApi
context.Token = accessToken; context.Token = accessToken;
} }
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
var idUser = context.Principal?.GetUserId();
if (idUser is null)
{
context.Fail("idUser is null");
return Task.CompletedTask;
}
context.HttpContext.RequestServices.GetRequiredService<IUserRepository>();
var userService = services.BuildServiceProvider().GetRequiredService<IUserRepository>();
var user = userService.GetOrDefault(idUser.Value);
if (user is null)
{
context.Fail("user is null");
}
else if (user.IdState != User.ActiveStateId)
{
context.Fail("user is not active");
}
return Task.CompletedTask; return Task.CompletedTask;
} }
}; };
}); });
} }
public static void AddNotificationTransportServices(this IServiceCollection services)
{
services.AddTransient<INotificationTransportService, SignalRNotificationTransportService>();
}
} }
} }

View File

@ -0,0 +1,19 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
namespace AsbCloudWebApi.SignalR;
public abstract class BaseHub : Hub
{
public virtual Task AddToGroup(string groupName) =>
Groups.AddToGroupAsync(Context.ConnectionId, groupName);
public virtual Task RemoveFromGroup(string groupName) =>
Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
}
public abstract class BaseHub<T> : BaseHub
where T : class
{
}

View File

@ -0,0 +1,53 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Requests;
using AsbCloudApp.Services.Notifications;
using AsbCloudWebApi.SignalR.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace AsbCloudWebApi.SignalR;
[Authorize]
public class NotificationHub : BaseHub
{
private readonly ConnectionManagerService connectionManagerService;
private readonly NotificationService notificationService;
public NotificationHub(ConnectionManagerService connectionManagerService,
NotificationService notificationService)
{
this.connectionManagerService = connectionManagerService;
this.notificationService = notificationService;
}
public override async Task OnConnectedAsync()
{
var idUser = Context.User?.GetUserId();
if (!idUser.HasValue)
return;
string connectionId = Context.ConnectionId;
connectionManagerService.AddOrUpdateConnection(idUser.Value, connectionId);
await base.OnConnectedAsync();
await notificationService.ResendNotificationAsync(idUser.Value,
new NotificationRequest { IsSent = false, IdTransportType = 0},
CancellationToken.None);
}
public override async Task OnDisconnectedAsync(Exception? exception)
{
var idUser = Context.User?.GetUserId();
if (!idUser.HasValue)
return;
connectionManagerService.RemoveConnection(idUser.Value);
await base.OnDisconnectedAsync(exception);
}
}

View File

@ -1,6 +1,4 @@
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;
namespace AsbCloudWebApi.SignalR namespace AsbCloudWebApi.SignalR
{ {
@ -8,12 +6,8 @@ namespace AsbCloudWebApi.SignalR
// https://docs.microsoft.com/ru-ru/aspnet/core/signalr/introduction?view=aspnetcore-5.0 // https://docs.microsoft.com/ru-ru/aspnet/core/signalr/introduction?view=aspnetcore-5.0
[Authorize] [Authorize]
public class ReportsHub : Hub<IReportHubClient> public class ReportsHub : BaseHub<IReportHubClient>
{ {
public Task AddToGroup(string groupName)
=> Groups.AddToGroupAsync(Context.ConnectionId, groupName);
public Task RemoveFromGroup(string groupName)
=> Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
} }
} }

View File

@ -0,0 +1,25 @@
using System.Collections.Concurrent;
namespace AsbCloudWebApi.SignalR.Services;
public class ConnectionManagerService
{
private readonly ConcurrentDictionary<int, string> connections = new();
public void AddOrUpdateConnection(int userId, string connectionId)
{
connections.AddOrUpdate(userId, connectionId,
(key, existingConnectionId) => connectionId);
}
public void RemoveConnection(int userId)
{
connections.TryRemove(userId, out _);
}
public string? GetConnectionIdByUserId(int userId)
{
connections.TryGetValue(userId, out var connectionId);
return connectionId;
}
}

View File

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Services.Notifications;
using Microsoft.AspNetCore.SignalR;
namespace AsbCloudWebApi.SignalR.Services;
public class SignalRNotificationTransportService : INotificationTransportService
{
private readonly ConnectionManagerService connectionManagerService;
private readonly IHubContext<NotificationHub> notificationHubContext;
public SignalRNotificationTransportService(ConnectionManagerService connectionManagerService,
IHubContext<NotificationHub> notificationHubContext)
{
this.connectionManagerService = connectionManagerService;
this.notificationHubContext = notificationHubContext;
}
public int IdTransportType => 0;
public async Task SendAsync(NotificationDto notification,
CancellationToken cancellationToken)
{
const string method = "receiveNotifications";
var connectionId = connectionManagerService.GetConnectionIdByUserId(notification.IdUser);
if (!string.IsNullOrWhiteSpace(connectionId))
{
notification.SentDate = DateTime.UtcNow;
await notificationHubContext.Clients.Client(connectionId)
.SendAsync(method,
notification,
cancellationToken);
}
}
public Task SendRangeAsync(IEnumerable<NotificationDto> notifications,
CancellationToken cancellationToken)
{
var tasks = notifications
.Select(notification => SendAsync(notification, cancellationToken));
return Task.WhenAll(tasks);
}
}

View File

@ -1,6 +1,4 @@
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;
namespace AsbCloudWebApi.SignalR namespace AsbCloudWebApi.SignalR
{ {
@ -8,12 +6,8 @@ namespace AsbCloudWebApi.SignalR
// https://docs.microsoft.com/ru-ru/aspnet/core/signalr/introduction?view=aspnetcore-5.0 // https://docs.microsoft.com/ru-ru/aspnet/core/signalr/introduction?view=aspnetcore-5.0
[Authorize] [Authorize]
public class TelemetryHub : Hub public class TelemetryHub : BaseHub
{ {
public Task AddToGroup(string groupName)
=> Groups.AddToGroupAsync(Context.ConnectionId, groupName.ToString());
public Task RemoveFromGroup(string groupName)
=> Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
} }
} }

View File

@ -2,6 +2,7 @@ using AsbCloudInfrastructure;
using AsbCloudWebApi.Converters; using AsbCloudWebApi.Converters;
using AsbCloudWebApi.Middlewares; using AsbCloudWebApi.Middlewares;
using AsbCloudWebApi.SignalR; using AsbCloudWebApi.SignalR;
using AsbCloudWebApi.SignalR.Services;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
@ -42,9 +43,12 @@ namespace AsbCloudWebApi
services.AddInfrastructure(Configuration); services.AddInfrastructure(Configuration);
services.AddNotificationTransportServices();
services.AddJWTAuthentication(); services.AddJWTAuthentication();
services.AddSignalR(); services.AddSignalR()
.Services.AddSingleton<ConnectionManagerService>();
services.AddCors(options => services.AddCors(options =>
{ {
@ -147,6 +151,7 @@ namespace AsbCloudWebApi
app.UseEndpoints(endpoints => app.UseEndpoints(endpoints =>
{ {
endpoints.MapControllers(); endpoints.MapControllers();
endpoints.MapHub<NotificationHub>("/hubs/notifications");
endpoints.MapHub<TelemetryHub>("/hubs/telemetry"); endpoints.MapHub<TelemetryHub>("/hubs/telemetry");
endpoints.MapHub<ReportsHub>("/hubs/reports"); endpoints.MapHub<ReportsHub>("/hubs/reports");
}); });

View File

@ -10,7 +10,7 @@ internal class Program
{ {
var connectionBuilder = new HubConnectionBuilder(); var connectionBuilder = new HubConnectionBuilder();
var connection = connectionBuilder var connection = connectionBuilder
.WithUrl("http://test.digitaldrilling.ru/hubs/telemetry", connectionOptions => { .WithUrl("http://localhost:5000/hubs/notifications", connectionOptions => {
connectionOptions.AccessTokenProvider = AccessTokenProvider; connectionOptions.AccessTokenProvider = AccessTokenProvider;
}) })
.WithAutomaticReconnect() .WithAutomaticReconnect()
@ -25,9 +25,10 @@ internal class Program
Console.WriteLine("connecting"); Console.WriteLine("connecting");
connection.StartAsync().Wait(); connection.StartAsync().Wait();
Console.WriteLine("AddToGroup"); //Console.WriteLine("OnConnected");
connection.SendCoreAsync("AddToGroup", new object[] { "well_1" }).Wait(); //connection.SendCoreAsync("OnConnected", new object[] { }, CancellationToken.None).Wait();
var subsction = connection.On<object>("UpdateProcessMap", (str1) => {
var subsction = connection.On<object>("receiveNotifications", (str1) => {
Console.WriteLine(str1); Console.WriteLine(str1);
} ); } );