This commit is contained in:
Mikhail Kuznetsov
2023-06-16 15:00:11 +10:00
parent 73a824057b
commit 57fe9eff09
37 changed files with 1151 additions and 504 deletions

View File

@@ -27,6 +27,20 @@
public const string Metrics = Base + "/metrics";
}
public static class Scheduler
{
public const string GetAll = Base + "/schedulers/";
public const string GetByIp = Base + "/schedulers/ip/";
//public const string Get = Base + "/schedulers/" + getParam;
//public const string GetAreas = Base + "/schedulers/" + getParam + "/areas";
//public const string GetPlaces = Base + "/schedulers/" + getParam + "/places";
//public const string GetTemplates = Base + "/schedulers/" + getParam + "/templates";
public const string getParam = "{id}";
}
//public static class Layer
//{
// public const string GetAll = Base + "/layers/";
@@ -37,6 +51,7 @@
// public const string GetTemplates = Base + "/layers/" + getParam + "/templates";
// public const string getParam = "{id}";
//}
//
}
}
}

View File

@@ -0,0 +1,57 @@
namespace PARR.API.Contracts.V1.Requests.Queries
{
public class PaginationQuery
{
private int pageSizeMax = 100;
private int pageSizeDefault = 10;
private int pageNumberMin = 1;
public PaginationQuery()
{
PageNumber = pageNumberMin;
PageSize = pageSizeDefault;
}
public PaginationQuery(int pageNumber, int pageSize)
{
PageNumber = pageNumber;
PageSize = pageSize;
}
private int _pageNumber;
public int PageNumber
{
get
{
return _pageNumber;
}
set
{
if (value < pageNumberMin)
_pageNumber = pageNumberMin;
else
_pageNumber = value;
}
}
private int _pageSize;
public int PageSize
{
get
{
return _pageSize;
}
set
{
if (value > pageSizeMax)
_pageSize = pageSizeMax;
else
_pageSize = value;
if (_pageSize < 1)
_pageSize = pageSizeDefault;
}
}
}
}

View File

@@ -0,0 +1,15 @@
namespace PARR.API.Contracts.V1.Requests.Queries
{
public class SchedulerGetAllQuery
{
/// <summary>
/// true - вкл задания, false - выкл задания, null - все задания
/// </summary>
public bool? IsEnabled { get; set; }
/// <summary>
/// Список запланированных заданий на указанную дату
/// </summary>
public DateTimeOffset? StartDatePlanned { get; set; }
}
}

View File

@@ -0,0 +1,7 @@
namespace PARR.API.Contracts.V1.Requests.Queries
{
public class SchedulerGetByIpQuery
{
public string? Ip { get; set; }
}
}

View File

@@ -0,0 +1,6 @@
namespace PARR.API.Contracts.V1.Responses
{
public class ApiHealthResponse
{
}
}

View File

@@ -0,0 +1,9 @@
namespace PARR.API.Contracts.V1.Responses
{
public class ApiVersionResponse
{
public string? Version { get; set; }
public string? Environment { get; set; }
}
}

View File

@@ -0,0 +1,29 @@
namespace PARR.API.Contracts.V1.Responses.Base
{
public class BaseResponse
{
public BaseResponse() { }
public BaseResponse(bool isSuccess)
{
IsSuccess = isSuccess;
}
public BaseResponse(bool isSuccess, List<ErrorModel> errors)
{
IsSuccess = isSuccess;
Errors = errors;
}
public BaseResponse(bool isSuccess, List<ErrorModel> errors, string message)
{
IsSuccess = isSuccess;
Errors = errors;
Message = message;
}
public List<ErrorModel> Errors { get; set; } = new List<ErrorModel>();
public bool IsSuccess { get; private set; }
public string? Message { get; private set; }
}
}

View File

@@ -0,0 +1,9 @@
namespace PARR.API.Contracts.V1.Responses.Base
{
public class ErrorModel
{
public string? FieldName { get; set; }
public string? Message { get; set; }
}
}

View File

