使用分布式事件总线异步通信

This commit is contained in:
hello 2024-09-24 22:53:54 +08:00
parent 385b09d482
commit c16d6ece15
26 changed files with 1130 additions and 91 deletions

View File

@ -0,0 +1,17 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.BasketService.DistributedEvents.Events;
using HelloShop.BasketService.Repositories;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
namespace HelloShop.BasketService.DistributedEvents.EventHandling
{
public class OrderStartedDistributedEventHandler(IBasketRepository repository) : IDistributedEventHandler<OrderStartedDistributedEvent>
{
public async Task HandleAsync(OrderStartedDistributedEvent @event)
{
await repository.DeleteBasketAsync(@event.UserId);
}
}
}

View File

@ -0,0 +1,9 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
namespace HelloShop.BasketService.DistributedEvents.Events
{
public record OrderStartedDistributedEvent(int UserId) : DistributedEvent;
}

View File

@ -2,8 +2,12 @@
// See the license file in the project root for more information.
using Calzolari.Grpc.AspNetCore.Validation;
using HelloShop.BasketService.DistributedEvents.EventHandling;
using HelloShop.BasketService.DistributedEvents.Events;
using HelloShop.BasketService.Repositories;
using HelloShop.BasketService.Services;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using HelloShop.ServiceDefaults.DistributedEvents.DaprBuildingBlocks;
using HelloShop.ServiceDefaults.Extensions;
using Microsoft.IdentityModel.Tokens;
using System.Text;
@ -38,6 +42,9 @@ builder.Services.AddLocalization().AddPermissionDefinitions();
builder.Services.AddAuthorization().AddRemotePermissionChecker().AddCustomAuthorization();
builder.Services.AddGrpcValidation();
builder.Services.AddCors();
builder.AddDaprDistributedEventBus().AddSubscription<OrderStartedDistributedEvent, OrderStartedDistributedEventHandler>();
// End addd extensions services to the container.
var app = builder.Build();
@ -50,6 +57,7 @@ app.MapDefaultEndpoints();
app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client.").WithTags("Welcome");
// Configure extensions request pipeline.
app.MapDaprDistributedEventBus();
app.UseAuthentication().UseAuthorization();
app.MapGrpcService<GreeterService>();
app.MapGrpcService<CustomerBasketService>();

View File

@ -2,17 +2,19 @@
// See the license file in the project root for more information.
using AutoMapper;
using HelloShop.OrderingService.DistributedEvents.Events;
using HelloShop.OrderingService.Entities.Buyers;
using HelloShop.OrderingService.Entities.Orders;
using HelloShop.OrderingService.Infrastructure;
using HelloShop.OrderingService.LocalEvents;
using HelloShop.OrderingService.Services;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace HelloShop.OrderingService.Commands.Orders
{
public class CreateOrderCommandHandler(IMediator mediator, OrderingServiceDbContext dbContext, IMapper mapper) : IRequestHandler<CreateOrderCommand, bool>
public class CreateOrderCommandHandler(IMediator mediator, OrderingServiceDbContext dbContext, IMapper mapper, IDistributedEventBus distributedEventBus) : IRequestHandler<CreateOrderCommand, bool>
{
public async Task<bool> Handle(CreateOrderCommand request, CancellationToken cancellationToken)
{
@ -45,6 +47,8 @@ namespace HelloShop.OrderingService.Commands.Orders
await mediator.Publish(new OrderStartedLocalEvent(order), cancellationToken);
await distributedEventBus.PublishAsync(new OrderStartedDistributedEvent(request.UserId), cancellationToken);
return await Task.FromResult(true);
}
}

View File

@ -0,0 +1,22 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.OrderingService.DistributedEvents.Events;
using HelloShop.OrderingService.Entities.Orders;
using HelloShop.OrderingService.Infrastructure;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
namespace HelloShop.OrderingService.DistributedEvents.EventHandling
{
public class OrderStockConfirmedDistributedEventHandler(OrderingServiceDbContext dbContext) : IDistributedEventHandler<OrderStockConfirmedDistributedEvent>
{
public async Task HandleAsync(OrderStockConfirmedDistributedEvent @event)
{
Order order = await dbContext.Set<Order>().FindAsync(@event.OrderId) ?? throw new Exception($"Order with id {@event.OrderId} not found");
order.OrderStatus = OrderStatus.StockConfirmed;
await dbContext.SaveChangesAsync();
}
}
}

View File

@ -0,0 +1,23 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.OrderingService.DistributedEvents.Events;
using HelloShop.OrderingService.Entities.Orders;
using HelloShop.OrderingService.Infrastructure;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
namespace HelloShop.OrderingService.DistributedEvents.EventHandling
{
public class OrderStockRejectedDistributedEventHandler(OrderingServiceDbContext dbContext) : IDistributedEventHandler<OrderStockRejectedDistributedEvent>
{
public async Task HandleAsync(OrderStockRejectedDistributedEvent @event)
{
Order order = await dbContext.Set<Order>().FindAsync(@event.OrderId) ?? throw new Exception($"Order with id {@event.OrderId} not found");
order.OrderStatus = OrderStatus.Cancelled;
order.Description = "Product out of stock.";
await dbContext.SaveChangesAsync();
}
}
}

View File

@ -0,0 +1,11 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
namespace HelloShop.OrderingService.DistributedEvents.Events
{
public record OrderAwaitingValidationDistributedEvent(int OrderId, IEnumerable<OrderStockItem> OrderStockItems) : DistributedEvent;
public record OrderStockItem(int ProductId, int Units);
}

View File

@ -0,0 +1,9 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
namespace HelloShop.OrderingService.DistributedEvents.Events
{
public record OrderStartedDistributedEvent(int UserId) : DistributedEvent;
}

View File

@ -0,0 +1,9 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
namespace HelloShop.OrderingService.DistributedEvents.Events
{
public record OrderStockConfirmedDistributedEvent(int OrderId) : DistributedEvent;
}

View File

@ -0,0 +1,9 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
namespace HelloShop.OrderingService.DistributedEvents.Events
{
public record OrderStockRejectedDistributedEvent(int OrderId) : DistributedEvent;
}

View File

@ -5,6 +5,9 @@ using HelloShop.OrderingService.Behaviors;
using HelloShop.OrderingService.Constants;
using HelloShop.OrderingService.Infrastructure;
using HelloShop.OrderingService.Services;
using HelloShop.OrderingService.Workers;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using HelloShop.ServiceDefaults.DistributedEvents.DaprBuildingBlocks;
using HelloShop.ServiceDefaults.Extensions;
using Microsoft.EntityFrameworkCore;
using System.Reflection;
@ -35,11 +38,21 @@ namespace HelloShop.OrderingService.Extensions
builder.Services.AddModelMapper().AddModelValidator();
builder.Services.AddTransient<ISmsSender, MessageService>().AddTransient<IEmailSender, MessageService>();
builder.AddDaprDistributedEventBus().AddSubscriptionFromAssembly();
builder.Services.Configure<HostOptions>(hostOptions =>
{
hostOptions.BackgroundServiceExceptionBehavior = BackgroundServiceExceptionBehavior.Ignore;
});
builder.Services.AddHostedService<GracePeriodWorker>();
}
public static WebApplication MapApplicationEndpoints(this WebApplication app)
{
app.UseDataSeedingProviders();
app.MapDaprDistributedEventBus();
return app;
}

View File

@ -0,0 +1,55 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.OrderingService.DistributedEvents.Events;
using HelloShop.OrderingService.Entities.Orders;
using HelloShop.OrderingService.Infrastructure;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using Microsoft.EntityFrameworkCore;
namespace HelloShop.OrderingService.Workers
{
public class GracePeriodWorker(IServiceScopeFactory serviceScopeFactory, ILogger<GracePeriodWorker> logger) : BackgroundService
{
protected async override Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
if (logger.IsEnabled(LogLevel.Information))
{
logger.LogInformation("{Worker} background task is doing background work.", GetType().Name);
}
using var scope = serviceScopeFactory.CreateAsyncScope();
var dbContext = scope.ServiceProvider.GetRequiredService<OrderingServiceDbContext>();
var distributedEventBus = scope.ServiceProvider.GetRequiredService<IDistributedEventBus>();
DateTimeOffset dateTimeOffset = DateTimeOffset.UtcNow.AddMinutes(-1);
try
{
var orders = await dbContext.Set<Order>().Include(o => o.OrderItems).Where(o => o.OrderStatus == OrderStatus.Submitted && o.OrderDate >= dateTimeOffset).ToListAsync(stoppingToken);
foreach (var order in orders)
{
order.OrderStatus = OrderStatus.AwaitingValidation;
var orderStockList = order.OrderItems.Select(orderItem => new OrderStockItem(orderItem.ProductId, orderItem.Units));
await distributedEventBus.PublishAsync(new OrderAwaitingValidationDistributedEvent(order.Id, orderStockList), stoppingToken);
await dbContext.SaveChangesAsync(stoppingToken);
}
}
catch (Exception ex)
{
logger.LogError(ex, "Error occurred executing {Worker} background task.", GetType().Name);
}
await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
}
}
}
}

