Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -438,13 +438,15 @@ services:
networks:
- cloud-native
smtp:
image: gessnerfl/fake-smtp-server
image: rnwood/smtp4dev
container_name: smtp
environment:
- SPRING_MAIL_USERNAME=cnadmin
- SPRING_MAIL_PASSWORD=somepassword
- ServerOptions__Port=5025
- ServerOptions__HostName=smtp
- ServerOptions__LockSettings=true
- ServerOptions__TlsMode=StartTls
ports:
- "5080:5080"
- "5080:80"
networks:
- cloud-native
cadvisor:
Expand Down
3 changes: 2 additions & 1 deletion src/AdminCli/CliCommands/Products/ProductCommandsModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ public static class ProductCommandsModule
public static IServiceCollection AddProductCommands(this IServiceCollection services) =>
services.AddSingleton<ProductsCommand>()
.AddSingleton<ListProductsCommand>()
.AddSingleton<DropPriceCommand>();
.AddSingleton<DropPriceCommand>()
.AddSingleton<SignUpForPriceDropCommand>();
}
1 change: 1 addition & 0 deletions src/AdminCli/CliCommands/Products/ProductsCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public void ConfigureCommand(CommandLineApplication config)
config.Description = "Allows you to list products or issue a price drop";
var context = new CommandConfigurationContext(config, ServiceProvider);
context.ConfigureCommand<ListProductsCommand>("list");
context.ConfigureCommand<SignUpForPriceDropCommand>("sign-up");
context.ConfigureCommand<DropPriceCommand>("drop-price");

config.OnExecute(() => config.ShowHelp());
Expand Down
78 changes: 78 additions & 0 deletions src/AdminCli/CliCommands/Products/SignUpForPriceDropCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using AdminCli.HttpAccess;
using McMaster.Extensions.CommandLineUtils;

namespace AdminCli.CliCommands.Products;

public sealed class SignUpForPriceDropCommand : ICliCommand
{
public SignUpForPriceDropCommand(IHttpService httpService) => HttpService = httpService;

private IHttpService HttpService { get; }

public void ConfigureCommand(CommandLineApplication config)
{
config.Description = "Sign up with your email address to get notified when a product's price drops.";
var emailOption =
config.Option("-e||--email <Email>",
"Your email address. We will write an email to this address once the price drops.",
CommandOptionType.SingleValue)
.IsRequired();

var productIdOption =
config.Option("-p|--product-id <ProductId>",
"The ID of the product that you want to watch the price on. Must be a GUID. Use the products list command to obtain the GUID of a product.",
CommandOptionType.SingleValue)
.IsRequired();

var priceOption =
config.Option("-t|--target-price <Price>",
"The target price. When the product's price is dropped to a value equal or below the target price, an email will be sent.",
CommandOptionType.SingleValue);

config.OnExecuteAsync(async cancellationToken =>
{
var email = emailOption.Value();
if (!ValidateEmail(email))
{
Console.WriteLine("You did not provide a valid email address");
return;
}

var productIdText = productIdOption.Value();
if (!Guid.TryParse(productIdText, out var parsedId))
{
Console.WriteLine($"Could not parse \"{productIdText}\" to a GUID.");
return;
}

var priceText = priceOption.Value();
if (!double.TryParse(priceText, CultureInfo.CurrentCulture, out var parsedTargetPrice))
{
Console.WriteLine($"Could not parse \"{priceText}\" to a decimal number.");
return;
}

var dto = new SignUpForPriceDropDto(email, parsedId, parsedTargetPrice);
await HttpService.PostAsync("/pricewatcher/register", dto, cancellationToken);
Console.WriteLine("You signed up successfully.");
});
}

private static bool ValidateEmail([NotNullWhen(true)] string? email)
{
// We simply check if the email address contain an @ which is not placed at the beginning or end.
if (email is null || email.Length < 3)
return false;

var indexOfAt = email.IndexOf('@');
return indexOfAt > 0 && indexOfAt != email.Length - 1;
}
}

// The properties of this record are read by the JSON serializer
// ReSharper disable NotAccessedPositionalProperty.Global
public sealed record SignUpForPriceDropDto(string Email, Guid ProductId, double Price);
// ReSharper restore NotAccessedPositionalProperty.Global
94 changes: 94 additions & 0 deletions src/PriceWatcherService/Entities/InMemoryProducts.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
namespace PriceWatcher.Entities;

