Skip to content

Commit

Permalink
Merge pull request #5989 from signalco-io/next
Browse files Browse the repository at this point in the history
Next
  • Loading branch information
AleksandarDev authored Sep 29, 2024
2 parents 95ba987 + a804e6f commit b6ef276
Show file tree
Hide file tree
Showing 22 changed files with 158 additions and 217 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/infra-deploy_reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ jobs:
run: pnpm build --filter=${{ inputs.packageName }}

- name: ⚡ Preview Deploy Infrastructure
uses: pulumi/actions@v5
uses: pulumi/actions@v6
with:
command: ${{ inputs.command }}
stack-name: signalco/${{ inputs.project }}/${{ steps.extract_branch.outputs.stack }}
Expand Down
2 changes: 1 addition & 1 deletion cloud/src/Signal.Core/CoreExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public static class CoreExtensions
{
public static IServiceCollection AddCore(this IServiceCollection services) =>
services
.AddTransient<INotificationSmtpService, NotificationSmtpService>()
.AddTransient<INotificationSmtpService, NotificationEmailService>()
.AddTransient<INotificationService, NotificationService>()
.AddTransient<ISharingService, SharingService>()
.AddTransient<IEntityService, EntityService>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ Task SendAsync(
string recipientEmail,
string title,
string content,
string? sender = "system",
CancellationToken cancellationToken = default);
}
40 changes: 40 additions & 0 deletions cloud/src/Signal.Core/Notifications/NotificationEmailService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Azure.Communication.Email;
using Signal.Core.Secrets;
using System.Threading;
using System.Threading.Tasks;

namespace Signal.Core.Notifications;

internal class NotificationEmailService(ISecretsProvider secretsProvider) : INotificationSmtpService
{
public async Task SendAsync(
string recipientEmail,
string title,
string content,
string? sender = "system",
CancellationToken cancellationToken = default)
{
var acsConnectionString = await secretsProvider.GetSecretAsync(SecretKeys.AzureCommunicationServices.ConnectionString, cancellationToken);
var acsDomain = await secretsProvider.GetSecretAsync(SecretKeys.AzureCommunicationServices.Domain, cancellationToken);


var emailClient = new EmailClient(acsConnectionString);
var emailSendOperation = await emailClient.SendAsync(
Azure.WaitUntil.Started,
$"{sender ?? "system"}@{acsDomain}",
recipientEmail,
title,
content,
cancellationToken: cancellationToken);

while (true)
{
await emailSendOperation.UpdateStatusAsync(cancellationToken);
if (emailSendOperation.HasCompleted)
{
break;
}
await Task.Delay(100, cancellationToken);
}
}
}
2 changes: 1 addition & 1 deletion cloud/src/Signal.Core/Notifications/NotificationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ await smtpService.SendAsync(
throw new InvalidOperationException($"Email not available for user {userId}"),
content.Title,
content.Content?.ToString() ?? string.Empty,
cancellationToken);
cancellationToken: cancellationToken);
}
}
catch
Expand Down
31 changes: 0 additions & 31 deletions cloud/src/Signal.Core/Notifications/NotificationSmtpService.cs

This file was deleted.

6 changes: 6 additions & 0 deletions cloud/src/Signal.Core/Secrets/SecretKeys.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ public static class AzureSpeech
public const string Region = "AzureSpeech--Region";
}

public static class AzureCommunicationServices
{
public const string ConnectionString = "AcsConnectionString";
public const string Domain = "AcsDomain";
}

