diff --git a/src/HelloShop.ProductService/AutoMapper/ProductsMapConfiguration.cs b/src/HelloShop.ProductService/AutoMapper/ProductsMapConfiguration.cs new file mode 100644 index 0000000..ce84097 --- /dev/null +++ b/src/HelloShop.ProductService/AutoMapper/ProductsMapConfiguration.cs @@ -0,0 +1,21 @@ +using AutoMapper; +using HelloShop.ProductService.Entities.Products; +using HelloShop.ProductService.Models.Products; + +namespace HelloShop.ProductService.AutoMapper; + +public class ProductsMapConfiguration : Profile +{ + public ProductsMapConfiguration() + { + CreateMap(); + CreateMap(); + CreateMap().AfterMap((src, dest) => dest.BrandName = src.Brand.Name); + CreateMap(); + + CreateMap(); + CreateMap(); + CreateMap(); + CreateMap(); + } +} diff --git a/src/HelloShop.ProductService/Constants/DbConstants.cs b/src/HelloShop.ProductService/Constants/DbConstants.cs new file mode 100644 index 0000000..a07b029 --- /dev/null +++ b/src/HelloShop.ProductService/Constants/DbConstants.cs @@ -0,0 +1,6 @@ +namespace HelloShop.ProductService.Constants; + +public static class DbConstants +{ + public const string ConnectionStringName = "ProductDatabase"; +} diff --git a/src/HelloShop.ProductService/Controllers/BrandsController.cs b/src/HelloShop.ProductService/Controllers/BrandsController.cs new file mode 100644 index 0000000..bd76dde --- /dev/null +++ b/src/HelloShop.ProductService/Controllers/BrandsController.cs @@ -0,0 +1,115 @@ +using AutoMapper; +using HelloShop.ProductService.Entities.Products; +using HelloShop.ProductService.EntityFrameworks; +using HelloShop.ProductService.Models.Products; +using HelloShop.ServiceDefaults.Models.Paging; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using HelloShop.ServiceDefaults.Extensions; +using Microsoft.AspNetCore.Authorization; +using HelloShop.ProductService.PermissionProviders; + +namespace HelloShop.ProductService; +[Route("api/[controller]")] +[ApiController] +public class BrandsController(ProductServiceDbContext dbContext, IMapper mapper) : ControllerBase +{ + [HttpGet] + [Authorize(CatalogPermissions.Brands.Default)] + public async Task>> GetBrands([FromQuery] KeywordSearchRequest model) + { + IQueryable query = dbContext.Set().AsNoTracking(); + + query = query.WhereIf(model.Keyword is not null, x => model.Keyword != null && x.Name.Contains(model.Keyword)); + + var pagedBrands = query.SortAndPageBy(model); + + return new PagedResponse(mapper.Map>(await pagedBrands.ToListAsync()), await query.CountAsync()); + } + + [HttpGet("{id}")] + [Authorize(CatalogPermissions.Brands.Details)] + public async Task> GetBrand(int id) + { + Brand? entity = await dbContext.Set().FindAsync(id); + + if (entity is null) + { + return NotFound(); + } + + return mapper.Map(entity); + } + + [HttpPost] + [Authorize(CatalogPermissions.Brands.Create)] + public async Task> PostBrand(BrandCreateRequest model) + { + Brand entity = mapper.Map(model); + + await dbContext.AddAsync(entity); + + await dbContext.SaveChangesAsync(); + + BrandDetailsResponse result = mapper.Map(entity); + + return CreatedAtAction(nameof(GetBrand), new { id = entity.Id }, result); + } + + [HttpPut("{id}")] + [Authorize(CatalogPermissions.Brands.Update)] + public async Task PutBrand(int id, BrandUpdateRequest model) + { + if (id != model.Id) + { + return BadRequest(); + } + + Brand entity = mapper.Map(model); + + dbContext.Entry(entity).State = EntityState.Modified; + + try + { + await dbContext.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!dbContext.Set().Any(e => e.Id == id)) + { + return NotFound(); + } + else + { + throw; + } + } + return NoContent(); + } + + [HttpDelete("{id}")] + [Authorize(CatalogPermissions.Brands.Delete)] + public async Task DeleteBrand(int id) + { + Brand? entity = await dbContext.Set().FindAsync(id); + + if (entity is null) + { + return NotFound(); + } + + dbContext.Remove(entity); + + await dbContext.SaveChangesAsync(); + return NoContent(); + } + + [HttpDelete] + [Authorize(CatalogPermissions.Brands.Delete)] + public async Task DeleteBrands([FromQuery] IEnumerable ids) + { + await dbContext.Set().Where(e => ids.Contains(e.Id)).ExecuteDeleteAsync(); + + return NoContent(); + } +} \ No newline at end of file diff --git a/src/HelloShop.ProductService/Controllers/ProductsController.cs b/src/HelloShop.ProductService/Controllers/ProductsController.cs new file mode 100644 index 0000000..997bbc8 --- /dev/null +++ b/src/HelloShop.ProductService/Controllers/ProductsController.cs @@ -0,0 +1,118 @@ +using HelloShop.ProductService.Entities.Products; +using HelloShop.ProductService.EntityFrameworks; +using HelloShop.ProductService.Models.Products; +using HelloShop.ServiceDefaults.Models.Paging; +using Microsoft.AspNetCore.Mvc; +using HelloShop.ServiceDefaults.Extensions; +using AutoMapper; +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Authorization; +using HelloShop.ProductService.PermissionProviders; + +namespace HelloShop.ProductService; + + [Route("api/[controller]")] + [ApiController] + public class ProductsController(ProductServiceDbContext dbContext, IMapper mapper) : ControllerBase + { + [HttpGet] + [Authorize(CatalogPermissions.Products.Default)] + public async Task>> GetProducts([FromQuery] KeywordSearchRequest model) + { + IQueryable query = dbContext.Set().Include(x => x.Brand).AsNoTracking(); + + query = query.WhereIf(model.Keyword is not null, x => model.Keyword != null && x.Name.Contains(model.Keyword)); + + var pagedProducts = query.SortAndPageBy(model); + + return new PagedResponse(mapper.Map>(await pagedProducts.ToListAsync()), await query.CountAsync()); + } + + [HttpGet("{id}")] + [Authorize(CatalogPermissions.Products.Details)] + public async Task> GetProduct(int id) + { + Product? entity = await dbContext.Set().FindAsync(id); + + if (entity is null) + { + return NotFound(); + } + + await dbContext.Entry(entity).Reference(e => e.Brand).LoadAsync(); + + return mapper.Map(entity); + } + + [HttpPost] + [Authorize(CatalogPermissions.Products.Create)] + public async Task> PostProduct(ProductCreateRequest model) + { + Product entity = mapper.Map(model); + + await dbContext.AddAsync(entity); + + await dbContext.SaveChangesAsync(); + + ProductDetailsResponse result = mapper.Map(entity); + + return CreatedAtAction(nameof(GetProduct), new { id = entity.Id }, result); + } + + [HttpPut("{id}")] + [Authorize(CatalogPermissions.Products.Update)] + public async Task PutProduct(int id, ProductUpdateRequest model) + { + if (id != model.Id) + { + return BadRequest(); + } + + Product entity = mapper.Map(model); + + dbContext.Entry(entity).State = EntityState.Modified; + + try + { + await dbContext.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!dbContext.Set().Any(e => e.Id == id)) + { + return NotFound(); + } + else + { + throw; + } + } + return NoContent(); + } + + [HttpDelete("{id}")] + [Authorize(CatalogPermissions.Products.Delete)] + public async Task DeleteProduct(int id) + { + Product? entity = await dbContext.Set().FindAsync(id); + + if (entity is null) + { + return NotFound(); + } + + dbContext.Remove(entity); + + await dbContext.SaveChangesAsync(); + return NoContent(); + } + + [HttpDelete] + [Authorize(CatalogPermissions.Products.Delete)] + public async Task DeleteProducts([FromQuery] IEnumerable ids) + { + await dbContext.Set().Where(e => ids.Contains(e.Id)).ExecuteDeleteAsync(); + + return NoContent(); + } + } \ No newline at end of file diff --git a/src/HelloShop.ProductService/Controllers/WeatherForecastController.cs b/src/HelloShop.ProductService/Controllers/WeatherForecastController.cs deleted file mode 100644 index 185dd30..0000000 --- a/src/HelloShop.ProductService/Controllers/WeatherForecastController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace HelloShop.ProductService.Controllers; - -[ApiController] -[Route("[controller]")] -public class WeatherForecastController : ControllerBase -{ - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - private readonly ILogger _logger; - - public WeatherForecastController(ILogger logger) - { - _logger = logger; - } - - [HttpGet(Name = "GetWeatherForecast")] - public IEnumerable Get() - { - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), - TemperatureC = Random.Shared.Next(-20, 55), - Summary = Summaries[Random.Shared.Next(Summaries.Length)] - }) - .ToArray(); - } -} diff --git a/src/HelloShop.ProductService/DataSeeding/ProductDataSeedingProvider.cs b/src/HelloShop.ProductService/DataSeeding/ProductDataSeedingProvider.cs new file mode 100644 index 0000000..aaddc97 --- /dev/null +++ b/src/HelloShop.ProductService/DataSeeding/ProductDataSeedingProvider.cs @@ -0,0 +1,95 @@ +using HelloShop.ProductService.Entities.Products; +using HelloShop.ProductService.EntityFrameworks; +using HelloShop.ServiceDefaults.Infrastructure; + +namespace HelloShop.ProductService.DataSeeding +{ + public class ProductDataSeedingProvider(ProductServiceDbContext dbContext) : IDataSeedingProvider + { + public async Task SeedingAsync(IServiceProvider ServiceProvider) + { + if (!dbContext.Set().Any()) + { + Dictionary> products = new() + { + { "Google", new List{ + new() { Name = "Android", Price = 156.99m }, + new() { Name = "Gmail", Price = 135.82m }, + new() { Name = "Google Drive", Price = 99.99m }, + new() { Name = "Google Maps", Price = 199.99m }, + new() { Name = "Google Photos", Price = 299.99m }, + new() { Name = "Google Play", Price = 399.99m }, + new() { Name = "Google Search", Price = 499.99m }, + new() { Name = "Google Translate", Price = 599.99m }, + new() { Name = "Google Chrome", Price = 699.99m }, + new() { Name = "Google Earth", Price = 799.99m }, + }}, + { "Microsoft", new List{ + new() { Name = "Windows", Price = 156.99m }, + new() { Name = "Office", Price = 135.82m }, + new() { Name = "Azure", Price = 99.99m }, + new() { Name = "Xbox", Price = 199.99m }, + new() { Name = "Skype", Price = 299.99m }, + new() { Name = "LinkedIn", Price = 399.99m }, + new() { Name = "GitHub", Price = 499.99m }, + new() { Name = "Visual Studio", Price = 599.99m }, + new() { Name = "Bing", Price = 699.99m }, + new() { Name = "OneDrive", Price = 799.99m }, + }}, + { "Apple", new List{ + new() { Name = "iPhone", Price = 156.99m }, + new() { Name = "iPad", Price = 135.82m }, + new() { Name = "Mac", Price = 99.99m }, + new() { Name = "Apple Watch", Price = 199.99m }, + new() { Name = "Apple TV", Price = 299.99m }, + new() { Name = "AirPods", Price = 399.99m }, + new() { Name = "HomePod", Price = 499.99m }, + new() { Name = "iPod", Price = 599.99m }, + new() { Name = "Apple Music", Price = 699.99m }, + new() { Name = "Apple Pay", Price = 799.99m }, + }}, + { "Amazon", new List{ + new() { Name = "Amazon Prime", Price = 156.99m }, + new() { Name = "Kindle", Price = 135.82m }, + new() { Name = "Fire TV", Price = 99.99m }, + new() { Name = "Echo", Price = 199.99m }, + new() { Name = "Ring", Price = 299.99m }, + new() { Name = "Twitch", Price = 399.99m }, + new() { Name = "Audible", Price = 499.99m }, + new() { Name = "Goodreads", Price = 599.99m }, + new() { Name = "IMDb", Price = 699.99m }, + new() { Name = "Whole Foods", Price = 799.99m }, + }}, + { "Samsung", new List + { + new() { Name = "Galaxy", Price = 156.99m }, + new() { Name = "SmartThings", Price = 135.82m }, + new() { Name = "Samsung Pay", Price = 99.99m }, + new() { Name = "Samsung Health", Price = 199.99m }, + new() { Name = "Samsung DeX", Price = 299.99m }, + new() { Name = "Samsung Knox", Price = 399.99m }, + new() { Name = "Samsung Internet", Price = 499.99m }, + new() { Name = "Samsung Cloud", Price = 599.99m }, + new() { Name = "Samsung TV", Price = 699.99m }, + new() { Name = "Samsung Galaxy Store", Price = 799.99m }, + }} + }; + + foreach (var (brandName, productList) in products) + { + var brand = new Brand { Name = brandName }; + + await dbContext.AddAsync(brand); + + foreach (var product in productList) + { + product.Brand = brand; + await dbContext.AddAsync(product); + } + } + + await dbContext.SaveChangesAsync(); + } + } + } +} \ No newline at end of file diff --git a/src/HelloShop.ProductService/Entities/Products/Brand.cs b/src/HelloShop.ProductService/Entities/Products/Brand.cs new file mode 100644 index 0000000..e89e6fa --- /dev/null +++ b/src/HelloShop.ProductService/Entities/Products/Brand.cs @@ -0,0 +1,9 @@ +namespace HelloShop.ProductService.Entities.Products +{ + public class Brand + { + public int Id { get; set; } + + public required string Name { get; set; } + } +} \ No newline at end of file diff --git a/src/HelloShop.ProductService/Entities/Products/Product.cs b/src/HelloShop.ProductService/Entities/Products/Product.cs new file mode 100644 index 0000000..f9e8759 --- /dev/null +++ b/src/HelloShop.ProductService/Entities/Products/Product.cs @@ -0,0 +1,21 @@ +namespace HelloShop.ProductService.Entities.Products +{ + public class Product + { + public int Id { get; set; } + + public required string Name { get; set; } + + public string? Description { get; set; } + + public decimal Price { get; set; } + + public int BrandId { get; set; } + + public Brand Brand { get; set; } = default!; + + public string? ImageUrl { get; set; } + + public DateTimeOffset CreationTime { get; init; } = TimeProvider.System.GetUtcNow(); + } +} \ No newline at end of file diff --git a/src/HelloShop.ProductService/EntityFrameworks/EntityConfigurations/Products/BrandEntityTypeConfiguration.cs b/src/HelloShop.ProductService/EntityFrameworks/EntityConfigurations/Products/BrandEntityTypeConfiguration.cs new file mode 100644 index 0000000..084479f --- /dev/null +++ b/src/HelloShop.ProductService/EntityFrameworks/EntityConfigurations/Products/BrandEntityTypeConfiguration.cs @@ -0,0 +1,14 @@ +using HelloShop.ProductService.Entities.Products; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace HelloShop.ProductService.EntityFrameworks.EntityConfigurations.Products; + + public class BrandEntityTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Brands"); + builder.Property(x => x.Name).HasMaxLength(32); + } + } \ No newline at end of file diff --git a/src/HelloShop.ProductService/EntityFrameworks/EntityConfigurations/Products/ProductEntityTypeConfiguration.cs b/src/HelloShop.ProductService/EntityFrameworks/EntityConfigurations/Products/ProductEntityTypeConfiguration.cs new file mode 100644 index 0000000..98f6036 --- /dev/null +++ b/src/HelloShop.ProductService/EntityFrameworks/EntityConfigurations/Products/ProductEntityTypeConfiguration.cs @@ -0,0 +1,19 @@ +using HelloShop.ProductService.Entities.Products; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace HelloShop.ProductService.EntityFrameworks.EntityConfigurations.Products; + + public class ProductEntityTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Products"); + + builder.Property(x => x.Name).HasMaxLength(32); + + builder.Property(x => x.ImageUrl).HasMaxLength(256); + + builder.HasOne(x => x.Brand).WithMany(); + } + } diff --git a/src/HelloShop.ProductService/EntityFrameworks/Migrations/20240419145737_InitialCreate.Designer.cs b/src/HelloShop.ProductService/EntityFrameworks/Migrations/20240419145737_InitialCreate.Designer.cs new file mode 100644 index 0000000..9c17e47 --- /dev/null +++ b/src/HelloShop.ProductService/EntityFrameworks/Migrations/20240419145737_InitialCreate.Designer.cs @@ -0,0 +1,95 @@ +// +using System; +using HelloShop.ProductService.EntityFrameworks; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace HelloShop.ProductService.EntityFrameworks.Migrations +{ + [DbContext(typeof(ProductServiceDbContext))] + [Migration("20240419145737_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("HelloShop.ProductService.Entities.Products.Brand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.HasKey("Id"); + + b.ToTable("Brands", (string)null); + }); + + modelBuilder.Entity("HelloShop.ProductService.Entities.Products.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BrandId") + .HasColumnType("integer"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ImageUrl") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Price") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("BrandId"); + + b.ToTable("Products", (string)null); + }); + + modelBuilder.Entity("HelloShop.ProductService.Entities.Products.Product", b => + { + b.HasOne("HelloShop.ProductService.Entities.Products.Brand", "Brand") + .WithMany() + .HasForeignKey("BrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Brand"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/HelloShop.ProductService/EntityFrameworks/Migrations/20240419145737_InitialCreate.cs b/src/HelloShop.ProductService/EntityFrameworks/Migrations/20240419145737_InitialCreate.cs new file mode 100644 index 0000000..5a8770d --- /dev/null +++ b/src/HelloShop.ProductService/EntityFrameworks/Migrations/20240419145737_InitialCreate.cs @@ -0,0 +1,68 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace HelloShop.ProductService.EntityFrameworks.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Brands", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "character varying(32)", maxLength: 32, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Brands", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Products", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + Description = table.Column(type: "text", nullable: true), + Price = table.Column(type: "numeric", nullable: false), + BrandId = table.Column(type: "integer", nullable: false), + ImageUrl = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + CreationTime = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Products", x => x.Id); + table.ForeignKey( + name: "FK_Products_Brands_BrandId", + column: x => x.BrandId, + principalTable: "Brands", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Products_BrandId", + table: "Products", + column: "BrandId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Products"); + + migrationBuilder.DropTable( + name: "Brands"); + } + } +} diff --git a/src/HelloShop.ProductService/EntityFrameworks/Migrations/ProductServiceDbContextModelSnapshot.cs b/src/HelloShop.ProductService/EntityFrameworks/Migrations/ProductServiceDbContextModelSnapshot.cs new file mode 100644 index 0000000..2554a97 --- /dev/null +++ b/src/HelloShop.ProductService/EntityFrameworks/Migrations/ProductServiceDbContextModelSnapshot.cs @@ -0,0 +1,92 @@ +// +using System; +using HelloShop.ProductService.EntityFrameworks; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace HelloShop.ProductService.EntityFrameworks.Migrations +{ + [DbContext(typeof(ProductServiceDbContext))] + partial class ProductServiceDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("HelloShop.ProductService.Entities.Products.Brand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.HasKey("Id"); + + b.ToTable("Brands", (string)null); + }); + + modelBuilder.Entity("HelloShop.ProductService.Entities.Products.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BrandId") + .HasColumnType("integer"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ImageUrl") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Price") + .HasColumnType("numeric"); + + b.HasKey("Id"); + + b.HasIndex("BrandId"); + + b.ToTable("Products", (string)null); + }); + + modelBuilder.Entity("HelloShop.ProductService.Entities.Products.Product", b => + { + b.HasOne("HelloShop.ProductService.Entities.Products.Brand", "Brand") + .WithMany() + .HasForeignKey("BrandId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Brand"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/HelloShop.ProductService/EntityFrameworks/ProductServiceDbContext.cs b/src/HelloShop.ProductService/EntityFrameworks/ProductServiceDbContext.cs new file mode 100644 index 0000000..78eff35 --- /dev/null +++ b/src/HelloShop.ProductService/EntityFrameworks/ProductServiceDbContext.cs @@ -0,0 +1,14 @@ +using Microsoft.EntityFrameworkCore; +using System.Reflection; + +namespace HelloShop.ProductService.EntityFrameworks; + + public class ProductServiceDbContext(DbContextOptions options) : DbContext(options) + { + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + } + } diff --git a/src/HelloShop.ProductService/HelloShop.ProductService.csproj b/src/HelloShop.ProductService/HelloShop.ProductService.csproj index eeb933d..6617dda 100644 --- a/src/HelloShop.ProductService/HelloShop.ProductService.csproj +++ b/src/HelloShop.ProductService/HelloShop.ProductService.csproj @@ -5,9 +5,20 @@ enable + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + \ No newline at end of file diff --git a/src/HelloShop.ProductService/Models/Products/BrandCreateRequest.cs b/src/HelloShop.ProductService/Models/Products/BrandCreateRequest.cs new file mode 100644 index 0000000..464c7e4 --- /dev/null +++ b/src/HelloShop.ProductService/Models/Products/BrandCreateRequest.cs @@ -0,0 +1,6 @@ +namespace HelloShop.ProductService.Models.Products; + +public class BrandCreateRequest +{ + public required string Name { get; init; } +} diff --git a/src/HelloShop.ProductService/Models/Products/BrandDetailsResponse.cs b/src/HelloShop.ProductService/Models/Products/BrandDetailsResponse.cs new file mode 100644 index 0000000..96b16fa --- /dev/null +++ b/src/HelloShop.ProductService/Models/Products/BrandDetailsResponse.cs @@ -0,0 +1,8 @@ +namespace HelloShop.ProductService.Models.Products; + +public class BrandDetailsResponse +{ + public int Id { get; set; } + + public required string Name { get; set; } +} diff --git a/src/HelloShop.ProductService/Models/Products/BrandListItem.cs b/src/HelloShop.ProductService/Models/Products/BrandListItem.cs new file mode 100644 index 0000000..6a84182 --- /dev/null +++ b/src/HelloShop.ProductService/Models/Products/BrandListItem.cs @@ -0,0 +1,8 @@ +namespace HelloShop.ProductService.Models.Products; + +public class BrandListItem +{ + public int Id { get; set; } + + public required string Name { get; set; } +} diff --git a/src/HelloShop.ProductService/Models/Products/BrandUpdateRequest.cs b/src/HelloShop.ProductService/Models/Products/BrandUpdateRequest.cs new file mode 100644 index 0000000..1ebab26 --- /dev/null +++ b/src/HelloShop.ProductService/Models/Products/BrandUpdateRequest.cs @@ -0,0 +1,8 @@ +namespace HelloShop.ProductService.Models.Products; + +public class BrandUpdateRequest +{ + public int Id { get; init; } + + public required string Name { get; init; } +} diff --git a/src/HelloShop.ProductService/Models/Products/ProductCreateRequest.cs b/src/HelloShop.ProductService/Models/Products/ProductCreateRequest.cs new file mode 100644 index 0000000..68160b5 --- /dev/null +++ b/src/HelloShop.ProductService/Models/Products/ProductCreateRequest.cs @@ -0,0 +1,15 @@ +namespace HelloShop.ProductService.Models.Products +{ + public class ProductCreateRequest + { + public required string Name { get; init; } + + public string? Description { get; init; } + + public decimal Price { get; init; } + + public int BrandId { get; init; } + + public string? ImageUrl { get; init; } + } +} \ No newline at end of file diff --git a/src/HelloShop.ProductService/Models/Products/ProductDetailsResponse.cs b/src/HelloShop.ProductService/Models/Products/ProductDetailsResponse.cs new file mode 100644 index 0000000..4e5a7c5 --- /dev/null +++ b/src/HelloShop.ProductService/Models/Products/ProductDetailsResponse.cs @@ -0,0 +1,17 @@ +namespace HelloShop.ProductService.Models.Products; +public class ProductDetailsResponse +{ + public int Id { get; init; } + + public required string Name { get; init; } + + public string? Description { get; init; } + + public decimal Price { get; init; } + + public required BrandDetailsResponse Brand { get; init; } + + public string? ImageUrl { get; init; } + + public DateTimeOffset CreationTime { get; init; } +} diff --git a/src/HelloShop.ProductService/Models/Products/ProductListItem.cs b/src/HelloShop.ProductService/Models/Products/ProductListItem.cs new file mode 100644 index 0000000..b7a55a4 --- /dev/null +++ b/src/HelloShop.ProductService/Models/Products/ProductListItem.cs @@ -0,0 +1,14 @@ +namespace HelloShop.ProductService.Models.Products; + +public class ProductListItem +{ + public int Id { get; init; } + + public required string Name { get; init; } + + public decimal Price { get; init; } + + public required string BrandName { get; set; } + + public DateTimeOffset CreationTime { get; init; } +} diff --git a/src/HelloShop.ProductService/Models/Products/ProductUpdateRequest.cs b/src/HelloShop.ProductService/Models/Products/ProductUpdateRequest.cs new file mode 100644 index 0000000..551ec68 --- /dev/null +++ b/src/HelloShop.ProductService/Models/Products/ProductUpdateRequest.cs @@ -0,0 +1,17 @@ +namespace HelloShop.ProductService.Models.Products +{ + public class ProductUpdateRequest + { + public int Id { get; init; } + + public required string Name { get; init; } + + public string? Description { get; init; } + + public decimal Price { get; init; } + + public int BrandId { get; init; } + + public string? ImageUrl { get; init; } + } +} \ No newline at end of file diff --git a/src/HelloShop.ProductService/PermissionProviders/CatalogPermissionDefinitionProvider.cs b/src/HelloShop.ProductService/PermissionProviders/CatalogPermissionDefinitionProvider.cs new file mode 100644 index 0000000..a072b14 --- /dev/null +++ b/src/HelloShop.ProductService/PermissionProviders/CatalogPermissionDefinitionProvider.cs @@ -0,0 +1,25 @@ +using HelloShop.ServiceDefaults.Permissions; +using Microsoft.Extensions.Localization; +using System.Security; + +namespace HelloShop.ProductService.PermissionProviders; + + public class CatalogPermissionDefinitionProvider(IStringLocalizer localizer) : IPermissionDefinitionProvider + { + public void Define(PermissionDefinitionContext context) + { + var identityGroup = context.AddGroup(CatalogPermissions.GroupName, localizer["CatalogManagement"]); + + var productsPermission = identityGroup.AddPermission(CatalogPermissions.Products.Default, localizer["ProductManagement"]); + productsPermission.AddChild(CatalogPermissions.Products.Create, localizer["Create"]); + productsPermission.AddChild(CatalogPermissions.Products.Details, localizer["Details"]); + productsPermission.AddChild(CatalogPermissions.Products.Update, localizer["Edit"]); + productsPermission.AddChild(CatalogPermissions.Products.Delete, localizer["Delete"]); + + var brandsPermission = identityGroup.AddPermission(CatalogPermissions.Brands.Default, localizer["BrandManagement"]); + brandsPermission.AddChild(CatalogPermissions.Brands.Create, localizer["Create"]); + brandsPermission.AddChild(CatalogPermissions.Brands.Details, localizer["Details"]); + brandsPermission.AddChild(CatalogPermissions.Brands.Update, localizer["Edit"]); + brandsPermission.AddChild(CatalogPermissions.Brands.Delete, localizer["Delete"]); + } + } \ No newline at end of file diff --git a/src/HelloShop.ProductService/PermissionProviders/CatalogPermissions.cs b/src/HelloShop.ProductService/PermissionProviders/CatalogPermissions.cs new file mode 100644 index 0000000..1c35d39 --- /dev/null +++ b/src/HelloShop.ProductService/PermissionProviders/CatalogPermissions.cs @@ -0,0 +1,24 @@ +namespace HelloShop.ProductService.PermissionProviders; + + public static class CatalogPermissions + { + public const string GroupName = "Catalog"; + + public static class Products + { + public const string Default = GroupName + ".Products"; + public const string Details = Default + ".Details"; + public const string Create = Default + ".Create"; + public const string Update = Default + ".Update"; + public const string Delete = Default + ".Delete"; + } + + public static class Brands + { + public const string Default = GroupName + ".Brands"; + public const string Details = Default + ".Details"; + public const string Create = Default + ".Create"; + public const string Update = Default + ".Update"; + public const string Delete = Default + ".Delete"; + } + } \ No newline at end of file diff --git a/src/HelloShop.ProductService/Program.cs b/src/HelloShop.ProductService/Program.cs index edda8a7..38f9161 100644 --- a/src/HelloShop.ProductService/Program.cs +++ b/src/HelloShop.ProductService/Program.cs @@ -1,3 +1,8 @@ +using HelloShop.ProductService.Constants; +using HelloShop.ProductService.EntityFrameworks; +using Microsoft.EntityFrameworkCore; +using HelloShop.ServiceDefaults.Extensions; + var builder = WebApplication.CreateBuilder(args); builder.AddServiceDefaults(); @@ -5,25 +10,39 @@ builder.AddServiceDefaults(); // Add services to the container. builder.Services.AddControllers(); -// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); + +// Add extensions services to the container. +builder.Services.AddDbContext(options => +{ + options.UseNpgsql(builder.Configuration.GetConnectionString(DbConstants.ConnectionStringName)); +}); +builder.Services.AddHttpClient().AddHttpContextAccessor().AddDistributedMemoryCache(); +builder.Services.AddDataSeedingProviders(); +builder.Services.AddCustomLocalization(); +builder.Services.AddOpenApi(); +builder.Services.AddModelMapper().AddModelValidator(); +builder.Services.AddLocalization().AddPermissionDefinitions(); +builder.Services.AddAuthorization().AddRemotePermissionChecker(options => +{ + options.ApiEndpoint = "https://localhost:5001"; +}).AddCustomAuthorization(); +// End addd extensions services to the container. var app = builder.Build(); app.MapDefaultEndpoints(); -// Configure the HTTP request pipeline. -if (app.Environment.IsDevelopment()) -{ - app.UseSwagger(); - app.UseSwaggerUI(); -} - app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); +// Configure extensions request pipeline. +app.UseDataSeedingProviders(); +app.UseCustomLocalization(); +app.UseOpenApi(); +app.MapGroup("api/Permissions").MapPermissionDefinitions("Permissions"); +// End configure extensions request pipeline. + app.Run(); diff --git a/src/HelloShop.ProductService/Resources/Models/Products/BrandCreateRequest.en-US.resx b/src/HelloShop.ProductService/Resources/Models/Products/BrandCreateRequest.en-US.resx new file mode 100644 index 0000000..adb478a --- /dev/null +++ b/src/HelloShop.ProductService/Resources/Models/Products/BrandCreateRequest.en-US.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Name + + \ No newline at end of file diff --git a/src/HelloShop.ProductService/Resources/Models/Products/BrandCreateRequest.zh-CN.resx b/src/HelloShop.ProductService/Resources/Models/Products/BrandCreateRequest.zh-CN.resx new file mode 100644 index 0000000..e5ddb8a --- /dev/null +++ b/src/HelloShop.ProductService/Resources/Models/Products/BrandCreateRequest.zh-CN.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 名称 + + \ No newline at end of file diff --git a/src/HelloShop.ProductService/Resources/Models/Products/BrandUpdateRequest.en-US.resx b/src/HelloShop.ProductService/Resources/Models/Products/BrandUpdateRequest.en-US.resx new file mode 100644 index 0000000..bb2d1f0 --- /dev/null +++ b/src/HelloShop.ProductService/Resources/Models/Products/BrandUpdateRequest.en-US.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Identifier + + + Name + + \ No newline at end of file diff --git a/src/HelloShop.ProductService/Resources/Models/Products/BrandUpdateRequest.zh-CN.resx b/src/HelloShop.ProductService/Resources/Models/Products/BrandUpdateRequest.zh-CN.resx new file mode 100644 index 0000000..a5a3060 --- /dev/null +++ b/src/HelloShop.ProductService/Resources/Models/Products/BrandUpdateRequest.zh-CN.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 编号 + + + 名称 + + \ No newline at end of file diff --git a/src/HelloShop.ProductService/Resources/Models/Products/ProductCreateRequest.en-US.resx b/src/HelloShop.ProductService/Resources/Models/Products/ProductCreateRequest.en-US.resx new file mode 100644 index 0000000..8fb5449 --- /dev/null +++ b/src/HelloShop.ProductService/Resources/Models/Products/ProductCreateRequest.en-US.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Brand + + + Description + + + Image + + + Name + + + Price + + \ No newline at end of file diff --git a/src/HelloShop.ProductService/Resources/Models/Products/ProductCreateRequest.zh-CN.resx b/src/HelloShop.ProductService/Resources/Models/Products/ProductCreateRequest.zh-CN.resx new file mode 100644 index 0000000..792ebfb --- /dev/null +++ b/src/HelloShop.ProductService/Resources/Models/Products/ProductCreateRequest.zh-CN.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 品牌 + + + 描述 + + + 图片 + + + 名称 + + + 价格 + + \ No newline at end of file diff --git a/src/HelloShop.ProductService/Resources/Models/Products/ProductUpdateRequest.en-US.resx b/src/HelloShop.ProductService/Resources/Models/Products/ProductUpdateRequest.en-US.resx new file mode 100644 index 0000000..478a326 --- /dev/null +++ b/src/HelloShop.ProductService/Resources/Models/Products/ProductUpdateRequest.en-US.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Brand + + + Description + + + Identifier + + + Image + + + Name + + + Price + + \ No newline at end of file diff --git a/src/HelloShop.ProductService/Resources/Models/Products/ProductUpdateRequest.zh-CN.resx b/src/HelloShop.ProductService/Resources/Models/Products/ProductUpdateRequest.zh-CN.resx new file mode 100644 index 0000000..2f6e775 --- /dev/null +++ b/src/HelloShop.ProductService/Resources/Models/Products/ProductUpdateRequest.zh-CN.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 品牌 + + + 描述 + + + 编号 + + + 图片 + + + 名称 + + + 价格 + + \ No newline at end of file diff --git a/src/HelloShop.ProductService/Resources/PermissionProviders/CatalogPermissionDefinitionProvider.en-US.resx b/src/HelloShop.ProductService/Resources/PermissionProviders/CatalogPermissionDefinitionProvider.en-US.resx new file mode 100644 index 0000000..869aafa --- /dev/null +++ b/src/HelloShop.ProductService/Resources/PermissionProviders/CatalogPermissionDefinitionProvider.en-US.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Brand Management + + + Catalog Management + + + Create + + + Delete + + + Details + + + Edit + + + Product Management + + \ No newline at end of file diff --git a/src/HelloShop.ProductService/Resources/PermissionProviders/CatalogPermissionDefinitionProvider.zh-CN.resx b/src/HelloShop.ProductService/Resources/PermissionProviders/CatalogPermissionDefinitionProvider.zh-CN.resx new file mode 100644 index 0000000..6d6cf82 --- /dev/null +++ b/src/HelloShop.ProductService/Resources/PermissionProviders/CatalogPermissionDefinitionProvider.zh-CN.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 品牌管理 + + + 商品管理 + + + 创建 + + + 删除 + + + 详细 + + + 编辑 + + + 产品管理 + + \ No newline at end of file diff --git a/src/HelloShop.ProductService/Validations/Products/BrandCreateRequestValidator.cs b/src/HelloShop.ProductService/Validations/Products/BrandCreateRequestValidator.cs new file mode 100644 index 0000000..8fdd0c3 --- /dev/null +++ b/src/HelloShop.ProductService/Validations/Products/BrandCreateRequestValidator.cs @@ -0,0 +1,13 @@ +using FluentValidation; +using HelloShop.ProductService.Models.Products; + +namespace HelloShop.ProductService.Validations.Products +{ + public class BrandCreateRequestValidator : AbstractValidator + { + public BrandCreateRequestValidator() + { + RuleFor(x => x.Name).NotNull().NotEmpty().Length(8, 32); + } + } +} \ No newline at end of file diff --git a/src/HelloShop.ProductService/Validations/Products/BrandUpdateRequestValidator.cs b/src/HelloShop.ProductService/Validations/Products/BrandUpdateRequestValidator.cs new file mode 100644 index 0000000..7b0ba35 --- /dev/null +++ b/src/HelloShop.ProductService/Validations/Products/BrandUpdateRequestValidator.cs @@ -0,0 +1,14 @@ +using FluentValidation; +using HelloShop.ProductService.Models.Products; + +namespace HelloShop.ProductService.Validations.Products +{ + public class BrandUpdateRequestValidator : AbstractValidator + { + public BrandUpdateRequestValidator() + { + RuleFor(x => x.Id).GreaterThan(0); + RuleFor(x => x.Name).NotNull().NotEmpty().Length(8, 32); + } + } +} \ No newline at end of file diff --git a/src/HelloShop.ProductService/Validations/Products/ProductCreateRequestValidator.cs b/src/HelloShop.ProductService/Validations/Products/ProductCreateRequestValidator.cs new file mode 100644 index 0000000..f6829ad --- /dev/null +++ b/src/HelloShop.ProductService/Validations/Products/ProductCreateRequestValidator.cs @@ -0,0 +1,20 @@ +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +using FluentValidation; +using HelloShop.ProductService.Models.Products; + +namespace HelloWorld.ProductService.Validations.Products +{ + public class ProductCreateRequestValidator : AbstractValidator + { + public ProductCreateRequestValidator() + { + RuleFor(x => x.Name).NotNull().NotEmpty().Length(8, 32); + RuleFor(x => x.Description).Length(8, 256); + RuleFor(x => x.Price).GreaterThan(0); + RuleFor(x => x.BrandId).GreaterThan(0); + RuleFor(x => x.ImageUrl).Length(8, 256); + } + } +} diff --git a/src/HelloShop.ProductService/Validations/Products/ProductUpdateRequestValidator.cs b/src/HelloShop.ProductService/Validations/Products/ProductUpdateRequestValidator.cs new file mode 100644 index 0000000..4dfa1dc --- /dev/null +++ b/src/HelloShop.ProductService/Validations/Products/ProductUpdateRequestValidator.cs @@ -0,0 +1,20 @@ +// Copyright (c) HelloShop Corporation. All rights reserved. +// See the license file in the project root for more information. + +using FluentValidation; +using HelloShop.ProductService.Models.Products; + +namespace HelloWorld.ProductService.Validations.Products +{ + public class ProductUpdateRequestValidator : AbstractValidator + { + public ProductUpdateRequestValidator() + { + RuleFor(x => x.Name).NotNull().NotEmpty().Length(8, 32); + RuleFor(x => x.Description).Length(8, 256); + RuleFor(x => x.Price).GreaterThan(0); + RuleFor(x => x.BrandId).GreaterThan(0); + RuleFor(x => x.ImageUrl).Length(8, 256); + } + } +} diff --git a/src/HelloShop.ProductService/appsettings.json b/src/HelloShop.ProductService/appsettings.json index 10f68b8..1c3fe29 100644 --- a/src/HelloShop.ProductService/appsettings.json +++ b/src/HelloShop.ProductService/appsettings.json @@ -5,5 +5,8 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "ConnectionStrings": { + "ProductDatabase": "Host=localhost;Port=5432;Database=ProductService;Username=postgres;Password=postgres" + } } diff --git a/src/HelloShop.ServiceDefaults/Extensions/PermissionExtensions.cs b/src/HelloShop.ServiceDefaults/Extensions/PermissionExtensions.cs index 55732d8..62f6e9d 100644 --- a/src/HelloShop.ServiceDefaults/Extensions/PermissionExtensions.cs +++ b/src/HelloShop.ServiceDefaults/Extensions/PermissionExtensions.cs @@ -17,9 +17,9 @@ public static class PermissionExtensions var permissionDefinitionProviders = assembly.ExportedTypes.Where(t => t.IsAssignableTo(typeof(IPermissionDefinitionProvider))); - permissionDefinitionProviders.ToList().ForEach(t => services.AddSingleton(typeof(IPermissionDefinitionProvider), t)); + permissionDefinitionProviders.ToList().ForEach(t => services.AddTransient(typeof(IPermissionDefinitionProvider), t)); - services.AddSingleton(); + services.AddTransient(); return services; }