diff --git a/src/Resend/Automation.cs b/src/Resend/Automation.cs new file mode 100644 index 0000000..b28df6f --- /dev/null +++ b/src/Resend/Automation.cs @@ -0,0 +1,59 @@ +using System.Text.Json.Serialization; + +namespace Resend; + +/// +/// Full automation resource including graph details. +/// +public class Automation +{ + /// + /// Object type discriminator. + /// + [JsonPropertyName( "object" )] + public string Object { get; set; } = default!; + + /// + /// Automation identifier. + /// + [JsonPropertyName( "id" )] + public Guid Id { get; set; } + + /// + /// Display name. + /// + [JsonPropertyName( "name" )] + public string Name { get; set; } = default!; + + /// + /// Either enabled or disabled. + /// + [JsonPropertyName( "status" )] + public string Status { get; set; } = default!; + + /// + /// When the automation was created. + /// + [JsonPropertyName( "created_at" )] + [JsonConverter( typeof( JsonUtcDateTimeConverter ) )] + public DateTime MomentCreated { get; set; } + + /// + /// When the automation was last updated. + /// + [JsonPropertyName( "updated_at" )] + [JsonConverter( typeof( JsonUtcDateTimeConverter ) )] + public DateTime MomentUpdated { get; set; } + + /// + /// Steps in execution order as stored by the API. + /// + [JsonPropertyName( "steps" )] + public List Steps { get; set; } = default!; + + /// + /// Edges connecting steps. + /// + [JsonPropertyName( "edges" )] + public List Edges { get; set; } = default!; +} diff --git a/src/Resend/AutomationCreateData.cs b/src/Resend/AutomationCreateData.cs new file mode 100644 index 0000000..97cb85e --- /dev/null +++ b/src/Resend/AutomationCreateData.cs @@ -0,0 +1,34 @@ +using System.Text.Json.Serialization; + +namespace Resend; + +/// +/// Request body to create an automation. +/// +public class AutomationCreateData +{ + /// + /// Display name. + /// + [JsonPropertyName( "name" )] + public string Name { get; set; } = default!; + + /// + /// Either enabled or disabled. Defaults to disabled when omitted. + /// + [JsonPropertyName( "status" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public string? Status { get; set; } + + /// + /// Steps; must include at least one trigger step. + /// + [JsonPropertyName( "steps" )] + public List Steps { get; set; } = default!; + + /// + /// Edges between steps (may be empty for single-step automations). + /// + [JsonPropertyName( "edges" )] + public List Edges { get; set; } = default!; +} diff --git a/src/Resend/AutomationDeleteResult.cs b/src/Resend/AutomationDeleteResult.cs new file mode 100644 index 0000000..f2fff06 --- /dev/null +++ b/src/Resend/AutomationDeleteResult.cs @@ -0,0 +1,27 @@ +using System.Text.Json.Serialization; + +namespace Resend; + +/// +/// Response from deleting an automation. +/// +public class AutomationDeleteResult +{ + /// + /// Object type discriminator. + /// + [JsonPropertyName( "object" )] + public string Object { get; set; } = default!; + + /// + /// Automation identifier. + /// + [JsonPropertyName( "id" )] + public Guid Id { get; set; } + + /// + /// Whether the automation was deleted. + /// + [JsonPropertyName( "deleted" )] + public bool Deleted { get; set; } +} diff --git a/src/Resend/AutomationEdge.cs b/src/Resend/AutomationEdge.cs new file mode 100644 index 0000000..ac19d5d --- /dev/null +++ b/src/Resend/AutomationEdge.cs @@ -0,0 +1,32 @@ +using System.Text.Json.Serialization; + +namespace Resend; + +/// +/// Connection between two automation steps. +/// +/// +/// On create and update, and are step refs. +/// On retrieve, they are internal step identifiers. +/// +public class AutomationEdge +{ + /// + /// Source step ref or internal id. + /// + [JsonPropertyName( "from" )] + public string From { get; set; } = default!; + + /// + /// Destination step ref or internal id. + /// + [JsonPropertyName( "to" )] + public string To { get; set; } = default!; + + /// + /// Edge kind: default, condition_met, condition_not_met, timeout, event_received. + /// + [JsonPropertyName( "edge_type" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public string? EdgeType { get; set; } +} diff --git a/src/Resend/AutomationListQuery.cs b/src/Resend/AutomationListQuery.cs new file mode 100644 index 0000000..4c71bbc --- /dev/null +++ b/src/Resend/AutomationListQuery.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace Resend; + +/// +/// Query parameters for . +/// +public class AutomationListQuery : PaginatedQuery +{ + /// + /// Filter by enabled or disabled. + /// + [JsonIgnore] + public string? Status { get; set; } +} diff --git a/src/Resend/AutomationRun.cs b/src/Resend/AutomationRun.cs new file mode 100644 index 0000000..0ce24b9 --- /dev/null +++ b/src/Resend/AutomationRun.cs @@ -0,0 +1,54 @@ +using System.Text.Json.Serialization; + +namespace Resend; + +/// +/// A single automation run with per-step execution details. +/// +public class AutomationRun +{ + /// + /// Object type discriminator. + /// + [JsonPropertyName( "object" )] + public string Object { get; set; } = default!; + + /// + /// Run identifier. + /// + [JsonPropertyName( "id" )] + public Guid Id { get; set; } + + /// + /// Run status. + /// + [JsonPropertyName( "status" )] + public string Status { get; set; } = default!; + + /// + /// When the run started. + /// + [JsonPropertyName( "started_at" )] + [JsonConverter( typeof( JsonUtcDateTimeConverter ) )] + public DateTime MomentStarted { get; set; } + + /// + /// When the run finished, if applicable. + /// + [JsonPropertyName( "completed_at" )] + [JsonConverter( typeof( JsonUtcDateTimeConverter ) )] + public DateTime? MomentCompleted { get; set; } + + /// + /// When the run record was created. + /// + [JsonPropertyName( "created_at" )] + [JsonConverter( typeof( JsonUtcDateTimeConverter ) )] + public DateTime MomentCreated { get; set; } + + /// + /// Per-step execution details. + /// + [JsonPropertyName( "steps" )] + public List Steps { get; set; } = default!; +} diff --git a/src/Resend/AutomationRunListQuery.cs b/src/Resend/AutomationRunListQuery.cs new file mode 100644 index 0000000..2572d33 --- /dev/null +++ b/src/Resend/AutomationRunListQuery.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace Resend; + +/// +/// Query parameters for . +/// +public class AutomationRunListQuery : PaginatedQuery +{ + /// + /// Comma-separated run statuses: running, completed, failed, cancelled. + /// + [JsonIgnore] + public string? Status { get; set; } +} diff --git a/src/Resend/AutomationRunStep.cs b/src/Resend/AutomationRunStep.cs new file mode 100644 index 0000000..0512cd3 --- /dev/null +++ b/src/Resend/AutomationRunStep.cs @@ -0,0 +1,57 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Resend; + +/// +/// Execution record for one step within an automation run. +/// +public class AutomationRunStep +{ + /// + /// Step type. + /// + [JsonPropertyName( "type" )] + public string Type { get; set; } = default!; + + /// + /// Step execution status. + /// + [JsonPropertyName( "status" )] + public string Status { get; set; } = default!; + + /// + /// When the step started. + /// + [JsonPropertyName( "started_at" )] + [JsonConverter( typeof( JsonUtcDateTimeConverter ) )] + public DateTime MomentStarted { get; set; } + + /// + /// When the step finished, if applicable. + /// + [JsonPropertyName( "completed_at" )] + [JsonConverter( typeof( JsonUtcDateTimeConverter ) )] + public DateTime? MomentCompleted { get; set; } + + /// + /// Step output payload when present. + /// + [JsonPropertyName( "output" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public JsonElement? Output { get; set; } + + /// + /// Error details when the step failed. + /// + [JsonPropertyName( "error" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public JsonElement? Error { get; set; } + + /// + /// When the step record was created. + /// + [JsonPropertyName( "created_at" )] + [JsonConverter( typeof( JsonUtcDateTimeConverter ) )] + public DateTime MomentCreated { get; set; } +} diff --git a/src/Resend/AutomationRunSummary.cs b/src/Resend/AutomationRunSummary.cs new file mode 100644 index 0000000..4175583 --- /dev/null +++ b/src/Resend/AutomationRunSummary.cs @@ -0,0 +1,42 @@ +using System.Text.Json.Serialization; + +namespace Resend; + +/// +/// A single automation run in a list response. +/// +public class AutomationRunSummary +{ + /// + /// Run identifier. + /// + [JsonPropertyName( "id" )] + public Guid Id { get; set; } + + /// + /// Run status. + /// + [JsonPropertyName( "status" )] + public string Status { get; set; } = default!; + + /// + /// When the run started. + /// + [JsonPropertyName( "started_at" )] + [JsonConverter( typeof( JsonUtcDateTimeConverter ) )] + public DateTime MomentStarted { get; set; } + + /// + /// When the run finished, if applicable. + /// + [JsonPropertyName( "completed_at" )] + [JsonConverter( typeof( JsonUtcDateTimeConverter ) )] + public DateTime? MomentCompleted { get; set; } + + /// + /// When the run record was created. + /// + [JsonPropertyName( "created_at" )] + [JsonConverter( typeof( JsonUtcDateTimeConverter ) )] + public DateTime MomentCreated { get; set; } +} diff --git a/src/Resend/AutomationStep.cs b/src/Resend/AutomationStep.cs new file mode 100644 index 0000000..20c1495 --- /dev/null +++ b/src/Resend/AutomationStep.cs @@ -0,0 +1,22 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Resend; + +/// +/// A step returned when retrieving an automation (responses omit per-step ref). +/// +public class AutomationStep +{ + /// + /// Step type. + /// + [JsonPropertyName( "type" )] + public string Type { get; set; } = default!; + + /// + /// Step configuration; shape depends on . + /// + [JsonPropertyName( "config" )] + public JsonElement Config { get; set; } +} diff --git a/src/Resend/AutomationStepData.cs b/src/Resend/AutomationStepData.cs new file mode 100644 index 0000000..60ab042 --- /dev/null +++ b/src/Resend/AutomationStepData.cs @@ -0,0 +1,28 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Resend; + +/// +/// A step in an automation graph for create and update requests. +/// +public class AutomationStepData +{ + /// + /// Unique reference for this step, used by and . + /// + [JsonPropertyName( "key" )] + public string Ref { get; set; } = default!; + + /// + /// Step type (for example trigger, send_email, delay). + /// + [JsonPropertyName( "type" )] + public string Type { get; set; } = default!; + + /// + /// Step configuration; shape depends on . + /// + [JsonPropertyName( "config" )] + public JsonElement Config { get; set; } +} diff --git a/src/Resend/AutomationStopResult.cs b/src/Resend/AutomationStopResult.cs new file mode 100644 index 0000000..04fe55d --- /dev/null +++ b/src/Resend/AutomationStopResult.cs @@ -0,0 +1,27 @@ +using System.Text.Json.Serialization; + +namespace Resend; + +/// +/// Response from stopping an automation run (automation is set to disabled). +/// +public class AutomationStopResult +{ + /// + /// Object type discriminator. + /// + [JsonPropertyName( "object" )] + public string Object { get; set; } = default!; + + /// + /// Automation identifier. + /// + [JsonPropertyName( "id" )] + public Guid Id { get; set; } + + /// + /// Updated status (for example disabled). + /// + [JsonPropertyName( "status" )] + public string Status { get; set; } = default!; +} diff --git a/src/Resend/AutomationSummary.cs b/src/Resend/AutomationSummary.cs new file mode 100644 index 0000000..9af2cb9 --- /dev/null +++ b/src/Resend/AutomationSummary.cs @@ -0,0 +1,41 @@ +using System.Text.Json.Serialization; + +namespace Resend; + +/// +/// Automation summary returned when listing automations. +/// +public class AutomationSummary +{ + /// + /// Automation identifier. + /// + [JsonPropertyName( "id" )] + public Guid Id { get; set; } + + /// + /// Display name. + /// + [JsonPropertyName( "name" )] + public string Name { get; set; } = default!; + + /// + /// Either enabled or disabled. + /// + [JsonPropertyName( "status" )] + public string Status { get; set; } = default!; + + /// + /// When the automation was created. + /// + [JsonPropertyName( "created_at" )] + [JsonConverter( typeof( JsonUtcDateTimeConverter ) )] + public DateTime MomentCreated { get; set; } + + /// + /// When the automation was last updated. + /// + [JsonPropertyName( "updated_at" )] + [JsonConverter( typeof( JsonUtcDateTimeConverter ) )] + public DateTime MomentUpdated { get; set; } +} diff --git a/src/Resend/AutomationUpdateData.cs b/src/Resend/AutomationUpdateData.cs new file mode 100644 index 0000000..059528f --- /dev/null +++ b/src/Resend/AutomationUpdateData.cs @@ -0,0 +1,38 @@ +using System.Text.Json.Serialization; + +namespace Resend; + +/// +/// Request body to update an automation. Provide at least one field; when updating the graph, +/// send and together. +/// +public class AutomationUpdateData +{ + /// + /// Display name. + /// + [JsonPropertyName( "name" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public string? Name { get; set; } + + /// + /// Either enabled or disabled. + /// + [JsonPropertyName( "status" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public string? Status { get; set; } + + /// + /// Replacement step list when updating the graph. + /// + [JsonPropertyName( "steps" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public List? Steps { get; set; } + + /// + /// Replacement edge list when updating the graph. + /// + [JsonPropertyName( "edges" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public List? Edges { get; set; } +} diff --git a/src/Resend/EventCreateData.cs b/src/Resend/EventCreateData.cs new file mode 100644 index 0000000..476def3 --- /dev/null +++ b/src/Resend/EventCreateData.cs @@ -0,0 +1,23 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Resend; + +/// +/// Request body to create an event definition. +/// +public class EventCreateData +{ + /// + /// Event name (for example user.created). + /// + [JsonPropertyName( "name" )] + public string Name { get; set; } = default!; + + /// + /// Optional payload schema as flat key/type pairs. + /// + [JsonPropertyName( "schema" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public JsonElement? Schema { get; set; } +} diff --git a/src/Resend/EventResource.cs b/src/Resend/EventResource.cs new file mode 100644 index 0000000..eb23c34 --- /dev/null +++ b/src/Resend/EventResource.cs @@ -0,0 +1,49 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Resend; + +/// +/// A named event definition (schema and metadata) used with automations. +/// +public class EventResource +{ + /// + /// Object type discriminator. + /// + [JsonPropertyName( "object" )] + public string Object { get; set; } = default!; + + /// + /// Event identifier. + /// + [JsonPropertyName( "id" )] + public Guid Id { get; set; } + + /// + /// Event name (matches trigger configuration). + /// + [JsonPropertyName( "name" )] + public string Name { get; set; } = default!; + + /// + /// Optional payload schema as flat key/type pairs (values: string, number, boolean, date). + /// + [JsonPropertyName( "schema" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public JsonElement? Schema { get; set; } + + /// + /// When the event was created. + /// + [JsonPropertyName( "created_at" )] + [JsonConverter( typeof( JsonUtcDateTimeConverter ) )] + public DateTime MomentCreated { get; set; } + + /// + /// When the event was last updated. + /// + [JsonPropertyName( "updated_at" )] + [JsonConverter( typeof( JsonUtcDateTimeConverter ) )] + public DateTime MomentUpdated { get; set; } +} diff --git a/src/Resend/EventSendData.cs b/src/Resend/EventSendData.cs new file mode 100644 index 0000000..f4c0078 --- /dev/null +++ b/src/Resend/EventSendData.cs @@ -0,0 +1,40 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Resend; + +/// +/// Request body to send a named event (for example to trigger automations). +/// +/// +/// Provide exactly one of or . +/// +public class EventSendData +{ + /// + /// Event name (must match a trigger step). + /// + [JsonPropertyName( "event" )] + public string Event { get; set; } = default!; + + /// + /// Contact to associate with the event. + /// + [JsonPropertyName( "contact_id" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public Guid? ContactId { get; set; } + + /// + /// Email address to associate with the event. + /// + [JsonPropertyName( "email" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public string? Email { get; set; } + + /// + /// Optional payload passed to templates and conditions. + /// + [JsonPropertyName( "payload" )] + [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] + public JsonElement? Payload { get; set; } +} diff --git a/src/Resend/EventSendResult.cs b/src/Resend/EventSendResult.cs new file mode 100644 index 0000000..4afb4f1 --- /dev/null +++ b/src/Resend/EventSendResult.cs @@ -0,0 +1,21 @@ +using System.Text.Json.Serialization; + +namespace Resend; + +/// +/// Response from accepting an event (HTTP 202). +/// +public class EventSendResult +{ + /// + /// Object type discriminator. + /// + [JsonPropertyName( "object" )] + public string Object { get; set; } = default!; + + /// + /// Event name that was accepted. + /// + [JsonPropertyName( "event" )] + public string Event { get; set; } = default!; +} diff --git a/src/Resend/EventUpdateData.cs b/src/Resend/EventUpdateData.cs new file mode 100644 index 0000000..119d1cb --- /dev/null +++ b/src/Resend/EventUpdateData.cs @@ -0,0 +1,16 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Resend; + +/// +/// Request body to update an event definition (typically the payload schema). +/// +public class EventUpdateData +{ + /// + /// Payload schema as flat key/type pairs; include null to clear the schema. + /// + [JsonPropertyName( "schema" )] + public JsonElement? Schema { get; set; } +} diff --git a/src/Resend/IResend.cs b/src/Resend/IResend.cs index e4de213..e892165 100644 --- a/src/Resend/IResend.cs +++ b/src/Resend/IResend.cs @@ -986,4 +986,166 @@ public interface IResend Task> LogRetrieveAsync( Guid logId, CancellationToken cancellationToken = default ); #endregion + + #region Automations and events + + /// + /// Creates an automation. + /// + /// Automation definition. + /// Cancellation token. + /// New automation identifier. + /// + Task> AutomationCreateAsync( AutomationCreateData data, CancellationToken cancellationToken = default ); + + /// + /// Updates an automation. + /// + /// Automation identifier. + /// Fields to update. + /// Cancellation token. + /// Automation identifier. + /// + Task> AutomationUpdateAsync( Guid automationId, AutomationUpdateData data, CancellationToken cancellationToken = default ); + + /// + /// Retrieves an automation by identifier. + /// + /// Automation identifier. + /// Cancellation token. + /// Automation. + /// + Task> AutomationRetrieveAsync( Guid automationId, CancellationToken cancellationToken = default ); + + /// + /// Lists automations. + /// + /// Filters and pagination. + /// Cancellation token. + /// Page of automations. + /// + Task>> AutomationListAsync( AutomationListQuery? query = null, CancellationToken cancellationToken = default ); + + /// + /// Stops a running automation (sets status to disabled). + /// + /// Automation identifier. + /// Cancellation token. + /// Result including updated status. + /// + Task> AutomationStopAsync( Guid automationId, CancellationToken cancellationToken = default ); + + /// + /// Deletes an automation. + /// + /// Automation identifier. + /// Cancellation token. + /// Deletion confirmation. + /// + Task> AutomationDeleteAsync( Guid automationId, CancellationToken cancellationToken = default ); + + /// + /// Lists runs for an automation. + /// + /// Automation identifier. + /// Filters and pagination. + /// Cancellation token. + /// Page of runs. + /// + Task>> AutomationRunListAsync( Guid automationId, AutomationRunListQuery? query = null, CancellationToken cancellationToken = default ); + + /// + /// Retrieves a single automation run. + /// + /// Automation identifier. + /// Run identifier. + /// Cancellation token. + /// Run detail. + /// + Task> AutomationRunRetrieveAsync( Guid automationId, Guid runId, CancellationToken cancellationToken = default ); + + /// + /// Creates an event definition (name and optional payload schema). + /// + /// Event name and optional schema. + /// Cancellation token. + /// New event identifier (HTTP 201). + /// + Task> EventCreateAsync( EventCreateData data, CancellationToken cancellationToken = default ); + + /// + /// Retrieves an event by identifier. + /// + /// Event identifier. + /// Cancellation token. + /// Event definition. + /// + Task> EventRetrieveAsync( Guid eventId, CancellationToken cancellationToken = default ); + + /// + /// Retrieves an event by identifier or name. + /// + /// Event id (UUID) or name (for example user.created). + /// Cancellation token. + /// Event definition. + /// + Task> EventRetrieveAsync( string eventIdOrName, CancellationToken cancellationToken = default ); + + /// + /// Lists event definitions. + /// + /// Pagination query. + /// Cancellation token. + /// Page of events. + /// + Task>> EventListAsync( PaginatedQuery? query = null, CancellationToken cancellationToken = default ); + + /// + /// Updates an event definition (for example its payload schema). + /// + /// Event identifier. + /// Fields to update. + /// Cancellation token. + /// Response. + /// + Task EventUpdateAsync( Guid eventId, EventUpdateData data, CancellationToken cancellationToken = default ); + + /// + /// Updates an event definition by identifier or name. + /// + /// Event id (UUID) or name. + /// Fields to update. + /// Cancellation token. + /// Response. + /// + Task EventUpdateAsync( string eventIdOrName, EventUpdateData data, CancellationToken cancellationToken = default ); + + /// + /// Deletes an event definition. + /// + /// Event identifier. + /// Cancellation token. + /// Response. + /// + Task EventDeleteAsync( Guid eventId, CancellationToken cancellationToken = default ); + + /// + /// Deletes an event definition by identifier or name. + /// + /// Event id (UUID) or name. + /// Cancellation token. + /// Response. + /// + Task EventDeleteAsync( string eventIdOrName, CancellationToken cancellationToken = default ); + + /// + /// Sends a named event (for example to trigger automations). + /// + /// Event name, contact or email, and optional payload. + /// Cancellation token. + /// Accepted event metadata (HTTP 202). + /// + Task> EventSendAsync( EventSendData data, CancellationToken cancellationToken = default ); + + #endregion } \ No newline at end of file diff --git a/src/Resend/ResendClient.Automations.cs b/src/Resend/ResendClient.Automations.cs new file mode 100644 index 0000000..78a2101 --- /dev/null +++ b/src/Resend/ResendClient.Automations.cs @@ -0,0 +1,125 @@ +using Microsoft.AspNetCore.WebUtilities; +using Resend.Payloads; +using System.Net.Http.Json; + +namespace Resend; + +public partial class ResendClient +{ + /// + public Task> AutomationCreateAsync( AutomationCreateData data, CancellationToken cancellationToken = default ) + { + var req = new HttpRequestMessage( HttpMethod.Post, "/automations" ); + req.Content = JsonContent.Create( data ); + + return Execute( req, ( x ) => x.Id, cancellationToken ); + } + + + /// + public Task> AutomationUpdateAsync( Guid automationId, AutomationUpdateData data, CancellationToken cancellationToken = default ) + { + var req = new HttpRequestMessage( HttpMethod.Patch, $"/automations/{automationId}" ); + req.Content = JsonContent.Create( data ); + + return Execute( req, ( x ) => x.Id, cancellationToken ); + } + + + /// + public Task> AutomationRetrieveAsync( Guid automationId, CancellationToken cancellationToken = default ) + { + var req = new HttpRequestMessage( HttpMethod.Get, $"/automations/{automationId}" ); + + return Execute( req, ( x ) => x, cancellationToken ); + } + + + /// + public Task>> AutomationListAsync( AutomationListQuery? query = null, CancellationToken cancellationToken = default ) + { + var baseUrl = "/automations"; + var url = baseUrl; + + if ( query != null ) + { + var qs = new Dictionary(); + + if ( query.Limit.HasValue == true ) + qs.Add( "limit", query.Limit.Value.ToString() ); + + if ( query.Before != null ) + qs.Add( "before", query.Before ); + + if ( query.After != null ) + qs.Add( "after", query.After ); + + if ( query.Status != null ) + qs.Add( "status", query.Status ); + + url = QueryHelpers.AddQueryString( baseUrl, qs ); + } + + var req = new HttpRequestMessage( HttpMethod.Get, url ); + + return Execute, PaginatedResult>( req, ( x ) => x, cancellationToken ); + } + + + /// + public Task> AutomationStopAsync( Guid automationId, CancellationToken cancellationToken = default ) + { + var req = new HttpRequestMessage( HttpMethod.Post, $"/automations/{automationId}/stop" ); + + return Execute( req, ( x ) => x, cancellationToken ); + } + + + /// + public Task> AutomationDeleteAsync( Guid automationId, CancellationToken cancellationToken = default ) + { + var req = new HttpRequestMessage( HttpMethod.Delete, $"/automations/{automationId}" ); + + return Execute( req, ( x ) => x, cancellationToken ); + } + + + /// + public Task>> AutomationRunListAsync( Guid automationId, AutomationRunListQuery? query = null, CancellationToken cancellationToken = default ) + { + var baseUrl = $"/automations/{automationId}/runs"; + var url = baseUrl; + + if ( query != null ) + { + var qs = new Dictionary(); + + if ( query.Limit.HasValue == true ) + qs.Add( "limit", query.Limit.Value.ToString() ); + + if ( query.Before != null ) + qs.Add( "before", query.Before ); + + if ( query.After != null ) + qs.Add( "after", query.After ); + + if ( query.Status != null ) + qs.Add( "status", query.Status ); + + url = QueryHelpers.AddQueryString( baseUrl, qs ); + } + + var req = new HttpRequestMessage( HttpMethod.Get, url ); + + return Execute, PaginatedResult>( req, ( x ) => x, cancellationToken ); + } + + + /// + public Task> AutomationRunRetrieveAsync( Guid automationId, Guid runId, CancellationToken cancellationToken = default ) + { + var req = new HttpRequestMessage( HttpMethod.Get, $"/automations/{automationId}/runs/{runId}" ); + + return Execute( req, ( x ) => x, cancellationToken ); + } +} diff --git a/src/Resend/ResendClient.Events.cs b/src/Resend/ResendClient.Events.cs new file mode 100644 index 0000000..888cef0 --- /dev/null +++ b/src/Resend/ResendClient.Events.cs @@ -0,0 +1,111 @@ +using Microsoft.AspNetCore.WebUtilities; +using Resend.Payloads; +using System.Net.Http.Json; + +namespace Resend; + +public partial class ResendClient +{ + /// + public Task> EventCreateAsync( EventCreateData data, CancellationToken cancellationToken = default ) + { + var req = new HttpRequestMessage( HttpMethod.Post, "/events" ); + req.Content = JsonContent.Create( data ); + + return Execute( req, ( x ) => x.Id, cancellationToken ); + } + + + /// + public Task> EventRetrieveAsync( Guid eventId, CancellationToken cancellationToken = default ) + { + var req = new HttpRequestMessage( HttpMethod.Get, $"/events/{eventId}" ); + + return Execute( req, ( x ) => x, cancellationToken ); + } + + + /// + public Task> EventRetrieveAsync( string eventIdOrName, CancellationToken cancellationToken = default ) + { + var req = new HttpRequestMessage( HttpMethod.Get, $"/events/{Uri.EscapeDataString( eventIdOrName )}" ); + + return Execute( req, ( x ) => x, cancellationToken ); + } + + + /// + public Task>> EventListAsync( PaginatedQuery? query = null, CancellationToken cancellationToken = default ) + { + var baseUrl = "/events"; + var url = baseUrl; + + if ( query != null ) + { + var qs = new Dictionary(); + + if ( query.Limit.HasValue == true ) + qs.Add( "limit", query.Limit.Value.ToString() ); + + if ( query.Before != null ) + qs.Add( "before", query.Before ); + + if ( query.After != null ) + qs.Add( "after", query.After ); + + url = QueryHelpers.AddQueryString( baseUrl, qs ); + } + + var req = new HttpRequestMessage( HttpMethod.Get, url ); + + return Execute, PaginatedResult>( req, ( x ) => x, cancellationToken ); + } + + + /// + public Task EventUpdateAsync( Guid eventId, EventUpdateData data, CancellationToken cancellationToken = default ) + { + var req = new HttpRequestMessage( HttpMethod.Patch, $"/events/{eventId}" ); + req.Content = JsonContent.Create( data ); + + return Execute( req, cancellationToken ); + } + + + /// + public Task EventUpdateAsync( string eventIdOrName, EventUpdateData data, CancellationToken cancellationToken = default ) + { + var req = new HttpRequestMessage( HttpMethod.Patch, $"/events/{Uri.EscapeDataString( eventIdOrName )}" ); + req.Content = JsonContent.Create( data ); + + return Execute( req, cancellationToken ); + } + + + /// + public Task EventDeleteAsync( Guid eventId, CancellationToken cancellationToken = default ) + { + var req = new HttpRequestMessage( HttpMethod.Delete, $"/events/{eventId}" ); + + return Execute( req, cancellationToken ); + } + + + /// + public Task EventDeleteAsync( string eventIdOrName, CancellationToken cancellationToken = default ) + { + var req = new HttpRequestMessage( HttpMethod.Delete, $"/events/{Uri.EscapeDataString( eventIdOrName )}" ); + + return Execute( req, cancellationToken ); + } + + + /// + public Task> EventSendAsync( EventSendData data, CancellationToken cancellationToken = default ) + { + var req = new HttpRequestMessage( HttpMethod.Post, "/events/send" ); + req.Content = JsonContent.Create( data ); + + return Execute( req, ( x ) => x, cancellationToken ); + } +} diff --git a/tests/Resend.Tests/ResendClientTests.Automations.cs b/tests/Resend.Tests/ResendClientTests.Automations.cs new file mode 100644 index 0000000..6c444f3 --- /dev/null +++ b/tests/Resend.Tests/ResendClientTests.Automations.cs @@ -0,0 +1,175 @@ +using System.Text.Json; + +namespace Resend.Tests; + +/// +public partial class ResendClientTests +{ + /// + [Fact] + public async Task AutomationCreate() + { + var resp = await _resend.AutomationCreateAsync( new AutomationCreateData() + { + Name = "Welcome series", + Steps = + [ + new AutomationStepData() + { + Ref = "trigger", + Type = "trigger", + Config = JsonDocument.Parse( "{\"event_name\":\"user.created\"}" ).RootElement, + }, + ], + Edges = [], + } ); + + Assert.NotNull( resp ); + Assert.True( resp.Success ); + Assert.NotEqual( Guid.Empty, resp.Content ); + } + + + /// + [Fact] + public async Task AutomationUpdate() + { + var id = Guid.NewGuid(); + + var resp = await _resend.AutomationUpdateAsync( id, new AutomationUpdateData() + { + Status = "enabled", + } ); + + Assert.NotNull( resp ); + Assert.True( resp.Success ); + Assert.Equal( id, resp.Content ); + } + + + /// + [Fact] + public async Task AutomationRetrieve() + { + var id = Guid.Parse( "c9b16d4f-ba6c-4e2e-b044-6bf4404e57fd" ); + + var resp = await _resend.AutomationRetrieveAsync( id ); + + Assert.NotNull( resp ); + Assert.True( resp.Success ); + Assert.Equal( id, resp.Content.Id ); + Assert.Equal( "Welcome series", resp.Content.Name ); + Assert.Equal( "trigger", resp.Content.Steps[ 0 ].Type ); + } + + + /// + [Fact] + public async Task AutomationList() + { + var resp = await _resend.AutomationListAsync(); + + Assert.NotNull( resp ); + Assert.True( resp.Success ); + Assert.NotNull( resp.Content ); + Assert.False( resp.Content.HasMore ); + Assert.Equal( 2, resp.Content.Data.Count ); + Assert.Equal( "Welcome series", resp.Content.Data[ 0 ].Name ); + } + + + /// + [Fact] + public async Task AutomationList_WithQuery() + { + var resp = await _resend.AutomationListAsync( new AutomationListQuery() + { + Limit = 20, + Status = "enabled", + After = Guid.NewGuid().ToString(), + } ); + + Assert.NotNull( resp ); + Assert.True( resp.Success ); + Assert.NotNull( resp.Content ); + } + + + /// + [Fact] + public async Task AutomationStop() + { + var id = Guid.NewGuid(); + + var resp = await _resend.AutomationStopAsync( id ); + + Assert.NotNull( resp ); + Assert.True( resp.Success ); + Assert.Equal( id, resp.Content.Id ); + Assert.Equal( "disabled", resp.Content.Status ); + } + + + /// + [Fact] + public async Task AutomationDelete() + { + var id = Guid.NewGuid(); + + var resp = await _resend.AutomationDeleteAsync( id ); + + Assert.NotNull( resp ); + Assert.True( resp.Success ); + Assert.True( resp.Content.Deleted ); + } + + + /// + [Fact] + public async Task AutomationRunList() + { + var automationId = Guid.NewGuid(); + + var resp = await _resend.AutomationRunListAsync( automationId ); + + Assert.NotNull( resp ); + Assert.True( resp.Success ); + Assert.Equal( 2, resp.Content.Data.Count ); + Assert.Equal( "completed", resp.Content.Data[ 0 ].Status ); + Assert.Null( resp.Content.Data[ 1 ].MomentCompleted ); + } + + + /// + [Fact] + public async Task AutomationRunList_WithQuery() + { + var automationId = Guid.NewGuid(); + + var resp = await _resend.AutomationRunListAsync( automationId, new AutomationRunListQuery() + { + Limit = 10, + Status = "running,completed", + } ); + + Assert.NotNull( resp ); + Assert.True( resp.Success ); + } + + + /// + [Fact] + public async Task AutomationRunRetrieve() + { + var automationId = Guid.NewGuid(); + var runId = Guid.Parse( "a1b2c3d4-e5f6-7890-abcd-ef1234567890" ); + + var resp = await _resend.AutomationRunRetrieveAsync( automationId, runId ); + + Assert.NotNull( resp ); + Assert.True( resp.Success ); + Assert.Equal( runId, resp.Content.Id ); + Assert.Equal( 2, resp.Content.Steps.Count ); + Assert.Equal( "trigger", resp.Content.Steps[ 0 ].Type ); + } +} diff --git a/tests/Resend.Tests/ResendClientTests.Events.cs b/tests/Resend.Tests/ResendClientTests.Events.cs new file mode 100644 index 0000000..eef7a73 --- /dev/null +++ b/tests/Resend.Tests/ResendClientTests.Events.cs @@ -0,0 +1,150 @@ +using System.Text.Json; + +namespace Resend.Tests; + +/// +public partial class ResendClientTests +{ + /// + [Fact] + public async Task EventCreate() + { + var resp = await _resend.EventCreateAsync( new EventCreateData() + { + Name = "user.created", + Schema = JsonDocument.Parse( "{\"plan\":\"string\"}" ).RootElement, + } ); + + Assert.NotNull( resp ); + Assert.True( resp.Success ); + Assert.NotEqual( Guid.Empty, resp.Content ); + } + + + /// + [Fact] + public async Task EventRetrieve_ByGuid() + { + var id = Guid.Parse( "a1b2c3d4-e5f6-7890-abcd-ef1234567890" ); + + var resp = await _resend.EventRetrieveAsync( id ); + + Assert.NotNull( resp ); + Assert.True( resp.Success ); + Assert.Equal( id, resp.Content.Id ); + Assert.Equal( "user.created", resp.Content.Name ); + } + + + /// + [Fact] + public async Task EventRetrieve_ByName() + { + var resp = await _resend.EventRetrieveAsync( "user.created" ); + + Assert.NotNull( resp ); + Assert.True( resp.Success ); + Assert.Equal( "user.created", resp.Content.Name ); + } + + + /// + [Fact] + public async Task EventList() + { + var resp = await _resend.EventListAsync(); + + Assert.NotNull( resp ); + Assert.True( resp.Success ); + Assert.Equal( 2, resp.Content.Data.Count ); + Assert.Equal( "user.created", resp.Content.Data[ 0 ].Name ); + Assert.Null( resp.Content.Data[ 1 ].Schema ); + } + + + /// + [Fact] + public async Task EventList_WithPagination() + { + var resp = await _resend.EventListAsync( new PaginatedQuery() + { + Limit = 20, + After = Guid.NewGuid().ToString(), + } ); + + Assert.NotNull( resp ); + Assert.True( resp.Success ); + } + + + /// + [Fact] + public async Task EventUpdate_ByName() + { + var resp = await _resend.EventUpdateAsync( "user.created", new EventUpdateData() + { + Schema = JsonDocument.Parse( "{\"plan\":\"string\",\"trial\":\"boolean\"}" ).RootElement, + } ); + + Assert.NotNull( resp ); + Assert.True( resp.Success ); + } + + + /// + [Fact] + public async Task EventUpdate_ByGuid() + { + var id = Guid.NewGuid(); + + var resp = await _resend.EventUpdateAsync( id, new EventUpdateData() + { + Schema = JsonDocument.Parse( "{\"plan\":\"string\"}" ).RootElement, + } ); + + Assert.NotNull( resp ); + Assert.True( resp.Success ); + } + + + /// + [Fact] + public async Task EventDelete_ByName() + { + var resp = await _resend.EventDeleteAsync( "user.created" ); + + Assert.NotNull( resp ); + Assert.True( resp.Success ); + } + + + /// + [Fact] + public async Task EventDelete_ByGuid() + { + var resp = await _resend.EventDeleteAsync( Guid.NewGuid() ); + + Assert.NotNull( resp ); + Assert.True( resp.Success ); + } + + + /// + [Fact] + public async Task EventSend_WithContactId() + { + var contactId = Guid.Parse( "7f2e4a3b-dfbc-4e9a-8b2c-5f3a1d6e7c8b" ); + + var resp = await _resend.EventSendAsync( new EventSendData() + { + Event = "user.created", + ContactId = contactId, + Payload = JsonDocument.Parse( "{\"plan\":\"pro\"}" ).RootElement, + } ); + + Assert.NotNull( resp ); + Assert.True( resp.Success ); + Assert.Equal( "user.created", resp.Content.Event ); + Assert.Equal( "event", resp.Content.Object ); + } +} diff --git a/tools/Resend.ApiServer/Controllers/AutomationController.cs b/tools/Resend.ApiServer/Controllers/AutomationController.cs new file mode 100644 index 0000000..2446e61 --- /dev/null +++ b/tools/Resend.ApiServer/Controllers/AutomationController.cs @@ -0,0 +1,251 @@ +using Microsoft.AspNetCore.Mvc; +using Resend; +using Resend.Payloads; +using System.Text.Json; + +namespace Resend.ApiServer.Controllers; + +/// +[ApiController] +public class AutomationController : ControllerBase +{ + private readonly ILogger _logger; + + + /// + public AutomationController( ILogger logger ) + { + _logger = logger; + } + + + /// + [HttpPost] + [Route( "automations" )] + public ObjectId AutomationCreate( [FromBody] AutomationCreateData request ) + { + _logger.LogDebug( "AutomationCreate" ); + + return new ObjectId() + { + Object = "automation", + Id = Guid.NewGuid(), + }; + } + + + /// + [HttpPatch] + [Route( "automations/{id}" )] + public ObjectId AutomationUpdate( [FromRoute] Guid id, [FromBody] AutomationUpdateData request ) + { + _logger.LogDebug( "AutomationUpdate" ); + + return new ObjectId() + { + Object = "automation", + Id = id, + }; + } + + + /// + [HttpGet] + [Route( "automations/{id}" )] + public Automation AutomationRetrieve( [FromRoute] Guid id ) + { + _logger.LogDebug( "AutomationRetrieve" ); + + var stepA = Guid.Parse( "a1b2c3d4-e5f6-7890-abcd-ef1234567890" ); + var stepB = Guid.Parse( "b2c3d4e5-f6a7-8901-bcde-f12345678901" ); + + return new Automation() + { + Object = "automation", + Id = id, + Name = "Welcome series", + Status = "enabled", + MomentCreated = DateTime.Parse( "2025-10-01 12:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ), + MomentUpdated = DateTime.Parse( "2025-10-01 12:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ), + Steps = + [ + new AutomationStep() + { + Type = "trigger", + Config = JsonDocument.Parse( "{\"event_name\":\"user.created\"}" ).RootElement, + }, + new AutomationStep() + { + Type = "send_email", + Config = JsonDocument.Parse( "{\"template_id\":\"tpl_xxxxxxxxx\",\"subject\":\"Welcome!\",\"from\":\"Acme \"}" ).RootElement, + }, + ], + Edges = + [ + new AutomationEdge() + { + From = stepA.ToString(), + To = stepB.ToString(), + EdgeType = "default", + }, + ], + }; + } + + + /// + [HttpGet] + [Route( "automations" )] + public PaginatedResult AutomationList( + [FromQuery] string? limit = null, + [FromQuery] string? before = null, + [FromQuery] string? after = null, + [FromQuery] string? status = null + ) + { + _logger.LogDebug( "AutomationList" ); + + var id1 = Guid.Parse( "c9b16d4f-ba6c-4e2e-b044-6bf4404e57fd" ); + var id2 = Guid.Parse( "662892b2-4270-4130-a186-33a19752319d" ); + + return new PaginatedResult() + { + HasMore = false, + Data = + [ + new AutomationSummary() + { + Id = id1, + Name = "Welcome series", + Status = "enabled", + MomentCreated = DateTime.Parse( "2025-10-01 12:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ), + MomentUpdated = DateTime.Parse( "2025-10-01 12:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ), + }, + new AutomationSummary() + { + Id = id2, + Name = "Re-engagement", + Status = "disabled", + MomentCreated = DateTime.Parse( "2025-09-15 08:30:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ), + MomentUpdated = DateTime.Parse( "2025-09-20 14:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ), + }, + ], + }; + } + + + /// + [HttpPost] + [Route( "automations/{id}/stop" )] + public AutomationStopResult AutomationStop( [FromRoute] Guid id ) + { + _logger.LogDebug( "AutomationStop" ); + + return new AutomationStopResult() + { + Object = "automation", + Id = id, + Status = "disabled", + }; + } + + + /// + [HttpDelete] + [Route( "automations/{id}" )] + public AutomationDeleteResult AutomationDelete( [FromRoute] Guid id ) + { + _logger.LogDebug( "AutomationDelete" ); + + return new AutomationDeleteResult() + { + Object = "automation", + Id = id, + Deleted = true, + }; + } + + + /// + [HttpGet] + [Route( "automations/{automationId}/runs" )] + public PaginatedResult AutomationRunList( + [FromRoute] Guid automationId, + [FromQuery] string? limit = null, + [FromQuery] string? before = null, + [FromQuery] string? after = null, + [FromQuery] string? status = null + ) + { + _logger.LogDebug( "AutomationRunList" ); + + var run1 = Guid.Parse( "a1b2c3d4-e5f6-7890-abcd-ef1234567890" ); + var run2 = Guid.Parse( "b2c3d4e5-f6a7-8901-bcde-f12345678901" ); + + return new PaginatedResult() + { + HasMore = false, + Data = + [ + new AutomationRunSummary() + { + Id = run1, + Status = "completed", + MomentStarted = DateTime.Parse( "2025-10-01 12:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ), + MomentCompleted = DateTime.Parse( "2025-10-01 12:05:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ), + MomentCreated = DateTime.Parse( "2025-10-01 12:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ), + }, + new AutomationRunSummary() + { + Id = run2, + Status = "running", + MomentStarted = DateTime.Parse( "2025-10-02 08:30:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ), + MomentCompleted = null, + MomentCreated = DateTime.Parse( "2025-10-02 08:30:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ), + }, + ], + }; + } + + + /// + [HttpGet] + [Route( "automations/{automationId}/runs/{runId}" )] + public AutomationRun AutomationRunRetrieve( [FromRoute] Guid automationId, [FromRoute] Guid runId ) + { + _logger.LogDebug( "AutomationRunRetrieve" ); + + return new AutomationRun() + { + Object = "automation_run", + Id = runId, + Status = "completed", + MomentStarted = DateTime.Parse( "2025-10-01 12:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ), + MomentCompleted = DateTime.Parse( "2025-10-01 12:05:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ), + MomentCreated = DateTime.Parse( "2025-10-01 12:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ), + Steps = + [ + new AutomationRunStep() + { + Type = "trigger", + Status = "completed", + MomentStarted = DateTime.Parse( "2025-10-01 12:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ), + MomentCompleted = DateTime.Parse( "2025-10-01 12:00:01.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ), + Output = null, + Error = null, + MomentCreated = DateTime.Parse( "2025-10-01 12:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ), + }, + new AutomationRunStep() + { + Type = "send_email", + Status = "completed", + MomentStarted = DateTime.Parse( "2025-10-01 12:00:01.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ), + MomentCompleted = DateTime.Parse( "2025-10-01 12:00:02.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ), + Output = null, + Error = null, + MomentCreated = DateTime.Parse( "2025-10-01 12:00:01.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ), + }, + ], + }; + } +} diff --git a/tools/Resend.ApiServer/Controllers/EventController.cs b/tools/Resend.ApiServer/Controllers/EventController.cs new file mode 100644 index 0000000..5e1d4f5 --- /dev/null +++ b/tools/Resend.ApiServer/Controllers/EventController.cs @@ -0,0 +1,137 @@ +using Microsoft.AspNetCore.Mvc; +using Resend; +using Resend.Payloads; +using System.Text.Json; + +namespace Resend.ApiServer.Controllers; + +/// +[ApiController] +public class EventController : ControllerBase +{ + private readonly ILogger _logger; + + + /// + public EventController( ILogger logger ) + { + _logger = logger; + } + + + /// + [HttpPost] + [Route( "events" )] + [ProducesResponseType( typeof( ObjectId ), StatusCodes.Status201Created )] + public IActionResult EventCreate( [FromBody] EventCreateData request ) + { + _logger.LogDebug( "EventCreate" ); + + return StatusCode( StatusCodes.Status201Created, new ObjectId() + { + Object = "event", + Id = Guid.NewGuid(), + } ); + } + + + /// + [HttpGet] + [Route( "events/{id}" )] + public EventResource EventRetrieve( [FromRoute] string id ) + { + _logger.LogDebug( "EventRetrieve" ); + + var isGuid = Guid.TryParse( id, out var eid ); + + return new EventResource() + { + Object = "event", + Id = isGuid ? eid : Guid.Parse( "a1b2c3d4-e5f6-7890-abcd-ef1234567890" ), + Name = isGuid ? "user.created" : id, + Schema = JsonDocument.Parse( "{\"plan\":\"string\"}" ).RootElement, + MomentCreated = DateTime.Parse( "2025-10-01 12:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ), + MomentUpdated = DateTime.Parse( "2025-10-01 12:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ), + }; + } + + + /// + [HttpGet] + [Route( "events" )] + public PaginatedResult EventList( + [FromQuery] string? limit = null, + [FromQuery] string? before = null, + [FromQuery] string? after = null + ) + { + _logger.LogDebug( "EventList" ); + + var id1 = Guid.Parse( "a1b2c3d4-e5f6-7890-abcd-ef1234567890" ); + var id2 = Guid.Parse( "b2c3d4e5-f6a7-8901-bcde-f12345678901" ); + + return new PaginatedResult() + { + HasMore = false, + Data = + [ + new EventResource() + { + Object = "event", + Id = id1, + Name = "user.created", + Schema = JsonDocument.Parse( "{\"plan\":\"string\"}" ).RootElement, + MomentCreated = DateTime.Parse( "2025-10-01 12:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ), + MomentUpdated = DateTime.Parse( "2025-10-01 12:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ), + }, + new EventResource() + { + Object = "event", + Id = id2, + Name = "user.upgraded", + Schema = null, + MomentCreated = DateTime.Parse( "2025-09-15 08:30:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ), + MomentUpdated = DateTime.Parse( "2025-09-20 14:00:00.000000+00", null, System.Globalization.DateTimeStyles.RoundtripKind ), + }, + ], + }; + } + + + /// + [HttpPatch] + [Route( "events/{id}" )] + public IActionResult EventUpdate( [FromRoute] string id, [FromBody] EventUpdateData request ) + { + _logger.LogDebug( "EventUpdate" ); + + return Ok(); + } + + + /// + [HttpDelete] + [Route( "events/{id}" )] + public IActionResult EventDelete( [FromRoute] string id ) + { + _logger.LogDebug( "EventDelete" ); + + return Ok(); + } + + + /// + [HttpPost] + [Route( "events/send" )] + [ProducesResponseType( typeof( EventSendResult ), StatusCodes.Status202Accepted )] + public IActionResult EventSend( [FromBody] EventSendData request ) + { + _logger.LogDebug( "EventSend" ); + + return StatusCode( StatusCodes.Status202Accepted, new EventSendResult() + { + Object = "event", + Event = request.Event, + } ); + } +}