Skip to content

Conversation

@maximgorbatyuk
Copy link
Member

@maximgorbatyuk maximgorbatyuk commented May 3, 2025

Summary by CodeRabbit

  • New Features

    • Introduced OpenAI integration for generating and analyzing salary subscription reports.
    • Added API endpoints for retrieving OpenAI analysis and reports for Telegram subscriptions.
    • Added new data structures for detailed role-based salary statistics and historical salary trends.
    • Added JWT token generator for secure OpenAI service authentication.
  • Improvements

    • Enhanced salary data models with additional properties including min and max salaries.
    • Standardized profession selection property names to SelectedProfessionIds across queries, controllers, and user commands.
    • Updated currency service to explicitly handle nullable currency retrieval.
    • Refactored salary subscription data handling for clearer initialization and caching.
    • Simplified salary grade group iteration and filtering in Telegram message processing.
  • Tests

    • Added unit tests for JWT token generation.
  • Chores

    • Updated environment and configuration files to include OpenAI API settings.
    • Registered OpenAI service in application dependency injection.
    • Enhanced deployment workflow to inject OpenAI secret from GitHub Secrets.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented May 3, 2025

Walkthrough

This update introduces OpenAI integration and refactors several data structures and query parameter names for clarity and consistency. It adds new records and interfaces to support OpenAI report generation and analysis, including service implementations, MediatR handlers, and supporting data models. The environment and configuration files are updated to include OpenAI API credentials. The salary data model is unified and extended, and the property ProfessionsToInclude is consistently renamed to SelectedProfessionIds across the codebase. JWT token generation logic is added, and related tests are introduced. Service registration and controller endpoints are updated to support the new OpenAI features.

Changes

File(s) / Group Change Summary
src/.env.example, src/Web.Api/appsettings.json, src/Web.Api/Properties/launchSettings.json, src/Web.Api/appsettings.Production.json, .github/workflows/deploy.yml Added OpenAI API secret and URL configuration entries to environment and settings files; updated deployment workflow to inject OpenAI secret.
src/Domain/Entities/StatData/SalaryBaseData.cs Introduced SalaryBaseData record with salary, profession, grade, and timestamp.
src/Domain/Entities/StatData/SalaryGraveValue.cs Removed obsolete SalaryGraveValue record.
src/Domain/Entities/StatData/StatDataCacheItemSalaryData.cs Refactored to use SalaryBaseData; added MinLocalSalary and MaxLocalSalary properties.
src/Domain/ValueObjects/ISalariesChartQueryParams.cs, src/Web.Api/Features/Salaries/GetSalaries/GetSalariesPaginatedQuery.cs, src/Web.Api/Features/Salaries/SalariesChartQueryParamsBase.cs, src/Web.Api/Features/Salaries/GetSalariesChart/Charts/SalariesChartQueryParams.cs, src/Web.Api/Features/Telegram/ProcessMessage/UserCommands/TelegramBotUserCommandParameters.cs, src/Infrastructure/Salaries/ChartPageLink.cs, src/Web.Api/Views/ChartSharePageViewModel.cs, src/Web.Api/Features/Historical/GetSurveyHistoricalChart/GetSurveyHistoricalChartHandler.cs, src/Web.Api/Features/Historical/HistoricalChartsController.cs, src/Web.Api/Features/Salaries/Admin/SalariesAdminQuery.cs, src/Web.Api/Features/Salaries/SalariesController.cs, src/Web.Api/Features/SalariesChartShare/GetChartSharePage/GetChartSharePageHandler.cs Renamed property ProfessionsToInclude to SelectedProfessionIds throughout query parameter classes, interfaces, and related usages.
src/Infrastructure/Currencies/Contracts/ICurrencyService.cs, src/Infrastructure/Currencies/CurrencyService.cs, src/TestUtils/Mocks/CurrenciesServiceFake.cs, src/Web.Api/Features/Telegram/ProcessMessage/ProcessTelegramMessageHandler.cs Renamed method GetCurrencyAsync to GetCurrencyOrNullAsync in currency service interfaces and implementations; updated calls accordingly.
src/Infrastructure/Infrastructure.csproj Added dependency on System.IdentityModel.Tokens.Jwt NuGet package.
src/Infrastructure/Jwt/TechinterviewJwtTokenGenerator.cs, src/InfrastructureTests/Jwt/TechinterviewJwtTokenGeneratorTests.cs Added new JWT token generator class for OpenAI integration and corresponding unit test.
src/Infrastructure/Salaries/SalariesForChartQuery.cs Introduced MonthsToShow constant; refactored to use SelectedProfessionIds.
src/Infrastructure/Services/OpenAi/IOpenAiService.cs, src/Infrastructure/Services/OpenAi/OpenAiService.cs Added OpenAI service interface and implementation for bearer token generation and analysis retrieval.
src/Web.Api/Setup/ServiceRegistration.cs Registered IOpenAiService in dependency injection container.
src/Web.Api/Features/BackgroundJobs/SalarySubscriptionData.cs Added new record to encapsulate salary subscription data and initialization logic.
src/Web.Api/Features/BackgroundJobs/StatDataChangeSubscriptionCalculateJob.cs Refactored to use SalarySubscriptionData and improved link generation.
src/Web.Api/Features/Subscribtions/GetOpenAiReport/GetOpenAiReportHandler.cs, src/Web.Api/Features/Subscribtions/GetOpenAiReport/GetOpenAiReportQuery.cs, src/Web.Api/Features/Subscribtions/GetOpenAiReportAnalysis/GetOpenAiReportAnalysisHandler.cs, src/Web.Api/Features/Subscribtions/GetOpenAiReportAnalysis/GetOpenAiReportAnalysisQuery.cs, src/Web.Api/Features/Subscribtions/GetOpenAiReportAnalysis/GetOpenAiReportAnalysisResponse.cs Added MediatR queries and handlers for OpenAI report and analysis retrieval.
src/Web.Api/Features/Subscribtions/TelegramSubscriptionsController.cs Added HTTP GET endpoints for OpenAI analysis and report retrieval.
src/Web.Api/Integrations/OpenAiAnalysisIntegration/OpenAiBodyReport.cs, src/Web.Api/Integrations/OpenAiAnalysisIntegration/OpenAiBodyReportMetadata.cs, src/Web.Api/Integrations/OpenAiAnalysisIntegration/OpenAiBodyReportRole.cs, src/Web.Api/Integrations/OpenAiAnalysisIntegration/OpenAiBodyReportRoleHistoricalDataItem.cs, src/Web.Api/Integrations/OpenAiAnalysisIntegration/OpenAiBodyReportRoleSalaryData.cs Added new records for OpenAI report structure, metadata, roles, historical data, and salary statistics.
src/Web.Api.Tests/Features/Telegram/ProcessMessage/UserCommands/QaAndTestersTelegramBotUserCommandParametersTests.cs Updated tests to use SelectedProfessionIds.

Sequence Diagram(s)

OpenAI Report Analysis Request Flow

