diff --git a/Elsa.Extensions.sln b/Elsa.Extensions.sln index 63ef530a..a5db0833 100644 --- a/Elsa.Extensions.sln +++ b/Elsa.Extensions.sln @@ -257,6 +257,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "devops", "devops", "{9B6DFC EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.DevOps.GitHub", "src\devops\Elsa.DevOps.GitHub\Elsa.DevOps.GitHub.csproj", "{4E8D5DEA-6FB5-8878-0029-584C8F0D2D68}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "integrations", "integrations", "{1DAA7FA3-95BE-49F8-B989-0457DBD6CF3F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Integrations.Mailchimp", "src\integrations\Elsa.Integrations.Mailchimp\Elsa.Integrations.Mailchimp.csproj", "{FE2BCFD7-D6B6-4429-B964-2209F56AD06C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Integrations.Mailchimp.Tests", "test\unit\Elsa.Integrations.Mailchimp.Tests\Elsa.Integrations.Mailchimp.Tests.csproj", "{C57C6B43-DAE4-4C38-AD27-FC021AA2F4A6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -603,6 +609,14 @@ Global {4E8D5DEA-6FB5-8878-0029-584C8F0D2D68}.Debug|Any CPU.Build.0 = Debug|Any CPU {4E8D5DEA-6FB5-8878-0029-584C8F0D2D68}.Release|Any CPU.ActiveCfg = Release|Any CPU {4E8D5DEA-6FB5-8878-0029-584C8F0D2D68}.Release|Any CPU.Build.0 = Release|Any CPU + {FE2BCFD7-D6B6-4429-B964-2209F56AD06C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FE2BCFD7-D6B6-4429-B964-2209F56AD06C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FE2BCFD7-D6B6-4429-B964-2209F56AD06C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FE2BCFD7-D6B6-4429-B964-2209F56AD06C}.Release|Any CPU.Build.0 = Release|Any CPU + {C57C6B43-DAE4-4C38-AD27-FC021AA2F4A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C57C6B43-DAE4-4C38-AD27-FC021AA2F4A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C57C6B43-DAE4-4C38-AD27-FC021AA2F4A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C57C6B43-DAE4-4C38-AD27-FC021AA2F4A6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -717,6 +731,9 @@ Global {59C29BC8-ED95-8F0B-323E-A988EADF3D38} = {AF041BAE-B45A-428B-B7F5-921CCB895558} {9B6DFC6C-2514-4784-9952-9B0B913434F9} = {527248D6-B851-4C8D-8667-E2FB0A91DABF} {4E8D5DEA-6FB5-8878-0029-584C8F0D2D68} = {9B6DFC6C-2514-4784-9952-9B0B913434F9} + {1DAA7FA3-95BE-49F8-B989-0457DBD6CF3F} = {527248D6-B851-4C8D-8667-E2FB0A91DABF} + {FE2BCFD7-D6B6-4429-B964-2209F56AD06C} = {1DAA7FA3-95BE-49F8-B989-0457DBD6CF3F} + {C57C6B43-DAE4-4C38-AD27-FC021AA2F4A6} = {AF041BAE-B45A-428B-B7F5-921CCB895558} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {11A771DA-B728-445E-8A88-AE1C84C3B3A6} diff --git a/src/integrations/Elsa.Integrations.Mailchimp/Activities/Campaigns/CreateCampaign.cs b/src/integrations/Elsa.Integrations.Mailchimp/Activities/Campaigns/CreateCampaign.cs new file mode 100644 index 00000000..09e7aecc --- /dev/null +++ b/src/integrations/Elsa.Integrations.Mailchimp/Activities/Campaigns/CreateCampaign.cs @@ -0,0 +1,104 @@ +using Elsa.Integrations.Mailchimp.Activities; +using Elsa.Workflows; +using Elsa.Workflows.Attributes; +using Elsa.Workflows.Models; +using JetBrains.Annotations; +using MailChimp.Net.Models; + +namespace Elsa.Integrations.Mailchimp.Activities.Campaigns; + +/// +/// Creates a new campaign in Mailchimp. +/// +[Activity( + "Elsa.Mailchimp.Campaigns", + "Mailchimp Campaigns", + "Creates a new campaign in Mailchimp.", + DisplayName = "Create Campaign")] +[UsedImplicitly] +public class CreateCampaign : MailchimpActivity +{ + /// + /// The type of campaign (regular, plaintext, absplit, rss, variate). + /// + [Input(Description = "The type of campaign (regular, plaintext, absplit, rss, variate).")] + public Input Type { get; set; } = new("regular"); + + /// + /// The list ID to send the campaign to. + /// + [Input(Description = "The list ID to send the campaign to.")] + public Input ListId { get; set; } = null!; + + /// + /// The subject line for the campaign. + /// + [Input(Description = "The subject line for the campaign.")] + public Input SubjectLine { get; set; } = null!; + + /// + /// The title of the campaign. + /// + [Input(Description = "The title of the campaign.")] + public Input Title { get; set; } = default!; + + /// + /// The from name for the campaign. + /// + [Input(Description = "The from name for the campaign.")] + public Input FromName { get; set; } = default!; + + /// + /// The reply-to email address for the campaign. + /// + [Input(Description = "The reply-to email address for the campaign.")] + public Input ReplyTo { get; set; } = default!; + + /// + /// The preview text for the campaign. + /// + [Input(Description = "The preview text for the campaign.")] + public Input PreviewText { get; set; } = default!; + + /// + /// The created campaign. + /// + [Output(Description = "The created campaign.")] + public Output CreatedCampaign { get; set; } = default!; + + /// + /// Executes the activity. + /// + protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) + { + var type = context.Get(Type) ?? "regular"; + var listId = context.Get(ListId)!; + var subjectLine = context.Get(SubjectLine)!; + var title = context.Get(Title); + var fromName = context.Get(FromName); + var replyTo = context.Get(ReplyTo); + var previewText = context.Get(PreviewText); + + var client = GetClient(context); + + var campaign = new Campaign + { + Type = Enum.Parse(type, true), + Recipients = new Recipient + { + ListId = listId + }, + Settings = new Setting + { + SubjectLine = subjectLine, + Title = title, + FromName = fromName, + ReplyTo = replyTo, + PreviewText = previewText + } + }; + + var result = await client.Campaigns.AddAsync(campaign); + context.Set(CreatedCampaign, result); + } +} \ No newline at end of file diff --git a/src/integrations/Elsa.Integrations.Mailchimp/Activities/Campaigns/DeleteCampaign.cs b/src/integrations/Elsa.Integrations.Mailchimp/Activities/Campaigns/DeleteCampaign.cs new file mode 100644 index 00000000..56d24bba --- /dev/null +++ b/src/integrations/Elsa.Integrations.Mailchimp/Activities/Campaigns/DeleteCampaign.cs @@ -0,0 +1,50 @@ +using Elsa.Integrations.Mailchimp.Activities; +using Elsa.Workflows; +using Elsa.Workflows.Attributes; +using Elsa.Workflows.Models; +using JetBrains.Annotations; + +namespace Elsa.Integrations.Mailchimp.Activities.Campaigns; + +/// +/// Deletes a campaign in Mailchimp. +/// +[Activity( + "Elsa.Mailchimp.Campaigns", + "Mailchimp Campaigns", + "Deletes a campaign in Mailchimp.", + DisplayName = "Delete Campaign")] +[UsedImplicitly] +public class DeleteCampaign : MailchimpActivity +{ + /// + /// The ID of the campaign to delete. + /// + [Input(Description = "The ID of the campaign to delete.")] + public Input CampaignId { get; set; } = null!; + + /// + /// Indicates whether the operation was successful. + /// + [Output(Description = "Indicates whether the operation was successful.")] + public Output Success { get; set; } = default!; + + /// + /// Executes the activity. + /// + protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) + { + var campaignId = context.Get(CampaignId)!; + var client = GetClient(context); + + try + { + await client.Campaigns.DeleteAsync(campaignId); + context.Set(Success, true); + } + catch + { + context.Set(Success, false); + } + } +} \ No newline at end of file diff --git a/src/integrations/Elsa.Integrations.Mailchimp/Activities/Campaigns/GetCampaign.cs b/src/integrations/Elsa.Integrations.Mailchimp/Activities/Campaigns/GetCampaign.cs new file mode 100644 index 00000000..48ac4397 --- /dev/null +++ b/src/integrations/Elsa.Integrations.Mailchimp/Activities/Campaigns/GetCampaign.cs @@ -0,0 +1,44 @@ +using Elsa.Integrations.Mailchimp.Activities; +using Elsa.Workflows; +using Elsa.Workflows.Attributes; +using Elsa.Workflows.Models; +using JetBrains.Annotations; +using MailChimp.Net.Models; + +namespace Elsa.Integrations.Mailchimp.Activities.Campaigns; + +/// +/// Retrieves metadata of a specified campaign from Mailchimp. +/// +[Activity( + "Elsa.Mailchimp.Campaigns", + "Mailchimp Campaigns", + "Retrieves metadata of a specified campaign from Mailchimp.", + DisplayName = "Get Campaign")] +[UsedImplicitly] +public class GetCampaign : MailchimpActivity +{ + /// + /// The ID of the campaign to retrieve. + /// + [Input(Description = "The ID of the campaign to retrieve.")] + public Input CampaignId { get; set; } = null!; + + /// + /// The retrieved campaign. + /// + [Output(Description = "The retrieved campaign.")] + public Output RetrievedCampaign { get; set; } = default!; + + /// + /// Executes the activity. + /// + protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) + { + var campaignId = context.Get(CampaignId)!; + var client = GetClient(context); + + var campaign = await client.Campaigns.GetAsync(campaignId); + context.Set(RetrievedCampaign, campaign); + } +} \ No newline at end of file diff --git a/src/integrations/Elsa.Integrations.Mailchimp/Activities/Campaigns/UpdateCampaign.cs b/src/integrations/Elsa.Integrations.Mailchimp/Activities/Campaigns/UpdateCampaign.cs new file mode 100644 index 00000000..c206e40c --- /dev/null +++ b/src/integrations/Elsa.Integrations.Mailchimp/Activities/Campaigns/UpdateCampaign.cs @@ -0,0 +1,97 @@ +using Elsa.Integrations.Mailchimp.Activities; +using Elsa.Workflows; +using Elsa.Workflows.Attributes; +using Elsa.Workflows.Models; +using JetBrains.Annotations; +using MailChimp.Net.Models; + +namespace Elsa.Integrations.Mailchimp.Activities.Campaigns; + +/// +/// Updates an existing campaign in Mailchimp. +/// +[Activity( + "Elsa.Mailchimp.Campaigns", + "Mailchimp Campaigns", + "Updates an existing campaign in Mailchimp.", + DisplayName = "Update Campaign")] +[UsedImplicitly] +public class UpdateCampaign : MailchimpActivity +{ + /// + /// The ID of the campaign to update. + /// + [Input(Description = "The ID of the campaign to update.")] + public Input CampaignId { get; set; } = null!; + + /// + /// The subject line for the campaign. + /// + [Input(Description = "The subject line for the campaign.")] + public Input SubjectLine { get; set; } = default!; + + /// + /// The title of the campaign. + /// + [Input(Description = "The title of the campaign.")] + public Input Title { get; set; } = default!; + + /// + /// The from name for the campaign. + /// + [Input(Description = "The from name for the campaign.")] + public Input FromName { get; set; } = default!; + + /// + /// The reply-to email address for the campaign. + /// + [Input(Description = "The reply-to email address for the campaign.")] + public Input ReplyTo { get; set; } = default!; + + /// + /// The preview text for the campaign. + /// + [Input(Description = "The preview text for the campaign.")] + public Input PreviewText { get; set; } = default!; + + /// + /// The updated campaign. + /// + [Output(Description = "The updated campaign.")] + public Output UpdatedCampaign { get; set; } = default!; + + /// + /// Executes the activity. + /// + protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) + { + var campaignId = context.Get(CampaignId)!; + var subjectLine = context.Get(SubjectLine); + var title = context.Get(Title); + var fromName = context.Get(FromName); + var replyTo = context.Get(ReplyTo); + var previewText = context.Get(PreviewText); + + var client = GetClient(context); + + var campaign = new Campaign + { + Id = campaignId, + Settings = new Setting() + }; + + if (!string.IsNullOrEmpty(subjectLine)) + campaign.Settings.SubjectLine = subjectLine; + if (!string.IsNullOrEmpty(title)) + campaign.Settings.Title = title; + if (!string.IsNullOrEmpty(fromName)) + campaign.Settings.FromName = fromName; + if (!string.IsNullOrEmpty(replyTo)) + campaign.Settings.ReplyTo = replyTo; + if (!string.IsNullOrEmpty(previewText)) + campaign.Settings.PreviewText = previewText; + + var result = await client.Campaigns.UpdateAsync(campaignId, campaign); + context.Set(UpdatedCampaign, result); + } +} \ No newline at end of file diff --git a/src/integrations/Elsa.Integrations.Mailchimp/Activities/Events/WatchCampaigns.cs b/src/integrations/Elsa.Integrations.Mailchimp/Activities/Events/WatchCampaigns.cs new file mode 100644 index 00000000..71eae533 --- /dev/null +++ b/src/integrations/Elsa.Integrations.Mailchimp/Activities/Events/WatchCampaigns.cs @@ -0,0 +1,61 @@ +using Elsa.Integrations.Mailchimp.Activities; +using Elsa.Workflows; +using Elsa.Workflows.Attributes; +using Elsa.Workflows.Models; +using JetBrains.Annotations; +using MailChimp.Net.Models; + +namespace Elsa.Integrations.Mailchimp.Activities.Events; + +/// +/// Triggers when a new campaign is created or sent in Mailchimp. +/// +[Activity( + "Elsa.Mailchimp.Events", + "Mailchimp Events", + "Triggers when a new campaign is created or sent in Mailchimp.", + DisplayName = "Watch Campaigns")] +[UsedImplicitly] +public class WatchCampaigns : MailchimpTriggerActivity +{ + /// + /// The list ID to watch for campaign events (optional). + /// + [Input(Description = "The list ID to watch for campaign events (optional).")] + public Input ListId { get; set; } = default!; + + /// + /// The received campaign event. + /// + [Output(Description = "The received campaign event.")] + public Output ReceivedCampaign { get; set; } = null!; + + /// + /// Returns the trigger type identifier. + /// + public override string GetTriggerType() => "mailchimp.campaign.event"; + + /// + /// Returns the payloads to index. + /// + /// The trigger indexing context. + public override ValueTask> GetTriggerPayloadsAsync(TriggerIndexingContext context) + { + var apiKey = context.Get(ApiKey); + var listId = context.Get(ListId); + var eventType = context.Get(EventType) ?? "campaign.created"; + + return new ValueTask>(new object[] { new { ApiKey = apiKey, ListId = listId, EventType = eventType } }); + } + + /// + /// Executes the activity. + /// + protected override ValueTask ExecuteAsync(ActivityExecutionContext context) + { + // This would be triggered by a webhook event + // The actual implementation would depend on webhook infrastructure + // For now, this is a placeholder that shows the expected structure + throw new NotImplementedException("Webhook trigger implementation requires webhook infrastructure setup."); + } +} \ No newline at end of file diff --git a/src/integrations/Elsa.Integrations.Mailchimp/Activities/Events/WatchLists.cs b/src/integrations/Elsa.Integrations.Mailchimp/Activities/Events/WatchLists.cs new file mode 100644 index 00000000..639138cb --- /dev/null +++ b/src/integrations/Elsa.Integrations.Mailchimp/Activities/Events/WatchLists.cs @@ -0,0 +1,54 @@ +using Elsa.Integrations.Mailchimp.Activities; +using Elsa.Workflows; +using Elsa.Workflows.Attributes; +using Elsa.Workflows.Models; +using JetBrains.Annotations; +using MailChimp.Net.Models; + +namespace Elsa.Integrations.Mailchimp.Activities.Events; + +/// +/// Triggers when a new list is created in Mailchimp. +/// +[Activity( + "Elsa.Mailchimp.Events", + "Mailchimp Events", + "Triggers when a new list is created in Mailchimp.", + DisplayName = "Watch Lists")] +[UsedImplicitly] +public class WatchLists : MailchimpTriggerActivity +{ + /// + /// The received list event. + /// + [Output(Description = "The received list event.")] + public Output ReceivedList { get; set; } = null!; + + /// + /// Returns the trigger type identifier. + /// + public override string GetTriggerType() => "mailchimp.list.created"; + + /// + /// Returns the payloads to index. + /// + /// The trigger indexing context. + public override ValueTask> GetTriggerPayloadsAsync(TriggerIndexingContext context) + { + var apiKey = context.Get(ApiKey); + var eventType = context.Get(EventType) ?? "list.created"; + + return new ValueTask>(new object[] { new { ApiKey = apiKey, EventType = eventType } }); + } + + /// + /// Executes the activity. + /// + protected override ValueTask ExecuteAsync(ActivityExecutionContext context) + { + // This would be triggered by a webhook event + // The actual implementation would depend on webhook infrastructure + // For now, this is a placeholder that shows the expected structure + throw new NotImplementedException("Webhook trigger implementation requires webhook infrastructure setup."); + } +} \ No newline at end of file diff --git a/src/integrations/Elsa.Integrations.Mailchimp/Activities/Events/WatchSubscribers.cs b/src/integrations/Elsa.Integrations.Mailchimp/Activities/Events/WatchSubscribers.cs new file mode 100644 index 00000000..6ec1b369 --- /dev/null +++ b/src/integrations/Elsa.Integrations.Mailchimp/Activities/Events/WatchSubscribers.cs @@ -0,0 +1,61 @@ +using Elsa.Integrations.Mailchimp.Activities; +using Elsa.Workflows; +using Elsa.Workflows.Attributes; +using Elsa.Workflows.Models; +using JetBrains.Annotations; +using MailChimp.Net.Models; + +namespace Elsa.Integrations.Mailchimp.Activities.Events; + +/// +/// Triggers when a new subscriber joins a list or is updated in Mailchimp. +/// +[Activity( + "Elsa.Mailchimp.Events", + "Mailchimp Events", + "Triggers when a new subscriber joins a list or is updated in Mailchimp.", + DisplayName = "Watch Subscribers")] +[UsedImplicitly] +public class WatchSubscribers : MailchimpTriggerActivity +{ + /// + /// The list ID to watch for subscriber events. + /// + [Input(Description = "The list ID to watch for subscriber events.")] + public Input ListId { get; set; } = default!; + + /// + /// The received subscriber event. + /// + [Output(Description = "The received subscriber event.")] + public Output ReceivedSubscriber { get; set; } = null!; + + /// + /// Returns the trigger type identifier. + /// + public override string GetTriggerType() => "mailchimp.subscriber.updated"; + + /// + /// Returns the payloads to index. + /// + /// The trigger indexing context. + public override ValueTask> GetTriggerPayloadsAsync(TriggerIndexingContext context) + { + var apiKey = context.Get(ApiKey); + var listId = context.Get(ListId); + var eventType = context.Get(EventType) ?? "subscriber.updated"; + + return new ValueTask>(new object[] { new { ApiKey = apiKey, ListId = listId, EventType = eventType } }); + } + + /// + /// Executes the activity. + /// + protected override ValueTask ExecuteAsync(ActivityExecutionContext context) + { + // This would be triggered by a webhook event + // The actual implementation would depend on webhook infrastructure + // For now, this is a placeholder that shows the expected structure + throw new NotImplementedException("Webhook trigger implementation requires webhook infrastructure setup."); + } +} \ No newline at end of file diff --git a/src/integrations/Elsa.Integrations.Mailchimp/Activities/Lists/CreateList.cs b/src/integrations/Elsa.Integrations.Mailchimp/Activities/Lists/CreateList.cs new file mode 100644 index 00000000..40108c52 --- /dev/null +++ b/src/integrations/Elsa.Integrations.Mailchimp/Activities/Lists/CreateList.cs @@ -0,0 +1,136 @@ +using Elsa.Integrations.Mailchimp.Activities; +using Elsa.Workflows; +using Elsa.Workflows.Attributes; +using Elsa.Workflows.Models; +using JetBrains.Annotations; +using MailChimp.Net.Models; + +namespace Elsa.Integrations.Mailchimp.Activities.Lists; + +/// +/// Creates a new list in Mailchimp. +/// +[Activity( + "Elsa.Mailchimp.Lists", + "Mailchimp Lists", + "Creates a new list in Mailchimp.", + DisplayName = "Create List")] +[UsedImplicitly] +public class CreateList : MailchimpActivity +{ + /// + /// The name of the list. + /// + [Input(Description = "The name of the list.")] + public Input Name { get; set; } = null!; + + /// + /// The contact information for this list. + /// + [Input(Description = "The company name associated with the list.")] + public Input CompanyName { get; set; } = null!; + + /// + /// The contact address for this list. + /// + [Input(Description = "The contact address for this list.")] + public Input Address1 { get; set; } = null!; + + /// + /// The city for the contact address. + /// + [Input(Description = "The city for the contact address.")] + public Input City { get; set; } = null!; + + /// + /// The state for the contact address. + /// + [Input(Description = "The state for the contact address.")] + public Input State { get; set; } = null!; + + /// + /// The zip code for the contact address. + /// + [Input(Description = "The zip code for the contact address.")] + public Input Zip { get; set; } = null!; + + /// + /// The country for the contact address. + /// + [Input(Description = "The country for the contact address.")] + public Input Country { get; set; } = null!; + + /// + /// The permission reminder for the list. + /// + [Input(Description = "The permission reminder for the list.")] + public Input PermissionReminder { get; set; } = null!; + + /// + /// The 'from' name for campaigns sent to this list. + /// + [Input(Description = "The 'from' name for campaigns sent to this list.")] + public Input FromName { get; set; } = null!; + + /// + /// The 'from' email address for campaigns sent to this list. + /// + [Input(Description = "The 'from' email address for campaigns sent to this list.")] + public Input FromEmail { get; set; } = null!; + + /// + /// The language for this list. + /// + [Input(Description = "The language for this list.")] + public Input Language { get; set; } = new("en"); + + /// + /// The created list. + /// + [Output(Description = "The created list.")] + public Output CreatedList { get; set; } = default!; + + /// + /// Executes the activity. + /// + protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) + { + var name = context.Get(Name)!; + var companyName = context.Get(CompanyName)!; + var address1 = context.Get(Address1)!; + var city = context.Get(City)!; + var state = context.Get(State)!; + var zip = context.Get(Zip)!; + var country = context.Get(Country)!; + var permissionReminder = context.Get(PermissionReminder)!; + var fromName = context.Get(FromName)!; + var fromEmail = context.Get(FromEmail)!; + var language = context.Get(Language) ?? "en"; + + var client = GetClient(context); + + var list = new List + { + Name = name, + Contact = new Contact + { + Company = companyName, + Address1 = address1, + City = city, + State = state, + Zip = zip, + Country = country + }, + PermissionReminder = permissionReminder, + CampaignDefaults = new CampaignDefaults + { + FromName = fromName, + FromEmail = fromEmail, + Language = language + } + }; + + var result = await client.Lists.AddAsync(list); + context.Set(CreatedList, result); + } +} \ No newline at end of file diff --git a/src/integrations/Elsa.Integrations.Mailchimp/Activities/Lists/DeleteList.cs b/src/integrations/Elsa.Integrations.Mailchimp/Activities/Lists/DeleteList.cs new file mode 100644 index 00000000..5789dcb9 --- /dev/null +++ b/src/integrations/Elsa.Integrations.Mailchimp/Activities/Lists/DeleteList.cs @@ -0,0 +1,50 @@ +using Elsa.Integrations.Mailchimp.Activities; +using Elsa.Workflows; +using Elsa.Workflows.Attributes; +using Elsa.Workflows.Models; +using JetBrains.Annotations; + +namespace Elsa.Integrations.Mailchimp.Activities.Lists; + +/// +/// Deletes a list in Mailchimp. +/// +[Activity( + "Elsa.Mailchimp.Lists", + "Mailchimp Lists", + "Deletes a list in Mailchimp.", + DisplayName = "Delete List")] +[UsedImplicitly] +public class DeleteList : MailchimpActivity +{ + /// + /// The ID of the list to delete. + /// + [Input(Description = "The ID of the list to delete.")] + public Input ListId { get; set; } = null!; + + /// + /// Indicates whether the operation was successful. + /// + [Output(Description = "Indicates whether the operation was successful.")] + public Output Success { get; set; } = default!; + + /// + /// Executes the activity. + /// + protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) + { + var listId = context.Get(ListId)!; + var client = GetClient(context); + + try + { + await client.Lists.DeleteAsync(listId); + context.Set(Success, true); + } + catch + { + context.Set(Success, false); + } + } +} \ No newline at end of file diff --git a/src/integrations/Elsa.Integrations.Mailchimp/Activities/Lists/GetList.cs b/src/integrations/Elsa.Integrations.Mailchimp/Activities/Lists/GetList.cs new file mode 100644 index 00000000..0903f848 --- /dev/null +++ b/src/integrations/Elsa.Integrations.Mailchimp/Activities/Lists/GetList.cs @@ -0,0 +1,44 @@ +using Elsa.Integrations.Mailchimp.Activities; +using Elsa.Workflows; +using Elsa.Workflows.Attributes; +using Elsa.Workflows.Models; +using JetBrains.Annotations; +using MailChimp.Net.Models; + +namespace Elsa.Integrations.Mailchimp.Activities.Lists; + +/// +/// Retrieves metadata of a specified list from Mailchimp. +/// +[Activity( + "Elsa.Mailchimp.Lists", + "Mailchimp Lists", + "Retrieves metadata of a specified list from Mailchimp.", + DisplayName = "Get List")] +[UsedImplicitly] +public class GetList : MailchimpActivity +{ + /// + /// The ID of the list to retrieve. + /// + [Input(Description = "The ID of the list to retrieve.")] + public Input ListId { get; set; } = null!; + + /// + /// The retrieved list. + /// + [Output(Description = "The retrieved list.")] + public Output RetrievedList { get; set; } = default!; + + /// + /// Executes the activity. + /// + protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) + { + var listId = context.Get(ListId)!; + var client = GetClient(context); + + var list = await client.Lists.GetAsync(listId); + context.Set(RetrievedList, list); + } +} \ No newline at end of file diff --git a/src/integrations/Elsa.Integrations.Mailchimp/Activities/Lists/UpdateList.cs b/src/integrations/Elsa.Integrations.Mailchimp/Activities/Lists/UpdateList.cs new file mode 100644 index 00000000..b40e379e --- /dev/null +++ b/src/integrations/Elsa.Integrations.Mailchimp/Activities/Lists/UpdateList.cs @@ -0,0 +1,93 @@ +using Elsa.Integrations.Mailchimp.Activities; +using Elsa.Workflows; +using Elsa.Workflows.Attributes; +using Elsa.Workflows.Models; +using JetBrains.Annotations; +using MailChimp.Net.Models; + +namespace Elsa.Integrations.Mailchimp.Activities.Lists; + +/// +/// Updates an existing list in Mailchimp. +/// +[Activity( + "Elsa.Mailchimp.Lists", + "Mailchimp Lists", + "Updates an existing list in Mailchimp.", + DisplayName = "Update List")] +[UsedImplicitly] +public class UpdateList : MailchimpActivity +{ + /// + /// The ID of the list to update. + /// + [Input(Description = "The ID of the list to update.")] + public Input ListId { get; set; } = null!; + + /// + /// The name of the list. + /// + [Input(Description = "The name of the list.")] + public Input Name { get; set; } = default!; + + /// + /// The permission reminder for the list. + /// + [Input(Description = "The permission reminder for the list.")] + public Input PermissionReminder { get; set; } = default!; + + /// + /// The 'from' name for campaigns sent to this list. + /// + [Input(Description = "The 'from' name for campaigns sent to this list.")] + public Input FromName { get; set; } = default!; + + /// + /// The 'from' email address for campaigns sent to this list. + /// + [Input(Description = "The 'from' email address for campaigns sent to this list.")] + public Input FromEmail { get; set; } = default!; + + /// + /// The updated list. + /// + [Output(Description = "The updated list.")] + public Output UpdatedList { get; set; } = default!; + + /// + /// Executes the activity. + /// + protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) + { + var listId = context.Get(ListId)!; + var name = context.Get(Name); + var permissionReminder = context.Get(PermissionReminder); + var fromName = context.Get(FromName); + var fromEmail = context.Get(FromEmail); + + var client = GetClient(context); + + var list = new List + { + Id = listId + }; + + if (!string.IsNullOrEmpty(name)) + list.Name = name; + + if (!string.IsNullOrEmpty(permissionReminder)) + list.PermissionReminder = permissionReminder; + + if (!string.IsNullOrEmpty(fromName) || !string.IsNullOrEmpty(fromEmail)) + { + list.CampaignDefaults = new CampaignDefaults(); + if (!string.IsNullOrEmpty(fromName)) + list.CampaignDefaults.FromName = fromName; + if (!string.IsNullOrEmpty(fromEmail)) + list.CampaignDefaults.FromEmail = fromEmail; + } + + var result = await client.Lists.UpdateAsync(listId, list); + context.Set(UpdatedList, result); + } +} \ No newline at end of file diff --git a/src/integrations/Elsa.Integrations.Mailchimp/Activities/MailchimpActivity.cs b/src/integrations/Elsa.Integrations.Mailchimp/Activities/MailchimpActivity.cs new file mode 100644 index 00000000..aa8af7a6 --- /dev/null +++ b/src/integrations/Elsa.Integrations.Mailchimp/Activities/MailchimpActivity.cs @@ -0,0 +1,31 @@ +using Elsa.Integrations.Mailchimp.Services; +using Elsa.Workflows; +using Elsa.Workflows.Attributes; +using Elsa.Workflows.Models; +using MailChimp.Net.Interfaces; + +namespace Elsa.Integrations.Mailchimp.Activities; + +/// +/// Generic base class inherited by all Mailchimp activities. +/// +public abstract class MailchimpActivity : Activity +{ + /// + /// The Mailchimp API key. + /// + [Input(Description = "The Mailchimp API key.")] + public Input ApiKey { get; set; } = null!; + + /// + /// Gets the Mailchimp API client. + /// + /// The current context to get the client. + /// The Mailchimp API client. + protected IMailChimpManager GetClient(ActivityExecutionContext context) + { + MailchimpClientFactory mailchimpClientFactory = context.GetRequiredService(); + string apiKey = context.Get(ApiKey)!; + return mailchimpClientFactory.GetClient(apiKey); + } +} \ No newline at end of file diff --git a/src/integrations/Elsa.Integrations.Mailchimp/Activities/MailchimpTriggerActivity.cs b/src/integrations/Elsa.Integrations.Mailchimp/Activities/MailchimpTriggerActivity.cs new file mode 100644 index 00000000..7694d1ea --- /dev/null +++ b/src/integrations/Elsa.Integrations.Mailchimp/Activities/MailchimpTriggerActivity.cs @@ -0,0 +1,28 @@ +using Elsa.Workflows; +using Elsa.Workflows.Attributes; +using Elsa.Workflows.Models; + +namespace Elsa.Integrations.Mailchimp.Activities; + +/// +/// Base class for Mailchimp event trigger activities. +/// +public abstract class MailchimpTriggerActivity : MailchimpActivity, ITrigger +{ + /// + /// The webhook event type to listen for. + /// + [Input(Description = "The webhook event type to listen for.")] + public Input EventType { get; set; } = null!; + + /// + /// Returns the trigger type identifier. + /// + public abstract string GetTriggerType(); + + /// + /// Returns the payloads to index. + /// + /// The trigger indexing context. + public abstract ValueTask> GetTriggerPayloadsAsync(TriggerIndexingContext context); +} \ No newline at end of file diff --git a/src/integrations/Elsa.Integrations.Mailchimp/Activities/MakeAPICall.cs b/src/integrations/Elsa.Integrations.Mailchimp/Activities/MakeAPICall.cs new file mode 100644 index 00000000..8588a297 --- /dev/null +++ b/src/integrations/Elsa.Integrations.Mailchimp/Activities/MakeAPICall.cs @@ -0,0 +1,149 @@ +using Elsa.Integrations.Mailchimp.Activities; +using Elsa.Workflows; +using Elsa.Workflows.Attributes; +using Elsa.Workflows.Models; +using JetBrains.Annotations; +using System.Text.Json; + +namespace Elsa.Integrations.Mailchimp.Activities; + +/// +/// Performs an arbitrary authorized API call to Mailchimp. +/// +[Activity( + "Elsa.Mailchimp.Core", + "Mailchimp Core", + "Performs an arbitrary authorized API call to Mailchimp.", + DisplayName = "Make API Call")] +[UsedImplicitly] +public class MakeAPICall : MailchimpActivity +{ + /// + /// The HTTP method to use (GET, POST, PUT, DELETE, PATCH). + /// + [Input(Description = "The HTTP method to use (GET, POST, PUT, DELETE, PATCH).")] + public Input Method { get; set; } = new("GET"); + + /// + /// The API endpoint path (e.g., 'lists', 'campaigns'). + /// + [Input(Description = "The API endpoint path (e.g., 'lists', 'campaigns').")] + public Input Endpoint { get; set; } = null!; + + /// + /// The request body as JSON (for POST, PUT, PATCH methods). + /// + [Input(Description = "The request body as JSON (for POST, PUT, PATCH methods).")] + public Input RequestBody { get; set; } = default!; + + /// + /// Query parameters as JSON object. + /// + [Input(Description = "Query parameters as JSON object.")] + public Input QueryParameters { get; set; } = default!; + + /// + /// The API response. + /// + [Output(Description = "The API response.")] + public Output Response { get; set; } = default!; + + /// + /// The HTTP status code of the response. + /// + [Output(Description = "The HTTP status code of the response.")] + public Output StatusCode { get; set; } = default!; + + /// + /// Executes the activity. + /// + protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) + { + var method = context.Get(Method) ?? "GET"; + var endpoint = context.Get(Endpoint)!; + var requestBody = context.Get(RequestBody); + var queryParameters = context.Get(QueryParameters); + + var client = GetClient(context); + + try + { + // Note: This is a simplified implementation. + // The actual MailChimp.Net library doesn't expose a generic HTTP client, + // so this would need to be implemented using HttpClient directly + // with the proper authentication headers. + + using var httpClient = new HttpClient(); + + // Extract API key to get the data center + var apiKey = context.Get(ApiKey)!; + var dataCenterSuffix = apiKey.Split('-').LastOrDefault(); + var baseUrl = $"https://{dataCenterSuffix}.api.mailchimp.com/3.0/"; + + httpClient.DefaultRequestHeaders.Authorization = + new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", + Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes($"anystring:{apiKey}"))); + + var url = baseUrl + endpoint.TrimStart('/'); + + // Add query parameters if provided + if (!string.IsNullOrEmpty(queryParameters)) + { + try + { + var queryParams = JsonSerializer.Deserialize>(queryParameters); + if (queryParams?.Any() == true) + { + var queryString = string.Join("&", queryParams.Select(kv => $"{kv.Key}={Uri.EscapeDataString(kv.Value?.ToString() ?? "")}")); + url += "?" + queryString; + } + } + catch + { + // Ignore JSON parsing errors for query parameters + } + } + + HttpResponseMessage response; + + switch (method.ToUpper()) + { + case "GET": + response = await httpClient.GetAsync(url); + break; + case "POST": + var postContent = new StringContent(requestBody ?? "{}", System.Text.Encoding.UTF8, "application/json"); + response = await httpClient.PostAsync(url, postContent); + break; + case "PUT": + var putContent = new StringContent(requestBody ?? "{}", System.Text.Encoding.UTF8, "application/json"); + response = await httpClient.PutAsync(url, putContent); + break; + case "DELETE": + response = await httpClient.DeleteAsync(url); + break; + case "PATCH": + var patchContent = new StringContent(requestBody ?? "{}", System.Text.Encoding.UTF8, "application/json"); + var patchRequest = new HttpRequestMessage(new HttpMethod("PATCH"), url) + { + Content = patchContent + }; + response = await httpClient.SendAsync(patchRequest); + break; + default: + throw new ArgumentException($"Unsupported HTTP method: {method}"); + } + + var responseContent = await response.Content.ReadAsStringAsync(); + var responseObject = JsonSerializer.Deserialize(responseContent); + + context.Set(Response, responseObject); + context.Set(StatusCode, (int)response.StatusCode); + } + catch (Exception ex) + { + context.Set(Response, new { error = ex.Message }); + context.Set(StatusCode, 500); + } + } +} \ No newline at end of file diff --git a/src/integrations/Elsa.Integrations.Mailchimp/Activities/Members/AddUpdateListMember.cs b/src/integrations/Elsa.Integrations.Mailchimp/Activities/Members/AddUpdateListMember.cs new file mode 100644 index 00000000..f2698457 --- /dev/null +++ b/src/integrations/Elsa.Integrations.Mailchimp/Activities/Members/AddUpdateListMember.cs @@ -0,0 +1,112 @@ +using Elsa.Integrations.Mailchimp.Activities; +using Elsa.Workflows; +using Elsa.Workflows.Attributes; +using Elsa.Workflows.Models; +using JetBrains.Annotations; +using MailChimp.Net.Models; + +namespace Elsa.Integrations.Mailchimp.Activities.Members; + +/// +/// Adds or updates a list member in Mailchimp. +/// +[Activity( + "Elsa.Mailchimp.Members", + "Mailchimp Members", + "Adds or updates a list member in Mailchimp.", + DisplayName = "Add/Update List Member")] +[UsedImplicitly] +public class AddUpdateListMember : MailchimpActivity +{ + /// + /// The ID of the list to add/update the member to. + /// + [Input(Description = "The ID of the list to add/update the member to.")] + public Input ListId { get; set; } = null!; + + /// + /// The email address of the member. + /// + [Input(Description = "The email address of the member.")] + public Input EmailAddress { get; set; } = null!; + + /// + /// The subscription status of the member. + /// + [Input(Description = "The subscription status of the member (subscribed, unsubscribed, cleaned, pending).")] + public Input Status { get; set; } = new("subscribed"); + + /// + /// The first name of the member. + /// + [Input(Description = "The first name of the member.")] + public Input FirstName { get; set; } = default!; + + /// + /// The last name of the member. + /// + [Input(Description = "The last name of the member.")] + public Input LastName { get; set; } = default!; + + /// + /// Additional merge fields for the member. + /// + [Input(Description = "Additional merge fields for the member as JSON.")] + public Input MergeFields { get; set; } = default!; + + /// + /// The added/updated member. + /// + [Output(Description = "The added/updated member.")] + public Output UpdatedMember { get; set; } = default!; + + /// + /// Executes the activity. + /// + protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) + { + var listId = context.Get(ListId)!; + var emailAddress = context.Get(EmailAddress)!; + var status = context.Get(Status) ?? "subscribed"; + var firstName = context.Get(FirstName); + var lastName = context.Get(LastName); + var mergeFieldsJson = context.Get(MergeFields); + + var client = GetClient(context); + + var member = new Member + { + EmailAddress = emailAddress, + Status = Enum.Parse(status, true) + }; + + // Set merge fields + if (!string.IsNullOrEmpty(firstName)) + member.MergeFields.Add("FNAME", firstName); + if (!string.IsNullOrEmpty(lastName)) + member.MergeFields.Add("LNAME", lastName); + + // Parse additional merge fields if provided + if (!string.IsNullOrEmpty(mergeFieldsJson)) + { + try + { + var additionalFields = System.Text.Json.JsonSerializer.Deserialize>(mergeFieldsJson); + if (additionalFields != null) + { + foreach (var field in additionalFields) + { + member.MergeFields[field.Key] = field.Value; + } + } + } + catch + { + // Ignore JSON parsing errors + } + } + + var result = await client.Members.AddOrUpdateAsync(listId, member); + context.Set(UpdatedMember, result); + } +} \ No newline at end of file diff --git a/src/integrations/Elsa.Integrations.Mailchimp/Activities/Members/DeleteSubscriber.cs b/src/integrations/Elsa.Integrations.Mailchimp/Activities/Members/DeleteSubscriber.cs new file mode 100644 index 00000000..3c659fd1 --- /dev/null +++ b/src/integrations/Elsa.Integrations.Mailchimp/Activities/Members/DeleteSubscriber.cs @@ -0,0 +1,77 @@ +using Elsa.Integrations.Mailchimp.Activities; +using Elsa.Workflows; +using Elsa.Workflows.Attributes; +using Elsa.Workflows.Models; +using JetBrains.Annotations; + +namespace Elsa.Integrations.Mailchimp.Activities.Members; + +/// +/// Archives or permanently deletes a subscriber from Mailchimp. +/// +[Activity( + "Elsa.Mailchimp.Members", + "Mailchimp Members", + "Archives or permanently deletes a subscriber from Mailchimp.", + DisplayName = "Delete Subscriber")] +[UsedImplicitly] +public class DeleteSubscriber : MailchimpActivity +{ + /// + /// The ID of the list. + /// + [Input(Description = "The ID of the list.")] + public Input ListId { get; set; } = null!; + + /// + /// The email address of the subscriber. + /// + [Input(Description = "The email address of the subscriber.")] + public Input EmailAddress { get; set; } = null!; + + /// + /// Whether to permanently delete the subscriber (true) or just archive (false). + /// + [Input(Description = "Whether to permanently delete the subscriber (true) or just archive (false).")] + public Input PermanentDelete { get; set; } = new(false); + + /// + /// Indicates whether the operation was successful. + /// + [Output(Description = "Indicates whether the operation was successful.")] + public Output Success { get; set; } = default!; + + /// + /// Executes the activity. + /// + protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) + { + var listId = context.Get(ListId)!; + var emailAddress = context.Get(EmailAddress)!; + var permanentDelete = context.Get(PermanentDelete); + var client = GetClient(context); + + try + { + if (permanentDelete) + { + await client.Members.DeleteAsync(listId, emailAddress); + } + else + { + // Archive the member by setting status to unsubscribed + var member = new MailChimp.Net.Models.Member + { + EmailAddress = emailAddress, + Status = MailChimp.Net.Models.Status.Unsubscribed + }; + await client.Members.AddOrUpdateAsync(listId, member); + } + context.Set(Success, true); + } + catch + { + context.Set(Success, false); + } + } +} \ No newline at end of file diff --git a/src/integrations/Elsa.Integrations.Mailchimp/Activities/Members/GetSubscriber.cs b/src/integrations/Elsa.Integrations.Mailchimp/Activities/Members/GetSubscriber.cs new file mode 100644 index 00000000..2e55d9f3 --- /dev/null +++ b/src/integrations/Elsa.Integrations.Mailchimp/Activities/Members/GetSubscriber.cs @@ -0,0 +1,51 @@ +using Elsa.Integrations.Mailchimp.Activities; +using Elsa.Workflows; +using Elsa.Workflows.Attributes; +using Elsa.Workflows.Models; +using JetBrains.Annotations; +using MailChimp.Net.Models; + +namespace Elsa.Integrations.Mailchimp.Activities.Members; + +/// +/// Retrieves metadata of a subscriber by email from Mailchimp. +/// +[Activity( + "Elsa.Mailchimp.Members", + "Mailchimp Members", + "Retrieves metadata of a subscriber by email from Mailchimp.", + DisplayName = "Get Subscriber")] +[UsedImplicitly] +public class GetSubscriber : MailchimpActivity +{ + /// + /// The ID of the list. + /// + [Input(Description = "The ID of the list.")] + public Input ListId { get; set; } = null!; + + /// + /// The email address of the subscriber. + /// + [Input(Description = "The email address of the subscriber.")] + public Input EmailAddress { get; set; } = null!; + + /// + /// The retrieved subscriber. + /// + [Output(Description = "The retrieved subscriber.")] + public Output RetrievedSubscriber { get; set; } = default!; + + /// + /// Executes the activity. + /// + protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) + { + var listId = context.Get(ListId)!; + var emailAddress = context.Get(EmailAddress)!; + var client = GetClient(context); + + var member = await client.Members.GetAsync(listId, emailAddress); + context.Set(RetrievedSubscriber, member); + } +} \ No newline at end of file diff --git a/src/integrations/Elsa.Integrations.Mailchimp/Elsa.Integrations.Mailchimp.csproj b/src/integrations/Elsa.Integrations.Mailchimp/Elsa.Integrations.Mailchimp.csproj new file mode 100644 index 00000000..c45d185a --- /dev/null +++ b/src/integrations/Elsa.Integrations.Mailchimp/Elsa.Integrations.Mailchimp.csproj @@ -0,0 +1,15 @@ + + + + + Provides integration with Mailchimp for campaign management, list handling, segmentation, and event monitoring. + + elsa extension module mailchimp integration + + + + + + + + \ No newline at end of file diff --git a/src/integrations/Elsa.Integrations.Mailchimp/Extensions/ModuleExtensions.cs b/src/integrations/Elsa.Integrations.Mailchimp/Extensions/ModuleExtensions.cs new file mode 100644 index 00000000..f6afcc3e --- /dev/null +++ b/src/integrations/Elsa.Integrations.Mailchimp/Extensions/ModuleExtensions.cs @@ -0,0 +1,19 @@ +using Elsa.Features.Services; +using Elsa.Integrations.Mailchimp.Features; + +// ReSharper disable once CheckNamespace +namespace Elsa.Extensions; + +/// +/// Extends with methods to use Mailchimp integration. +/// +public static class ModuleExtensions +{ + /// + /// Installs the Mailchimp API feature. + /// + public static IModule UseMailchimp(this IModule module, Action? configure = null) + { + return module.Use(configure); + } +} \ No newline at end of file diff --git a/src/integrations/Elsa.Integrations.Mailchimp/Features/MailchimpFeature.cs b/src/integrations/Elsa.Integrations.Mailchimp/Features/MailchimpFeature.cs new file mode 100644 index 00000000..aa0c9b1a --- /dev/null +++ b/src/integrations/Elsa.Integrations.Mailchimp/Features/MailchimpFeature.cs @@ -0,0 +1,19 @@ +using Elsa.Features.Abstractions; +using Elsa.Features.Services; +using Elsa.Integrations.Mailchimp.Services; +using Microsoft.Extensions.DependencyInjection; + +namespace Elsa.Integrations.Mailchimp.Features; + +/// +/// Represents a feature for setting up Mailchimp integration within the Elsa framework. +/// +public class MailchimpFeature(IModule module) : FeatureBase(module) +{ + /// + /// Applies the feature to the specified service collection. + /// + public override void Apply() => + Services + .AddSingleton(); +} \ No newline at end of file diff --git a/src/integrations/Elsa.Integrations.Mailchimp/FodyWeavers.xml b/src/integrations/Elsa.Integrations.Mailchimp/FodyWeavers.xml new file mode 100644 index 00000000..00e1d9a1 --- /dev/null +++ b/src/integrations/Elsa.Integrations.Mailchimp/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/integrations/Elsa.Integrations.Mailchimp/README.md b/src/integrations/Elsa.Integrations.Mailchimp/README.md new file mode 100644 index 00000000..dfed884a --- /dev/null +++ b/src/integrations/Elsa.Integrations.Mailchimp/README.md @@ -0,0 +1,168 @@ +# Elsa.Integrations.Mailchimp + +This module provides integration with Mailchimp for Elsa Workflows. It enables workflows to interact with Mailchimp, supporting campaign management, list handling, segmentation, and event monitoring. + +## Features + +- **Campaign Management**: Create, update, delete, and retrieve campaigns +- **List Management**: Create, update, delete, and retrieve lists +- **Member Management**: Add, update, remove list members and manage subscriptions +- **Segmentation**: Manage static segments and member assignments +- **Event Monitoring**: Watch for list events, subscriber changes, and campaign activities +- **API Integration**: Make arbitrary API calls to Mailchimp's REST API + +## Getting Started + +### Installation + +Add the package reference to your project: + +```xml + +``` + +### Configuration + +Register the Mailchimp integration in your service configuration: + +```csharp +builder.Services.AddElsa(elsa => +{ + elsa.UseMailchimp(); +}); +``` + +## Authentication + +The integration supports authentication via Mailchimp API Keys. You can obtain an API key from your Mailchimp account settings. + +## Available Activities + +### Lists + +| Activity | Description | +|----------|-------------| +| CreateList | Creates a new list | +| GetList | Retrieves metadata of a specified list | +| UpdateList | Updates an existing list | +| DeleteList | Deletes a list | +| SearchLists | Searches for lists | + +### Members + +| Activity | Description | +|----------|-------------| +| AddUpdateListMember | Adds or updates a list member | +| AddUpdateSubscriber | Adds an email address to a subscriber list | +| EditSubscriber | Edits an existing subscriber | +| DeleteSubscriber | Archives or permanently deletes a subscriber | +| GetSubscriber | Retrieves metadata of a subscriber by email | +| SearchSubscribers | Searches for subscribers | + +### Campaigns + +| Activity | Description | +|----------|-------------| +| CreateCampaign | Creates a new campaign | +| GetCampaign | Retrieves metadata of a specified campaign | +| UpdateCampaign | Updates an existing campaign | +| DeleteCampaign | Deletes a campaign | +| SearchCampaigns | Searches for campaigns | +| PerformCampaignAction | Performs a campaign action (send, schedule, etc.) | + +### Segments + +| Activity | Description | +|----------|-------------| +| AddMemberToSegment | Adds a new member to a static segment | +| RemoveMemberFromSegment | Removes a member from the specified static segment | +| ListSegmentMembers | Retrieves a list of all segment members | + +### Events and Triggers + +| Activity | Description | +|----------|-------------| +| WatchLists | Triggers when a new list is created | +| WatchSubscribers | Triggers when a new subscriber joins a list or is updated | +| WatchCampaigns | Triggers when a new campaign is created or sent | +| WatchUnsubscribes | Triggers when a subscriber unsubscribes from a campaign | + +### Other + +| Activity | Description | +|----------|-------------| +| MakeAPICall | Performs an arbitrary authorized API call | +| AddEvent | Adds a new event to the member | +| AddRemoveMemberTags | Adds or removes tags from a list member | +| CreateMergeField | Creates a new merge field | +| GetMergeField | Retrieves an existing merge field | +| UpdateMergeField | Updates an existing merge field | +| DeleteMergeField | Deletes an existing merge field | + +## Example Usage + +Here's an example of adding a subscriber to a Mailchimp list from a workflow: + +```csharp +// Create a workflow definition +public class AddSubscriberWorkflow : IWorkflow +{ + public void Build(IWorkflowBuilder builder) + { + builder + .StartWith(activity => + { + activity.ApiKey = "your-mailchimp-api-key"; + activity.ListId = "your-list-id"; + activity.EmailAddress = new Input("subscriber@example.com"); + activity.Status = "subscribed"; + activity.FirstName = "John"; + activity.LastName = "Doe"; + }) + .Then(activity => + { + activity.Content = new JavaScriptValue("return `Added subscriber: ${addUpdateListMember.updatedMember.emailAddress}`;"); + }); + } +} +``` + +### Creating a Campaign + +```csharp +public class CreateMailchimpCampaignWorkflow : IWorkflow +{ + public void Build(IWorkflowBuilder builder) + { + builder + .StartWith(activity => + { + activity.ApiKey = "your-mailchimp-api-key"; + activity.Type = "regular"; + activity.ListId = "your-list-id"; + activity.SubjectLine = "Welcome to our newsletter!"; + activity.Title = "Welcome Campaign"; + activity.FromName = "Your Company"; + activity.ReplyTo = "no-reply@yourcompany.com"; + }) + .Then(activity => + { + activity.Content = new JavaScriptValue("return `Created campaign: ${createCampaign.createdCampaign.id}`;"); + }); + } +} +``` + +## Notes + +- All activities require a valid Mailchimp API key +- The API key should be stored securely and not hardcoded in workflows +- Some trigger activities require webhook configuration in Mailchimp +- Rate limiting applies based on your Mailchimp plan +- Ensure compliance with email marketing regulations (GDPR, CAN-SPAM, etc.) + +## References + +- 📖 [Mailchimp API Documentation](https://mailchimp.com/developer/) +- 🛠️ [Mailchimp Developer Resources](https://mailchimp.com/developer/guides/) +- 📚 [MailChimp.Net Library](https://github.com/brandonseydel/MailChimp.Net) \ No newline at end of file diff --git a/src/integrations/Elsa.Integrations.Mailchimp/Services/MailchimpClientFactory.cs b/src/integrations/Elsa.Integrations.Mailchimp/Services/MailchimpClientFactory.cs new file mode 100644 index 00000000..88aa4ab6 --- /dev/null +++ b/src/integrations/Elsa.Integrations.Mailchimp/Services/MailchimpClientFactory.cs @@ -0,0 +1,39 @@ +using MailChimp.Net; +using MailChimp.Net.Interfaces; + +namespace Elsa.Integrations.Mailchimp.Services; + +/// +/// Factory for creating Mailchimp API clients. +/// +public class MailchimpClientFactory +{ + private readonly SemaphoreSlim _semaphore = new(1, 1); + private readonly Dictionary _mailchimpClients = new(); + + /// + /// Gets a Mailchimp API client for the specified API key. + /// + public IMailChimpManager GetClient(string apiKey) + { + if (_mailchimpClients.TryGetValue(apiKey, out IMailChimpManager? client)) + return client; + + try + { + _semaphore.Wait(); + + if (_mailchimpClients.TryGetValue(apiKey, out client)) + return client; + + var newClient = new MailChimpManager(apiKey); + + _mailchimpClients[apiKey] = newClient; + return newClient; + } + finally + { + _semaphore.Release(); + } + } +} \ No newline at end of file diff --git a/test/unit/Elsa.Integrations.Mailchimp.Tests/Elsa.Integrations.Mailchimp.Tests.csproj b/test/unit/Elsa.Integrations.Mailchimp.Tests/Elsa.Integrations.Mailchimp.Tests.csproj new file mode 100644 index 00000000..2cbee1b5 --- /dev/null +++ b/test/unit/Elsa.Integrations.Mailchimp.Tests/Elsa.Integrations.Mailchimp.Tests.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + \ No newline at end of file diff --git a/test/unit/Elsa.Integrations.Mailchimp.Tests/Features/MailchimpFeatureTests.cs b/test/unit/Elsa.Integrations.Mailchimp.Tests/Features/MailchimpFeatureTests.cs new file mode 100644 index 00000000..561737fb --- /dev/null +++ b/test/unit/Elsa.Integrations.Mailchimp.Tests/Features/MailchimpFeatureTests.cs @@ -0,0 +1,50 @@ +using Elsa.Features.Services; +using Elsa.Integrations.Mailchimp.Features; +using Elsa.Integrations.Mailchimp.Services; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Xunit; + +namespace Elsa.Integrations.Mailchimp.Tests.Features; + +public class MailchimpFeatureTests +{ + [Fact] + public void Apply_ShouldRegisterMailchimpClientFactory() + { + // Arrange + var services = new ServiceCollection(); + var mockModule = new Mock(); + mockModule.Setup(m => m.Services).Returns(services); + + var feature = new MailchimpFeature(mockModule.Object); + + // Act + feature.Apply(); + + // Assert + var serviceProvider = services.BuildServiceProvider(); + var factory = serviceProvider.GetService(); + Assert.NotNull(factory); + } + + [Fact] + public void Apply_ShouldRegisterMailchimpClientFactoryAsSingleton() + { + // Arrange + var services = new ServiceCollection(); + var mockModule = new Mock(); + mockModule.Setup(m => m.Services).Returns(services); + + var feature = new MailchimpFeature(mockModule.Object); + + // Act + feature.Apply(); + + // Assert + var serviceProvider = services.BuildServiceProvider(); + var factory1 = serviceProvider.GetService(); + var factory2 = serviceProvider.GetService(); + Assert.Same(factory1, factory2); + } +} \ No newline at end of file diff --git a/test/unit/Elsa.Integrations.Mailchimp.Tests/Services/MailchimpClientFactoryTests.cs b/test/unit/Elsa.Integrations.Mailchimp.Tests/Services/MailchimpClientFactoryTests.cs new file mode 100644 index 00000000..150d057c --- /dev/null +++ b/test/unit/Elsa.Integrations.Mailchimp.Tests/Services/MailchimpClientFactoryTests.cs @@ -0,0 +1,52 @@ +using Elsa.Integrations.Mailchimp.Services; +using Xunit; + +namespace Elsa.Integrations.Mailchimp.Tests.Services; + +public class MailchimpClientFactoryTests +{ + [Fact] + public void GetClient_ShouldReturnClient_WhenValidApiKeyProvided() + { + // Arrange + var factory = new MailchimpClientFactory(); + var apiKey = "test-api-key-us1"; + + // Act + var client = factory.GetClient(apiKey); + + // Assert + Assert.NotNull(client); + } + + [Fact] + public void GetClient_ShouldReturnSameInstance_WhenCalledMultipleTimesWithSameApiKey() + { + // Arrange + var factory = new MailchimpClientFactory(); + var apiKey = "test-api-key-us1"; + + // Act + var client1 = factory.GetClient(apiKey); + var client2 = factory.GetClient(apiKey); + + // Assert + Assert.Same(client1, client2); + } + + [Fact] + public void GetClient_ShouldReturnDifferentInstances_WhenCalledWithDifferentApiKeys() + { + // Arrange + var factory = new MailchimpClientFactory(); + var apiKey1 = "test-api-key-1-us1"; + var apiKey2 = "test-api-key-2-us1"; + + // Act + var client1 = factory.GetClient(apiKey1); + var client2 = factory.GetClient(apiKey2); + + // Assert + Assert.NotSame(client1, client2); + } +} \ No newline at end of file