feat(api): AgentTask, AgentHistory

This commit is contained in:
Mikhail Trubnikov
2023-10-19 11:22:35 +10:00
parent 5a40e5c1c1
commit b3b3cc8e0d
21 changed files with 3031 additions and 3 deletions

View File

@@ -1,4 +1,6 @@
namespace PARR.API.Contracts.V1 using System.Runtime.InteropServices;
namespace PARR.API.Contracts.V1
{ {
// https://tproger.ru/translations/luchshie-praktiki-razrabotki-rest-api-20-sovetov/ // https://tproger.ru/translations/luchshie-praktiki-razrabotki-rest-api-20-sovetov/
@@ -112,6 +114,15 @@
public const string taskId = "{taskId}"; public const string taskId = "{taskId}";
} }
public static class AgentHistory
{
public const string GetAll = Base + "/agent-histories/";
public const string Get = Base + "/agent-histories/" + getParam;
public const string Create = Base + "/agent-histories/";
public const string getParam = "{id}";
}
//public static class Layer //public static class Layer
//{ //{

View File

@@ -0,0 +1,22 @@
using PARR.DAL.Contracts;
namespace PARR.API.Contracts.V1.Requests
{
public class AgentHistoryRequest
{
/// <summary>
/// ИД шаблона
/// </summary>
public Guid TemplateId { get; set; }
/// <summary>
/// Сообщение
/// </summary>
public string? Message { get; set; }
/// <summary>
/// Уровень истории (1 - Start, 5 - End)
/// </summary>
public AgentHistoryLevelEnum Level { get; set; }
}
}

View File

@@ -0,0 +1,10 @@
namespace PARR.API.Contracts.V1.Requests.Queries
{
public class AgentHistoryQuery
{
/// <summary>
/// Ид шаблона
/// </summary>
public Guid? TemplateId { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
namespace PARR.API.Contracts.V1.Responses
{
public class AgentHistoryLevelResponse
{
public int Id { get; set; }
public required string Name { get; set; }
public required string Description { get; set; }
}
}

View File

@@ -0,0 +1,15 @@
namespace PARR.API.Contracts.V1.Responses
{
public class AgentHistoryResponse
{
public Guid Id { get; set; }
public DateTimeOffset Date { get; set; }
public string? Message { get; set; }
public Guid TemplateId { get; set; }
public AgentHistoryLevelResponse? Level { get; set; }
}
}

View File

@@ -16,5 +16,7 @@
public required string Script { get; set; } public required string Script { get; set; }
public required string Name { get; set; } public required string Name { get; set; }
public Guid TemplateId { get; set; }
} }
} }

View File