View File

@ -4,94 +4,52 @@
using HelloShop.ProductService.Entities.Products;
using HelloShop.ProductService.Infrastructure;
using HelloShop.ServiceDefaults.Infrastructure;
using Microsoft.EntityFrameworkCore;
using System.Text.Json;
namespace HelloShop.ProductService.DataSeeding
{
public class ProductDataSeedingProvider(ProductServiceDbContext dbContext) : IDataSeedingProvider
public class ProductDataSeedingProvider(ProductServiceDbContext dbContext, IWebHostEnvironment env, ILogger<ProductDataSeedingProvider> logger) : IDataSeedingProvider
{
public record CatalogSourceEntry(int Id, string Name, string Type, string Brand, string Description, decimal Price);
public async Task SeedingAsync(IServiceProvider ServiceProvider, CancellationToken cancellationToken = default)
{
await dbContext.Database.EnsureCreatedAsync(cancellationToken);
if (!dbContext.Set<Product>().Any())
{
Dictionary<string, List<Product>> products = new()
string sourcePath = Path.Combine(env.ContentRootPath, "DataSeeding", "SetupCatalogSamples.json");
string sourceJson = await File.ReadAllTextAsync(sourcePath, cancellationToken);
var catalogItems = JsonSerializer.Deserialize<IEnumerable<CatalogSourceEntry>>(sourceJson);
if (catalogItems != null && catalogItems.Any())
{
{ "Google", new List<Product>{
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<Product>{
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<Product>{
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<Product>{
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<Product>
dbContext.RemoveRange(dbContext.Set<Brand>());
await dbContext.Set<Brand>().AddRangeAsync(catalogItems.DistinctBy(x => x.Type).Select(x => new Brand { Name = x.Type }), cancellationToken);
await dbContext.SaveChangesAsync(cancellationToken);
var brandIdsByName = await dbContext.Set<Brand>().ToDictionaryAsync(x => x.Name, x => x.Id, cancellationToken: cancellationToken);
logger.LogInformation("Seeded catalog with {NumBrands} brands", brandIdsByName.Count);
await dbContext.Set<Product>().AddRangeAsync(catalogItems.Select(x => new Product
{
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 },
}}
};
Id = x.Id,
Name = x.Name,
Description = x.Description,
Price = x.Price,
BrandId = brandIdsByName[x.Type],
ImageUrl = $"https://oss.xcode.me/notes/helloshop/products/{x.Id}.webp",
AvailableStock = 100
}), cancellationToken);
foreach (var (brandName, productList) in products)
{
var brand = new Brand { Name = brandName };
int result = await dbContext.SaveChangesAsync(cancellationToken);
await dbContext.AddAsync(brand, cancellationToken);
foreach (var product in productList)
{
product.Brand = brand;
await dbContext.AddAsync(product, cancellationToken);
}
logger.LogInformation("Seeded catalog with {NumItems} items", result);
}
await dbContext.SaveChangesAsync(cancellationToken);
}
}
}

View File

@ -0,0 +1,810 @@
[
{
"Id": 1,
"Type": "Footwear",
"Brand": "Daybird",
"Name": "Wanderer Black Hiking Boots",
"Description": "Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long.",
"Price": 109.99
},
{
"Id": 2,
"Type": "Climbing",
"Brand": "Gravitator",
"Name": "Summit Pro Harness",
"Description": "Conquer new heights with the Summit Pro Harness by Gravitator. This lightweight and durable climbing harness features adjustable leg loops and waist belt for a customized fit. With its vibrant blue color, you'll look stylish while maneuvering difficult routes. Safety is a top priority with a reinforced tie-in point and strong webbing loops.",
"Price": 89.99
},
{
"Id": 3,
"Type": "Boarding",
"Brand": "WildRunner",
"Name": "Alpine Fusion Goggles",
"Description": "Enhance your skiing experience with the Alpine Fusion Goggles from WildRunner. These goggles offer full UV protection and anti-fog lenses to keep your vision clear on the slopes. With their stylish silver frame and orange lenses, you'll stand out from the crowd. Adjustable straps ensure a secure fit, while the soft foam padding provides comfort all day long.",
"Price": 79.99
},
{
"Id": 4,
"Type": "Bags",
"Brand": "Quester",
"Name": "Expedition Backpack",
"Description": "The Expedition Backpack by Quester is a must-have for every outdoor enthusiast. With its spacious interior and multiple pockets, you can easily carry all your gear and essentials. Made with durable nylon fabric, this backpack is built to withstand the toughest conditions. The orange accents add a touch of style to this functional backpack.",
"Price": 129.99
},
{
"Id": 5,
"Type": "Boarding",
"Brand": "B&R",
"Name": "Blizzard Rider Snowboard",
"Description": "Get ready to ride the slopes with the Blizzard Rider Snowboard by B&R. This versatile snowboard is perfect for riders of all levels with its medium flex and twin shape. Its black and blue color scheme gives it a sleek and cool look. Whether you're carving turns or hitting the terrain park, this snowboard will help you shred with confidence.",
"Price": 299.99
},
{
"Id": 6,
"Type": "Trekking",
"Brand": "Raptor Elite",
"Name": "Carbon Fiber Trekking Poles",
"Description": "The Carbon Fiber Trekking Poles by Raptor Elite are the ultimate companion for your hiking adventures. Designed with lightweight carbon fiber shafts, these poles provide excellent support and durability. The comfortable and adjustable cork grips ensure a secure hold, while the blue accents add a stylish touch. Compact and collapsible, these trekking poles are easy to transport and store.",
"Price": 69.99
},
{
"Id": 7,
"Type": "Bags",
"Brand": "Solstix",
"Name": "Explorer 45L Backpack",
"Description": "The Explorer 45L Backpack by Solstix is perfect for your next outdoor expedition. Made with waterproof and tear-resistant materials, this backpack can withstand even the harshest weather conditions. With its spacious main compartment and multiple pockets, you can easily organize your gear. The green and black color scheme adds a rugged and adventurous edge.",
"Price": 149.99
},
{
"Id": 8,
"Type": "Jackets",
"Brand": "Grolltex",
"Name": "Frostbite Insulated Jacket",
"Description": "Stay warm and stylish with the Frostbite Insulated Jacket by Grolltex. Featuring a water-resistant outer shell and lightweight insulation, this jacket is perfect for cold weather adventures. The black and gray color combination and Grolltex logo add a touch of sophistication. With its adjustable hood and multiple pockets, this jacket offers both style and functionality.",
"Price": 179.99
},
{
"Id": 9,
"Type": "Navigation",
"Brand": "AirStrider",
"Name": "VenturePro GPS Watch",
"Description": "Navigate with confidence using the VenturePro GPS Watch by AirStrider. This rugged and durable watch features a built-in GPS, altimeter, and compass, allowing you to track your progress and find your way in any terrain. With its sleek black design and easy-to-read display, this watch is both stylish and practical. The VenturePro GPS Watch is a must-have for every adventurer.",
"Price": 199.99
},
{
"Id": 10,
"Type": "Cycling",
"Brand": "Green Equipment",
"Name": "Trailblazer Bike Helmet",
"Description": "Stay safe on your cycling adventures with the Trailblazer Bike Helmet by Green Equipment. This lightweight and durable helmet features an adjustable fit system and ventilation for added comfort. With its vibrant green color and sleek design, you'll stand out on the road. The Trailblazer Bike Helmet is perfect for all types of cycling, from mountain biking to road cycling.",
"Price": 59.99
},
{
"Id": 11,
"Type": "Climbing",
"Brand": "WildRunner",
"Name": "Vertical Journey Climbing Shoes",
"Description": "The Vertical Journey Climbing Shoes from WildRunner in sleek black are the perfect companion for any climbing enthusiast. With an aggressive down-turned toe, sticky rubber outsole, and reinforced heel cup for added support, these shoes offer ultimate performance on even the most challenging routes.",
"Price": 129.99
},
{
"Id": 12,
"Type": "Boarding",
"Brand": "Zephyr",
"Name": "Powder Pro Snowboard",
"Description": "The Powder Pro Snowboard by Zephyr is designed for the ultimate ride through deep snow. Its floating camber allows for effortless turns and smooth maneuverability, while the lightweight carbon fiber construction ensures maximum control at high speeds. This board, available in vibrant turquoise, is a must-have for any backcountry shredder.",
"Price": 399.00
},
{
"Id": 13,
"Type": "Bags",
"Brand": "Daybird",
"Name": "Trailblaze hiking backpack",
"Description": "The Daybird Trailblaze backpack in forest green is a reliable and spacious bag for all your outdoor adventures. With a 40-liter capacity and durable ripstop fabric, this backpack provides ample storage and protection for your gear. Its ergonomic design and adjustable straps ensure a comfortable fit no matter the length of the hike.",
"Price": 89.99
},
{
"Id": 14,
"Type": "Bags",
"Brand": "Gravitator",
"Name": "Stellar Duffle Bag",
"Description": "The Stellar Duffle Bag from Gravitator is perfect for weekend getaways or short trips. Made from waterproof nylon and available in sleek black, it features multiple internal pockets and a separate shoe compartment to keep your belongings organized. With its adjustable shoulder strap and reinforced handles, this bag is as functional as it is stylish.",
"Price": 59.99
},
{
"Id": 15,
"Type": "Jackets",
"Brand": "Raptor Elite",
"Name": "Summit Pro Insulated Jacket",
"Description": "The Summit Pro Insulated Jacket by Raptor Elite is designed to keep you warm and dry in extreme conditions. With its waterproof and breathable construction, heat-sealed seams, and insulation made from recycled materials, this jacket is both eco-friendly and high-performance. Available in vibrant red, it also features a removable hood and plenty of storage pockets.",
"Price": 249.99
},
{
"Id": 16,
"Type": "Boarding",
"Brand": "Solstix",
"Name": "Expedition 2022 Goggles",
"Description": "Solstix Expedition 2022 Goggles provide clear vision and optimal protection on the slopes. With an anti-fog lens, UV protection, and a comfortable foam lining, these goggles ensure a great fit and unrestricted vision even in challenging conditions. The matte black frame gives them a sleek and modern look.",
"Price": 89.00
},
{
"Id": 17,
"Type": "Climbing",
"Brand": "Legend",
"Name": "Apex Climbing Harness",
"Description": "The Apex Climbing Harness by Legend is a lightweight and durable harness designed for maximum comfort and safety. With adjustable leg loops, a contoured waistbelt, and a secure buckle system, this harness provides a secure fit for all-day climbing sessions. Available in bold orange, it also features gear loops for easy access to your equipment.",
"Price": 89.99
},
{
"Id": 18,
"Type": "Climbing",
"Brand": "Grolltex",
"Name": "Alpine Tech Crampons",
"Description": "The Alpine Tech Crampons by Grolltex are essential for icy and challenging mountain terrains. Made from strong and lightweight stainless steel, these crampons provide excellent traction and stability. Their simple adjustment system allows for easy fitting and quick attachment to most hiking boots. Available in silver, they are suitable for both beginners and experienced mountaineers.",
"Price": 149.00
},
{
"Id": 19,
"Type": "Footwear",
"Brand": "Green Equipment",
"Name": "EcoTrail Running Shoes",
"Description": "Experience the great outdoors while reducing your carbon footprint with the Green Equipment EcoTrail Running Shoes. Made from recycled materials, these shoes offer a lightweight, breathable, and flexible design in an earthy green color. With their durable Vibram outsole and cushioned midsole, they provide optimal comfort and grip on any trail.",
"Price": 119.99
},
{
"Id": 20,
"Type": "Navigation",
"Brand": "B&R",
"Name": "Explorer Biking Computer",
"Description": "The Explorer Biking Computer by B&R is the ultimate accessory for cyclists seeking data and navigation assistance. With its intuitive touchscreen display and GPS capabilities, it allows you to track your route, monitor performance metrics, and receive turn-by-turn directions. Its sleek black design and waterproof construction make it a reliable companion on all your cycling adventures.",
"Price": 199.99
},
{
"Id": 21,
"Type": "Footwear",
"Brand": "Legend",
"Name": "Trailblazer Black Hiking Shoes",
"Description": "The Legend Trailblazer is a versatile hiking shoe designed to provide unparalleled durability and comfort on any adventure. With its black color, these shoes offer a sleek and minimalist style. The shoes feature a waterproof GORE-TEX lining, Vibram rubber outsole for enhanced traction, and a reinforced toe cap for added protection. Conquer any trail with confidence in the Legend Trailblazer Black Hiking Shoes.",
"Price": 129.99
},
{
"Id": 22,
"Type": "Boarding",
"Brand": "Raptor Elite",
"Name": "Venture 2022 Snowboard",
"Description": "The Raptor Elite Venture 2022 Snowboard is a true all-mountain performer, perfect for riders of all levels. Its sleek design, combined with the vibrant blue color, makes it stand out on the slopes. The snowboard features a responsive camber profile, carbon fiber laminates for enhanced stability, and a sintered base for maximum speed. Take your snowboarding skills to new heights with the Raptor Elite Venture 2022 Snowboard.",
"Price": 499.00
},
{
"Id": 23,
"Type": "Climbing",
"Brand": "Zephyr",
"Name": "Summit Pro Climbing Harness",
"Description": "The Zephyr Summit Pro Climbing Harness is designed for professional climbers who demand the utmost in reliability and performance. Available in a striking orange color, this harness features 30kN rated webbing, speed-adjust buckles, and multiple gear loops for easy organization. With its lightweight design, the Summit Pro Harness offers unmatched comfort and freedom of movement. Reach new heights of confidence with the Zephyr Summit Pro Climbing Harness.",
"Price": 189.99
},
{
"Id": 24,
"Type": "Bags",
"Brand": "WildRunner",
"Name": "Ridgevent Stealth Hiking Backpack",
"Description": "The WildRunner Ridgevent Stealth Hiking Backpack is the ultimate companion for your outdoor adventures. With its stealthy red color, this backpack combines style with functionality. Made from durable nylon and featuring multiple compartments, this backpack offers ample storage space for all your essentials. Whether you're venturing into the mountains or exploring hidden trails, the Ridgevent Stealth Hiking Backpack has got you covered.",
"Price": 69.99
},
{
"Id": 25,
"Type": "Cycling",
"Brand": "Daybird",
"Name": "Stealth Lite Bike Helmet",
"Description": "The Daybird Stealth Lite Bike Helmet is designed for cyclists who value both safety and style. With its sleek matte silver color, this helmet will make you stand out on the road. The helmet features a lightweight in-mold construction, adjustable retention system, and multiple ventilation channels for optimal airflow. Stay protected and look cool with the Daybird Stealth Lite Bike Helmet.",
"Price": 89.99
},
{
"Id": 26,
"Type": "Climbing",
"Brand": "Gravitator",
"Name": "Gravity Beam Climbing Rope",
"Description": "The Gravitator Gravity Beam Climbing Rope is the perfect companion for vertical endeavors. This high-quality climbing rope features a kernmantle construction, providing excellent strength and durability. With its vibrant yellow color, the Gravity Beam Rope is highly visible and easy to work with. Whether you're tackling steep rock faces or conquering frozen waterfalls, trust the Gravitator Gravity Beam Climbing Rope to get you to the top.",
"Price": 179.99
},
{
"Id": 27,
"Type": "Bags",
"Brand": "Green Equipment",
"Name": "EcoLodge 45L Travel Backpack",
"Description": "The Green Equipment EcoLodge 45L Travel Backpack is a sustainable and versatile option for all your travel needs. With its earth-inspired green color, this backpack is not only stylish but also environmentally friendly. Made from recycled materials, this backpack features multiple compartments, a padded laptop sleeve, and durable zippers. Explore the world with the Green Equipment EcoLodge 45L Travel Backpack.",
"Price": 129.00
},
{
"Id": 28,
"Type": "Jackets",
"Brand": "Solstix",
"Name": "Alpine Peak Down Jacket",
"Description": "The Solstix Alpine Peak Down Jacket is crafted for extreme cold conditions. With its bold red color and sleek design, this jacket combines style with functionality. Made with high-quality goose down insulation, the Alpine Peak Jacket provides exceptional warmth and comfort. The jacket features a removable hood, adjustable cuffs, and multiple zippered pockets for storage. Conquer the harshest weather with the Solstix Alpine Peak Down Jacket.",
"Price": 249.99
},
{
"Id": 29,
"Type": "Navigation",
"Brand": "B&R",
"Name": "Pulse Recon Tactical GPS Watch",
"Description": "The B&R Pulse Recon Tactical GPS Watch is a must-have for outdoor enthusiasts. This reliable navigation tool features a built-in GPS, altimeter, compass, and multiple sports modes. With its military green color and durable construction, the Pulse Recon watch is built to withstand your toughest adventures. Stay on track and keep track of your performance with the B&R Pulse Recon Tactical GPS Watch.",
"Price": 169.00
},
{
"Id": 30,
"Type": "Boarding",
"Brand": "Gravitator",
"Name": "Zero Gravity Ski Goggles",
"Description": "The Gravitator Zero Gravity Ski Goggles combine style, performance, and comfort for the ultimate ski experience. With their sleek white frame and red mirrored lenses, these goggles offer a futuristic look on the slopes. The goggles feature an anti-fog coating, 100% UV protection, and an adjustable strap for a secure fit. Enhance your vision and carve your way down the slopes with the Gravitator Zero Gravity Ski Goggles.",
"Price": 79.99
},
{
"Id": 31,
"Type": "Climbing",
"Brand": "Legend",
"Name": "Guardian Blue Chalk Bag",
"Description": "Stay focused on your route with the Guardian Blue Chalk Bag by Legend. This durable bag features a spacious compartment for your chalk, a drawstring closure, and a waist belt for easy access while climbing. The vibrant blue color adds a stylish touch to your climbing gear.",
"Price": 21.99
},
{
"Id": 32,
"Type": "Boarding",
"Brand": "Gravitator",
"Name": "Cosmic Purple Snowboard",
"Description": "Conquer the slopes with the Cosmic Purple Snowboard by Gravitator. This freestyle board delivers a perfect balance of control and maneuverability. Its bright purple design is complemented by the Gravitator emblem, sure to turn heads on the mountain.",
"Price": 419.99
},
{
"Id": 33,
"Type": "Footwear",
"Brand": "WildRunner",
"Name": "Venture Grey Trail Shoes",
"Description": "Hit the trails in style and comfort with the Venture Grey Trail Shoes by WildRunner. Constructed with breathable mesh and a rugged outsole, these shoes provide excellent traction and long-lasting durability. The versatile grey color makes them suitable for any adventure.",
"Price": 79.99
},
{
"Id": 34,
"Type": "Cycling",
"Brand": "AirStrider",
"Name": "Velocity Red Bike Helmet",
"Description": "Protect yourself while cycling in style with the Velocity Red Bike Helmet by AirStrider. This lightweight helmet features a streamlined design, adjustable straps, and ventilation channels for optimal airflow. Stay safe on the road or trails with this vibrant red helmet.",
"Price": 54.99
},
{
"Id": 35,
"Type": "Trekking",
"Brand": "Raptor Elite",
"Name": "Carbon Fiber Trekking Poles",
"Description": "Hike with confidence using the Raptor Elite Carbon Fiber Trekking Poles. These lightweight and durable poles provide stability on various terrains and reduce strain on your joints. With an ergonomic grip and adjustable length, these poles are a must-have for your outdoor adventures.",
"Price": 99.00
},
{
"Id": 36,
"Type": "Bags",
"Brand": "B&R",
"Name": "Excursion 20L Daypack",
"Description": "The Excursion 20L Daypack by B&R is the perfect companion for your hiking or camping trips. Made from durable waterproof nylon, this spacious pack features multiple pockets, adjustable straps, and a padded back for enhanced comfort. The sleek design and versatile white color make it a stylish choice.",
"Price": 64.99
},
{
"Id": 37,
"Type": "Jackets",
"Brand": "Zephyr",
"Name": "Stormbreaker Waterproof Jacket",
"Description": "Take on any weather with the Stormbreaker Waterproof Jacket by Zephyr. This jacket offers superior protection with its fully waterproof and windproof design. The bold red color, coupled with the Zephyr logo, adds a stylish touch to your outdoor look. Stay dry and comfortable during your adventures.",
"Price": 139.99
},
{
"Id": 38,
"Type": "Navigation",
"Brand": "Solstix",
"Name": "Pathfinder Portable GPS",
"Description": "Never lose your way with the Pathfinder Portable GPS by Solstix. This compact and reliable navigation device features a color display, preloaded maps, and advanced tracking capabilities. With its intuitive interface and long battery life, you can explore confidently wherever you go.",
"Price": 199.00
},
{
"Id": 39,
"Type": "Boarding",
"Brand": "Daybird",
"Name": "Midnight Blue Goggles",
"Description": "Enhance your snowboarding experience with the Midnight Blue Goggles by Daybird. These goggles offer a wide field of vision, anti-fog coating, and UV protection to keep your eyes protected on the slopes. The sleek design and blue tinted lens add a touch of style to your riding gear.",
"Price": 89.99
},
{
"Id": 40,
"Type": "Footwear",
"Brand": "Green Equipment",
"Name": "EcoTrek Trail Running Shoes",
"Description": "Hit the trails with the EcoTrek Trail Running Shoes by Green Equipment. Designed with eco-friendly materials, these shoes feature a comfortable fit, responsive cushioning, and a durable outsole for optimal grip on rugged terrains. The forest green color is inspired by nature and adds a refreshing touch to your outdoor look.",
"Price": 99.99
},
{
"Id": 41,
"Type": "Footwear",
"Brand": "WildRunner",
"Name": "Trekker Clear Hiking Shoes",
"Description": "The Trekker Clear Hiking Shoes from WildRunner are designed for the adventurous hiker who seeks both comfort and durability. The transparent shoes feature a waterproof and breathable upper fabric, a rugged carbon-infused sole for excellent traction, and a shock-absorbing midsole for enhanced comfort on long hikes.",
"Price": 84.99
},
{
"Id": 42,
"Type": "Boarding",
"Brand": "Gravitator",
"Name": "Gravity 5000 All-Mountain Skis",
"Description": "Take on any slope confidently with the Gravity 5000 All-Mountain Skis by Gravitator. These skis feature a versatile design that excels in all conditions, from powder to hardpack. They are equipped with a lightweight wood core, carbon inserts for responsiveness, and ABS sidewalls for added durability. These skis come in a striking blue color.",
"Price": 699.00
},
{
"Id": 43,
"Type": "Boarding",
"Brand": "Legend",
"Name": "Glacier Frost Snowboard",
"Description": "Tame the snow-covered peaks with the Glacier Frost Snowboard from Legend. This high-performance board is constructed with a hybrid camber profile for excellent edge control and superior maneuverability. The board is built with a carbon fiber composite core for lightweight strength and optimal flex, enabling you to take your snowboarding skills to new heights. Available in a cool white color with vibrant frost graphic.",
"Price": 419.99
},
{
"Id": 44,
"Type": "Climbing",
"Brand": "Raptor Elite",
"Name": "Summit Pro Climbing Harness",
"Description": "Conquer the highest peaks with the Summit Pro Climbing Harness by Raptor Elite. This harness features a lightweight and breathable construction, complete with adjustable leg loops for a personalized fit. It has reinforced tie-in points for maximum safety and durability. The vivid green color adds a touch of style to your climbing gear.",
"Price": 109.99
},
{
"Id": 45,
"Type": "Trekking",
"Brand": "Solstix",
"Name": "Elemental 3-Season Tent",
"Description": "Experience the great outdoors with the Elemental 3-Season Tent by Solstix. This lightweight and compact tent is perfect for backpacking adventures. It offers ample space for two people and features a durable waterproof fabric, sturdy aluminum poles, and ventilation panels for optimal airflow. The vibrant green color adds a touch of visibility to your camping setup.",
"Price": 189.99
},
{
"Id": 46,
"Type": "Cycling",
"Brand": "B&R",
"Name": "Zenith Cycling Jersey",
"Description": "Get ready to hit the road with the Zenith Cycling Jersey by B&R. This high-performance jersey is made from moisture-wicking fabric to keep you cool and dry during intense rides. It features a full-length zipper, three rear pockets for storage, and reflective accents for increased visibility in low-light conditions. Available in a vibrant red color.",
"Price": 64.99
},
{
"Id": 47,
"Type": "Climbing",
"Brand": "Grolltex",
"Name": "Edge Pro Ice Axe",
"Description": "Take your ice climbing adventures to the next level with the Edge Pro Ice Axe from Grolltex. This axe features a lightweight aluminum shaft, a durable stainless steel pick, and a comfortable hand grip for maximum control. Perfect for tackling steep ice walls and mixed alpine terrain. The sleek orange color adds a touch of sophistication to your gear.",
"Price": 129.00
},
{
"Id": 48,
"Type": "Bags",
"Brand": "Zephyr",
"Name": "Trailblazer 45L Backpack",
"Description": "Take everything you need for your next adventure with the Trailblazer 45L Backpack by Zephyr. This spacious backpack features multiple compartments for easy organization, adjustable shoulder straps and hip belt for a customized fit, and durable waterproof construction for ultimate protection against the elements. The classic yellow color is timeless and versatile.",
"Price": 124.99
},
{
"Id": 49,
"Type": "Jackets",
"Brand": "Daybird",
"Name": "Arctic Shield Insulated Jacket",
"Description": "Stay warm and stylish in the Arctic Shield Insulated Jacket by Daybird. This jacket features a water-resistant outer shell, insulated fill for exceptional warmth, and a detachable hood for added versatility. The sleek pink color is perfect for any outdoor occasion.",
"Price": 169.99
},
{
"Id": 50,
"Type": "Navigation",
"Brand": "AirStrider",
"Name": "Astro GPS Navigator",
"Description": "Never get lost on your outdoor adventures with the Astro GPS Navigator by AirStrider. This compact and rugged device comes loaded with topographic maps, GPS tracking, waypoint storage, and a long-lasting battery. It is equipped with a high-resolution color display for easy navigation in any lighting condition. Available in a sleek gray color.",
"Price": 249.99
},
{
"Id": 51,
"Type": "Climbing",
"Brand": "Grolltex",
"Name": "SummitStone Chalk Bag",
"Description": "The SummitStone Chalk Bag in forest green is a must-have for climbers seeking adventure. Keep your hands dry and have easy access to chalk with this durable and compact bag. It features a drawstring closure, adjustable waist strap, and a Loop-Slider buckle for easy attachment to harnesses.",
"Price": 29.99
},
{
"Id": 52,
"Type": "Bags",
"Brand": "Legend",
"Name": "TrailHug 50L Backpack",
"Description": "The TrailHug 50L Backpack in navy blue is a perfect companion for all your hiking adventures. Made from lightweight and water-resistant nylon, this backpack features adjustable padded straps, multiple compartments for organized storage, and a breathable back panel for added comfort. It also comes with a handy built-in rain cover for unexpected showers.",
"Price": 129.99
},
{
"Id": 53,
"Type": "Boarding",
"Brand": "Daybird",
"Name": "Raven Swift Snowboard",
"Description": "The Raven Swift Snowboard is ready to take you on thrilling rides down the slopes. With its striking white design and black logo, this all-mountain board is perfect for riders of all skill levels. It features a camber profile for stability and pop, and a medium flex for smooth turns and responsive control. Get ready to fly and carve like never before!",
"Price": 349.00
},
{
"Id": 54,
"Type": "Trekking",
"Brand": "Gravitator",
"Name": "Nebula Pro Headlamp",
"Description": "Illuminate your outdoor adventures with the Nebula Pro Headlamp. Its sleek design and water-resistant construction in neon color make it perfect for night hikes, camping trips, and emergencies. With 500 lumens of bright white light, adjustable brightness modes, and a rechargeable battery, this headlamp will light up the darkness and keep you safe.",
"Price": 59.99
},
{
"Id": 55,
"Type": "Jackets",
"Brand": "Solstix",
"Name": "Vigor 2.0 Insulated Jacket",
"Description": "Stay warm and stylish on the slopes with the Vigor 2.0 Insulated Jacket in vibrant red. This waterproof and breathable jacket is made with a 2-layer technical shell and features a detachable hood, adjustable cuffs, and multiple pockets for storage. With its modern design and ergonomic fit, it's the perfect outer layer for your winter adventures.",
"Price": 189.99
},
{
"Id": 56,
"Type": "Bags",
"Brand": "B&R",
"Name": "Traveler's Companion Duffel Bag",
"Description": "Whether you're embarking on a weekend getaway or a month-long expedition, the Traveler's Companion Duffel Bag has you covered. Made from durable waxed canvas in earthy brown, this versatile bag features multiple carry options, including shoulder straps and handles, and multiple compartments for organized packing. It even has a padded laptop sleeve, making it great for both adventure and work.",
"Price": 79.99
},
{
"Id": 57,
"Type": "Footwear",
"Brand": "Zephyr",
"Name": "Ascend XT Trail Running Shoes",
"Description": "Take on any trail with confidence in the Ascend XT Trail Running Shoes in charcoal gray. These lightweight yet rugged shoes offer excellent grip and support, thanks to their durable rubber outsole and advanced cushioning technology. The breathable mesh upper keeps your feet cool when the adventure heats up. It's time to push your limits and conquer the great outdoors.",
"Price": 109.99
},
{
"Id": 58,
"Type": "Cycling",
"Brand": "Raptor Elite",
"Name": "VelociX 2000 Bike Helmet",
"Description": "Protect your head in style with the VelociX 2000 Bike Helmet in glossy black. This aerodynamic helmet features an adjustable fit system, detachable visor, and 14 ventilation channels to keep you cool during intense rides. With its sleek design and lightweight construction, it's the perfect choice for road cycling, mountain biking, and everything in between.",
"Price": 79.99
},
{
"Id": 59,
"Type": "Navigation",
"Brand": "Quester",
"Name": "TrailSeeker GPS Watch",
"Description": "Stay on track and explore new trails with the TrailSeeker GPS Watch. With its durable design in stealth black, this watch is packed with features like GPS navigation, heart rate monitoring, and activity tracking. It also offers a long battery life, so you can keep going without worrying about recharging. The TrailSeeker is the ultimate companion for outdoor enthusiasts who love to explore.",
"Price": 149.99
},
{
"Id": 60,
"Type": "Boarding",
"Brand": "WildRunner",
"Name": "SummitRider Snowboard Boots",
"Description": "Conquer the mountains in style with the SummitRider Snowboard Boots in matte black. These high-performance boots combine comfort, durability, and response to enhance your riding experience. Featuring a heat-moldable liner, dual-zone lacing system, and impact-resistant outsole, they provide a precise and snug fit, ensuring maximum control on any terrain. Get ready to take on the slopes like a pro!",
"Price": 249.00
},
{
"Id": 61,
"Type": "Footwear",
"Brand": "WildRunner",
"Name": "Trailblaze Steel-Blue Hiking Shoes",
"Description": "Explore the great outdoors with the Trailblaze Steel-Blue Hiking Shoes by WildRunner. These rugged and durable shoes feature a steel-blue color, a waterproof membrane, and a high-traction rubber outsole for superior grip on any terrain. The breathable upper keeps your feet cool and comfortable, while the reinforced toe cap adds extra protection. Perfect for hiking, camping, and other outdoor adventures.",
"Price": 129.99
},
{
"Id": 62,
"Type": "Boarding",
"Brand": "Daybird",
"Name": "Shadow Black Snowboard",
"Description": "Conquer the slopes with the Daybird Shadow Black Snowboard. This sleek and stylish snowboard features a black colorway, a camber profile for maximum stability, and a medium-flex rating for responsive turns and tricks. Its lightweight construction ensures easy maneuverability, while the sintered base provides excellent speed and durability. Whether you're shredding on groomed runs or exploring the backcountry, this snowboard is designed to deliver peak performance.",
"Price": 379.00
},
{
"Id": 63,
"Type": "Climbing",
"Brand": "Raptor Elite",
"Name": "Razor Climbing Harness",
"Description": "Reach new heights with the Raptor Elite Razor Climbing Harness. This lightweight and breathable harness is designed for maximum comfort and performance. With its adjustable waist and leg loops, it offers a secure and customized fit. The razor-shaped webbing adds a stylish touch to the blue color of the harness. Featuring durable construction and reinforced tie-in points, this harness is a must-have for climbers of all levels.",
"Price": 94.99
},
{
"Id": 64,
"Type": "Bags",
"Brand": "Green Equipment",
"Name": "EcoVenture Olive Green Backpack",
"Description": "Embark on your next adventure with the Green Equipment EcoVenture Olive Green Backpack. Made from recycled materials, this sustainable backpack is as eco-friendly as it is functional. It features a spacious main compartment, multiple pockets for organizing your gear, and adjustable padded shoulder straps for comfortable carrying. The olive green color adds a touch of nature to your outdoor excursions.",
"Price": 69.99
},
{
"Id": 65,
"Type": "Cycling",
"Brand": "Solstix",
"Name": "Sprint PRO Carbon Cycling Helmet",
"Description": "Stay safe while cycling with the Solstix Sprint PRO Carbon Cycling Helmet. This high-performance helmet is crafted from carbon fiber for optimal impact protection and durability. It features an aerodynamic design, adjustable fit system, and ventilation channels to keep you cool on long rides. The rainbow color with the Solstix emblem adds a touch of style to your cycling adventures.",
"Price": 179.99
},
{
"Id": 66,
"Type": "Navigation",
"Brand": "B&R",
"Name": "Compass Pro A-320 Professional Compass",
"Description": "Navigate with precision using the B&R Compass Pro A-320 Professional Compass. Designed for outdoor enthusiasts and professionals alike, this compass features a liquid-filled housing for accurate readings, a rotating bezel for easy navigation, and a lanyard for convenient carrying. The gunmetal color with white markings ensures clarity and visibility even in low-light conditions. Get ready to explore the wilderness with confidence.",
"Price": 59.99
},
{
"Id": 67,
"Type": "Bags",
"Brand": "Quester",
"Name": "Venture 2.0 40L Waterproof Duffel Bag",
"Description": "Pack your gear in the Quester Venture 2.0 40L Waterproof Duffel Bag. This versatile duffel bag is made from waterproof nylon material and features taped seams to keep your belongings safe from the elements. It offers a spacious main compartment, external zippered pockets, and adjustable shoulder straps for easy carrying. The vibrant orange color adds a pop of excitement to your outdoor adventures.",
"Price": 79.99
},
{
"Id": 68,
"Type": "Jackets",
"Brand": "Grolltex",
"Name": "Mens Horizon 80s Softshell Jacket",
"Description": "Stay protected from the elements in the Grolltex Mens Horizon 80s Softshell Jacket. Made from a water-resistant and breathable fabric in retro 1980s style, this jacket keeps you dry and comfortable in any weather. It features multiple colors, a detachable hood, adjustable cuffs, and multiple pockets for storing your essentials. Whether you're hiking, skiing, or exploring the city, this jacket combines style and functionality.",
"Price": 169.99
},
{
"Id": 69,
"Type": "Navigation",
"Brand": "Gravitator",
"Name": "Expedition 200 GPS Navigator",
"Description": "Navigate with confidence using the Gravitator Expedition 200 GPS Navigator. This rugged and reliable navigator features a built-in GPS antenna for accurate positioning, preloaded maps, and a user-friendly interface with intuitive controls. The black color with the Gravitator logo complements the sleek design. With its long battery life and durable construction, this navigator is your ultimate outdoor companion.",
"Price": 299.00
},
{
"Id": 70,
"Type": "Trekking",
"Brand": "WildRunner",
"Name": "GripTrek Hiking Poles",
"Description": "The GripTrek hiking poles by WildRunner are a must-have for adventurers. With their durable aluminum construction and anti-slip handles, these poles provide stability and support on any terrain. Available in sleek yellow, these hiking poles are perfect for tackling steep inclines and rough trails.",
"Price": 79.99
},
{
"Id": 71,
"Type": "Footwear",
"Brand": "Daybird",
"Name": "Explorer Frost Boots",
"Description": "The Explorer Frost Boots by Daybird are the perfect companion for cold-weather adventures. These premium boots are designed with a waterproof and insulated shell, keeping your feet warm and protected in icy conditions. The sleek black design with blue accents adds a touch of style to your outdoor gear.",
"Price": 149.99
},
{
"Id": 72,
"Type": "Boarding",
"Brand": "Gravitator",
"Name": "GravityZone All-Mountain Skis",
"Description": "Take your skiing to new heights with the GravityZone All-Mountain Skis by Gravitator. These high-performance skis are designed for precision and control on all types of terrain. The sleek metallic blue design will make you stand out on the slopes while the carbon fiber construction ensures lightweight durability.",
"Price": 699.00
},
{
"Id": 73,
"Type": "Boarding",
"Brand": "WildRunner",
"Name": "Omni-Snow Dual Snowboard",
"Description": "Unleash your snowboarding skills with the Omni-Snow Dual Snowboard by WildRunner. This innovative design combines the maneuverability of a skateboard with the speed and stability of a snowboard. The vibrant red and black color scheme adds a dash of excitement to your snowboarding adventures.",
"Price": 289.99
},
{
"Id": 74,
"Type": "Climbing",
"Brand": "Raptor Elite",
"Name": "Apex Climbing Harness",
"Description": "Conquer the heights with the Apex Climbing Harness by Raptor Elite. This harness is constructed with high-strength nylon webbing and features adjustable leg loops for a secure and comfortable fit. The sleek white design with a vibrant orange emblem ensures you'll look good while tackling challenging routes.",
"Price": 99.99
},
{
"Id": 75,
"Type": "Footwear",
"Brand": "AirStrider",
"Name": "TrailTracker Hiking Shoes",
"Description": "The TrailTracker Hiking Shoes by AirStrider are built to handle any terrain. These lightweight and breathable shoes feature a rugged rubber sole for excellent traction and stability. The cool gray color with green accents adds a touch of style to your hiking ensemble.",
"Price": 89.99
},
{
"Id": 76,
"Type": "Cycling",
"Brand": "B&R",
"Name": "Fusion Carbon Cycling Helmet",
"Description": "Protect yourself on two wheels with the Fusion Carbon Cycling Helmet by B&R. This helmet is made from lightweight carbon fiber and features an aerodynamic design for maximum speed. The colorful finish with a bold blue stripe will make you stand out on the road.",
"Price": 159.00
},
{
"Id": 77,
"Type": "Trekking",
"Brand": "XE",
"Name": "Survivor 2-Person Tent",
"Description": "Gear up for your next adventure with the Survivor 2-Person Tent by XE. This rugged tent is made from durable ripstop nylon and features a waterproof coating to keep you dry in any weather. The vibrant orange color ensures high visibility in the wild.",
"Price": 249.99
},
{
"Id": 78,
"Type": "Bags",
"Brand": "Solstix",
"Name": "Basecamp Duffle Bag",
"Description": "The Basecamp Duffle Bag by Solstix is the ultimate adventure companion. This spacious bag is made from durable nylon and features multiple compartments for optimal organization. Its sleek red design with gray accents exudes both style and functionality.",
"Price": 129.00
},
{
"Id": 79,
"Type": "Jackets",
"Brand": "Legend",
"Name": "Everest Insulated Jacket",
"Description": "Conquer the cold with the Everest Insulated Jacket by Legend. This jacket combines warmth and style with its insulated design and sleek grey color. The water-resistant shell will keep you dry during unexpected showers while the cozy fleece lining adds extra comfort.",
"Price": 179.99
},
{
"Id": 80,
"Type": "Navigation",
"Brand": "XE",
"Name": "Pathfinder GPS Watch",
"Description": "Navigate with confidence using the Pathfinder GPS Watch by XE. This feature-packed watch includes GPS tracking, altimeter, barometer, and compass functions to guide you on your outdoor adventures. The sleek pink design with a vibrant green dial adds a sporty touch to your wrist.",
"Price": 199.00
},
{
"Id": 81,
"Type": "Footwear",
"Brand": "AirStrider",
"Name": "Trail Breeze Hiking Shoes",
"Description": "Experience the ultimate comfort and stability with the Trail Breeze hiking shoes by AirStrider. These lightweight shoes feature a breathable mesh upper in vivid blue, providing excellent airflow on hot summer hikes. The durable rubber outsole offers exceptional grip, ensuring you stay steady on any terrain.",
"Price": 109.99
},
{
"Id": 82,
"Type": "Boarding",
"Brand": "WildRunner",
"Name": "Maverick Pro Ski Goggles",
"Description": "Conquer the slopes in style with the Maverick Pro ski goggles by WildRunner. Designed for maximum performance, these goggles feature a sleek black frame and mirrored, polarized lenses that reduce glare, enhancing your visibility. With a comfortable foam lining and adjustable strap, these goggles provide a secure and snug fit.",
"Price": 139.99
},
{
"Id": 83,
"Type": "Boarding",
"Brand": "Zephyr",
"Name": "Blizzard Freestyle Snowboard",
"Description": "Unleash your freestyle skills on the slopes with the Blizzard snowboard from Zephyr. Featuring a vibrant orange and black design, this snowboard is perfect for riders who crave speed and control. Constructed with a durable bamboo core and carbon fiber reinforcement, the Blizzard offers an optimal blend of flexibility and responsiveness.",
"Price": 379.00
},
{
"Id": 84,
"Type": "Climbing",
"Brand": "Gravitator",
"Name": "Gravity Harness",
"Description": "Reach new heights with the Gravitator Gravity harness. With its innovative design and sturdy construction, this harness ensures your safety while climbing. The sleek black and red color scheme adds a touch of style. It offers maximum comfort and freedom of movement, giving you the confidence to conquer any climbing challenge.",
"Price": 89.99
},
{
"Id": 85,
"Type": "Trekking",
"Brand": "Daybird",
"Name": "LumenHead Headlamp",
"Description": "Illuminate your outdoor adventures with the Daybird LumenHead headlamp. This compact yet powerful headlamp features a bright LED light in a vibrant green housing. With multiple lighting modes, including a red light for preserving night vision, the LumenHead provides exceptional visibility in any conditions.",
"Price": 49.99
},
{
"Id": 86,
"Type": "Cycling",
"Brand": "Raptor Elite",
"Name": "ProVent Bike Helmet",
"Description": "Stay safe and stylish on your cycling adventures with the Raptor Elite ProVent bike helmet. This sleek helmet features a matte black finish with striking red accents. The ProVent technology ensures optimal airflow, keeping you cool and comfortable. With its adjustable fit system and removable visor, this helmet is perfect for both casual and professional riders.",
"Price": 79.99
},
{
"Id": 87,
"Type": "Trekking",
"Brand": "XE",
"Name": "Nomad 2-Person Tent",
"Description": "Embark on your next camping expedition with the XE Nomad 2-person tent. Designed for rugged outdoor conditions, this tent features a durable waterproof fabric in earthy tones. The spacious interior and easy-to-use setup make it ideal for comfortable camping. With its innovative ventilation system, you'll stay cool and dry throughout the night.",
"Price": 229.00
},
{
"Id": 88,
"Type": "Bags",
"Brand": "Green Equipment",
"Name": "Alpine AlpinePack Backpack",
"Description": "The AlpinePack backpack by Green Equipment is your ultimate companion for outdoor adventures. This versatile and durable backpack features a sleek navy design with reinforced straps. With a capacity of 45 liters, multiple compartments, and a hydration pack sleeve, it offers ample storage and organization. The ergonomic back panel ensures maximum comfort, even on the most challenging treks.",
"Price": 129.00
},
{
"Id": 89,
"Type": "Jackets",
"Brand": "Legend",
"Name": "Summit Pro Down Jacket",
"Description": "Defy the coldest temperatures with the Legend Summit Pro down jacket. This high-performance jacket is filled with premium down insulation for exceptional warmth. The sleek design in deep navy blue is complemented by contrasting silver zippers and emblems. Equipped with weather-resistant fabric and a removable hood, this jacket is your ultimate companion for extreme winter adventures.",
"Price": 239.99
},
{
"Id": 90,
"Type": "Navigation",
"Brand": "B&R",
"Name": "TrailTracker GPS Watch",
"Description": "Navigate the trails like a pro with the B&R TrailTracker GPS watch. This rugged and reliable watch features a built-in GPS that tracks your location, speed, and distance accurately. The sleek camo design with a vivid orange strap adds a sporty touch. With its long battery life and water-resistant construction, you can trust this watch to guide you through any outdoor expedition.",
"Price": 199.00
},
{
"Id": 91,
"Type": "Footwear",
"Brand": "WildRunner",
"Name": "Trailblazer Trail Running Shoes",
"Description": "Conquer any terrain in the Trailblazer Trail Running Shoes by WildRunner. These lightweight shoes come in vibrant blue and feature a rugged outsole for excellent traction, a breathable mesh upper for maximum comfort, and quick-drying materials to keep you dry on your adventures.",
"Price": 89.99
},
{
"Id": 92,
"Type": "Boarding",
"Brand": "Daybird",
"Name": "Blizzard Snowboard",
"Description": "Take on the slopes with the Daybird Blizzard Snowboard. This powerful board features a sleek design in icy white, with a durable wood core, a versatile medium flex, and a precision base that allows for smooth rides and easy turns. Strap on and carve your way to glory.",
"Price": 449.99
},
{
"Id": 93,
"Type": "Climbing",
"Brand": "Raptor Elite",
"Name": "Summit Climbing Harness",
"Description": "Conquer the highest peaks with the Raptor Elite Summit Climbing Harness. This durable and lightweight harness, available in bold red, provides maximum comfort and safety while scaling tricky routes. Its adjustable waistband and leg loops ensure a snug fit, while the gear loops provide easy access to your equipment.",
"Price": 109.99
},
{
"Id": 94,
"Type": "Bags",
"Brand": "Gravitator",
"Name": "Gravity Hiking Backpack",
"Description": "Embark on unforgettable hikes with the Gravitator Gravity Hiking Backpack. Available in tiger stripes, this backpack offers a spacious main compartment, multiple pockets, and a hydration system compatible design. The lightweight and durable construction ensures maximum comfort on the trails.",
"Price": 79.99
},
{
"Id": 95,
"Type": "Cycling",
"Brand": "AirStrider",
"Name": "AeroLite Cycling Helmet",
"Description": "Stay safe and stylish on your cycling adventures with the AirStrider AeroLite Cycling Helmet. This helmet, in a glossy grey, features a lightweight design, adjustable straps, and excellent ventilation to keep you cool. The aerodynamic shape reduces air resistance, enabling you to pick up speed with confidence.",
"Price": 129.99
},
{
"Id": 96,
"Type": "Trekking",
"Brand": "B&R",
"Name": "Explorer Camping Tent",
"Description": "Experience the great outdoors with the B&R Explorer Camping Tent. This spacious tent, available in forest green, comfortably fits up to six people with a separate sleeping area and a generous living space. Its sturdy construction and weather-resistant materials ensure durability and protection from the elements.",
"Price": 279.99
},
{
"Id": 97,
"Type": "Bags",
"Brand": "Quester",
"Name": "Gravity Waterproof Dry Bag",
"Description": "Keep your essentials dry and secure with the Quester Gravity Waterproof Dry Bag. This versatile bag, in vibrant orange, features a roll-top closure system, adjustable shoulder straps, and durable PVC-coated fabric to withstand water, sand, and dirt. Ideal for adventures on land or water.",
"Price": 49.99
},
{
"Id": 98,
"Type": "Jackets",
"Brand": "Legend",
"Name": "Element Outdoor Jacket",
"Description": "Gear up for any adventure with the Legend Element Outdoor Jacket. Available in charcoal gray, this jacket offers ultimate protection with its waterproof and windproof shell. The breathable fabric and adjustable cuffs ensure comfort, allowing you to explore in any weather condition.",
"Price": 179.99
},
{
"Id": 99,
"Type": "Navigation",
"Brand": "Solstix",
"Name": "Adventurer GPS Watch",
"Description": "Take navigation to the next level with the Solstix Adventurer GPS Watch. This sleek and durable watch, in midnight blue, features a built-in GPS, altimeter, and compass, allowing you to track your routes and monitor your progress. With multiple sport modes, it's the ideal companion for outdoor enthusiasts.",
"Price": 199.99
},
{
"Id": 100,
"Type": "Trekking",
"Brand": "Green Equipment",
"Name": "EcoLite Trekking Poles",
"Description": "Tackle challenging trails with the Green Equipment EcoLite Trekking Poles. These lightweight poles, in vibrant green, feature adjustable height, shock-absorbing capabilities, and ergonomic cork handles for a comfortable grip. Whether ascending or descending, these poles provide stability and support.",
"Price": 79.99
},
{
"Id": 101,
"Type": "Footwear",
"Brand": "Raptor Elite",
"Name": "Trek Xtreme Hiking Shoes",
"Description": "The Trek Xtreme hiking shoes by Raptor Elite are built to endure any trail. With their durable leather upper and rugged rubber sole, they offer excellent traction and protection. These shoes come in a timeless brown color that adds a touch of style to your outdoor adventures.",
"Price": 135.99
}
]

View File

@ -0,0 +1,33 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.ProductService.DistributedEvents.Events;
using HelloShop.ProductService.Entities.Products;
using HelloShop.ProductService.Infrastructure;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
namespace HelloShop.ProductService.DistributedEvents.EventHandling
{
public class OrderAwaitingValidationDistributedEventHandler(ProductServiceDbContext dbContext, IDistributedEventBus distributedEventBus, ILogger<OrderAwaitingValidationDistributedEventHandler> logger) : IDistributedEventHandler<OrderAwaitingValidationDistributedEvent>
{
public async Task HandleAsync(OrderAwaitingValidationDistributedEvent @event)
{
logger.LogInformation("Handling distributed event {EventId} {Event}", @event.Id, @event);
var confirmedOrderStockItems = new Dictionary<int, bool>();
foreach (var orderStockItem in @event.OrderStockItems)
{
var product = await dbContext.Set<Product>().FindAsync(orderStockItem.ProductId) ?? throw new Exception($"Product with id {orderStockItem.ProductId} not found");
var hasStock = product.AvailableStock >= orderStockItem.Units;
confirmedOrderStockItems.Add(product.Id, hasStock);
}
DistributedEvent confirmedEvent = confirmedOrderStockItems.All(c => c.Value) ? new OrderStockConfirmedDistributedEvent(@event.OrderId) : new OrderStockRejectedDistributedEvent(@event.OrderId);
await distributedEventBus.PublishAsync(confirmedEvent);
}
}
}

View File

@ -0,0 +1,11 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
namespace HelloShop.ProductService.DistributedEvents.Events
{
public record OrderAwaitingValidationDistributedEvent(int OrderId, IEnumerable<OrderStockItem> OrderStockItems) : DistributedEvent;
public record OrderStockItem(int ProductId, int Units);
}

View File

@ -0,0 +1,9 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
namespace HelloShop.ProductService.DistributedEvents.Events
{
public record OrderStockConfirmedDistributedEvent(int OrderId) : DistributedEvent;
}

View File

@ -0,0 +1,9 @@
// Copyright (c) HelloShop Corporation. All rights reserved.
// See the license file in the project root for more information.
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
namespace HelloShop.ProductService.DistributedEvents.Events
{
public record OrderStockRejectedDistributedEvent(int OrderId) : DistributedEvent;
}

View File

@ -9,7 +9,7 @@ namespace HelloShop.ProductService.Entities.Products
public required string Name { get; set; }
public string? Description { get; set; }
public required string Description { get; set; }
public decimal Price { get; set; }
@ -17,7 +17,9 @@ namespace HelloShop.ProductService.Entities.Products
public Brand Brand { get; set; } = default!;
public string? ImageUrl { get; set; }
public required string ImageUrl { get; set; }
public int AvailableStock { get; set; }
public DateTimeOffset CreationTime { get; init; } = TimeProvider.System.GetUtcNow();
}

View File

@ -7,12 +7,14 @@ namespace HelloShop.ProductService.Models.Products
{
public required string Name { get; init; }
public string? Description { get; init; }
public required string Description { get; init; }
public decimal Price { get; init; }
public int BrandId { get; init; }
public string? ImageUrl { get; init; }
public required string ImageUrl { get; init; }
public int AvailableStock { get; init; }
}
}

View File

@ -8,13 +8,15 @@ public class ProductDetailsResponse
public required string Name { get; init; }
public string? Description { get; init; }
public required string Description { get; init; }
public decimal Price { get; init; }
public required BrandDetailsResponse Brand { get; init; }
public string? ImageUrl { get; init; }
public required string ImageUrl { get; init; }
public int AvailableStock { get; init; }
public DateTimeOffset CreationTime { get; init; }
}