sequenceDiagram
    participant Client
    participant Controller as TelegramSubscriptionsController
    participant Mediator
    participant Handler as GetOpenAiReportAnalysisHandler
    participant Db as DatabaseContext
    participant ProfCache as IProfessionsCacheService
    participant OpenAi as IOpenAiService

    Client->>Controller: GET /{id}/open-ai-analysis
    Controller->>Mediator: Send(GetOpenAiReportAnalysisQuery)
    Mediator->>Handler: Handle(query)
    Handler->>Db: Query StatDataChangeSubscription by id
    Handler->>ProfCache: GetAllProfessionsAsync()
    Handler->>OpenAi: GetAnalysisAsync()
    Handler->>OpenAi: GetBearer()
    Handler-->>Mediator: GetOpenAiReportAnalysisResponse
    Mediator-->>Controller: Response
    Controller-->>Client: 200 OK + Analysis Response
Loading

OpenAI Service Bearer and Analysis

sequenceDiagram
    participant OpenAiService
    participant JwtGen as TechinterviewJwtTokenGenerator
    participant HttpClient

    OpenAiService->>JwtGen: Generate JWT (GetBearer)
    OpenAiService->>HttpClient: GET OpenAI API (GetAnalysisAsync)
    HttpClient-->>OpenAiService: Response
    OpenAiService-->>Caller: Analysis string
Loading

Poem

In the warren, code now grows,
OpenAI secrets the config bestows.
JWT tokens hop with glee,
Reports and stats for all to see.
Professions selected, filters refined,
Rabbits and devs, together aligned.
Onward to analysis, with carrots in mind! 🥕


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2ecafbb and ed3af1e.

📒 Files selected for processing (3)
  • src/Infrastructure/Salaries/SalariesForChartQuery.cs (6 hunks)
  • src/Web.Api/Features/BackgroundJobs/SalarySubscriptionData.cs (1 hunks)
  • src/Web.Api/Features/Subscribtions/TelegramSubscriptionsController.cs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/Web.Api/Features/Subscribtions/TelegramSubscriptionsController.cs
  • src/Web.Api/Features/BackgroundJobs/SalarySubscriptionData.cs
  • src/Infrastructure/Salaries/SalariesForChartQuery.cs
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🧹 Nitpick comments (24)
src/Web.Api/Views/ChartSharePageViewModel.cs (1)

67-69: Consider renaming internal properties for consistency

While the external references have been updated, the internal property is still named ProfessionsToInclude. Consider renaming this and related methods like HasProfessionsToInclude to maintain consistency across the codebase.

-    public string ProfessionsToInclude { get; init; }
+    public string SelectedProfessionIds { get; init; }

-    public bool HasProfessionsToInclude => !string.IsNullOrEmpty(ProfessionsToInclude);
+    public bool HasSelectedProfessionIds => !string.IsNullOrEmpty(SelectedProfessionIds);
src/Web.Api/Features/Telegram/ProcessMessage/UserCommands/TelegramBotUserCommandParameters.cs (1)

29-37: Consider renaming the constructor parameter for better consistency

For full naming consistency, consider renaming the parameter professionsToInclude to selectedProfessions to match the property names.

public TelegramBotUserCommandParameters(
-        List<Profession> professionsToInclude)
+        List<Profession> selectedProfessions)
{
    Grade = null;
-    SelectedProfessions = professionsToInclude;
-    SelectedProfessionIds = professionsToInclude
+    SelectedProfessions = selectedProfessions;
+    SelectedProfessionIds = selectedProfessions
        .Select(x => x.Id)
        .ToList();
}
src/Web.Api/Integrations/OpenAiAnalysisIntegration/OpenAiBodyReportMetadata.cs (1)

6-21: Consider enhancing flexibility and standardizing currency format

The implementation is good for the current requirements, but consider these improvements:

  1. The hardcoded PeriodType value "weekly" might limit flexibility. Consider accepting this as a parameter.
  2. For currency representation, consider using ISO 4217 standard format (e.g., "USD" instead of "Usd").
public record OpenAiBodyReportMetadata
{
    public string ReportDate { get; }

    public string Currency { get; }

    public string PeriodType { get; }

    public OpenAiBodyReportMetadata(
-        Currency currency)
+        Currency currency,
+        string periodType = "weekly")
    {
-        Currency = currency.ToString();
+        Currency = currency.ToString().ToUpperInvariant();
        ReportDate = DateTime.UtcNow.ToString("yyyy-MM-dd");
-        PeriodType = "weekly";
+        PeriodType = periodType;
    }
}
src/InfrastructureTests/Jwt/TechinterviewJwtTokenGeneratorTests.cs (1)

6-17: Test looks good, but could be more comprehensive

The test validates that the token generator produces a string containing the expected JWT header format. This is a good start, but consider adding more assertions to validate other aspects of the token.

You could enhance the test by:

  1. Validating token structure (header, payload, signature)
  2. Checking that specific claims are present
  3. Testing with different input parameters
 [Fact]
 public void ToString_ShouldReturnToken()
 {
     const string secretKey = "hey-girl-its-super-secret-key-for-jwt";
     var generator = new TechinterviewJwtTokenGenerator(secretKey);

     var result = generator.ToString();
     Assert.Contains("eyJhbGciOiJodHRwOi8vd3d3LnczLm9yZy8y", result);
+    
+    // Validate token structure (3 parts separated by dots)
+    var parts = result.Split('.');
+    Assert.Equal(3, parts.Length);
 }
src/Web.Api/Features/Telegram/ProcessMessage/ProcessTelegramMessageHandler.cs (1)

278-368: Method could benefit from further refactoring

While the changes look good, the ReplyWithSalariesAsync method is quite long (about 90 lines). Consider breaking it down into smaller, focused methods to improve readability and maintainability.

You could extract the following logic into separate methods:

  1. Cache key generation
  2. Salary data retrieval
  3. Currency formatting
  4. Response text building
src/Infrastructure/Services/OpenAi/OpenAiService.cs (1)

24-28: Consider making GetBearer() private

The GetBearer() method appears to only be used internally within this class (in GetAnalysisAsync). Consider making it private to better encapsulate the service's implementation details.

-public string GetBearer()
+private string GetBearer()
{
    var secret = _configuration["OpenAiApi:Secret"];
    return new TechinterviewJwtTokenGenerator(secret).ToString();
}
src/Web.Api/Integrations/OpenAiAnalysisIntegration/OpenAiBodyReport.cs (1)

17-23: Handle edge cases with null or empty profession lists

The code doesn't explicitly handle the case where subscriptionData.FilterData.SelectedProfessions is null or empty, which could lead to an empty roles list.

Consider adding a check and potentially a log message or default behavior when no professions are selected:

_subscriptionData = subscriptionData.IsInitializedOrFail();
ReportMetadata = new OpenAiBodyReportMetadata(currency);
Roles = new List<OpenAiBodyReportRole>();

var now = DateTimeOffset.UtcNow;

+if (subscriptionData.FilterData.SelectedProfessions == null || !subscriptionData.FilterData.SelectedProfessions.Any())
+{
+    // Log warning or set default behavior
+}
src/Web.Api/Features/Subscribtions/GetOpenAiReport/GetOpenAiReportHandler.cs (2)

15-26: Use class instead of record for DI-managed handlers.