@@ -0,0 +1,29 @@
namespace PARR.API.Contracts.V1.Responses.Base
{
public class PagedResponse<T> : BaseResponse
{
public PagedResponse(bool isSuccess) : base(isSuccess) { }
public PagedResponse(bool isSuccess, List<ErrorModel> errors) : base(isSuccess, errors) { }
public PagedResponse(bool isSuccess, List<ErrorModel> errors, string message) : base(isSuccess, errors, message) { }
public PagedResponse(IEnumerable<T> data, bool isSuccess) : base(isSuccess) { Data = data; }
public PagedResponse(IEnumerable<T> data, bool isSuccess, List<ErrorModel> errors) : base(isSuccess, errors) { Data = data; }
public PagedResponse(IEnumerable<T> data, bool isSuccess, List<ErrorModel> errors, string message) : base(isSuccess, errors, message) { Data = data; }
public IEnumerable<T>? Data { get; set; }
public int? PageNumber { get; set; }
public int? PageSize { get; set; }
public int? TotalPage { get; set; }
public int? TotalItems { get; set; }
}
}

View File

@@ -0,0 +1,57 @@
using FluentValidation.Results;
namespace PARR.API.Contracts.V1.Responses.Base
{
public class Response<T> : BaseResponse
{
public Response(T response, bool isSuccess) : base(isSuccess)
{
Data = response;
}
public Response(T response, bool isSuccess, List<ErrorModel> errors) : base(isSuccess, errors)
{
Data = response;
}
public Response(T response, bool isSuccess, List<ErrorModel> errors, string message) : base(isSuccess, errors, message)
{
Data = response;
}
public T? Data { get; private set; }
}
public class Response : Response<object?>
{
/// <summary>
/// Респонс для валидации от FluentValidator
/// </summary>
/// <param name="validationErrors">ResultValidate.Errors</param>
public Response(List<ValidationFailure> validationErrors) : base(null, false)
{
var errors = new List<ErrorModel>();
foreach (var error in validationErrors)
{
errors.Add(new ErrorModel
{
FieldName = error.PropertyName,
Message = error.ErrorMessage
});
}
Errors = errors;
}
/// <summary>
/// Респонс когда не нужно передавать объект
/// </summary>
/// <param name="isSuccess"></param>
/// <param name="errors"></param>
public Response(bool isSuccess, List<ErrorModel> errors) : base(null, isSuccess, errors)
{
}
}
}

View File

@@ -0,0 +1,21 @@
namespace PARR.API.Contracts.V1.Responses
{
public class SchedulerBaseResponse
{
public required string Name { get; set; }
public DateTimeOffset StartAt { get; set; }
public int FrequencyMinute { get; set; }
}
public class SchedulerGetAllResponse: SchedulerBaseResponse
{
public Guid Id { get; set; }
public bool IsEnabled { get; set; }
}
public class SchedulerResponse
{
}
}

View File

@@ -1,9 +1,49 @@
using PARR.API.Controllers.V1.Base;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using PARR.API.Contracts.V1;
using PARR.API.Contracts.V1.Responses;
using PARR.API.Contracts.V1.Responses.Base;
using PARR.API.Controllers.V1.Base;
using System.Reflection;
namespace PARR.API.Controllers.V1
{
public class ApiStatusController : BaseApiController
{
//TODO:
/// <summary>
/// Получить версию и окружение
/// </summary>
/// <returns></returns>
[AllowAnonymous]
[HttpGet(ApiRoutes.ApiStatus.Version)]
public IActionResult GetVersion()
{
var version = Assembly.GetEntryAssembly()?.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
var response = new ApiVersionResponse
{
Environment = environment,
Version = version
};
return Ok(new Response<ApiVersionResponse>(response, true));
}
/// <summary>
/// Проверка доступности системы
/// </summary>
/// <returns></returns>
[AllowAnonymous]
[HttpGet(ApiRoutes.ApiStatus.Health)]
public IActionResult GetHealth()
{
//Так же можно реализовать проверку связи с БД
//Проверка доступности хранилищ
var response = new ApiHealthResponse { };
return Ok(new Response<ApiHealthResponse>(response, true));
}
}
}

View File

@@ -1,26 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using PARR.DAL.Services.Interfaces;
namespace PARR.API.Controllers.V1
{
public class HomeController : Controller
{
private readonly IHostService hostService;
public HomeController(IHostService hostService)
{
this.hostService = hostService;
}
[HttpGet("test")]
public async Task<IActionResult> Index()
{
var hosts = await hostService.Get().ToListAsync();
//foreach (var host in hosts) { logger.LogInformation($"{host.HostName}"); }
//return View();
return Ok(hosts.Select(t => new { t.HostName, t.IP, t.Id }));
}
}
}