// This class is copied from ProductService. Everything you change here should also be changed over there.
public static class InMemoryProducts
{
public static List<Product> Products { get; } =
new ()
{
new Product(
Guid.Parse("b3b749d1-fd02-4b47-8e3c-540555439db6"),
"Milk",
"Good milk",
new List<string> { "Food" },
0.99
),
new Product(
Guid.Parse("aaaaaaaa-fd02-4b47-8e3c-540555439db6"),
"Coffee",
"Delicious Coffee",
new List<string> { "Food" },
1.99
),
new Product(
Guid.Parse("08c64d77-4e3e-45f0-8455-078fca893049"),
"Coke",
"Tasty coke",
new List<string> { "Food" },
1.49
),
new Product(
Guid.Parse("f6877871-2a14-4f40-a61a-e1153592c0fb"),
"Beer",
"Good beer",
new List<string> { "Food" },
2.99
),
new Product(
Guid.Parse("9dfeb719-32e1-49a9-b55d-539f5b116dd6"),
"Bread",
"Delicious bread",
new List<string> { "Food" },
0.99
),
new Product(
Guid.Parse("1316ef5e-96b3-4976-adc4-ca97fd121078"),
"Sausage",
"Tasty sausage",
new List<string> { "Food" },
1.49
),
new Product(
Guid.Parse("d06c4115-85d5-4448-b398-464850eebf4e"),
"Cheese",
"Good cheese",
new List<string> { "Food" },
2.99
),
new Product(
Guid.Parse("4382ba39-c9e3-48bb-83b3-9f9171b4c33f"),
"Chocolate",
"Delicious chocolate",
new List<string> { "Food" },
0.99
),
new Product(
Guid.Parse("9d428166-3cb7-4513-ae0d-e1cb18ac1416"),
"Candy",
"Tasty candy",
new List<string> { "Food" },
1.49
),
new Product(
Guid.Parse("782080a1-7953-4ac0-92d8-59ec5497563b"),
"Ice cream",
"Good ice cream",
new List<string> { "Food" },
2.99
),
new Product(
Guid.Parse("128cc5a0-9a73-4cb8-896b-7d1f8e9fb5f3"),
"Burger",
"Delicious burger",
new List<string> { "Food" },
7.99
),
new Product(
Guid.Parse("a028d630-2da8-432d-ad8c-b4990d288841"),
"Pizza",
"Tasty pizza",
new List<string> { "Food" },
9.99
),
};
}
21 changes: 21 additions & 0 deletions src/PriceWatcherService/Entities/Product.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace PriceWatcher.Entities
{
// This class is copied from ProductService. Everything you change here should also be changed over there.
public class Product
{
public Product(Guid id, string name, string description, IEnumerable<string> categories, double price)
{
Id = id;
Name = name;
Description = description;
Categories = categories;
Price = price;
}

public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public IEnumerable<string> Categories { get; set; }
public double Price { get; set; }
}
}
10 changes: 0 additions & 10 deletions src/PriceWatcherService/Entities/Products.cs

This file was deleted.

21 changes: 5 additions & 16 deletions src/PriceWatcherService/Repositories/PriceWatcherRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,19 @@ public class PriceWatcherRepository : IPriceWatcherRepository
private readonly PriceWatcherServiceConfiguration _cfg;
private readonly ILogger<PriceWatcherRepository> _logger;
private readonly List<Watcher> _watchers = new List<Watcher>();

private static readonly List<Product> _products = new List<Product> {
new Product { Id = Guid.Parse("08ae4294-47e1-4b76-8cd3-052d6308d699"), Name = "Ice cream", Description = "Cool down on hot days", Price = 4.49 },
new Product { Id = Guid.Parse("67611138-7dc1-42d9-b910-42c5d0247c52"), Name = "Bread", Description = "Yummy! Fresh bread smells super good", Price = 4.29 },
new Product { Id = Guid.Parse("fed436f8-76a2-4ce0-83a2-6bdb0fed705b"), Name = "Coffee", Description = "Delicious Coffee", Price = 2.49 },
new Product { Id = Guid.Parse("870d8ca1-1936-41a2-9f40-7d399f29ac38"), Name = "Bacon Burger", Description = "Everything is better with bacon", Price = 8.99 },
new Product { Id = Guid.Parse("d96798d2-b429-4842-9a29-9a2a448d4ff2"), Name = "Whisky", Description = "Gentle drink for cold evenings", Price = 49.99 },
new Product { Id = Guid.Parse("83fc59d6-9e20-450a-84c0-c8bc8fd80ee1"), Name = "Coke", Description = "Tasty coke", Price = 1.99 },
new Product { Id = Guid.Parse("e2810857-327d-47d1-918c-cf3e3709d2d8"), Name = "Sausage", Description = "Time for some BBQ", Price = 3.79 },
new Product { Id = Guid.Parse("525f1786-c045-46b1-aac2-d06da196bac4"), Name = "Beer", Description = "Tasty craft beer", Price = 3.99 },
new Product { Id = Guid.Parse("9b699928-4600-44bf-9923-ec41a428b809"), Name = "Coffee", Description = "Delicious", Price = 2.49 },
new Product { Id = Guid.Parse("2620540e-bfcb-4a06-87a4-f6ed2b3c069b"), Name = "Pizza", Description = "It comes with Bacon. You know! Because everything is better with bacon", Price = 7.99 }
};


public PriceWatcherRepository(DaprClient dapr, PriceWatcherServiceConfiguration cfg, ILogger<PriceWatcherRepository> logger)
{
_dapr = dapr;
_cfg = cfg;
_logger = logger;
}

private static List<Product> Products => InMemoryProducts.Products;

public bool Register(string email, Guid productId, double price)
{
if (!_products.Any(p => p.Id.Equals(productId)))
if (!Products.Any(p => p.Id.Equals(productId)))
{
_logger.LogInformation("Product {ProductId} not found", productId);
return false;
Expand All @@ -61,7 +50,7 @@ public bool Register(string email, Guid productId, double price)
public bool DropPrice(Guid productId, double dropBy)
{

var found = _products.FirstOrDefault(p => p.Id.Equals(productId));
var found = Products.FirstOrDefault(p => p.Id.Equals(productId));
if (found == null)
{
_logger.LogInformation("Product {ProductId} not found", productId);
Expand Down
32 changes: 16 additions & 16 deletions src/ProductsService/Data/Entities/Product.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
namespace ProductsService.Data.Entities
namespace ProductsService.Data.Entities;

// This class is copied to PriceWatcher. Everything you change here should also be changed over there.
public class Product
{
public class Product
public Product(Guid id, string name, string description, IEnumerable<string> categories, double price)
{
public Product(Guid id, string name, string description, IEnumerable<string> categories, double price)
{
Id = id;
Name = name;
Description = description;
Categories = categories;
Price = price;
}

public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public IEnumerable<string> Categories { get; set; }
public double Price { get; set; }
Id = id;
Name = name;
Description = description;
Categories = categories;
Price = price;
}

public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public IEnumerable<string> Categories { get; set; }
public double Price { get; set; }
}
Loading