record types generate value-based equality members and deconstruction helpers that are unnecessary (and sometimes surprising) for services instantiated by the DI container.
Changing this to sealed class (or class) avoids unintended semantics and makes the intent clearer.


13-13: Spelling: SubscribtionsSubscriptions.

A consistent namespace spelling helps discoverability and avoids typos in future imports.

src/Web.Api/Integrations/OpenAiAnalysisIntegration/OpenAiBodyReportRole.cs (1)

22-33: Weekly slices overlap; consider using non-overlapping intervals.

Where(x => x.CreatedAt <= now.AddDays(-daysCount)) selects all salaries older than n days, so each successive interval includes data from earlier ones, skewing averages and trends.
A typical approach is to use half-open ranges, e.g.:

var from = now.AddDays(-daysCount);
var to   = now.AddDays(-i * 7);
var salariesForDate = salaries
    .Where(x => x.CreatedAt > from && x.CreatedAt <= to)
    .ToList();

This yields three distinct one-week buckets (7–14, 14–21, 21–28 days ago).

src/Web.Api/Features/BackgroundJobs/StatDataChangeSubscriptionCalculateJob.cs (1)

118-124: Simplify zero-difference check.

if (diffInPercent is > 0 or < 0) is equivalent to diffInPercent != 0 and is easier to read.

-                    if (diffInPercent is > 0 or < 0)
+                    if (diffInPercent != 0)
src/Web.Api/Features/Subscribtions/GetOpenAiReportAnalysis/GetOpenAiReportAnalysisHandler.cs (4)

20-20: Inconsistent naming convention between field and parameter.

The field name _openApiService doesn't match the parameter name openApiService on line 25 (case mismatch). Consistency in naming is important for code readability and maintainability.

-    private readonly IOpenAiService _openApiService;
+    private readonly IOpenAiService _openAiService;

25-25: Field name doesn't match parameter.

The parameter name uses 'Ai' while the field uses 'Api'. Update for consistency.

        IOpenAiService openApiService)
    {
        _context = context;
        _professionsCacheService = professionsCacheService;
-        _openApiService = openApiService;
+        _openAiService = openApiService;
    }

Also applies to: 29-29


16-16: Class lacks XML documentation.

Adding XML documentation for public classes and methods improves code maintainability. Consider documenting this handler class with a summary of its purpose and responsibility.

+/// <summary>
+/// Handles requests to retrieve an OpenAI report analysis for a specific subscription.
+/// </summary>
public class GetOpenAiReportAnalysisHandler : IRequestHandler<GetOpenAiReportAnalysisQuery, GetOpenAiReportAnalysisResponse>

32-34: Method lacks XML documentation.

Add XML documentation for the Handle method to describe its purpose, parameters, and return value.

+    /// <summary>
+    /// Handles the request to get an OpenAI report analysis for the specified subscription.
+    /// </summary>
+    /// <param name="request">The query containing the subscription ID.</param>
+    /// <param name="cancellationToken">Cancellation token.</param>
+    /// <returns>A response containing the OpenAI analysis, report data, and bearer token.</returns>
    public async Task<GetOpenAiReportAnalysisResponse> Handle(
        GetOpenAiReportAnalysisQuery request,
        CancellationToken cancellationToken)
src/Web.Api/Features/BackgroundJobs/SalarySubscriptionData.cs (6)

22-22: Fix typo in variable name.

The variable _isInitianlized has a spelling error and should be _isInitialized.

-    private bool _isInitianlized;
+    private bool _isInitialized;

42-42: Fix typo consistently throughout the code.

The misspelled variable _isInitianlized is used in multiple places. Fix all occurrences to maintain consistency.

-        _isInitianlized = false;
+        _isInitialized = false;

// Line 83
-        _isInitianlized = true;
+        _isInitialized = true;

// Line 89
-        if (_isInitianlized)
+        if (_isInitialized)

Also applies to: 83-83, 89-89


44-50: Consider using more explicit filtering logic.

The use of the When extension method with multiple conditions makes the code less obvious. Consider using more explicit filtering for better readability.

-        FilterData = new TelegramBotUserCommandParameters(
-            allProfessions
-                .When(
-                    subscription.ProfessionIds != null &&
-                    subscription.ProfessionIds.Count > 0,
-                    x => subscription.ProfessionIds.Contains(x.Id))
-                .ToList());
+        var filteredProfessions = allProfessions;
+        if (subscription.ProfessionIds != null && subscription.ProfessionIds.Count > 0)
+        {
+            filteredProfessions = allProfessions
+                .Where(x => subscription.ProfessionIds.Contains(x.Id))
+                .ToList();
+        }
+        
+        FilterData = new TelegramBotUserCommandParameters(filteredProfessions);

16-16: Class lacks XML documentation.

Adding XML documentation for public classes improves code maintainability. Consider documenting this record class with a summary of its purpose and responsibility.

+/// <summary>
+/// Encapsulates salary subscription data management for background jobs and report generation.
+/// Provides methods for loading and accessing salary data filtered by professions.
+/// </summary>
public record SalarySubscriptionData

58-60: Add XML documentation for public methods.

Public methods should have XML documentation to improve code understandability.

+    /// <summary>
+    /// Asynchronously initializes the subscription data by loading cache records,
+    /// calculating total salary count, and retrieving salary data with grades.
+    /// </summary>
+    /// <param name="cancellationToken">A token to cancel the operation.</param>
+    /// <returns>The current instance for fluent chaining.</returns>
    public async Task<SalarySubscriptionData> InitializeAsync(
        CancellationToken cancellationToken)

74-81: Consider creating a mapper function for SalaryBaseData.

The inline creation of SalaryBaseData objects could be encapsulated in a separate mapper method for better reusability and testing.

        Salaries = await _salariesForChartQuery
            .ToQueryable(CompanyType.Local)
            .Where(x => x.Grade.HasValue)
-            .Select(x => new SalaryBaseData
-            {
-                ProfessionId = x.ProfessionId,
-                Grade = x.Grade.Value,
-                Value = x.Value,
-                CreatedAt = x.CreatedAt,
-            })
+            .Select(MapToSalaryBaseData)
            .ToListAsync(cancellationToken);

+    private static SalaryBaseData MapToSalaryBaseData(/* appropriate input type here */)
+    {
+        return new SalaryBaseData
+        {
+            ProfessionId = /* source */.ProfessionId,
+            Grade = /* source */.Grade.Value,
+            Value = /* source */.Value,
+            CreatedAt = /* source */.CreatedAt,
+        };
+    }
src/Infrastructure/Services/OpenAi/IOpenAiService.cs (3)

3-9: Improve interface design clarity and documentation

While the interface is concise, there are several areas for improvement:

  1. Method naming could be more descriptive:

    • GetBearer() would be clearer as GenerateJwtToken() or GetAuthorizationToken()
    • GetAnalysisAsync() doesn't indicate what is being analyzed
  2. The GetAnalysisAsync() method lacks parameters that would explain what data is being analyzed or what kind of analysis is performed

  3. Consider adding XML documentation comments to explain the purpose and behavior of these methods

  4. For consistency, if GetBearer() performs any I/O operations, consider making it asynchronous

 namespace Infrastructure.Services.OpenAi;
 
