Skip to content

Commit

Permalink
Deploy Twitter integration to PROD. (#97)
Browse files Browse the repository at this point in the history
* Feature/publish to twitter (#95)

* Post to Twitter when a word is published.

* Upgrade deployment workflow for the API to.NET 8 on all environments. (#96)
  • Loading branch information
Zifah authored Aug 23, 2024
1 parent ba73c97 commit c34ae93
Show file tree
Hide file tree
Showing 23 changed files with 388 additions and 107 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/api-deploy-PROD.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
secrets: inherit
with:
project-name: Api
dotnet-version: '6.0.x'
dotnet-version: '8.0.x'
service-name: ${{ needs.fetch-vars.outputs.service_name }}
service-path: ${{ needs.fetch-vars.outputs.service_path }}
environment: PROD
2 changes: 1 addition & 1 deletion .github/workflows/api-deploy-staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
secrets: inherit
with:
project-name: Api
dotnet-version: '6.0.x'
dotnet-version: '8.0.x'
service-name: ${{ needs.fetch-vars.outputs.service_name }}
service-path: ${{ needs.fetch-vars.outputs.service_path }}
environment: staging
10 changes: 3 additions & 7 deletions Api/Api.csproj
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>15373697-caf3-4a5a-b976-46077b7bff45</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerComposeProjectPath>..\docker-compose.dcproj</DockerComposeProjectPath>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.9.1" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.4" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Application\Application.csproj" />
<ProjectReference Include="..\Infrastructure.MongoDB\Infrastructure.MongoDB.csproj" />
<ProjectReference Include="..\Infrastructure\Infrastructure.csproj" />
</ItemGroup>

</Project>
</Project>
4 changes: 2 additions & 2 deletions Api/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["Api/Api.csproj", "Api/"]
RUN dotnet restore "Api/Api.csproj"
Expand Down
31 changes: 22 additions & 9 deletions Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,19 @@
using Core.Events;
using Core.StringObjectConverters;
using FluentValidation;
using Infrastructure;
using Infrastructure.MongoDB;
using Infrastructure.Services;
using Microsoft.AspNetCore.Authentication;
using Microsoft.OpenApi.Models;
using MySqlConnector;
using System.Collections.Concurrent;
using System.Text.Json.Serialization;

var builder = WebApplication.CreateBuilder(args);
var Configuration = builder.Configuration;
var configuration = builder.Configuration;
configuration.AddEnvironmentVariables("YND_");

string DevCORSAllowAll = "AllowAllForDev";
var services = builder.Services;

Expand Down Expand Up @@ -81,28 +86,36 @@
}
});
});
var mongoDbSettings = Configuration.GetSection("MongoDB");
var mongoDbSettings = configuration.GetSection("MongoDB");
services.InitializeDatabase(mongoDbSettings.GetValue<string>("ConnectionString"), mongoDbSettings.GetValue<string>("DatabaseName"));

builder.Services.AddTransient(x =>
new MySqlConnection(builder.Configuration.GetSection("MySQL:ConnectionString").Value));

services.AddScoped<NameEntryService>();
services.AddScoped<GeoLocationsService>();
services.AddScoped<NameEntryFeedbackService>();
services.AddScoped<IEventPubService, EventPubService>();
services.AddScoped<SearchService>();
services.AddScoped<SuggestedNameService>();
services.AddScoped<UserService>();
services.AddSingleton<NameEntryService>();
services.AddSingleton<GeoLocationsService>();
services.AddSingleton<NameEntryFeedbackService>();
services.AddSingleton<IEventPubService, EventPubService>();
services.AddSingleton<SearchService>();
services.AddSingleton<SuggestedNameService>();
services.AddSingleton<UserService>();
services.AddScoped<GeoLocationValidator>();
services.AddScoped<EmbeddedVideoValidator>();
services.AddScoped<EtymologyValidator>();
services.AddScoped<SqlToMongoMigrator>();
services.AddSingleton<IRecentIndexesCache, RecentIndexesCache>();
services.AddSingleton<IRecentSearchesCache, RecentSearchesCache>();