@@ -0,0 +1,121 @@
using AutoMapper;
using FluentValidation;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using PARR.API.Contracts.V1;
using PARR.API.Contracts.V1.Requests;
using PARR.API.Contracts.V1.Requests.Queries;
using PARR.API.Contracts.V1.Responses;
using PARR.API.Contracts.V1.Responses.Base;
using PARR.API.Controllers.V1.Base;
using PARR.API.Extensions;
using PARR.API.Services.Interfaces;
using PARR.DAL.DomainModels;
using PARR.DAL.Models;
using PARR.DAL.Services.Interfaces;
namespace PARR.API.Controllers.V1
{
/// <summary>
/// История работы агента
/// </summary>
public class AgentHistoryController : BaseApiController
{
private readonly IValidator<AgentHistoryRequest> validator;
private readonly IAgentHistoryService agentHistoryService;
private readonly IUriService uriService;
private readonly IMapper mapper;
public AgentHistoryController(
IValidator<AgentHistoryRequest> validator,
IAgentHistoryService agentHistoryService,
IUriService uriService,
IMapper mapper
)
{
this.validator = validator;
this.agentHistoryService = agentHistoryService;
this.uriService = uriService;
this.mapper = mapper;
}
/// <summary>
/// Получить историю работы агента постранично
/// </summary>
/// <returns></returns>
[HttpGet(ApiRoutes.AgentHistory.GetAll)]
public async Task<IActionResult> GetAll([FromQuery] PaginationQuery paginationQuery, [FromQuery] AgentHistoryQuery request)
{
var paginationFilter = mapper.Map<PaginationFilter>(paginationQuery);
IQueryable<AgentHistory> query = agentHistoryService.Get()
.Include(t => t.AgentHistoryLevel);
if (request.TemplateId.HasValue)
query = query.Where(t => t.TemplateId == request.TemplateId);
var history = await agentHistoryService.GetPage(query.OrderByDescending(t => t.DateCreated), paginationFilter).ToListAsync();
if (!history.Any())
return NoContent();
var response = mapper.Map<List<AgentHistoryResponse>>(history);
var paginationResponse = new PagedResponse<AgentHistoryResponse>(response, true).GetPaginatedProps(paginationFilter, query);
return Ok(paginationResponse);
}
/// <summary>
/// Получить запись истории работы агента по id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet(ApiRoutes.AgentHistory.Get)]
public async Task<IActionResult> GetById([FromRoute] Guid id)
{
var history = await agentHistoryService.Get().Include(t => t.AgentHistoryLevel).FirstAsync(t => t.Id == id);
if (history == null)
return NotFound();
var response = mapper.Map<AgentHistoryResponse>(history);
return Ok(new Response<AgentHistoryResponse>(response, true));
}
/// <summary>
/// Добавить запись в историю работы агента
/// </summary>
/// <returns></returns>
[HttpPost(ApiRoutes.AgentHistory.Create)]
public async Task<IActionResult> Create([FromBody] AgentHistoryRequest request)
{
var resultValidate = await validator.ValidateAsync(request);
if (!resultValidate.IsValid)
return BadRequest(new Response(resultValidate.Errors));
//todo: проверять что этот этот шаблон привязан к этому серверу по ip???
var agentJournal = new AgentHistory
{
Id = Guid.NewGuid(),
Message = request.Message,
HistoryLevelId = (int)request.Level,
TemplateId = request.TemplateId
};
if (!await agentHistoryService.CreateAsync(agentJournal) || !await agentHistoryService.CommitAsync())
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Ошибка при добавлении записи в историю работы агента." } }));
var createdObject = await agentHistoryService.Get().Include(t => t.AgentHistoryLevel).FirstAsync(t => t.Id == agentJournal.Id);
var response = mapper.Map<AgentHistoryResponse>(createdObject);
var createdUri = uriService.GetUri(ApiRoutes.AgentHistory.Get, ApiRoutes.AgentHistory.getParam, agentJournal.Id);
return Created(createdUri, new Response<AgentHistoryResponse>(response, true));
}
}
}

View File

@@ -98,7 +98,13 @@ namespace PARR.API.Controllers.V1
continue; continue;
} }
templateSchedule.ForEach(item => response.Scheduled.Add(new AgentTaskMinScheduleResponse { Name = template.ApplicationsInWork!.AgentName, Script = template.ApplicationsInWork!.AgentScript, StartAt = item })); templateSchedule.ForEach(item => response.Scheduled.Add(new AgentTaskMinScheduleResponse
{
Name = template.ApplicationsInWork!.AgentName,
Script = template.ApplicationsInWork!.AgentScript,
StartAt = item,
TemplateId = template.Id
}));
} }
if (!response.Scheduled.Any()) if (!response.Scheduled.Any())

View File

@@ -142,6 +142,12 @@ namespace PARR.API.MappingProfiles
.ForMember(d => d.Type, o => o.MapFrom(s => s.Type)); .ForMember(d => d.Type, o => o.MapFrom(s => s.Type));
// === EsppSch === // === EsppSch ===
CreateMap<AgentHistoryLevel, AgentHistoryLevelResponse>();
CreateMap<AgentHistory, AgentHistoryResponse>()
.ForMember(t => t.Date, o => o.MapFrom(s => s.DateCreated))
.ForMember(t => t.Level, o => o.MapFrom(s => s.AgentHistoryLevel));
} }
} }
} }

View File

@@ -0,0 +1,18 @@
using FluentValidation;
using PARR.API.Contracts.V1.Requests;
using PARR.DAL.Contracts;
namespace PARR.API.Validators
{
public class AgentHistoryRequestValidator : AbstractValidator<AgentHistoryRequest>
{
public AgentHistoryRequestValidator()
{
RuleFor(t => t.TemplateId).NotEmpty().NotNull();
RuleFor(t => t.Level)
.Must(t => t == AgentHistoryLevelEnum.Start || t == AgentHistoryLevelEnum.End)
.WithMessage($"Допустимые значения: {(int)AgentHistoryLevelEnum.Start}, {(int)AgentHistoryLevelEnum.End}");
}
}
}

View File