+/// <summary>
+/// Provides functionality for interacting with OpenAI services
+/// </summary>
 public interface IOpenAiService
 {
+    /// <summary>
+    /// Generates a JWT bearer token for authenticating with the OpenAI service
+    /// </summary>
+    /// <returns>A JWT token string</returns>
     string GetBearer();
 
+    /// <summary>
+    /// Retrieves AI analysis for salary data
+    /// </summary>
+    /// <param name="cancellationToken">Optional cancellation token</param>
+    /// <returns>Analysis results as a string</returns>
     Task<string> GetAnalysisAsync(
         CancellationToken cancellationToken = default);
 }

5-5: Consider type safety for authentication token

The GetBearer() method returns a simple string. Consider creating a dedicated type to represent the JWT token for better type safety and to make the token's purpose more explicit in the code.


7-8: Consider structured return type for analysis results

The GetAnalysisAsync() method returns a simple string for the analysis results. Consider creating a dedicated response type that provides structure to the analysis data, which would improve type safety and make the structure of the analysis results explicit.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 25e5156 and d132ddd.

📒 Files selected for processing (43)
  • src/.env.example (1 hunks)
  • src/Domain/Entities/StatData/SalaryBaseData.cs (1 hunks)
  • src/Domain/Entities/StatData/SalaryGraveValue.cs (0 hunks)
  • src/Domain/Entities/StatData/StatDataCacheItemSalaryData.cs (2 hunks)
  • src/Domain/ValueObjects/ISalariesChartQueryParams.cs (2 hunks)
  • src/Infrastructure/Currencies/Contracts/ICurrencyService.cs (1 hunks)
  • src/Infrastructure/Currencies/CurrencyService.cs (1 hunks)
  • src/Infrastructure/Infrastructure.csproj (1 hunks)
  • src/Infrastructure/Jwt/TechinterviewJwtTokenGenerator.cs (1 hunks)
  • src/Infrastructure/Salaries/ChartPageLink.cs (1 hunks)
  • src/Infrastructure/Salaries/SalariesForChartQuery.cs (3 hunks)
  • src/Infrastructure/Services/OpenAi/IOpenAiService.cs (1 hunks)
  • src/Infrastructure/Services/OpenAi/OpenAiService.cs (1 hunks)
  • src/InfrastructureTests/Jwt/TechinterviewJwtTokenGeneratorTests.cs (1 hunks)
  • src/TestUtils/Mocks/CurrenciesServiceFake.cs (1 hunks)
  • src/Web.Api.Tests/Features/Telegram/ProcessMessage/UserCommands/QaAndTestersTelegramBotUserCommandParametersTests.cs (1 hunks)
  • src/Web.Api/Features/BackgroundJobs/SalarySubscriptionData.cs (1 hunks)
  • src/Web.Api/Features/BackgroundJobs/StatDataChangeSubscriptionCalculateJob.cs (5 hunks)
  • src/Web.Api/Features/Historical/GetSurveyHistoricalChart/GetSurveyHistoricalChartHandler.cs (1 hunks)
  • src/Web.Api/Features/Historical/HistoricalChartsController.cs (2 hunks)
  • src/Web.Api/Features/Salaries/Admin/SalariesAdminQuery.cs (1 hunks)
  • src/Web.Api/Features/Salaries/GetSalaries/GetSalariesPaginatedQuery.cs (2 hunks)
  • src/Web.Api/Features/Salaries/GetSalariesChart/Charts/SalariesChartQueryParams.cs (2 hunks)
  • src/Web.Api/Features/Salaries/SalariesChartQueryParamsBase.cs (2 hunks)
  • src/Web.Api/Features/Salaries/SalariesController.cs (2 hunks)
  • src/Web.Api/Features/SalariesChartShare/GetChartSharePage/GetChartSharePageHandler.cs (1 hunks)
  • src/Web.Api/Features/Subscribtions/GetOpenAiReport/GetOpenAiReportHandler.cs (1 hunks)
  • src/Web.Api/Features/Subscribtions/GetOpenAiReport/GetOpenAiReportQuery.cs (1 hunks)
  • src/Web.Api/Features/Subscribtions/GetOpenAiReportAnalysis/GetOpenAiReportAnalysisHandler.cs (1 hunks)
  • src/Web.Api/Features/Subscribtions/GetOpenAiReportAnalysis/GetOpenAiReportAnalysisQuery.cs (1 hunks)
  • src/Web.Api/Features/Subscribtions/GetOpenAiReportAnalysis/GetOpenAiReportAnalysisResponse.cs (1 hunks)
  • src/Web.Api/Features/Subscribtions/TelegramSubscriptionsController.cs (2 hunks)
  • src/Web.Api/Features/Telegram/ProcessMessage/ProcessTelegramMessageHandler.cs (2 hunks)
  • src/Web.Api/Features/Telegram/ProcessMessage/UserCommands/TelegramBotUserCommandParameters.cs (2 hunks)
  • src/Web.Api/Integrations/OpenAiAnalysisIntegration/OpenAiBodyReport.cs (1 hunks)
  • src/Web.Api/Integrations/OpenAiAnalysisIntegration/OpenAiBodyReportMetadata.cs (1 hunks)
  • src/Web.Api/Integrations/OpenAiAnalysisIntegration/OpenAiBodyReportRole.cs (1 hunks)
  • src/Web.Api/Integrations/OpenAiAnalysisIntegration/OpenAiBodyReportRoleHistoricalDataItem.cs (1 hunks)
  • src/Web.Api/Integrations/OpenAiAnalysisIntegration/OpenAiBodyReportRoleSalaryData.cs (1 hunks)
  • src/Web.Api/Properties/launchSettings.json (1 hunks)
  • src/Web.Api/Setup/ServiceRegistration.cs (2 hunks)
  • src/Web.Api/Views/ChartSharePageViewModel.cs (1 hunks)
  • src/Web.Api/appsettings.json (1 hunks)
💤 Files with no reviewable changes (1)
  • src/Domain/Entities/StatData/SalaryGraveValue.cs
🧰 Additional context used
🧬 Code Graph Analysis (9)
src/Infrastructure/Services/OpenAi/IOpenAiService.cs (1)
src/Infrastructure/Services/OpenAi/OpenAiService.cs (1)
  • GetBearer (24-28)
src/Infrastructure/Currencies/Contracts/ICurrencyService.cs (1)
src/Infrastructure/Currencies/Contracts/CurrencyContent.cs (3)
  • CurrencyContent (11-18)
  • CurrencyContent (20-35)
  • CurrencyContent (37-45)
src/Web.Api/Features/Subscribtions/GetOpenAiReportAnalysis/GetOpenAiReportAnalysisResponse.cs (1)
src/Web.Api/Integrations/OpenAiAnalysisIntegration/OpenAiBodyReport.cs (1)
  • OpenAiBodyReport (13-35)
src/Domain/Entities/StatData/SalaryBaseData.cs (1)
src/Web.Api/Features/Import/ImportKolesaCsv/KolesaDeveloperCsvLine.cs (1)
  • DeveloperGrade (45-55)
