Initial Sync.
This commit is contained in:
@@ -0,0 +1,25 @@
|
|||||||
|
**/.classpath
|
||||||
|
**/.dockerignore
|
||||||
|
**/.env
|
||||||
|
**/.git
|
||||||
|
**/.gitignore
|
||||||
|
**/.project
|
||||||
|
**/.settings
|
||||||
|
**/.toolstarget
|
||||||
|
**/.vs
|
||||||
|
**/.vscode
|
||||||
|
**/*.*proj.user
|
||||||
|
**/*.dbmdl
|
||||||
|
**/*.jfm
|
||||||
|
**/azds.yaml
|
||||||
|
**/bin
|
||||||
|
**/charts
|
||||||
|
**/docker-compose*
|
||||||
|
**/Dockerfile*
|
||||||
|
**/node_modules
|
||||||
|
**/npm-debug.log
|
||||||
|
**/obj
|
||||||
|
**/secrets.dev.yaml
|
||||||
|
**/values.dev.yaml
|
||||||
|
LICENSE
|
||||||
|
README.md
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<UserSecretsId>72602978-2f98-4a25-bd8b-1f2274a49fe4</UserSecretsId>
|
||||||
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
|
<DockerComposeProjectPath>..\docker-compose.dcproj</DockerComposeProjectPath>
|
||||||
|
<RootNamespace>DGM.HoC.$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="Protos\BookMng.proto" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Protobuf Include="Protos\BookMng.proto" GrpcServices="Server" />
|
||||||
|
<Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="AutoMapper" Version="12.0.1" />
|
||||||
|
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
|
||||||
|
<PackageReference Include="Grpc.AspNetCore" Version="2.53.0" />
|
||||||
|
<PackageReference Include="Grpc.AspNetCore.Server.Reflection" Version="2.53.0" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.5" />
|
||||||
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.18.1" />
|
||||||
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="7.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 80
|
||||||
|
EXPOSE 443
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
|
||||||
|
WORKDIR /src
|
||||||
|
COPY ["BookMng.API/BookMng.API.csproj", "BookMng.API/"]
|
||||||
|
RUN dotnet restore "BookMng.API/BookMng.API.csproj"
|
||||||
|
COPY . .
|
||||||
|
WORKDIR "/src/BookMng.API"
|
||||||
|
RUN dotnet build "BookMng.API.csproj" -c Release -o /app/build
|
||||||
|
|
||||||
|
FROM build AS publish
|
||||||
|
RUN dotnet publish "BookMng.API.csproj" -c Release -o /app/publish /p:UseAppHost=false
|
||||||
|
|
||||||
|
FROM base AS final
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=publish /app/publish .
|
||||||
|
ENTRYPOINT ["dotnet", "BookMng.API.dll"]
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using DGM.HoC.BookMng.API.Infrastructure.EntityConfigs;
|
||||||
|
using DGM.HoC.BookMng.API.Model;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace DGM.HoC.BookMng.API.Infrastructure;
|
||||||
|
|
||||||
|
public class BookingContext : DbContext
|
||||||
|
{
|
||||||
|
public BookingContext(DbContextOptions<BookingContext> options)
|
||||||
|
: base(options)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public DbSet<Booking> Bookings { get; set; }
|
||||||
|
public DbSet<RoomType> RoomTypes { get; set; }
|
||||||
|
public DbSet<RoomItem> RoomItems { get; set; }
|
||||||
|
public DbSet<CustomerData> BookingCustomerData { get; set; }
|
||||||
|
|
||||||
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
modelBuilder.ApplyConfiguration(new BookingTypeEntityConfig());
|
||||||
|
modelBuilder.ApplyConfiguration(new RoomItemTypeEntityConfig());
|
||||||
|
modelBuilder.ApplyConfiguration(new CustomerDataTypeEntityConfig());
|
||||||
|
|
||||||
|
base.OnModelCreating(modelBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
namespace DGM.HoC.BookMng.API.Infrastructure
|
||||||
|
{
|
||||||
|
public class BookingContextSeed
|
||||||
|
{
|
||||||
|
public static void InitData(BookingContext dbContext)
|
||||||
|
{
|
||||||
|
if (dbContext.RoomTypes.Count() == 0)
|
||||||
|
{
|
||||||
|
InitRoomTypes(dbContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (dbContext.Bookings.Count() == 0)
|
||||||
|
{
|
||||||
|
InitBookings(dbContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void InitRoomTypes(BookingContext dbContext)
|
||||||
|
{
|
||||||
|
dbContext.RoomTypes.Add(new Model.RoomType()
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Name = "Individual con baño",
|
||||||
|
Description = "Individual con baño privado, televisión y wifi",
|
||||||
|
GuestAmount = 1
|
||||||
|
});
|
||||||
|
|
||||||
|
dbContext.RoomTypes.Add(new Model.RoomType()
|
||||||
|
{
|
||||||
|
Id = 2,
|
||||||
|
Name = "Matrimonio con baño",
|
||||||
|
Description = "Doble de matrimonio, con baño privado, televisión y wifi",
|
||||||
|
GuestAmount = 2
|
||||||
|
});
|
||||||
|
|
||||||
|
dbContext.RoomTypes.Add(new Model.RoomType()
|
||||||
|
{
|
||||||
|
Id = 3,
|
||||||
|
Name = "Dos camas con baño",
|
||||||
|
Description = "Doble de dos camas, con baño privado, televisión y wifi",
|
||||||
|
GuestAmount = 2
|
||||||
|
});
|
||||||
|
|
||||||
|
dbContext.RoomTypes.Add(new Model.RoomType()
|
||||||
|
{
|
||||||
|
Id = 4,
|
||||||
|
Name = "Triple con vistas",
|
||||||
|
Description = "Habitación triple, con una cama matrimonio y otra individual, mirador con vistas, baño privado, televisión y wifi",
|
||||||
|
GuestAmount = 3
|
||||||
|
});
|
||||||
|
|
||||||
|
dbContext.SaveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void InitBookings(BookingContext dbContext)
|
||||||
|
{
|
||||||
|
dbContext.Bookings.Add(new Model.Booking()
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
CustomerId = "N12345",
|
||||||
|
Status = "Reservada",
|
||||||
|
Description = "Vienen de paso, probablemente con caballos y carga"
|
||||||
|
});
|
||||||
|
|
||||||
|
dbContext.RoomItems.Add(new Model.RoomItem()
|
||||||
|
{
|
||||||
|
BookingId = 1,
|
||||||
|
Id = 1,
|
||||||
|
TypeId = 3,
|
||||||
|
GuestAmount = 2,
|
||||||
|
DateIn = new DateTime(2023, 5, 23),
|
||||||
|
DateOut = new DateTime(2023, 5, 24),
|
||||||
|
PricePerNight = 45,
|
||||||
|
Notes = ""
|
||||||
|
});
|
||||||
|
|
||||||
|
dbContext.RoomItems.Add(new Model.RoomItem()
|
||||||
|
{
|
||||||
|
BookingId = 1,
|
||||||
|
Id = 2,
|
||||||
|
TypeId = 1,
|
||||||
|
GuestAmount = 1,
|
||||||
|
DateIn = new DateTime(2023, 5, 23),
|
||||||
|
DateOut = new DateTime(2023, 5, 24),
|
||||||
|
PricePerNight = 30,
|
||||||
|
Notes = ""
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
dbContext.Bookings.Add(new Model.Booking()
|
||||||
|
{
|
||||||
|
Id = 2,
|
||||||
|
CustomerId = "N567432",
|
||||||
|
Status = "Finalizada",
|
||||||
|
Description = "Gandalf llamó para reservar para un día, puede que se quede alguno más."
|
||||||
|
});
|
||||||
|
|
||||||
|
dbContext.RoomItems.Add(new Model.RoomItem()
|
||||||
|
{
|
||||||
|
BookingId = 2,
|
||||||
|
Id = 1,
|
||||||
|
TypeId = 1,
|
||||||
|
GuestAmount = 1,
|
||||||
|
DateIn = new DateTime(2023, 4, 10),
|
||||||
|
DateOut = new DateTime(2023, 5, 11),
|
||||||
|
PricePerNight = 25,
|
||||||
|
Notes = ""
|
||||||
|
});
|
||||||
|
|
||||||
|
dbContext.SaveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using DGM.HoC.BookMng.API.Model;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace DGM.HoC.BookMng.API.Infrastructure.EntityConfigs;
|
||||||
|
|
||||||
|
public class BookingTypeEntityConfig : IEntityTypeConfiguration<Booking>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<Booking> builder)
|
||||||
|
{
|
||||||
|
builder.HasMany(b => b.Rooms)
|
||||||
|
.WithOne()
|
||||||
|
.HasForeignKey(r => r.BookingId)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.HasOne(b => b.BookingCustomer)
|
||||||
|
.WithOne()
|
||||||
|
.HasForeignKey<CustomerData>(cd => cd.BookingId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using DGM.HoC.BookMng.API.Model;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace DGM.HoC.BookMng.API.Infrastructure.EntityConfigs;
|
||||||
|
|
||||||
|
public class CustomerDataTypeEntityConfig : IEntityTypeConfiguration<CustomerData>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<CustomerData> builder)
|
||||||
|
{
|
||||||
|
builder.HasKey(cd => cd.BookingId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using DGM.HoC.BookMng.API.Model;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace DGM.HoC.BookMng.API.Infrastructure.EntityConfigs;
|
||||||
|
|
||||||
|
public class RoomItemTypeEntityConfig : IEntityTypeConfiguration<RoomItem>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<RoomItem> builder)
|
||||||
|
{
|
||||||
|
builder.HasKey(item => new { item.BookingId, item.Id });
|
||||||
|
|
||||||
|
builder.HasOne(item => item.RoomType)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(item => item.TypeId)
|
||||||
|
.IsRequired();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using DGM.HoC.BookMng.API.Model;
|
||||||
|
using DGM.HoC.BookMng.API.RPC;
|
||||||
|
using Google.Protobuf.Collections;
|
||||||
|
|
||||||
|
namespace DGM.HoC.BookMng.API.Infrastructure.Mappers;
|
||||||
|
|
||||||
|
public class EnumerableRoomItemToRepeatedFieldRpcRoomItem : IValueConverter<IEnumerable<RoomItem>, RepeatedField<rpcRoomItem>>
|
||||||
|
{
|
||||||
|
public RepeatedField<rpcRoomItem> Convert(IEnumerable<RoomItem> sourceMember, ResolutionContext context)
|
||||||
|
{
|
||||||
|
RepeatedField<rpcRoomItem> rpf = new RepeatedField<rpcRoomItem>();
|
||||||
|
foreach(var room in sourceMember)
|
||||||
|
{
|
||||||
|
rpf.Add(context.Mapper.Map<RoomItem, rpcRoomItem>(room));
|
||||||
|
}
|
||||||
|
|
||||||
|
return rpf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RepeatedFieldRpcRoomItemToEnumerableRoomItem : IValueConverter<RepeatedField<rpcRoomItem>, IEnumerable<RoomItem>>
|
||||||
|
{
|
||||||
|
|
||||||
|
public IEnumerable<RoomItem> Convert(RepeatedField<rpcRoomItem> sourceMember, ResolutionContext context)
|
||||||
|
{
|
||||||
|
List<RoomItem> roomList = new List<RoomItem>();
|
||||||
|
foreach(var room in sourceMember)
|
||||||
|
{
|
||||||
|
var rpc = context.Mapper.Map<rpcRoomItem, RoomItem>(room);
|
||||||
|
roomList.Add(rpc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return roomList;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using DGM.HoC.BookMng.API.Model;
|
||||||
|
using DGM.HoC.BookMng.API.RPC;
|
||||||
|
using Google.Protobuf.Collections;
|
||||||
|
using Microsoft.AspNetCore.Routing.Constraints;
|
||||||
|
|
||||||
|
namespace DGM.HoC.BookMng.API.Infrastructure.Mappers;
|
||||||
|
|
||||||
|
public class MappingProfile : Profile
|
||||||
|
{
|
||||||
|
public MappingProfile()
|
||||||
|
{
|
||||||
|
CreateMap<RoomType, rpcRoomType>();
|
||||||
|
CreateMap<rpcRoomType, RoomType>();
|
||||||
|
|
||||||
|
CreateMap<CustomerData, rpcBookingCustomer>();
|
||||||
|
CreateMap<rpcBookingCustomer, CustomerData>();
|
||||||
|
|
||||||
|
//TODO: Create mapping from String to DateTime.
|
||||||
|
CreateMap<string, DateTime>().ConvertUsing<StringToDateTimeConverter>();
|
||||||
|
CreateMap<DateTime, string>().ConvertUsing<DateTimeToStringConverter>();
|
||||||
|
|
||||||
|
CreateMap<RoomItem, rpcRoomItem>(MemberList.Destination)
|
||||||
|
.ForMember(dest => dest.ItemId, opt => opt.MapFrom(src => src.Id));
|
||||||
|
CreateMap<rpcRoomItem, RoomItem>(MemberList.Source)
|
||||||
|
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.ItemId));
|
||||||
|
|
||||||
|
CreateMap<Booking, rpcBooking>(MemberList.Destination);
|
||||||
|
CreateMap<rpcBooking, Booking>(MemberList.Source);
|
||||||
|
|
||||||
|
//TODO: Crear un Custom Type Converter: https://docs.automapper.org/en/stable/Custom-type-converters.html
|
||||||
|
CreateMap<Booking, rpcBookingDetails>()
|
||||||
|
.ForPath(dest => dest.Data.Id, opt => opt.MapFrom(src => src.Id))
|
||||||
|
.ForPath(dest => dest.Data.Description, opt => opt.MapFrom(src => src.Description))
|
||||||
|
.ForPath(dest => dest.Data.Status, opt => opt.MapFrom(src => src.Status))
|
||||||
|
.ForPath(dest => dest.Data.CustomerId, opt => opt.MapFrom(src => src.CustomerId))
|
||||||
|
.ForMember(dest => dest.Rooms, opt => opt.ConvertUsing<EnumerableRoomItemToRepeatedFieldRpcRoomItem, IEnumerable<RoomItem>>());
|
||||||
|
CreateMap<rpcBookingDetails, Booking>()
|
||||||
|
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Data.Id))
|
||||||
|
.ForMember(dest => dest.Description, opt => opt.MapFrom(src => src.Data.Description))
|
||||||
|
.ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.Data.Status))
|
||||||
|
.ForMember(dest => dest.CustomerId, opt => opt.MapFrom(src => src.Data.CustomerId))
|
||||||
|
.ForMember(dest => dest.Rooms, opt => opt.ConvertUsing<RepeatedFieldRpcRoomItemToEnumerableRoomItem, RepeatedField<rpcRoomItem>>());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using DGM.HoC.BookMng.API.Model;
|
||||||
|
using DGM.HoC.BookMng.API.RPC;
|
||||||
|
|
||||||
|
namespace DGM.HoC.BookMng.API.Infrastructure.Mappers;
|
||||||
|
|
||||||
|
public class RoomTypeMapper
|
||||||
|
{
|
||||||
|
public static RoomType MapRpcRoomTypeToRoomType(rpcRoomType rpc)
|
||||||
|
{
|
||||||
|
return new RoomType
|
||||||
|
{
|
||||||
|
Id = rpc.Id,
|
||||||
|
Name = rpc.Name,
|
||||||
|
Description = rpc.Description,
|
||||||
|
GuestAmount = rpc.GuestAmount
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static rpcRoomType MapRoomTypeToRpcRoomType(RoomType type)
|
||||||
|
{
|
||||||
|
return new rpcRoomType
|
||||||
|
{
|
||||||
|
Id = type.Id,
|
||||||
|
Name = type.Name,
|
||||||
|
Description = type.Description,
|
||||||
|
GuestAmount = type.GuestAmount
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace DGM.HoC.BookMng.API.Infrastructure.Mappers
|
||||||
|
{
|
||||||
|
public class StringToDateTimeConverter : ITypeConverter<string, DateTime>
|
||||||
|
{
|
||||||
|
|
||||||
|
public DateTime Convert(string source, DateTime destination, ResolutionContext context)
|
||||||
|
{
|
||||||
|
var esES = new CultureInfo("es-ES");
|
||||||
|
|
||||||
|
DateTime dateTime;
|
||||||
|
|
||||||
|
if (DateTime.TryParseExact(source, "dd/MM/yyyy", esES, DateTimeStyles.AllowLeadingWhite, out dateTime))
|
||||||
|
{
|
||||||
|
return dateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DateTime.UnixEpoch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class DateTimeToStringConverter : ITypeConverter<DateTime, string>
|
||||||
|
{
|
||||||
|
public string Convert(DateTime source, string destination, ResolutionContext context)
|
||||||
|
{
|
||||||
|
return source.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
namespace DGM.HoC.BookMng.API.Model;
|
||||||
|
|
||||||
|
public class Booking
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public string Status { get; set; }
|
||||||
|
|
||||||
|
public IEnumerable<RoomItem> Rooms { get; set; }
|
||||||
|
public CustomerData? BookingCustomer { get; set; }
|
||||||
|
public string? CustomerId { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace DGM.HoC.BookMng.API.Model;
|
||||||
|
|
||||||
|
public class CustomerData
|
||||||
|
{
|
||||||
|
public int BookingId { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Phone { get; set; }
|
||||||
|
public string Email { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
namespace DGM.HoC.BookMng.API.Model;
|
||||||
|
|
||||||
|
public class RoomItem
|
||||||
|
{
|
||||||
|
|
||||||
|
//Primary key (BookingId, Id).
|
||||||
|
public int BookingId { get; set; }
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public int TypeId { get; set; }
|
||||||
|
public int GuestAmount { get; set; }
|
||||||
|
public DateTime DateIn { get; set; }
|
||||||
|
public DateTime DateOut { get; set; }
|
||||||
|
public float PricePerNight { get; set; }
|
||||||
|
|
||||||
|
public string Notes { get; set; }
|
||||||
|
|
||||||
|
public RoomType RoomType { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace DGM.HoC.BookMng.API.Model;
|
||||||
|
|
||||||
|
public class RoomType
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public int GuestAmount { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using BookMng.API.Services;
|
||||||
|
using DGM.HoC.BookMng.API.Infrastructure;
|
||||||
|
using DGM.HoC.BookMng.API.Infrastructure.Mappers;
|
||||||
|
using DGM.HoC.BookMng.API.Services;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace BookMng.API
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
|
||||||
|
// Add services to the container.
|
||||||
|
builder.Services.AddGrpc();
|
||||||
|
builder.Services.AddGrpcReflection();
|
||||||
|
builder.Services.AddDbContext<BookingContext>(options =>
|
||||||
|
{
|
||||||
|
string? connString = builder.Environment.IsDevelopment() ? builder.Configuration["HOC:BOOKMNG:DB"] : builder.Configuration["HOC_DBString"];
|
||||||
|
options.UseMySql(connString, new MariaDbServerVersion(new Version(10, 11, 2)));
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.AddAutoMapper(typeof(MappingProfile));
|
||||||
|
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
app.Logger.LogInformation("App builded");
|
||||||
|
|
||||||
|
if (app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.MapGrpcReflectionService();
|
||||||
|
|
||||||
|
//Test Automapper config.
|
||||||
|
TestAutoMapperConfig(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateDbIfNotExists(app);
|
||||||
|
|
||||||
|
// Configure the HTTP request pipeline.
|
||||||
|
app.MapGrpcService<BookMngService>();
|
||||||
|
app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client.");
|
||||||
|
|
||||||
|
app.Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void CreateDbIfNotExists(WebApplication app)
|
||||||
|
{
|
||||||
|
using (var scope = app.Services.CreateScope())
|
||||||
|
{
|
||||||
|
var sp = scope.ServiceProvider;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var dbContext = sp.GetRequiredService<BookingContext>();
|
||||||
|
//dbContext.Database.EnsureDeleted();
|
||||||
|
dbContext.Database.EnsureCreated();
|
||||||
|
BookingContextSeed.InitData(dbContext);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
app.Logger.LogError($"Error creating db - {e.Message}: {e.StackTrace} ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TestAutoMapperConfig(WebApplication app)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
app.Logger.LogInformation("Validating Automapper mapping config...");
|
||||||
|
var mapper = app.Services.GetRequiredService<IMapper>();
|
||||||
|
mapper.ConfigurationProvider.AssertConfigurationIsValid();
|
||||||
|
app.Logger.LogInformation("Automapper mapping config is valid");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
app.Logger.LogError($"Error creating Mapping - {e.Message}: {e.StackTrace} ");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*var mapperConfig = new MapperConfiguration(cfg =>
|
||||||
|
{
|
||||||
|
cfg.AddProfile<MappingProfile>();
|
||||||
|
});
|
||||||
|
mapperConfig.AssertConfigurationIsValid();*/
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
},
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"applicationUrl": "http://localhost:5162"
|
||||||
|
},
|
||||||
|
"https": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
},
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"applicationUrl": "https://localhost:7029;http://localhost:5162"
|
||||||
|
},
|
||||||
|
"Docker": {
|
||||||
|
"commandName": "Docker",
|
||||||
|
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
|
||||||
|
"publishAllPorts": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
option csharp_namespace = "DGM.HoC.BookMng.API.RPC";
|
||||||
|
|
||||||
|
package HoC;
|
||||||
|
|
||||||
|
service rpcBookMng {
|
||||||
|
rpc GetAllRoomTypes (EmptyReq) returns (GetAllRoomTypesResponse);
|
||||||
|
rpc GetRoomTypeById (RoomTypeId) returns (rpcRoomType);
|
||||||
|
|
||||||
|
rpc AddRoomType (rpcRoomType) returns (rpcRoomType);
|
||||||
|
rpc RemoveRoomType (RoomTypeId) returns (EmptyResponse);
|
||||||
|
rpc UpdateRoomType (rpcRoomType) returns (rpcRoomType);
|
||||||
|
|
||||||
|
|
||||||
|
rpc GetAllBookings (EmptyReq) returns (GetAllBookingsResponse);
|
||||||
|
rpc GetBookingById (BookingId) returns (rpcBooking);
|
||||||
|
rpc GetBookingDetailsById (BookingId) returns (rpcBookingDetails);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Cuando creas un Booking no es sólo el booking,
|
||||||
|
también contiene una lista de RoomItems y el customerData.
|
||||||
|
Igual ocurre con el Remove, pues son dependientes.
|
||||||
|
*/
|
||||||
|
rpc AddBooking (rpcBookingDetails) returns (rpcBookingDetails);
|
||||||
|
rpc RemoveBooking (BookingId) returns (EmptyResponse);
|
||||||
|
rpc UpdateBooking (rpcBooking) returns (rpcBooking);
|
||||||
|
|
||||||
|
|
||||||
|
rpc GetRoomItemsByBooking (BookingId) returns (rpcRoomItemList);
|
||||||
|
rpc AddRoomItem (rpcRoomItem) returns (rpcRoomItem);
|
||||||
|
rpc RemoveRoomItem (RoomItemId) returns (EmptyResponse);
|
||||||
|
rpc UpdateRoomItem (rpcRoomItem) returns (rpcRoomItem);
|
||||||
|
|
||||||
|
// Los datos de clientes no se pueden crear ni eliminar, sólo actualizar.
|
||||||
|
// Dependen del booking.
|
||||||
|
rpc GetBookingCustomer (BookingId) returns (rpcBookingCustomer);
|
||||||
|
rpc UpdateBookingCustomer (rpcBookingCustomer) returns (rpcBookingCustomer);
|
||||||
|
}
|
||||||
|
|
||||||
|
message EmptyReq {}
|
||||||
|
message EmptyResponse {
|
||||||
|
string msg = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RoomTypeId {
|
||||||
|
int32 id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RoomItemId {
|
||||||
|
int32 bookingId = 1;
|
||||||
|
int32 itemId = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BookingId {
|
||||||
|
int32 bookingId = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetAllRoomTypesResponse {
|
||||||
|
repeated rpcRoomType roomTypeList = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message GetAllBookingsResponse {
|
||||||
|
repeated rpcBooking bookingList = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message rpcRoomType {
|
||||||
|
int32 id = 1;
|
||||||
|
string name = 2;
|
||||||
|
string description = 3;
|
||||||
|
int32 guestAmount = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message rpcRoomItem {
|
||||||
|
int32 bookingId = 1;
|
||||||
|
int32 itemId = 2;
|
||||||
|
int32 guestAmount = 3;
|
||||||
|
string dateIn = 4;
|
||||||
|
string dateOut = 5;
|
||||||
|
float pricePerNight = 6;
|
||||||
|
string notes = 7;
|
||||||
|
int32 typeId = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
message rpcRoomItemList {
|
||||||
|
repeated rpcRoomItem roomItemList = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message rpcBookingCustomer {
|
||||||
|
int32 bookingId = 1;
|
||||||
|
string name = 2;
|
||||||
|
string phone = 3;
|
||||||
|
string email = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message rpcBooking {
|
||||||
|
int32 id = 1;
|
||||||
|
string description = 2;
|
||||||
|
string status = 3;
|
||||||
|
string customerId = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message rpcBookingDetails {
|
||||||
|
rpcBooking data = 1;
|
||||||
|
rpcBookingCustomer bookingCustomer = 2;
|
||||||
|
repeated rpcRoomItem rooms = 3;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
option csharp_namespace = "BookMng.API";
|
||||||
|
|
||||||
|
package greet;
|
||||||
|
|
||||||
|
// The greeting service definition.
|
||||||
|
service Greeter {
|
||||||
|
// Sends a greeting
|
||||||
|
rpc SayHello (HelloRequest) returns (HelloReply);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The request message containing the user's name.
|
||||||
|
message HelloRequest {
|
||||||
|
string name = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The response message containing the greetings.
|
||||||
|
message HelloReply {
|
||||||
|
string message = 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,279 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using DGM.HoC.BookMng.API.Infrastructure;
|
||||||
|
using DGM.HoC.BookMng.API.Model;
|
||||||
|
using DGM.HoC.BookMng.API.RPC;
|
||||||
|
using Grpc.Core;
|
||||||
|
using Microsoft.AspNetCore.Cors.Infrastructure;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace DGM.HoC.BookMng.API.Services;
|
||||||
|
|
||||||
|
public class BookMngService : rpcBookMng.rpcBookMngBase
|
||||||
|
{
|
||||||
|
private readonly ILogger<BookMngService> _logger;
|
||||||
|
private readonly BookingContext _context;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
public BookMngService(BookingContext context, ILogger<BookMngService> logger, IMapper mapper)
|
||||||
|
{
|
||||||
|
_context = context ?? throw new ArgumentNullException("context");
|
||||||
|
_logger = logger;
|
||||||
|
_mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region RoomType
|
||||||
|
|
||||||
|
public override async Task<GetAllRoomTypesResponse> GetAllRoomTypes(EmptyReq request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Begin grpc call BookMngService.GetAllRoomTypes");
|
||||||
|
|
||||||
|
var roomTypeList = await _context.RoomTypes.ToListAsync();
|
||||||
|
|
||||||
|
GetAllRoomTypesResponse response = new GetAllRoomTypesResponse();
|
||||||
|
foreach (var c in roomTypeList)
|
||||||
|
{
|
||||||
|
response.RoomTypeList.Add(_mapper.Map<RoomType, rpcRoomType>(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<rpcRoomType> GetRoomTypeById(RoomTypeId request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Begin grpc call BookMngService.GetRoomTypeById");
|
||||||
|
|
||||||
|
var roomType = await _context.RoomTypes.FindAsync(request.Id);
|
||||||
|
|
||||||
|
if (roomType == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("BookMngService.GetRoomTypeById - RoomType not found");
|
||||||
|
throw new RpcException(new Status(StatusCode.NotFound, $"Not Found RoomType with ID {request.Id}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return _mapper.Map<RoomType, rpcRoomType>(roomType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<rpcRoomType> AddRoomType(rpcRoomType request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Begin grpc call BookMngService.AddRoomType");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var roomTypeToAdd = _mapper.Map<rpcRoomType, RoomType>(request);
|
||||||
|
|
||||||
|
//Check if ID already exists. Find podría ser Async...
|
||||||
|
if (_context.RoomTypes.Find(roomTypeToAdd.Id) != null)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.AlreadyExists, $"Already exists RoomType with ID {roomTypeToAdd.Id}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
//No hacer async para el Add.
|
||||||
|
var item = _context.RoomTypes.Add(roomTypeToAdd);
|
||||||
|
int rows = await _context.SaveChangesAsync();
|
||||||
|
_logger.LogInformation($"BookMngService.AddRoomType: Added {rows} RoomTypes");
|
||||||
|
|
||||||
|
if (rows < 1)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.DataLoss, "RoomType can't be saved"));
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Status = new Status(StatusCode.OK, $"RoomType with ID: {roomTypeToAdd.Id} added");
|
||||||
|
return _mapper.Map<RoomType, rpcRoomType>(item.Entity);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError($"BookMngService.GetBookingById - Error adding RoomType: {e.Message} - {e.StackTrace}");
|
||||||
|
throw new RpcException(new Status(StatusCode.Aborted, "Error adding RoomType: " + e.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Bookings
|
||||||
|
|
||||||
|
public override async Task<GetAllBookingsResponse> GetAllBookings(EmptyReq request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Begin grpc call BookMngService.GetAllBookings");
|
||||||
|
|
||||||
|
var bookingList = await _context.Bookings.ToListAsync();
|
||||||
|
|
||||||
|
GetAllBookingsResponse response = new GetAllBookingsResponse();
|
||||||
|
foreach (var b in bookingList)
|
||||||
|
{
|
||||||
|
response.BookingList.Add(_mapper.Map<Booking, rpcBooking>(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<rpcBooking> GetBookingById(BookingId request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Begin grpc call BookMngService.GetBookingById");
|
||||||
|
|
||||||
|
var booking = await _context.Bookings.FindAsync(request.BookingId_);
|
||||||
|
|
||||||
|
if (booking == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("BookMngService.GetBookingById - Booking not found");
|
||||||
|
throw new RpcException(new Status(StatusCode.NotFound, $"Not Found Booking with ID {request.BookingId_}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return _mapper.Map<Booking, rpcBooking>(booking);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override async Task<rpcBookingDetails> AddBooking(rpcBookingDetails request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Begin grpc call BookMngService.AddBooking");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var bookingToAdd = _mapper.Map<rpcBookingDetails, Booking>(request);
|
||||||
|
|
||||||
|
//Check if ID already exists. Find podría ser Async...
|
||||||
|
if (_context.Bookings.Find(bookingToAdd.Id) != null)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.AlreadyExists, $"Already exists Booking with ID {bookingToAdd.Id}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
//No hacer async para el Add.
|
||||||
|
var item = _context.Bookings.Add(bookingToAdd);
|
||||||
|
|
||||||
|
//Añadir CustomerData en caso de tenerlo.
|
||||||
|
if(bookingToAdd.BookingCustomer != null)
|
||||||
|
{
|
||||||
|
// NOTA: Comprobar si funciona esto, o hay que conseguir el ID de otra forma. En cualquier caso, no se debería de poder recibir un ID desde gRPC para la creación
|
||||||
|
//Actualizamos el ID.
|
||||||
|
bookingToAdd.BookingCustomer.BookingId = item.Entity.Id;
|
||||||
|
_context.BookingCustomerData.Add(bookingToAdd.BookingCustomer);
|
||||||
|
}
|
||||||
|
foreach(var room in bookingToAdd.Rooms)
|
||||||
|
{
|
||||||
|
room.BookingId = item.Entity.Id;
|
||||||
|
_context.RoomItems.Add(room);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rows = await _context.SaveChangesAsync();
|
||||||
|
_logger.LogInformation($"BookMngService.AddBooking: Added {rows} Booking");
|
||||||
|
|
||||||
|
if (rows < 1)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.DataLoss, "Booking can't be saved"));
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Status = new Status(StatusCode.OK, $"Booking with ID: {bookingToAdd.Id} added");
|
||||||
|
return _mapper.Map<Booking, rpcBookingDetails>(item.Entity);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError($"BookMngService.GetBookingById - Error adding Booking: {e.Message} - {e.StackTrace}");
|
||||||
|
throw new RpcException(new Status(StatusCode.Aborted, "Error adding Booking: " + e.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region BookingItem
|
||||||
|
|
||||||
|
public override async Task<rpcRoomItemList> GetRoomItemsByBooking(BookingId request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Begin grpc call BookMngService.GetRoomItemsByBooking");
|
||||||
|
|
||||||
|
|
||||||
|
var roomList = await _context.RoomItems.Where(ri => ri.BookingId == request.BookingId_).ToListAsync();
|
||||||
|
|
||||||
|
rpcRoomItemList response = new rpcRoomItemList();
|
||||||
|
foreach (var ri in roomList)
|
||||||
|
{
|
||||||
|
response.RoomItemList.Add(_mapper.Map<RoomItem, rpcRoomItem>(ri));
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<rpcRoomItem> AddRoomItem(rpcRoomItem request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Begin grpc call BookMngService.AddRoomItem");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var roomItemToAdd = _mapper.Map<rpcRoomItem, RoomItem>(request);
|
||||||
|
|
||||||
|
//Check if ID already exists. Find podría ser Async...
|
||||||
|
if (_context.RoomItems.Find(roomItemToAdd.Id, roomItemToAdd.BookingId) != null)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.AlreadyExists, $"Already exists RoomItem with ID: {roomItemToAdd.BookingId} - {roomItemToAdd.Id}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get Last ID for given booking.
|
||||||
|
var lastItem = await _context.RoomItems.Where(ri => ri.BookingId == request.BookingId).OrderBy(ri => ri.Id).LastOrDefaultAsync();
|
||||||
|
roomItemToAdd.Id = lastItem is null ? 1 : lastItem.Id;
|
||||||
|
|
||||||
|
//No hacer async para el Add.
|
||||||
|
var item = _context.RoomItems.Add(roomItemToAdd);
|
||||||
|
|
||||||
|
int rows = await _context.SaveChangesAsync();
|
||||||
|
_logger.LogInformation($"BookMngService.AddRoomItem: Added {rows} RoomItem");
|
||||||
|
|
||||||
|
if (rows < 1)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.DataLoss, "RoomItem can't be saved"));
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Status = new Status(StatusCode.OK, $"RoomItem with ID: {roomItemToAdd.BookingId} - {roomItemToAdd.Id} added");
|
||||||
|
return _mapper.Map<RoomItem, rpcRoomItem>(item.Entity);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.Aborted, "Error adding RoomItem: " + e.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<EmptyResponse> RemoveRoomItem(RoomItemId request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Begin grpc call BookMngService.RemoveRoomItem");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
RoomItem? roomItem = await _context.RoomItems.FindAsync(request.BookingId, request.ItemId);
|
||||||
|
if (roomItem == null)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.NotFound, $"RoomItem ID: {request.BookingId} - {request.ItemId} not fount"));
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.RoomItems.Remove(roomItem);
|
||||||
|
int rows = await _context.SaveChangesAsync();
|
||||||
|
if (rows < 1)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.DataLoss, "RoomItem can't be deleted"));
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Status = new Status(StatusCode.OK, $"RoomItem with ID: {request.BookingId} - {request.ItemId} deleted");
|
||||||
|
return new EmptyResponse() { Msg = $"RoomItem Deleted - ID {request.BookingId} - {request.ItemId}" };
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.Aborted, "Error removing RoomItem: " + e.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region BookingCustomerData
|
||||||
|
|
||||||
|
public override Task<rpcBookingCustomer> GetBookingCustomer(BookingId request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
return base.GetBookingCustomer(request, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using BookMng.API;
|
||||||
|
using Grpc.Core;
|
||||||
|
|
||||||
|
namespace BookMng.API.Services
|
||||||
|
{
|
||||||
|
public class GreeterService : Greeter.GreeterBase
|
||||||
|
{
|
||||||
|
private readonly ILogger<GreeterService> _logger;
|
||||||
|
public GreeterService(ILogger<GreeterService> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
return Task.FromResult(new HelloReply
|
||||||
|
{
|
||||||
|
Message = "Hello " + request.Name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"Kestrel": {
|
||||||
|
"EndpointDefaults": {
|
||||||
|
"Protocols": "Http2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<UserSecretsId>9c59ecba-0968-470b-8791-9d83a1b8c2f1</UserSecretsId>
|
||||||
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
|
<RootNamespace>DGM.HoC.$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="Protos\CRM.proto" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Protobuf Include="Protos\CRM.proto" GrpcServices="Server" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="AutoMapper" Version="12.0.1" />
|
||||||
|
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
|
||||||
|
<PackageReference Include="Grpc.AspNetCore" Version="2.56.0" />
|
||||||
|
<PackageReference Include="Grpc.AspNetCore.Server.Reflection" Version="2.56.0" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.10" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.10">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
|
||||||
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="7.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 80
|
||||||
|
EXPOSE 443
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
|
||||||
|
WORKDIR /src
|
||||||
|
COPY ["CRM.API/CRM.API.csproj", "CRM.API/"]
|
||||||
|
RUN dotnet restore "CRM.API/CRM.API.csproj"
|
||||||
|
COPY . .
|
||||||
|
WORKDIR "/src/CRM.API"
|
||||||
|
RUN dotnet build "CRM.API.csproj" -c Release -o /app/build
|
||||||
|
|
||||||
|
FROM build AS publish
|
||||||
|
RUN dotnet publish "CRM.API.csproj" -c Release -o /app/publish /p:UseAppHost=false
|
||||||
|
|
||||||
|
FROM base AS final
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=publish /app/publish .
|
||||||
|
ENTRYPOINT ["dotnet", "CRM.API.dll"]
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using DGM.HoC.CRM.API.Infrastructure.EntityConfigs;
|
||||||
|
using DGM.HoC.CRM.API.Model;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace DGM.HoC.CRM.API.Infrastructure;
|
||||||
|
|
||||||
|
public class CRMContext : DbContext
|
||||||
|
{
|
||||||
|
public CRMContext(DbContextOptions<CRMContext> options)
|
||||||
|
: base(options)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public DbSet<Customer> Customers { get; set; }
|
||||||
|
public DbSet<ContactDetails> ContactDetails { get; set; }
|
||||||
|
public DbSet<Note> Notes { get; set; }
|
||||||
|
|
||||||
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
modelBuilder.ApplyConfiguration(new CustomerTypeEntityConfig());
|
||||||
|
base.OnModelCreating(modelBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
using DGM.HoC.CRM.API.Model;
|
||||||
|
|
||||||
|
namespace DGM.HoC.CRM.API.Infrastructure
|
||||||
|
{
|
||||||
|
public class CRMContextSeed
|
||||||
|
{
|
||||||
|
public static void InitData(CRMContext dbContext)
|
||||||
|
{
|
||||||
|
if(dbContext.Customers.Count() == 0)
|
||||||
|
{
|
||||||
|
InitCustomerData(dbContext);
|
||||||
|
dbContext.SaveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (dbContext.Notes.Count() == 0)
|
||||||
|
{
|
||||||
|
InitCustomerNotes(dbContext);
|
||||||
|
dbContext.SaveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void InitCustomerData(CRMContext dbContext)
|
||||||
|
{
|
||||||
|
dbContext.Customers.Add(new Model.Customer
|
||||||
|
{
|
||||||
|
NIF = "N12345",
|
||||||
|
Name = "Bilbo Bolsón",
|
||||||
|
CustomerAddress = new Address
|
||||||
|
{
|
||||||
|
TipoVia = "Ciudad",
|
||||||
|
Poblacion = "Rivendel"
|
||||||
|
},
|
||||||
|
CreateTime = DateTime.Now,
|
||||||
|
LastUsedTime = DateTime.Now
|
||||||
|
});
|
||||||
|
|
||||||
|
dbContext.Customers.Add(new Model.Customer
|
||||||
|
{
|
||||||
|
NIF = "N98765",
|
||||||
|
Name = "Frodo Bolsón",
|
||||||
|
CustomerAddress = new Address
|
||||||
|
{
|
||||||
|
TipoVia = "Ciudad",
|
||||||
|
Poblacion = "Bolson Cerrado"
|
||||||
|
},
|
||||||
|
CreateTime = DateTime.Now,
|
||||||
|
LastUsedTime = DateTime.Now
|
||||||
|
});
|
||||||
|
|
||||||
|
dbContext.Customers.Add(new Model.Customer
|
||||||
|
{
|
||||||
|
NIF = "N567432",
|
||||||
|
Name = "Gandalf el Gris",
|
||||||
|
CustomerAddress = new Address(),
|
||||||
|
CreateTime = DateTime.Now,
|
||||||
|
LastUsedTime = DateTime.Now
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void InitCustomerNotes(CRMContext dbContext)
|
||||||
|
{
|
||||||
|
dbContext.Notes.Add(new Model.Note
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
CustomerNIF = "N12345",
|
||||||
|
Status = "Abierto",
|
||||||
|
Data = "Solicitud de pergamino y material de escritura",
|
||||||
|
CreateTime = DateTime.Now
|
||||||
|
});
|
||||||
|
|
||||||
|
dbContext.Notes.Add(new Model.Note
|
||||||
|
{
|
||||||
|
Id = 2,
|
||||||
|
CustomerNIF = "N98765",
|
||||||
|
Status = "Cerrado",
|
||||||
|
Data = "Incluir establo para caballos",
|
||||||
|
CreateTime = DateTime.Now
|
||||||
|
});
|
||||||
|
|
||||||
|
dbContext.Notes.Add(new Model.Note
|
||||||
|
{
|
||||||
|
Id = 3,
|
||||||
|
CustomerNIF = "N98765",
|
||||||
|
Status = "Abierto",
|
||||||
|
Data = "Vendrá Sam a pagar la semana que viene",
|
||||||
|
CreateTime = DateTime.Now
|
||||||
|
});
|
||||||
|
|
||||||
|
dbContext.Notes.Add(new Model.Note
|
||||||
|
{
|
||||||
|
Id = 4,
|
||||||
|
CustomerNIF = "N567432",
|
||||||
|
Status = "Cerrado",
|
||||||
|
Data = "Nos ha dejado un recado para un hobbit",
|
||||||
|
CreateTime = DateTime.Now
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using DGM.HoC.CRM.API.Model;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace DGM.HoC.CRM.API.Infrastructure.EntityConfigs;
|
||||||
|
|
||||||
|
public class CustomerTypeEntityConfig : IEntityTypeConfiguration<Customer>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<Customer> builder)
|
||||||
|
{
|
||||||
|
//PK
|
||||||
|
builder.HasKey(e => e.NIF);
|
||||||
|
|
||||||
|
//Address Data Class.
|
||||||
|
builder.OwnsOne(e => e.CustomerAddress);
|
||||||
|
|
||||||
|
//FK - ContactData
|
||||||
|
builder.HasMany(e => e.ContactDetails)
|
||||||
|
.WithOne(cd => cd.Customer)
|
||||||
|
.HasForeignKey(cd => cd.CustomerNIF)
|
||||||
|
.IsRequired()
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
//FK - Notes
|
||||||
|
builder.HasMany(e => e.Notes)
|
||||||
|
.WithOne(note => note.Customer)
|
||||||
|
.HasForeignKey(note => note.CustomerNIF)
|
||||||
|
.IsRequired()
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using Google.Protobuf.WellKnownTypes;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Reflection.Metadata.Ecma335;
|
||||||
|
|
||||||
|
namespace DGM.HoC.CRM.API.Infrastructure.Mappers
|
||||||
|
{
|
||||||
|
public class StringToDateTimeConverter : ITypeConverter<string, DateTime>
|
||||||
|
{
|
||||||
|
|
||||||
|
public DateTime Convert(string source, DateTime destination, ResolutionContext context)
|
||||||
|
{
|
||||||
|
var esES = new CultureInfo("es-ES");
|
||||||
|
|
||||||
|
DateTime dateTime;
|
||||||
|
|
||||||
|
if (DateTime.TryParseExact(source, "dd/MM/yyyy", esES, DateTimeStyles.AllowLeadingWhite, out dateTime))
|
||||||
|
{
|
||||||
|
return dateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DateTime.UnixEpoch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class DateTimeToStringConverter : ITypeConverter<DateTime, string>
|
||||||
|
{
|
||||||
|
public string Convert(DateTime source, string destination, ResolutionContext context)
|
||||||
|
{
|
||||||
|
return source.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DateTimeToTimeStamp : ITypeConverter<DateTime, Google.Protobuf.WellKnownTypes.Timestamp>
|
||||||
|
{
|
||||||
|
public Timestamp Convert(DateTime source, Timestamp destination, ResolutionContext context)
|
||||||
|
{
|
||||||
|
//Timestamp.FromDateTime() requiere el time en UTC.
|
||||||
|
DateTime utcTime = source.Kind == DateTimeKind.Utc ? source : source.ToUniversalTime();
|
||||||
|
return Timestamp.FromDateTime(utcTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TimeStampToDateTime : ITypeConverter<Google.Protobuf.WellKnownTypes.Timestamp, DateTime>
|
||||||
|
{
|
||||||
|
public DateTime Convert(Timestamp source, DateTime destination, ResolutionContext context)
|
||||||
|
{
|
||||||
|
if(source is null)
|
||||||
|
{
|
||||||
|
return DateTime.UnixEpoch;
|
||||||
|
}
|
||||||
|
return source.ToDateTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using DGM.HoC.CRM.API.Model;
|
||||||
|
using DGM.HoC.CRM.API.RPC;
|
||||||
|
|
||||||
|
namespace DGM.HoC.CRM.API.Infrastructure.Mappers
|
||||||
|
{
|
||||||
|
public class MappingProfile : Profile
|
||||||
|
{
|
||||||
|
public MappingProfile()
|
||||||
|
{
|
||||||
|
//DateTime <--> String Mapping.
|
||||||
|
CreateMap<string, DateTime>().ConvertUsing<StringToDateTimeConverter>();
|
||||||
|
CreateMap<DateTime, string>().ConvertUsing<DateTimeToStringConverter>();
|
||||||
|
|
||||||
|
CreateMap<Google.Protobuf.WellKnownTypes.Timestamp, DateTime>().ConvertUsing<TimeStampToDateTime>();
|
||||||
|
CreateMap<DateTime, Google.Protobuf.WellKnownTypes.Timestamp>().ConvertUsing<DateTimeToTimeStamp>();
|
||||||
|
|
||||||
|
|
||||||
|
CreateMap<rpcAddress, Address>();
|
||||||
|
CreateMap<Address, rpcAddress>().ForAllMembers(map => map.NullSubstitute(""));
|
||||||
|
|
||||||
|
CreateMap<ContactDetails, rpcContact>(MemberList.Destination);
|
||||||
|
CreateMap<rpcContact, ContactDetails>(MemberList.Source);
|
||||||
|
|
||||||
|
CreateMap<Customer, rpcCustomer>(MemberList.Destination);
|
||||||
|
CreateMap<rpcCustomer, Customer>(MemberList.Source);
|
||||||
|
|
||||||
|
CreateMap<Note, rpcNote>(MemberList.Destination);
|
||||||
|
CreateMap<rpcNote, Note>(MemberList.Source);
|
||||||
|
|
||||||
|
|
||||||
|
CreateMap<Customer, rpcAddCustomerReq>(MemberList.Destination);
|
||||||
|
CreateMap<Customer, rpcUpdateCustomerReq>(MemberList.Destination);
|
||||||
|
CreateMap<rpcAddCustomerReq, Customer>(MemberList.Source);
|
||||||
|
//CreateMap<rpcUpdateCustomerReq, Customer>(MemberList.None);
|
||||||
|
|
||||||
|
CreateMap<Note, rpcAddNoteReq>(MemberList.Destination);
|
||||||
|
CreateMap<Note, rpcUpdateNoteReq>(MemberList.Destination);
|
||||||
|
CreateMap<rpcAddNoteReq, Note>(MemberList.Source);
|
||||||
|
//CreateMap<rpcUpdateNoteReq, Note>(MemberList.None);
|
||||||
|
|
||||||
|
CreateMap<ContactDetails, rpcAddContactDetailsReq>(MemberList.Destination);
|
||||||
|
CreateMap<ContactDetails, rpcUpdateContactDetailsReq>(MemberList.Destination);
|
||||||
|
CreateMap<rpcAddContactDetailsReq, ContactDetails>(MemberList.Source);
|
||||||
|
//CreateMap<rpcUpdateContactDetailsReq, ContactDetails>(MemberList.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using DGM.HoC.CRM.API.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DGM.HoC.CRM.API.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(CRMContext))]
|
||||||
|
[Migration("20230901133350_addressData-Tests")]
|
||||||
|
partial class addressDataTests
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "7.0.10")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||||
|
|
||||||
|
modelBuilder.Entity("DGM.HoC.CRM.API.Model.Customer", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("NIF")
|
||||||
|
.HasColumnType("varchar(255)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreateDate")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastUsedDate")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Phone")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("NIF");
|
||||||
|
|
||||||
|
b.ToTable("Customers");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DGM.HoC.CRM.API.Model.Note", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreationDate")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("CustomerNIF")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("varchar(255)");
|
||||||
|
|
||||||
|
b.Property<string>("Data")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Status")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CustomerNIF");
|
||||||
|
|
||||||
|
b.ToTable("Notes");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DGM.HoC.CRM.API.Model.Customer", b =>
|
||||||
|
{
|
||||||
|
b.OwnsOne("DGM.HoC.CRM.API.Model.Address", "AddressData", b1 =>
|
||||||
|
{
|
||||||
|
b1.Property<string>("CustomerNIF")
|
||||||
|
.HasColumnType("varchar(255)");
|
||||||
|
|
||||||
|
b1.Property<string>("CodigoPostal")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b1.Property<string>("NombreVia")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b1.Property<string>("Numero")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b1.Property<string>("Pais")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b1.Property<string>("Poblacion")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b1.Property<string>("Provincia")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b1.Property<string>("TipoVia")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b1.HasKey("CustomerNIF");
|
||||||
|
|
||||||
|
b1.ToTable("Customers");
|
||||||
|
|
||||||
|
b1.WithOwner()
|
||||||
|
.HasForeignKey("CustomerNIF");
|
||||||
|
});
|
||||||
|
|
||||||
|
b.Navigation("AddressData")
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DGM.HoC.CRM.API.Model.Note", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DGM.HoC.CRM.API.Model.Customer", "Customer")
|
||||||
|
.WithMany("Notes")
|
||||||
|
.HasForeignKey("CustomerNIF")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Customer");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DGM.HoC.CRM.API.Model.Customer", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Notes");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DGM.HoC.CRM.API.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class addressDataTests : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterDatabase()
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Customers",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
NIF = table.Column<string>(type: "varchar(255)", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Name = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
AddressData_TipoVia = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
AddressData_NombreVia = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
AddressData_Numero = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
AddressData_Provincia = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
AddressData_Poblacion = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
AddressData_CodigoPostal = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
AddressData_Pais = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Phone = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Email = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
CreateDate = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||||
|
LastUsedDate = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Customers", x => x.NIF);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Notes",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
CustomerNIF = table.Column<string>(type: "varchar(255)", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Data = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
CreationDate = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||||
|
Status = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4")
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Notes", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Notes_Customers_CustomerNIF",
|
||||||
|
column: x => x.CustomerNIF,
|
||||||
|
principalTable: "Customers",
|
||||||
|
principalColumn: "NIF",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Notes_CustomerNIF",
|
||||||
|
table: "Notes",
|
||||||
|
column: "CustomerNIF");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Notes");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Customers");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using DGM.HoC.CRM.API.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DGM.HoC.CRM.API.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(CRMContext))]
|
||||||
|
partial class CRMContextModelSnapshot : ModelSnapshot
|
||||||
|
{
|
||||||
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "7.0.10")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||||
|
|
||||||
|
modelBuilder.Entity("DGM.HoC.CRM.API.Model.Customer", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("NIF")
|
||||||
|
.HasColumnType("varchar(255)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreateDate")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastUsedDate")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Phone")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("NIF");
|
||||||
|
|
||||||
|
b.ToTable("Customers");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DGM.HoC.CRM.API.Model.Note", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreationDate")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("CustomerNIF")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("varchar(255)");
|
||||||
|
|
||||||
|
b.Property<string>("Data")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Status")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CustomerNIF");
|
||||||
|
|
||||||
|
b.ToTable("Notes");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DGM.HoC.CRM.API.Model.Customer", b =>
|
||||||
|
{
|
||||||
|
b.OwnsOne("DGM.HoC.CRM.API.Model.Address", "AddressData", b1 =>
|
||||||
|
{
|
||||||
|
b1.Property<string>("CustomerNIF")
|
||||||
|
.HasColumnType("varchar(255)");
|
||||||
|
|
||||||
|
b1.Property<string>("CodigoPostal")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b1.Property<string>("NombreVia")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b1.Property<string>("Numero")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b1.Property<string>("Pais")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b1.Property<string>("Poblacion")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b1.Property<string>("Provincia")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b1.Property<string>("TipoVia")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b1.HasKey("CustomerNIF");
|
||||||
|
|
||||||
|
b1.ToTable("Customers");
|
||||||
|
|
||||||
|
b1.WithOwner()
|
||||||
|
.HasForeignKey("CustomerNIF");
|
||||||
|
});
|
||||||
|
|
||||||
|
b.Navigation("AddressData")
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DGM.HoC.CRM.API.Model.Note", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DGM.HoC.CRM.API.Model.Customer", "Customer")
|
||||||
|
.WithMany("Notes")
|
||||||
|
.HasForeignKey("CustomerNIF")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Customer");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DGM.HoC.CRM.API.Model.Customer", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Notes");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace DGM.HoC.CRM.API.Model
|
||||||
|
{
|
||||||
|
[Owned]
|
||||||
|
public class Address
|
||||||
|
{
|
||||||
|
public string? TipoVia { get; set; }
|
||||||
|
public string? NombreVia { get; set; }
|
||||||
|
public string? Numero { get; set; }
|
||||||
|
public string? Provincia { get; set; }
|
||||||
|
public string? Poblacion { get; set; }
|
||||||
|
public string? CodigoPostal { get; set; }
|
||||||
|
public string? Pais { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
Type AddressType = GetType();
|
||||||
|
|
||||||
|
if(obj is null || obj.GetType() != AddressType)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var props = AddressType.GetProperties();
|
||||||
|
foreach(var p in props)
|
||||||
|
{
|
||||||
|
if(p.GetValue(obj) != p.GetValue(this))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
namespace DGM.HoC.CRM.API.Model;
|
||||||
|
|
||||||
|
public class ContactDetails
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public string Type { get; set; }
|
||||||
|
public string Value { get; set; }
|
||||||
|
public string? Comment { get; set; }
|
||||||
|
|
||||||
|
public ContactDetails(string type, string value, string? comment)
|
||||||
|
{
|
||||||
|
Type = type ?? throw new ArgumentNullException(nameof(type));
|
||||||
|
Value = value ?? throw new ArgumentNullException(nameof(value));
|
||||||
|
Comment = comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public string CustomerNIF { get; set; }
|
||||||
|
public Customer Customer { get; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace DGM.HoC.CRM.API.Model;
|
||||||
|
|
||||||
|
public class Customer
|
||||||
|
{
|
||||||
|
public string NIF { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public Address CustomerAddress { get; set; }
|
||||||
|
public ICollection<ContactDetails> ContactDetails { get; } = new List<ContactDetails>();
|
||||||
|
|
||||||
|
|
||||||
|
public DateTime CreateTime { get; set; }
|
||||||
|
public DateTime LastUsedTime { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public ICollection<Note> Notes { get; } = new List<Note>();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
namespace DGM.HoC.CRM.API.Model;
|
||||||
|
|
||||||
|
public class Note
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string CustomerNIF { get; set; }
|
||||||
|
public string Data { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public string Status { get; set; }
|
||||||
|
public DateTime CreateTime { get; set; }
|
||||||
|
|
||||||
|
public Customer Customer { get; }
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using DGM.HoC.CRM.API.Infrastructure;
|
||||||
|
using DGM.HoC.CRM.API.Infrastructure.Mappers;
|
||||||
|
using DGM.HoC.CRM.API.Services;
|
||||||
|
using Microsoft.AspNetCore.Hosting.Server;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace DGM.HoC.CRM.API;
|
||||||
|
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
// Add services to the container.
|
||||||
|
builder.Services.AddGrpc();
|
||||||
|
builder.Services.AddGrpcReflection();
|
||||||
|
builder.Services.AddDbContext<CRMContext>(options =>
|
||||||
|
{
|
||||||
|
string? conn = builder.Environment.IsDevelopment() ? builder.Configuration["HOC:CRM:DB"] : builder.Configuration["HOC_DBString"];
|
||||||
|
options.UseMySql(conn, new MariaDbServerVersion(new Version(10, 11, 2)), o => o.EnableRetryOnFailure());
|
||||||
|
});
|
||||||
|
builder.Services.AddAutoMapper(typeof(MappingProfile));
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
app.Logger.LogInformation("App builded");
|
||||||
|
|
||||||
|
if(app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.MapGrpcReflectionService();
|
||||||
|
|
||||||
|
//Test Automapper config.
|
||||||
|
TestAutoMapperConfig(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateDbIfNotExists(app);
|
||||||
|
|
||||||
|
// Configure the HTTP request pipeline.
|
||||||
|
app.MapGrpcService<CRMService>();
|
||||||
|
app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client.");
|
||||||
|
|
||||||
|
app.Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void CreateDbIfNotExists(WebApplication app)
|
||||||
|
{
|
||||||
|
using (var scope = app.Services.CreateScope())
|
||||||
|
{
|
||||||
|
var sp = scope.ServiceProvider;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var dbContext = sp.GetRequiredService<CRMContext>();
|
||||||
|
//dbContext.Database.EnsureDeleted();
|
||||||
|
dbContext.Database.EnsureCreated();
|
||||||
|
//dbContext.Database.Migrate();
|
||||||
|
if(app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
CRMContextSeed.InitData(dbContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
app.Logger.LogError($"Error creating db - {e.Message}: {e.StackTrace} ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TestAutoMapperConfig(WebApplication app)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
app.Logger.LogInformation("Validating Automapper mapping config...");
|
||||||
|
var mapper = app.Services.GetRequiredService<IMapper>();
|
||||||
|
mapper.ConfigurationProvider.AssertConfigurationIsValid();
|
||||||
|
app.Logger.LogInformation("Automapper mapping config is valid");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
app.Logger.LogError($"Error creating Mapping - {e.Message}: {e.StackTrace} ");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*var mapperConfig = new MapperConfiguration(cfg =>
|
||||||
|
{
|
||||||
|
cfg.AddProfile<MappingProfile>();
|
||||||
|
});
|
||||||
|
mapperConfig.AssertConfigurationIsValid();*/
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
},
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"applicationUrl": "http://localhost:5201"
|
||||||
|
},
|
||||||
|
"https": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
},
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"applicationUrl": "https://localhost:7061;http://localhost:5201"
|
||||||
|
},
|
||||||
|
"Docker": {
|
||||||
|
"commandName": "Docker",
|
||||||
|
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
|
||||||
|
"publishAllPorts": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
option csharp_namespace = "DGM.HoC.CRM.API.RPC";
|
||||||
|
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
import "google/protobuf/empty.proto";
|
||||||
|
|
||||||
|
|
||||||
|
package HoC;
|
||||||
|
|
||||||
|
service rpcCRM {
|
||||||
|
|
||||||
|
rpc GetCustomerById (CustomerIdReq) returns (rpcCustomer);
|
||||||
|
rpc GetAllCustomers (google.protobuf.Empty) returns (CustomerListResponse);
|
||||||
|
rpc GetCustomersByFilter (rpcCustomerFilter) returns (CustomerListResponse);
|
||||||
|
|
||||||
|
rpc AddCustomer (rpcAddCustomerReq) returns (rpcCustomer);
|
||||||
|
rpc RemoveCustomer (CustomerIdReq) returns (google.protobuf.Empty);
|
||||||
|
rpc UpdateCustomer (rpcUpdateCustomerReq) returns (rpcCustomer);
|
||||||
|
|
||||||
|
|
||||||
|
rpc GetAllCustomerContactDetails (google.protobuf.Empty) returns (ContactDetailsListResponse);
|
||||||
|
rpc GetContactDetailsByCustomer (CustomerIdReq) returns (ContactDetailsListResponse);
|
||||||
|
rpc GetContactDetailsById (ContactIdReq) returns (rpcContact);
|
||||||
|
|
||||||
|
rpc AddCustomerContact (rpcAddContactDetailsReq) returns (rpcContact);
|
||||||
|
rpc RemoveCustomerContact (ContactIdReq) returns (EmptyResponse);
|
||||||
|
rpc UpdateCustomerContact (rpcUpdateContactDetailsReq) returns (rpcContact);
|
||||||
|
|
||||||
|
|
||||||
|
rpc GetNoteById (NoteIdReq) returns (rpcNote);
|
||||||
|
rpc GetNoteByCustomer (CustomerIdReq) returns (NoteListResponse);
|
||||||
|
rpc GetAllNotes (EmptyReq) returns (NoteListResponse);
|
||||||
|
|
||||||
|
rpc AddNote (rpcAddNoteReq) returns (rpcNote);
|
||||||
|
rpc RemoveNote (NoteIdReq) returns (EmptyResponse);
|
||||||
|
rpc UpdateNote (rpcUpdateNoteReq) returns (rpcNote);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
message EmptyReq {}
|
||||||
|
|
||||||
|
message EmptyResponse {
|
||||||
|
string msg = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Requests messages
|
||||||
|
|
||||||
|
message CustomerIdReq {
|
||||||
|
string customerId = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ContactIdReq {
|
||||||
|
int32 contactId = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message NoteIdReq {
|
||||||
|
int32 noteId = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message rpcAddCustomerReq {
|
||||||
|
string nif = 1;
|
||||||
|
string name = 2;
|
||||||
|
rpcAddress customerAddress = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message rpcUpdateCustomerReq {
|
||||||
|
string nif = 1;
|
||||||
|
optional string name = 2;
|
||||||
|
optional rpcAddress customerAddress = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message rpcAddContactDetailsReq {
|
||||||
|
string customerNIF = 2;
|
||||||
|
string type = 5;
|
||||||
|
string value = 6;
|
||||||
|
string comment = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
message rpcUpdateContactDetailsReq {
|
||||||
|
int32 id = 1;
|
||||||
|
optional string customerNIF = 2;
|
||||||
|
optional string type = 5;
|
||||||
|
optional string value = 6;
|
||||||
|
optional string comment = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
message rpcAddNoteReq {
|
||||||
|
string customerNIF = 2;
|
||||||
|
string data = 5;
|
||||||
|
string status = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message rpcUpdateNoteReq {
|
||||||
|
int32 id = 1;
|
||||||
|
optional string customerNIF = 2;
|
||||||
|
optional string data = 5;
|
||||||
|
optional string status = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Response messages
|
||||||
|
|
||||||
|
|
||||||
|
message CustomerListResponse {
|
||||||
|
repeated rpcCustomer customerList = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ContactDetailsListResponse {
|
||||||
|
repeated rpcContact contactList = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message NoteListResponse {
|
||||||
|
repeated rpcNote noteList = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Entity messages
|
||||||
|
|
||||||
|
message rpcCustomer {
|
||||||
|
string nif = 1;
|
||||||
|
string name = 2;
|
||||||
|
rpcAddress customerAddress = 3;
|
||||||
|
optional google.protobuf.Timestamp createTime = 6;
|
||||||
|
optional google.protobuf.Timestamp lastUsedTime = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
message rpcCustomerFilter {
|
||||||
|
string nif = 1;
|
||||||
|
string name = 2;
|
||||||
|
string contactData = 3;
|
||||||
|
bool strict = 10;
|
||||||
|
optional int32 pageIndex = 11;
|
||||||
|
optional int32 pageSize = 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
message rpcAddress {
|
||||||
|
optional string tipoVia = 1;
|
||||||
|
optional string nombreVia = 2;
|
||||||
|
optional string numero = 3;
|
||||||
|
optional string provincia = 4;
|
||||||
|
optional string poblacion = 5;
|
||||||
|
optional string codigoPostal = 6;
|
||||||
|
optional string pais = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
message rpcContact {
|
||||||
|
string customerNIF = 1;
|
||||||
|
int32 id = 2;
|
||||||
|
string type = 3;
|
||||||
|
string value = 4;
|
||||||
|
string comment = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message rpcNote {
|
||||||
|
string customerNIF = 1;
|
||||||
|
int32 id = 2;
|
||||||
|
string data = 3;
|
||||||
|
string status = 4;
|
||||||
|
optional google.protobuf.Timestamp createTime = 5;
|
||||||
|
}
|
||||||
@@ -0,0 +1,560 @@
|
|||||||
|
namespace DGM.HoC.CRM.API.Services;
|
||||||
|
|
||||||
|
using AutoMapper;
|
||||||
|
using DGM.HoC.CRM.API.Infrastructure;
|
||||||
|
using DGM.HoC.CRM.API.Infrastructure.Mappers;
|
||||||
|
using DGM.HoC.CRM.API.Model;
|
||||||
|
using Google.Protobuf.WellKnownTypes;
|
||||||
|
using Grpc.Core;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using RPC;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
public class CRMService : rpcCRM.rpcCRMBase
|
||||||
|
{
|
||||||
|
|
||||||
|
private readonly CRMContext _context;
|
||||||
|
private readonly ILogger<CRMService> _logger;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
public CRMService(CRMContext context, ILogger<CRMService> logger, IMapper mapper)
|
||||||
|
{
|
||||||
|
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||||
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
|
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Customers
|
||||||
|
|
||||||
|
#region Customers Queries
|
||||||
|
|
||||||
|
public override async Task<rpcCustomer> GetCustomerById(CustomerIdReq request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
LogCall();
|
||||||
|
|
||||||
|
var customer = await _context.Customers.FindAsync(request.CustomerId);
|
||||||
|
|
||||||
|
if (customer == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("CRMService.GetCustomerById - Customer not found");
|
||||||
|
throw new RpcException(new Status(StatusCode.NotFound, $"Not Found Customer with ID {request.CustomerId}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return _mapper.Map<Customer, rpcCustomer>(customer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<CustomerListResponse> GetAllCustomers(Empty request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
LogCall();
|
||||||
|
|
||||||
|
var customerList = await _context.Customers.ToListAsync();
|
||||||
|
|
||||||
|
CustomerListResponse response = new CustomerListResponse();
|
||||||
|
foreach (var c in customerList)
|
||||||
|
{
|
||||||
|
response.CustomerList.Add(_mapper.Map<Customer, rpcCustomer>(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override async Task<CustomerListResponse> GetCustomersByFilter(rpcCustomerFilter request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
LogCall();
|
||||||
|
|
||||||
|
var query = _context.Customers.Where(
|
||||||
|
c => c.NIF.Contains(request.Nif)
|
||||||
|
&& c.Name.Contains(request.Name));
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(request.ContactData))
|
||||||
|
{
|
||||||
|
query = query.Where(c => c.ContactDetails.Any(ct => ct.Value.Contains(request.ContactData)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.HasPageSize && request.HasPageIndex)
|
||||||
|
{
|
||||||
|
query = query.Skip(request.PageSize * request.PageIndex).Take(request.PageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
var list = await query.ToListAsync();
|
||||||
|
var rs = new CustomerListResponse();
|
||||||
|
rs.CustomerList.AddRange(list.Select(c => _mapper.Map<rpcCustomer>(c)));
|
||||||
|
return rs;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
public override async Task<rpcCustomer> AddCustomer(rpcAddCustomerReq request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
LogCall();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var customerToAdd = _mapper.Map<rpcAddCustomerReq, Customer>(request);
|
||||||
|
|
||||||
|
if(String.IsNullOrWhiteSpace(customerToAdd.NIF))
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.InvalidArgument, $"Customer ID cant be empty"));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if ID already exists. Find podría ser Async...
|
||||||
|
if (_context.Customers.Find(customerToAdd.NIF) != null)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.AlreadyExists, $"Already exists Customer with ID {customerToAdd.NIF}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTime now = DateTime.UtcNow;
|
||||||
|
customerToAdd.CreateTime = now;
|
||||||
|
customerToAdd.LastUsedTime = now;
|
||||||
|
|
||||||
|
//No hacer async para el Add.
|
||||||
|
var item = _context.Customers.Add(customerToAdd);
|
||||||
|
int rows = await _context.SaveChangesAsync();
|
||||||
|
_logger.LogInformation($"CRMService.AddCustomer: Added {rows} customers");
|
||||||
|
|
||||||
|
if (rows < 1)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.Internal, "Customer can't be saved"));
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Status = new Status(StatusCode.OK, $"Customer with ID: {customerToAdd.NIF} added");
|
||||||
|
return _mapper.Map<Customer, rpcCustomer>(item.Entity);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LogUnkwonErrors(e);
|
||||||
|
throw new RpcException(new Status(StatusCode.Unknown, "Error adding Customer: " + e.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<Empty> RemoveCustomer(CustomerIdReq request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
LogCall(); try
|
||||||
|
{
|
||||||
|
Customer? c = await _context.Customers.FindAsync(request.CustomerId);
|
||||||
|
if(c == null)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.NotFound, $"Customer ID: {request.CustomerId} not fount"));
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.Customers.Remove(c);
|
||||||
|
int rows = await _context.SaveChangesAsync();
|
||||||
|
_logger.LogInformation($"CRMService.RemoveCustomer: Removed {rows} customers");
|
||||||
|
if (rows < 1)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.Internal, "Customer can't be deleted"));
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Status = new Status(StatusCode.OK, $"Customer with ID: {request.CustomerId} deleted");
|
||||||
|
return new Empty();
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LogUnkwonErrors(e);
|
||||||
|
throw new RpcException(new Status(StatusCode.Unknown, "Error removing Customer: " + e.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override async Task<rpcCustomer> UpdateCustomer(rpcUpdateCustomerReq request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
LogCall();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//var updatedCustomer = _mapper.Map<rpcUpdateCustomerReq, Customer>(request);
|
||||||
|
|
||||||
|
//Check if ID exists. Find podría ser Async...
|
||||||
|
var customer = _context.Customers.Find(request.Nif);
|
||||||
|
|
||||||
|
if (customer is null)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.NotFound, $"Customer with ID {request.Nif} Not Found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Update Entity.
|
||||||
|
bool updated = false;
|
||||||
|
if (request.HasName && request.Name != customer.Name) { customer.Name = request.Name; updated = true; }
|
||||||
|
if (request.CustomerAddress is not null)
|
||||||
|
{
|
||||||
|
var newAddress = _mapper.Map<rpcAddress, Address>(request.CustomerAddress);
|
||||||
|
if(customer.CustomerAddress != newAddress)
|
||||||
|
{
|
||||||
|
customer.CustomerAddress = newAddress;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(updated)
|
||||||
|
{
|
||||||
|
customer.LastUsedTime = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//var item = _context.Customers.Update(updatedCustomer);
|
||||||
|
int rows = await _context.SaveChangesAsync();
|
||||||
|
_logger.LogInformation($"CRMService.UpdateCustomerData: Updated {rows} customers");
|
||||||
|
|
||||||
|
if (updated && rows < 1)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.Internal, "Customer can't be saved"));
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Status = new Status(StatusCode.OK, $"Customer with ID: {request.Nif} updated");
|
||||||
|
return _mapper.Map<Customer, rpcCustomer>(customer);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LogUnkwonErrors(e);
|
||||||
|
throw new RpcException(new Status(StatusCode.Unknown, "Error updating Customer: " + e.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Customer Contact Details
|
||||||
|
|
||||||
|
#region Customers Contact Details Queries
|
||||||
|
|
||||||
|
public override async Task<ContactDetailsListResponse> GetAllCustomerContactDetails(Empty request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
LogCall();
|
||||||
|
var contactList = await _context.ContactDetails.ToListAsync();
|
||||||
|
|
||||||
|
ContactDetailsListResponse response = new ContactDetailsListResponse();
|
||||||
|
foreach (var n in contactList)
|
||||||
|
{
|
||||||
|
response.ContactList.Add(_mapper.Map<ContactDetails, rpcContact>(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<ContactDetailsListResponse> GetContactDetailsByCustomer(CustomerIdReq request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
LogCall();
|
||||||
|
var contactList = _context.ContactDetails.Where(e => e.CustomerNIF == request.CustomerId);
|
||||||
|
|
||||||
|
if (contactList == null)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.NotFound, $"Not Found Contacts for customer with ID {request.CustomerId}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
ContactDetailsListResponse response = new ContactDetailsListResponse();
|
||||||
|
foreach (var n in contactList)
|
||||||
|
{
|
||||||
|
response.ContactList.Add(_mapper.Map<ContactDetails, rpcContact>(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<rpcContact> GetContactDetailsById(ContactIdReq request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
LogCall();
|
||||||
|
ContactDetails? note = await _context.ContactDetails.FindAsync(request.ContactId);
|
||||||
|
|
||||||
|
if (note == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("CRMService.GetContactDetailsById - Contact not found");
|
||||||
|
throw new RpcException(new Status(StatusCode.NotFound, $"Not Found Contact with ID {request.ContactId}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return _mapper.Map<ContactDetails, rpcContact>(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public override async Task<rpcContact> AddCustomerContact(rpcAddContactDetailsReq request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
LogCall();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var ContactToAdd = _mapper.Map<rpcAddContactDetailsReq, ContactDetails>(request);
|
||||||
|
|
||||||
|
if (String.IsNullOrWhiteSpace(ContactToAdd.CustomerNIF))
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.InvalidArgument, $"Invalid customer ID"));
|
||||||
|
}
|
||||||
|
|
||||||
|
//No hacer async para el Add.
|
||||||
|
var item = _context.ContactDetails.Add(ContactToAdd);
|
||||||
|
int rows = await _context.SaveChangesAsync();
|
||||||
|
_logger.LogInformation($"CRMService.AddCustomerContact: Added {rows} contacts");
|
||||||
|
|
||||||
|
if (rows < 1)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.Internal, "Note can't be saved"));
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Status = new Status(StatusCode.OK, $"Contact with ID: {ContactToAdd.Id} added");
|
||||||
|
return _mapper.Map<ContactDetails, rpcContact>(item.Entity);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LogUnkwonErrors(e);
|
||||||
|
throw new RpcException(new Status(StatusCode.Unknown, "Error adding Contact: " + e.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<EmptyResponse> RemoveCustomerContact(ContactIdReq request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
LogCall(); try
|
||||||
|
{
|
||||||
|
ContactDetails? n = await _context.ContactDetails.FindAsync(request.ContactId);
|
||||||
|
if (n == null)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.NotFound, $"Contact with ID: {request.ContactId} not fount"));
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.ContactDetails.Remove(n);
|
||||||
|
int rows = await _context.SaveChangesAsync();
|
||||||
|
if (rows < 1)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.Internal, "Contact can't be deleted"));
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Status = new Status(StatusCode.OK, $"Contact with ID: {request.ContactId} deleted");
|
||||||
|
return new EmptyResponse() { Msg = $"Contact Deleted - ID {request.ContactId}" };
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LogUnkwonErrors(e);
|
||||||
|
throw new RpcException(new Status(StatusCode.Unknown, "Error removing Contact: " + e.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<rpcContact> UpdateCustomerContact(rpcUpdateContactDetailsReq request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
LogCall();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//var updatedContact = _mapper.Map<rpcUpdateContactDetailsReq, ContactDetails>(request);
|
||||||
|
|
||||||
|
//Check if ID already exists. Find podría ser Async...
|
||||||
|
var contact = await _context.ContactDetails.FindAsync(request.Id);
|
||||||
|
|
||||||
|
if (contact is null)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.NotFound, $"Contact with ID {request.Id} Not Found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Update Entity.
|
||||||
|
bool updated = false;
|
||||||
|
if (request.HasCustomerNIF && request.CustomerNIF != contact.CustomerNIF) { contact.CustomerNIF = request.CustomerNIF; updated = true; }
|
||||||
|
if (request.HasType && request.Type != contact.Type) { contact.Type = request.Type; updated = true; }
|
||||||
|
if (request.HasValue && request.Value != contact.Value) { contact.Value = request.Value; updated = true; }
|
||||||
|
if (request.HasComment && request.Comment != contact.Comment) { contact.Comment = request.Comment; updated = true; }
|
||||||
|
|
||||||
|
|
||||||
|
//var item = _context.Customers.Update(updatedCustomer);
|
||||||
|
int rows = await _context.SaveChangesAsync();
|
||||||
|
_logger.LogInformation($"CRMService.UpdateCustomerContact: Updated {rows} Contacts");
|
||||||
|
|
||||||
|
if (updated && rows < 1)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.DataLoss, "Contact can't be saved"));
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Status = new Status(StatusCode.OK, $"Contact with ID: {request.Id} updated");
|
||||||
|
return _mapper.Map<ContactDetails, rpcContact>(contact);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LogUnkwonErrors(e);
|
||||||
|
throw new RpcException(new Status(StatusCode.Aborted, "Error updating Contact: " + e.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Customer Notes
|
||||||
|
|
||||||
|
#region Customer Notes Queries
|
||||||
|
|
||||||
|
public override async Task<rpcNote> GetNoteById(NoteIdReq request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
LogCall();
|
||||||
|
Note? note = await _context.Notes.FindAsync(request.NoteId);
|
||||||
|
|
||||||
|
if (note == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("CRMService.GetNoteById - Note not found");
|
||||||
|
throw new RpcException(new Status(StatusCode.NotFound, $"Not Found Note with ID {request.NoteId}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return _mapper.Map<Note, rpcNote>(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<NoteListResponse> GetNoteByCustomer(CustomerIdReq request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
LogCall();
|
||||||
|
var noteList = _context.Notes.Where(e => e.CustomerNIF == request.CustomerId);
|
||||||
|
|
||||||
|
if (noteList == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("CRMService.GetNoteByCustomer - Note not found");
|
||||||
|
throw new RpcException(new Status(StatusCode.NotFound, $"Not Found Note for customer with ID {request.CustomerId}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
NoteListResponse response = new NoteListResponse();
|
||||||
|
foreach(var n in noteList)
|
||||||
|
{
|
||||||
|
response.NoteList.Add(_mapper.Map<Note, rpcNote>(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<NoteListResponse> GetAllNotes(EmptyReq request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
LogCall();
|
||||||
|
var noteList = await _context.Notes.ToListAsync();
|
||||||
|
|
||||||
|
NoteListResponse response = new NoteListResponse();
|
||||||
|
foreach (var n in noteList)
|
||||||
|
{
|
||||||
|
response.NoteList.Add(_mapper.Map<Note, rpcNote>(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public override async Task<rpcNote> AddNote(rpcAddNoteReq request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
LogCall();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var noteToAdd = _mapper.Map<rpcAddNoteReq, Note>(request);
|
||||||
|
|
||||||
|
if (String.IsNullOrWhiteSpace(noteToAdd.CustomerNIF))
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.InvalidArgument, $"Invalid customer ID"));
|
||||||
|
}
|
||||||
|
|
||||||
|
noteToAdd.CreateTime = DateTime.UtcNow;
|
||||||
|
|
||||||
|
//No hacer async para el Add.
|
||||||
|
var item = _context.Notes.Add(noteToAdd);
|
||||||
|
int rows = await _context.SaveChangesAsync();
|
||||||
|
_logger.LogInformation($"CRMService.AddNote: Added {rows} customers");
|
||||||
|
|
||||||
|
if (rows < 1)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.Internal, "Note can't be saved"));
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Status = new Status(StatusCode.OK, $"Note with ID: {noteToAdd.Id} added");
|
||||||
|
return _mapper.Map<Note, rpcNote>(item.Entity);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LogUnkwonErrors(e);
|
||||||
|
throw new RpcException(new Status(StatusCode.Unknown, "Error adding Customer: " + e.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<EmptyResponse> RemoveNote(NoteIdReq request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
LogCall(); try
|
||||||
|
{
|
||||||
|
Note? n = await _context.Notes.FindAsync(request.NoteId);
|
||||||
|
if (n == null)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.NotFound, $"Note ID: {request.NoteId} not fount"));
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.Notes.Remove(n);
|
||||||
|
int rows = await _context.SaveChangesAsync();
|
||||||
|
if (rows < 1)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.Internal, "Note can't be deleted"));
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Status = new Status(StatusCode.OK, $"Note with ID: {request.NoteId} deleted");
|
||||||
|
return new EmptyResponse() { Msg = $"Note Deleted - ID {request.NoteId}" };
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LogUnkwonErrors(e);
|
||||||
|
throw new RpcException(new Status(StatusCode.Unknown, "Error removing Note: " + e.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override async Task<rpcNote> UpdateNote(rpcUpdateNoteReq request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
LogCall();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//var updatedNote = _mapper.Map<rpcUpdateNoteReq, Note>(request);
|
||||||
|
|
||||||
|
//Check if ID already exists. Find podría ser Async...
|
||||||
|
var note = await _context.Notes.FindAsync(request.Id);
|
||||||
|
|
||||||
|
if (note is null)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.NotFound, $"Note with ID {request.Id} Not Found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Update Entity.
|
||||||
|
bool updated = false;
|
||||||
|
if (request.HasCustomerNIF && request.CustomerNIF != note.CustomerNIF) { note.CustomerNIF = request.CustomerNIF; updated = true; }
|
||||||
|
if (request.HasData && request.Data != note.Data) { note.Data = request.Data; updated = true; }
|
||||||
|
if (request.HasData && request.Status != note.Status) { note.Status = request.Status; updated = true; }
|
||||||
|
|
||||||
|
//No hacer async para el Add.
|
||||||
|
//var item = _context.Customers.Update(updatedCustomer);
|
||||||
|
int rows = await _context.SaveChangesAsync();
|
||||||
|
_logger.LogInformation($"CRMService.UpdateNote: Updated {rows} Note");
|
||||||
|
|
||||||
|
if (updated && rows < 1)
|
||||||
|
{
|
||||||
|
throw new RpcException(new Status(StatusCode.DataLoss, "Note can't be saved"));
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Status = new Status(StatusCode.OK, $"Note with ID: {request.Id} updated");
|
||||||
|
return _mapper.Map<Note, rpcNote>(note);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LogUnkwonErrors(e);
|
||||||
|
throw new RpcException(new Status(StatusCode.Aborted, "Error updating Note: " + e.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Utils.
|
||||||
|
|
||||||
|
private void LogCall([CallerMemberName] string callerName = "")
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"Begin grpc call {GetType().FullName}.{callerName}:");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LogUnkwonErrors(Exception e, [CallerMemberName] string callerName = "")
|
||||||
|
{
|
||||||
|
_logger.LogError($"{GetType().FullName}.{callerName}: UnknowError -> Exception: {e.Message} - {e.StackTrace}");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"Kestrel": {
|
||||||
|
"EndpointDefaults": {
|
||||||
|
"Protocols": "Http2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.5.33627.172
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CRM.API", "CRM.API\CRM.API.csproj", "{A8F7493B-C3B0-4CDD-8956-A9A8CC48A512}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Services", "Services", "{627DBAE1-551C-45AA-8BF8-2C8A66FED5E3}"
|
||||||
|
EndProject
|
||||||
|
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{C53CC93C-36C0-4A7B-AAFE-F5A327EDEF38}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BookMng.API", "BookMng.API\BookMng.API.csproj", "{7FEB5B10-6385-4B35-A099-9E6B8B74F075}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{A8F7493B-C3B0-4CDD-8956-A9A8CC48A512}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A8F7493B-C3B0-4CDD-8956-A9A8CC48A512}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A8F7493B-C3B0-4CDD-8956-A9A8CC48A512}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A8F7493B-C3B0-4CDD-8956-A9A8CC48A512}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C53CC93C-36C0-4A7B-AAFE-F5A327EDEF38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C53CC93C-36C0-4A7B-AAFE-F5A327EDEF38}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C53CC93C-36C0-4A7B-AAFE-F5A327EDEF38}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C53CC93C-36C0-4A7B-AAFE-F5A327EDEF38}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{7FEB5B10-6385-4B35-A099-9E6B8B74F075}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{7FEB5B10-6385-4B35-A099-9E6B8B74F075}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{7FEB5B10-6385-4B35-A099-9E6B8B74F075}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{7FEB5B10-6385-4B35-A099-9E6B8B74F075}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{A8F7493B-C3B0-4CDD-8956-A9A8CC48A512} = {627DBAE1-551C-45AA-8BF8-2C8A66FED5E3}
|
||||||
|
{7FEB5B10-6385-4B35-A099-9E6B8B74F075} = {627DBAE1-551C-45AA-8BF8-2C8A66FED5E3}
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {F5B82DFB-D123-41DE-9BEA-C6E13F805A3B}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="15.0" Sdk="Microsoft.Docker.Sdk">
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<ProjectVersion>2.1</ProjectVersion>
|
||||||
|
<DockerTargetOS>Linux</DockerTargetOS>
|
||||||
|
<ProjectGuid>c53cc93c-36c0-4a7b-aafe-f5a327edef38</ProjectGuid>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="docker-compose.override.yml">
|
||||||
|
<DependentUpon>docker-compose.yml</DependentUpon>
|
||||||
|
</None>
|
||||||
|
<None Include="docker-compose.yml" />
|
||||||
|
<None Include=".dockerignore" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
version: '3.4'
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
crm.api:
|
||||||
|
environment:
|
||||||
|
- ASPNETCORE_ENVIRONMENT=Development
|
||||||
|
- ASPNETCORE_URLS=https://+:443;http://+:80
|
||||||
|
ports:
|
||||||
|
- "5105:80"
|
||||||
|
- "5106:443"
|
||||||
|
volumes:
|
||||||
|
- ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro
|
||||||
|
- ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro
|
||||||
|
|
||||||
|
bookmng.api:
|
||||||
|
environment:
|
||||||
|
- ASPNETCORE_ENVIRONMENT=Development
|
||||||
|
- ASPNETCORE_URLS=https://+:443;http://+:80
|
||||||
|
ports:
|
||||||
|
- "5107:80"
|
||||||
|
- "5108:443"
|
||||||
|
volumes:
|
||||||
|
- ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro
|
||||||
|
- ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
version: '3.4'
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
sqldb:
|
||||||
|
image: mariadb
|
||||||
|
|
||||||
|
crm.api:
|
||||||
|
image: ${DOCKER_REGISTRY-}crmapi
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: CRM.API/Dockerfile
|
||||||
|
depends_on:
|
||||||
|
- sqldb
|
||||||
|
|
||||||
|
bookmng.api:
|
||||||
|
image: ${DOCKER_REGISTRY-}bookmngapi
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: BookMng.API/Dockerfile
|
||||||
|
depends_on:
|
||||||
|
- sqldb
|
||||||
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"Docker Compose": {
|
||||||
|
"commandName": "DockerCompose",
|
||||||
|
"commandVersion": "1.0",
|
||||||
|
"serviceActions": {
|
||||||
|
"bookmng.api": "StartDebugging",
|
||||||
|
"crm.api": "StartDebugging"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user