View File

@@ -0,0 +1,78 @@
using Microsoft.AspNetCore.Mvc;
using PARR.API.Contracts.V1.Requests.Queries;
using PARR.API.Contracts.V1;
using PARR.API.Controllers.V1.Base;
using AutoMapper;
using PARR.DAL.DomainModels;
using PARR.DAL.Models;
using PARR.DAL.Services.Interfaces;
using Microsoft.EntityFrameworkCore;
using PARR.API.Contracts.V1.Responses;
using PARR.API.Contracts.V1.Responses.Base;
using PARR.API.Extensions;
using System.Runtime.CompilerServices;
using PARR.API.Services.Interfaces;
namespace PARR.API.Controllers.V1
{
public class ShchedullerController : BaseApiController
{
private readonly IMapper mapper;
private readonly ISchedulerService schedulerService;
private readonly IClientService clientService;
public ShchedullerController(
IMapper mapper,
ISchedulerService schedulerService,
IClientService clientService
)
{
this.mapper = mapper;
this.schedulerService = schedulerService;
this.clientService = clientService;
}
/// <summary>
/// Список всех планировщиков постранично в соотвествии с фильтрами
/// </summary>
/// <param name="paginationQuery"></param>
/// <returns></returns>
[HttpGet(ApiRoutes.Scheduler.GetAll)]
public async Task<IActionResult> GetAll([FromQuery] PaginationQuery paginationQuery, [FromQuery] SchedulerGetAllQuery filter)
{
var paginationFilter = mapper.Map<PaginationFilter>(paginationQuery);
IQueryable<Scheduler> query = schedulerService.Get().OrderBy(s => s.StartAt);
if (filter.IsEnabled.HasValue)
query = query.Where(t => t.IsEnabled == filter.IsEnabled);
if (filter.StartDatePlanned.HasValue)
query = query.Where(t => t.StartAt.DateTime.Date <= filter.StartDatePlanned.Value.DateTime.Date); ;
var schedulers = await schedulerService.GetPage(query, paginationFilter).ToListAsync();
if (!schedulers.Any())
return NoContent();
var schedulerResponse = mapper.Map<List<SchedulerGetAllResponse>>(schedulers);
var paginationResponse = new PagedResponse<SchedulerGetAllResponse>(schedulerResponse, true).GetPaginatedProps(paginationFilter, query);
return Ok(paginationResponse);
}
[HttpGet(ApiRoutes.Scheduler.GetByIp)]
public async Task<IActionResult> GetByClientIP([FromQuery] SchedulerGetByIpQuery requestQuery)
{
var ip = requestQuery.Ip ?? clientService.GetClientIp()?.ToString();
if (string.IsNullOrEmpty(ip))
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Client IP address is null." } }));
// TODO;
return Ok();
}
}
}

View File