src/Web.Api/Features/Telegram/ProcessMessage/UserCommands/TelegramBotUserCommandParameters.cs (4)
src/Web.Api/Features/Import/ImportKolesaCsv/KolesaDeveloperCsvLine.cs (1)
  • DeveloperGrade (45-55)
src/Domain/Entities/Salaries/Profession.cs (1)
  • List (39-45)
src/Web.Api/Features/Telegram/ProcessMessage/UserCommands/ProductManagersTelegramBotUserCommandParameters.cs (1)
  • List (22-36)
src/Web.Api/Features/Telegram/ProcessMessage/UserCommands/QaAndTestersTelegramBotUserCommandParameters.cs (1)
  • List (34-49)
src/Web.Api/Features/Subscribtions/GetOpenAiReportAnalysis/GetOpenAiReportAnalysisQuery.cs (1)
src/Web.Api/Features/Subscribtions/GetOpenAiReportAnalysis/GetOpenAiReportAnalysisResponse.cs (1)
  • GetOpenAiReportAnalysisResponse (7-15)
src/InfrastructureTests/Jwt/TechinterviewJwtTokenGeneratorTests.cs (1)
src/Infrastructure/Jwt/TechinterviewJwtTokenGenerator.cs (2)
  • TechinterviewJwtTokenGenerator (14-18)
  • ToString (20-23)
src/Web.Api/Features/Subscribtions/GetOpenAiReport/GetOpenAiReportQuery.cs (1)
src/Web.Api/Integrations/OpenAiAnalysisIntegration/OpenAiBodyReport.cs (1)
  • OpenAiBodyReport (13-35)
src/Web.Api/Features/Subscribtions/GetOpenAiReport/GetOpenAiReportHandler.cs (3)
src/Web.Api/Features/Subscribtions/GetOpenAiReport/GetOpenAiReportQuery.cs (1)
  • GetOpenAiReportQuery (9-13)
src/Web.Api/Integrations/OpenAiAnalysisIntegration/OpenAiBodyReport.cs (1)
  • OpenAiBodyReport (13-35)
src/Web.Api/Features/BackgroundJobs/SalarySubscriptionData.cs (3)
  • Task (58-85)
  • SalarySubscriptionData (34-56)
  • SalarySubscriptionData (87-96)
🔇 Additional comments (43)
src/Infrastructure/Infrastructure.csproj (1)

24-24: Added JWT library to support token generation for OpenAI integration.

The addition of the System.IdentityModel.Tokens.Jwt package aligns with the PR objective of adding JWT token generation capabilities. This package provides the necessary functionality for creating and managing JWT tokens that will be used to authenticate requests to the OpenAI API.

src/.env.example (1)

4-4: LGTM: Added OpenAI API secret environment variable.

The addition of the OpenAiApi__Secret environment variable is appropriate for the OpenAI integration. This follows good security practices by storing API credentials in environment variables instead of hardcoding them in the application.

src/Web.Api/Features/SalariesChartShare/GetChartSharePage/GetChartSharePageHandler.cs (1)

41-41: Property renaming for consistency.

The change from ProfessionsToInclude to SelectedProfessionIds is consistent with a broader renaming effort across the codebase. This improves naming clarity and consistency.

Consider also renaming the local variable professionsToInclude to match the new property name for better readability:

-var professionsToInclude = new DeveloperProfessionsCollection(request.SelectedProfessionIds).ToList();
+var selectedProfessionIds = new DeveloperProfessionsCollection(request.SelectedProfessionIds).ToList();

And updating the usage:

-SelectedProfessionIds = professionsToInclude,
+SelectedProfessionIds = selectedProfessionIds,

Also applies to: 46-46

src/Domain/ValueObjects/ISalariesChartQueryParams.cs (2)

12-12: Improved property naming for better clarity.

Renaming from ProfessionsToInclude to SelectedProfessionIds provides better clarity about what the property contains (IDs of selected professions rather than profession objects).


25-25: Correctly updated HasAnyFilter logic to match property rename.

The property rename was consistently applied in the HasAnyFilter expression, maintaining the same logical behavior.

src/Web.Api/Features/Historical/GetSurveyHistoricalChart/GetSurveyHistoricalChartHandler.cs (1)

104-108: Consistent property renaming in filter logic.

The filter conditions correctly use the renamed property SelectedProfessionIds both for checking the count and filtering profession IDs, maintaining the original behavior.

src/Web.Api.Tests/Features/Telegram/ProcessMessage/UserCommands/QaAndTestersTelegramBotUserCommandParametersTests.cs (1)

24-27: Updated test assertions to match renamed property.

Test assertions now correctly reference the renamed SelectedProfessionIds property, ensuring the tests validate the same behavior as before.

src/Web.Api/Features/Salaries/Admin/SalariesAdminQuery.cs (1)

61-61: Updated query filter logic to use renamed property.

The filter clause in ApplyFilters method now correctly uses SelectedProfessionIds for consistency with the interface changes, maintaining the same filtering behavior.

src/Infrastructure/Salaries/ChartPageLink.cs (1)

48-50: Property name refactoring looks good.

The change from ProfessionsToInclude to SelectedProfessionIds is consistent with the broader system-wide renaming mentioned in the summary. This standardizes the naming convention for profession filters throughout the application.

src/Web.Api/Setup/ServiceRegistration.cs (2)

30-41: Good refactoring to method chaining style.

The refactoring to method chaining makes the service registration more concise and readable while maintaining the same functionality.


37-37: OpenAI service registration aligns with PR objectives.

The registration of IOpenAiService with implementation OpenAiService properly integrates the new OpenAI functionality mentioned in the PR objectives about JWT token generation.

src/Infrastructure/Currencies/CurrencyService.cs (1)

32-32: Method renaming improves clarity.

Renaming GetCurrencyAsync to GetCurrencyOrNullAsync makes the API more self-documenting by explicitly indicating that the method may return null when a currency is not found.

src/TestUtils/Mocks/CurrenciesServiceFake.cs (1)

41-41: Consistent method renaming in test mock.

The mock implementation correctly mirrors the interface change from GetCurrencyAsync to GetCurrencyOrNullAsync, maintaining consistency across the codebase.

src/Web.Api/Features/Salaries/SalariesChartQueryParamsBase.cs (1)

18-18: Consistent property renaming across the codebase.

The renaming of ProfessionsToInclude to SelectedProfessionIds is consistently applied here as well, maintaining the improved naming convention observed in the first file. This consistency across related classes is excellent for code maintainability and readability. The external API contract is preserved through the unchanged [FromQuery(Name = "profsInclude")] attribute.

Also applies to: 36-36, 41-41

src/Web.Api/Features/Salaries/GetSalariesChart/Charts/SalariesChartQueryParams.cs (3)

18-18: Good rename for improved clarity

The change from ProfessionsToInclude to SelectedProfessionIds improves naming clarity while maintaining API compatibility through the unchanged profsInclude query parameter name.


35-36: Consistent property renaming

The update in HasAnyFilter property is consistent with the renamed property.


41-41: Consistent property renaming in method

The GetKeyPostfix method correctly uses the renamed property.

src/Domain/Entities/StatData/SalaryBaseData.cs (1)

6-15: Well-designed record for salary data

