diff --git a/DeveloperTest.sln b/DeveloperTest.sln index a8b5d77..4c66c5e 100644 --- a/DeveloperTest.sln +++ b/DeveloperTest.sln @@ -1,9 +1,11 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29806.167 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32319.34 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeveloperTest", "DeveloperTest\DeveloperTest.csproj", "{1D4CF36B-FE89-404A-867A-4986AB960BD1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeveloperTest", "DeveloperTest\DeveloperTest.csproj", "{1D4CF36B-FE89-404A-867A-4986AB960BD1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "UnitTests\UnitTests.csproj", "{11383945-67C6-4F7A-8C98-93FAB13BA22E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -15,6 +17,10 @@ Global {1D4CF36B-FE89-404A-867A-4986AB960BD1}.Debug|Any CPU.Build.0 = Debug|Any CPU {1D4CF36B-FE89-404A-867A-4986AB960BD1}.Release|Any CPU.ActiveCfg = Release|Any CPU {1D4CF36B-FE89-404A-867A-4986AB960BD1}.Release|Any CPU.Build.0 = Release|Any CPU + {11383945-67C6-4F7A-8C98-93FAB13BA22E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {11383945-67C6-4F7A-8C98-93FAB13BA22E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {11383945-67C6-4F7A-8C98-93FAB13BA22E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {11383945-67C6-4F7A-8C98-93FAB13BA22E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/DeveloperTest/Business/CustomerService.cs b/DeveloperTest/Business/CustomerService.cs new file mode 100644 index 0000000..cebe248 --- /dev/null +++ b/DeveloperTest/Business/CustomerService.cs @@ -0,0 +1,57 @@ +using AutoMapper; +using DeveloperTest.Business.Interfaces; +using DeveloperTest.Database; +using DeveloperTest.Database.Models; +using DeveloperTest.Models; +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace DeveloperTest.Business +{ + public class CustomerService : ICustomerService + { + private readonly ApplicationDbContext context; + private readonly IMapper mapper; + + public CustomerService(ApplicationDbContext context, IMapper mapper) + { + this.context = context; + this.mapper = mapper; + } + + public async Task> GetCustomersAsync(CancellationToken token) + { + return mapper.Map>(await context.Customers.Include(x => x.CustomerType).ToListAsync(token)); + } + + public async Task GetCustomerAsync(int id, CancellationToken token) + { + return mapper.Map(await context.Customers.Include(x => x.CustomerType).SingleOrDefaultAsync(x => x.Id == id, token)); + } + + public async Task CreateCustomerAsync(BaseCustomerModel model, CancellationToken token) + { + var addedCustomer = await context.Customers.AddAsync(mapper.Map(model)); + + await context.SaveChangesAsync(); + + return mapper.Map(await context.Customers.Include(x => x.CustomerType).SingleOrDefaultAsync(x => x.Id == addedCustomer.Entity.Id, token)); + } + + //Added for manual testing purposes + public async Task DeleteCustomer(int id, CancellationToken token) + { + var customerToDelete = await context.Customers.SingleOrDefaultAsync(x => x.Id == id, token); + if (customerToDelete is null) + { + return false; + } + + context.Customers.Remove(customerToDelete); + await context.SaveChangesAsync(token); + return true; + } + } +} diff --git a/DeveloperTest/Business/CustomerTypeService.cs b/DeveloperTest/Business/CustomerTypeService.cs new file mode 100644 index 0000000..1a4394f --- /dev/null +++ b/DeveloperTest/Business/CustomerTypeService.cs @@ -0,0 +1,28 @@ +using AutoMapper; +using DeveloperTest.Business.Interfaces; +using DeveloperTest.Database; +using DeveloperTest.Models; +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace DeveloperTest.Business +{ + public class CustomerTypeService : ICustomerTypeService + { + private readonly ApplicationDbContext context; + private readonly IMapper mapper; + + public CustomerTypeService(ApplicationDbContext context, IMapper mapper) + { + this.context = context; + this.mapper = mapper; + } + + public async Task> GetCustomerTypesAsync(CancellationToken token) + { + return mapper.Map>(await context.CustomerTypes.ToListAsync(token)); + } + } +} diff --git a/DeveloperTest/Business/Interfaces/ICustomerService.cs b/DeveloperTest/Business/Interfaces/ICustomerService.cs new file mode 100644 index 0000000..cf5fb9f --- /dev/null +++ b/DeveloperTest/Business/Interfaces/ICustomerService.cs @@ -0,0 +1,17 @@ +using DeveloperTest.Models; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace DeveloperTest.Business.Interfaces +{ + public interface ICustomerService + { + Task> GetCustomersAsync(CancellationToken token); + Task GetCustomerAsync(int id, CancellationToken token); + Task CreateCustomerAsync(BaseCustomerModel model, CancellationToken token); + + //Added for manual testing purposes + Task DeleteCustomer(int id, CancellationToken token); + } +} diff --git a/DeveloperTest/Business/Interfaces/ICustomerTypeService.cs b/DeveloperTest/Business/Interfaces/ICustomerTypeService.cs new file mode 100644 index 0000000..42218aa --- /dev/null +++ b/DeveloperTest/Business/Interfaces/ICustomerTypeService.cs @@ -0,0 +1,12 @@ +using DeveloperTest.Models; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace DeveloperTest.Business.Interfaces +{ + public interface ICustomerTypeService + { + Task> GetCustomerTypesAsync(CancellationToken token); + } +} diff --git a/DeveloperTest/Business/JobService.cs b/DeveloperTest/Business/JobService.cs index 7eb8f64..3d1ca5e 100644 --- a/DeveloperTest/Business/JobService.cs +++ b/DeveloperTest/Business/JobService.cs @@ -3,12 +3,14 @@ using DeveloperTest.Database; using DeveloperTest.Database.Models; using DeveloperTest.Models; +using Microsoft.EntityFrameworkCore; namespace DeveloperTest.Business { public class JobService : IJobService { private readonly ApplicationDbContext context; + private readonly string unknown = "Unknown"; public JobService(ApplicationDbContext context) { @@ -17,11 +19,12 @@ public JobService(ApplicationDbContext context) public JobModel[] GetJobs() { - return context.Jobs.Select(x => new JobModel + return context.Jobs.Include(x => x.Customer).Select(x => new JobModel { JobId = x.JobId, Engineer = x.Engineer, - When = x.When + When = x.When, + CustomerName = x.Customer.Name ?? unknown }).ToArray(); } @@ -31,7 +34,9 @@ public JobModel GetJob(int jobId) { JobId = x.JobId, Engineer = x.Engineer, - When = x.When + When = x.When, + CustomerName = x.Customer.Name ?? unknown, + CustomerType = x.Customer.Type.ToString() }).SingleOrDefault(); } @@ -40,16 +45,19 @@ public JobModel CreateJob(BaseJobModel model) var addedJob = context.Jobs.Add(new Job { Engineer = model.Engineer, - When = model.When + When = model.When, + CustomerId = model.CustomerId }); context.SaveChanges(); + var job = context.Jobs.Include(x => x.Customer).SingleOrDefault(x => x.JobId == addedJob.Entity.JobId); return new JobModel { - JobId = addedJob.Entity.JobId, - Engineer = addedJob.Entity.Engineer, - When = addedJob.Entity.When + JobId = job.JobId, + Engineer = job.Engineer, + When = job.When, + CustomerName = job.Customer.Name ?? unknown }; } } diff --git a/DeveloperTest/Controllers/CustomerController.cs b/DeveloperTest/Controllers/CustomerController.cs new file mode 100644 index 0000000..e4e74ef --- /dev/null +++ b/DeveloperTest/Controllers/CustomerController.cs @@ -0,0 +1,69 @@ +using DeveloperTest.Business.Interfaces; +using DeveloperTest.Models; +using Microsoft.AspNetCore.Mvc; +using System.Threading; +using System.Threading.Tasks; + +namespace DeveloperTest.Controllers +{ + [ApiController, Route("[controller]")] + public class CustomerController : ControllerBase + { + private readonly ICustomerService _customerService; + + public CustomerController(ICustomerService customerService) + { + _customerService = customerService; + } + + [HttpGet] + public async Task Get(CancellationToken token) + { + var customers = await _customerService.GetCustomersAsync(token); + + if (customers is null) + return NotFound(); + + return Ok(customers); + } + + [HttpGet("{id}")] + public async Task Get([FromRoute]int id, CancellationToken token) + { + var customers = await _customerService.GetCustomerAsync(id, token); + + if (customers is null) + return NotFound(); + + return Ok(customers); + } + + [HttpPost] + public async Task Create([FromBody] BaseCustomerModel model, CancellationToken token) + { + var created = await _customerService.CreateCustomerAsync(model, token); + + if (created is null) + return BadRequest(new { error = "Unable to create customer" }); + + return Created($"{GetBaseUrl()}/customer/{created.Id}", created); + } + + //Added for manual testing purposes + [HttpDelete("{id}")] + public async Task Delete([FromRoute] int id, CancellationToken token) + { + var deleted = await _customerService.DeleteCustomer(id, token); + + if (deleted) + return NoContent(); + + return NotFound(); + } + + private string GetBaseUrl() + { + return $"{HttpContext.Request.Scheme}://{HttpContext.Request.Host.ToUriComponent()}"; + } + } +} diff --git a/DeveloperTest/Controllers/CustomerTypeController.cs b/DeveloperTest/Controllers/CustomerTypeController.cs new file mode 100644 index 0000000..dc6cc1b --- /dev/null +++ b/DeveloperTest/Controllers/CustomerTypeController.cs @@ -0,0 +1,29 @@ +using DeveloperTest.Business.Interfaces; +using Microsoft.AspNetCore.Mvc; +using System.Threading; +using System.Threading.Tasks; + +namespace DeveloperTest.Controllers +{ + [ApiController, Route("[controller]")] + public class CustomerTypeController : ControllerBase + { + private readonly ICustomerTypeService _customerTypeService; + + public CustomerTypeController(ICustomerTypeService customerTypeService) + { + _customerTypeService = customerTypeService; + } + + [HttpGet] + public async Task Get(CancellationToken token) + { + var customerTypes = await _customerTypeService.GetCustomerTypesAsync(token); + + if (customerTypes is null) + return NotFound(); + + return Ok(customerTypes); + } + } +} \ No newline at end of file diff --git a/DeveloperTest/Controllers/JobController.cs b/DeveloperTest/Controllers/JobController.cs index 2ce1c0e..0117119 100644 --- a/DeveloperTest/Controllers/JobController.cs +++ b/DeveloperTest/Controllers/JobController.cs @@ -1,5 +1,4 @@ -using System; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using DeveloperTest.Business.Interfaces; using DeveloperTest.Models; @@ -37,14 +36,16 @@ public IActionResult Get(int id) [HttpPost] public IActionResult Create(BaseJobModel model) { - if (model.When.Date < DateTime.Now.Date) - { - return BadRequest("Date cannot be in the past"); - } - var job = jobService.CreateJob(model); - return Created($"job/{job.JobId}", job); + if (job is null) + return BadRequest(new { error = "Unable to create job" }); + + return Created($"{GetBaseUrl}/job/{job.JobId}", job); + } + private string GetBaseUrl() + { + return $"{HttpContext.Request.Scheme}://{HttpContext.Request.Host.ToUriComponent()}"; } } } \ No newline at end of file diff --git a/DeveloperTest/Database/ApplicationDbContext.cs b/DeveloperTest/Database/ApplicationDbContext.cs index f5be4a1..1e4d31c 100644 --- a/DeveloperTest/Database/ApplicationDbContext.cs +++ b/DeveloperTest/Database/ApplicationDbContext.cs @@ -1,13 +1,16 @@ using System; using Microsoft.EntityFrameworkCore; using DeveloperTest.Database.Models; +using DeveloperTest.Enums; +using System.Linq; namespace DeveloperTest.Database { public class ApplicationDbContext : DbContext { public DbSet Jobs { get; set; } - + public DbSet Customers { get; set; } + public DbSet CustomerTypes { get; set; } public ApplicationDbContext(DbContextOptions options) : base(options) { @@ -31,6 +34,17 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) Engineer = "Test", When = new DateTime(2022, 2, 1, 12, 0, 0) }); + + foreach (CustomerTypeEnum cte in Enum.GetValues(typeof(CustomerTypeEnum)).Cast()) + { + CustomerType ct = new CustomerType + { + Id = cte, + Name = cte.ToString(), + }; + + modelBuilder.Entity().HasData(ct); + } } } } diff --git a/DeveloperTest/Database/Models/Customer.cs b/DeveloperTest/Database/Models/Customer.cs new file mode 100644 index 0000000..25819ac --- /dev/null +++ b/DeveloperTest/Database/Models/Customer.cs @@ -0,0 +1,19 @@ +using DeveloperTest.Enums; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace DeveloperTest.Database.Models +{ + public class Customer + { + [Key] + public int Id { get; set; } + + public string Name { get; set; } + + [Required] + public CustomerTypeEnum Type { get; set; } + [ForeignKey("Type")] + public CustomerType CustomerType { get; set; } + } +} diff --git a/DeveloperTest/Database/Models/CustomerType.cs b/DeveloperTest/Database/Models/CustomerType.cs new file mode 100644 index 0000000..640c78f --- /dev/null +++ b/DeveloperTest/Database/Models/CustomerType.cs @@ -0,0 +1,13 @@ +using DeveloperTest.Enums; +using System.ComponentModel.DataAnnotations; + +namespace DeveloperTest.Database.Models +{ + public class CustomerType + { + [Key] + public CustomerTypeEnum Id { get; set; } + [Required] + public string Name { get; set; } + } +} diff --git a/DeveloperTest/Database/Models/Job.cs b/DeveloperTest/Database/Models/Job.cs index 8a2abd0..3eebe42 100644 --- a/DeveloperTest/Database/Models/Job.cs +++ b/DeveloperTest/Database/Models/Job.cs @@ -1,13 +1,20 @@ using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace DeveloperTest.Database.Models { public class Job { + [Key] public int JobId { get; set; } public string Engineer { get; set; } public DateTime When { get; set; } + + [ForeignKey("CustomerId")] + public int? CustomerId { get; set; } + public Customer Customer { get; set; } } } diff --git a/DeveloperTest/DearRecrutersReadme.txt b/DeveloperTest/DearRecrutersReadme.txt new file mode 100644 index 0000000..1df3a4b --- /dev/null +++ b/DeveloperTest/DearRecrutersReadme.txt @@ -0,0 +1,6 @@ +The task said that I should not refactor the existing code, so I tried not to do such a thing. +Of course, I would provide changes to existing code to look like something that I have written during this task. +The task was entertaining :) + +Sincerely +Jacob \ No newline at end of file diff --git a/DeveloperTest/DeveloperTest.csproj b/DeveloperTest/DeveloperTest.csproj index cb8732d..ae3b6d7 100644 --- a/DeveloperTest/DeveloperTest.csproj +++ b/DeveloperTest/DeveloperTest.csproj @@ -5,6 +5,9 @@ + + + @@ -12,6 +15,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/DeveloperTest/Enums/CustomerTypeEnum.cs b/DeveloperTest/Enums/CustomerTypeEnum.cs new file mode 100644 index 0000000..b29096c --- /dev/null +++ b/DeveloperTest/Enums/CustomerTypeEnum.cs @@ -0,0 +1,8 @@ +namespace DeveloperTest.Enums +{ + public enum CustomerTypeEnum + { + Small = 1, + Large = 2 + } +} diff --git a/DeveloperTest/MappingProfiles/DomainToModelProfile.cs b/DeveloperTest/MappingProfiles/DomainToModelProfile.cs new file mode 100644 index 0000000..9117853 --- /dev/null +++ b/DeveloperTest/MappingProfiles/DomainToModelProfile.cs @@ -0,0 +1,24 @@ +using AutoMapper; +using DeveloperTest.Database.Models; +using DeveloperTest.Models; + +namespace DeveloperTest.MappingProfiles +{ + public class DomainToModelProfile : Profile + { + public DomainToModelProfile() + { + CreateMap() + .ForMember(dest => dest.Type, opt => + opt.MapFrom(src => src.CustomerType.Name)); + + CreateMap() + .ForMember(dest => dest.Type, opt => + opt.MapFrom(src => src.CustomerType.Id)) + .ForMember(dest => dest.TypeName, opt => + opt.MapFrom(src => src.CustomerType.Name)); + + CreateMap(); + } + } +} diff --git a/DeveloperTest/MappingProfiles/ModelToDomainProfile.cs b/DeveloperTest/MappingProfiles/ModelToDomainProfile.cs new file mode 100644 index 0000000..52273b2 --- /dev/null +++ b/DeveloperTest/MappingProfiles/ModelToDomainProfile.cs @@ -0,0 +1,20 @@ +using AutoMapper; +using DeveloperTest.Database.Models; +using DeveloperTest.Enums; +using DeveloperTest.Models; + +namespace DeveloperTest.MappingProfiles +{ + public class ModelToDomainProfile : Profile + { + public ModelToDomainProfile() + { + CreateMap(); + CreateMap() + .ForMember(dest => dest.Type, opt => + opt.MapFrom(src => (CustomerTypeEnum)src.Type)); + + CreateMap(); + } + } +} diff --git a/DeveloperTest/Migrations/20220401231955_job-customer.Designer.cs b/DeveloperTest/Migrations/20220401231955_job-customer.Designer.cs new file mode 100644 index 0000000..d1e2082 --- /dev/null +++ b/DeveloperTest/Migrations/20220401231955_job-customer.Designer.cs @@ -0,0 +1,128 @@ +// +using System; +using DeveloperTest.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace DeveloperTest.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20220401231955_job-customer")] + partial class jobcustomer + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + + modelBuilder.Entity("DeveloperTest.Database.Models.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("Type"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("DeveloperTest.Database.Models.CustomerType", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CustomerTypes"); + + b.HasData( + new + { + Id = 1, + Name = "Small" + }, + new + { + Id = 2, + Name = "Large" + }); + }); + + modelBuilder.Entity("DeveloperTest.Database.Models.Job", b => + { + b.Property("JobId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("JobId"), 1L, 1); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("Engineer") + .HasColumnType("nvarchar(max)"); + + b.Property("When") + .HasColumnType("datetime2"); + + b.HasKey("JobId"); + + b.HasIndex("CustomerId"); + + b.ToTable("Jobs"); + + b.HasData( + new + { + JobId = 1, + Engineer = "Test", + When = new DateTime(2022, 2, 1, 12, 0, 0, 0, DateTimeKind.Unspecified) + }); + }); + + modelBuilder.Entity("DeveloperTest.Database.Models.Customer", b => + { + b.HasOne("DeveloperTest.Database.Models.CustomerType", "CustomerType") + .WithMany() + .HasForeignKey("Type") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CustomerType"); + }); + + modelBuilder.Entity("DeveloperTest.Database.Models.Job", b => + { + b.HasOne("DeveloperTest.Database.Models.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId"); + + b.Navigation("Customer"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/DeveloperTest/Migrations/20220401231955_job-customer.cs b/DeveloperTest/Migrations/20220401231955_job-customer.cs new file mode 100644 index 0000000..1ae42b2 --- /dev/null +++ b/DeveloperTest/Migrations/20220401231955_job-customer.cs @@ -0,0 +1,98 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace DeveloperTest.Migrations +{ + public partial class jobcustomer : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CustomerId", + table: "Jobs", + type: "int", + nullable: true); + + migrationBuilder.CreateTable( + name: "CustomerTypes", + columns: table => new + { + Id = table.Column(type: "int", nullable: false), + Name = table.Column(type: "nvarchar(max)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CustomerTypes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Customers", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(max)", nullable: true), + Type = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Customers", x => x.Id); + table.ForeignKey( + name: "FK_Customers_CustomerTypes_Type", + column: x => x.Type, + principalTable: "CustomerTypes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.InsertData( + table: "CustomerTypes", + columns: new[] { "Id", "Name" }, + values: new object[] { 1, "Small" }); + + migrationBuilder.InsertData( + table: "CustomerTypes", + columns: new[] { "Id", "Name" }, + values: new object[] { 2, "Large" }); + + migrationBuilder.CreateIndex( + name: "IX_Jobs_CustomerId", + table: "Jobs", + column: "CustomerId"); + + migrationBuilder.CreateIndex( + name: "IX_Customers_Type", + table: "Customers", + column: "Type"); + + migrationBuilder.AddForeignKey( + name: "FK_Jobs_Customers_CustomerId", + table: "Jobs", + column: "CustomerId", + principalTable: "Customers", + principalColumn: "Id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Jobs_Customers_CustomerId", + table: "Jobs"); + + migrationBuilder.DropTable( + name: "Customers"); + + migrationBuilder.DropTable( + name: "CustomerTypes"); + + migrationBuilder.DropIndex( + name: "IX_Jobs_CustomerId", + table: "Jobs"); + + migrationBuilder.DropColumn( + name: "CustomerId", + table: "Jobs"); + } + } +} diff --git a/DeveloperTest/Migrations/ApplicationDbContextModelSnapshot.cs b/DeveloperTest/Migrations/ApplicationDbContextModelSnapshot.cs index 0ee623b..93f639c 100644 --- a/DeveloperTest/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/DeveloperTest/Migrations/ApplicationDbContextModelSnapshot.cs @@ -22,6 +22,53 @@ protected override void BuildModel(ModelBuilder modelBuilder) SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + modelBuilder.Entity("DeveloperTest.Database.Models.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("Type"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("DeveloperTest.Database.Models.CustomerType", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CustomerTypes"); + + b.HasData( + new + { + Id = 1, + Name = "Small" + }, + new + { + Id = 2, + Name = "Large" + }); + }); + modelBuilder.Entity("DeveloperTest.Database.Models.Job", b => { b.Property("JobId") @@ -30,6 +77,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("JobId"), 1L, 1); + b.Property("CustomerId") + .HasColumnType("int"); + b.Property("Engineer") .HasColumnType("nvarchar(max)"); @@ -38,6 +88,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("JobId"); + b.HasIndex("CustomerId"); + b.ToTable("Jobs"); b.HasData( @@ -48,6 +100,26 @@ protected override void BuildModel(ModelBuilder modelBuilder) When = new DateTime(2022, 2, 1, 12, 0, 0, 0, DateTimeKind.Unspecified) }); }); + + modelBuilder.Entity("DeveloperTest.Database.Models.Customer", b => + { + b.HasOne("DeveloperTest.Database.Models.CustomerType", "CustomerType") + .WithMany() + .HasForeignKey("Type") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CustomerType"); + }); + + modelBuilder.Entity("DeveloperTest.Database.Models.Job", b => + { + b.HasOne("DeveloperTest.Database.Models.Customer", "Customer") + .WithMany() + .HasForeignKey("CustomerId"); + + b.Navigation("Customer"); + }); #pragma warning restore 612, 618 } } diff --git a/DeveloperTest/Models/BaseCustomerModel.cs b/DeveloperTest/Models/BaseCustomerModel.cs new file mode 100644 index 0000000..7099175 --- /dev/null +++ b/DeveloperTest/Models/BaseCustomerModel.cs @@ -0,0 +1,11 @@ +using DeveloperTest.Enums; + +namespace DeveloperTest.Models +{ + public class BaseCustomerModel + { + public string Name { get; set; } + + public int Type { get; set; } + } +} diff --git a/DeveloperTest/Models/BaseJobModel.cs b/DeveloperTest/Models/BaseJobModel.cs index d2bc052..7cab3d6 100644 --- a/DeveloperTest/Models/BaseJobModel.cs +++ b/DeveloperTest/Models/BaseJobModel.cs @@ -7,5 +7,6 @@ public class BaseJobModel public string Engineer { get; set; } public DateTime When { get; set; } + public int CustomerId { get; set; } } } diff --git a/DeveloperTest/Models/CustomerModel.cs b/DeveloperTest/Models/CustomerModel.cs new file mode 100644 index 0000000..53eff83 --- /dev/null +++ b/DeveloperTest/Models/CustomerModel.cs @@ -0,0 +1,15 @@ +using DeveloperTest.Enums; + +namespace DeveloperTest.Models +{ + public class CustomerModel + { + public int Id { get; set; } + + public string Name { get; set; } + + public CustomerTypeEnum Type { get; set; } + + public string TypeName { get; set; } + } +} diff --git a/DeveloperTest/Models/CustomerTypeModel.cs b/DeveloperTest/Models/CustomerTypeModel.cs new file mode 100644 index 0000000..56d0373 --- /dev/null +++ b/DeveloperTest/Models/CustomerTypeModel.cs @@ -0,0 +1,10 @@ +using DeveloperTest.Enums; + +namespace DeveloperTest.Models +{ + public class CustomerTypeModel + { + public CustomerTypeEnum Id { get; set; } + public string Name { get; set; } + } +} diff --git a/DeveloperTest/Models/JobModel.cs b/DeveloperTest/Models/JobModel.cs index 8f2b5be..994a2ef 100644 --- a/DeveloperTest/Models/JobModel.cs +++ b/DeveloperTest/Models/JobModel.cs @@ -9,5 +9,7 @@ public class JobModel public string Engineer { get; set; } public DateTime When { get; set; } + public string CustomerName { get; set; } + public string CustomerType { get; set; } } } diff --git a/DeveloperTest/Startup.cs b/DeveloperTest/Startup.cs index 5242f22..8fcc32c 100644 --- a/DeveloperTest/Startup.cs +++ b/DeveloperTest/Startup.cs @@ -7,6 +7,7 @@ using DeveloperTest.Business; using DeveloperTest.Business.Interfaces; using DeveloperTest.Database; +using FluentValidation.AspNetCore; namespace DeveloperTest { @@ -23,11 +24,16 @@ public Startup(IConfiguration configuration) public void ConfigureServices(IServiceCollection services) { services.AddControllers(); + services.AddSwaggerGen(); + services.AddAutoMapper(typeof(Startup)); + services.AddFluentValidation(config => config.RegisterValidatorsFromAssemblyContaining()); services.AddDbContext(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddTransient(); + services.AddTransient(); + services.AddTransient(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -36,6 +42,12 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); + app.UseSwagger(); + app.UseSwaggerUI(options => + { + options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1"); + options.RoutePrefix = string.Empty; + }); } app.UseHttpsRedirection(); diff --git a/DeveloperTest/Validators/BaseCustomerModelValidator.cs b/DeveloperTest/Validators/BaseCustomerModelValidator.cs new file mode 100644 index 0000000..55cfc6d --- /dev/null +++ b/DeveloperTest/Validators/BaseCustomerModelValidator.cs @@ -0,0 +1,18 @@ +using DeveloperTest.Models; +using FluentValidation; + +namespace DeveloperTest.Validators +{ + public class BaseCustomerModelValidator : AbstractValidator + { + public BaseCustomerModelValidator() + { + RuleFor(x => x.Name) + .NotEmpty() + .MinimumLength(5); + + RuleFor(x => x.Type) + .NotNull(); + } + } +} diff --git a/DeveloperTest/Validators/BaseJobModelValidator.cs b/DeveloperTest/Validators/BaseJobModelValidator.cs new file mode 100644 index 0000000..7574d20 --- /dev/null +++ b/DeveloperTest/Validators/BaseJobModelValidator.cs @@ -0,0 +1,18 @@ +using DeveloperTest.Models; +using FluentValidation; +using System; + +namespace DeveloperTest.Validators +{ + public class BaseJobModelValidator : AbstractValidator + { + public BaseJobModelValidator() + { + RuleFor(x => x.CustomerId) + .GreaterThan(0); + + RuleFor(x => x.When.Date) + .GreaterThan(DateTime.Now.Date); + } + } +} diff --git a/UnitTests/CustomerServiceTests.cs b/UnitTests/CustomerServiceTests.cs new file mode 100644 index 0000000..4d880be --- /dev/null +++ b/UnitTests/CustomerServiceTests.cs @@ -0,0 +1,61 @@ +using AutoMapper; +using DeveloperTest.Business; +using DeveloperTest.Database; +using DeveloperTest.Database.Models; +using DeveloperTest.Enums; +using DeveloperTest.Models; +using Microsoft.EntityFrameworkCore; +using Moq; +using System.Collections; +using Xunit; + +namespace UnitTests +{ + + /// + /// This is only an example of creating unit tests for my application because it wasn't required in the task. + /// This is more of showing how I would approach unit tests rather than writing them. + /// U can see used libraries and usage of mocks. + /// Thank you :) + /// + public class CustomerServiceTests + { + private readonly Mock> _customerDbSetMock = new Mock>(); + private readonly Mock _applicationDbContextMock = new Mock(); + private readonly Mock _mapperMock = new Mock(); + + private readonly CustomerService _customerService; + + public CustomerServiceTests() + { + _customerDbSetMock.Setup(x => x.AddAsync(It.IsAny(), It.IsAny())).Returns((Customer c) => c); + _applicationDbContextMock.Setup(x => x.Customers).Returns(_customerDbSetMock.Object); + _mapperMock.Setup(x => x.Map(It.IsAny())).Returns(new Customer()); + + _customerService = new CustomerService(_applicationDbContextMock.Object, _mapperMock.Object); + } + + [Theory, MemberData(nameof(CustomerCreateData))] + public async Task AddCustomer_Invoke_CustomerCreatedOnce(BaseCustomerModel input, Customer expectedData) + { + var user = await _customerService.CreateCustomerAsync(input, It.IsAny()); + + Assert.NotNull(user); + + _customerDbSetMock.Verify(x => x.Add(It.Is(u => u.Name == expectedData.Name && u.Type == expectedData.Type && u.Id != 0)), Times.Once); + _applicationDbContextMock.Verify(x => x.SaveChangesAsync(It.IsAny()), Times.Once); + } + } + + public class CustomerCreateData : IEnumerable + { + public IEnumerator GetEnumerator() + { + yield return new object[] { + new BaseCustomerModel { Name = "customer 1", Type = 1 }, + new Customer { Name = "customer 1", Type = CustomerTypeEnum.Small } + }; + } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} \ No newline at end of file diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj new file mode 100644 index 0000000..ef28b5c --- /dev/null +++ b/UnitTests/UnitTests.csproj @@ -0,0 +1,20 @@ + + + + net6.0 + enable + enable + AnyCPU + + + + + + + + + + + + + diff --git a/ui/src/app/app-routing.module.ts b/ui/src/app/app-routing.module.ts index a6af4f8..157078d 100644 --- a/ui/src/app/app-routing.module.ts +++ b/ui/src/app/app-routing.module.ts @@ -1,3 +1,5 @@ +import { CustomerDetailComponent } from './customer-detail/customer-detail.component'; +import { CustomerComponent } from './customer/customer.component'; import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { JobComponent } from './job/job.component'; @@ -9,7 +11,9 @@ const routes: Routes = [ { path: '', component: HomeComponent }, { path: 'home', component: HomeComponent }, { path: 'jobs', component: JobComponent }, - { path: 'job/:id', component: JobDetailComponent } + { path: 'job/:id', component: JobDetailComponent }, + { path: 'customers', component: CustomerComponent }, + { path: 'customer/:id', component: CustomerDetailComponent }, ]; @NgModule({ diff --git a/ui/src/app/app.component.html b/ui/src/app/app.component.html index 49133ec..94af8ff 100644 --- a/ui/src/app/app.component.html +++ b/ui/src/app/app.component.html @@ -2,6 +2,7 @@ \ No newline at end of file diff --git a/ui/src/app/app.module.ts b/ui/src/app/app.module.ts index 0d1f678..6b95279 100644 --- a/ui/src/app/app.module.ts +++ b/ui/src/app/app.module.ts @@ -8,13 +8,17 @@ import { AppComponent } from './app.component'; import { JobComponent } from './job/job.component'; import { HomeComponent } from './home/home.component'; import { JobDetailComponent } from './job-detail/job-detail.component'; +import { CustomerComponent } from './customer/customer.component'; +import { CustomerDetailComponent } from './customer-detail/customer-detail.component'; @NgModule({ declarations: [ AppComponent, JobComponent, HomeComponent, - JobDetailComponent + JobDetailComponent, + CustomerComponent, + CustomerDetailComponent ], imports: [ FormsModule, diff --git a/ui/src/app/customer-detail/customer-detail.component.html b/ui/src/app/customer-detail/customer-detail.component.html new file mode 100644 index 0000000..26a8eba --- /dev/null +++ b/ui/src/app/customer-detail/customer-detail.component.html @@ -0,0 +1,6 @@ +
+

JobId: {{ customer.id }}

+

Engineer: {{ customer.type }}

+

When: {{ customer.typeName }}

+
+Back diff --git a/ui/src/app/customer-detail/customer-detail.component.scss b/ui/src/app/customer-detail/customer-detail.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/ui/src/app/customer-detail/customer-detail.component.spec.ts b/ui/src/app/customer-detail/customer-detail.component.spec.ts new file mode 100644 index 0000000..8ae027e --- /dev/null +++ b/ui/src/app/customer-detail/customer-detail.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CustomerDetailComponent } from './customer-detail.component'; + +describe('CustomerDetailComponent', () => { + let component: CustomerDetailComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ CustomerDetailComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(CustomerDetailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ui/src/app/customer-detail/customer-detail.component.ts b/ui/src/app/customer-detail/customer-detail.component.ts new file mode 100644 index 0000000..262f808 --- /dev/null +++ b/ui/src/app/customer-detail/customer-detail.component.ts @@ -0,0 +1,48 @@ +import { ActivatedRoute } from "@angular/router"; +import { Component, OnInit } from "@angular/core"; +import { CustomerModel } from "../models/customer.Model"; +import { CustomerService } from "../services/customer.service"; + +@Component({ + selector: "app-customer-detail", + templateUrl: "./customer-detail.component.html", + styleUrls: ["./customer-detail.component.scss"], +}) +export class CustomerDetailComponent implements OnInit { + private id: number; + + public customer: CustomerModel; + + constructor( + private route: ActivatedRoute, + private customerService: CustomerService + ) {} + + ngOnInit() { + this.id = this.route.snapshot.params.id; + this.customerService.GetCustomer(this.id).subscribe( + (customerData) => { + this.customer = customerData; + }, + (error) => { + this.handleError(error); + } + ); + } + + handleError(error) { + let errorMessage = ""; + + if (error.error instanceof ErrorEvent) { + // client-side error + + errorMessage = `${error.error.message}`; + } else { + // server-side error + + errorMessage = `${error.status}\nMessage: ${error.message}`; + } + + window.alert(errorMessage); + } +} diff --git a/ui/src/app/customer/customer.component.html b/ui/src/app/customer/customer.component.html new file mode 100644 index 0000000..1493e34 --- /dev/null +++ b/ui/src/app/customer/customer.component.html @@ -0,0 +1,33 @@ +

New customer form

+
+ + + Please select type + + + Please provide name longer than 5 characters + +
+ +

Customers

+ + + + + + + + + + + + + + + +
NameType
{{customer.name}}{{customer.typeName}} + Open +
\ No newline at end of file diff --git a/ui/src/app/customer/customer.component.scss b/ui/src/app/customer/customer.component.scss new file mode 100644 index 0000000..9b45842 --- /dev/null +++ b/ui/src/app/customer/customer.component.scss @@ -0,0 +1,40 @@ +h2 { + margin-left: 15px; + } + + form { + margin: 15px; + + label { + display: block; + } + + input, select, button { + display: block; + width: 250px; + margin-bottom: 15px; + } + + small { + color: red; + margin-top: -12px; + margin-bottom: 15px; + display: block; + } + } + + table { + margin: 15px; + border-collapse: collapse; + + th, td { + border: 1px solid #ddd; + padding: 5px; + min-width: 100px; + text-align: left; + } + + th { + background-color: #ddd; + } + } \ No newline at end of file diff --git a/ui/src/app/customer/customer.component.spec.ts b/ui/src/app/customer/customer.component.spec.ts new file mode 100644 index 0000000..2092a8c --- /dev/null +++ b/ui/src/app/customer/customer.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CustomerComponent } from './customer.component'; + +describe('CustomerComponent', () => { + let component: CustomerComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ CustomerComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(CustomerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ui/src/app/customer/customer.component.ts b/ui/src/app/customer/customer.component.ts new file mode 100644 index 0000000..d2a7a91 --- /dev/null +++ b/ui/src/app/customer/customer.component.ts @@ -0,0 +1,52 @@ +import { NgForm } from '@angular/forms'; +import { CustomerTypeModel } from './../models/customer-type.Model'; +import { CustomerTypeService } from './../services/customer-type.service'; +import { CustomerModel, BaseCustomerModel } from './../models/customer.Model'; +import { CustomerService } from './../services/customer.service'; +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-customer', + templateUrl: './customer.component.html', + styleUrls: ['./customer.component.scss'] +}) +export class CustomerComponent implements OnInit { + + public customers: CustomerModel[] = []; + public customerTypes: CustomerTypeModel[] = []; + + public newCustomer: BaseCustomerModel = { + name: null, + type: null + }; + + constructor(private customerService: CustomerService, private customerTypeService: CustomerTypeService) { } + + ngOnInit(): void { + this.customerService.GetCustomers().subscribe(customers => this.customers = customers, error => this.handleError(error)); + this.customerTypeService.GetCustomerTypes().subscribe(customerTypes => this.customerTypes = customerTypes, error => this.handleError(error)); + } + + public createCustomer(form: NgForm): void { + if (form.invalid) { + alert('form is not valid'); + } else { + this.customerService.CreateCustomer(this.newCustomer).then(() => { + this.customerService.GetCustomers().subscribe(customers => this.customers = customers, error => this.handleError(error)); + }); + } + } + + handleError(error) { + let errorMessage = ""; + + if (error.error instanceof ErrorEvent) { + errorMessage = `${error.error.message}`; + } else { + errorMessage = `${error.status}\nMessage: ${error.message}`; + } + + window.alert(errorMessage); + } + +} diff --git a/ui/src/app/job-detail/job-detail.component.html b/ui/src/app/job-detail/job-detail.component.html index b92e031..6c5bd44 100644 --- a/ui/src/app/job-detail/job-detail.component.html +++ b/ui/src/app/job-detail/job-detail.component.html @@ -1,5 +1,8 @@ -

JobId: {{job.jobId}}

-

Engineer: {{job.engineer}}

-

When: {{job.when | date:'shortDate'}}

- -Back \ No newline at end of file +
+

JobId: {{ job.jobId }}

+

Engineer: {{ job.engineer }}

+

When: {{ job.when | date: "shortDate" }}

+

Customer: {{ job.customerName }}

+

Customer Type: {{ job.customerType }}

+
+Back diff --git a/ui/src/app/job/job.component.html b/ui/src/app/job/job.component.html index 085c531..94bd682 100644 --- a/ui/src/app/job/job.component.html +++ b/ui/src/app/job/job.component.html @@ -1,14 +1,24 @@

New job form

+ Please select an engineer + + + + Please select a customer + Please select a valid date +
@@ -18,6 +28,7 @@

Jobs list

Engineer When + Customer @@ -25,6 +36,7 @@

Jobs list

{{job.engineer}} {{job.when | date:'shortDate'}} + {{job.customerName}} Open diff --git a/ui/src/app/job/job.component.ts b/ui/src/app/job/job.component.ts index e9de751..e83a03f 100644 --- a/ui/src/app/job/job.component.ts +++ b/ui/src/app/job/job.component.ts @@ -1,8 +1,11 @@ +import { CustomerModel } from './../models/customer.Model'; +import { BaseJobModel } from './../models/job.model'; import { Component, OnInit } from '@angular/core'; import { NgForm } from '@angular/forms'; import { EngineerService } from '../services/engineer.service'; import { JobService } from '../services/job.service'; import { JobModel } from '../models/job.model'; +import { CustomerService } from '../services/customer.service'; @Component({ selector: 'app-job', @@ -15,19 +18,23 @@ export class JobComponent implements OnInit { public jobs: JobModel[] = []; - public newJob: JobModel = { - jobId: null, + public customers: CustomerModel[] = []; + + public newJob: BaseJobModel = { engineer: null, - when: null + when: null, + customerId: null }; constructor( private engineerService: EngineerService, - private jobService: JobService) { } + private jobService: JobService, + private customerService: CustomerService) { } ngOnInit() { this.engineerService.GetEngineers().subscribe(engineers => this.engineers = engineers); this.jobService.GetJobs().subscribe(jobs => this.jobs = jobs); + this.customerService.GetCustomers().subscribe(customers => this.customers = customers); } public createJob(form: NgForm): void { diff --git a/ui/src/app/models/customer-type.Model.ts b/ui/src/app/models/customer-type.Model.ts new file mode 100644 index 0000000..d3188bb --- /dev/null +++ b/ui/src/app/models/customer-type.Model.ts @@ -0,0 +1,5 @@ +export interface CustomerTypeModel { + id: number; + name: string; + } + \ No newline at end of file diff --git a/ui/src/app/models/customer.Model.ts b/ui/src/app/models/customer.Model.ts new file mode 100644 index 0000000..444559f --- /dev/null +++ b/ui/src/app/models/customer.Model.ts @@ -0,0 +1,12 @@ +export interface CustomerModel { + id: number; + name: string; + type: number; + typeName: string; + } + + export interface BaseCustomerModel { + name: string; + type: number; + } + \ No newline at end of file diff --git a/ui/src/app/models/job.model.ts b/ui/src/app/models/job.model.ts index 5c3342c..1854758 100644 --- a/ui/src/app/models/job.model.ts +++ b/ui/src/app/models/job.model.ts @@ -2,4 +2,12 @@ export interface JobModel { jobId: number; engineer: string; when: Date; + customerName: string; + customerType: string; +} + +export interface BaseJobModel { + engineer: string; + when: Date; + customerId: number; } diff --git a/ui/src/app/services/customer-type.service.spec.ts b/ui/src/app/services/customer-type.service.spec.ts new file mode 100644 index 0000000..4faa97f --- /dev/null +++ b/ui/src/app/services/customer-type.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { CustomerTypeService } from './customer-type.service'; + +describe('CustomerTypeService', () => { + let service: CustomerTypeService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(CustomerTypeService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/ui/src/app/services/customer-type.service.ts b/ui/src/app/services/customer-type.service.ts new file mode 100644 index 0000000..0279190 --- /dev/null +++ b/ui/src/app/services/customer-type.service.ts @@ -0,0 +1,18 @@ +import { CustomerTypeModel } from './../models/customer-type.Model'; +import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class CustomerTypeService { + + private customerRoute = `http://localhost:63235/customerType` + + constructor(private httpClient: HttpClient) { } + + public GetCustomerTypes(): Observable { + return this.httpClient.get(`${this.customerRoute}`); + } +} diff --git a/ui/src/app/services/customer.service.spec.ts b/ui/src/app/services/customer.service.spec.ts new file mode 100644 index 0000000..adabb97 --- /dev/null +++ b/ui/src/app/services/customer.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { CustomerService } from './customer.service'; + +describe('CustomerService', () => { + let service: CustomerService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(CustomerService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/ui/src/app/services/customer.service.ts b/ui/src/app/services/customer.service.ts new file mode 100644 index 0000000..9cab31f --- /dev/null +++ b/ui/src/app/services/customer.service.ts @@ -0,0 +1,26 @@ +import { CustomerModel, BaseCustomerModel } from './../models/customer.Model'; +import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class CustomerService { + + private customerRoute = `http://localhost:63235/customer` + + constructor(private httpClient: HttpClient) { } + + public GetCustomers(): Observable { + return this.httpClient.get(`${this.customerRoute}`); + } + + public GetCustomer(id: number): Observable { + return this.httpClient.get(`${this.customerRoute}/${id}`); + } + + public CreateCustomer(customer: BaseCustomerModel): Promise { + return this.httpClient.post(`${this.customerRoute}`, customer).toPromise(); + } +} diff --git a/ui/src/app/services/job.service.ts b/ui/src/app/services/job.service.ts index 4e2b0d4..7a9a7b6 100644 --- a/ui/src/app/services/job.service.ts +++ b/ui/src/app/services/job.service.ts @@ -1,3 +1,4 @@ +import { BaseJobModel } from './../models/job.model'; import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; @@ -18,7 +19,7 @@ export class JobService { return this.httpClient.get(`http://localhost:63235/job/${jobId}`); } - public CreateJob(job: JobModel): Promise { + public CreateJob(job: BaseJobModel): Promise { return this.httpClient.post('http://localhost:63235/job', job).toPromise(); } }