-
-
Notifications
You must be signed in to change notification settings - Fork 2
Merge use storage feature/capability into main #89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
161602d
Added capability for storing incoming/outgoing messages
adalon 1fad556
Fixed interface usage
adalon 89b5173
Refactored ResponseContentMessage into IMessage
adalon 13988fa
Added some more comments and fixed formatting
adalon ae68a08
Making IStorageService public
adalon 80e0f0d
More nits
adalon 1a172e5
Saving all messages
adalon d6c24ca
Passing missed ct
adalon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| using System.Text.Json.Serialization; | ||
|
|
||
| namespace Devlooped.WhatsApp; | ||
|
|
||
| /// <summary> | ||
| /// Represents a message exchanged in a communication system, serving as a base type for various message types. | ||
| /// </summary> | ||
| /// <remarks>This interface is designed to support polymorphic serialization and deserialization of different | ||
| /// message types. Derived types are identified using JSON type discrimination, as specified by the <see | ||
| /// cref="JsonPolymorphic"/> and <see cref="JsonDerivedTypeAttribute"/> annotations. Examples of derived types include | ||
| /// content messages, error messages, and interactive messages.</remarks> | ||
| [JsonPolymorphic] | ||
| [JsonDerivedType(typeof(ContentMessage), "content")] | ||
| [JsonDerivedType(typeof(ErrorMessage), "error")] | ||
| [JsonDerivedType(typeof(InteractiveMessage), "interactive")] | ||
| [JsonDerivedType(typeof(ReactionMessage), "reaction")] | ||
| [JsonDerivedType(typeof(StatusMessage), "status")] | ||
| [JsonDerivedType(typeof(UnsupportedMessage), "unsupported")] | ||
| [JsonDerivedType(typeof(TextResponse), "response/text")] | ||
| [JsonDerivedType(typeof(TemplateResponse), "response/template")] | ||
| [JsonDerivedType(typeof(ReactionResponse), "response/reaction")] | ||
| public interface IMessage | ||
| { | ||
| /// <summary> | ||
| /// Gets the phone number associated with the message sender. | ||
| /// </summary> | ||
| string Number { get; } | ||
|
|
||
| /// <summary> | ||
| /// Gets the message id. | ||
| /// </summary> | ||
| string Id { get; } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| namespace Devlooped.WhatsApp; | ||
|
|
||
| /// <summary> | ||
| /// Defines methods for storing and retrieving messages in an asynchronous manner. | ||
| /// </summary> | ||
| /// <remarks>This interface provides functionality to retrieve messages associated with a specific identifier and | ||
| /// to save messages or responses to the storage. Implementations of this interface should ensure thread safety and | ||
| /// proper handling of cancellation tokens for asynchronous operations.</remarks> | ||
| public interface IStorageService | ||
| { | ||
| /// <summary> | ||
| /// Retrieves a stream of messages associated with the specified phone number. | ||
| /// </summary> | ||
| /// <remarks>This method uses asynchronous streaming to retrieve messages, allowing the caller to process | ||
| /// messages as they are received. Ensure proper handling of the <see cref="IAsyncEnumerable{T}"/> by using `await | ||
| /// foreach` or equivalent constructs.</remarks> | ||
| /// <param name="number">The phone number for which to retrieve messages. This must be a valid phone number in the expected format.</param> | ||
| /// <param name="cancellationToken">A token to monitor for cancellation requests. The operation will terminate early if the token is canceled.</param> | ||
| /// <returns>An asynchronous stream of <see cref="Message"/> objects representing the messages associated with the specified | ||
| /// phone number. The stream will be empty if no messages are found.</returns> | ||
| IAsyncEnumerable<IMessage> GetMessagesAsync(string number, CancellationToken cancellationToken = default); | ||
|
|
||
| /// <summary> | ||
| /// Asynchronously saves a collection of messages to the underlying storage. | ||
| /// </summary> | ||
| /// <remarks>If the operation is canceled via the <paramref name="cancellationToken"/>, the returned task | ||
| /// will be in a canceled state.</remarks> | ||
| /// <param name="messages">The collection of <see cref="Message"/> objects to be saved. Cannot be null or empty.</param> | ||
| /// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the save operation. The default value is <see | ||
| /// langword="default"/>.</param> | ||
| /// <returns>A <see cref="Task"/> that represents the asynchronous save operation.</returns> | ||
| Task SaveAsync(IEnumerable<IMessage> messages, CancellationToken cancellationToken = default); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| using System.Runtime.CompilerServices; | ||
|
|
||
| namespace Devlooped.WhatsApp; | ||
|
|
||
| /// <summary> | ||
| /// Handles incoming messages by saving user messages to storage and delegating further processing to an inner handler. | ||
| /// </summary> | ||
| class MessageStorageHandler : DelegatingWhatsAppHandler | ||
| { | ||
| readonly IStorageService storageService; | ||
|
|
||
| public MessageStorageHandler(IWhatsAppHandler innerHandler, IStorageService storageService) | ||
| : base(innerHandler) | ||
| { | ||
| this.storageService = storageService; | ||
| } | ||
|
|
||
| public override async IAsyncEnumerable<Response> HandleAsync(IEnumerable<Message> messages, [EnumeratorCancellation] CancellationToken cancellation = default) | ||
| { | ||
| // Save the incoming user messages only. Avoid system messages, etc | ||
| // TODO: Fire and forget? Do we really need to wait for the messages to be fully saved here? | ||
| await storageService.SaveAsync(messages, cancellation); | ||
|
|
||
| await foreach (var response in base.HandleAsync(messages, cancellation)) | ||
| { | ||
| yield return response; | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,29 @@ | ||
| using System.Text.Json.Serialization; | ||
| using static System.Runtime.InteropServices.JavaScript.JSType; | ||
|
|
||
| namespace Devlooped.WhatsApp; | ||
| namespace Devlooped.WhatsApp; | ||
|
|
||
| /// <summary> | ||
| /// Base class for responses. | ||
| /// Represents a response sent via WhatsApp, containing the associated message and response metadata. | ||
| /// </summary> | ||
| public abstract partial record Response | ||
| /// <remarks>This abstract record serves as a base type for specific response implementations. It encapsulates the | ||
| /// message being sent and provides functionality for sending the response asynchronously using a WhatsApp | ||
| /// client.</remarks> | ||
| /// <param name="Message">The message this response is created for</param> | ||
| public abstract partial record Response(Message Message) : IMessage | ||
| { | ||
| /// <inheritdoc/> | ||
| public string Id { get; set; } = string.Empty; | ||
|
|
||
| /// <inheritdoc/> | ||
| public string Number => Message.From.Number; | ||
|
|
||
| /// <summary> | ||
| /// Sends a request asynchronously using the specified WhatsApp client. | ||
| /// </summary> | ||
| /// <remarks>This method is abstract and must be implemented by a derived class to define the specific | ||
| /// behavior for sending a request.</remarks> | ||
| /// <param name="client">The <see cref="IWhatsAppClient"/> instance used to send the request. This parameter cannot be <see | ||
| /// langword="null"/>.</param> | ||
| /// <param name="cancellation">An optional <see cref="CancellationToken"/> to observe while waiting for the task to complete. Defaults to <see | ||
| /// cref="CancellationToken.None"/>.</param> | ||
| /// <returns>A <see cref="Task"/> that represents the asynchronous operation.</returns> | ||
| internal abstract Task SendAsync(IWhatsAppClient client, CancellationToken cancellation = default); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| using System.Runtime.CompilerServices; | ||
|
|
||
| namespace Devlooped.WhatsApp; | ||
|
|
||
| /// <summary> | ||
| /// A handler that processes WhatsApp messages and stores the generated responses using a storage service. | ||
| /// </summary> | ||
| class ResponseStorageHandler : DelegatingWhatsAppHandler | ||
| { | ||
| readonly IStorageService storageService; | ||
|
|
||
| public ResponseStorageHandler(IWhatsAppHandler innerHandler, IStorageService storageService) | ||
| : base(innerHandler) | ||
| { | ||
| this.storageService = storageService; | ||
| } | ||
|
|
||
| public async override IAsyncEnumerable<Response> HandleAsync(IEnumerable<Message> messages, [EnumeratorCancellation] CancellationToken cancellation = default) | ||
| { | ||
| await foreach (var response in InnerHandler.HandleAsync(messages, cancellation)) | ||
| { | ||
| await storageService.SaveAsync([response], cancellation); | ||
|
|
||
| yield return response; | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| using System.Runtime.CompilerServices; | ||
| namespace Devlooped.WhatsApp; | ||
|
|
||
| /// <summary> | ||
| /// Handles the processing of messages by delegating to an inner handler and sending the resulting responses using the | ||
| /// specified WhatsApp client. | ||
| /// </summary> | ||
| class SendResponsesHandler : DelegatingWhatsAppHandler | ||
| { | ||
| readonly IWhatsAppClient client; | ||
|
|
||
| public SendResponsesHandler(IWhatsAppHandler innerHandler, IWhatsAppClient client) | ||
| : base(innerHandler) | ||
| { | ||
| this.client = client; | ||
| } | ||
|
|
||
| public async override IAsyncEnumerable<Response> HandleAsync(IEnumerable<Message> messages, [EnumeratorCancellation] CancellationToken cancellation = default) | ||
| { | ||
| await foreach (var response in InnerHandler.HandleAsync(messages, cancellation)) | ||
| { | ||
| await response.SendAsync(client, cancellation); | ||
|
|
||
| yield return response; | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| using Microsoft.Extensions.DependencyInjection; | ||
|
|
||
| namespace Devlooped.WhatsApp; | ||
|
|
||
| /// <summary> | ||
| /// Provides extensions for configuring <see cref="MessageStorageHandler"/> instances. | ||
| /// </summary> | ||
| public static class StorageHandlerExtensions | ||
| { | ||
| public static WhatsAppHandlerBuilder UseStorage(this WhatsAppHandlerBuilder builder) | ||
| { | ||
| _ = Throw.IfNull(builder); | ||
|
|
||
| // By adding the storage service, the incoming and outgoing handlers will be automatically added to the pipeline | ||
| builder.Services.AddSingleton<IStorageService, StorageService>(services => new StorageService(services.GetRequiredService<CloudStorageAccount>())); | ||
|
|
||
| return builder; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| namespace Devlooped.WhatsApp; | ||
|
|
||
| class StorageService(CloudStorageAccount storage) : IStorageService | ||
| { | ||
| const string MessagesTableName = "messages"; | ||
|
|
||
| Lazy<IDocumentRepository<IMessage>> messagesRepository = new(() => | ||
| DocumentRepository.Create<IMessage>( | ||
| storage, | ||
| MessagesTableName, | ||
| x => x.Number, | ||
| x => x.Id)); | ||
|
|
||
| /// <inheritdoc/> | ||
| public async Task SaveAsync(IEnumerable<IMessage> messages, CancellationToken cancellationToken = default) | ||
| { | ||
| var repository = messagesRepository.Value; | ||
|
|
||
| foreach (var message in messages.Where(x => !string.IsNullOrEmpty(x.Id))) | ||
| { | ||
| await repository.PutAsync(message, cancellationToken); | ||
| } | ||
adalon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| public IAsyncEnumerable<IMessage> GetMessagesAsync(string number, CancellationToken cancellationToken = default) | ||
| => messagesRepository.Value.EnumerateAsync(number, cancellationToken); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,23 +1,28 @@ | ||||||
| namespace Devlooped.WhatsApp; | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// A simple text response to a user message. | ||||||
| /// Represents a response containing text and optional interactive buttons, which can be sent as a reply to a message. | ||||||
|
||||||
| /// Represents a response containing text and optional interactive buttons, which can be sent as a reply to a message. | |
| /// Represents a response containing text and optional interactive buttons, which can be sent as a reply to a message. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.