diff --git a/Messaging.Tests/UnitTest1.cs b/Messaging.Tests/UnitTest1.cs index 9395f503..a4114256 100644 --- a/Messaging.Tests/UnitTest1.cs +++ b/Messaging.Tests/UnitTest1.cs @@ -134,7 +134,7 @@ public async Task CorrectlyFormattedButInvalidMessage() Assert.False(response.IsSuccessStatusCode); Assert.True(response.StatusCode is System.Net.HttpStatusCode.BadRequest); var message = await response.Content.ReadAsStringAsync(); - Assert.Equal("\"To 14445556543 could not be parsed as valid NANP (North American Numbering Plan) numbers. msisdn:15555551212, ,to:14445556543, ,message:Your Lyft code is 12345, \"", message); + Assert.Equal("\"To 14445556543 could not be parsed as valid NANP (North American Numbering Plan) numbers. msisdn:15555551212,to:14445556543,message:Your Lyft code is 12345\"", message); } [Fact] diff --git a/Messaging/Migrations/20230807220201_ToFoward.Designer.cs b/Messaging/Migrations/20230807220201_ToFoward.Designer.cs new file mode 100644 index 00000000..b52936c1 --- /dev/null +++ b/Messaging/Migrations/20230807220201_ToFoward.Designer.cs @@ -0,0 +1,109 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Models; + +#nullable disable + +namespace Messaging.Migrations +{ + [DbContext(typeof(MessagingContext))] + [Migration("20230807220201_ToFoward")] + partial class ToFoward + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.5"); + + modelBuilder.Entity("Models.ClientRegistration", b => + { + b.Property("ClientRegistrationId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AsDialed") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CallbackUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ClientSecret") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DateRegistered") + .HasColumnType("TEXT"); + + b.Property("RegisteredUpstream") + .HasColumnType("INTEGER"); + + b.Property("UpstreamStatusDescription") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("ClientRegistrationId"); + + b.ToTable("ClientRegistrations"); + }); + + modelBuilder.Entity("Models.MessageRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Content") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DateReceivedUTC") + .HasColumnType("TEXT"); + + b.Property("From") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("MediaURLs") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("MessageSource") + .HasColumnType("INTEGER"); + + b.Property("MessageType") + .HasColumnType("INTEGER"); + + b.Property("RawRequest") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RawResponse") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Succeeded") + .HasColumnType("INTEGER"); + + b.Property("To") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ToForward") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Messages"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Messaging/Migrations/20230807220201_ToFoward.cs b/Messaging/Migrations/20230807220201_ToFoward.cs new file mode 100644 index 00000000..1f4ddfdf --- /dev/null +++ b/Messaging/Migrations/20230807220201_ToFoward.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Messaging.Migrations +{ + /// + public partial class ToFoward : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ToForward", + table: "Messages", + type: "TEXT", + nullable: false, + defaultValue: ""); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ToForward", + table: "Messages"); + } + } +} diff --git a/Messaging/Migrations/MessagingContextModelSnapshot.cs b/Messaging/Migrations/MessagingContextModelSnapshot.cs index 64861455..b5c50edb 100644 --- a/Messaging/Migrations/MessagingContextModelSnapshot.cs +++ b/Messaging/Migrations/MessagingContextModelSnapshot.cs @@ -92,6 +92,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("TEXT"); + b.Property("ToForward") + .IsRequired() + .HasColumnType("TEXT"); + b.HasKey("Id"); b.ToTable("Messages"); diff --git a/Messaging/Program.cs b/Messaging/Program.cs index 5ff9af70..51025050 100644 --- a/Messaging/Program.cs +++ b/Messaging/Program.cs @@ -18,8 +18,6 @@ using Models; -using Org.BouncyCastle.Ocsp; - using Prometheus; using Serilog; @@ -27,15 +25,11 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; -using System.Security.Cryptography; -using System.ServiceModel.Channels; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading.RateLimiting; -using static System.Runtime.InteropServices.JavaScript.JSType; - Log.Logger = new LoggerConfiguration() .MinimumLevel.Override("Microsoft", LogEventLevel.Information) .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning) @@ -560,6 +554,37 @@ }); await db.SaveChangesAsync(); } + + // Forward failed incoming messages for this number. + var inboundMMS = await db.Messages.Where(x => x.To.Contains(asDialedNumber.DialedNumber) && x.MessageSource == MessageSource.Incoming && x.MessageType == MessageType.MMS).ToListAsync(); + var inboundSMS = await db.Messages.Where(x => x.To.Contains(asDialedNumber.DialedNumber) && x.MessageSource == MessageSource.Incoming && x.MessageType == MessageType.SMS).ToListAsync(); + inboundMMS.AddRange(inboundSMS); + + foreach (var failedMessage in inboundMMS) + { + try + { + // Forward to the newly registered callback URL. + var messageToForward = System.Text.Json.JsonSerializer.Deserialize(failedMessage.ToForward); + + if (messageToForward is not null && !string.IsNullOrWhiteSpace(messageToForward.Content)) + { + messageToForward.ClientSecret = registration.ClientSecret; + var response = await registration.CallbackUrl.PostJsonAsync(messageToForward); + string responseText = await response.GetStringAsync(); + Log.Information(responseText); + Log.Information(System.Text.Json.JsonSerializer.Serialize(messageToForward)); + failedMessage.RawResponse = responseText; + failedMessage.Succeeded = true; + await db.SaveChangesAsync(); + } + } + catch (Exception ex) + { + Log.Error(ex.Message); + Log.Error(ex.StackTrace ?? "No stack trace found."); + } + } } catch (Exception ex) { @@ -647,25 +672,25 @@ app.MapGet("/message/all/failed", async Task, NotFound, BadRequest>> (MessagingContext db, DateTime start, DateTime end) => { - try - { - var messages = await db.Messages.Where(x => !x.Succeeded && x.DateReceivedUTC > start && x.DateReceivedUTC <= end).OrderByDescending(x => x.DateReceivedUTC).ToArrayAsync(); + try + { + var messages = await db.Messages.Where(x => !x.Succeeded && x.DateReceivedUTC > start && x.DateReceivedUTC <= end).OrderByDescending(x => x.DateReceivedUTC).ToArrayAsync(); - if (messages is not null && messages.Any()) - { - return TypedResults.Ok(messages); - } - else - { - return TypedResults.NotFound($"No failed messages have been recorded between {start} and {end}."); - } + if (messages is not null && messages.Any()) + { + return TypedResults.Ok(messages); } - catch (Exception ex) + else { - Log.Error(ex.Message); - Log.Error(ex.StackTrace ?? "No stack trace found."); - return TypedResults.BadRequest(ex.Message); + return TypedResults.NotFound($"No failed messages have been recorded between {start} and {end}."); } + } + catch (Exception ex) + { + Log.Error(ex.Message); + Log.Error(ex.StackTrace ?? "No stack trace found."); + return TypedResults.BadRequest(ex.Message); + } }) .RequireAuthorization().WithOpenApi(x => new(x) { Summary = "View all sent and received messages that failed.", Description = "This is intended to help you debug problems with message sending and delivery so you can see if it's this API or the upstream vendor that is causing problems." }); @@ -999,7 +1024,7 @@ //string timezone = context.Request.Form["timezone"].ToString(); //string origtime = context.Request.Form["origtime"].ToString(); string fullrecipientlist = context.Request.Form["FullRecipientList"].ToString(); - string incomingRequest = string.Join(',', context.Request.Form.Select(x => $"{x.Key}:{x.Value}, ")); + string incomingRequest = string.Join(',', context.Request.Form.Select(x => $"{x.Key}:{x.Value}")); // The message field is a JSON object. var MMSDescription = System.Text.Json.JsonSerializer.Deserialize(message); @@ -1156,9 +1181,11 @@ { toForward.ClientSecret = client.ClientSecret; var response = await client.CallbackUrl.PostJsonAsync(toForward); - Log.Information(await response.GetStringAsync()); + string responseText = await response.GetStringAsync(); + Log.Information(responseText); Log.Information(System.Text.Json.JsonSerializer.Serialize(toForward)); - messageRecord.RawResponse = System.Text.Json.JsonSerializer.Serialize(toForward); + messageRecord.ToForward = System.Text.Json.JsonSerializer.Serialize(toForward); + messageRecord.RawResponse = responseText; messageRecord.Succeeded = true; } catch (FlurlHttpException ex) @@ -1190,6 +1217,7 @@ messageRecord.To = toForward.To; messageRecord.From = toForward.From; + messageRecord.ToForward = System.Text.Json.JsonSerializer.Serialize(toForward); messageRecord.RawResponse = $"{toForward.To} is not registered as a client."; db.Messages.Add(messageRecord); await db.SaveChangesAsync(); @@ -1227,7 +1255,7 @@ //string timezone = context.Request.Form["timezone"].ToString(); //string origtime = context.Request.Form["origtime"].ToString(); string fullrecipientlist = context.Request.Form["FullRecipientList"].ToString(); - string incomingRequest = string.Join(',', context.Request.Form.Select(x => $"{x.Key}:{x.Value}, ")); + string incomingRequest = string.Join(',', context.Request.Form.Select(x => $"{x.Key}:{x.Value}")); // Disabled because this secret value changes whenever. //if (serversecret != firstPointIncomingSMSSecret) @@ -1347,9 +1375,11 @@ { toForward.ClientSecret = client.ClientSecret; var response = await client.CallbackUrl.PostJsonAsync(toForward); - Log.Information(await response.GetStringAsync()); + string responseText = await response.GetStringAsync(); + Log.Information(responseText); Log.Information(System.Text.Json.JsonSerializer.Serialize(toForward)); - messageRecord.RawResponse = System.Text.Json.JsonSerializer.Serialize(toForward); + messageRecord.ToForward = System.Text.Json.JsonSerializer.Serialize(toForward); + messageRecord.RawResponse = responseText; messageRecord.Succeeded = true; } catch (FlurlHttpException ex) @@ -1380,6 +1410,7 @@ messageRecord.To = toForward.To; messageRecord.From = toForward.From; + messageRecord.ToForward = System.Text.Json.JsonSerializer.Serialize(toForward); messageRecord.RawResponse = $"{toForward.To} is not registered as a client."; db.Messages.Add(messageRecord); await db.SaveChangesAsync(); @@ -1568,6 +1599,7 @@ public class MessageRecord public string RawRequest { get; set; } = string.Empty; public string RawResponse { get; set; } = string.Empty; public bool Succeeded { get; set; } = false; + public string ToForward { get; set; } = string.Empty; } // Format forward to client apps as JSON.