View File

@ -13,5 +13,7 @@ public class ProductListItem
public required string BrandName { get; set; }
public int AvailableStock { get; set; }
public DateTimeOffset CreationTime { get; init; }
}

View File

@ -9,12 +9,14 @@ namespace HelloShop.ProductService.Models.Products
public required string Name { get; init; }
public string? Description { get; init; }
public required string Description { get; init; }
public decimal Price { get; init; }
public int BrandId { get; init; }
public string? ImageUrl { get; init; }
public required string ImageUrl { get; init; }
public int AvailableStock { get; init; }
}
}

View File

@ -3,6 +3,8 @@
using HelloShop.ProductService.Constants;
using HelloShop.ProductService.Infrastructure;
using HelloShop.ServiceDefaults.DistributedEvents.Abstractions;
using HelloShop.ServiceDefaults.DistributedEvents.DaprBuildingBlocks;
using HelloShop.ServiceDefaults.Extensions;
using Microsoft.EntityFrameworkCore;
@ -26,6 +28,7 @@ builder.Services.AddOpenApi();
builder.Services.AddModelMapper().AddModelValidator();
builder.Services.AddLocalization().AddPermissionDefinitions();
builder.Services.AddAuthorization().AddRemotePermissionChecker().AddCustomAuthorization();
builder.AddDaprDistributedEventBus().AddSubscriptionFromAssembly();
// End addd extensions services to the container.
var app = builder.Build();
@ -43,6 +46,7 @@ app.UseDataSeedingProviders();
app.UseCustomLocalization();
app.UseOpenApi();
app.MapGroup("api/Permissions").MapPermissionDefinitions("Permissions");
app.MapDaprDistributedEventBus();
// End configure extensions request pipeline.
app.Run();