@@ -43,6 +43,9 @@ namespace PARR.DAL.Context
public DbSet<EsppSchTypeSchedule> EsppSchTypeSchedules { get; set; } public DbSet<EsppSchTypeSchedule> EsppSchTypeSchedules { get; set; }
public DbSet<EsppSchValue> EsppSchValues { get; set; } public DbSet<EsppSchValue> EsppSchValues { get; set; }
public DbSet<AgentHistory> AgentHistories { get; set; }
public DbSet<AgentHistoryLevel> AgentHistoryLevels { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
base.OnModelCreating(modelBuilder); base.OnModelCreating(modelBuilder);
@@ -318,6 +321,14 @@ namespace PARR.DAL.Context
#endregion #endregion
modelBuilder.Entity<AgentHistoryLevel>(f =>
{
f.HasData(
new { Id = (int)AgentHistoryLevelEnum.Start, Name = AgentHistoryLevelEnum.Start.ToString(), Description= "Агент начал выполнять задание" },
new { Id = (int)AgentHistoryLevelEnum.End, Name = AgentHistoryLevelEnum.End.ToString(), Description = "Агент завершил выполнение задания" }
);
});
} }

View File

@@ -0,0 +1,21 @@
namespace PARR.DAL.Contracts
{
/// <summary>
/// Уровень истории работы агента
/// </summary>
public enum AgentHistoryLevelEnum
{
// При добавлении новых значений, добавить в валидатор AgentHistoryRequestValidator
/// <summary>
/// Агент начал выполнять задание
/// </summary>
Start = 1,
/// <summary>
/// Агент завершил выполнение задания
/// </summary>
End = 5
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,88 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
namespace PARR.DAL.Migrations
{
/// <inheritdoc />
public partial class TblAgentHistory : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AgentHistoryLevels",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Name = table.Column<string>(type: "text", nullable: false),
Description = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AgentHistoryLevels", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AgentHistories",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
DateCreated = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
Message = table.Column<string>(type: "text", nullable: true),
HistoryLevelId = table.Column<int>(type: "integer", nullable: false),
TemplateId = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AgentHistories", x => x.Id);
table.ForeignKey(
name: "FK_AgentHistories_AgentHistoryLevels_HistoryLevelId",
column: x => x.HistoryLevelId,
principalTable: "AgentHistoryLevels",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_AgentHistories_Templates_TemplateId",
column: x => x.TemplateId,
principalTable: "Templates",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.InsertData(
table: "AgentHistoryLevels",
columns: new[] { "Id", "Description", "Name" },
values: new object[,]
{
{ 1, "Агент начал выполнять задание", "Start" },
{ 5, "Агент завершил выполнение задания", "End" }
});
migrationBuilder.CreateIndex(
name: "IX_AgentHistories_HistoryLevelId",
table: "AgentHistories",
column: "HistoryLevelId");
migrationBuilder.CreateIndex(
name: "IX_AgentHistories_TemplateId",
table: "AgentHistories",
column: "TemplateId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AgentHistories");
migrationBuilder.DropTable(
name: "AgentHistoryLevels");
}
}
}

View File

@@ -369,6 +369,68 @@ namespace PARR.DAL.Migrations
}); });
}); });
modelBuilder.Entity("PARR.DAL.Models.AgentHistory", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTimeOffset>("DateCreated")
.HasColumnType("timestamp with time zone");
b.Property<int>("HistoryLevelId")
.HasColumnType("integer");
b.Property<string>("Message")
.HasColumnType("text");
b.Property<Guid>("TemplateId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("HistoryLevelId");
b.HasIndex("TemplateId");
b.ToTable("AgentHistories");
});
modelBuilder.Entity("PARR.DAL.Models.AgentHistoryLevel", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("AgentHistoryLevels");
b.HasData(
new
{
Id = 1,
Description = "Агент начал выполнять задание",
Name = "Start"
},
new
{
Id = 5,
Description = "Агент завершил выполнение задания",
Name = "End"
});
});
modelBuilder.Entity("PARR.DAL.Models.Application", b => modelBuilder.Entity("PARR.DAL.Models.Application", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
@@ -2066,6 +2128,25 @@ namespace PARR.DAL.Migrations
b.ToTable("Works"); b.ToTable("Works");
}); });
modelBuilder.Entity("PARR.DAL.Models.AgentHistory", b =>
{
b.HasOne("PARR.DAL.Models.AgentHistoryLevel", "AgentHistoryLevel")
.WithMany("AgentHistories")
.HasForeignKey("HistoryLevelId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("PARR.DAL.Models.Template", "Template")
.WithMany("AgentHistories")
.HasForeignKey("TemplateId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("AgentHistoryLevel");
b.Navigation("Template");
});
modelBuilder.Entity("PARR.DAL.Models.Application", b => modelBuilder.Entity("PARR.DAL.Models.Application", b =>
{ {
b.HasOne("PARR.DAL.Models.ApplicationType", "ApplicationType") b.HasOne("PARR.DAL.Models.ApplicationType", "ApplicationType")
@@ -2305,6 +2386,11 @@ namespace PARR.DAL.Migrations
b.Navigation("Tnk"); b.Navigation("Tnk");
}); });
modelBuilder.Entity("PARR.DAL.Models.AgentHistoryLevel", b =>
{
b.Navigation("AgentHistories");
});
modelBuilder.Entity("PARR.DAL.Models.Application", b => modelBuilder.Entity("PARR.DAL.Models.Application", b =>
{ {
b.Navigation("ApplicationsInHosts"); b.Navigation("ApplicationsInHosts");
@@ -2402,6 +2488,8 @@ namespace PARR.DAL.Migrations
modelBuilder.Entity("PARR.DAL.Models.Template", b => modelBuilder.Entity("PARR.DAL.Models.Template", b =>
{ {
b.Navigation("AgentHistories");
b.Navigation("RobotConfigurations"); b.Navigation("RobotConfigurations");
}); });

View File

@@ -0,0 +1,33 @@
using PARR.DAL.Models.Base;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace PARR.DAL.Models
{
/// <summary>
/// История работы агентов
/// </summary>
[Table("AgentHistories")]
public class AgentHistory : IBase
{
[Key]
public Guid Id { get; set; }
public DateTimeOffset DateCreated { get; set; }
[NotMapped]
public DateTimeOffset? DateModified { get; set; }
public string? Message { get; set; }
public int HistoryLevelId { get; set; }
public Guid TemplateId { get; set; }
[ForeignKey(nameof(TemplateId))]
public Template? Template { get; set; }
[ForeignKey(nameof(HistoryLevelId))]
public AgentHistoryLevel? AgentHistoryLevel { get; set; }
}
}

View File

@@ -0,0 +1,19 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace PARR.DAL.Models
{
[Table("AgentHistoryLevels")]
public class AgentHistoryLevel
{
[Key]
public int Id { get; set; }
public required string Name { get; set; }
public required string Description { get; set; }
public ICollection<AgentHistory> AgentHistories { get; set; } = new HashSet<AgentHistory>();
}
}

View File

@@ -25,7 +25,7 @@ namespace PARR.DAL.Models
/// <summary> /// <summary>
/// Номер расписания еспп /// Номер расписания еспп
/// </summary> /// </summary>
public string? ScheduleEsppId { get; set; } public string? ScheduleEsppId { get; set; }
///// <summary> ///// <summary>
@@ -51,5 +51,7 @@ namespace PARR.DAL.Models
public ICollection<RobotConfiguration> RobotConfigurations { get; set; } = new HashSet<RobotConfiguration>(); public ICollection<RobotConfiguration> RobotConfigurations { get; set; } = new HashSet<RobotConfiguration>();
public ICollection<AgentHistory> AgentHistories { get; set; } = new HashSet<AgentHistory>();
} }
} }

View File

@@ -50,6 +50,7 @@ namespace PARR.DAL
services.AddTransient<IRobotConfigurationService, RobotConfigurationService>(); services.AddTransient<IRobotConfigurationService, RobotConfigurationService>();
services.AddTransient<IRobotHistoryService, RobotHistoryService>(); services.AddTransient<IRobotHistoryService, RobotHistoryService>();
services.AddTransient<IEsppSchTypeConfigService, EsppSchTypeConfigService>(); services.AddTransient<IEsppSchTypeConfigService, EsppSchTypeConfigService>();
services.AddTransient<IAgentHistoryService, AgentHistoryService>();
// TransformServices // TransformServices

View File

@@ -0,0 +1,23 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PARR.DAL.Context;
using PARR.DAL.Models;
using PARR.DAL.Services.Abstracts;
using PARR.DAL.Services.Interfaces;
namespace PARR.DAL.Services.Implementations
{
internal class AgentHistoryService : BaseService<AgentHistory>, IAgentHistoryService
{
private readonly DataContext dataContext;
public AgentHistoryService(DataContext dataContext, ILogger<AgentHistoryService> logger) : base(logger)
{
this.dataContext = dataContext;
}
protected override DbSet<AgentHistory> EntitySet => dataContext.AgentHistories;
protected override DataContext EntitiContext => dataContext;
}
}

View File

@@ -0,0 +1,9 @@
using PARR.DAL.Models;
using PARR.DAL.Services.Interfaces.Base;
namespace PARR.DAL.Services.Interfaces
{
public interface IAgentHistoryService : IBaseService<AgentHistory>
{
}
}