From 0387bb74e3d5be69608f3ca1cb8d63a9c7e0150a Mon Sep 17 00:00:00 2001 From: eugbyte Date: Sat, 4 Oct 2025 23:09:52 +0800 Subject: [PATCH 01/13] chore: tf-azure-ai-foundry --- .../Features/Files/Services/ObjectStorageService.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs b/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs index 285c02b..ef00731 100644 --- a/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs +++ b/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs @@ -83,10 +83,11 @@ public async Task PassesContentModeration(BinaryData binaryData) { } AnalyzeImageResult result = response.Value; - int? score = result.CategoriesAnalysis + int score = result.CategoriesAnalysis .Select(v => v.Severity) + .DefaultIfEmpty(0) .Aggregate((a, b) => a + b) - ?? 0; + .GetValueOrDefault(0); return score == 0; } } \ No newline at end of file From 8551736b4b4782841322696afc3c2a7af4b3f4f4 Mon Sep 17 00:00:00 2001 From: eugbyte Date: Sat, 4 Oct 2025 23:18:25 +0800 Subject: [PATCH 02/13] fmt: tf --- deploy/Terraform/ai-content-moderation.tf | 13 +++++++++++++ deploy/Terraform/container-app.tf | 11 +++-------- deploy/Terraform/variables.tf | 11 ----------- 3 files changed, 16 insertions(+), 19 deletions(-) create mode 100644 deploy/Terraform/ai-content-moderation.tf diff --git a/deploy/Terraform/ai-content-moderation.tf b/deploy/Terraform/ai-content-moderation.tf new file mode 100644 index 0000000..880c199 --- /dev/null +++ b/deploy/Terraform/ai-content-moderation.tf @@ -0,0 +1,13 @@ +# Azure AI Foundry Content Safety resource +resource "azurerm_cognitive_account" "content_safety" { + name = "cs-evently-dev-sea" + location = "azurerm_resource_group.rg.location" + resource_group_name = azurerm_resource_group.rg.name + kind = "ContentSafety" + sku_name = "S0" + + tags = { + Environment = "production" + Purpose = "content-safety" + } +} diff --git a/deploy/Terraform/container-app.tf b/deploy/Terraform/container-app.tf index ceba8de..12ef8e3 100644 --- a/deploy/Terraform/container-app.tf +++ b/deploy/Terraform/container-app.tf @@ -181,13 +181,13 @@ resource "azurerm_container_app" "app" { } env { - name = "AzureAIFoundry__ContentSafetyKey" - secret_name = "content-safety-key" + name = "AzureAIFoundry__ContentSafetyKey" + value = azurerm_cognitive_account.content_safety.primary_access_key } env { name = "AzureAIFoundry__ContentSafetyEndpoint" - value = var.content_safety_api + value = azurerm_cognitive_account.content_safety.endpoint } } } @@ -226,9 +226,4 @@ resource "azurerm_container_app" "app" { value = var.smtp_password } - secret { - name = "content-safety-key" - value = var.content_safety_key - } - } \ No newline at end of file diff --git a/deploy/Terraform/variables.tf b/deploy/Terraform/variables.tf index b53efd6..8c4c7c2 100644 --- a/deploy/Terraform/variables.tf +++ b/deploy/Terraform/variables.tf @@ -34,15 +34,4 @@ variable "smtp_password" { description = "SMTP password for email sending" type = string sensitive = true -} - -variable "content_safety_key" { - description = "Azure AI foundry content safety key" - type = string - sensitive = true -} - -variable "content_safety_api" { - description = "Azure AI foundry content safety api endpoint" - type = string } \ No newline at end of file From ed6140cfd14de920710b4086c849bae0571a0d49 Mon Sep 17 00:00:00 2001 From: eugbyte Date: Sat, 4 Oct 2025 23:20:24 +0800 Subject: [PATCH 03/13] fix: fmt --- deploy/Terraform/container-app.tf | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/deploy/Terraform/container-app.tf b/deploy/Terraform/container-app.tf index 12ef8e3..e0cb185 100644 --- a/deploy/Terraform/container-app.tf +++ b/deploy/Terraform/container-app.tf @@ -168,26 +168,26 @@ resource "azurerm_container_app" "app" { value = "Warning" } - # General Settings env { - name = "AllowedHosts" - value = "*" + name = "AzureAIFoundry__ContentSafetyKey" + value = azurerm_cognitive_account.content_safety.primary_access_key } - # Environment indicator env { - name = "ASPNETCORE_ENVIRONMENT" - value = "Production" + name = "AzureAIFoundry__ContentSafetyEndpoint" + value = azurerm_cognitive_account.content_safety.endpoint } + # General Settings env { - name = "AzureAIFoundry__ContentSafetyKey" - value = azurerm_cognitive_account.content_safety.primary_access_key + name = "AllowedHosts" + value = "*" } + # Environment indicator env { - name = "AzureAIFoundry__ContentSafetyEndpoint" - value = azurerm_cognitive_account.content_safety.endpoint + name = "ASPNETCORE_ENVIRONMENT" + value = "Production" } } } From 46ef1029dd897da03fca8e789f4862000b9f201c Mon Sep 17 00:00:00 2001 From: eugbyte Date: Sat, 4 Oct 2025 23:44:51 +0800 Subject: [PATCH 04/13] chore: update --- deploy/Terraform/ai-content-moderation.tf | 2 +- .../Files/Services/ObjectStorageService.cs | 42 +++++++++++++------ 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/deploy/Terraform/ai-content-moderation.tf b/deploy/Terraform/ai-content-moderation.tf index 880c199..a46e8a6 100644 --- a/deploy/Terraform/ai-content-moderation.tf +++ b/deploy/Terraform/ai-content-moderation.tf @@ -1,7 +1,7 @@ # Azure AI Foundry Content Safety resource resource "azurerm_cognitive_account" "content_safety" { name = "cs-evently-dev-sea" - location = "azurerm_resource_group.rg.location" + location = azurerm_resource_group.rg.location resource_group_name = azurerm_resource_group.rg.name kind = "ContentSafety" sku_name = "S0" diff --git a/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs b/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs index ef00731..20f8a1b 100644 --- a/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs +++ b/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs @@ -10,12 +10,26 @@ namespace Evently.Server.Features.Files.Services; // Based on https://tinyurl.com/5pam66xn -public sealed class ObjectStorageService(IOptions settings, ILogger logger) : IObjectStorageService { - private readonly BlobServiceClient _blobServiceClient = - new(settings.Value.StorageAccount.AzureStorageConnectionString); - private readonly ContentSafetyClient _contentSafetyClient = new( - endpoint: new Uri(settings.Value.AzureAiFoundry.ContentSafetyEndpoint), - credential: new AzureKeyCredential(settings.Value.AzureAiFoundry.ContentSafetyKey)); +public sealed class ObjectStorageService : IObjectStorageService { + private readonly BlobServiceClient _blobServiceClient; + private readonly ContentSafetyClient? _contentSafetyClient; + private readonly ILogger _logger; + + private ObjectStorageService(IOptions settings, ILogger logger) { + _logger = logger; + _blobServiceClient = + new BlobServiceClient(settings.Value.StorageAccount.AzureStorageConnectionString); + + try { + _contentSafetyClient = new ContentSafetyClient( + endpoint: new Uri(settings.Value.AzureAiFoundry.ContentSafetyEndpoint), + credential: new AzureKeyCredential(settings.Value.AzureAiFoundry.ContentSafetyKey)); + } catch (Exception ex) { + // silence the error + _logger.LogError("error creating content safety client: {}", ex.Message); + } + + } public async Task UploadFile(string containerName, string fileName, BinaryData binaryData, string mimeType = "application/octet-stream") { @@ -63,7 +77,7 @@ public async Task GetFile(string containerName, string fileName) { try { await blobClient.DownloadToAsync(ms); } catch (Exception ex) { - logger.LogError("error getting file: {}", ex.Message); + _logger.LogError("error getting file: {}", ex.Message); } byte[] bytes = ms.ToArray(); @@ -72,22 +86,26 @@ public async Task GetFile(string containerName, string fileName) { } public async Task PassesContentModeration(BinaryData binaryData) { + if (_contentSafetyClient is null) { + return false; + } + ContentSafetyImageData image = new(binaryData); AnalyzeImageOptions request = new(image); Response response; try { response = await _contentSafetyClient.AnalyzeImageAsync(request); } catch (RequestFailedException ex) { - logger.LogContentModerationError(ex.Status.ToString(), ex.ErrorCode ?? "", ex.Message); + _logger.LogContentModerationError(statusCode: ex.Status.ToString(), errorCode: ex.ErrorCode ?? "", ex.Message); throw; } AnalyzeImageResult result = response.Value; int score = result.CategoriesAnalysis - .Select(v => v.Severity) - .DefaultIfEmpty(0) - .Aggregate((a, b) => a + b) - .GetValueOrDefault(0); + .Select(v => v.Severity) + .DefaultIfEmpty(0) + .Aggregate((a, b) => a + b) + .GetValueOrDefault(0); return score == 0; } } \ No newline at end of file From 372b03d5bc454d62fe7ee3f2137a97415983dd50 Mon Sep 17 00:00:00 2001 From: eugbyte Date: Sat, 4 Oct 2025 23:46:22 +0800 Subject: [PATCH 05/13] chore: error handling safety --- .../Features/Files/Services/ObjectStorageService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs b/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs index 20f8a1b..009f2c2 100644 --- a/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs +++ b/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs @@ -26,7 +26,7 @@ private ObjectStorageService(IOptions settings, ILogger GetFile(string containerName, string fileName) { public async Task PassesContentModeration(BinaryData binaryData) { if (_contentSafetyClient is null) { - return false; + return true; } ContentSafetyImageData image = new(binaryData); From c81ce2a1ad07cee51088cdfac627f5cab718daeb Mon Sep 17 00:00:00 2001 From: eugbyte Date: Sun, 5 Oct 2025 09:27:05 +0800 Subject: [PATCH 06/13] fix: tf --- deploy/Terraform/ai-content-moderation.tf | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/deploy/Terraform/ai-content-moderation.tf b/deploy/Terraform/ai-content-moderation.tf index a46e8a6..65b99f8 100644 --- a/deploy/Terraform/ai-content-moderation.tf +++ b/deploy/Terraform/ai-content-moderation.tf @@ -4,10 +4,5 @@ resource "azurerm_cognitive_account" "content_safety" { location = azurerm_resource_group.rg.location resource_group_name = azurerm_resource_group.rg.name kind = "ContentSafety" - sku_name = "S0" - - tags = { - Environment = "production" - Purpose = "content-safety" - } -} + sku_name = "F0" +} \ No newline at end of file From acfc9a4cb366167cccdb02f69c26545288267068 Mon Sep 17 00:00:00 2001 From: eugbyte Date: Sun, 5 Oct 2025 09:38:10 +0800 Subject: [PATCH 07/13] fix: ctor --- .../Features/Files/Services/ObjectStorageService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs b/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs index 009f2c2..ee59eaf 100644 --- a/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs +++ b/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs @@ -15,7 +15,7 @@ public sealed class ObjectStorageService : IObjectStorageService { private readonly ContentSafetyClient? _contentSafetyClient; private readonly ILogger _logger; - private ObjectStorageService(IOptions settings, ILogger logger) { + public ObjectStorageService(IOptions settings, ILogger logger) { _logger = logger; _blobServiceClient = new BlobServiceClient(settings.Value.StorageAccount.AzureStorageConnectionString); From cb85e1114deb826112a21c16a17d39a1845b24f0 Mon Sep 17 00:00:00 2001 From: eugbyte Date: Sun, 5 Oct 2025 09:39:15 +0800 Subject: [PATCH 08/13] fix: fmt --- src/Evently.Server/Common/Extensions/LoggerExtension.cs | 2 +- .../Features/Files/Services/ObjectStorageService.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Evently.Server/Common/Extensions/LoggerExtension.cs b/src/Evently.Server/Common/Extensions/LoggerExtension.cs index 56f6a79..eb99e5f 100644 --- a/src/Evently.Server/Common/Extensions/LoggerExtension.cs +++ b/src/Evently.Server/Common/Extensions/LoggerExtension.cs @@ -28,7 +28,7 @@ public static partial void LogSuccessEmail( Message = "Error occurred at {context}: {errorMsg}")] public static partial void LogErrorContext( this ILogger logger, string context, string errorMsg); - + [LoggerMessage( EventId = 5, Level = LogLevel.Error, diff --git a/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs b/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs index ee59eaf..a6386e1 100644 --- a/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs +++ b/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs @@ -28,7 +28,6 @@ public ObjectStorageService(IOptions settings, ILogger UploadFile(string containerName, string fileName, BinaryData binaryData, From 3c666aa0e87ab5f09c8cb9fbb27f2c8953e0db63 Mon Sep 17 00:00:00 2001 From: eugbyte Date: Sun, 5 Oct 2025 09:41:50 +0800 Subject: [PATCH 09/13] fix: fmt --- src/Evently.Server/Common/Domains/Models/Settings.cs | 2 ++ .../Features/Bookings/Services/BookingService.cs | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Evently.Server/Common/Domains/Models/Settings.cs b/src/Evently.Server/Common/Domains/Models/Settings.cs index 75348cf..073e9e7 100644 --- a/src/Evently.Server/Common/Domains/Models/Settings.cs +++ b/src/Evently.Server/Common/Domains/Models/Settings.cs @@ -35,6 +35,8 @@ public sealed class EmailSettings { public string SmtpPassword { get; init; } = string.Empty; } +[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")] +// ReSharper disable once InconsistentNaming public sealed class AzureAIFoundry { public string ContentSafetyKey { get; init; } = string.Empty; public string ContentSafetyEndpoint { get; init; } = string.Empty; diff --git a/src/Evently.Server/Features/Bookings/Services/BookingService.cs b/src/Evently.Server/Features/Bookings/Services/BookingService.cs index 1ee3973..5d2e709 100644 --- a/src/Evently.Server/Features/Bookings/Services/BookingService.cs +++ b/src/Evently.Server/Features/Bookings/Services/BookingService.cs @@ -126,8 +126,4 @@ public async Task RenderTicket(string bookingId) { return await mediaRenderer.RenderComponentHtml(props); } - - public async Task Exists(string bookingId) { - return await db.Bookings.AnyAsync(b => b.BookingId == bookingId); - } } \ No newline at end of file From 42ac7bfafd22b7a4fdffd3af4ab4bf51313b636d Mon Sep 17 00:00:00 2001 From: eugbyte Date: Sun, 5 Oct 2025 09:43:49 +0800 Subject: [PATCH 10/13] fix: fmt --- .../Features/Files/Services/ObjectStorageService.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs b/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs index a6386e1..49238f4 100644 --- a/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs +++ b/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs @@ -101,10 +101,9 @@ public async Task PassesContentModeration(BinaryData binaryData) { AnalyzeImageResult result = response.Value; int score = result.CategoriesAnalysis - .Select(v => v.Severity) + .Select(v => v.Severity ?? 0) .DefaultIfEmpty(0) - .Aggregate((a, b) => a + b) - .GetValueOrDefault(0); + .Aggregate((a, b) => a + b); return score == 0; } } \ No newline at end of file From c40377e5a5765497e730224d2cb8394e6a18f23b Mon Sep 17 00:00:00 2001 From: eugbyte Date: Sun, 5 Oct 2025 09:48:31 +0800 Subject: [PATCH 11/13] fix: fmt --- .../Features/Emails/Services/EmailBackgroundService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Evently.Server/Features/Emails/Services/EmailBackgroundService.cs b/src/Evently.Server/Features/Emails/Services/EmailBackgroundService.cs index 237a1bb..d6e9a7a 100644 --- a/src/Evently.Server/Features/Emails/Services/EmailBackgroundService.cs +++ b/src/Evently.Server/Features/Emails/Services/EmailBackgroundService.cs @@ -1,7 +1,7 @@ using Evently.Server.Common.Domains.Entities; using Evently.Server.Common.Domains.Interfaces; +using Evently.Server.Common.Extensions; using System.Threading.Channels; -using LoggerExtension=Evently.Server.Common.Extensions.LoggerExtension; namespace Evently.Server.Features.Emails.Services; @@ -26,7 +26,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) { string html = await bookingService.RenderTicket(bookingId); await emailerAdapter.SendEmailAsync("noreply@evently", account.Email, "Test QR ticket", html); - LoggerExtension.LogSuccessEmail(logger, account.Email); + logger.LogSuccessEmail(account.Email); } catch (Exception ex) { logger.LogError("email error: {}", ex.Message); } From 51f860ab3c1169605e029992964e7da0a02d0140 Mon Sep 17 00:00:00 2001 From: eugbyte Date: Sun, 5 Oct 2025 09:51:58 +0800 Subject: [PATCH 12/13] fix: fmt --- .../Features/Files/Services/ObjectStorageService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs b/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs index 49238f4..2e35ec1 100644 --- a/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs +++ b/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs @@ -100,10 +100,10 @@ public async Task PassesContentModeration(BinaryData binaryData) { } AnalyzeImageResult result = response.Value; - int score = result.CategoriesAnalysis + int dangerScore = result.CategoriesAnalysis .Select(v => v.Severity ?? 0) .DefaultIfEmpty(0) .Aggregate((a, b) => a + b); - return score == 0; + return dangerScore == 0; } } \ No newline at end of file From 5ac7fd402fbd7f9f5df5dbf0f5ba5f4c0b4b13d3 Mon Sep 17 00:00:00 2001 From: eugbyte Date: Sun, 5 Oct 2025 09:58:03 +0800 Subject: [PATCH 13/13] fix: fmt --- .../Features/Files/Services/ObjectStorageService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs b/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs index 2e35ec1..add97e3 100644 --- a/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs +++ b/src/Evently.Server/Features/Files/Services/ObjectStorageService.cs @@ -26,7 +26,7 @@ public ObjectStorageService(IOptions settings, ILogger PassesContentModeration(BinaryData binaryData) { try { response = await _contentSafetyClient.AnalyzeImageAsync(request); } catch (RequestFailedException ex) { - _logger.LogContentModerationError(statusCode: ex.Status.ToString(), errorCode: ex.ErrorCode ?? "", ex.Message); + _logger.LogContentModerationError(ex.Status.ToString(), ex.ErrorCode ?? "", ex.Message); throw; }