View File

@ -36,7 +36,12 @@ namespace HelloShop.ProductService.UnitTests
// Arrange
Mock<IProductService> mock = new();
mock.Setup(m => m.GetAllAsync(It.IsAny<CancellationToken>())).ReturnsAsync([new Product { Id = 1, Name = "Product 1", Price = 10 }, new Product { Id = 2, Name = "Product 2", Price = 20 }]);
var mockProducts = new List<Product> {
new() { Id = 1, Name = "Product 1", Description = "Product 1", ImageUrl = "1.jpg", Price = 10 },
new() { Id = 2, Name = "Product 2",Description="Product 2", ImageUrl="2.jpg", Price = 20 }
};
mock.Setup(m => m.GetAllAsync(It.IsAny<CancellationToken>())).ReturnsAsync(mockProducts);
// Act
IMapper mapper = new Mapper(new MapperConfiguration(cfg => cfg.CreateMap<Product, ProductListItem>()));

View File

@ -20,7 +20,7 @@ namespace HelloShop.ProductService.UnitTests
// Arrange
await using ProductServiceDbContext dbContext = new FakeDbContextFactory().CreateDbContext();
await dbContext.AddAsync(new Product { Id = 1, Name = "Product 1", Price = 10 });
await dbContext.AddAsync(new Product { Id = 1, Name = "Product 1", Price = 10, Description = "Product 1", ImageUrl = "1.jpg" });
await dbContext.SaveChangesAsync();
@ -36,10 +36,10 @@ namespace HelloShop.ProductService.UnitTests
}
[Theory]
[InlineData("Product 1", 10)]
[InlineData("Product 2", 20)]
[InlineData("Product 3", 30)]
public async Task PostProductReturnsProductDetailsResponse(string productName, decimal price)
[InlineData("Product 1", 10, "Product 1", 1, "1.jpg", 100)]
[InlineData("Product 2", 20, "Product 2", 2, "2.jpg", 200)]
[InlineData("Product 3", 30, "Product 3", 3, "3.jpg", 300)]
public async Task PostProductReturnsProductDetailsResponse(string productName, decimal price, string description, int brandId, string imageUrl, int availableStock)
{
// Arrange
await using ProductServiceDbContext dbContext = new FakeDbContextFactory().CreateDbContext();
@ -49,7 +49,8 @@ namespace HelloShop.ProductService.UnitTests
ProductsController productsController = new(dbContext, mapper);
// Act
ActionResult<ProductDetailsResponse> createdAtActionResult = await productsController.PostProduct(new ProductCreateRequest { Name = productName, Price = price });
ProductCreateRequest productCreateRequest = new() { Name = productName, Price = price, Description = description, BrandId = brandId, ImageUrl = imageUrl, AvailableStock = availableStock };
ActionResult<ProductDetailsResponse> createdAtActionResult = await productsController.PostProduct(productCreateRequest);
ProductDetailsResponse? result = (createdAtActionResult?.Result as CreatedAtActionResult)?.Value as ProductDetailsResponse;
//Assert