public static class SmtpNotification
{
public const string Username = "SmtpNotificationUsername";
Expand Down
1 change: 1 addition & 0 deletions cloud/src/Signal.Core/Signal.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<PackageLicenseExpression>AGPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Communication.Email" Version="1.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.1.0" />
Expand Down
4 changes: 2 additions & 2 deletions cloud/src/Signalco.Api.RemoteBrowser/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

RUN apt-get update && \
apt install -y libglib2.0-0 libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libasound2 libpango-1.0-0 libpangocairo-1.0-0

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src

COPY . .
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>75b1b62c-8ca6-40ee-a213-e98c0340b5e6</UserSecretsId>
Expand Down
26 changes: 19 additions & 7 deletions infra/apps/cloud-primary/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import {
createFunctionsStorage,
createLogWorkspace,
createAppInsights,
acsEmails,
} from '@infra/pulumi/azure';
import { createSes } from '@infra/pulumi/aws';
import { apiStatusCheck } from '@infra/pulumi/checkly';
import { dnsRecord } from '@infra/pulumi/cloudflare';
import { publishProjectAsync } from '@infra/pulumi/dotnet';
Expand Down Expand Up @@ -160,8 +160,22 @@ const up = async () => {
// Create general storage and prepare tables
const storage = createStorageAccount(resourceGroup, storagePrefix, shouldProtect);

// Create AWS SES service
const ses = createSes(`ses-${stack}`, 'notification');
// ACS (Email)
// DomainName can be root (e.g. signalco.io) or subdomain (e.g. next.signalco.io)
// ACS Domain name should be the root domain (e.g. signalco.io)
// Subdomain is used for the ACS subdomain (e.g. next)
const acsDomainName = domainName.split('.').length > 2 ? domainName.split('.').slice(1).join('.') : domainName;
const acsSubDomain = domainName.split('.').length > 2 ? domainName.split('.')[0] : null;
const { acsPrimaryConnectionString } = await acsEmails(
'cp',
resourceGroup,
acsDomainName,
acsSubDomain,
stack,
[
{ subdomain: 'notifications', displayName: 'Signalco Notifications' },
{ subdomain: 'system', displayName: 'Signalco' },
]);

// Create and populate vault
const vault = createKeyVault(resourceGroup, keyvaultPrefix, shouldProtect, [
Expand All @@ -188,10 +202,8 @@ const up = async () => {
};

const internalEnvVariables = {
SmtpNotificationServerUrl: ses.smtpServer,
SmtpNotificationFromDomain: ses.smtpFromDomain,
SmtpNotificationUsername: ses.smtpUsername,
SmtpNotificationPassword: ses.smtpPassword,
AcsConnectionString: acsPrimaryConnectionString,
AcsDomain: acsDomainName,
'Auth0_ClientId_Station': config.requireSecret('secret-auth0ClientIdStation'),
'Auth0_ClientSecret_Station': config.requireSecret('secret-auth0ClientSecretStation'),
'HCaptcha_Secret': config.requireSecret('secret-hcaptchaSecret'),
Expand Down
1 change: 0 additions & 1 deletion infra/apps/remote-browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
},
"dependencies": {
"@checkly/pulumi": "1.1.4",
"@pulumi/aws": "6.54.0",
"@pulumi/azure-native": "2.63.0",
"@pulumi/cloudflare": "5.39.1",
"@pulumi/command": "1.0.1",
Expand Down
1 change: 0 additions & 1 deletion infra/apps/uier/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
},
"dependencies": {
"@checkly/pulumi": "1.1.4",
"@pulumi/aws": "6.54.0",
"@pulumi/azure-native": "2.63.0",
"@pulumi/cloudflare": "5.39.1",
"@pulumi/command": "1.0.1",
Expand Down
78 changes: 12 additions & 66 deletions infra/apps/workingparty/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import { DatabaseAccount, SqlResourceSqlDatabase, SqlResourceSqlContainer, Datab
import { nextJsApp } from '@infra/pulumi/vercel';
import { dnsRecord } from '@infra/pulumi/cloudflare';
import { ProjectDomain, ProjectEnvironmentVariable } from '@pulumiverse/vercel';
import { CommunicationService, EmailService, Domain, DomainManagement, UserEngagementTracking, SenderUsername, listCommunicationServiceKeysOutput } from '@pulumi/azure-native/communication/index.js';
import { createStorageAccount } from '@infra/pulumi/azure';
import { createStorageAccount, acsEmails } from '@infra/pulumi/azure';

const up = async () => {
const config = new Config();
Expand Down Expand Up @@ -114,70 +113,17 @@ const up = async () => {
queueName: 'email-queue',
});

// Create ACS
const aes = new EmailService('wp-azure-email-service', {
dataLocation: 'Europe',
emailServiceName: 'wpemail',
location: 'Global',
resourceGroupName: resourceGroup.name,
});
const aesDomain = new Domain('wp-aes-domain', {
resourceGroupName: resourceGroup.name,
emailServiceName: aes.name,
domainManagement: DomainManagement.CustomerManaged,
domainName: domainName,
location: 'Global',
userEngagementTracking: UserEngagementTracking.Disabled,
});
if (aesDomain.verificationRecords.domain) {
const aesDomainVerifyDataName = aesDomain.verificationRecords.domain.apply(domainVerification => domainVerification?.name ?? '');
const aesDomainVerifyDataValue = aesDomain.verificationRecords.domain.apply(domainVerification => domainVerification?.value ?? '');
dnsRecord('wp-aes-domain-domainverify', aesDomainVerifyDataName, aesDomainVerifyDataValue, 'TXT', false);
}
if (aesDomain.verificationRecords.sPF) {
const aesDomainVerifySpfName = aesDomain.verificationRecords.sPF.apply(dkimVerification => dkimVerification?.name ?? '');
const aesDomainVerifySpfValue = aesDomain.verificationRecords.sPF.apply(dkimVerification => dkimVerification?.value ?? '');
dnsRecord('wp-aes-domain-spf', aesDomainVerifySpfName, aesDomainVerifySpfValue, 'TXT', false);
}
if (aesDomain.verificationRecords.dKIM) {
const aesDomainVerifyDkimName = aesDomain.verificationRecords.dKIM.apply(dkimVerification => subdomain ? (`${dkimVerification?.name ?? ''}.${subdomain}`) : dkimVerification?.name ?? '');
const aesDomainVerifyDkimValue = aesDomain.verificationRecords.dKIM.apply(dkimVerification => dkimVerification?.value ?? '');
dnsRecord('wp-aes-domain-dkim', aesDomainVerifyDkimName, aesDomainVerifyDkimValue, 'CNAME', false);
}
if (aesDomain.verificationRecords.dKIM2) {
const aesDomainVerifyDkimName = aesDomain.verificationRecords.dKIM2.apply(dkimVerification => subdomain ? (`${dkimVerification?.name ?? ''}.${subdomain}`) : dkimVerification?.name ?? '');
const aesDomainVerifyDkimValue = aesDomain.verificationRecords.dKIM2.apply(dkimVerification => dkimVerification?.value ?? '');
dnsRecord('wp-aes-domain-dkim2', aesDomainVerifyDkimName, aesDomainVerifyDkimValue, 'CNAME', false);
}
// NOTE: Domain needs to be verified manually in Azure Communication Services

new SenderUsername('wp-aes-sender-notifications', {
resourceGroupName: resourceGroup.name,
emailServiceName: aes.name,
domainName: aesDomain.name,
displayName: 'WorkingParty Notifications',
senderUsername: 'notifications',
username: 'notifications',
});
new SenderUsername('wp-aes-sender-system', {
resourceGroupName: resourceGroup.name,
emailServiceName: aes.name,
domainName: aesDomain.name,
displayName: 'WorkingParty',
senderUsername: 'system',
username: 'system',
});
const communicaionService = new CommunicationService('wp-azure-communication-service', {
communicationServiceName: `wpacs-${stack}`,
dataLocation: 'Europe',
location: 'Global',
resourceGroupName: resourceGroup.name,
linkedDomains: [aesDomain.id],
});
const acsPrimaryConnectionString = listCommunicationServiceKeysOutput({
resourceGroupName: resourceGroup.name,
communicationServiceName: communicaionService.name,
}).apply(keys => keys.primaryConnectionString ?? '');
// ACS (Email)
const { acsPrimaryConnectionString } = await acsEmails(
'wp',
resourceGroup,
domainName,
subdomain,
stack,
[
{ subdomain: 'notifications', displayName: 'WorkingParty Notifications' },
{ subdomain: 'system', displayName: 'WorkingParty' },
]);

// Vercel setup
const app = nextJsApp('wp', 'workingparty', 'web/apps/workingparty');
Expand Down
4 changes: 0 additions & 4 deletions infra/packages/pulumi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@
"./azure": {
"import": "./src/azure/index.ts"
},
"./aws": {
"import": "./src/aws/index.ts"
},
"./cloudflare": {
"import": "./src/cloudflare/index.ts"
},
Expand Down Expand Up @@ -40,7 +37,6 @@
},
"dependencies": {
"@checkly/pulumi": "1.1.4",
"@pulumi/aws": "6.54.0",
"@pulumi/azure-native": "2.63.0",
"@pulumi/cloudflare": "5.39.1",
"@pulumi/command": "1.0.1",
Expand Down
Loading

0 comments on commit b6ef276

Please sign in to comment.