//Validation
services.AddValidatorsFromAssemblyContaining<CreateUserValidator>();

services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(ExactNameSearchedAdapter).Assembly));

// Twitter integration configuration
services.AddSingleton<ConcurrentQueue<PostPublishedNameCommand>>();
services.AddTwitterClient(configuration);
services.AddHostedService<NamePostingService>();


var app = builder.Build();

Expand Down
11 changes: 9 additions & 2 deletions Api/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
"Application.Services.BasicAuthenticationHandler": "Warning"
}
},
"AllowedHosts": "*"

"AllowedHosts": "*",
"Twitter": {
"AccessToken": "your-access-token",
"AccessTokenSecret": "your-access-token-secret",
"ConsumerKey": "your-consumer-key",
"ConsumerSecret": "your-consumer-secret",
"NameUrlPrefix": "https://www.yorubaname.com/entries",
"TweetTemplate": "New name entry: {name}, {meaning}. More here: {link}"
}
}
6 changes: 6 additions & 0 deletions Application/Application.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<Compile Remove="Interfaces\**" />
<EmbeddedResource Remove="Interfaces\**" />
<None Remove="Interfaces\**" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="Dapper" Version="2.1.35" />
Expand Down
38 changes: 38 additions & 0 deletions Application/EventHandlers/DeletedNameCachingHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Application.Events;
using Core.Cache;
using MediatR;
using Microsoft.Extensions.Logging;

namespace Application.EventHandlers
{
public class DeletedNameCachingHandler : INotificationHandler<NameDeletedAdapter>
{
private readonly IRecentIndexesCache _recentIndexesCache;
private readonly IRecentSearchesCache _recentSearchesCache;
private readonly ILogger<DeletedNameCachingHandler> _logger;

public DeletedNameCachingHandler(
IRecentIndexesCache recentIndexesCache,
IRecentSearchesCache recentSearchesCache,
ILogger<DeletedNameCachingHandler> logger
)
{
_recentIndexesCache = recentIndexesCache;
_recentSearchesCache = recentSearchesCache;
_logger = logger;
}

public async Task Handle(NameDeletedAdapter notification, CancellationToken cancellationToken)
{
try
{
await _recentIndexesCache.Remove(notification.Name);
await _recentSearchesCache.Remove(notification.Name);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while removing deleted name '{name}' from cache.", notification.Name);
}
}
}
}
38 changes: 0 additions & 38 deletions Application/EventHandlers/NameDeletedEventHandler.cs

This file was deleted.

7 changes: 6 additions & 1 deletion Application/EventHandlers/NameIndexedEventHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,20 @@ namespace Application.EventHandlers
public class NameIndexedEventHandler : INotificationHandler<NameIndexedAdapter>
{
public IRecentIndexesCache _recentIndexesCache;
private readonly IMediator _mediator;

public NameIndexedEventHandler(IRecentIndexesCache recentIndexesCache)
public NameIndexedEventHandler(
IRecentIndexesCache recentIndexesCache,
IMediator mediator)
{
_recentIndexesCache = recentIndexesCache;
_mediator = mediator;
}

public async Task Handle(NameIndexedAdapter notification, CancellationToken cancellationToken)
{
await _recentIndexesCache.Stack(notification.Name);
await _mediator.Publish(new PostPublishedNameCommand(notification.Name), cancellationToken);
}
}
}
25 changes: 25 additions & 0 deletions Application/EventHandlers/PostPublishedNameCommandHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Application.Events;
using MediatR;
using System.Collections.Concurrent;

namespace Application.EventHandlers
{
public class PostPublishedNameCommandHandler : INotificationHandler<PostPublishedNameCommand>
{
private readonly ConcurrentQueue<PostPublishedNameCommand> _nameQueue;

public PostPublishedNameCommandHandler(ConcurrentQueue<PostPublishedNameCommand> nameQueue)
{
_nameQueue = nameQueue;
}

public Task Handle(PostPublishedNameCommand notification, CancellationToken cancellationToken)
{
// Enqueue the indexed name for processing by the BackgroundService
_nameQueue.Enqueue(notification);

// Return a completed task, so it doesn't block the main thread
return Task.CompletedTask;
}
}
}
8 changes: 8 additions & 0 deletions Application/Events/PostPublishedNameCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using MediatR;