This new record is well-structured with appropriate property types:

  • Nullable ProfessionId allows for cases without an associated profession
  • Grade uses the appropriate enum type
  • Value as double is suitable for salary amounts
  • CreatedAt using DateTimeOffset is appropriate for timestamps

The use of init-only properties promotes immutability, which is a good practice for data models.

src/Web.Api/Features/Historical/HistoricalChartsController.cs (2)

35-35: Consistent property renaming

The rename from ProfessionsToInclude to SelectedProfessionIds is consistent with changes in other files while maintaining the same functionality.


56-56: Consistent property renaming

The rename from ProfessionsToInclude to SelectedProfessionIds is consistent with changes in other files while maintaining the same functionality.

src/Web.Api/Views/ChartSharePageViewModel.cs (2)

27-27: Consistent property reference update

The condition now correctly references the renamed property from requestParams.


30-30: Consistent property reference in URL generation

The URL parameter generation now correctly uses the renamed property while maintaining the same URL parameter name for backward compatibility.

src/Web.Api/Features/Subscribtions/GetOpenAiReport/GetOpenAiReportQuery.cs (1)

7-16: LGTM - Simple, focused MediatR query record

The record is well-structured with an immutable property and follows good practice for MediatR requests. The query properly encapsulates the single parameter needed to fetch an OpenAI report for a specific subscription.

src/Web.Api/Features/Telegram/ProcessMessage/UserCommands/TelegramBotUserCommandParameters.cs (3)

34-36: LGTM - Property initialization updated correctly

The initialization of the SelectedProfessionIds property has been properly updated to match the renamed property.


41-41: LGTM - Property renamed for consistency

The property has been renamed from ProfessionsToInclude to SelectedProfessionIds to maintain consistency with the naming conventions used across the codebase.


58-58: LGTM - Method updated to use renamed property

The GetKeyPostfix method has been properly updated to use the renamed SelectedProfessionIds property.

src/Web.Api/Features/Salaries/SalariesController.cs (2)

72-72: Property name consistently updated

The property name has been renamed from ProfessionsToInclude to SelectedProfessionIds for improved clarity and consistency with the rest of the codebase.


93-93: Property name consistently updated

The property name has been renamed from ProfessionsToInclude to SelectedProfessionIds for improved clarity and consistency with the rest of the codebase.

src/Web.Api/Features/Subscribtions/GetOpenAiReportAnalysis/GetOpenAiReportAnalysisQuery.cs (1)

6-15: Well-structured query record

The query record follows best practices:

  • Properly implements MediatR's IRequest interface
  • Uses immutable design with a constructor-initialized property
  • Clear and descriptive naming
src/Web.Api/Features/Telegram/ProcessMessage/ProcessTelegramMessageHandler.cs (3)

301-307: Updated data model with better structure

The code now uses SalaryBaseData instead of the previous data type, adding important properties like ProfessionId and CreatedAt. This aligns with the domain model changes in the rest of the codebase.


322-324: Improved null handling in currency service

Method name changed from GetCurrencyAsync to GetCurrencyOrNullAsync, which better reflects that the method can return null, improving code readability and intent clarity.


328-328: Using centralized grade groups collection

The code now uses the static collection StatDataCacheItemSalaryData.GradeGroupsForRegularStats instead of a locally defined collection. This improves consistency and maintainability.

src/Domain/Entities/StatData/StatDataCacheItemSalaryData.cs (3)

21-28: Constructor signature updated to match domain model changes

The constructor now accepts List<SalaryBaseData> instead of the previous SalaryGraveValue type, aligning with the broader domain model refactoring described in the summary. The extraction of salary values for calculation is done efficiently.


32-33: Added min/max salary calculations

New properties to track minimum and maximum salary values have been added with proper null handling when the list is empty.


59-61: New properties for min/max salary values

The new nullable properties properly complement the existing salary statistics and will provide additional insights for data analysis.

src/Web.Api/Integrations/OpenAiAnalysisIntegration/OpenAiBodyReportRoleSalaryData.cs (1)

8-43: Well-structured salary data record for OpenAI reporting

The record is well-implemented with:

  • Proper empty list handling
  • Clear separation of input processing and property storage
  • Appropriate usage of nullable types for min/max values
  • Consistent extraction of salary values using the same approach as in StatDataCacheItemSalaryData

This seems to be part of a new OpenAI reporting feature and follows good practices for data encapsulation.

src/Web.Api/Features/Subscribtions/TelegramSubscriptionsController.cs (2)

12-12: Added using statement for OpenAI integration

The import has been correctly added to support the new OpenAI analysis functionality.


88-97: New endpoint for OpenAI analysis looks good

The implementation follows the controller's existing patterns for endpoint structure and mediator usage.

src/Infrastructure/Salaries/SalariesForChartQuery.cs (2)

15-15: LGTM: Good use of constants to avoid magic numbers

Creating a named constant for the previously hardcoded value 18 is a good practice that improves code readability and maintainability.


74-74: LGTM: Consistent use of the new constant

Good job on using the newly defined MonthsToShow constant here instead of the hardcoded value.

src/Web.Api/Features/Salaries/GetSalaries/GetSalariesPaginatedQuery.cs (2)

20-20: Property name improved for better semantic clarity

Renaming from ProfessionsToInclude to SelectedProfessionIds better reflects that this property contains IDs rather than profession objects. Good change that maintains API compatibility by keeping the external query parameter name as profsInclude.


38-38: References consistently updated

All references to the renamed property are consistently updated throughout the class, including the HasAnyFilter property and the GetKeyPostfix() method.

Also applies to: 43-43

src/Infrastructure/Currencies/Contracts/ICurrencyService.cs (1)

7-7: Method name improved to clarify nullability

Renaming from GetCurrencyAsync to GetCurrencyOrNullAsync explicitly indicates that the method may return null, making the nullability contract clearer to callers. This helps prevent potential null reference exceptions by encouraging proper null checking.

"ConnectionStrings__Elasticsearch": "http://localhost:9200",
"GenerateStubData": "true"
"GenerateStubData": "true",
"OpenAiApi__Secret": "my-secret"
Copy link
Contributor

@coderabbitai coderabbitai bot May 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove hardcoded secret value from launchSettings.json.

While this is a development configuration, including even placeholder secrets in files that are committed to version control is a security risk. If the repository were ever to become public or be compromised, this would expose the secret pattern.

Consider replacing the literal value with a placeholder notation that clearly indicates a value needs to be supplied:

-"OpenAiApi__Secret": "my-secret"
+"OpenAiApi__Secret": "REPLACE_WITH_ACTUAL_SECRET"

Alternatively, consider using user secrets for development environments:

dotnet user-secrets set "OpenAiApi:Secret" "your-actual-secret"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's ok, I am going to replace it in Production.json file

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maximgorbatyuk Understood! As long as you're planning to handle the secret properly in the production environment, that's what matters most. Just remember that the development file is still typically committed to version control, but it's your call on how to manage development secrets.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

Comment on lines +23 to +26
"OpenAiApi": {
"Url": "https://wf.belyaev.live/webhook/e69fa36a-adff-410c-8cd0-04ec833d849e",
"Secret": "my-secret"
},
Copy link
Contributor

