diff --git a/.azure/container-group.yml b/.azure/container-group.yml new file mode 100644 index 0000000..f8f8090 --- /dev/null +++ b/.azure/container-group.yml @@ -0,0 +1,63 @@ +--- +apiVersion: "2021-10-01" +location: uk south +name: bottomly-live +properties: + containers: + - name: bottomly + properties: + image: ${IMAGE} + ports: + - port: 80 + protocol: TCP + resources: + requests: + cpu: 0.5 + memoryInGB: 0.5 + environmentVariables: + - name: bottomly_giphy_api_key + value: ${GIPHY_API_KEY} + - name: bottomly_brave_api_key + value: ${BRAVE_API_KEY} + - name: bottomly_slack_bot_token + secureValue: ${SLACK_TOKEN} + - name: bottomly_slack_app_token + secureValue: ${SLACK_APP_TOKEN} + - name: bottomly_prefix + value: ${PREFIX} + - name: bottomly_env + value: ${ENV} + - name: ConnectionStrings__mongodb + secureValue: ${MONGO_CONN_STR} + - name: bottomly_github_token + secureValue: ${RELEASE_HISTORY_TOKEN} + - name: APPLICATIONINSIGHTS_CONNECTION_STRING + value: ${APPINSIGHTS_CONN_STR} + - name: ConnectionStrings__bottomlymodel + secureValue: "Endpoint=http://localhost:11434;Key=${OLLAMA_API_KEY}" + - name: EnableLlm + value: "true" + - name: ollama + properties: + image: ollama/ollama:latest + resources: + requests: + cpu: 0.5 + memoryInGB: 0.5 + environmentVariables: + - name: OLLAMA_API_KEY + secureValue: ${OLLAMA_API_KEY} + imageRegistryCredentials: + - server: ghcr.io + username: ${REGISTRY_USERNAME} + password: ${REGISTRY_PASSWORD} + ipAddress: + type: Public + dnsNameLabel: ${DNS_NAME_LABEL} + ports: + - port: 80 + protocol: TCP + osType: Linux + restartPolicy: Always +tags: {} +type: Microsoft.ContainerInstance/containerGroups diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 1fd89ff..d52612f 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -1,40 +1,32 @@ +--- name: .NET Build and Test - on: push: - branches: [ "main" ] + branches: [main] pull_request: - branches: [ "main" ] - + branches: [main] permissions: packages: write - jobs: build-and-test: runs-on: ubuntu-latest - steps: - uses: actions/checkout@v4 - - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: '10.0.x' - + dotnet-version: 10.0.x - name: Restore dependencies run: dotnet restore bottomly.net.slnx - - name: Build run: dotnet build bottomly.net.slnx --no-restore --configuration Release - - name: Test - run: dotnet test bottomly.net.slnx --no-build --configuration Release --verbosity normal + run: dotnet test bottomly.net.slnx --no-build --configuration Release --verbosity + normal env: BOTTOMLY_BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }} - - name: Build Docker image run: docker build -t bottomly . - - name: Login to GitHub Container Registry if: github.event_name == 'push' && github.ref == 'refs/heads/main' uses: docker/login-action@v3 @@ -42,13 +34,10 @@ jobs: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - - name: Set lowercase image name - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - run: echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV - - name: Push image to GitHub Container Registry if: github.event_name == 'push' && github.ref == 'refs/heads/main' - run: | - docker tag bottomly ghcr.io/${{ env.IMAGE_NAME }}:${{ github.sha }} - docker push ghcr.io/${{ env.IMAGE_NAME }}:${{ github.sha }} + shell: bash + run: |- + IMAGE_NAME="${GITHUB_REPOSITORY,,}" + docker tag bottomly ghcr.io/$IMAGE_NAME:${{ github.sha }} + docker push ghcr.io/$IMAGE_NAME:${{ github.sha }} diff --git a/.github/workflows/push_to_live.yml b/.github/workflows/push_to_live.yml index 4f4f2dc..b2883d3 100644 --- a/.github/workflows/push_to_live.yml +++ b/.github/workflows/push_to_live.yml @@ -1,10 +1,10 @@ --- +name: Push to Live on: workflow_run: - workflows: [ ".NET Build and Test" ] - types: [ completed ] - branches: [ main ] -name: Push to Live + workflows: [.NET Build and Test] + types: [completed] + branches: [main] jobs: build-and-deploy: runs-on: ubuntu-latest @@ -28,7 +28,9 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set lowercase image name - run: echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV + id: image_name + shell: bash + run: echo "value=${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT" - name: Ensure Application Insights exists id: appinsights run: | @@ -47,22 +49,24 @@ jobs: --output tsv) echo "connection_string=$CONN_STR" >> $GITHUB_OUTPUT - name: Deploy to Azure Container Instances - uses: azure/aci-deploy@v1 - with: - resource-group: ${{ secrets.RESOURCE_GROUP }} - dns-name-label: ${{ secrets.RESOURCE_GROUP }}live${{ github.run_number }} - image: ghcr.io/${{ env.IMAGE_NAME }}:${{ github.sha }} - registry-login-server: ghcr.io - registry-username: ${{ github.actor }} - registry-password: ${{ secrets.GITHUB_TOKEN }} - name: bottomly-live - location: uk south - environment-variables: bottomly_giphy_api_key=${{ secrets.GIPHY_API_KEY }} - bottomly_brave_api_key=${{ secrets.BRAVE_API_KEY }} - bottomly_slack_bot_token=${{ secrets.SLACK_TOKEN }} - bottomly_slack_app_token=${{ secrets.SLACK_APP_TOKEN }} - bottomly_prefix=${{ secrets.PREFIX }} - bottomly_env=${{ secrets.ENV }} - ConnectionStrings__mongodb=${{ secrets.MONGO_CONN_STR }} - bottomly_github_token=${{ secrets.RELEASE_HISTORY_TOKEN }} - APPLICATIONINSIGHTS_CONNECTION_STRING=${{ steps.appinsights.outputs.connection_string }} + env: + IMAGE: ghcr.io/${{ steps.image_name.outputs.value }}:${{ github.sha }} + DNS_NAME_LABEL: ${{ secrets.RESOURCE_GROUP }}live${{ github.run_number }} + GIPHY_API_KEY: ${{ secrets.GIPHY_API_KEY }} + BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }} + SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} + SLACK_APP_TOKEN: ${{ secrets.SLACK_APP_TOKEN }} + PREFIX: ${{ secrets.PREFIX }} + ENV: ${{ secrets.ENV }} + MONGO_CONN_STR: ${{ secrets.MONGO_CONN_STR }} + RELEASE_HISTORY_TOKEN: ${{ secrets.RELEASE_HISTORY_TOKEN }} + APPINSIGHTS_CONN_STR: ${{ steps.appinsights.outputs.connection_string }} + OLLAMA_API_KEY: ${{ secrets.OLLAMA_API_KEY }} + REGISTRY_USERNAME: ${{ github.actor }} + REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} + shell: bash + run: | + envsubst < .azure/container-group.yml > /tmp/resolved-container-group.yml + az container create \ + --resource-group ${{ secrets.RESOURCE_GROUP }} \ + --file /tmp/resolved-container-group.yml diff --git a/.vscode/settings.json b/.vscode/settings.json index 4ac3b43..6b75864 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,7 +12,13 @@ "test_*.py" ], "cSpell.words": [ + "appinsights", "Bottomly", - "sass" + "CONN", + "creds", + "GIPHY", + "OLLAMA", + "sass", + "slnx" ] } \ No newline at end of file diff --git a/Bottomly.Tests/Commands/Integration/ImageSearchCommandIntegrationTests.cs b/Bottomly.Tests/Commands/Integration/ImageSearchCommandIntegrationTests.cs index 4c8417a..39514b9 100644 --- a/Bottomly.Tests/Commands/Integration/ImageSearchCommandIntegrationTests.cs +++ b/Bottomly.Tests/Commands/Integration/ImageSearchCommandIntegrationTests.cs @@ -55,7 +55,10 @@ private sealed class DefaultHttpClientFactory : IHttpClientFactory [Fact] public async Task ExecuteAsync_EmptyInput_ReturnsEmptySearchTermErrorResult() { - if (!CredentialsAvailable) return; + if (!CredentialsAvailable) + { + return; + } var result = await CreateCommand().ExecuteAsync(""); @@ -65,7 +68,10 @@ public async Task ExecuteAsync_EmptyInput_ReturnsEmptySearchTermErrorResult() [Fact] public async Task ExecuteAsync_KnownSearchTerm_ReturnsResultWithLink() { - if (!CredentialsAvailable) return; + if (!CredentialsAvailable) + { + return; + } var result = await CreateCommand().ExecuteAsync("GitHub"); @@ -79,7 +85,10 @@ public async Task ExecuteAsync_KnownSearchTerm_ReturnsResultWithLink() [Fact] public async Task ExecuteAsync_KnownSearchTerm_ReturnsRelevantResult() { - if (!CredentialsAvailable) return; + if (!CredentialsAvailable) + { + return; + } var result = await CreateCommand().ExecuteAsync("Wikipedia logo"); @@ -88,4 +97,4 @@ public async Task ExecuteAsync_KnownSearchTerm_ReturnsRelevantResult() searchResult.Link.ShouldNotBeNullOrEmpty(); searchResult.Link.ShouldStartWith("http"); } -} +} \ No newline at end of file diff --git a/Bottomly.Tests/Commands/Integration/SearchCommandIntegrationTests.cs b/Bottomly.Tests/Commands/Integration/SearchCommandIntegrationTests.cs index 4ae44ab..72f4b84 100644 --- a/Bottomly.Tests/Commands/Integration/SearchCommandIntegrationTests.cs +++ b/Bottomly.Tests/Commands/Integration/SearchCommandIntegrationTests.cs @@ -55,7 +55,10 @@ private sealed class DefaultHttpClientFactory : IHttpClientFactory [Fact] public async Task ExecuteAsync_EmptyInput_ReturnsEmptySearchTermErrorResult() { - if (!CredentialsAvailable) return; // credentials not configured — skip + if (!CredentialsAvailable) + { + return; // credentials not configured — skip + } var result = await CreateCommand().ExecuteAsync(""); @@ -65,7 +68,10 @@ public async Task ExecuteAsync_EmptyInput_ReturnsEmptySearchTermErrorResult() [Fact] public async Task ExecuteAsync_KnownSearchTerm_ReturnsResultWithLink() { - if (!CredentialsAvailable) return; // credentials not configured — skip + if (!CredentialsAvailable) + { + return; // credentials not configured — skip + } var result = await CreateCommand().ExecuteAsync("GitHub"); @@ -79,7 +85,10 @@ public async Task ExecuteAsync_KnownSearchTerm_ReturnsResultWithLink() [Fact] public async Task ExecuteAsync_KnownSearchTerm_ReturnsRelevantResult() { - if (!CredentialsAvailable) return; // credentials not configured — skip + if (!CredentialsAvailable) + { + return; // credentials not configured — skip + } var result = await CreateCommand().ExecuteAsync("Wikipedia"); diff --git a/Bottomly.Tests/Commands/RegSearchCommandTests.cs b/Bottomly.Tests/Commands/RegSearchCommandTests.cs index c0cc169..8f1c363 100644 --- a/Bottomly.Tests/Commands/RegSearchCommandTests.cs +++ b/Bottomly.Tests/Commands/RegSearchCommandTests.cs @@ -94,4 +94,4 @@ public async Task ExecuteAsync_HtmlWithError_ReturnsErrorText() result.ShouldBe("Vehicle not found"); } -} +} \ No newline at end of file diff --git a/Bottomly.Tests/Infrastructure/MongoDbCollection.cs b/Bottomly.Tests/Infrastructure/MongoDbCollection.cs index 272eeb9..0acf458 100644 --- a/Bottomly.Tests/Infrastructure/MongoDbCollection.cs +++ b/Bottomly.Tests/Infrastructure/MongoDbCollection.cs @@ -1,4 +1,4 @@ namespace Bottomly.Tests.Infrastructure; [CollectionDefinition("MongoDB")] -public class MongoDbCollection : ICollectionFixture; +public class MongoDbCollection : ICollectionFixture; \ No newline at end of file diff --git a/Bottomly.Tests/Infrastructure/MongoDbFixture.cs b/Bottomly.Tests/Infrastructure/MongoDbFixture.cs index 30956c3..2778318 100644 --- a/Bottomly.Tests/Infrastructure/MongoDbFixture.cs +++ b/Bottomly.Tests/Infrastructure/MongoDbFixture.cs @@ -23,4 +23,4 @@ public async Task InitializeAsync() } public async Task DisposeAsync() => await _container.DisposeAsync(); -} +} \ No newline at end of file diff --git a/Bottomly.Tests/LlmBot/LlmClientTests.cs b/Bottomly.Tests/LlmBot/LlmClientTests.cs index 8dfabed..daa613a 100644 --- a/Bottomly.Tests/LlmBot/LlmClientTests.cs +++ b/Bottomly.Tests/LlmBot/LlmClientTests.cs @@ -102,4 +102,4 @@ public async Task Respond_PassesPromptToClient() It.IsAny(), It.IsAny()), Times.Once()); } -} +} \ No newline at end of file diff --git a/Bottomly.Tests/Repositories/Integration/KarmaRepositoryIntegrationTests.cs b/Bottomly.Tests/Repositories/Integration/KarmaRepositoryIntegrationTests.cs index 8fd83fa..dda33d4 100644 --- a/Bottomly.Tests/Repositories/Integration/KarmaRepositoryIntegrationTests.cs +++ b/Bottomly.Tests/Repositories/Integration/KarmaRepositoryIntegrationTests.cs @@ -187,6 +187,8 @@ private static Karma MakeKarma( private async Task AddKarmaMultiple(string recipient, KarmaType type, int count) { for (var i = 0; i < count; i++) + { await _sut.AddAsync(MakeKarma(recipient, "giver", type)); + } } -} +} \ No newline at end of file diff --git a/Bottomly.Tests/Repositories/Integration/MemberRepositoryIntegrationTests.cs b/Bottomly.Tests/Repositories/Integration/MemberRepositoryIntegrationTests.cs index 4f2edef..2c9e04a 100644 --- a/Bottomly.Tests/Repositories/Integration/MemberRepositoryIntegrationTests.cs +++ b/Bottomly.Tests/Repositories/Integration/MemberRepositoryIntegrationTests.cs @@ -151,4 +151,4 @@ public async Task UpdateInfoAsync_WhenUsernameDoesNotExist_DoesNotThrow() await Should.NotThrowAsync(() => _sut.UpdateInfoAsync("ghost", "Ghost", Gender.Unknown, SassLevel.None, "")); } -} +} \ No newline at end of file diff --git a/Bottomly.Tests/Slack/EventHandlers/RegHandlerTests.cs b/Bottomly.Tests/Slack/EventHandlers/RegHandlerTests.cs index 1462ddf..9b6050b 100644 --- a/Bottomly.Tests/Slack/EventHandlers/RegHandlerTests.cs +++ b/Bottomly.Tests/Slack/EventHandlers/RegHandlerTests.cs @@ -98,4 +98,4 @@ public async Task HandleAsync_HelpEvent_SendsHelpMessage() _mockBroker.Verify(b => b.SendMessageAsync( It.Is(s => s.Contains("Test")), "C1", null), Times.Once()); } -} +} \ No newline at end of file diff --git a/Bottomly.Tests/Slack/MemberlistPopulatorTests.cs b/Bottomly.Tests/Slack/MemberlistPopulatorTests.cs index 5e4514e..2b34336 100644 --- a/Bottomly.Tests/Slack/MemberlistPopulatorTests.cs +++ b/Bottomly.Tests/Slack/MemberlistPopulatorTests.cs @@ -61,4 +61,4 @@ public async Task PopulateMembers_NotSeeded_FetchesAndSavesMembers() result.ShouldContain(m => m.Username == "bob" && m.SlackId == "U2"); _mockRepo.Verify(r => r.AddAsync(It.Is>(members => members.Count() == 2)), Times.Once()); } -} +} \ No newline at end of file diff --git a/Bottomly.Tests/Slack/MembershipEventHandlers/MemberJoinedEventHandlerTests.cs b/Bottomly.Tests/Slack/MembershipEventHandlers/MemberJoinedEventHandlerTests.cs index 8862698..ad5137c 100644 --- a/Bottomly.Tests/Slack/MembershipEventHandlers/MemberJoinedEventHandlerTests.cs +++ b/Bottomly.Tests/Slack/MembershipEventHandlers/MemberJoinedEventHandlerTests.cs @@ -59,4 +59,4 @@ public async Task ExecuteAsync_ValidEvent_AddsNewMember() _mockRepo.Verify(r => r.AddAsync(It.Is(m => m.SlackId == "U1" && m.Username == "alice")), Times.Once()); } -} +} \ No newline at end of file diff --git a/Bottomly.Tests/Slack/MessageEventHandlers/ConversationMessageHandling/ConversationMessageHandlerTests.cs b/Bottomly.Tests/Slack/MessageEventHandlers/ConversationMessageHandling/ConversationMessageHandlerTests.cs index 0f22447..4fe63eb 100644 --- a/Bottomly.Tests/Slack/MessageEventHandlers/ConversationMessageHandling/ConversationMessageHandlerTests.cs +++ b/Bottomly.Tests/Slack/MessageEventHandlers/ConversationMessageHandling/ConversationMessageHandlerTests.cs @@ -132,5 +132,4 @@ private void SetupConversationHistory(string channel, List message private void SetupMembers(List members) => _mockMemberRepo.Setup(r => r.GetBySlackIdsAsync(It.IsAny>())) .ReturnsAsync(members); -} - +} \ No newline at end of file diff --git a/Bottomly.Tests/Slack/MessageEventHandlers/ConversationMessageHandling/MessageContextExtensionsTests.cs b/Bottomly.Tests/Slack/MessageEventHandlers/ConversationMessageHandling/MessageContextExtensionsTests.cs index 57d732d..440314a 100644 --- a/Bottomly.Tests/Slack/MessageEventHandlers/ConversationMessageHandling/MessageContextExtensionsTests.cs +++ b/Bottomly.Tests/Slack/MessageEventHandlers/ConversationMessageHandling/MessageContextExtensionsTests.cs @@ -49,4 +49,4 @@ public void CreateFromSlackMessage_UnknownUser_FallsBackToSlackId() inputMessage.Username.ShouldBe("U_UNKNOWN"); inputMessage.Text.ShouldBe("hey"); } -} +} \ No newline at end of file diff --git a/Bottomly.Tests/Slack/MessageEventHandlers/ConversationMessageHandling/ResponseMessageFactoryTests.cs b/Bottomly.Tests/Slack/MessageEventHandlers/ConversationMessageHandling/ResponseMessageFactoryTests.cs index 0c2585e..f887b3e 100644 --- a/Bottomly.Tests/Slack/MessageEventHandlers/ConversationMessageHandling/ResponseMessageFactoryTests.cs +++ b/Bottomly.Tests/Slack/MessageEventHandlers/ConversationMessageHandling/ResponseMessageFactoryTests.cs @@ -47,4 +47,4 @@ public void ToSlackResponse_LlmUnknownErrorResponse_ReturnsFallbackMessage() result.ShouldNotBeNullOrEmpty(); } -} +} \ No newline at end of file diff --git a/Bottomly.Tests/Slack/SlackMessageBrokerTests.cs b/Bottomly.Tests/Slack/SlackMessageBrokerTests.cs index b34988d..d25d21f 100644 --- a/Bottomly.Tests/Slack/SlackMessageBrokerTests.cs +++ b/Bottomly.Tests/Slack/SlackMessageBrokerTests.cs @@ -111,4 +111,4 @@ public async Task SendDmAsync_ValidText_OpensConversationAndPosts() _mockChat.Verify(c => c.PostMessage(It.Is(m => m.Channel == "D_CHANNEL" && m.Text == "Private message")), Times.Once()); } -} +} \ No newline at end of file diff --git a/Bottomly.Tests/Slack/SlackWorkerTests.cs b/Bottomly.Tests/Slack/SlackWorkerTests.cs index 0d33bf7..a4e410a 100644 --- a/Bottomly.Tests/Slack/SlackWorkerTests.cs +++ b/Bottomly.Tests/Slack/SlackWorkerTests.cs @@ -304,4 +304,4 @@ public async Task SlackReactionEventDispatcher_DelegatesToWorker() var dispatcher = new SlackReactionEventDispatcher(worker); await dispatcher.Handle(new ReactionAdded { Reaction = "joy" }); } -} +} \ No newline at end of file diff --git a/Bottomly/Commands/Search/ImageSearchCommand.cs b/Bottomly/Commands/Search/ImageSearchCommand.cs index ec7356e..b8c3247 100644 --- a/Bottomly/Commands/Search/ImageSearchCommand.cs +++ b/Bottomly/Commands/Search/ImageSearchCommand.cs @@ -19,13 +19,24 @@ protected override string BuildUrl(string searchTerm) => protected override SearchCommandResult ExtractFirstResult(JsonElement root) { - if (!root.TryGetProperty("results", out var results)) return new NoResultsFoundResult(); - if (results.GetArrayLength() == 0) return new NoResultsFoundResult(); + if (!root.TryGetProperty("results", out var results)) + { + return new NoResultsFoundResult(); + } + + if (results.GetArrayLength() == 0) + { + return new NoResultsFoundResult(); + } var first = results[0]; var title = first.TryGetProperty("title", out var t) ? t.GetString() ?? string.Empty : string.Empty; - if (!first.TryGetProperty("properties", out var props)) return new NoResultsFoundResult(); + if (!first.TryGetProperty("properties", out var props)) + { + return new NoResultsFoundResult(); + } + var url = props.TryGetProperty("url", out var u) ? u.GetString() ?? string.Empty : string.Empty; return new SearchResult(title, url); } -} +} \ No newline at end of file diff --git a/Bottomly/Commands/Search/SearchCommand.cs b/Bottomly/Commands/Search/SearchCommand.cs index 5294767..08a562e 100644 --- a/Bottomly/Commands/Search/SearchCommand.cs +++ b/Bottomly/Commands/Search/SearchCommand.cs @@ -19,13 +19,24 @@ protected override string BuildUrl(string searchTerm) => protected override SearchCommandResult ExtractFirstResult(JsonElement root) { - if (!root.TryGetProperty("web", out var web)) return new NoResultsFoundResult(); - if (!web.TryGetProperty("results", out var results)) return new NoResultsFoundResult(); - if (results.GetArrayLength() == 0) return new NoResultsFoundResult(); + if (!root.TryGetProperty("web", out var web)) + { + return new NoResultsFoundResult(); + } + + if (!web.TryGetProperty("results", out var results)) + { + return new NoResultsFoundResult(); + } + + if (results.GetArrayLength() == 0) + { + return new NoResultsFoundResult(); + } var first = results[0]; var title = first.TryGetProperty("title", out var t) ? t.GetString() ?? string.Empty : string.Empty; var url = first.TryGetProperty("url", out var u) ? u.GetString() ?? string.Empty : string.Empty; return new SearchResult(title, url); } -} +} \ No newline at end of file diff --git a/Bottomly/Commands/Search/SearchCommandBase.cs b/Bottomly/Commands/Search/SearchCommandBase.cs index 9daa3ec..6ddd09b 100644 --- a/Bottomly/Commands/Search/SearchCommandBase.cs +++ b/Bottomly/Commands/Search/SearchCommandBase.cs @@ -20,7 +20,10 @@ public abstract class SearchCommandBase( public virtual async Task ExecuteAsync(string searchTerm) { - if (string.IsNullOrWhiteSpace(searchTerm)) return new EmptySearchTermErrorResult(); + if (string.IsNullOrWhiteSpace(searchTerm)) + { + return new EmptySearchTermErrorResult(); + } try { @@ -55,7 +58,9 @@ private static async Task ExtractErrorMessageAsync(HttpResponseMessage r var body = await response.Content.ReadAsStringAsync(); using var doc = JsonDocument.Parse(body); if (doc.RootElement.TryGetProperty("message", out var msg)) + { return msg.GetString()!; + } } catch (JsonException) { @@ -64,4 +69,4 @@ private static async Task ExtractErrorMessageAsync(HttpResponseMessage r return response.ReasonPhrase ?? "Unknown error"; } -} +} \ No newline at end of file diff --git a/Bottomly/Commands/Search/SearchCommandResult.cs b/Bottomly/Commands/Search/SearchCommandResult.cs index 0416291..a00ed96 100644 --- a/Bottomly/Commands/Search/SearchCommandResult.cs +++ b/Bottomly/Commands/Search/SearchCommandResult.cs @@ -8,4 +8,4 @@ public record SearchApiErrorResult(string Error) : SearchCommandResult; public record NoResultsFoundResult : SearchCommandResult; -public record EmptySearchTermErrorResult : SearchCommandResult; +public record EmptySearchTermErrorResult : SearchCommandResult; \ No newline at end of file diff --git a/Bottomly/Commands/WikipediaSearchCommand.cs b/Bottomly/Commands/WikipediaSearchCommand.cs index 827b7a6..9c46ab6 100644 --- a/Bottomly/Commands/WikipediaSearchCommand.cs +++ b/Bottomly/Commands/WikipediaSearchCommand.cs @@ -15,7 +15,10 @@ public string GetPurpose() public virtual async Task ExecuteAsync(string searchTerm) { - if (string.IsNullOrWhiteSpace(searchTerm)) return null; + if (string.IsNullOrWhiteSpace(searchTerm)) + { + return null; + } var url = $"https://en.wikipedia.org/w/api.php?action=opensearch&format=json&search={Uri.EscapeDataString(searchTerm)}"; @@ -31,7 +34,10 @@ public string GetPurpose() var titles = root[1]; var links = root[3]; - if (titles.GetArrayLength() == 0) return null; + if (titles.GetArrayLength() == 0) + { + return null; + } return new WikipediaResult(titles[0].GetString()!, links[0].GetString()!); } diff --git a/Bottomly/LlmBot/BottomlyInputMessage.cs b/Bottomly/LlmBot/BottomlyInputMessage.cs index ff9f2b9..08014f2 100644 --- a/Bottomly/LlmBot/BottomlyInputMessage.cs +++ b/Bottomly/LlmBot/BottomlyInputMessage.cs @@ -1,4 +1,4 @@ -namespace Bottomly.LlmBot; +namespace Bottomly.LlmBot; public record BottomlyInputMessage { diff --git a/Bottomly/LlmBot/BottomlyUserNote.cs b/Bottomly/LlmBot/BottomlyUserNote.cs index 884d293..fff1da2 100644 --- a/Bottomly/LlmBot/BottomlyUserNote.cs +++ b/Bottomly/LlmBot/BottomlyUserNote.cs @@ -1,4 +1,4 @@ -namespace Bottomly.LlmBot; +namespace Bottomly.LlmBot; public record BottomlyUserNote { diff --git a/Bottomly/LlmBot/FullPromptContext.cs b/Bottomly/LlmBot/FullPromptContext.cs index da86cec..99083c3 100644 --- a/Bottomly/LlmBot/FullPromptContext.cs +++ b/Bottomly/LlmBot/FullPromptContext.cs @@ -1,4 +1,4 @@ -using System.Text; +using System.Text; using Microsoft.Extensions.AI; namespace Bottomly.LlmBot; diff --git a/Bottomly/LlmBot/LlmClient.cs b/Bottomly/LlmBot/LlmClient.cs index a555f1a..92f3023 100644 --- a/Bottomly/LlmBot/LlmClient.cs +++ b/Bottomly/LlmBot/LlmClient.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.AI; +using Microsoft.Extensions.AI; using Microsoft.Extensions.Logging; namespace Bottomly.LlmBot; diff --git a/Bottomly/LlmBot/MessageHistoryContext.cs b/Bottomly/LlmBot/MessageHistoryContext.cs index 2dc12bd..b71f483 100644 --- a/Bottomly/LlmBot/MessageHistoryContext.cs +++ b/Bottomly/LlmBot/MessageHistoryContext.cs @@ -1,4 +1,4 @@ -namespace Bottomly.LlmBot; +namespace Bottomly.LlmBot; public record MessageHistoryContext { diff --git a/Bottomly/Models/Gender.cs b/Bottomly/Models/Gender.cs index a2c08d8..a1ba880 100644 --- a/Bottomly/Models/Gender.cs +++ b/Bottomly/Models/Gender.cs @@ -7,4 +7,4 @@ public enum Gender Female, NonBinary, Other -} +} \ No newline at end of file diff --git a/Bottomly/Models/Karma.cs b/Bottomly/Models/Karma.cs index 58ea5bf..2bd7a2c 100644 --- a/Bottomly/Models/Karma.cs +++ b/Bottomly/Models/Karma.cs @@ -20,7 +20,7 @@ public class Karma [BsonElement("awarded")] public DateTime Awarded { get; set; } [BsonElement("karma_type")] public string KarmaTypeValue { get; set; } = string.Empty; - + /// /// Legacy element to enable support from pymongo persisted data /// diff --git a/Bottomly/Models/Member.cs b/Bottomly/Models/Member.cs index eae6c81..11f2770 100644 --- a/Bottomly/Models/Member.cs +++ b/Bottomly/Models/Member.cs @@ -4,7 +4,7 @@ namespace Bottomly.Models; public class Member { - [BsonId] [BsonElement("_id")] public string Username { get; set; } = string.Empty; + [BsonId][BsonElement("_id")] public string Username { get; set; } = string.Empty; [BsonElement("slack_id")] public string SlackId { get; set; } = string.Empty; diff --git a/Bottomly/Models/SassLevel.cs b/Bottomly/Models/SassLevel.cs index 6106c86..5cf57d7 100644 --- a/Bottomly/Models/SassLevel.cs +++ b/Bottomly/Models/SassLevel.cs @@ -7,4 +7,4 @@ public enum SassLevel Moderate, Frequent, Constant -} +} \ No newline at end of file diff --git a/Bottomly/Seed/MemberSeedDataDto.cs b/Bottomly/Seed/MemberSeedDataDto.cs index 35cc926..6500f9c 100644 --- a/Bottomly/Seed/MemberSeedDataDto.cs +++ b/Bottomly/Seed/MemberSeedDataDto.cs @@ -8,4 +8,4 @@ public class MemberSeedDataDto public string Gender { get; set; } = "Unknown"; public string SassLevel { get; set; } = "Moderate"; public string MiscInfo { get; set; } = string.Empty; -} +} \ No newline at end of file diff --git a/Bottomly/Seed/MemberSeedDataImporter.cs b/Bottomly/Seed/MemberSeedDataImporter.cs index d25f1db..e1a43d8 100644 --- a/Bottomly/Seed/MemberSeedDataImporter.cs +++ b/Bottomly/Seed/MemberSeedDataImporter.cs @@ -30,7 +30,9 @@ public async Task ImportAsync() logger.LogInformation("Importing seed data from {Count} YAML files in {Path}", files.Length, seedDir); foreach (var file in files) + { await ImportFileAsync(file); + } logger.LogInformation("Seed data import complete."); } @@ -42,7 +44,10 @@ private string ResolveSeedDir() { var candidate = Path.Combine(dir.FullName, "MemberSeedData"); if (Directory.Exists(candidate)) + { return candidate; + } + dir = dir.Parent; } return Path.Combine(env.ContentRootPath, "MemberSeedData"); @@ -82,4 +87,4 @@ private async Task ImportFileAsync(string file) logger.LogError(ex, "Failed to import seed data from {File}.", Path.GetFileName(file)); } } -} +} \ No newline at end of file diff --git a/Bottomly/Slack/MessageEventHandlers/ConversationMessageHandling/ConversationMessageHandler.cs b/Bottomly/Slack/MessageEventHandlers/ConversationMessageHandling/ConversationMessageHandler.cs index b3f5330..15bde8b 100644 --- a/Bottomly/Slack/MessageEventHandlers/ConversationMessageHandling/ConversationMessageHandler.cs +++ b/Bottomly/Slack/MessageEventHandlers/ConversationMessageHandling/ConversationMessageHandler.cs @@ -1,4 +1,4 @@ -using Bottomly.LlmBot; +using Bottomly.LlmBot; using Bottomly.Repositories; using Microsoft.Extensions.Logging; using SlackNet; diff --git a/Bottomly/Slack/MessageEventHandlers/ConversationMessageHandling/MessageContextExtensions.cs b/Bottomly/Slack/MessageEventHandlers/ConversationMessageHandling/MessageContextExtensions.cs index 628aed2..71f0fb3 100644 --- a/Bottomly/Slack/MessageEventHandlers/ConversationMessageHandling/MessageContextExtensions.cs +++ b/Bottomly/Slack/MessageEventHandlers/ConversationMessageHandling/MessageContextExtensions.cs @@ -1,4 +1,4 @@ -using Bottomly.LlmBot; +using Bottomly.LlmBot; using Bottomly.Models; using SlackNet.Events; diff --git a/Bottomly/Slack/MessageEventHandlers/ConversationMessageHandling/ResponseMessageFactory.cs b/Bottomly/Slack/MessageEventHandlers/ConversationMessageHandling/ResponseMessageFactory.cs index 5f7ea55..5f4e0b2 100644 --- a/Bottomly/Slack/MessageEventHandlers/ConversationMessageHandling/ResponseMessageFactory.cs +++ b/Bottomly/Slack/MessageEventHandlers/ConversationMessageHandling/ResponseMessageFactory.cs @@ -1,4 +1,4 @@ -using Bottomly.LlmBot; +using Bottomly.LlmBot; namespace Bottomly.Slack.MessageEventHandlers.ConversationMessageHandling;