namespace Application.Events
{
public record PostPublishedNameCommand(string Name) : INotification
{
}
}
3 changes: 1 addition & 2 deletions Application/Services/EventPubService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ public EventPubService(IMediator mediator)
_mediator = mediator;
}


public async Task PublishEvent<T>(T theEvent)
{
var adapterClassName = typeof(T).Name + "Adapter";
Expand All @@ -24,7 +23,7 @@ public async Task PublishEvent<T>(T theEvent)
throw new InvalidOperationException("Adapter type not found for " + typeof(T).FullName);
}

var adapterEvent = Activator.CreateInstance(adapterType, theEvent);
var adapterEvent = Activator.CreateInstance(adapterType, theEvent)!;
await _mediator.Publish(adapterEvent);
}
}
Expand Down
14 changes: 7 additions & 7 deletions Infrastructure.MongoDB/DependencyInjection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ public static class DependencyInjection
public static void InitializeDatabase(this IServiceCollection services, string connectionString, string databaseName)
{
services.AddSingleton<IMongoClient, MongoClient>(s => new MongoClient(connectionString));
services.AddScoped(s => s.GetRequiredService<IMongoClient>().GetDatabase(databaseName));
services.AddSingleton(s => s.GetRequiredService<IMongoClient>().GetDatabase(databaseName));

services.AddScoped<INameEntryRepository, NameEntryRepository>();
services.AddScoped<IGeoLocationsRepository, GeoLocationsRepository>();
services.AddScoped<INameEntryFeedbackRepository, NameEntryFeedbackRepository>();
services.AddScoped<ISuggestedNameRepository, SuggestedNameRepository>();
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IEtymologyRepository, EtymologyRepository>();
services.AddSingleton<INameEntryRepository, NameEntryRepository>();
services.AddSingleton<IGeoLocationsRepository, GeoLocationsRepository>();
services.AddSingleton<INameEntryFeedbackRepository, NameEntryFeedbackRepository>();
services.AddSingleton<ISuggestedNameRepository, SuggestedNameRepository>();
services.AddSingleton<IUserRepository, UserRepository>();
services.AddSingleton<IEtymologyRepository, EtymologyRepository>();
}
}
}
13 changes: 13 additions & 0 deletions Infrastructure/Configuration/TwitterConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Infrastructure.Configuration
{
public record TwitterConfig(
string ConsumerKey,
string ConsumerSecret,
string AccessToken,
string AccessTokenSecret,
string NameUrlPrefix,
string TweetTemplate)
{
public TwitterConfig() : this("", "", "", "", "", "") { }
}
}
32 changes: 32 additions & 0 deletions Infrastructure/DependencyInjection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Infrastructure.Configuration;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Tweetinvi;

namespace Infrastructure
{
public static class DependencyInjection
{
private const string ConfigSectionName = "Twitter";

public static IServiceCollection AddTwitterClient(this IServiceCollection services, IConfiguration configuration)
{
var config = configuration.GetRequiredSection(ConfigSectionName);

services.Configure<TwitterConfig>(config);

services.AddSingleton<ITwitterClient>(provider =>
{
var twitterConfig = config.Get<TwitterConfig>()!;
return new TwitterClient(
twitterConfig.ConsumerKey,
twitterConfig.ConsumerSecret,
twitterConfig.AccessToken,
twitterConfig.AccessTokenSecret
);
});

return services;
}
}
}
20 changes: 20 additions & 0 deletions Infrastructure/Infrastructure.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
<PackageReference Include="TweetinviAPI" Version="5.0.4" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Application\Application.csproj" />
</ItemGroup>

</Project>
Loading

0 comments on commit c34ae93

Please sign in to comment.