Skip to content
Merged
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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ var token = await client.Tokens.SendAsync(new SendTokenRequest
});
```

Check account balance:

```csharp
var balance = await client.Insights.GetBalanceAsync();
```

## Examples

Run the examples project after setting your Termii API key:
Expand Down
22 changes: 11 additions & 11 deletions docs/API_COVERAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,17 @@ The Termii docs describe a REST/JSON API and state that each account has its own
| Messaging | Send WhatsApp conversational message | POST | `/api/sms/send` | JSON body | `TermiiClient.Messaging` | Implemented | #7, PR #21 | Unit tests added |
| Messaging | Send bulk message | POST | `/api/sms/send/bulk` | JSON body | `TermiiClient.Messaging` | Implemented | #7, PR #21 | Unit tests added |
| Messaging | Send via Number API | POST | `/api/sms/number/send` | JSON body | `TermiiClient.Numbers` | Implemented | #5, PR #25 | Unit tests added |
| Token | Send OTP token | POST | `/api/sms/otp/send` | JSON body | `TermiiClient.Tokens` | In progress | #4 | Unit tests added |
| Token | Verify OTP token | POST | `/api/sms/otp/verify` | JSON body | `TermiiClient.Tokens` | In progress | #4 | Unit tests added |
| Token | Generate in-app OTP token | POST | `/api/sms/otp/generate` | JSON body | `TermiiClient.Tokens` | In progress | #4 | Unit tests added |
| Token | Send voice OTP | POST | `/api/sms/otp/send/voice` | JSON body | `TermiiClient.Tokens` | In progress | #4 | Unit tests added |
| Token | Send voice call token | POST | `/api/sms/otp/call` | JSON body | `TermiiClient.Tokens` | In progress | #4 | Unit tests added |
| Token | Send email OTP token | POST | `/api/email/otp/send` | JSON body | `TermiiClient.Tokens` | In progress | #4 | Unit tests added |
| Token | Send WhatsApp OTP token | POST | `/api/sms/send` | JSON body with `channel=whatsapp_otp` | `TermiiClient.Tokens` | In progress | #4 | Unit tests added |
| Insights | Get balance | GET | `/api/get-balance` | Query | `TermiiClient.Insights` | Planned | #9 | Planned unit + optional integration |
| Insights | Search DND/number status | GET | `/api/check/dnd` | Query | `TermiiClient.Insights` | Planned | #9 | Planned unit + optional integration |
| Insights | Query number intelligence/status | GET | `/api/insight/number/query` | Query | `TermiiClient.Insights` | Planned | #9 | Planned unit + optional integration |
| Insights | Fetch message inbox/history | GET | `/api/sms/inbox` | Query | `TermiiClient.Insights` | Planned | #9 | Planned unit + optional integration |
| Token | Send OTP token | POST | `/api/sms/otp/send` | JSON body | `TermiiClient.Tokens` | Implemented | #4, PR #26 | Unit tests added |
| Token | Verify OTP token | POST | `/api/sms/otp/verify` | JSON body | `TermiiClient.Tokens` | Implemented | #4, PR #26 | Unit tests added |
| Token | Generate in-app OTP token | POST | `/api/sms/otp/generate` | JSON body | `TermiiClient.Tokens` | Implemented | #4, PR #26 | Unit tests added |
| Token | Send voice OTP | POST | `/api/sms/otp/send/voice` | JSON body | `TermiiClient.Tokens` | Implemented | #4, PR #26 | Unit tests added |
| Token | Send voice call token | POST | `/api/sms/otp/call` | JSON body | `TermiiClient.Tokens` | Implemented | #4, PR #26 | Unit tests added |
| Token | Send email OTP token | POST | `/api/email/otp/send` | JSON body | `TermiiClient.Tokens` | Implemented | #4, PR #26 | Unit tests added |
| Token | Send WhatsApp OTP token | POST | `/api/sms/send` | JSON body with `channel=whatsapp_otp` | `TermiiClient.Tokens` | Implemented | #4, PR #26 | Unit tests added |
| Insights | Get balance | GET | `/api/get-balance` | Query | `TermiiClient.Insights` | In progress | #9 | Unit tests added |
| Insights | Search DND/number status | GET | `/api/check/dnd` | Query | `TermiiClient.Insights` | In progress | #9 | Unit tests added |
| Insights | Query number intelligence/status | GET | `/api/insight/number/query` | Query | `TermiiClient.Insights` | In progress | #9 | Unit tests added |
| Insights | Fetch message inbox/history | GET | `/api/sms/inbox` | Query | `TermiiClient.Insights` | In progress | #9 | Unit tests added |
| Insights | Fetch message analytics/history | GET | `/api/sms/history/analytics` | Query | `TermiiClient.Insights` | Needs verification | #9 | Planned unit + optional integration |

## Deferred Coverage
Expand Down
16 changes: 16 additions & 0 deletions src/Termii/Insights/CheckDndRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Termii;

public sealed class CheckDndRequest
{
public string PhoneNumber { get; set; } = string.Empty;

internal string ToPath()
{
TermiiRequestValidation.Required(PhoneNumber, nameof(PhoneNumber));

var query = new List<string>();
TermiiInsightsQueryString.AddIfPresent(query, "phone_number", PhoneNumber);

return TermiiInsightsQueryString.Append("/api/check/dnd", query);
}
}
25 changes: 25 additions & 0 deletions src/Termii/Insights/CheckDndResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Termii;

public sealed class CheckDndResponse
{
[JsonPropertyName("number")]
public string? Number { get; set; }

[JsonPropertyName("phone_number")]
public string? PhoneNumber { get; set; }

[JsonPropertyName("status")]
public string? Status { get; set; }

[JsonPropertyName("network")]
public string? Network { get; set; }

[JsonPropertyName("dnd_active")]
public bool? DndActive { get; set; }

[JsonExtensionData]
public Dictionary<string, JsonElement>? AdditionalData { get; set; }
}
19 changes: 19 additions & 0 deletions src/Termii/Insights/GetBalanceResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Termii;

public sealed class GetBalanceResponse
{
[JsonPropertyName("user")]
public string? User { get; set; }

[JsonPropertyName("balance")]
public decimal? Balance { get; set; }

[JsonPropertyName("currency")]
public string? Currency { get; set; }

[JsonExtensionData]
public Dictionary<string, JsonElement>? AdditionalData { get; set; }
}
35 changes: 35 additions & 0 deletions src/Termii/Insights/GetMessageHistoryRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
namespace Termii;

public sealed class GetMessageHistoryRequest
{
public int? Page { get; set; }

public int? Size { get; set; }

public string? Sender { get; set; }

public string? Receiver { get; set; }

public string? MessageId { get; set; }

public string? Status { get; set; }

public string? FromDate { get; set; }

public string? ToDate { get; set; }

internal string ToPath()
{
var query = new List<string>();
TermiiInsightsQueryString.AddIfPresent(query, "page", Page);
TermiiInsightsQueryString.AddIfPresent(query, "size", Size);
TermiiInsightsQueryString.AddIfPresent(query, "sender", Sender);
TermiiInsightsQueryString.AddIfPresent(query, "receiver", Receiver);
TermiiInsightsQueryString.AddIfPresent(query, "message_id", MessageId);
TermiiInsightsQueryString.AddIfPresent(query, "status", Status);
TermiiInsightsQueryString.AddIfPresent(query, "from", FromDate);
TermiiInsightsQueryString.AddIfPresent(query, "to", ToDate);

return TermiiInsightsQueryString.Append("/api/sms/inbox", query);
}
}
18 changes: 18 additions & 0 deletions src/Termii/Insights/ITermiiInsightsClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Termii;

public interface ITermiiInsightsClient
{
Task<GetBalanceResponse> GetBalanceAsync(CancellationToken cancellationToken = default);

Task<CheckDndResponse> CheckDndAsync(
CheckDndRequest request,
CancellationToken cancellationToken = default);

Task<QueryNumberResponse> QueryNumberAsync(
QueryNumberRequest request,
CancellationToken cancellationToken = default);

Task<MessageHistoryResponse> GetMessageHistoryAsync(
GetMessageHistoryRequest? request = null,
CancellationToken cancellationToken = default);
}
43 changes: 43 additions & 0 deletions src/Termii/Insights/MessageHistoryRecord.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Termii;

public sealed class MessageHistoryRecord
{
[JsonPropertyName("message_id")]
public string? MessageId { get; set; }

[JsonPropertyName("sender")]
public string? Sender { get; set; }

[JsonPropertyName("receiver")]
public string? Receiver { get; set; }

[JsonPropertyName("message")]
public string? Message { get; set; }

[JsonPropertyName("amount")]
public decimal? Amount { get; set; }

[JsonPropertyName("reroute")]
public int? Reroute { get; set; }

[JsonPropertyName("status")]
public string? Status { get; set; }

[JsonPropertyName("sms_type")]
public string? SmsType { get; set; }

[JsonPropertyName("send_by")]
public string? SentBy { get; set; }

[JsonPropertyName("created_at")]
public string? CreatedAt { get; set; }

[JsonPropertyName("updated_at")]
public string? UpdatedAt { get; set; }

[JsonExtensionData]
public Dictionary<string, JsonElement>? AdditionalData { get; set; }
}
34 changes: 34 additions & 0 deletions src/Termii/Insights/MessageHistoryResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Termii;

public sealed class MessageHistoryResponse
{
[JsonPropertyName("content")]
public IReadOnlyList<MessageHistoryRecord> Content { get; set; } = Array.Empty<MessageHistoryRecord>();

[JsonPropertyName("totalElements")]
public int? TotalElements { get; set; }

[JsonPropertyName("totalPages")]
public int? TotalPages { get; set; }

[JsonPropertyName("last")]
public bool? Last { get; set; }

[JsonPropertyName("size")]
public int? Size { get; set; }

[JsonPropertyName("number")]
public int? Number { get; set; }

[JsonPropertyName("first")]
public bool? First { get; set; }

[JsonPropertyName("empty")]
public bool? Empty { get; set; }

[JsonExtensionData]
public Dictionary<string, JsonElement>? AdditionalData { get; set; }
}
19 changes: 19 additions & 0 deletions src/Termii/Insights/QueryNumberRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace Termii;

public sealed class QueryNumberRequest
{
public string PhoneNumber { get; set; } = string.Empty;

public string? CountryCode { get; set; }

internal string ToPath()
{
TermiiRequestValidation.Required(PhoneNumber, nameof(PhoneNumber));

var query = new List<string>();
TermiiInsightsQueryString.AddIfPresent(query, "phone_number", PhoneNumber);
TermiiInsightsQueryString.AddIfPresent(query, "country_code", CountryCode);

return TermiiInsightsQueryString.Append("/api/insight/number/query", query);
}
}
37 changes: 37 additions & 0 deletions src/Termii/Insights/QueryNumberResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Termii;

public sealed class QueryNumberResponse
{
[JsonPropertyName("number")]
public string? Number { get; set; }

[JsonPropertyName("phone_number")]
public string? PhoneNumber { get; set; }

[JsonPropertyName("status")]
public string? Status { get; set; }

[JsonPropertyName("network")]
public string? Network { get; set; }

[JsonPropertyName("network_code")]
public string? NetworkCode { get; set; }

[JsonPropertyName("country_code")]
public string? CountryCode { get; set; }

[JsonPropertyName("country_name")]
public string? CountryName { get; set; }

[JsonPropertyName("ported")]
public bool? Ported { get; set; }

[JsonPropertyName("roaming")]
public bool? Roaming { get; set; }

[JsonExtensionData]
public Dictionary<string, JsonElement>? AdditionalData { get; set; }
}
67 changes: 67 additions & 0 deletions src/Termii/Insights/TermiiInsightsClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
namespace Termii;

internal sealed class TermiiInsightsClient : ITermiiInsightsClient
{
private readonly TermiiJsonHttpPipeline _pipeline;

public TermiiInsightsClient(TermiiJsonHttpPipeline pipeline)
{
_pipeline = pipeline ?? throw new ArgumentNullException(nameof(pipeline));
}

public Task<GetBalanceResponse> GetBalanceAsync(CancellationToken cancellationToken = default)
{
return _pipeline.SendJsonAsync<GetBalanceResponse>(
HttpMethod.Get,
"/api/get-balance",
body: null,
TermiiAuthenticationLocation.Query,
cancellationToken);
}

public Task<CheckDndResponse> CheckDndAsync(
CheckDndRequest request,
CancellationToken cancellationToken = default)
{
if (request is null)
{
throw new ArgumentNullException(nameof(request));
}

return _pipeline.SendJsonAsync<CheckDndResponse>(
HttpMethod.Get,
request.ToPath(),
body: null,
TermiiAuthenticationLocation.Query,
cancellationToken);
}

public Task<QueryNumberResponse> QueryNumberAsync(
QueryNumberRequest request,
CancellationToken cancellationToken = default)
{
if (request is null)
{
throw new ArgumentNullException(nameof(request));
}

return _pipeline.SendJsonAsync<QueryNumberResponse>(
HttpMethod.Get,
request.ToPath(),
body: null,
TermiiAuthenticationLocation.Query,
cancellationToken);
}

public Task<MessageHistoryResponse> GetMessageHistoryAsync(
GetMessageHistoryRequest? request = null,
CancellationToken cancellationToken = default)
{
return _pipeline.SendJsonAsync<MessageHistoryResponse>(
HttpMethod.Get,
(request ?? new GetMessageHistoryRequest()).ToPath(),
body: null,
TermiiAuthenticationLocation.Query,
cancellationToken);
}
}
27 changes: 27 additions & 0 deletions src/Termii/Insights/TermiiInsightsQueryString.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace Termii;

internal static class TermiiInsightsQueryString
{
public static void AddIfPresent(List<string> query, string name, string? value)
{
if (!string.IsNullOrWhiteSpace(value))
{
query.Add($"{name}={Uri.EscapeDataString(value)}");
}
}

public static void AddIfPresent(List<string> query, string name, int? value)
{
if (value.HasValue)
{
query.Add($"{name}={value.Value}");
}
}

public static string Append(string path, List<string> query)
{
return query.Count == 0
? path
: $"{path}?{string.Join("&", query)}";
}
}
Loading
Loading