@@ -0,0 +1,59 @@
using PARR.API.Contracts.V1.Responses.Base;
using PARR.DAL.DomainModels;
namespace PARR.API.Extensions
{
public static class PaginationExtensions
{
/// <summary>
/// Заполняет TotalItems, PageNumber, PageSize, TotalPage
/// </summary>
/// <typeparam name="TypeResponse"></typeparam>
/// <typeparam name="Query"></typeparam>
/// <param name="pagedResponse"></param>
/// <param name="paginationFilter"></param>
/// <param name="quaryAllItemsApplyFilters"></param>
/// <returns></returns>
public static PagedResponse<TypeResponse> GetPaginatedProps<TypeResponse, Query>(
this PagedResponse<TypeResponse> pagedResponse,
PaginationFilter paginationFilter,
IQueryable<Query> quaryAllItemsApplyFilters
)
{
// TODO: CountAsync - подумать и затолкать в Task
int totalItems = quaryAllItemsApplyFilters.Count();//.CountAsync().GetAwaiter().GetResult();
return CreatePaginatedResponse(totalItems, paginationFilter, pagedResponse);
}
/// <summary>
/// Заполняет TotalItems, PageNumber, PageSize, TotalPage
/// </summary>
/// <typeparam name="TypeResponse"></typeparam>
/// <param name="pagedResponse"></param>
/// <param name="paginationFilter"></param>
/// <param name="totalItems"></param>
/// <returns></returns>
public static PagedResponse<TypeResponse> GetPaginatedProps<TypeResponse>(
this PagedResponse<TypeResponse> pagedResponse,
PaginationFilter paginationFilter,
int totalItems
)
{
return CreatePaginatedResponse(totalItems, paginationFilter, pagedResponse);
}
private static PagedResponse<T> CreatePaginatedResponse<T>(int totalItems, PaginationFilter paginationFilter, PagedResponse<T> pagedResponse)
{
pagedResponse.TotalItems = totalItems;
pagedResponse.PageNumber = paginationFilter.PageNumber >= 1 ? paginationFilter.PageNumber : (int?)null;
pagedResponse.PageSize = paginationFilter.PageSize >= 1 ? paginationFilter.PageSize : (int?)null;
pagedResponse.TotalPage =
(pagedResponse.TotalItems.HasValue && pagedResponse.PageSize.HasValue)
? (int)Math.Ceiling(pagedResponse.TotalItems.Value / (double)pagedResponse.PageSize.Value)
: (int?)null;
return pagedResponse;
}
}
}

View File

@@ -19,7 +19,7 @@ namespace PARR.API.Installers
return new UriService(absoluteUri);
});
services.AddTransient<IClientService, ClientService>();
//services.AddTransient<IFileService, FileService>();

View File

@@ -19,9 +19,9 @@ namespace PARR.API.Installers
"v1",
new OpenApiInfo
{
Title = "GEO API",
Title = "PARR API",
Version = $"v{version}",
Description = "API for the project \"GEO DVGD\"",
Description = "API for the project \"PARR DVGD\"",
Contact = new OpenApiContact { Email = "IVC_TrubnikovME@dvgd.rzd;IVC_KuznetsovMV@dvgd.rzd", Name = "Trubnikov M.E., Kuznetsov M.V." },
License = new OpenApiLicense { Name = "© PTK-DVGD Software LLC" }
});

View File

@@ -1,4 +1,6 @@
using AutoMapper;
using PARR.API.Contracts.V1.Responses;
using PARR.DAL.Models;
namespace PARR.API.MappingProfiles
{
@@ -6,7 +8,11 @@ namespace PARR.API.MappingProfiles
{
public DomainToResponseProfile()
{
// из проекта наружу
// --- Scheduler ---
CreateMap<Scheduler, SchedulerBaseResponse>()
.Include<Scheduler, SchedulerGetAllResponse>()
.ForMember(d => d.FrequencyMinute, o => o.MapFrom(s => s.Frequency));
CreateMap<Scheduler, SchedulerGetAllResponse>();
}
}
}

View File

@@ -1,4 +1,6 @@
using AutoMapper;
using PARR.API.Contracts.V1.Requests.Queries;
using PARR.DAL.DomainModels;
namespace PARR.API.MappingProfiles
{
@@ -6,7 +8,7 @@ namespace PARR.API.MappingProfiles
{
public RequestToDomainProfile()
{
// снаружи в проект
CreateMap<PaginationQuery, PaginationFilter>();
}
}
}

View File

@@ -34,8 +34,6 @@
</ItemGroup>
<ItemGroup>
<Folder Include="Contracts\V1\Requests\Queries\" />
<Folder Include="Contracts\V1\Responses\" />
<Folder Include="MappingProfiles\Resolvers\" />
<Folder Include="Validators\" />
</ItemGroup>

View File

@@ -0,0 +1,19 @@
using PARR.API.Services.Interfaces;
using System.Net;
namespace PARR.API.Services.Implementations
{
public class ClientService : IClientService
{
private readonly IHttpContextAccessor httpContextAccessor;
public ClientService(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public IPAddress? GetClientIp()
{
return httpContextAccessor.HttpContext?.Connection.RemoteIpAddress;
}
}
}

View File

@@ -0,0 +1,9 @@
using System.Net;
namespace PARR.API.Services.Interfaces
{
public interface IClientService
{
IPAddress? GetClientIp();
}
}