From bd67fcdc21eed03f5ad11b19b4421c923ff28fc2 Mon Sep 17 00:00:00 2001 From: huebyte Date: Wed, 26 Oct 2022 00:19:05 +0200 Subject: [PATCH] T #98 working on reflection mapper --- .../Abstraction/IReflectionMappable.cs | 10 ++ .../Abstraction/MappableToAttribute.cs | 20 ++++ .../HuppyService.Core/Models/CommandLog.cs | 2 + .../Utilities/ReflectionMapper.cs | 77 +++++++++++++ .../Utilities/ReflectionMapperHelpers.cs | 12 ++ .../Protos/database.proto | 2 +- .../Services/CommandLogService.cs | 3 + .../Services/TicketService.cs | 106 ++++++++++++++++-- 8 files changed, 220 insertions(+), 12 deletions(-) create mode 100644 src/HuppyService/HuppyService.Core/Abstraction/IReflectionMappable.cs create mode 100644 src/HuppyService/HuppyService.Core/Abstraction/MappableToAttribute.cs create mode 100644 src/HuppyService/HuppyService.Core/Utilities/ReflectionMapper.cs create mode 100644 src/HuppyService/HuppyService.Core/Utilities/ReflectionMapperHelpers.cs diff --git a/src/HuppyService/HuppyService.Core/Abstraction/IReflectionMappable.cs b/src/HuppyService/HuppyService.Core/Abstraction/IReflectionMappable.cs new file mode 100644 index 0000000..f92e538 --- /dev/null +++ b/src/HuppyService/HuppyService.Core/Abstraction/IReflectionMappable.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HuppyService.Core.Abstraction +{ + public interface IReflectionMappableToT where T : class { } +} diff --git a/src/HuppyService/HuppyService.Core/Abstraction/MappableToAttribute.cs b/src/HuppyService/HuppyService.Core/Abstraction/MappableToAttribute.cs new file mode 100644 index 0000000..be93dfa --- /dev/null +++ b/src/HuppyService/HuppyService.Core/Abstraction/MappableToAttribute.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HuppyService.Core.Abstraction +{ + [AttributeUsage(AttributeTargets.Property)] + public class MappableToAttribute : Attribute + { + public Type MappableTo; + public string AlternativeName; + public MappableToAttribute(Type type, string mappingName) + { + MappableTo = type; + AlternativeName = mappingName; + } + } +} diff --git a/src/HuppyService/HuppyService.Core/Models/CommandLog.cs b/src/HuppyService/HuppyService.Core/Models/CommandLog.cs index eb6fc17..2632fd3 100644 --- a/src/HuppyService/HuppyService.Core/Models/CommandLog.cs +++ b/src/HuppyService/HuppyService.Core/Models/CommandLog.cs @@ -10,6 +10,8 @@ public class CommandLog : DbModel public override int Id { get; set; } public string? CommandName { get; set; } public string? ErrorMessage { get; set; } + + [MappableTo(typeof(ulong), "UnixTime")] public DateTime? Date { get; set; } public bool IsSuccess { get; set; } public long ExecutionTimeMs { get; set; } diff --git a/src/HuppyService/HuppyService.Core/Utilities/ReflectionMapper.cs b/src/HuppyService/HuppyService.Core/Utilities/ReflectionMapper.cs new file mode 100644 index 0000000..b8cc5a2 --- /dev/null +++ b/src/HuppyService/HuppyService.Core/Utilities/ReflectionMapper.cs @@ -0,0 +1,77 @@ +using HuppyService.Core.Abstraction; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace HuppyService.Core.Utilities +{ + public static class ReflectionMapper + { + public static D? Map(T input) + where T : class + where D : class, new() + { + //var type1 = typeof(T); + //var type2 = typeof(D); + + // create instance of D + D result = new(); + + // get props of D + var propsOfD = typeof(D).GetProperties(); + + // get props of T + var propsOfT = input.GetType().GetProperties(); + + // map props to each other via names + foreach (var tProp in propsOfT) + { + MappableToAttribute? attributeInfo = tProp.GetCustomAttribute(typeof(MappableToAttribute)) as MappableToAttribute; + //string searchName = attributeInfo is not null ? attributeInfo.AlternativeName : tProp.Name; + + // get instance of D prop + PropertyInfo propInfo; + if (attributeInfo != null) + propInfo = propsOfD.First(prop => prop.Name == tProp.Name || prop.Name == attributeInfo.AlternativeName); + else + propInfo = propsOfD.First(prop => prop.Name == tProp.Name); + + //PropertyInfo propInfo = propsOfD.First(prop => prop.Name == searchName); + var propInstance = propInfo.GetValue(result, null); + + propInfo.SetValue(result, GetFinalProperty(attributeInfo, propInstance)); + } + + return result; + } + + public static object? GetFinalProperty(MappableToAttribute? attributeInfo, object? instance) + { + // get Mappable attribute + //MappableToAttribute? attributeInfo = propInfo.GetCustomAttribute(typeof(MappableToAttribute)) as MappableToAttribute; + + // get instance of value + //var value = propInfo.GetValue(instance, null); + + // perform custom mappings + if (attributeInfo is not null) + { + if (instance is DateTime dateValue && attributeInfo.MappableTo == typeof(ulong)) + { + return Miscellaneous.DateTimeToUnixTimeStamp(dateValue); + } + else if (instance is ulong ulongValue && attributeInfo.MappableTo == typeof(DateTime)) + { + return Miscellaneous.UnixTimeStampToUtcDateTime(ulongValue); + } + + return instance; + } + + return instance; + } + } +} diff --git a/src/HuppyService/HuppyService.Core/Utilities/ReflectionMapperHelpers.cs b/src/HuppyService/HuppyService.Core/Utilities/ReflectionMapperHelpers.cs new file mode 100644 index 0000000..365a713 --- /dev/null +++ b/src/HuppyService/HuppyService.Core/Utilities/ReflectionMapperHelpers.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HuppyService.Core.Utilities +{ + internal class ReflectionMapperHelpers + { + } +} diff --git a/src/HuppyService/HuppyService.Service/Protos/database.proto b/src/HuppyService/HuppyService.Service/Protos/database.proto index e532482..220f709 100644 --- a/src/HuppyService/HuppyService.Service/Protos/database.proto +++ b/src/HuppyService/HuppyService.Service/Protos/database.proto @@ -41,7 +41,7 @@ message ReminderModel { } message TicketModel { - int32 Id = 1; + string Id = 1; string Topic = 2; string Description = 3; bool IsClosed = 4; diff --git a/src/HuppyService/HuppyService.Service/Services/CommandLogService.cs b/src/HuppyService/HuppyService.Service/Services/CommandLogService.cs index fefb616..a20716e 100644 --- a/src/HuppyService/HuppyService.Service/Services/CommandLogService.cs +++ b/src/HuppyService/HuppyService.Service/Services/CommandLogService.cs @@ -1,6 +1,7 @@ using Google.Protobuf.WellKnownTypes; using Grpc.Core; using HuppyService.Core.Interfaces.IRepositories; +using HuppyService.Core.Models; using HuppyService.Core.Utilities; using HuppyService.Service.Protos; using HuppyService.Service.Protos.Models; @@ -56,6 +57,8 @@ public override async Task GetAverageExecutionTime(Protos.V public override async Task AddCommand(CommandLogModel request, ServerCallContext context) { + var test = ReflectionMapper.Map(request); + //ReflectionMapper Core.Models.CommandLog commandLog = new() { ChannelId = request.ChannelId, diff --git a/src/HuppyService/HuppyService.Service/Services/TicketService.cs b/src/HuppyService/HuppyService.Service/Services/TicketService.cs index 6459f36..d58d270 100644 --- a/src/HuppyService/HuppyService.Service/Services/TicketService.cs +++ b/src/HuppyService/HuppyService.Service/Services/TicketService.cs @@ -1,8 +1,13 @@ -using Grpc.Core; +using Google.Protobuf; +using Grpc.Core; using HuppyService.Core.Interfaces.IRepositories; +using HuppyService.Core.Models; +using HuppyService.Core.Utilities; using HuppyService.Service.Protos; using HuppyService.Service.Protos.Models; +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; +using System.Net.Sockets; namespace HuppyService.Service.Services { @@ -14,29 +19,108 @@ public TicketService(ITicketRepository ticketRepository) _ticketRepository = ticketRepository; } - public override Task AddTicket(AddTicketInput request, ServerCallContext context) + public override async Task AddTicket(AddTicketInput request, ServerCallContext context) { - return base.AddTicket(request, context); + if (string.IsNullOrEmpty(request.Description)) throw new ArgumentException("Ticket description cannot be null"); + + Ticket ticket = new() + { + Id = Guid.NewGuid().ToString(), + Topic = request.Topic, + Description = request.Description, + CreatedDate = DateTime.UtcNow, + TicketAnswer = null, + ClosedDate = null, + IsClosed = false, + UserId = request.UserId, + }; + + await _ticketRepository.AddAsync(ticket); + await _ticketRepository.SaveChangesAsync(); + + return new TicketModel() + { + Id = ticket.Id, + UserId = ticket.UserId, + Description = ticket.Description, + Topic = ticket.Topic, + IsClosed = ticket.IsClosed, + TicketAnswer = ticket.TicketAnswer, + ClosedDate = ticket.ClosedDate is null ? 0 : Miscellaneous.DateTimeToUnixTimeStamp((DateTime)ticket.ClosedDate), + CreatedDate = Miscellaneous.DateTimeToUnixTimeStamp(ticket.CreatedDate) + }; } - public override Task CloseTicket(CloseTicketInput request, ServerCallContext context) + public override async Task CloseTicket(CloseTicketInput request, ServerCallContext context) { - return base.CloseTicket(request, context); + if (string.IsNullOrEmpty(request.TicketId)) + throw new ArgumentException("Ticked ID cannot be empty"); + + var ticket = await _ticketRepository.GetAsync(request.TicketId); + + if (ticket is null) throw new Exception("Ticket doesn't exist"); + + ticket.IsClosed = true; + ticket.TicketAnswer = request.Answer; + ticket.ClosedDate = DateTime.UtcNow; + + await _ticketRepository.UpdateAsync(ticket); + await _ticketRepository.SaveChangesAsync(); + + return new CommonResponse() { IsSuccess = true }; } - public override Task GetCountAsync(UserId request, ServerCallContext context) + public override async Task GetCountAsync(UserId request, ServerCallContext context) { - return base.GetCountAsync(request, context); + var tickets = await _ticketRepository.GetAllAsync(); + + // modify database query + var result = await tickets.Where(ticket => ticket.UserId == request.Id).CountAsync(); + return new Protos.Int32() { Number = result }; } - public override Task GetPaginatedTickets(GetPaginatedTicketsInput request, ServerCallContext context) + public override async Task GetPaginatedTickets(GetPaginatedTicketsInput request, ServerCallContext context) { - return base.GetPaginatedTickets(request, context); + var tickets = (await _ticketRepository.GetAllAsync()) + .OrderBy(ticket => ticket.IsClosed) + .ThenByDescending(ticket => ticket.CreatedDate) + .Skip(request.Skip) + .Take(request.Take) + .ToList(); + + var result = new TicketModelCollection(); + result.TicketsModels.AddRange(tickets.Select(ticket => new TicketModel + { + Id = ticket.Id, + UserId = ticket.UserId, + Description = ticket.Description, + Topic = ticket.Topic, + IsClosed = ticket.IsClosed, + TicketAnswer = ticket.TicketAnswer, + ClosedDate = ticket.ClosedDate is null ? 0 : Miscellaneous.DateTimeToUnixTimeStamp((DateTime)ticket.ClosedDate), + CreatedDate = Miscellaneous.DateTimeToUnixTimeStamp(ticket.CreatedDate) + })); + + return result; } - public override Task GetTicket(GetTicketInput request, ServerCallContext context) + public override async Task GetTicket(GetTicketInput request, ServerCallContext context) { - return base.GetTicket(request, context); + var ticket = await _ticketRepository.GetAsync(request.TicketId); + + if (ticket is null) return null!; + + return new TicketModel + { + Id = ticket.Id, + UserId = ticket.UserId, + Description = ticket.Description, + Topic = ticket.Topic, + IsClosed = ticket.IsClosed, + TicketAnswer = ticket.TicketAnswer, + ClosedDate = ticket.ClosedDate is null ? 0 : Miscellaneous.DateTimeToUnixTimeStamp((DateTime)ticket.ClosedDate), + CreatedDate = Miscellaneous.DateTimeToUnixTimeStamp(ticket.CreatedDate) + }; } public override Task GetTickets(Protos.Void request, ServerCallContext context)