@coderabbitai coderabbitai bot May 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Security concern: Hardcoded API configuration should be moved to environment-specific settings.

The OpenAI API configuration contains two issues:

  1. A hardcoded secret value ("my-secret") should never be included in appsettings.json as it will be committed to source control.
  2. The hardcoded URL contains what appears to be a specific instance identifier (e69fa36a-adff-410c-8cd0-04ec833d849e), which may need to change between environments.

Consider using placeholder values and loading the actual values from environment variables or user secrets:

"OpenAiApi": {
-  "Url": "https://wf.belyaev.live/webhook/e69fa36a-adff-410c-8cd0-04ec833d849e",
-  "Secret": "my-secret"
+  "Url": "",
+  "Secret": ""
},

This approach ensures sensitive configuration doesn't get committed to source control and provides flexibility for different environments.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's ok

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maximgorbatyuk Understood. I'll respect your decision to keep the current configuration with the hardcoded values.

Comment on lines +5 to +22
public record GetOpenAiReportAnalysisResponse
{
public GetOpenAiReportAnalysisResponse(
string analysis,
OpenAiBodyReport report,
string bearer)
{
Analysis = analysis;
Report = report;
Bearer = bearer;
}

public string Analysis { get; }

public OpenAiBodyReport Report { get; }

public string Bearer { get; }
} No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Consider security implications of including the Bearer token in the response body

While the response structure is well-designed with immutable properties, exposing the JWT bearer token in the response body might present security risks if the client isn't properly secured. Bearer tokens are typically transmitted via HTTP headers.

Consider either:

  1. Moving the token to headers-only authentication
  2. Implementing token expiration with a short lifespan
  3. Adding documentation to ensure proper client-side token handling

🏁 Script executed:

#!/bin/bash
# Locate all references to GetOpenAiReportAnalysisResponse in the codebase
rg "GetOpenAiReportAnalysisResponse" -n

Length of output: 1029


🏁 Script executed:

#!/bin/bash
# Display the handler to inspect how the Bearer token is used in the response
sed -n '1,200p' src/Web.Api/Features/Subscribtions/GetOpenAiReportAnalysis/GetOpenAiReportAnalysisHandler.cs

Length of output: 2338


🏁 Script executed:

#!/bin/bash
# Locate the IOpenAiService interface definition
rg "interface IOpenAiService" -n src

Length of output: 125


🏁 Script executed:

#!/bin/bash
# Display the IOpenAiService interface to inspect its methods
sed -n '1,200p' src/Infrastructure/Services/OpenAi/IOpenAiService.cs

# Find where GetBearer is implemented
rg "GetBearer" -n src/Infrastructure/Services/OpenAi

Length of output: 605


Remove exposed OpenAI API key from response payload

The Bearer property on GetOpenAiReportAnalysisResponse contains your internal OpenAI API key. Returning it in the JSON response leaks a secret and poses a critical security risk.

Please remove the Bearer token from the response object and keep it strictly server-side. If clients need to authenticate future calls, implement one of the following instead:

  • Proxy their requests through your API (so the client never sees the OpenAI key).
  • Issue short-lived JWTs or OAuth tokens scoped for the client’s operations.
  • If you must send a token, place it in a secure HTTP header (not in the JSON body), with proper CORS and header protections.

Locations to update:

  • src/Web.Api/Features/Subscribtions/GetOpenAiReportAnalysis/GetOpenAiReportAnalysisResponse.cs – remove the Bearer constructor parameter, property, and related documentation.
  • src/Web.Api/Features/Subscribtions/GetOpenAiReportAnalysis/GetOpenAiReportAnalysisHandler.cs – stop passing _openApiService.GetBearer() into the response.
  • Review any client-side code or documentation that expects Bearer in the response.

Comment on lines +8 to +44
public record TechinterviewJwtTokenGenerator
{
private readonly string _secretKey;

private string _result;

public TechinterviewJwtTokenGenerator(
string secretKey)
{
_secretKey = secretKey;
}

public override string ToString()
{
return _result ??= GenerateInternal();
}

private string GenerateInternal()
{
var securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_secretKey));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);

var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Name, "TechInterview.space Bot"),
new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64)
};

var token = new JwtSecurityToken(
claims: claims,
expires: DateTime.UtcNow.AddHours(1),
signingCredentials: credentials);

return new JwtSecurityTokenHandler().WriteToken(token);
}
} No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

JWT token generator uses unconventional design patterns

While the token generation logic works correctly, there are several design concerns:

  1. Using a record with mutable state (_result) is unconventional - records are typically used for immutable data
  2. The token is generated via ToString() which is not intuitive for JWT token generation
  3. The generated token has a fixed 1-hour expiration but is cached indefinitely, potentially causing authentication failures if used after expiration
  4. No validation is performed on the secret key to ensure it meets security requirements

Consider these improvements:

-public record TechinterviewJwtTokenGenerator
+public class TechinterviewJwtTokenGenerator
 {
     private readonly string _secretKey;
-
-    private string _result;
+    private string _cachedToken;
+    private DateTime _tokenExpiration;

     public TechinterviewJwtTokenGenerator(
         string secretKey)
     {
+        if (string.IsNullOrWhiteSpace(secretKey) || secretKey.Length < 32)
+        {
+            throw new ArgumentException("Secret key must be at least 32 characters long for security", nameof(secretKey));
+        }
         _secretKey = secretKey;
     }

-    public override string ToString()
+    public string GenerateToken()
     {
-        return _result ??= GenerateInternal();
+        // Regenerate if token doesn't exist or is close to expiration (10 min buffer)
+        if (_cachedToken == null || DateTime.UtcNow.AddMinutes(10) > _tokenExpiration)
+        {
+            _cachedToken = GenerateInternal();
+            _tokenExpiration = DateTime.UtcNow.AddHours(1);
+        }
+        return _cachedToken;
     }

     private string GenerateInternal()
     {
         var securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_secretKey));
         var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);

         var claims = new List<Claim>
         {
             new Claim(JwtRegisteredClaimNames.Sub, Guid.NewGuid().ToString()),
             new Claim(JwtRegisteredClaimNames.Name, "TechInterview.space Bot"),
             new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64)
         };

         var token = new JwtSecurityToken(
             claims: claims,
             expires: DateTime.UtcNow.AddHours(1),
             signingCredentials: credentials);

         return new JwtSecurityTokenHandler().WriteToken(token);
     }
