diff --git a/OrderDatabase(After Processed)-Joaquin.png b/OrderDatabase(After Processed)-Joaquin.png new file mode 100644 index 0000000..34f040c Binary files /dev/null and b/OrderDatabase(After Processed)-Joaquin.png differ diff --git a/OrderDatabase(New Orders Are Set to Processed False)-Joaquin.png b/OrderDatabase(New Orders Are Set to Processed False)-Joaquin.png new file mode 100644 index 0000000..4fb3c15 Binary files /dev/null and b/OrderDatabase(New Orders Are Set to Processed False)-Joaquin.png differ diff --git a/OrderGet(Processes Orders)-Joaquin.png b/OrderGet(Processes Orders)-Joaquin.png new file mode 100644 index 0000000..cb3e685 Binary files /dev/null and b/OrderGet(Processes Orders)-Joaquin.png differ diff --git a/OrderPost-Joaquin.png b/OrderPost-Joaquin.png new file mode 100644 index 0000000..5f0dc7d Binary files /dev/null and b/OrderPost-Joaquin.png differ diff --git a/OrderQueueAWS-Joaquin.png b/OrderQueueAWS-Joaquin.png new file mode 100644 index 0000000..8790d46 Binary files /dev/null and b/OrderQueueAWS-Joaquin.png differ diff --git a/README.md b/README.md index e08400b..98fa777 100644 --- a/README.md +++ b/README.md @@ -5,15 +5,15 @@ Note in Every command and URL being called there are fields that need to be repl Example replacements (make sure to replace the curly brackets as well: don't keep curly brackets) -`{studentName}` - example `ajdewilzin` - this can be your name, independent of your login details +`joaquin` - example `ajdewilzin` - this can be your name, independent of your login details -`{region}` - example `eu-north-1` +`eu-north-1` - example `eu-north-1` ### Steps 1. Create an SNS Topic: ```bash -aws sns create-topic --name {studentName}OrderCreatedTopic +aws sns create-topic --name joaquinOrderCreatedTopic ``` If successful, you will see in your terminal a JSON response that includes `"TopicArn": "...`. @@ -22,7 +22,7 @@ Replace `_topicArn` in your Controller code with the generated `TopicArn` value 2. Create an SQS Queue: ```bash -aws sqs create-queue --queue-name {studentName}OrderQueue +aws sqs create-queue --queue-name joaquinOrderQueue ``` If successful, you will see in your terminal a JSON response that includes `"QueueUrl": "some_aws_url`. @@ -31,7 +31,7 @@ Replace `_queueUrl` in your Controller code with the generated `QueueUrl` from t ```bash -aws sns subscribe --topic-arn arn:aws:sns:{region}:637423341661:{studentName}OrderCreatedTopic --protocol sqs --notification-endpoint arn:aws:sqs:{region}:637423341661:{studentName}OrderQueue +aws sns subscribe --topic-arn arn:aws:sns:eu-north-1:637423341661:joaquinOrderCreatedTopic --protocol sqs --notification-endpoint arn:aws:sqs:eu-north-1:637423341661:joaquinOrderQueue ``` You don't need to save the generated SubscriptionArn. @@ -39,13 +39,13 @@ You don't need to save the generated SubscriptionArn. 3. Create an EventBridge Event Bus: ```bash -aws events create-event-bus --name {StudentName}CustomEventBus --region {region} +aws events create-event-bus --name joaquinCustomEventBus --region eu-north-1 ``` 4. Create an EventBridge Rule: ```bash -aws events put-rule --name {StudentName}OrderProcessedRule --event-pattern '{\"source\": [\"order.service\"]}' --event-bus-name {StudentName}CustomEventBus +aws events put-rule --name joaquinOrderProcessedRule --event-pattern '{\"source\": [\"order.service\"]}' --event-bus-name joaquinCustomEventBus ``` If your terminal complains about double quotes, you might need to remove the backslash `\` from the command above (and commands later on). @@ -54,11 +54,11 @@ If your terminal complains about double quotes, you might need to remove the bac 5. Subscribe the SQS Queue to the SNS Topic ```bash -aws sqs get-queue-attributes --queue-url https://sqs.{region}.amazonaws.com/637423341661/{studentName}OrderQueue --attribute-name QueueArn --region {region} +aws sqs get-queue-attributes --queue-url https://sqs.eu-north-1.amazonaws.com/637423341661/joaquinOrderQueue --attribute-name QueueArn --region eu-north-1 ``` ```bash -aws sns subscribe --topic-arn arn:aws:sns:{region}:637423341661:{studentName}OrderCreatedTopic --protocol sqs --notification-endpoint arn:aws:sqs:{region}:637423341661:{studentName}OrderQueue --region {region} +aws sns subscribe --topic-arn arn:aws:sns:eu-north-1:637423341661:joaquinOrderCreatedTopic --protocol sqs --notification-endpoint arn:aws:sqs:eu-north-1:637423341661:joaquinOrderQueue --region eu-north-1 ``` 6. Grant SNS Permissions to SQS @@ -66,7 +66,7 @@ aws sns subscribe --topic-arn arn:aws:sns:{region}:637423341661:{studentName}Ord In Bash/Unix terminals you can run this command: ```bash -aws sqs set-queue-attributes --queue-url https://sqs.{region}.amazonaws.com/637423341661/{studentName}OrderQueue --attributes '{"Policy":"{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"*\"},\"Action\":\"SQS:SendMessage\",\"Resource\":\"arn:aws:sqs:{region}:637423341661:{studentName}OrderQueue\",\"Condition\":{\"ArnEquals\":{\"aws:SourceArn\":\"arn:aws:sns:{region}:637423341661:{studentName}OrderCreatedTopic\"}}}]}"}' --region {region} +aws sqs set-queue-attributes --queue-url https://sqs.eu-north-1.amazonaws.com/637423341661/joaquinOrderQueue --attributes '{"Policy":"{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"*\"},\"Action\":\"SQS:SendMessage\",\"Resource\":\"arn:aws:sqs:eu-north-1:637423341661:joaquinOrderQueue\",\"Condition\":{\"ArnEquals\":{\"aws:SourceArn\":\"arn:aws:sns:eu-north-1:637423341661:joaquinOrderCreatedTopic\"}}}]}"}' --region eu-north-1 ``` diff --git a/WorkshopBackend/OrderService/.gitignore b/WorkshopBackend/OrderService/.gitignore new file mode 100644 index 0000000..16bf3ad --- /dev/null +++ b/WorkshopBackend/OrderService/.gitignore @@ -0,0 +1 @@ +appsettings.json diff --git a/WorkshopBackend/OrderService/Context/OrderDbContext.cs b/WorkshopBackend/OrderService/Context/OrderDbContext.cs new file mode 100644 index 0000000..63a6677 --- /dev/null +++ b/WorkshopBackend/OrderService/Context/OrderDbContext.cs @@ -0,0 +1,12 @@ +using InventoryService.Models; +using Microsoft.EntityFrameworkCore; + +namespace OrderService.Context +{ + public class OrderDbContext : DbContext + { + public OrderDbContext(DbContextOptions options) : base(options) { } + + public DbSet Orders { get; set; } + } +} diff --git a/WorkshopBackend/OrderService/Controllers/OrderController.cs b/WorkshopBackend/OrderService/Controllers/OrderController.cs index e49adda..8f9367e 100644 --- a/WorkshopBackend/OrderService/Controllers/OrderController.cs +++ b/WorkshopBackend/OrderService/Controllers/OrderController.cs @@ -7,6 +7,8 @@ using Amazon.EventBridge.Model; using System.Text.Json; using InventoryService.Models; +using OrderService.Context; +using Microsoft.EntityFrameworkCore; [ApiController] [Route("[controller]")] @@ -15,15 +17,17 @@ public class OrderController : ControllerBase private readonly IAmazonSQS _sqs; private readonly IAmazonSimpleNotificationService _sns; private readonly IAmazonEventBridge _eventBridge; - private readonly string _queueUrl = ""; // Format of https://.* - private readonly string _topicArn = ""; // Format of arn:aws.* + private OrderDbContext _db; + private readonly string _queueUrl = "https://sqs.eu-north-1.amazonaws.com/637423341661/joaquinOrderQueue"; // Format of https://.* + private readonly string _topicArn = "arn:aws:sns:eu-north-1:637423341661:joaquinOrderCreatedTopic"; // Format of arn:aws.* - public OrderController() + public OrderController(OrderDbContext db) { // Instantiate clients with default configuration _sqs = new AmazonSQSClient(); _sns = new AmazonSimpleNotificationServiceClient(); _eventBridge = new AmazonEventBridgeClient(); + _db = db; } [HttpGet] @@ -40,12 +44,40 @@ public async Task GetOrdersAndProcess() WaitTimeSeconds = 20 }; + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + var response = await _sqs.ReceiveMessageAsync(request); foreach (var message in response.Messages) { - var order = JsonSerializer.Deserialize(message.Body); + // Define the order variable + Order? order = null; + //The message we want is nested in the queue request. + using(JsonDocument document = JsonDocument.Parse(message.Body)) + { + string innerMessage = document.RootElement.GetProperty("Message").GetString(); + + //Desereialize the inner message + order = JsonSerializer.Deserialize(innerMessage); + } + // Process order (e.g., update inventory) + if(order != null) + { + var existingOrder = await _db.Orders.FindAsync(order.OrderId); + if (existingOrder != null) + { + //Calculate the total and change processed flag to true + existingOrder.Total = existingOrder.Quantity * existingOrder.Amount; + existingOrder.Processed = true; + // Update the order on RDS + _db.Entry(existingOrder).State = EntityState.Modified; + await _db.SaveChangesAsync(); + } + } // Delete message after processing await _sqs.DeleteMessageAsync(_queueUrl, message.ReceiptHandle); @@ -60,6 +92,12 @@ public async Task CreateOrder([FromBody] Order order) * AmazonSimpleNotificationServiceClient * */ + // Save order to RDS + order.Processed = false; //Set processed to false, as we are just adding it to the database + await _db.Orders.AddAsync(order); + await _db.SaveChangesAsync(); + + // Publish to SNS var message = JsonSerializer.Serialize(order); var publishRequest = new PublishRequest diff --git a/WorkshopBackend/OrderService/Migrations/20241016114616_first.Designer.cs b/WorkshopBackend/OrderService/Migrations/20241016114616_first.Designer.cs new file mode 100644 index 0000000..2a732de --- /dev/null +++ b/WorkshopBackend/OrderService/Migrations/20241016114616_first.Designer.cs @@ -0,0 +1,59 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using OrderService.Context; + +#nullable disable + +namespace OrderService.Migrations +{ + [DbContext(typeof(OrderDbContext))] + [Migration("20241016114616_first")] + partial class first + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("InventoryService.Models.Order", b => + { + b.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("OrderId")); + + b.Property("Amount") + .HasColumnType("integer"); + + b.Property("Processed") + .HasColumnType("boolean"); + + b.Property("Product") + .IsRequired() + .HasColumnType("text"); + + b.Property("Quantity") + .HasColumnType("integer"); + + b.Property("Total") + .HasColumnType("integer"); + + b.HasKey("OrderId"); + + b.ToTable("Orders"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/WorkshopBackend/OrderService/Migrations/20241016114616_first.cs b/WorkshopBackend/OrderService/Migrations/20241016114616_first.cs new file mode 100644 index 0000000..0987b55 --- /dev/null +++ b/WorkshopBackend/OrderService/Migrations/20241016114616_first.cs @@ -0,0 +1,39 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace OrderService.Migrations +{ + /// + public partial class first : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + OrderId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Product = table.Column(type: "text", nullable: false), + Quantity = table.Column(type: "integer", nullable: false), + Amount = table.Column(type: "integer", nullable: false), + Processed = table.Column(type: "boolean", nullable: true), + Total = table.Column(type: "integer", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.OrderId); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Orders"); + } + } +} diff --git a/WorkshopBackend/OrderService/Migrations/OrderDbContextModelSnapshot.cs b/WorkshopBackend/OrderService/Migrations/OrderDbContextModelSnapshot.cs new file mode 100644 index 0000000..b4cb7c1 --- /dev/null +++ b/WorkshopBackend/OrderService/Migrations/OrderDbContextModelSnapshot.cs @@ -0,0 +1,56 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using OrderService.Context; + +#nullable disable + +namespace OrderService.Migrations +{ + [DbContext(typeof(OrderDbContext))] + partial class OrderDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("InventoryService.Models.Order", b => + { + b.Property("OrderId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("OrderId")); + + b.Property("Amount") + .HasColumnType("integer"); + + b.Property("Processed") + .HasColumnType("boolean"); + + b.Property("Product") + .IsRequired() + .HasColumnType("text"); + + b.Property("Quantity") + .HasColumnType("integer"); + + b.Property("Total") + .HasColumnType("integer"); + + b.HasKey("OrderId"); + + b.ToTable("Orders"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/WorkshopBackend/OrderService/OrderService.csproj b/WorkshopBackend/OrderService/OrderService.csproj index 385ff06..7e99840 100644 --- a/WorkshopBackend/OrderService/OrderService.csproj +++ b/WorkshopBackend/OrderService/OrderService.csproj @@ -12,6 +12,14 @@ + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/WorkshopBackend/OrderService/Program.cs b/WorkshopBackend/OrderService/Program.cs index 10621e8..b3da91c 100644 --- a/WorkshopBackend/OrderService/Program.cs +++ b/WorkshopBackend/OrderService/Program.cs @@ -1,4 +1,6 @@ +using Microsoft.EntityFrameworkCore; using Microsoft.OpenApi.Models; +using OrderService.Context; var builder = WebApplication.CreateBuilder(args); @@ -24,6 +26,10 @@ .AllowAnyHeader() )); +// Add DbContext +var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); +builder.Services.AddDbContext(options => options.UseNpgsql(connectionString)); + var app = builder.Build(); app.UseCors("CorsPolicy"); diff --git a/WorkshopBackend/OrderService/appsettings.json b/WorkshopBackend/OrderService/appsettings.json deleted file mode 100644 index 1c233fe..0000000 --- a/WorkshopBackend/OrderService/appsettings.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "AWS": { - "Profile": "default", - "Region": "us-west-2" - }, - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*" -} diff --git a/WorkshopBackend/WorkshopBackend.sln b/WorkshopBackend/WorkshopBackend.sln index 005d47c..6d34686 100644 --- a/WorkshopBackend/WorkshopBackend.sln +++ b/WorkshopBackend/WorkshopBackend.sln @@ -8,6 +8,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E49EF9F2-F493-4887-B8CF-FCF54A7C151D}" ProjectSection(SolutionItems) = preProject ..\README.md = ..\README.md + set-queue-attributes.json = set-queue-attributes.json EndProjectSection EndProject Global diff --git a/WorkshopBackend/set-queue-attributes.json b/WorkshopBackend/set-queue-attributes.json new file mode 100644 index 0000000..6918a20 --- /dev/null +++ b/WorkshopBackend/set-queue-attributes.json @@ -0,0 +1,4 @@ +{ + "Policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"*\"},\"Action\":\"SQS:SendMessage\",\"Resource\":\"arn:aws:sqs:eu-north-1:637423341661:joaquinOrderQueue\",\"Condition\":{\"ArnEquals\":{\"aws:SourceArn\":\"arn:aws:sns:eu-north-1:637423341661:joaquinOrderCreatedTopic\"}}}]}" +} +