+
+    // Keep ToString override for backward compatibility
+    public override string ToString()
+    {
+        return GenerateToken();
+    }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public record TechinterviewJwtTokenGenerator
{
private readonly string _secretKey;
private string _result;
public TechinterviewJwtTokenGenerator(
string secretKey)
{
_secretKey = secretKey;
}
public override string ToString()
{
return _result ??= GenerateInternal();
}
private string GenerateInternal()
{
var securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_secretKey));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Name, "TechInterview.space Bot"),
new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64)
};
var token = new JwtSecurityToken(
claims: claims,
expires: DateTime.UtcNow.AddHours(1),
signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
public class TechinterviewJwtTokenGenerator
{
private readonly string _secretKey;
private string _cachedToken;
private DateTime _tokenExpiration;
public TechinterviewJwtTokenGenerator(
string secretKey)
{
if (string.IsNullOrWhiteSpace(secretKey) || secretKey.Length < 32)
{
throw new ArgumentException("Secret key must be at least 32 characters long for security", nameof(secretKey));
}
_secretKey = secretKey;
}
public string GenerateToken()
{
// Regenerate if token doesn't exist or is close to expiration (10 min buffer)
if (_cachedToken == null || DateTime.UtcNow.AddMinutes(10) > _tokenExpiration)
{
_cachedToken = GenerateInternal();
_tokenExpiration = DateTime.UtcNow.AddHours(1);
}
return _cachedToken;
}
private string GenerateInternal()
{
var securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_secretKey));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Name, "TechInterview.space Bot"),
new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64)
};
var token = new JwtSecurityToken(
claims: claims,
expires: DateTime.UtcNow.AddHours(1),
signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
// Keep ToString override for backward compatibility
public override string ToString()
{
return GenerateToken();
}
}

Comment on lines +42 to +48
var result = new OpenAiBodyReport(
new SalarySubscriptionData(
allProfessions,
subscription,
_context,
DateTimeOffset.UtcNow),
Currency.KZT);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Initialize SalarySubscriptionData before passing it to OpenAiBodyReport.

OpenAiBodyReport calls IsInitializedOrFail() on the SalarySubscriptionData instance; the latter returns false unless InitializeAsync() has been executed.
As it stands, this handler will reliably throw InvalidOperationException at runtime.

-        var result = new OpenAiBodyReport(
-            new SalarySubscriptionData(
-                allProfessions,
-                subscription,
-                _context,
-                DateTimeOffset.UtcNow),
-            Currency.KZT);
+        var subscriptionData = await new SalarySubscriptionData(
+                allProfessions,
+                subscription,
+                _context,
+                DateTimeOffset.UtcNow)
+            .InitializeAsync(cancellationToken);
+
+        var result = new OpenAiBodyReport(
+            subscriptionData,
+            Currency.KZT);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var result = new OpenAiBodyReport(
new SalarySubscriptionData(
allProfessions,
subscription,
_context,
DateTimeOffset.UtcNow),
Currency.KZT);
var subscriptionData = await new SalarySubscriptionData(
allProfessions,
subscription,
_context,
DateTimeOffset.UtcNow)
.InitializeAsync(cancellationToken);
var result = new OpenAiBodyReport(
subscriptionData,
Currency.KZT);

Comment on lines +54 to +57
return new GetOpenAiReportAnalysisResponse(
await _openApiService.GetAnalysisAsync(cancellationToken),
report,
_openApiService.GetBearer());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for OpenAI service calls.

The calls to _openApiService.GetAnalysisAsync() and _openApiService.GetBearer() lack error handling. If these operations fail, the exception would propagate to the caller without proper context.

        return new GetOpenAiReportAnalysisResponse(
-            await _openApiService.GetAnalysisAsync(cancellationToken),
+            await _openApiService.GetAnalysisAsync(cancellationToken)
+                .ConfigureAwait(false),
            report,
-            _openApiService.GetBearer());
+            _openApiService.GetBearer());

Consider implementing a try-catch block to handle potential exceptions:

try
{
    var analysis = await _openApiService.GetAnalysisAsync(cancellationToken)
        .ConfigureAwait(false);
    var bearer = _openApiService.GetBearer();
    
    return new GetOpenAiReportAnalysisResponse(
        analysis,
        report,
        bearer);
}
catch (Exception ex)
{
    // Log the exception
    throw new ApplicationException("Failed to retrieve OpenAI analysis", ex);
}

Comment on lines +34 to +39
public SalarySubscriptionData(
List<Profession> allProfessions,
StatDataChangeSubscription subscription,
DatabaseContext context,
DateTimeOffset now)
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add null validation for constructor parameters.

The constructor doesn't check for null parameters, which could lead to NullReferenceExceptions later in the code.

    public SalarySubscriptionData(
        List<Profession> allProfessions,
        StatDataChangeSubscription subscription,
        DatabaseContext context,
        DateTimeOffset now)
    {
+        _subscription = subscription ?? throw new ArgumentNullException(nameof(subscription));
+        _context = context ?? throw new ArgumentNullException(nameof(context));
+        allProfessions ??= new List<Profession>();
-        _subscription = subscription;
-        _context = context;
        _isInitianlized = false;

Committable suggestion skipped: line range outside the PR's diff.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
.github/workflows/deploy.yml (1)

30-30: Use a safer sed delimiter to handle potential “/” characters in secrets.

Right now you’re using / as the sed delimiter, which will break if secrets.OPEN_AI_SECRET contains a /. Consider switching to a different delimiter (e.g. |) to avoid this pitfall:

-sed -i 's/"__OPEN_AI_SECRET",/"${{ secrets.OPEN_AI_SECRET }}",/g' ${{env.PRODUCTION_SETTINGS_FILE}}
+sed -i 's|"__OPEN_AI_SECRET",|"${{ secrets.OPEN_AI_SECRET }}",|g' ${{env.PRODUCTION_SETTINGS_FILE}}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d132ddd and 2ecafbb.

📒 Files selected for processing (2)
  • .github/workflows/deploy.yml (1 hunks)
  • src/Web.Api/appsettings.Production.json (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/Web.Api/appsettings.Production.json

sed -i 's/"__S3_BUCKET_NAME",/"${{ vars.S3_BUCKET_NAME }}",/g' ${{env.PRODUCTION_SETTINGS_FILE}}
sed -i 's/"__TELEGRAM_BOT_KEY",/"${{ secrets.TELEGRAM_BOT_KEY }}",/g' ${{env.PRODUCTION_SETTINGS_FILE}}
sed -i 's/"__SENDGRID_API_KEY",/"${{ secrets.SENDGRID_API_KEY }}",/g' ${{env.PRODUCTION_SETTINGS_FILE}}
sed -i 's/"__OPEN_AI_SECRET",/"${{ secrets.OPEN_AI_SECRET }}",/g' ${{env.PRODUCTION_SETTINGS_FILE}}
Copy link
Contributor

@coderabbitai coderabbitai bot May 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Missing JWT secret injection for production settings.

You’ve added JWT generation in this PR, but there’s no replacement step for a __JWT_SECRET placeholder in appsettings.Production.json. Without it, the app will continue to see the literal placeholder and fail at runtime. Add something like:

sed -i 's/"__JWT_SECRET",/"${{ secrets.JWT_SECRET }}",/g' ${{env.PRODUCTION_SETTINGS_FILE}}

right after the other sed commands.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JWT is generated with secret that in settings now. That's ok

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maximgorbatyuk I understand now. Since you're using the OpenAI API secret as the JWT secret, and that replacement is already handled in line 28, no additional secret injection is needed. Thanks for the clarification!

@maximgorbatyuk maximgorbatyuk merged commit 3fa648d into main May 3, 2025
3 of 4 checks passed
@maximgorbatyuk maximgorbatyuk deleted the ai-integration branch May 3, 2025 07:18
@coderabbitai coderabbitai bot mentioned this pull request Jun 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants